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 +}