mobile/android/base/home/HomeConfig.java

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/base/home/HomeConfig.java	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1563 @@
     1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
     1.5 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +package org.mozilla.gecko.home;
    1.10 +
    1.11 +import org.mozilla.gecko.GeckoAppShell;
    1.12 +import org.mozilla.gecko.GeckoEvent;
    1.13 +import org.mozilla.gecko.R;
    1.14 +import org.mozilla.gecko.util.ThreadUtils;
    1.15 +
    1.16 +import org.json.JSONArray;
    1.17 +import org.json.JSONException;
    1.18 +import org.json.JSONObject;
    1.19 +
    1.20 +import android.content.Context;
    1.21 +import android.os.Parcel;
    1.22 +import android.os.Parcelable;
    1.23 +import android.text.TextUtils;
    1.24 +
    1.25 +import java.util.ArrayList;
    1.26 +import java.util.Collections;
    1.27 +import java.util.EnumSet;
    1.28 +import java.util.HashMap;
    1.29 +import java.util.Iterator;
    1.30 +import java.util.LinkedList;
    1.31 +import java.util.List;
    1.32 +import java.util.Map;
    1.33 +
    1.34 +public final class HomeConfig {
    1.35 +    /**
    1.36 +     * Used to determine what type of HomeFragment subclass to use when creating
    1.37 +     * a given panel. With the exception of DYNAMIC, all of these types correspond
    1.38 +     * to a default set of built-in panels. The DYNAMIC panel type is used by
    1.39 +     * third-party services to create panels with varying types of content.
    1.40 +     */
    1.41 +    public static enum PanelType implements Parcelable {
    1.42 +        TOP_SITES("top_sites", TopSitesPanel.class),
    1.43 +        BOOKMARKS("bookmarks", BookmarksPanel.class),
    1.44 +        HISTORY("history", HistoryPanel.class),
    1.45 +        READING_LIST("reading_list", ReadingListPanel.class),
    1.46 +        DYNAMIC("dynamic", DynamicPanel.class);
    1.47 +
    1.48 +        private final String mId;
    1.49 +        private final Class<?> mPanelClass;
    1.50 +
    1.51 +        PanelType(String id, Class<?> panelClass) {
    1.52 +            mId = id;
    1.53 +            mPanelClass = panelClass;
    1.54 +        }
    1.55 +
    1.56 +        public static PanelType fromId(String id) {
    1.57 +            if (id == null) {
    1.58 +                throw new IllegalArgumentException("Could not convert null String to PanelType");
    1.59 +            }
    1.60 +
    1.61 +            for (PanelType panelType : PanelType.values()) {
    1.62 +                if (TextUtils.equals(panelType.mId, id.toLowerCase())) {
    1.63 +                    return panelType;
    1.64 +                }
    1.65 +            }
    1.66 +
    1.67 +            throw new IllegalArgumentException("Could not convert String id to PanelType");
    1.68 +        }
    1.69 +
    1.70 +        @Override
    1.71 +        public String toString() {
    1.72 +            return mId;
    1.73 +        }
    1.74 +
    1.75 +        public Class<?> getPanelClass() {
    1.76 +            return mPanelClass;
    1.77 +        }
    1.78 +
    1.79 +        @Override
    1.80 +        public int describeContents() {
    1.81 +            return 0;
    1.82 +        }
    1.83 +
    1.84 +        @Override
    1.85 +        public void writeToParcel(Parcel dest, int flags) {
    1.86 +            dest.writeInt(ordinal());
    1.87 +        }
    1.88 +
    1.89 +        public static final Creator<PanelType> CREATOR = new Creator<PanelType>() {
    1.90 +            @Override
    1.91 +            public PanelType createFromParcel(final Parcel source) {
    1.92 +                return PanelType.values()[source.readInt()];
    1.93 +            }
    1.94 +
    1.95 +            @Override
    1.96 +            public PanelType[] newArray(final int size) {
    1.97 +                return new PanelType[size];
    1.98 +            }
    1.99 +        };
   1.100 +    }
   1.101 +
   1.102 +    public static class PanelConfig implements Parcelable {
   1.103 +        private final PanelType mType;
   1.104 +        private final String mTitle;
   1.105 +        private final String mId;
   1.106 +        private final LayoutType mLayoutType;
   1.107 +        private final List<ViewConfig> mViews;
   1.108 +        private final AuthConfig mAuthConfig;
   1.109 +        private final EnumSet<Flags> mFlags;
   1.110 +
   1.111 +        private static final String JSON_KEY_TYPE = "type";
   1.112 +        private static final String JSON_KEY_TITLE = "title";
   1.113 +        private static final String JSON_KEY_ID = "id";
   1.114 +        private static final String JSON_KEY_LAYOUT = "layout";
   1.115 +        private static final String JSON_KEY_VIEWS = "views";
   1.116 +        private static final String JSON_KEY_AUTH_CONFIG = "authConfig";
   1.117 +        private static final String JSON_KEY_DEFAULT = "default";
   1.118 +        private static final String JSON_KEY_DISABLED = "disabled";
   1.119 +
   1.120 +        public enum Flags {
   1.121 +            DEFAULT_PANEL,
   1.122 +            DISABLED_PANEL
   1.123 +        }
   1.124 +
   1.125 +        public PanelConfig(JSONObject json) throws JSONException, IllegalArgumentException {
   1.126 +            final String panelType = json.optString(JSON_KEY_TYPE, null);
   1.127 +            if (TextUtils.isEmpty(panelType)) {
   1.128 +                mType = PanelType.DYNAMIC;
   1.129 +            } else {
   1.130 +                mType = PanelType.fromId(panelType);
   1.131 +            }
   1.132 +
   1.133 +            mTitle = json.getString(JSON_KEY_TITLE);
   1.134 +            mId = json.getString(JSON_KEY_ID);
   1.135 +
   1.136 +            final String layoutTypeId = json.optString(JSON_KEY_LAYOUT, null);
   1.137 +            if (layoutTypeId != null) {
   1.138 +                mLayoutType = LayoutType.fromId(layoutTypeId);
   1.139 +            } else {
   1.140 +                mLayoutType = null;
   1.141 +            }
   1.142 +
   1.143 +            final JSONArray jsonViews = json.optJSONArray(JSON_KEY_VIEWS);
   1.144 +            if (jsonViews != null) {
   1.145 +                mViews = new ArrayList<ViewConfig>();
   1.146 +
   1.147 +                final int viewCount = jsonViews.length();
   1.148 +                for (int i = 0; i < viewCount; i++) {
   1.149 +                    final JSONObject jsonViewConfig = (JSONObject) jsonViews.get(i);
   1.150 +                    final ViewConfig viewConfig = new ViewConfig(i, jsonViewConfig);
   1.151 +                    mViews.add(viewConfig);
   1.152 +                }
   1.153 +            } else {
   1.154 +                mViews = null;
   1.155 +            }
   1.156 +
   1.157 +            final JSONObject jsonAuthConfig = json.optJSONObject(JSON_KEY_AUTH_CONFIG);
   1.158 +            if (jsonAuthConfig != null) {
   1.159 +                mAuthConfig = new AuthConfig(jsonAuthConfig);
   1.160 +            } else {
   1.161 +                mAuthConfig = null;
   1.162 +            }
   1.163 +
   1.164 +            mFlags = EnumSet.noneOf(Flags.class);
   1.165 +
   1.166 +            if (json.optBoolean(JSON_KEY_DEFAULT, false)) {
   1.167 +                mFlags.add(Flags.DEFAULT_PANEL);
   1.168 +            }
   1.169 +
   1.170 +            if (json.optBoolean(JSON_KEY_DISABLED, false)) {
   1.171 +                mFlags.add(Flags.DISABLED_PANEL);
   1.172 +            }
   1.173 +
   1.174 +            validate();
   1.175 +        }
   1.176 +
   1.177 +        @SuppressWarnings("unchecked")
   1.178 +        public PanelConfig(Parcel in) {
   1.179 +            mType = (PanelType) in.readParcelable(getClass().getClassLoader());
   1.180 +            mTitle = in.readString();
   1.181 +            mId = in.readString();
   1.182 +            mLayoutType = (LayoutType) in.readParcelable(getClass().getClassLoader());
   1.183 +
   1.184 +            mViews = new ArrayList<ViewConfig>();
   1.185 +            in.readTypedList(mViews, ViewConfig.CREATOR);
   1.186 +
   1.187 +            mAuthConfig = (AuthConfig) in.readParcelable(getClass().getClassLoader());
   1.188 +
   1.189 +            mFlags = (EnumSet<Flags>) in.readSerializable();
   1.190 +
   1.191 +            validate();
   1.192 +        }
   1.193 +
   1.194 +        public PanelConfig(PanelConfig panelConfig) {
   1.195 +            mType = panelConfig.mType;
   1.196 +            mTitle = panelConfig.mTitle;
   1.197 +            mId = panelConfig.mId;
   1.198 +            mLayoutType = panelConfig.mLayoutType;
   1.199 +
   1.200 +            mViews = new ArrayList<ViewConfig>();
   1.201 +            List<ViewConfig> viewConfigs = panelConfig.mViews;
   1.202 +            if (viewConfigs != null) {
   1.203 +                for (ViewConfig viewConfig : viewConfigs) {
   1.204 +                    mViews.add(new ViewConfig(viewConfig));
   1.205 +                }
   1.206 +            }
   1.207 +
   1.208 +            mAuthConfig = panelConfig.mAuthConfig;
   1.209 +            mFlags = panelConfig.mFlags.clone();
   1.210 +
   1.211 +            validate();
   1.212 +        }
   1.213 +
   1.214 +        public PanelConfig(PanelType type, String title, String id) {
   1.215 +            this(type, title, id, EnumSet.noneOf(Flags.class));
   1.216 +        }
   1.217 +
   1.218 +        public PanelConfig(PanelType type, String title, String id, EnumSet<Flags> flags) {
   1.219 +            this(type, title, id, null, null, null, flags);
   1.220 +        }
   1.221 +
   1.222 +        public PanelConfig(PanelType type, String title, String id, LayoutType layoutType,
   1.223 +                List<ViewConfig> views, AuthConfig authConfig, EnumSet<Flags> flags) {
   1.224 +            mType = type;
   1.225 +            mTitle = title;
   1.226 +            mId = id;
   1.227 +            mLayoutType = layoutType;
   1.228 +            mViews = views;
   1.229 +            mAuthConfig = authConfig;
   1.230 +            mFlags = flags;
   1.231 +
   1.232 +            validate();
   1.233 +        }
   1.234 +
   1.235 +        private void validate() {
   1.236 +            if (mType == null) {
   1.237 +                throw new IllegalArgumentException("Can't create PanelConfig with null type");
   1.238 +            }
   1.239 +
   1.240 +            if (TextUtils.isEmpty(mTitle)) {
   1.241 +                throw new IllegalArgumentException("Can't create PanelConfig with empty title");
   1.242 +            }
   1.243 +
   1.244 +            if (TextUtils.isEmpty(mId)) {
   1.245 +                throw new IllegalArgumentException("Can't create PanelConfig with empty id");
   1.246 +            }
   1.247 +
   1.248 +            if (mLayoutType == null && mType == PanelType.DYNAMIC) {
   1.249 +                throw new IllegalArgumentException("Can't create a dynamic PanelConfig with null layout type");
   1.250 +            }
   1.251 +
   1.252 +            if ((mViews == null || mViews.size() == 0) && mType == PanelType.DYNAMIC) {
   1.253 +                throw new IllegalArgumentException("Can't create a dynamic PanelConfig with no views");
   1.254 +            }
   1.255 +
   1.256 +            if (mFlags == null) {
   1.257 +                throw new IllegalArgumentException("Can't create PanelConfig with null flags");
   1.258 +            }
   1.259 +        }
   1.260 +
   1.261 +        public PanelType getType() {
   1.262 +            return mType;
   1.263 +        }
   1.264 +
   1.265 +        public String getTitle() {
   1.266 +            return mTitle;
   1.267 +        }
   1.268 +
   1.269 +        public String getId() {
   1.270 +            return mId;
   1.271 +        }
   1.272 +
   1.273 +        public LayoutType getLayoutType() {
   1.274 +            return mLayoutType;
   1.275 +        }
   1.276 +
   1.277 +        public int getViewCount() {
   1.278 +            return (mViews != null ? mViews.size() : 0);
   1.279 +        }
   1.280 +
   1.281 +        public ViewConfig getViewAt(int index) {
   1.282 +            return (mViews != null ? mViews.get(index) : null);
   1.283 +        }
   1.284 +
   1.285 +        public boolean isDynamic() {
   1.286 +            return (mType == PanelType.DYNAMIC);
   1.287 +        }
   1.288 +
   1.289 +        public boolean isDefault() {
   1.290 +            return mFlags.contains(Flags.DEFAULT_PANEL);
   1.291 +        }
   1.292 +
   1.293 +        private void setIsDefault(boolean isDefault) {
   1.294 +            if (isDefault) {
   1.295 +                mFlags.add(Flags.DEFAULT_PANEL);
   1.296 +            } else {
   1.297 +                mFlags.remove(Flags.DEFAULT_PANEL);
   1.298 +            }
   1.299 +        }
   1.300 +
   1.301 +        public boolean isDisabled() {
   1.302 +            return mFlags.contains(Flags.DISABLED_PANEL);
   1.303 +        }
   1.304 +
   1.305 +        private void setIsDisabled(boolean isDisabled) {
   1.306 +            if (isDisabled) {
   1.307 +                mFlags.add(Flags.DISABLED_PANEL);
   1.308 +            } else {
   1.309 +                mFlags.remove(Flags.DISABLED_PANEL);
   1.310 +            }
   1.311 +        }
   1.312 +
   1.313 +        public AuthConfig getAuthConfig() {
   1.314 +            return mAuthConfig;
   1.315 +        }
   1.316 +
   1.317 +        public JSONObject toJSON() throws JSONException {
   1.318 +            final JSONObject json = new JSONObject();
   1.319 +
   1.320 +            json.put(JSON_KEY_TYPE, mType.toString());
   1.321 +            json.put(JSON_KEY_TITLE, mTitle);
   1.322 +            json.put(JSON_KEY_ID, mId);
   1.323 +
   1.324 +            if (mLayoutType != null) {
   1.325 +                json.put(JSON_KEY_LAYOUT, mLayoutType.toString());
   1.326 +            }
   1.327 +
   1.328 +            if (mViews != null) {
   1.329 +                final JSONArray jsonViews = new JSONArray();
   1.330 +
   1.331 +                final int viewCount = mViews.size();
   1.332 +                for (int i = 0; i < viewCount; i++) {
   1.333 +                    final ViewConfig viewConfig = mViews.get(i);
   1.334 +                    final JSONObject jsonViewConfig = viewConfig.toJSON();
   1.335 +                    jsonViews.put(jsonViewConfig);
   1.336 +                }
   1.337 +
   1.338 +                json.put(JSON_KEY_VIEWS, jsonViews);
   1.339 +            }
   1.340 +
   1.341 +            if (mAuthConfig != null) {
   1.342 +                json.put(JSON_KEY_AUTH_CONFIG, mAuthConfig.toJSON());
   1.343 +            }
   1.344 +
   1.345 +            if (mFlags.contains(Flags.DEFAULT_PANEL)) {
   1.346 +                json.put(JSON_KEY_DEFAULT, true);
   1.347 +            }
   1.348 +
   1.349 +            if (mFlags.contains(Flags.DISABLED_PANEL)) {
   1.350 +                json.put(JSON_KEY_DISABLED, true);
   1.351 +            }
   1.352 +
   1.353 +            return json;
   1.354 +        }
   1.355 +
   1.356 +        @Override
   1.357 +        public boolean equals(Object o) {
   1.358 +            if (o == null) {
   1.359 +                return false;
   1.360 +            }
   1.361 +
   1.362 +            if (this == o) {
   1.363 +                return true;
   1.364 +            }
   1.365 +
   1.366 +            if (!(o instanceof PanelConfig)) {
   1.367 +                return false;
   1.368 +            }
   1.369 +
   1.370 +            final PanelConfig other = (PanelConfig) o;
   1.371 +            return mId.equals(other.mId);
   1.372 +        }
   1.373 +
   1.374 +        @Override
   1.375 +        public int describeContents() {
   1.376 +            return 0;
   1.377 +        }
   1.378 +
   1.379 +        @Override
   1.380 +        public void writeToParcel(Parcel dest, int flags) {
   1.381 +            dest.writeParcelable(mType, 0);
   1.382 +            dest.writeString(mTitle);
   1.383 +            dest.writeString(mId);
   1.384 +            dest.writeParcelable(mLayoutType, 0);
   1.385 +            dest.writeTypedList(mViews);
   1.386 +            dest.writeParcelable(mAuthConfig, 0);
   1.387 +            dest.writeSerializable(mFlags);
   1.388 +        }
   1.389 +
   1.390 +        public static final Creator<PanelConfig> CREATOR = new Creator<PanelConfig>() {
   1.391 +            @Override
   1.392 +            public PanelConfig createFromParcel(final Parcel in) {
   1.393 +                return new PanelConfig(in);
   1.394 +            }
   1.395 +
   1.396 +            @Override
   1.397 +            public PanelConfig[] newArray(final int size) {
   1.398 +                return new PanelConfig[size];
   1.399 +            }
   1.400 +        };
   1.401 +    }
   1.402 +
   1.403 +    public static enum LayoutType implements Parcelable {
   1.404 +        FRAME("frame");
   1.405 +
   1.406 +        private final String mId;
   1.407 +
   1.408 +        LayoutType(String id) {
   1.409 +            mId = id;
   1.410 +        }
   1.411 +
   1.412 +        public static LayoutType fromId(String id) {
   1.413 +            if (id == null) {
   1.414 +                throw new IllegalArgumentException("Could not convert null String to LayoutType");
   1.415 +            }
   1.416 +
   1.417 +            for (LayoutType layoutType : LayoutType.values()) {
   1.418 +                if (TextUtils.equals(layoutType.mId, id.toLowerCase())) {
   1.419 +                    return layoutType;
   1.420 +                }
   1.421 +            }
   1.422 +
   1.423 +            throw new IllegalArgumentException("Could not convert String id to LayoutType");
   1.424 +        }
   1.425 +
   1.426 +        @Override
   1.427 +        public String toString() {
   1.428 +            return mId;
   1.429 +        }
   1.430 +
   1.431 +        @Override
   1.432 +        public int describeContents() {
   1.433 +            return 0;
   1.434 +        }
   1.435 +
   1.436 +        @Override
   1.437 +        public void writeToParcel(Parcel dest, int flags) {
   1.438 +            dest.writeInt(ordinal());
   1.439 +        }
   1.440 +
   1.441 +        public static final Creator<LayoutType> CREATOR = new Creator<LayoutType>() {
   1.442 +            @Override
   1.443 +            public LayoutType createFromParcel(final Parcel source) {
   1.444 +                return LayoutType.values()[source.readInt()];
   1.445 +            }
   1.446 +
   1.447 +            @Override
   1.448 +            public LayoutType[] newArray(final int size) {
   1.449 +                return new LayoutType[size];
   1.450 +            }
   1.451 +        };
   1.452 +    }
   1.453 +
   1.454 +    public static enum ViewType implements Parcelable {
   1.455 +        LIST("list"),
   1.456 +        GRID("grid");
   1.457 +
   1.458 +        private final String mId;
   1.459 +
   1.460 +        ViewType(String id) {
   1.461 +            mId = id;
   1.462 +        }
   1.463 +
   1.464 +        public static ViewType fromId(String id) {
   1.465 +            if (id == null) {
   1.466 +                throw new IllegalArgumentException("Could not convert null String to ViewType");
   1.467 +            }
   1.468 +
   1.469 +            for (ViewType viewType : ViewType.values()) {
   1.470 +                if (TextUtils.equals(viewType.mId, id.toLowerCase())) {
   1.471 +                    return viewType;
   1.472 +                }
   1.473 +            }
   1.474 +
   1.475 +            throw new IllegalArgumentException("Could not convert String id to ViewType");
   1.476 +        }
   1.477 +
   1.478 +        @Override
   1.479 +        public String toString() {
   1.480 +            return mId;
   1.481 +        }
   1.482 +
   1.483 +        @Override
   1.484 +        public int describeContents() {
   1.485 +            return 0;
   1.486 +        }
   1.487 +
   1.488 +        @Override
   1.489 +        public void writeToParcel(Parcel dest, int flags) {
   1.490 +            dest.writeInt(ordinal());
   1.491 +        }
   1.492 +
   1.493 +        public static final Creator<ViewType> CREATOR = new Creator<ViewType>() {
   1.494 +            @Override
   1.495 +            public ViewType createFromParcel(final Parcel source) {
   1.496 +                return ViewType.values()[source.readInt()];
   1.497 +            }
   1.498 +
   1.499 +            @Override
   1.500 +            public ViewType[] newArray(final int size) {
   1.501 +                return new ViewType[size];
   1.502 +            }
   1.503 +        };
   1.504 +    }
   1.505 +
   1.506 +    public static enum ItemType implements Parcelable {
   1.507 +        ARTICLE("article"),
   1.508 +        IMAGE("image");
   1.509 +
   1.510 +        private final String mId;
   1.511 +
   1.512 +        ItemType(String id) {
   1.513 +            mId = id;
   1.514 +        }
   1.515 +
   1.516 +        public static ItemType fromId(String id) {
   1.517 +            if (id == null) {
   1.518 +                throw new IllegalArgumentException("Could not convert null String to ItemType");
   1.519 +            }
   1.520 +
   1.521 +            for (ItemType itemType : ItemType.values()) {
   1.522 +                if (TextUtils.equals(itemType.mId, id.toLowerCase())) {
   1.523 +                    return itemType;
   1.524 +                }
   1.525 +            }
   1.526 +
   1.527 +            throw new IllegalArgumentException("Could not convert String id to ItemType");
   1.528 +        }
   1.529 +
   1.530 +        @Override
   1.531 +        public String toString() {
   1.532 +            return mId;
   1.533 +        }
   1.534 +
   1.535 +        @Override
   1.536 +        public int describeContents() {
   1.537 +            return 0;
   1.538 +        }
   1.539 +
   1.540 +        @Override
   1.541 +        public void writeToParcel(Parcel dest, int flags) {
   1.542 +            dest.writeInt(ordinal());
   1.543 +        }
   1.544 +
   1.545 +        public static final Creator<ItemType> CREATOR = new Creator<ItemType>() {
   1.546 +            @Override
   1.547 +            public ItemType createFromParcel(final Parcel source) {
   1.548 +                return ItemType.values()[source.readInt()];
   1.549 +            }
   1.550 +
   1.551 +            @Override
   1.552 +            public ItemType[] newArray(final int size) {
   1.553 +                return new ItemType[size];
   1.554 +            }
   1.555 +        };
   1.556 +    }
   1.557 +
   1.558 +    public static enum ItemHandler implements Parcelable {
   1.559 +        BROWSER("browser"),
   1.560 +        INTENT("intent");
   1.561 +
   1.562 +        private final String mId;
   1.563 +
   1.564 +        ItemHandler(String id) {
   1.565 +            mId = id;
   1.566 +        }
   1.567 +
   1.568 +        public static ItemHandler fromId(String id) {
   1.569 +            if (id == null) {
   1.570 +                throw new IllegalArgumentException("Could not convert null String to ItemHandler");
   1.571 +            }
   1.572 +
   1.573 +            for (ItemHandler itemHandler : ItemHandler.values()) {
   1.574 +                if (TextUtils.equals(itemHandler.mId, id.toLowerCase())) {
   1.575 +                    return itemHandler;
   1.576 +                }
   1.577 +            }
   1.578 +
   1.579 +            throw new IllegalArgumentException("Could not convert String id to ItemHandler");
   1.580 +        }
   1.581 +
   1.582 +        @Override
   1.583 +        public String toString() {
   1.584 +            return mId;
   1.585 +        }
   1.586 +
   1.587 +        @Override
   1.588 +        public int describeContents() {
   1.589 +            return 0;
   1.590 +        }
   1.591 +
   1.592 +        @Override
   1.593 +        public void writeToParcel(Parcel dest, int flags) {
   1.594 +            dest.writeInt(ordinal());
   1.595 +        }
   1.596 +
   1.597 +        public static final Creator<ItemHandler> CREATOR = new Creator<ItemHandler>() {
   1.598 +            @Override
   1.599 +            public ItemHandler createFromParcel(final Parcel source) {
   1.600 +                return ItemHandler.values()[source.readInt()];
   1.601 +            }
   1.602 +
   1.603 +            @Override
   1.604 +            public ItemHandler[] newArray(final int size) {
   1.605 +                return new ItemHandler[size];
   1.606 +            }
   1.607 +        };
   1.608 +    }
   1.609 +
   1.610 +    public static class ViewConfig implements Parcelable {
   1.611 +        private final int mIndex;
   1.612 +        private final ViewType mType;
   1.613 +        private final String mDatasetId;
   1.614 +        private final ItemType mItemType;
   1.615 +        private final ItemHandler mItemHandler;
   1.616 +        private final String mBackImageUrl;
   1.617 +        private final String mFilter;
   1.618 +        private final EmptyViewConfig mEmptyViewConfig;
   1.619 +        private final EnumSet<Flags> mFlags;
   1.620 +
   1.621 +        private static final String JSON_KEY_TYPE = "type";
   1.622 +        private static final String JSON_KEY_DATASET = "dataset";
   1.623 +        private static final String JSON_KEY_ITEM_TYPE = "itemType";
   1.624 +        private static final String JSON_KEY_ITEM_HANDLER = "itemHandler";
   1.625 +        private static final String JSON_KEY_BACK_IMAGE_URL = "backImageUrl";
   1.626 +        private static final String JSON_KEY_FILTER = "filter";
   1.627 +        private static final String JSON_KEY_EMPTY = "empty";
   1.628 +        private static final String JSON_KEY_REFRESH_ENABLED = "refreshEnabled";
   1.629 +
   1.630 +        public enum Flags {
   1.631 +            REFRESH_ENABLED
   1.632 +        }
   1.633 +
   1.634 +        public ViewConfig(int index, JSONObject json) throws JSONException, IllegalArgumentException {
   1.635 +            mIndex = index;
   1.636 +            mType = ViewType.fromId(json.getString(JSON_KEY_TYPE));
   1.637 +            mDatasetId = json.getString(JSON_KEY_DATASET);
   1.638 +            mItemType = ItemType.fromId(json.getString(JSON_KEY_ITEM_TYPE));
   1.639 +            mItemHandler = ItemHandler.fromId(json.getString(JSON_KEY_ITEM_HANDLER));
   1.640 +            mBackImageUrl = json.optString(JSON_KEY_BACK_IMAGE_URL, null);
   1.641 +            mFilter = json.optString(JSON_KEY_FILTER, null);
   1.642 +
   1.643 +            final JSONObject jsonEmptyViewConfig = json.optJSONObject(JSON_KEY_EMPTY);
   1.644 +            if (jsonEmptyViewConfig != null) {
   1.645 +                mEmptyViewConfig = new EmptyViewConfig(jsonEmptyViewConfig);
   1.646 +            } else {
   1.647 +                mEmptyViewConfig = null;
   1.648 +            }
   1.649 +
   1.650 +            mFlags = EnumSet.noneOf(Flags.class);
   1.651 +            if (json.optBoolean(JSON_KEY_REFRESH_ENABLED, false)) {
   1.652 +                mFlags.add(Flags.REFRESH_ENABLED);
   1.653 +            }
   1.654 +
   1.655 +            validate();
   1.656 +        }
   1.657 +
   1.658 +        @SuppressWarnings("unchecked")
   1.659 +        public ViewConfig(Parcel in) {
   1.660 +            mIndex = in.readInt();
   1.661 +            mType = (ViewType) in.readParcelable(getClass().getClassLoader());
   1.662 +            mDatasetId = in.readString();
   1.663 +            mItemType = (ItemType) in.readParcelable(getClass().getClassLoader());
   1.664 +            mItemHandler = (ItemHandler) in.readParcelable(getClass().getClassLoader());
   1.665 +            mBackImageUrl = in.readString();
   1.666 +            mFilter = in.readString();
   1.667 +            mEmptyViewConfig = (EmptyViewConfig) in.readParcelable(getClass().getClassLoader());
   1.668 +            mFlags = (EnumSet<Flags>) in.readSerializable();
   1.669 +
   1.670 +            validate();
   1.671 +        }
   1.672 +
   1.673 +        public ViewConfig(ViewConfig viewConfig) {
   1.674 +            mIndex = viewConfig.mIndex;
   1.675 +            mType = viewConfig.mType;
   1.676 +            mDatasetId = viewConfig.mDatasetId;
   1.677 +            mItemType = viewConfig.mItemType;
   1.678 +            mItemHandler = viewConfig.mItemHandler;
   1.679 +            mBackImageUrl = viewConfig.mBackImageUrl;
   1.680 +            mFilter = viewConfig.mFilter;
   1.681 +            mEmptyViewConfig = viewConfig.mEmptyViewConfig;
   1.682 +            mFlags = viewConfig.mFlags.clone();
   1.683 +
   1.684 +            validate();
   1.685 +        }
   1.686 +
   1.687 +        public ViewConfig(int index, ViewType type, String datasetId, ItemType itemType,
   1.688 +                          ItemHandler itemHandler, String backImageUrl, String filter,
   1.689 +                          EmptyViewConfig emptyViewConfig, EnumSet<Flags> flags) {
   1.690 +            mIndex = index;
   1.691 +            mType = type;
   1.692 +            mDatasetId = datasetId;
   1.693 +            mItemType = itemType;
   1.694 +            mItemHandler = itemHandler;
   1.695 +            mBackImageUrl = backImageUrl;
   1.696 +            mFilter = filter;
   1.697 +            mEmptyViewConfig = emptyViewConfig;
   1.698 +            mFlags = flags;
   1.699 +
   1.700 +            validate();
   1.701 +        }
   1.702 +
   1.703 +        private void validate() {
   1.704 +            if (mType == null) {
   1.705 +                throw new IllegalArgumentException("Can't create ViewConfig with null type");
   1.706 +            }
   1.707 +
   1.708 +            if (TextUtils.isEmpty(mDatasetId)) {
   1.709 +                throw new IllegalArgumentException("Can't create ViewConfig with empty dataset ID");
   1.710 +            }
   1.711 +
   1.712 +            if (mItemType == null) {
   1.713 +                throw new IllegalArgumentException("Can't create ViewConfig with null item type");
   1.714 +            }
   1.715 +
   1.716 +            if (mItemHandler == null) {
   1.717 +                throw new IllegalArgumentException("Can't create ViewConfig with null item handler");
   1.718 +            }
   1.719 +
   1.720 +            if (mFlags == null) {
   1.721 +               throw new IllegalArgumentException("Can't create ViewConfig with null flags");
   1.722 +            }
   1.723 +        }
   1.724 +
   1.725 +        public int getIndex() {
   1.726 +            return mIndex;
   1.727 +        }
   1.728 +
   1.729 +        public ViewType getType() {
   1.730 +            return mType;
   1.731 +        }
   1.732 +
   1.733 +        public String getDatasetId() {
   1.734 +            return mDatasetId;
   1.735 +        }
   1.736 +
   1.737 +        public ItemType getItemType() {
   1.738 +            return mItemType;
   1.739 +        }
   1.740 +
   1.741 +        public ItemHandler getItemHandler() {
   1.742 +            return mItemHandler;
   1.743 +        }
   1.744 +
   1.745 +        public String getBackImageUrl() {
   1.746 +            return mBackImageUrl;
   1.747 +        }
   1.748 +
   1.749 +        public String getFilter() {
   1.750 +            return mFilter;
   1.751 +        }
   1.752 +
   1.753 +        public EmptyViewConfig getEmptyViewConfig() {
   1.754 +            return mEmptyViewConfig;
   1.755 +        }
   1.756 +
   1.757 +        public boolean isRefreshEnabled() {
   1.758 +            return mFlags.contains(Flags.REFRESH_ENABLED);
   1.759 +        }
   1.760 +
   1.761 +        public JSONObject toJSON() throws JSONException {
   1.762 +            final JSONObject json = new JSONObject();
   1.763 +
   1.764 +            json.put(JSON_KEY_TYPE, mType.toString());
   1.765 +            json.put(JSON_KEY_DATASET, mDatasetId);
   1.766 +            json.put(JSON_KEY_ITEM_TYPE, mItemType.toString());
   1.767 +            json.put(JSON_KEY_ITEM_HANDLER, mItemHandler.toString());
   1.768 +
   1.769 +            if (!TextUtils.isEmpty(mBackImageUrl)) {
   1.770 +                json.put(JSON_KEY_BACK_IMAGE_URL, mBackImageUrl);
   1.771 +            }
   1.772 +
   1.773 +            if (!TextUtils.isEmpty(mFilter)) {
   1.774 +                json.put(JSON_KEY_FILTER, mFilter);
   1.775 +            }
   1.776 +
   1.777 +            if (mEmptyViewConfig != null) {
   1.778 +                json.put(JSON_KEY_EMPTY, mEmptyViewConfig.toJSON());
   1.779 +            }
   1.780 +
   1.781 +            if (mFlags.contains(Flags.REFRESH_ENABLED)) {
   1.782 +                json.put(JSON_KEY_REFRESH_ENABLED, true);
   1.783 +            }
   1.784 +
   1.785 +            return json;
   1.786 +        }
   1.787 +
   1.788 +        @Override
   1.789 +        public int describeContents() {
   1.790 +            return 0;
   1.791 +        }
   1.792 +
   1.793 +        @Override
   1.794 +        public void writeToParcel(Parcel dest, int flags) {
   1.795 +            dest.writeInt(mIndex);
   1.796 +            dest.writeParcelable(mType, 0);
   1.797 +            dest.writeString(mDatasetId);
   1.798 +            dest.writeParcelable(mItemType, 0);
   1.799 +            dest.writeParcelable(mItemHandler, 0);
   1.800 +            dest.writeString(mBackImageUrl);
   1.801 +            dest.writeString(mFilter);
   1.802 +            dest.writeParcelable(mEmptyViewConfig, 0);
   1.803 +            dest.writeSerializable(mFlags);
   1.804 +        }
   1.805 +
   1.806 +        public static final Creator<ViewConfig> CREATOR = new Creator<ViewConfig>() {
   1.807 +            @Override
   1.808 +            public ViewConfig createFromParcel(final Parcel in) {
   1.809 +                return new ViewConfig(in);
   1.810 +            }
   1.811 +
   1.812 +            @Override
   1.813 +            public ViewConfig[] newArray(final int size) {
   1.814 +                return new ViewConfig[size];
   1.815 +            }
   1.816 +        };
   1.817 +    }
   1.818 +
   1.819 +    public static class EmptyViewConfig implements Parcelable {
   1.820 +        private final String mText;
   1.821 +        private final String mImageUrl;
   1.822 +
   1.823 +        private static final String JSON_KEY_TEXT = "text";
   1.824 +        private static final String JSON_KEY_IMAGE_URL = "imageUrl";
   1.825 +
   1.826 +        public EmptyViewConfig(JSONObject json) throws JSONException, IllegalArgumentException {
   1.827 +            mText = json.optString(JSON_KEY_TEXT, null);
   1.828 +            mImageUrl = json.optString(JSON_KEY_IMAGE_URL, null);
   1.829 +        }
   1.830 +
   1.831 +        @SuppressWarnings("unchecked")
   1.832 +        public EmptyViewConfig(Parcel in) {
   1.833 +            mText = in.readString();
   1.834 +            mImageUrl = in.readString();
   1.835 +        }
   1.836 +
   1.837 +        public EmptyViewConfig(EmptyViewConfig emptyViewConfig) {
   1.838 +            mText = emptyViewConfig.mText;
   1.839 +            mImageUrl = emptyViewConfig.mImageUrl;
   1.840 +        }
   1.841 +
   1.842 +        public EmptyViewConfig(String text, String imageUrl) {
   1.843 +            mText = text;
   1.844 +            mImageUrl = imageUrl;
   1.845 +        }
   1.846 +
   1.847 +        public String getText() {
   1.848 +            return mText;
   1.849 +        }
   1.850 +
   1.851 +        public String getImageUrl() {
   1.852 +            return mImageUrl;
   1.853 +        }
   1.854 +
   1.855 +        public JSONObject toJSON() throws JSONException {
   1.856 +            final JSONObject json = new JSONObject();
   1.857 +
   1.858 +            json.put(JSON_KEY_TEXT, mText);
   1.859 +            json.put(JSON_KEY_IMAGE_URL, mImageUrl);
   1.860 +
   1.861 +            return json;
   1.862 +        }
   1.863 +
   1.864 +        @Override
   1.865 +        public int describeContents() {
   1.866 +            return 0;
   1.867 +        }
   1.868 +
   1.869 +        @Override
   1.870 +        public void writeToParcel(Parcel dest, int flags) {
   1.871 +            dest.writeString(mText);
   1.872 +            dest.writeString(mImageUrl);
   1.873 +        }
   1.874 +
   1.875 +        public static final Creator<EmptyViewConfig> CREATOR = new Creator<EmptyViewConfig>() {
   1.876 +            @Override
   1.877 +            public EmptyViewConfig createFromParcel(final Parcel in) {
   1.878 +                return new EmptyViewConfig(in);
   1.879 +            }
   1.880 +
   1.881 +            @Override
   1.882 +            public EmptyViewConfig[] newArray(final int size) {
   1.883 +                return new EmptyViewConfig[size];
   1.884 +            }
   1.885 +        };
   1.886 +    }
   1.887 +
   1.888 +    public static class AuthConfig implements Parcelable {
   1.889 +        private final String mMessageText;
   1.890 +        private final String mButtonText;
   1.891 +        private final String mImageUrl;
   1.892 +
   1.893 +        private static final String JSON_KEY_MESSAGE_TEXT = "messageText";
   1.894 +        private static final String JSON_KEY_BUTTON_TEXT = "buttonText";
   1.895 +        private static final String JSON_KEY_IMAGE_URL = "imageUrl";
   1.896 +
   1.897 +        public AuthConfig(JSONObject json) throws JSONException, IllegalArgumentException {
   1.898 +            mMessageText = json.optString(JSON_KEY_MESSAGE_TEXT);
   1.899 +            mButtonText = json.optString(JSON_KEY_BUTTON_TEXT);
   1.900 +            mImageUrl = json.optString(JSON_KEY_IMAGE_URL, null);
   1.901 +        }
   1.902 +
   1.903 +        @SuppressWarnings("unchecked")
   1.904 +        public AuthConfig(Parcel in) {
   1.905 +            mMessageText = in.readString();
   1.906 +            mButtonText = in.readString();
   1.907 +            mImageUrl = in.readString();
   1.908 +
   1.909 +            validate();
   1.910 +        }
   1.911 +
   1.912 +        public AuthConfig(AuthConfig authConfig) {
   1.913 +            mMessageText = authConfig.mMessageText;
   1.914 +            mButtonText = authConfig.mButtonText;
   1.915 +            mImageUrl = authConfig.mImageUrl;
   1.916 +
   1.917 +            validate();
   1.918 +        }
   1.919 +
   1.920 +        public AuthConfig(String messageText, String buttonText, String imageUrl) {
   1.921 +            mMessageText = messageText;
   1.922 +            mButtonText = buttonText;
   1.923 +            mImageUrl = imageUrl;
   1.924 +
   1.925 +            validate();
   1.926 +        }
   1.927 +
   1.928 +        private void validate() {
   1.929 +            if (mMessageText == null) {
   1.930 +                throw new IllegalArgumentException("Can't create AuthConfig with null message text");
   1.931 +            }
   1.932 +
   1.933 +            if (mButtonText == null) {
   1.934 +                throw new IllegalArgumentException("Can't create AuthConfig with null button text");
   1.935 +            }
   1.936 +        }
   1.937 +
   1.938 +        public String getMessageText() {
   1.939 +            return mMessageText;
   1.940 +        }
   1.941 +
   1.942 +        public String getButtonText() {
   1.943 +            return mButtonText;
   1.944 +        }
   1.945 +
   1.946 +        public String getImageUrl() {
   1.947 +            return mImageUrl;
   1.948 +        }
   1.949 +
   1.950 +        public JSONObject toJSON() throws JSONException {
   1.951 +            final JSONObject json = new JSONObject();
   1.952 +
   1.953 +            json.put(JSON_KEY_MESSAGE_TEXT, mMessageText);
   1.954 +            json.put(JSON_KEY_BUTTON_TEXT, mButtonText);
   1.955 +            json.put(JSON_KEY_IMAGE_URL, mImageUrl);
   1.956 +
   1.957 +            return json;
   1.958 +        }
   1.959 +
   1.960 +        @Override
   1.961 +        public int describeContents() {
   1.962 +            return 0;
   1.963 +        }
   1.964 +
   1.965 +        @Override
   1.966 +        public void writeToParcel(Parcel dest, int flags) {
   1.967 +            dest.writeString(mMessageText);
   1.968 +            dest.writeString(mButtonText);
   1.969 +            dest.writeString(mImageUrl);
   1.970 +        }
   1.971 +
   1.972 +        public static final Creator<AuthConfig> CREATOR = new Creator<AuthConfig>() {
   1.973 +            @Override
   1.974 +            public AuthConfig createFromParcel(final Parcel in) {
   1.975 +                return new AuthConfig(in);
   1.976 +            }
   1.977 +
   1.978 +            @Override
   1.979 +            public AuthConfig[] newArray(final int size) {
   1.980 +                return new AuthConfig[size];
   1.981 +            }
   1.982 +        };
   1.983 +    }
   1.984 +   /**
   1.985 +     * Immutable representation of the current state of {@code HomeConfig}.
   1.986 +     * This is what HomeConfig returns from a load() call and takes as
   1.987 +     * input to save a new state.
   1.988 +     *
   1.989 +     * Users of {@code State} should use an {@code Iterator} to iterate
   1.990 +     * through the contained {@code PanelConfig} instances.
   1.991 +     *
   1.992 +     * {@code State} is immutable i.e. you can't add, remove, or update
   1.993 +     * contained elements directly. You have to use an {@code Editor} to
   1.994 +     * change the state, which can be created through the {@code edit()}
   1.995 +     * method.
   1.996 +     */
   1.997 +    public static class State implements Iterable<PanelConfig> {
   1.998 +        private HomeConfig mHomeConfig;
   1.999 +        private final List<PanelConfig> mPanelConfigs;
  1.1000 +        private final boolean mIsDefault;
  1.1001 +
  1.1002 +        State(List<PanelConfig> panelConfigs, boolean isDefault) {
  1.1003 +            this(null, panelConfigs, isDefault);
  1.1004 +        }
  1.1005 +
  1.1006 +        private State(HomeConfig homeConfig, List<PanelConfig> panelConfigs, boolean isDefault) {
  1.1007 +            mHomeConfig = homeConfig;
  1.1008 +            mPanelConfigs = Collections.unmodifiableList(panelConfigs);
  1.1009 +            mIsDefault = isDefault;
  1.1010 +        }
  1.1011 +
  1.1012 +        private void setHomeConfig(HomeConfig homeConfig) {
  1.1013 +            if (mHomeConfig != null) {
  1.1014 +                throw new IllegalStateException("Can't set HomeConfig more than once");
  1.1015 +            }
  1.1016 +
  1.1017 +            mHomeConfig = homeConfig;
  1.1018 +        }
  1.1019 +
  1.1020 +        @Override
  1.1021 +        public Iterator<PanelConfig> iterator() {
  1.1022 +            return mPanelConfigs.iterator();
  1.1023 +        }
  1.1024 +
  1.1025 +        /**
  1.1026 +         * Returns whether this {@code State} instance represents the default
  1.1027 +         * {@code HomeConfig} configuration or not.
  1.1028 +         */
  1.1029 +        public boolean isDefault() {
  1.1030 +            return mIsDefault;
  1.1031 +        }
  1.1032 +
  1.1033 +        /**
  1.1034 +         * Creates an {@code Editor} for this state.
  1.1035 +         */
  1.1036 +        public Editor edit() {
  1.1037 +            return new Editor(mHomeConfig, this);
  1.1038 +        }
  1.1039 +    }
  1.1040 +
  1.1041 +    /**
  1.1042 +     * {@code Editor} allows you to make changes to a {@code State}. You
  1.1043 +     * can create {@code Editor} by calling {@code edit()} on the target
  1.1044 +     * {@code State} instance.
  1.1045 +     *
  1.1046 +     * {@code Editor} works on a copy of the {@code State} that originated
  1.1047 +     * it. This means that adding, removing, or updating panels in an
  1.1048 +     * {@code Editor} will never change the {@code State} which you
  1.1049 +     * created the {@code Editor} from. Calling {@code commit()} or
  1.1050 +     * {@code apply()} will cause the new {@code State} instance to be
  1.1051 +     * created and saved using the {@code HomeConfig} instance that
  1.1052 +     * created the source {@code State}.
  1.1053 +     *
  1.1054 +     * {@code Editor} is *not* thread-safe. You can only make calls on it
  1.1055 +     * from the thread where it was originally created. It will throw an
  1.1056 +     * exception if you don't follow this invariant.
  1.1057 +     */
  1.1058 +    public static class Editor implements Iterable<PanelConfig> {
  1.1059 +        private final HomeConfig mHomeConfig;
  1.1060 +        private final Map<String, PanelConfig> mConfigMap;
  1.1061 +        private final List<String> mConfigOrder;
  1.1062 +        private final List<GeckoEvent> mEventQueue;
  1.1063 +        private final Thread mOriginalThread;
  1.1064 +
  1.1065 +        private PanelConfig mDefaultPanel;
  1.1066 +        private int mEnabledCount;
  1.1067 +
  1.1068 +        private boolean mHasChanged;
  1.1069 +        private final boolean mIsFromDefault;
  1.1070 +
  1.1071 +        private Editor(HomeConfig homeConfig, State configState) {
  1.1072 +            mHomeConfig = homeConfig;
  1.1073 +            mOriginalThread = Thread.currentThread();
  1.1074 +            mConfigMap = new HashMap<String, PanelConfig>();
  1.1075 +            mConfigOrder = new LinkedList<String>();
  1.1076 +            mEventQueue = new LinkedList<GeckoEvent>();
  1.1077 +            mEnabledCount = 0;
  1.1078 +
  1.1079 +            mHasChanged = false;
  1.1080 +            mIsFromDefault = configState.isDefault();
  1.1081 +
  1.1082 +            initFromState(configState);
  1.1083 +        }
  1.1084 +
  1.1085 +        /**
  1.1086 +         * Initialize the initial state of the editor from the given
  1.1087 +         * {@sode State}. A HashMap is used to represent the list of
  1.1088 +         * panels as it provides fast access, and a LinkedList is used to
  1.1089 +         * keep track of order. We keep a reference to the default panel
  1.1090 +         * and the number of enabled panels to avoid iterating through the
  1.1091 +         * map every time we need those.
  1.1092 +         *
  1.1093 +         * @param configState The source State to load the editor from.
  1.1094 +         */
  1.1095 +        private void initFromState(State configState) {
  1.1096 +            for (PanelConfig panelConfig : configState) {
  1.1097 +                final PanelConfig panelCopy = new PanelConfig(panelConfig);
  1.1098 +
  1.1099 +                if (!panelCopy.isDisabled()) {
  1.1100 +                    mEnabledCount++;
  1.1101 +                }
  1.1102 +
  1.1103 +                if (panelCopy.isDefault()) {
  1.1104 +                    if (mDefaultPanel == null) {
  1.1105 +                        mDefaultPanel = panelCopy;
  1.1106 +                    } else {
  1.1107 +                        throw new IllegalStateException("Multiple default panels in HomeConfig state");
  1.1108 +                    }
  1.1109 +                }
  1.1110 +
  1.1111 +                final String panelId = panelConfig.getId();
  1.1112 +                mConfigOrder.add(panelId);
  1.1113 +                mConfigMap.put(panelId, panelCopy);
  1.1114 +            }
  1.1115 +
  1.1116 +            // We should always have a defined default panel if there's
  1.1117 +            // at least one enabled panel around.
  1.1118 +            if (mEnabledCount > 0 && mDefaultPanel == null) {
  1.1119 +                throw new IllegalStateException("Default panel in HomeConfig state is undefined");
  1.1120 +            }
  1.1121 +        }
  1.1122 +
  1.1123 +        private PanelConfig getPanelOrThrow(String panelId) {
  1.1124 +            final PanelConfig panelConfig = mConfigMap.get(panelId);
  1.1125 +            if (panelConfig == null) {
  1.1126 +                throw new IllegalStateException("Tried to access non-existing panel: " + panelId);
  1.1127 +            }
  1.1128 +
  1.1129 +            return panelConfig;
  1.1130 +        }
  1.1131 +
  1.1132 +        private boolean isCurrentDefaultPanel(PanelConfig panelConfig) {
  1.1133 +            if (mDefaultPanel == null) {
  1.1134 +                return false;
  1.1135 +            }
  1.1136 +
  1.1137 +            return mDefaultPanel.equals(panelConfig);
  1.1138 +        }
  1.1139 +
  1.1140 +        private void findNewDefault() {
  1.1141 +            // Pick the first panel that is neither disabled nor currently
  1.1142 +            // set as default.
  1.1143 +            for (PanelConfig panelConfig : mConfigMap.values()) {
  1.1144 +                if (!panelConfig.isDefault() && !panelConfig.isDisabled()) {
  1.1145 +                    setDefault(panelConfig.getId());
  1.1146 +                    return;
  1.1147 +                }
  1.1148 +            }
  1.1149 +
  1.1150 +            mDefaultPanel = null;
  1.1151 +        }
  1.1152 +
  1.1153 +        /**
  1.1154 +         * Makes an ordered list of PanelConfigs that can be references
  1.1155 +         * or deep copied objects.
  1.1156 +         *
  1.1157 +         * @param deepCopy true to make deep-copied objects
  1.1158 +         * @return ordered List of PanelConfigs
  1.1159 +         */
  1.1160 +        private List<PanelConfig> makeOrderedCopy(boolean deepCopy) {
  1.1161 +            final List<PanelConfig> copiedList = new ArrayList<PanelConfig>(mConfigOrder.size());
  1.1162 +            for (String panelId : mConfigOrder) {
  1.1163 +                PanelConfig panelConfig = mConfigMap.get(panelId);
  1.1164 +                if (deepCopy) {
  1.1165 +                    panelConfig = new PanelConfig(panelConfig);
  1.1166 +                }
  1.1167 +                copiedList.add(panelConfig);
  1.1168 +            }
  1.1169 +
  1.1170 +            return copiedList;
  1.1171 +        }
  1.1172 +
  1.1173 +        private void setPanelIsDisabled(PanelConfig panelConfig, boolean disabled) {
  1.1174 +            if (panelConfig.isDisabled() == disabled) {
  1.1175 +                return;
  1.1176 +            }
  1.1177 +
  1.1178 +            panelConfig.setIsDisabled(disabled);
  1.1179 +            mEnabledCount += (disabled ? -1 : 1);
  1.1180 +        }
  1.1181 +
  1.1182 +        /**
  1.1183 +         * Gets the ID of the current default panel.
  1.1184 +         */
  1.1185 +        public String getDefaultPanelId() {
  1.1186 +            ThreadUtils.assertOnThread(mOriginalThread);
  1.1187 +
  1.1188 +            if (mDefaultPanel == null) {
  1.1189 +                return null;
  1.1190 +            }
  1.1191 +
  1.1192 +            return mDefaultPanel.getId();
  1.1193 +        }
  1.1194 +
  1.1195 +        /**
  1.1196 +         * Set a new default panel.
  1.1197 +         *
  1.1198 +         * @param panelId the ID of the new default panel.
  1.1199 +         */
  1.1200 +        public void setDefault(String panelId) {
  1.1201 +            ThreadUtils.assertOnThread(mOriginalThread);
  1.1202 +
  1.1203 +            final PanelConfig panelConfig = getPanelOrThrow(panelId);
  1.1204 +            if (isCurrentDefaultPanel(panelConfig)) {
  1.1205 +                return;
  1.1206 +            }
  1.1207 +
  1.1208 +            if (mDefaultPanel != null) {
  1.1209 +                mDefaultPanel.setIsDefault(false);
  1.1210 +            }
  1.1211 +
  1.1212 +            panelConfig.setIsDefault(true);
  1.1213 +            setPanelIsDisabled(panelConfig, false);
  1.1214 +
  1.1215 +            mDefaultPanel = panelConfig;
  1.1216 +            mHasChanged = true;
  1.1217 +        }
  1.1218 +
  1.1219 +        /**
  1.1220 +         * Toggles disabled state for a panel.
  1.1221 +         *
  1.1222 +         * @param panelId the ID of the target panel.
  1.1223 +         * @param disabled true to disable the panel.
  1.1224 +         */
  1.1225 +        public void setDisabled(String panelId, boolean disabled) {
  1.1226 +            ThreadUtils.assertOnThread(mOriginalThread);
  1.1227 +
  1.1228 +            final PanelConfig panelConfig = getPanelOrThrow(panelId);
  1.1229 +            if (panelConfig.isDisabled() == disabled) {
  1.1230 +                return;
  1.1231 +            }
  1.1232 +
  1.1233 +            setPanelIsDisabled(panelConfig, disabled);
  1.1234 +
  1.1235 +            if (disabled) {
  1.1236 +                if (isCurrentDefaultPanel(panelConfig)) {
  1.1237 +                    panelConfig.setIsDefault(false);
  1.1238 +                    findNewDefault();
  1.1239 +                }
  1.1240 +            } else if (mEnabledCount == 1) {
  1.1241 +                setDefault(panelId);
  1.1242 +            }
  1.1243 +
  1.1244 +            mHasChanged = true;
  1.1245 +        }
  1.1246 +
  1.1247 +        /**
  1.1248 +         * Adds a new {@code PanelConfig}. It will do nothing if the
  1.1249 +         * {@code Editor} already contains a panel with the same ID.
  1.1250 +         *
  1.1251 +         * @param panelConfig the {@code PanelConfig} instance to be added.
  1.1252 +         * @return true if the item has been added.
  1.1253 +         */
  1.1254 +        public boolean install(PanelConfig panelConfig) {
  1.1255 +            ThreadUtils.assertOnThread(mOriginalThread);
  1.1256 +
  1.1257 +            if (panelConfig == null) {
  1.1258 +                throw new IllegalStateException("Can't install a null panel");
  1.1259 +            }
  1.1260 +
  1.1261 +            if (!panelConfig.isDynamic()) {
  1.1262 +                throw new IllegalStateException("Can't install a built-in panel: " + panelConfig.getId());
  1.1263 +            }
  1.1264 +
  1.1265 +            if (panelConfig.isDisabled()) {
  1.1266 +                throw new IllegalStateException("Can't install a disabled panel: " + panelConfig.getId());
  1.1267 +            }
  1.1268 +
  1.1269 +            boolean installed = false;
  1.1270 +
  1.1271 +            final String id = panelConfig.getId();
  1.1272 +            if (!mConfigMap.containsKey(id)) {
  1.1273 +                mConfigMap.put(id, panelConfig);
  1.1274 +                mConfigOrder.add(id);
  1.1275 +
  1.1276 +                mEnabledCount++;
  1.1277 +                if (mEnabledCount == 1 || panelConfig.isDefault()) {
  1.1278 +                    setDefault(panelConfig.getId());
  1.1279 +                }
  1.1280 +
  1.1281 +                installed = true;
  1.1282 +
  1.1283 +                // Add an event to the queue if a new panel is sucessfully installed.
  1.1284 +                mEventQueue.add(GeckoEvent.createBroadcastEvent("HomePanels:Installed", panelConfig.getId()));
  1.1285 +            }
  1.1286 +
  1.1287 +            mHasChanged = true;
  1.1288 +            return installed;
  1.1289 +        }
  1.1290 +
  1.1291 +        /**
  1.1292 +         * Removes an existing panel.
  1.1293 +         *
  1.1294 +         * @return true if the item has been removed.
  1.1295 +         */
  1.1296 +        public boolean uninstall(String panelId) {
  1.1297 +            ThreadUtils.assertOnThread(mOriginalThread);
  1.1298 +
  1.1299 +            final PanelConfig panelConfig = mConfigMap.get(panelId);
  1.1300 +            if (panelConfig == null) {
  1.1301 +                return false;
  1.1302 +            }
  1.1303 +
  1.1304 +            if (!panelConfig.isDynamic()) {
  1.1305 +                throw new IllegalStateException("Can't uninstall a built-in panel: " + panelConfig.getId());
  1.1306 +            }
  1.1307 +
  1.1308 +            mConfigMap.remove(panelId);
  1.1309 +            mConfigOrder.remove(panelId);
  1.1310 +
  1.1311 +            if (!panelConfig.isDisabled()) {
  1.1312 +                mEnabledCount--;
  1.1313 +            }
  1.1314 +
  1.1315 +            if (isCurrentDefaultPanel(panelConfig)) {
  1.1316 +                findNewDefault();
  1.1317 +            }
  1.1318 +
  1.1319 +            // Add an event to the queue if a panel is succesfully uninstalled.
  1.1320 +            mEventQueue.add(GeckoEvent.createBroadcastEvent("HomePanels:Uninstalled", panelId));
  1.1321 +
  1.1322 +            mHasChanged = true;
  1.1323 +            return true;
  1.1324 +        }
  1.1325 +
  1.1326 +        /**
  1.1327 +         * Moves panel associated with panelId to the specified position.
  1.1328 +         *
  1.1329 +         * @param panelId Id of panel
  1.1330 +         * @param destIndex Destination position
  1.1331 +         * @return true if move succeeded
  1.1332 +         */
  1.1333 +        public boolean moveTo(String panelId, int destIndex) {
  1.1334 +            ThreadUtils.assertOnThread(mOriginalThread);
  1.1335 +
  1.1336 +            if (!mConfigOrder.contains(panelId)) {
  1.1337 +                return false;
  1.1338 +            }
  1.1339 +
  1.1340 +            mConfigOrder.remove(panelId);
  1.1341 +            mConfigOrder.add(destIndex, panelId);
  1.1342 +            mHasChanged = true;
  1.1343 +
  1.1344 +            return true;
  1.1345 +        }
  1.1346 +
  1.1347 +        /**
  1.1348 +         * Replaces an existing panel with a new {@code PanelConfig} instance.
  1.1349 +         *
  1.1350 +         * @return true if the item has been updated.
  1.1351 +         */
  1.1352 +        public boolean update(PanelConfig panelConfig) {
  1.1353 +            ThreadUtils.assertOnThread(mOriginalThread);
  1.1354 +
  1.1355 +            if (panelConfig == null) {
  1.1356 +                throw new IllegalStateException("Can't update a null panel");
  1.1357 +            }
  1.1358 +
  1.1359 +            boolean updated = false;
  1.1360 +
  1.1361 +            final String id = panelConfig.getId();
  1.1362 +            if (mConfigMap.containsKey(id)) {
  1.1363 +                final PanelConfig oldPanelConfig = mConfigMap.put(id, panelConfig);
  1.1364 +
  1.1365 +                // The disabled and default states can't never be
  1.1366 +                // changed by an update operation.
  1.1367 +                panelConfig.setIsDefault(oldPanelConfig.isDefault());
  1.1368 +                panelConfig.setIsDisabled(oldPanelConfig.isDisabled());
  1.1369 +
  1.1370 +                updated = true;
  1.1371 +            }
  1.1372 +
  1.1373 +            mHasChanged = true;
  1.1374 +            return updated;
  1.1375 +        }
  1.1376 +
  1.1377 +        /**
  1.1378 +         * Saves the current {@code Editor} state asynchronously in the
  1.1379 +         * background thread.
  1.1380 +         *
  1.1381 +         * @return the resulting {@code State} instance.
  1.1382 +         */
  1.1383 +        public State apply() {
  1.1384 +            ThreadUtils.assertOnThread(mOriginalThread);
  1.1385 +
  1.1386 +            // We're about to save the current state in the background thread
  1.1387 +            // so we should use a deep copy of the PanelConfig instances to
  1.1388 +            // avoid saving corrupted state.
  1.1389 +            final State newConfigState =
  1.1390 +                    new State(mHomeConfig, makeOrderedCopy(true), isDefault());
  1.1391 +
  1.1392 +            // Copy the event queue to a new list, so that we only modify mEventQueue on
  1.1393 +            // the original thread where it was created.
  1.1394 +            final LinkedList<GeckoEvent> eventQueueCopy = new LinkedList<GeckoEvent>(mEventQueue);
  1.1395 +            mEventQueue.clear();
  1.1396 +
  1.1397 +            ThreadUtils.getBackgroundHandler().post(new Runnable() {
  1.1398 +                @Override
  1.1399 +                public void run() {
  1.1400 +                    mHomeConfig.save(newConfigState);
  1.1401 +
  1.1402 +                    // Send pending events after the new config is saved.
  1.1403 +                    sendEventsToGecko(eventQueueCopy);
  1.1404 +                }
  1.1405 +            });
  1.1406 +
  1.1407 +            return newConfigState;
  1.1408 +        }
  1.1409 +
  1.1410 +        /**
  1.1411 +         * Saves the current {@code Editor} state synchronously in the
  1.1412 +         * current thread.
  1.1413 +         *
  1.1414 +         * @return the resulting {@code State} instance.
  1.1415 +         */
  1.1416 +        public State commit() {
  1.1417 +            ThreadUtils.assertOnThread(mOriginalThread);
  1.1418 +
  1.1419 +            final State newConfigState =
  1.1420 +                    new State(mHomeConfig, makeOrderedCopy(false), isDefault());
  1.1421 +
  1.1422 +            // This is a synchronous blocking operation, hence no
  1.1423 +            // need to deep copy the current PanelConfig instances.
  1.1424 +            mHomeConfig.save(newConfigState);
  1.1425 +
  1.1426 +            // Send pending events after the new config is saved.
  1.1427 +            sendEventsToGecko(mEventQueue);
  1.1428 +            mEventQueue.clear();
  1.1429 +
  1.1430 +            return newConfigState;
  1.1431 +        }
  1.1432 +
  1.1433 +        /**
  1.1434 +         * Returns whether the {@code Editor} represents the default
  1.1435 +         * {@code HomeConfig} configuration without any unsaved changes.
  1.1436 +         */
  1.1437 +        public boolean isDefault() {
  1.1438 +            ThreadUtils.assertOnThread(mOriginalThread);
  1.1439 +
  1.1440 +            return (!mHasChanged && mIsFromDefault);
  1.1441 +        }
  1.1442 +
  1.1443 +        public boolean isEmpty() {
  1.1444 +            return mConfigMap.isEmpty();
  1.1445 +        }
  1.1446 +
  1.1447 +        private void sendEventsToGecko(List<GeckoEvent> events) {
  1.1448 +            for (GeckoEvent e : events) {
  1.1449 +                GeckoAppShell.sendEventToGecko(e);
  1.1450 +            }
  1.1451 +        }
  1.1452 +
  1.1453 +        private class EditorIterator implements Iterator<PanelConfig> {
  1.1454 +            private final Iterator<String> mOrderIterator;
  1.1455 +
  1.1456 +            public EditorIterator() {
  1.1457 +                mOrderIterator = mConfigOrder.iterator();
  1.1458 +            }
  1.1459 +
  1.1460 +            @Override
  1.1461 +            public boolean hasNext() {
  1.1462 +                return mOrderIterator.hasNext();
  1.1463 +            }
  1.1464 +
  1.1465 +            @Override
  1.1466 +            public PanelConfig next() {
  1.1467 +                final String panelId = mOrderIterator.next();
  1.1468 +                return mConfigMap.get(panelId);
  1.1469 +            }
  1.1470 +
  1.1471 +            @Override
  1.1472 +            public void remove() {
  1.1473 +                throw new UnsupportedOperationException("Can't 'remove' from on Editor iterator.");
  1.1474 +            }
  1.1475 +        }
  1.1476 +
  1.1477 +        @Override
  1.1478 +        public Iterator<PanelConfig> iterator() {
  1.1479 +            ThreadUtils.assertOnThread(mOriginalThread);
  1.1480 +
  1.1481 +            return new EditorIterator();
  1.1482 +        }
  1.1483 +    }
  1.1484 +
  1.1485 +    public interface OnReloadListener {
  1.1486 +        public void onReload();
  1.1487 +    }
  1.1488 +
  1.1489 +    public interface HomeConfigBackend {
  1.1490 +        public State load();
  1.1491 +        public void save(State configState);
  1.1492 +        public String getLocale();
  1.1493 +        public void setOnReloadListener(OnReloadListener listener);
  1.1494 +    }
  1.1495 +
  1.1496 +    // UUIDs used to create PanelConfigs for default built-in panels
  1.1497 +    private static final String TOP_SITES_PANEL_ID = "4becc86b-41eb-429a-a042-88fe8b5a094e";
  1.1498 +    private static final String BOOKMARKS_PANEL_ID = "7f6d419a-cd6c-4e34-b26f-f68b1b551907";
  1.1499 +    private static final String READING_LIST_PANEL_ID = "20f4549a-64ad-4c32-93e4-1dcef792733b";
  1.1500 +    private static final String HISTORY_PANEL_ID = "f134bf20-11f7-4867-ab8b-e8e705d7fbe8";
  1.1501 +
  1.1502 +    private final HomeConfigBackend mBackend;
  1.1503 +
  1.1504 +    public HomeConfig(HomeConfigBackend backend) {
  1.1505 +        mBackend = backend;
  1.1506 +    }
  1.1507 +
  1.1508 +    public State load() {
  1.1509 +        final State configState = mBackend.load();
  1.1510 +        configState.setHomeConfig(this);
  1.1511 +
  1.1512 +        return configState;
  1.1513 +    }
  1.1514 +
  1.1515 +    public String getLocale() {
  1.1516 +        return mBackend.getLocale();
  1.1517 +    }
  1.1518 +
  1.1519 +    public void save(State configState) {
  1.1520 +        mBackend.save(configState);
  1.1521 +    }
  1.1522 +
  1.1523 +    public void setOnReloadListener(OnReloadListener listener) {
  1.1524 +        mBackend.setOnReloadListener(listener);
  1.1525 +    }
  1.1526 +
  1.1527 +    public static PanelConfig createBuiltinPanelConfig(Context context, PanelType panelType) {
  1.1528 +        return createBuiltinPanelConfig(context, panelType, EnumSet.noneOf(PanelConfig.Flags.class));
  1.1529 +    }
  1.1530 +
  1.1531 +    public static PanelConfig createBuiltinPanelConfig(Context context, PanelType panelType, EnumSet<PanelConfig.Flags> flags) {
  1.1532 +        int titleId = 0;
  1.1533 +        String id = null;
  1.1534 +
  1.1535 +        switch(panelType) {
  1.1536 +            case TOP_SITES:
  1.1537 +                titleId = R.string.home_top_sites_title;
  1.1538 +                id = TOP_SITES_PANEL_ID;
  1.1539 +                break;
  1.1540 +
  1.1541 +            case BOOKMARKS:
  1.1542 +                titleId = R.string.bookmarks_title;
  1.1543 +                id = BOOKMARKS_PANEL_ID;
  1.1544 +                break;
  1.1545 +
  1.1546 +            case HISTORY:
  1.1547 +                titleId = R.string.home_history_title;
  1.1548 +                id = HISTORY_PANEL_ID;
  1.1549 +                break;
  1.1550 +
  1.1551 +            case READING_LIST:
  1.1552 +                titleId = R.string.reading_list_title;
  1.1553 +                id = READING_LIST_PANEL_ID;
  1.1554 +                break;
  1.1555 +
  1.1556 +            case DYNAMIC:
  1.1557 +                throw new IllegalArgumentException("createBuiltinPanelConfig() is only for built-in panels");
  1.1558 +        }
  1.1559 +
  1.1560 +        return new PanelConfig(panelType, context.getString(titleId), id, flags);
  1.1561 +    }
  1.1562 +
  1.1563 +    public static HomeConfig getDefault(Context context) {
  1.1564 +        return new HomeConfig(new HomeConfigPrefsBackend(context));
  1.1565 +    }
  1.1566 +}

mercurial