mobile/android/base/home/HomeConfig.java

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
michael@0 2 * This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 package org.mozilla.gecko.home;
michael@0 7
michael@0 8 import org.mozilla.gecko.GeckoAppShell;
michael@0 9 import org.mozilla.gecko.GeckoEvent;
michael@0 10 import org.mozilla.gecko.R;
michael@0 11 import org.mozilla.gecko.util.ThreadUtils;
michael@0 12
michael@0 13 import org.json.JSONArray;
michael@0 14 import org.json.JSONException;
michael@0 15 import org.json.JSONObject;
michael@0 16
michael@0 17 import android.content.Context;
michael@0 18 import android.os.Parcel;
michael@0 19 import android.os.Parcelable;
michael@0 20 import android.text.TextUtils;
michael@0 21
michael@0 22 import java.util.ArrayList;
michael@0 23 import java.util.Collections;
michael@0 24 import java.util.EnumSet;
michael@0 25 import java.util.HashMap;
michael@0 26 import java.util.Iterator;
michael@0 27 import java.util.LinkedList;
michael@0 28 import java.util.List;
michael@0 29 import java.util.Map;
michael@0 30
michael@0 31 public final class HomeConfig {
michael@0 32 /**
michael@0 33 * Used to determine what type of HomeFragment subclass to use when creating
michael@0 34 * a given panel. With the exception of DYNAMIC, all of these types correspond
michael@0 35 * to a default set of built-in panels. The DYNAMIC panel type is used by
michael@0 36 * third-party services to create panels with varying types of content.
michael@0 37 */
michael@0 38 public static enum PanelType implements Parcelable {
michael@0 39 TOP_SITES("top_sites", TopSitesPanel.class),
michael@0 40 BOOKMARKS("bookmarks", BookmarksPanel.class),
michael@0 41 HISTORY("history", HistoryPanel.class),
michael@0 42 READING_LIST("reading_list", ReadingListPanel.class),
michael@0 43 DYNAMIC("dynamic", DynamicPanel.class);
michael@0 44
michael@0 45 private final String mId;
michael@0 46 private final Class<?> mPanelClass;
michael@0 47
michael@0 48 PanelType(String id, Class<?> panelClass) {
michael@0 49 mId = id;
michael@0 50 mPanelClass = panelClass;
michael@0 51 }
michael@0 52
michael@0 53 public static PanelType fromId(String id) {
michael@0 54 if (id == null) {
michael@0 55 throw new IllegalArgumentException("Could not convert null String to PanelType");
michael@0 56 }
michael@0 57
michael@0 58 for (PanelType panelType : PanelType.values()) {
michael@0 59 if (TextUtils.equals(panelType.mId, id.toLowerCase())) {
michael@0 60 return panelType;
michael@0 61 }
michael@0 62 }
michael@0 63
michael@0 64 throw new IllegalArgumentException("Could not convert String id to PanelType");
michael@0 65 }
michael@0 66
michael@0 67 @Override
michael@0 68 public String toString() {
michael@0 69 return mId;
michael@0 70 }
michael@0 71
michael@0 72 public Class<?> getPanelClass() {
michael@0 73 return mPanelClass;
michael@0 74 }
michael@0 75
michael@0 76 @Override
michael@0 77 public int describeContents() {
michael@0 78 return 0;
michael@0 79 }
michael@0 80
michael@0 81 @Override
michael@0 82 public void writeToParcel(Parcel dest, int flags) {
michael@0 83 dest.writeInt(ordinal());
michael@0 84 }
michael@0 85
michael@0 86 public static final Creator<PanelType> CREATOR = new Creator<PanelType>() {
michael@0 87 @Override
michael@0 88 public PanelType createFromParcel(final Parcel source) {
michael@0 89 return PanelType.values()[source.readInt()];
michael@0 90 }
michael@0 91
michael@0 92 @Override
michael@0 93 public PanelType[] newArray(final int size) {
michael@0 94 return new PanelType[size];
michael@0 95 }
michael@0 96 };
michael@0 97 }
michael@0 98
michael@0 99 public static class PanelConfig implements Parcelable {
michael@0 100 private final PanelType mType;
michael@0 101 private final String mTitle;
michael@0 102 private final String mId;
michael@0 103 private final LayoutType mLayoutType;
michael@0 104 private final List<ViewConfig> mViews;
michael@0 105 private final AuthConfig mAuthConfig;
michael@0 106 private final EnumSet<Flags> mFlags;
michael@0 107
michael@0 108 private static final String JSON_KEY_TYPE = "type";
michael@0 109 private static final String JSON_KEY_TITLE = "title";
michael@0 110 private static final String JSON_KEY_ID = "id";
michael@0 111 private static final String JSON_KEY_LAYOUT = "layout";
michael@0 112 private static final String JSON_KEY_VIEWS = "views";
michael@0 113 private static final String JSON_KEY_AUTH_CONFIG = "authConfig";
michael@0 114 private static final String JSON_KEY_DEFAULT = "default";
michael@0 115 private static final String JSON_KEY_DISABLED = "disabled";
michael@0 116
michael@0 117 public enum Flags {
michael@0 118 DEFAULT_PANEL,
michael@0 119 DISABLED_PANEL
michael@0 120 }
michael@0 121
michael@0 122 public PanelConfig(JSONObject json) throws JSONException, IllegalArgumentException {
michael@0 123 final String panelType = json.optString(JSON_KEY_TYPE, null);
michael@0 124 if (TextUtils.isEmpty(panelType)) {
michael@0 125 mType = PanelType.DYNAMIC;
michael@0 126 } else {
michael@0 127 mType = PanelType.fromId(panelType);
michael@0 128 }
michael@0 129
michael@0 130 mTitle = json.getString(JSON_KEY_TITLE);
michael@0 131 mId = json.getString(JSON_KEY_ID);
michael@0 132
michael@0 133 final String layoutTypeId = json.optString(JSON_KEY_LAYOUT, null);
michael@0 134 if (layoutTypeId != null) {
michael@0 135 mLayoutType = LayoutType.fromId(layoutTypeId);
michael@0 136 } else {
michael@0 137 mLayoutType = null;
michael@0 138 }
michael@0 139
michael@0 140 final JSONArray jsonViews = json.optJSONArray(JSON_KEY_VIEWS);
michael@0 141 if (jsonViews != null) {
michael@0 142 mViews = new ArrayList<ViewConfig>();
michael@0 143
michael@0 144 final int viewCount = jsonViews.length();
michael@0 145 for (int i = 0; i < viewCount; i++) {
michael@0 146 final JSONObject jsonViewConfig = (JSONObject) jsonViews.get(i);
michael@0 147 final ViewConfig viewConfig = new ViewConfig(i, jsonViewConfig);
michael@0 148 mViews.add(viewConfig);
michael@0 149 }
michael@0 150 } else {
michael@0 151 mViews = null;
michael@0 152 }
michael@0 153
michael@0 154 final JSONObject jsonAuthConfig = json.optJSONObject(JSON_KEY_AUTH_CONFIG);
michael@0 155 if (jsonAuthConfig != null) {
michael@0 156 mAuthConfig = new AuthConfig(jsonAuthConfig);
michael@0 157 } else {
michael@0 158 mAuthConfig = null;
michael@0 159 }
michael@0 160
michael@0 161 mFlags = EnumSet.noneOf(Flags.class);
michael@0 162
michael@0 163 if (json.optBoolean(JSON_KEY_DEFAULT, false)) {
michael@0 164 mFlags.add(Flags.DEFAULT_PANEL);
michael@0 165 }
michael@0 166
michael@0 167 if (json.optBoolean(JSON_KEY_DISABLED, false)) {
michael@0 168 mFlags.add(Flags.DISABLED_PANEL);
michael@0 169 }
michael@0 170
michael@0 171 validate();
michael@0 172 }
michael@0 173
michael@0 174 @SuppressWarnings("unchecked")
michael@0 175 public PanelConfig(Parcel in) {
michael@0 176 mType = (PanelType) in.readParcelable(getClass().getClassLoader());
michael@0 177 mTitle = in.readString();
michael@0 178 mId = in.readString();
michael@0 179 mLayoutType = (LayoutType) in.readParcelable(getClass().getClassLoader());
michael@0 180
michael@0 181 mViews = new ArrayList<ViewConfig>();
michael@0 182 in.readTypedList(mViews, ViewConfig.CREATOR);
michael@0 183
michael@0 184 mAuthConfig = (AuthConfig) in.readParcelable(getClass().getClassLoader());
michael@0 185
michael@0 186 mFlags = (EnumSet<Flags>) in.readSerializable();
michael@0 187
michael@0 188 validate();
michael@0 189 }
michael@0 190
michael@0 191 public PanelConfig(PanelConfig panelConfig) {
michael@0 192 mType = panelConfig.mType;
michael@0 193 mTitle = panelConfig.mTitle;
michael@0 194 mId = panelConfig.mId;
michael@0 195 mLayoutType = panelConfig.mLayoutType;
michael@0 196
michael@0 197 mViews = new ArrayList<ViewConfig>();
michael@0 198 List<ViewConfig> viewConfigs = panelConfig.mViews;
michael@0 199 if (viewConfigs != null) {
michael@0 200 for (ViewConfig viewConfig : viewConfigs) {
michael@0 201 mViews.add(new ViewConfig(viewConfig));
michael@0 202 }
michael@0 203 }
michael@0 204
michael@0 205 mAuthConfig = panelConfig.mAuthConfig;
michael@0 206 mFlags = panelConfig.mFlags.clone();
michael@0 207
michael@0 208 validate();
michael@0 209 }
michael@0 210
michael@0 211 public PanelConfig(PanelType type, String title, String id) {
michael@0 212 this(type, title, id, EnumSet.noneOf(Flags.class));
michael@0 213 }
michael@0 214
michael@0 215 public PanelConfig(PanelType type, String title, String id, EnumSet<Flags> flags) {
michael@0 216 this(type, title, id, null, null, null, flags);
michael@0 217 }
michael@0 218
michael@0 219 public PanelConfig(PanelType type, String title, String id, LayoutType layoutType,
michael@0 220 List<ViewConfig> views, AuthConfig authConfig, EnumSet<Flags> flags) {
michael@0 221 mType = type;
michael@0 222 mTitle = title;
michael@0 223 mId = id;
michael@0 224 mLayoutType = layoutType;
michael@0 225 mViews = views;
michael@0 226 mAuthConfig = authConfig;
michael@0 227 mFlags = flags;
michael@0 228
michael@0 229 validate();
michael@0 230 }
michael@0 231
michael@0 232 private void validate() {
michael@0 233 if (mType == null) {
michael@0 234 throw new IllegalArgumentException("Can't create PanelConfig with null type");
michael@0 235 }
michael@0 236
michael@0 237 if (TextUtils.isEmpty(mTitle)) {
michael@0 238 throw new IllegalArgumentException("Can't create PanelConfig with empty title");
michael@0 239 }
michael@0 240
michael@0 241 if (TextUtils.isEmpty(mId)) {
michael@0 242 throw new IllegalArgumentException("Can't create PanelConfig with empty id");
michael@0 243 }
michael@0 244
michael@0 245 if (mLayoutType == null && mType == PanelType.DYNAMIC) {
michael@0 246 throw new IllegalArgumentException("Can't create a dynamic PanelConfig with null layout type");
michael@0 247 }
michael@0 248
michael@0 249 if ((mViews == null || mViews.size() == 0) && mType == PanelType.DYNAMIC) {
michael@0 250 throw new IllegalArgumentException("Can't create a dynamic PanelConfig with no views");
michael@0 251 }
michael@0 252
michael@0 253 if (mFlags == null) {
michael@0 254 throw new IllegalArgumentException("Can't create PanelConfig with null flags");
michael@0 255 }
michael@0 256 }
michael@0 257
michael@0 258 public PanelType getType() {
michael@0 259 return mType;
michael@0 260 }
michael@0 261
michael@0 262 public String getTitle() {
michael@0 263 return mTitle;
michael@0 264 }
michael@0 265
michael@0 266 public String getId() {
michael@0 267 return mId;
michael@0 268 }
michael@0 269
michael@0 270 public LayoutType getLayoutType() {
michael@0 271 return mLayoutType;
michael@0 272 }
michael@0 273
michael@0 274 public int getViewCount() {
michael@0 275 return (mViews != null ? mViews.size() : 0);
michael@0 276 }
michael@0 277
michael@0 278 public ViewConfig getViewAt(int index) {
michael@0 279 return (mViews != null ? mViews.get(index) : null);
michael@0 280 }
michael@0 281
michael@0 282 public boolean isDynamic() {
michael@0 283 return (mType == PanelType.DYNAMIC);
michael@0 284 }
michael@0 285
michael@0 286 public boolean isDefault() {
michael@0 287 return mFlags.contains(Flags.DEFAULT_PANEL);
michael@0 288 }
michael@0 289
michael@0 290 private void setIsDefault(boolean isDefault) {
michael@0 291 if (isDefault) {
michael@0 292 mFlags.add(Flags.DEFAULT_PANEL);
michael@0 293 } else {
michael@0 294 mFlags.remove(Flags.DEFAULT_PANEL);
michael@0 295 }
michael@0 296 }
michael@0 297
michael@0 298 public boolean isDisabled() {
michael@0 299 return mFlags.contains(Flags.DISABLED_PANEL);
michael@0 300 }
michael@0 301
michael@0 302 private void setIsDisabled(boolean isDisabled) {
michael@0 303 if (isDisabled) {
michael@0 304 mFlags.add(Flags.DISABLED_PANEL);
michael@0 305 } else {
michael@0 306 mFlags.remove(Flags.DISABLED_PANEL);
michael@0 307 }
michael@0 308 }
michael@0 309
michael@0 310 public AuthConfig getAuthConfig() {
michael@0 311 return mAuthConfig;
michael@0 312 }
michael@0 313
michael@0 314 public JSONObject toJSON() throws JSONException {
michael@0 315 final JSONObject json = new JSONObject();
michael@0 316
michael@0 317 json.put(JSON_KEY_TYPE, mType.toString());
michael@0 318 json.put(JSON_KEY_TITLE, mTitle);
michael@0 319 json.put(JSON_KEY_ID, mId);
michael@0 320
michael@0 321 if (mLayoutType != null) {
michael@0 322 json.put(JSON_KEY_LAYOUT, mLayoutType.toString());
michael@0 323 }
michael@0 324
michael@0 325 if (mViews != null) {
michael@0 326 final JSONArray jsonViews = new JSONArray();
michael@0 327
michael@0 328 final int viewCount = mViews.size();
michael@0 329 for (int i = 0; i < viewCount; i++) {
michael@0 330 final ViewConfig viewConfig = mViews.get(i);
michael@0 331 final JSONObject jsonViewConfig = viewConfig.toJSON();
michael@0 332 jsonViews.put(jsonViewConfig);
michael@0 333 }
michael@0 334
michael@0 335 json.put(JSON_KEY_VIEWS, jsonViews);
michael@0 336 }
michael@0 337
michael@0 338 if (mAuthConfig != null) {
michael@0 339 json.put(JSON_KEY_AUTH_CONFIG, mAuthConfig.toJSON());
michael@0 340 }
michael@0 341
michael@0 342 if (mFlags.contains(Flags.DEFAULT_PANEL)) {
michael@0 343 json.put(JSON_KEY_DEFAULT, true);
michael@0 344 }
michael@0 345
michael@0 346 if (mFlags.contains(Flags.DISABLED_PANEL)) {
michael@0 347 json.put(JSON_KEY_DISABLED, true);
michael@0 348 }
michael@0 349
michael@0 350 return json;
michael@0 351 }
michael@0 352
michael@0 353 @Override
michael@0 354 public boolean equals(Object o) {
michael@0 355 if (o == null) {
michael@0 356 return false;
michael@0 357 }
michael@0 358
michael@0 359 if (this == o) {
michael@0 360 return true;
michael@0 361 }
michael@0 362
michael@0 363 if (!(o instanceof PanelConfig)) {
michael@0 364 return false;
michael@0 365 }
michael@0 366
michael@0 367 final PanelConfig other = (PanelConfig) o;
michael@0 368 return mId.equals(other.mId);
michael@0 369 }
michael@0 370
michael@0 371 @Override
michael@0 372 public int describeContents() {
michael@0 373 return 0;
michael@0 374 }
michael@0 375
michael@0 376 @Override
michael@0 377 public void writeToParcel(Parcel dest, int flags) {
michael@0 378 dest.writeParcelable(mType, 0);
michael@0 379 dest.writeString(mTitle);
michael@0 380 dest.writeString(mId);
michael@0 381 dest.writeParcelable(mLayoutType, 0);
michael@0 382 dest.writeTypedList(mViews);
michael@0 383 dest.writeParcelable(mAuthConfig, 0);
michael@0 384 dest.writeSerializable(mFlags);
michael@0 385 }
michael@0 386
michael@0 387 public static final Creator<PanelConfig> CREATOR = new Creator<PanelConfig>() {
michael@0 388 @Override
michael@0 389 public PanelConfig createFromParcel(final Parcel in) {
michael@0 390 return new PanelConfig(in);
michael@0 391 }
michael@0 392
michael@0 393 @Override
michael@0 394 public PanelConfig[] newArray(final int size) {
michael@0 395 return new PanelConfig[size];
michael@0 396 }
michael@0 397 };
michael@0 398 }
michael@0 399
michael@0 400 public static enum LayoutType implements Parcelable {
michael@0 401 FRAME("frame");
michael@0 402
michael@0 403 private final String mId;
michael@0 404
michael@0 405 LayoutType(String id) {
michael@0 406 mId = id;
michael@0 407 }
michael@0 408
michael@0 409 public static LayoutType fromId(String id) {
michael@0 410 if (id == null) {
michael@0 411 throw new IllegalArgumentException("Could not convert null String to LayoutType");
michael@0 412 }
michael@0 413
michael@0 414 for (LayoutType layoutType : LayoutType.values()) {
michael@0 415 if (TextUtils.equals(layoutType.mId, id.toLowerCase())) {
michael@0 416 return layoutType;
michael@0 417 }
michael@0 418 }
michael@0 419
michael@0 420 throw new IllegalArgumentException("Could not convert String id to LayoutType");
michael@0 421 }
michael@0 422
michael@0 423 @Override
michael@0 424 public String toString() {
michael@0 425 return mId;
michael@0 426 }
michael@0 427
michael@0 428 @Override
michael@0 429 public int describeContents() {
michael@0 430 return 0;
michael@0 431 }
michael@0 432
michael@0 433 @Override
michael@0 434 public void writeToParcel(Parcel dest, int flags) {
michael@0 435 dest.writeInt(ordinal());
michael@0 436 }
michael@0 437
michael@0 438 public static final Creator<LayoutType> CREATOR = new Creator<LayoutType>() {
michael@0 439 @Override
michael@0 440 public LayoutType createFromParcel(final Parcel source) {
michael@0 441 return LayoutType.values()[source.readInt()];
michael@0 442 }
michael@0 443
michael@0 444 @Override
michael@0 445 public LayoutType[] newArray(final int size) {
michael@0 446 return new LayoutType[size];
michael@0 447 }
michael@0 448 };
michael@0 449 }
michael@0 450
michael@0 451 public static enum ViewType implements Parcelable {
michael@0 452 LIST("list"),
michael@0 453 GRID("grid");
michael@0 454
michael@0 455 private final String mId;
michael@0 456
michael@0 457 ViewType(String id) {
michael@0 458 mId = id;
michael@0 459 }
michael@0 460
michael@0 461 public static ViewType fromId(String id) {
michael@0 462 if (id == null) {
michael@0 463 throw new IllegalArgumentException("Could not convert null String to ViewType");
michael@0 464 }
michael@0 465
michael@0 466 for (ViewType viewType : ViewType.values()) {
michael@0 467 if (TextUtils.equals(viewType.mId, id.toLowerCase())) {
michael@0 468 return viewType;
michael@0 469 }
michael@0 470 }
michael@0 471
michael@0 472 throw new IllegalArgumentException("Could not convert String id to ViewType");
michael@0 473 }
michael@0 474
michael@0 475 @Override
michael@0 476 public String toString() {
michael@0 477 return mId;
michael@0 478 }
michael@0 479
michael@0 480 @Override
michael@0 481 public int describeContents() {
michael@0 482 return 0;
michael@0 483 }
michael@0 484
michael@0 485 @Override
michael@0 486 public void writeToParcel(Parcel dest, int flags) {
michael@0 487 dest.writeInt(ordinal());
michael@0 488 }
michael@0 489
michael@0 490 public static final Creator<ViewType> CREATOR = new Creator<ViewType>() {
michael@0 491 @Override
michael@0 492 public ViewType createFromParcel(final Parcel source) {
michael@0 493 return ViewType.values()[source.readInt()];
michael@0 494 }
michael@0 495
michael@0 496 @Override
michael@0 497 public ViewType[] newArray(final int size) {
michael@0 498 return new ViewType[size];
michael@0 499 }
michael@0 500 };
michael@0 501 }
michael@0 502
michael@0 503 public static enum ItemType implements Parcelable {
michael@0 504 ARTICLE("article"),
michael@0 505 IMAGE("image");
michael@0 506
michael@0 507 private final String mId;
michael@0 508
michael@0 509 ItemType(String id) {
michael@0 510 mId = id;
michael@0 511 }
michael@0 512
michael@0 513 public static ItemType fromId(String id) {
michael@0 514 if (id == null) {
michael@0 515 throw new IllegalArgumentException("Could not convert null String to ItemType");
michael@0 516 }
michael@0 517
michael@0 518 for (ItemType itemType : ItemType.values()) {
michael@0 519 if (TextUtils.equals(itemType.mId, id.toLowerCase())) {
michael@0 520 return itemType;
michael@0 521 }
michael@0 522 }
michael@0 523
michael@0 524 throw new IllegalArgumentException("Could not convert String id to ItemType");
michael@0 525 }
michael@0 526
michael@0 527 @Override
michael@0 528 public String toString() {
michael@0 529 return mId;
michael@0 530 }
michael@0 531
michael@0 532 @Override
michael@0 533 public int describeContents() {
michael@0 534 return 0;
michael@0 535 }
michael@0 536
michael@0 537 @Override
michael@0 538 public void writeToParcel(Parcel dest, int flags) {
michael@0 539 dest.writeInt(ordinal());
michael@0 540 }
michael@0 541
michael@0 542 public static final Creator<ItemType> CREATOR = new Creator<ItemType>() {
michael@0 543 @Override
michael@0 544 public ItemType createFromParcel(final Parcel source) {
michael@0 545 return ItemType.values()[source.readInt()];
michael@0 546 }
michael@0 547
michael@0 548 @Override
michael@0 549 public ItemType[] newArray(final int size) {
michael@0 550 return new ItemType[size];
michael@0 551 }
michael@0 552 };
michael@0 553 }
michael@0 554
michael@0 555 public static enum ItemHandler implements Parcelable {
michael@0 556 BROWSER("browser"),
michael@0 557 INTENT("intent");
michael@0 558
michael@0 559 private final String mId;
michael@0 560
michael@0 561 ItemHandler(String id) {
michael@0 562 mId = id;
michael@0 563 }
michael@0 564
michael@0 565 public static ItemHandler fromId(String id) {
michael@0 566 if (id == null) {
michael@0 567 throw new IllegalArgumentException("Could not convert null String to ItemHandler");
michael@0 568 }
michael@0 569
michael@0 570 for (ItemHandler itemHandler : ItemHandler.values()) {
michael@0 571 if (TextUtils.equals(itemHandler.mId, id.toLowerCase())) {
michael@0 572 return itemHandler;
michael@0 573 }
michael@0 574 }
michael@0 575
michael@0 576 throw new IllegalArgumentException("Could not convert String id to ItemHandler");
michael@0 577 }
michael@0 578
michael@0 579 @Override
michael@0 580 public String toString() {
michael@0 581 return mId;
michael@0 582 }
michael@0 583
michael@0 584 @Override
michael@0 585 public int describeContents() {
michael@0 586 return 0;
michael@0 587 }
michael@0 588
michael@0 589 @Override
michael@0 590 public void writeToParcel(Parcel dest, int flags) {
michael@0 591 dest.writeInt(ordinal());
michael@0 592 }
michael@0 593
michael@0 594 public static final Creator<ItemHandler> CREATOR = new Creator<ItemHandler>() {
michael@0 595 @Override
michael@0 596 public ItemHandler createFromParcel(final Parcel source) {
michael@0 597 return ItemHandler.values()[source.readInt()];
michael@0 598 }
michael@0 599
michael@0 600 @Override
michael@0 601 public ItemHandler[] newArray(final int size) {
michael@0 602 return new ItemHandler[size];
michael@0 603 }
michael@0 604 };
michael@0 605 }
michael@0 606
michael@0 607 public static class ViewConfig implements Parcelable {
michael@0 608 private final int mIndex;
michael@0 609 private final ViewType mType;
michael@0 610 private final String mDatasetId;
michael@0 611 private final ItemType mItemType;
michael@0 612 private final ItemHandler mItemHandler;
michael@0 613 private final String mBackImageUrl;
michael@0 614 private final String mFilter;
michael@0 615 private final EmptyViewConfig mEmptyViewConfig;
michael@0 616 private final EnumSet<Flags> mFlags;
michael@0 617
michael@0 618 private static final String JSON_KEY_TYPE = "type";
michael@0 619 private static final String JSON_KEY_DATASET = "dataset";
michael@0 620 private static final String JSON_KEY_ITEM_TYPE = "itemType";
michael@0 621 private static final String JSON_KEY_ITEM_HANDLER = "itemHandler";
michael@0 622 private static final String JSON_KEY_BACK_IMAGE_URL = "backImageUrl";
michael@0 623 private static final String JSON_KEY_FILTER = "filter";
michael@0 624 private static final String JSON_KEY_EMPTY = "empty";
michael@0 625 private static final String JSON_KEY_REFRESH_ENABLED = "refreshEnabled";
michael@0 626
michael@0 627 public enum Flags {
michael@0 628 REFRESH_ENABLED
michael@0 629 }
michael@0 630
michael@0 631 public ViewConfig(int index, JSONObject json) throws JSONException, IllegalArgumentException {
michael@0 632 mIndex = index;
michael@0 633 mType = ViewType.fromId(json.getString(JSON_KEY_TYPE));
michael@0 634 mDatasetId = json.getString(JSON_KEY_DATASET);
michael@0 635 mItemType = ItemType.fromId(json.getString(JSON_KEY_ITEM_TYPE));
michael@0 636 mItemHandler = ItemHandler.fromId(json.getString(JSON_KEY_ITEM_HANDLER));
michael@0 637 mBackImageUrl = json.optString(JSON_KEY_BACK_IMAGE_URL, null);
michael@0 638 mFilter = json.optString(JSON_KEY_FILTER, null);
michael@0 639
michael@0 640 final JSONObject jsonEmptyViewConfig = json.optJSONObject(JSON_KEY_EMPTY);
michael@0 641 if (jsonEmptyViewConfig != null) {
michael@0 642 mEmptyViewConfig = new EmptyViewConfig(jsonEmptyViewConfig);
michael@0 643 } else {
michael@0 644 mEmptyViewConfig = null;
michael@0 645 }
michael@0 646
michael@0 647 mFlags = EnumSet.noneOf(Flags.class);
michael@0 648 if (json.optBoolean(JSON_KEY_REFRESH_ENABLED, false)) {
michael@0 649 mFlags.add(Flags.REFRESH_ENABLED);
michael@0 650 }
michael@0 651
michael@0 652 validate();
michael@0 653 }
michael@0 654
michael@0 655 @SuppressWarnings("unchecked")
michael@0 656 public ViewConfig(Parcel in) {
michael@0 657 mIndex = in.readInt();
michael@0 658 mType = (ViewType) in.readParcelable(getClass().getClassLoader());
michael@0 659 mDatasetId = in.readString();
michael@0 660 mItemType = (ItemType) in.readParcelable(getClass().getClassLoader());
michael@0 661 mItemHandler = (ItemHandler) in.readParcelable(getClass().getClassLoader());
michael@0 662 mBackImageUrl = in.readString();
michael@0 663 mFilter = in.readString();
michael@0 664 mEmptyViewConfig = (EmptyViewConfig) in.readParcelable(getClass().getClassLoader());
michael@0 665 mFlags = (EnumSet<Flags>) in.readSerializable();
michael@0 666
michael@0 667 validate();
michael@0 668 }
michael@0 669
michael@0 670 public ViewConfig(ViewConfig viewConfig) {
michael@0 671 mIndex = viewConfig.mIndex;
michael@0 672 mType = viewConfig.mType;
michael@0 673 mDatasetId = viewConfig.mDatasetId;
michael@0 674 mItemType = viewConfig.mItemType;
michael@0 675 mItemHandler = viewConfig.mItemHandler;
michael@0 676 mBackImageUrl = viewConfig.mBackImageUrl;
michael@0 677 mFilter = viewConfig.mFilter;
michael@0 678 mEmptyViewConfig = viewConfig.mEmptyViewConfig;
michael@0 679 mFlags = viewConfig.mFlags.clone();
michael@0 680
michael@0 681 validate();
michael@0 682 }
michael@0 683
michael@0 684 public ViewConfig(int index, ViewType type, String datasetId, ItemType itemType,
michael@0 685 ItemHandler itemHandler, String backImageUrl, String filter,
michael@0 686 EmptyViewConfig emptyViewConfig, EnumSet<Flags> flags) {
michael@0 687 mIndex = index;
michael@0 688 mType = type;
michael@0 689 mDatasetId = datasetId;
michael@0 690 mItemType = itemType;
michael@0 691 mItemHandler = itemHandler;
michael@0 692 mBackImageUrl = backImageUrl;
michael@0 693 mFilter = filter;
michael@0 694 mEmptyViewConfig = emptyViewConfig;
michael@0 695 mFlags = flags;
michael@0 696
michael@0 697 validate();
michael@0 698 }
michael@0 699
michael@0 700 private void validate() {
michael@0 701 if (mType == null) {
michael@0 702 throw new IllegalArgumentException("Can't create ViewConfig with null type");
michael@0 703 }
michael@0 704
michael@0 705 if (TextUtils.isEmpty(mDatasetId)) {
michael@0 706 throw new IllegalArgumentException("Can't create ViewConfig with empty dataset ID");
michael@0 707 }
michael@0 708
michael@0 709 if (mItemType == null) {
michael@0 710 throw new IllegalArgumentException("Can't create ViewConfig with null item type");
michael@0 711 }
michael@0 712
michael@0 713 if (mItemHandler == null) {
michael@0 714 throw new IllegalArgumentException("Can't create ViewConfig with null item handler");
michael@0 715 }
michael@0 716
michael@0 717 if (mFlags == null) {
michael@0 718 throw new IllegalArgumentException("Can't create ViewConfig with null flags");
michael@0 719 }
michael@0 720 }
michael@0 721
michael@0 722 public int getIndex() {
michael@0 723 return mIndex;
michael@0 724 }
michael@0 725
michael@0 726 public ViewType getType() {
michael@0 727 return mType;
michael@0 728 }
michael@0 729
michael@0 730 public String getDatasetId() {
michael@0 731 return mDatasetId;
michael@0 732 }
michael@0 733
michael@0 734 public ItemType getItemType() {
michael@0 735 return mItemType;
michael@0 736 }
michael@0 737
michael@0 738 public ItemHandler getItemHandler() {
michael@0 739 return mItemHandler;
michael@0 740 }
michael@0 741
michael@0 742 public String getBackImageUrl() {
michael@0 743 return mBackImageUrl;
michael@0 744 }
michael@0 745
michael@0 746 public String getFilter() {
michael@0 747 return mFilter;
michael@0 748 }
michael@0 749
michael@0 750 public EmptyViewConfig getEmptyViewConfig() {
michael@0 751 return mEmptyViewConfig;
michael@0 752 }
michael@0 753
michael@0 754 public boolean isRefreshEnabled() {
michael@0 755 return mFlags.contains(Flags.REFRESH_ENABLED);
michael@0 756 }
michael@0 757
michael@0 758 public JSONObject toJSON() throws JSONException {
michael@0 759 final JSONObject json = new JSONObject();
michael@0 760
michael@0 761 json.put(JSON_KEY_TYPE, mType.toString());
michael@0 762 json.put(JSON_KEY_DATASET, mDatasetId);
michael@0 763 json.put(JSON_KEY_ITEM_TYPE, mItemType.toString());
michael@0 764 json.put(JSON_KEY_ITEM_HANDLER, mItemHandler.toString());
michael@0 765
michael@0 766 if (!TextUtils.isEmpty(mBackImageUrl)) {
michael@0 767 json.put(JSON_KEY_BACK_IMAGE_URL, mBackImageUrl);
michael@0 768 }
michael@0 769
michael@0 770 if (!TextUtils.isEmpty(mFilter)) {
michael@0 771 json.put(JSON_KEY_FILTER, mFilter);
michael@0 772 }
michael@0 773
michael@0 774 if (mEmptyViewConfig != null) {
michael@0 775 json.put(JSON_KEY_EMPTY, mEmptyViewConfig.toJSON());
michael@0 776 }
michael@0 777
michael@0 778 if (mFlags.contains(Flags.REFRESH_ENABLED)) {
michael@0 779 json.put(JSON_KEY_REFRESH_ENABLED, true);
michael@0 780 }
michael@0 781
michael@0 782 return json;
michael@0 783 }
michael@0 784
michael@0 785 @Override
michael@0 786 public int describeContents() {
michael@0 787 return 0;
michael@0 788 }
michael@0 789
michael@0 790 @Override
michael@0 791 public void writeToParcel(Parcel dest, int flags) {
michael@0 792 dest.writeInt(mIndex);
michael@0 793 dest.writeParcelable(mType, 0);
michael@0 794 dest.writeString(mDatasetId);
michael@0 795 dest.writeParcelable(mItemType, 0);
michael@0 796 dest.writeParcelable(mItemHandler, 0);
michael@0 797 dest.writeString(mBackImageUrl);
michael@0 798 dest.writeString(mFilter);
michael@0 799 dest.writeParcelable(mEmptyViewConfig, 0);
michael@0 800 dest.writeSerializable(mFlags);
michael@0 801 }
michael@0 802
michael@0 803 public static final Creator<ViewConfig> CREATOR = new Creator<ViewConfig>() {
michael@0 804 @Override
michael@0 805 public ViewConfig createFromParcel(final Parcel in) {
michael@0 806 return new ViewConfig(in);
michael@0 807 }
michael@0 808
michael@0 809 @Override
michael@0 810 public ViewConfig[] newArray(final int size) {
michael@0 811 return new ViewConfig[size];
michael@0 812 }
michael@0 813 };
michael@0 814 }
michael@0 815
michael@0 816 public static class EmptyViewConfig implements Parcelable {
michael@0 817 private final String mText;
michael@0 818 private final String mImageUrl;
michael@0 819
michael@0 820 private static final String JSON_KEY_TEXT = "text";
michael@0 821 private static final String JSON_KEY_IMAGE_URL = "imageUrl";
michael@0 822
michael@0 823 public EmptyViewConfig(JSONObject json) throws JSONException, IllegalArgumentException {
michael@0 824 mText = json.optString(JSON_KEY_TEXT, null);
michael@0 825 mImageUrl = json.optString(JSON_KEY_IMAGE_URL, null);
michael@0 826 }
michael@0 827
michael@0 828 @SuppressWarnings("unchecked")
michael@0 829 public EmptyViewConfig(Parcel in) {
michael@0 830 mText = in.readString();
michael@0 831 mImageUrl = in.readString();
michael@0 832 }
michael@0 833
michael@0 834 public EmptyViewConfig(EmptyViewConfig emptyViewConfig) {
michael@0 835 mText = emptyViewConfig.mText;
michael@0 836 mImageUrl = emptyViewConfig.mImageUrl;
michael@0 837 }
michael@0 838
michael@0 839 public EmptyViewConfig(String text, String imageUrl) {
michael@0 840 mText = text;
michael@0 841 mImageUrl = imageUrl;
michael@0 842 }
michael@0 843
michael@0 844 public String getText() {
michael@0 845 return mText;
michael@0 846 }
michael@0 847
michael@0 848 public String getImageUrl() {
michael@0 849 return mImageUrl;
michael@0 850 }
michael@0 851
michael@0 852 public JSONObject toJSON() throws JSONException {
michael@0 853 final JSONObject json = new JSONObject();
michael@0 854
michael@0 855 json.put(JSON_KEY_TEXT, mText);
michael@0 856 json.put(JSON_KEY_IMAGE_URL, mImageUrl);
michael@0 857
michael@0 858 return json;
michael@0 859 }
michael@0 860
michael@0 861 @Override
michael@0 862 public int describeContents() {
michael@0 863 return 0;
michael@0 864 }
michael@0 865
michael@0 866 @Override
michael@0 867 public void writeToParcel(Parcel dest, int flags) {
michael@0 868 dest.writeString(mText);
michael@0 869 dest.writeString(mImageUrl);
michael@0 870 }
michael@0 871
michael@0 872 public static final Creator<EmptyViewConfig> CREATOR = new Creator<EmptyViewConfig>() {
michael@0 873 @Override
michael@0 874 public EmptyViewConfig createFromParcel(final Parcel in) {
michael@0 875 return new EmptyViewConfig(in);
michael@0 876 }
michael@0 877
michael@0 878 @Override
michael@0 879 public EmptyViewConfig[] newArray(final int size) {
michael@0 880 return new EmptyViewConfig[size];
michael@0 881 }
michael@0 882 };
michael@0 883 }
michael@0 884
michael@0 885 public static class AuthConfig implements Parcelable {
michael@0 886 private final String mMessageText;
michael@0 887 private final String mButtonText;
michael@0 888 private final String mImageUrl;
michael@0 889
michael@0 890 private static final String JSON_KEY_MESSAGE_TEXT = "messageText";
michael@0 891 private static final String JSON_KEY_BUTTON_TEXT = "buttonText";
michael@0 892 private static final String JSON_KEY_IMAGE_URL = "imageUrl";
michael@0 893
michael@0 894 public AuthConfig(JSONObject json) throws JSONException, IllegalArgumentException {
michael@0 895 mMessageText = json.optString(JSON_KEY_MESSAGE_TEXT);
michael@0 896 mButtonText = json.optString(JSON_KEY_BUTTON_TEXT);
michael@0 897 mImageUrl = json.optString(JSON_KEY_IMAGE_URL, null);
michael@0 898 }
michael@0 899
michael@0 900 @SuppressWarnings("unchecked")
michael@0 901 public AuthConfig(Parcel in) {
michael@0 902 mMessageText = in.readString();
michael@0 903 mButtonText = in.readString();
michael@0 904 mImageUrl = in.readString();
michael@0 905
michael@0 906 validate();
michael@0 907 }
michael@0 908
michael@0 909 public AuthConfig(AuthConfig authConfig) {
michael@0 910 mMessageText = authConfig.mMessageText;
michael@0 911 mButtonText = authConfig.mButtonText;
michael@0 912 mImageUrl = authConfig.mImageUrl;
michael@0 913
michael@0 914 validate();
michael@0 915 }
michael@0 916
michael@0 917 public AuthConfig(String messageText, String buttonText, String imageUrl) {
michael@0 918 mMessageText = messageText;
michael@0 919 mButtonText = buttonText;
michael@0 920 mImageUrl = imageUrl;
michael@0 921
michael@0 922 validate();
michael@0 923 }
michael@0 924
michael@0 925 private void validate() {
michael@0 926 if (mMessageText == null) {
michael@0 927 throw new IllegalArgumentException("Can't create AuthConfig with null message text");
michael@0 928 }
michael@0 929
michael@0 930 if (mButtonText == null) {
michael@0 931 throw new IllegalArgumentException("Can't create AuthConfig with null button text");
michael@0 932 }
michael@0 933 }
michael@0 934
michael@0 935 public String getMessageText() {
michael@0 936 return mMessageText;
michael@0 937 }
michael@0 938
michael@0 939 public String getButtonText() {
michael@0 940 return mButtonText;
michael@0 941 }
michael@0 942
michael@0 943 public String getImageUrl() {
michael@0 944 return mImageUrl;
michael@0 945 }
michael@0 946
michael@0 947 public JSONObject toJSON() throws JSONException {
michael@0 948 final JSONObject json = new JSONObject();
michael@0 949
michael@0 950 json.put(JSON_KEY_MESSAGE_TEXT, mMessageText);
michael@0 951 json.put(JSON_KEY_BUTTON_TEXT, mButtonText);
michael@0 952 json.put(JSON_KEY_IMAGE_URL, mImageUrl);
michael@0 953
michael@0 954 return json;
michael@0 955 }
michael@0 956
michael@0 957 @Override
michael@0 958 public int describeContents() {
michael@0 959 return 0;
michael@0 960 }
michael@0 961
michael@0 962 @Override
michael@0 963 public void writeToParcel(Parcel dest, int flags) {
michael@0 964 dest.writeString(mMessageText);
michael@0 965 dest.writeString(mButtonText);
michael@0 966 dest.writeString(mImageUrl);
michael@0 967 }
michael@0 968
michael@0 969 public static final Creator<AuthConfig> CREATOR = new Creator<AuthConfig>() {
michael@0 970 @Override
michael@0 971 public AuthConfig createFromParcel(final Parcel in) {
michael@0 972 return new AuthConfig(in);
michael@0 973 }
michael@0 974
michael@0 975 @Override
michael@0 976 public AuthConfig[] newArray(final int size) {
michael@0 977 return new AuthConfig[size];
michael@0 978 }
michael@0 979 };
michael@0 980 }
michael@0 981 /**
michael@0 982 * Immutable representation of the current state of {@code HomeConfig}.
michael@0 983 * This is what HomeConfig returns from a load() call and takes as
michael@0 984 * input to save a new state.
michael@0 985 *
michael@0 986 * Users of {@code State} should use an {@code Iterator} to iterate
michael@0 987 * through the contained {@code PanelConfig} instances.
michael@0 988 *
michael@0 989 * {@code State} is immutable i.e. you can't add, remove, or update
michael@0 990 * contained elements directly. You have to use an {@code Editor} to
michael@0 991 * change the state, which can be created through the {@code edit()}
michael@0 992 * method.
michael@0 993 */
michael@0 994 public static class State implements Iterable<PanelConfig> {
michael@0 995 private HomeConfig mHomeConfig;
michael@0 996 private final List<PanelConfig> mPanelConfigs;
michael@0 997 private final boolean mIsDefault;
michael@0 998
michael@0 999 State(List<PanelConfig> panelConfigs, boolean isDefault) {
michael@0 1000 this(null, panelConfigs, isDefault);
michael@0 1001 }
michael@0 1002
michael@0 1003 private State(HomeConfig homeConfig, List<PanelConfig> panelConfigs, boolean isDefault) {
michael@0 1004 mHomeConfig = homeConfig;
michael@0 1005 mPanelConfigs = Collections.unmodifiableList(panelConfigs);
michael@0 1006 mIsDefault = isDefault;
michael@0 1007 }
michael@0 1008
michael@0 1009 private void setHomeConfig(HomeConfig homeConfig) {
michael@0 1010 if (mHomeConfig != null) {
michael@0 1011 throw new IllegalStateException("Can't set HomeConfig more than once");
michael@0 1012 }
michael@0 1013
michael@0 1014 mHomeConfig = homeConfig;
michael@0 1015 }
michael@0 1016
michael@0 1017 @Override
michael@0 1018 public Iterator<PanelConfig> iterator() {
michael@0 1019 return mPanelConfigs.iterator();
michael@0 1020 }
michael@0 1021
michael@0 1022 /**
michael@0 1023 * Returns whether this {@code State} instance represents the default
michael@0 1024 * {@code HomeConfig} configuration or not.
michael@0 1025 */
michael@0 1026 public boolean isDefault() {
michael@0 1027 return mIsDefault;
michael@0 1028 }
michael@0 1029
michael@0 1030 /**
michael@0 1031 * Creates an {@code Editor} for this state.
michael@0 1032 */
michael@0 1033 public Editor edit() {
michael@0 1034 return new Editor(mHomeConfig, this);
michael@0 1035 }
michael@0 1036 }
michael@0 1037
michael@0 1038 /**
michael@0 1039 * {@code Editor} allows you to make changes to a {@code State}. You
michael@0 1040 * can create {@code Editor} by calling {@code edit()} on the target
michael@0 1041 * {@code State} instance.
michael@0 1042 *
michael@0 1043 * {@code Editor} works on a copy of the {@code State} that originated
michael@0 1044 * it. This means that adding, removing, or updating panels in an
michael@0 1045 * {@code Editor} will never change the {@code State} which you
michael@0 1046 * created the {@code Editor} from. Calling {@code commit()} or
michael@0 1047 * {@code apply()} will cause the new {@code State} instance to be
michael@0 1048 * created and saved using the {@code HomeConfig} instance that
michael@0 1049 * created the source {@code State}.
michael@0 1050 *
michael@0 1051 * {@code Editor} is *not* thread-safe. You can only make calls on it
michael@0 1052 * from the thread where it was originally created. It will throw an
michael@0 1053 * exception if you don't follow this invariant.
michael@0 1054 */
michael@0 1055 public static class Editor implements Iterable<PanelConfig> {
michael@0 1056 private final HomeConfig mHomeConfig;
michael@0 1057 private final Map<String, PanelConfig> mConfigMap;
michael@0 1058 private final List<String> mConfigOrder;
michael@0 1059 private final List<GeckoEvent> mEventQueue;
michael@0 1060 private final Thread mOriginalThread;
michael@0 1061
michael@0 1062 private PanelConfig mDefaultPanel;
michael@0 1063 private int mEnabledCount;
michael@0 1064
michael@0 1065 private boolean mHasChanged;
michael@0 1066 private final boolean mIsFromDefault;
michael@0 1067
michael@0 1068 private Editor(HomeConfig homeConfig, State configState) {
michael@0 1069 mHomeConfig = homeConfig;
michael@0 1070 mOriginalThread = Thread.currentThread();
michael@0 1071 mConfigMap = new HashMap<String, PanelConfig>();
michael@0 1072 mConfigOrder = new LinkedList<String>();
michael@0 1073 mEventQueue = new LinkedList<GeckoEvent>();
michael@0 1074 mEnabledCount = 0;
michael@0 1075
michael@0 1076 mHasChanged = false;
michael@0 1077 mIsFromDefault = configState.isDefault();
michael@0 1078
michael@0 1079 initFromState(configState);
michael@0 1080 }
michael@0 1081
michael@0 1082 /**
michael@0 1083 * Initialize the initial state of the editor from the given
michael@0 1084 * {@sode State}. A HashMap is used to represent the list of
michael@0 1085 * panels as it provides fast access, and a LinkedList is used to
michael@0 1086 * keep track of order. We keep a reference to the default panel
michael@0 1087 * and the number of enabled panels to avoid iterating through the
michael@0 1088 * map every time we need those.
michael@0 1089 *
michael@0 1090 * @param configState The source State to load the editor from.
michael@0 1091 */
michael@0 1092 private void initFromState(State configState) {
michael@0 1093 for (PanelConfig panelConfig : configState) {
michael@0 1094 final PanelConfig panelCopy = new PanelConfig(panelConfig);
michael@0 1095
michael@0 1096 if (!panelCopy.isDisabled()) {
michael@0 1097 mEnabledCount++;
michael@0 1098 }
michael@0 1099
michael@0 1100 if (panelCopy.isDefault()) {
michael@0 1101 if (mDefaultPanel == null) {
michael@0 1102 mDefaultPanel = panelCopy;
michael@0 1103 } else {
michael@0 1104 throw new IllegalStateException("Multiple default panels in HomeConfig state");
michael@0 1105 }
michael@0 1106 }
michael@0 1107
michael@0 1108 final String panelId = panelConfig.getId();
michael@0 1109 mConfigOrder.add(panelId);
michael@0 1110 mConfigMap.put(panelId, panelCopy);
michael@0 1111 }
michael@0 1112
michael@0 1113 // We should always have a defined default panel if there's
michael@0 1114 // at least one enabled panel around.
michael@0 1115 if (mEnabledCount > 0 && mDefaultPanel == null) {
michael@0 1116 throw new IllegalStateException("Default panel in HomeConfig state is undefined");
michael@0 1117 }
michael@0 1118 }
michael@0 1119
michael@0 1120 private PanelConfig getPanelOrThrow(String panelId) {
michael@0 1121 final PanelConfig panelConfig = mConfigMap.get(panelId);
michael@0 1122 if (panelConfig == null) {
michael@0 1123 throw new IllegalStateException("Tried to access non-existing panel: " + panelId);
michael@0 1124 }
michael@0 1125
michael@0 1126 return panelConfig;
michael@0 1127 }
michael@0 1128
michael@0 1129 private boolean isCurrentDefaultPanel(PanelConfig panelConfig) {
michael@0 1130 if (mDefaultPanel == null) {
michael@0 1131 return false;
michael@0 1132 }
michael@0 1133
michael@0 1134 return mDefaultPanel.equals(panelConfig);
michael@0 1135 }
michael@0 1136
michael@0 1137 private void findNewDefault() {
michael@0 1138 // Pick the first panel that is neither disabled nor currently
michael@0 1139 // set as default.
michael@0 1140 for (PanelConfig panelConfig : mConfigMap.values()) {
michael@0 1141 if (!panelConfig.isDefault() && !panelConfig.isDisabled()) {
michael@0 1142 setDefault(panelConfig.getId());
michael@0 1143 return;
michael@0 1144 }
michael@0 1145 }
michael@0 1146
michael@0 1147 mDefaultPanel = null;
michael@0 1148 }
michael@0 1149
michael@0 1150 /**
michael@0 1151 * Makes an ordered list of PanelConfigs that can be references
michael@0 1152 * or deep copied objects.
michael@0 1153 *
michael@0 1154 * @param deepCopy true to make deep-copied objects
michael@0 1155 * @return ordered List of PanelConfigs
michael@0 1156 */
michael@0 1157 private List<PanelConfig> makeOrderedCopy(boolean deepCopy) {
michael@0 1158 final List<PanelConfig> copiedList = new ArrayList<PanelConfig>(mConfigOrder.size());
michael@0 1159 for (String panelId : mConfigOrder) {
michael@0 1160 PanelConfig panelConfig = mConfigMap.get(panelId);
michael@0 1161 if (deepCopy) {
michael@0 1162 panelConfig = new PanelConfig(panelConfig);
michael@0 1163 }
michael@0 1164 copiedList.add(panelConfig);
michael@0 1165 }
michael@0 1166
michael@0 1167 return copiedList;
michael@0 1168 }
michael@0 1169
michael@0 1170 private void setPanelIsDisabled(PanelConfig panelConfig, boolean disabled) {
michael@0 1171 if (panelConfig.isDisabled() == disabled) {
michael@0 1172 return;
michael@0 1173 }
michael@0 1174
michael@0 1175 panelConfig.setIsDisabled(disabled);
michael@0 1176 mEnabledCount += (disabled ? -1 : 1);
michael@0 1177 }
michael@0 1178
michael@0 1179 /**
michael@0 1180 * Gets the ID of the current default panel.
michael@0 1181 */
michael@0 1182 public String getDefaultPanelId() {
michael@0 1183 ThreadUtils.assertOnThread(mOriginalThread);
michael@0 1184
michael@0 1185 if (mDefaultPanel == null) {
michael@0 1186 return null;
michael@0 1187 }
michael@0 1188
michael@0 1189 return mDefaultPanel.getId();
michael@0 1190 }
michael@0 1191
michael@0 1192 /**
michael@0 1193 * Set a new default panel.
michael@0 1194 *
michael@0 1195 * @param panelId the ID of the new default panel.
michael@0 1196 */
michael@0 1197 public void setDefault(String panelId) {
michael@0 1198 ThreadUtils.assertOnThread(mOriginalThread);
michael@0 1199
michael@0 1200 final PanelConfig panelConfig = getPanelOrThrow(panelId);
michael@0 1201 if (isCurrentDefaultPanel(panelConfig)) {
michael@0 1202 return;
michael@0 1203 }
michael@0 1204
michael@0 1205 if (mDefaultPanel != null) {
michael@0 1206 mDefaultPanel.setIsDefault(false);
michael@0 1207 }
michael@0 1208
michael@0 1209 panelConfig.setIsDefault(true);
michael@0 1210 setPanelIsDisabled(panelConfig, false);
michael@0 1211
michael@0 1212 mDefaultPanel = panelConfig;
michael@0 1213 mHasChanged = true;
michael@0 1214 }
michael@0 1215
michael@0 1216 /**
michael@0 1217 * Toggles disabled state for a panel.
michael@0 1218 *
michael@0 1219 * @param panelId the ID of the target panel.
michael@0 1220 * @param disabled true to disable the panel.
michael@0 1221 */
michael@0 1222 public void setDisabled(String panelId, boolean disabled) {
michael@0 1223 ThreadUtils.assertOnThread(mOriginalThread);
michael@0 1224
michael@0 1225 final PanelConfig panelConfig = getPanelOrThrow(panelId);
michael@0 1226 if (panelConfig.isDisabled() == disabled) {
michael@0 1227 return;
michael@0 1228 }
michael@0 1229
michael@0 1230 setPanelIsDisabled(panelConfig, disabled);
michael@0 1231
michael@0 1232 if (disabled) {
michael@0 1233 if (isCurrentDefaultPanel(panelConfig)) {
michael@0 1234 panelConfig.setIsDefault(false);
michael@0 1235 findNewDefault();
michael@0 1236 }
michael@0 1237 } else if (mEnabledCount == 1) {
michael@0 1238 setDefault(panelId);
michael@0 1239 }
michael@0 1240
michael@0 1241 mHasChanged = true;
michael@0 1242 }
michael@0 1243
michael@0 1244 /**
michael@0 1245 * Adds a new {@code PanelConfig}. It will do nothing if the
michael@0 1246 * {@code Editor} already contains a panel with the same ID.
michael@0 1247 *
michael@0 1248 * @param panelConfig the {@code PanelConfig} instance to be added.
michael@0 1249 * @return true if the item has been added.
michael@0 1250 */
michael@0 1251 public boolean install(PanelConfig panelConfig) {
michael@0 1252 ThreadUtils.assertOnThread(mOriginalThread);
michael@0 1253
michael@0 1254 if (panelConfig == null) {
michael@0 1255 throw new IllegalStateException("Can't install a null panel");
michael@0 1256 }
michael@0 1257
michael@0 1258 if (!panelConfig.isDynamic()) {
michael@0 1259 throw new IllegalStateException("Can't install a built-in panel: " + panelConfig.getId());
michael@0 1260 }
michael@0 1261
michael@0 1262 if (panelConfig.isDisabled()) {
michael@0 1263 throw new IllegalStateException("Can't install a disabled panel: " + panelConfig.getId());
michael@0 1264 }
michael@0 1265
michael@0 1266 boolean installed = false;
michael@0 1267
michael@0 1268 final String id = panelConfig.getId();
michael@0 1269 if (!mConfigMap.containsKey(id)) {
michael@0 1270 mConfigMap.put(id, panelConfig);
michael@0 1271 mConfigOrder.add(id);
michael@0 1272
michael@0 1273 mEnabledCount++;
michael@0 1274 if (mEnabledCount == 1 || panelConfig.isDefault()) {
michael@0 1275 setDefault(panelConfig.getId());
michael@0 1276 }
michael@0 1277
michael@0 1278 installed = true;
michael@0 1279
michael@0 1280 // Add an event to the queue if a new panel is sucessfully installed.
michael@0 1281 mEventQueue.add(GeckoEvent.createBroadcastEvent("HomePanels:Installed", panelConfig.getId()));
michael@0 1282 }
michael@0 1283
michael@0 1284 mHasChanged = true;
michael@0 1285 return installed;
michael@0 1286 }
michael@0 1287
michael@0 1288 /**
michael@0 1289 * Removes an existing panel.
michael@0 1290 *
michael@0 1291 * @return true if the item has been removed.
michael@0 1292 */
michael@0 1293 public boolean uninstall(String panelId) {
michael@0 1294 ThreadUtils.assertOnThread(mOriginalThread);
michael@0 1295
michael@0 1296 final PanelConfig panelConfig = mConfigMap.get(panelId);
michael@0 1297 if (panelConfig == null) {
michael@0 1298 return false;
michael@0 1299 }
michael@0 1300
michael@0 1301 if (!panelConfig.isDynamic()) {
michael@0 1302 throw new IllegalStateException("Can't uninstall a built-in panel: " + panelConfig.getId());
michael@0 1303 }
michael@0 1304
michael@0 1305 mConfigMap.remove(panelId);
michael@0 1306 mConfigOrder.remove(panelId);
michael@0 1307
michael@0 1308 if (!panelConfig.isDisabled()) {
michael@0 1309 mEnabledCount--;
michael@0 1310 }
michael@0 1311
michael@0 1312 if (isCurrentDefaultPanel(panelConfig)) {
michael@0 1313 findNewDefault();
michael@0 1314 }
michael@0 1315
michael@0 1316 // Add an event to the queue if a panel is succesfully uninstalled.
michael@0 1317 mEventQueue.add(GeckoEvent.createBroadcastEvent("HomePanels:Uninstalled", panelId));
michael@0 1318
michael@0 1319 mHasChanged = true;
michael@0 1320 return true;
michael@0 1321 }
michael@0 1322
michael@0 1323 /**
michael@0 1324 * Moves panel associated with panelId to the specified position.
michael@0 1325 *
michael@0 1326 * @param panelId Id of panel
michael@0 1327 * @param destIndex Destination position
michael@0 1328 * @return true if move succeeded
michael@0 1329 */
michael@0 1330 public boolean moveTo(String panelId, int destIndex) {
michael@0 1331 ThreadUtils.assertOnThread(mOriginalThread);
michael@0 1332
michael@0 1333 if (!mConfigOrder.contains(panelId)) {
michael@0 1334 return false;
michael@0 1335 }
michael@0 1336
michael@0 1337 mConfigOrder.remove(panelId);
michael@0 1338 mConfigOrder.add(destIndex, panelId);
michael@0 1339 mHasChanged = true;
michael@0 1340
michael@0 1341 return true;
michael@0 1342 }
michael@0 1343
michael@0 1344 /**
michael@0 1345 * Replaces an existing panel with a new {@code PanelConfig} instance.
michael@0 1346 *
michael@0 1347 * @return true if the item has been updated.
michael@0 1348 */
michael@0 1349 public boolean update(PanelConfig panelConfig) {
michael@0 1350 ThreadUtils.assertOnThread(mOriginalThread);
michael@0 1351
michael@0 1352 if (panelConfig == null) {
michael@0 1353 throw new IllegalStateException("Can't update a null panel");
michael@0 1354 }
michael@0 1355
michael@0 1356 boolean updated = false;
michael@0 1357
michael@0 1358 final String id = panelConfig.getId();
michael@0 1359 if (mConfigMap.containsKey(id)) {
michael@0 1360 final PanelConfig oldPanelConfig = mConfigMap.put(id, panelConfig);
michael@0 1361
michael@0 1362 // The disabled and default states can't never be
michael@0 1363 // changed by an update operation.
michael@0 1364 panelConfig.setIsDefault(oldPanelConfig.isDefault());
michael@0 1365 panelConfig.setIsDisabled(oldPanelConfig.isDisabled());
michael@0 1366
michael@0 1367 updated = true;
michael@0 1368 }
michael@0 1369
michael@0 1370 mHasChanged = true;
michael@0 1371 return updated;
michael@0 1372 }
michael@0 1373
michael@0 1374 /**
michael@0 1375 * Saves the current {@code Editor} state asynchronously in the
michael@0 1376 * background thread.
michael@0 1377 *
michael@0 1378 * @return the resulting {@code State} instance.
michael@0 1379 */
michael@0 1380 public State apply() {
michael@0 1381 ThreadUtils.assertOnThread(mOriginalThread);
michael@0 1382
michael@0 1383 // We're about to save the current state in the background thread
michael@0 1384 // so we should use a deep copy of the PanelConfig instances to
michael@0 1385 // avoid saving corrupted state.
michael@0 1386 final State newConfigState =
michael@0 1387 new State(mHomeConfig, makeOrderedCopy(true), isDefault());
michael@0 1388
michael@0 1389 // Copy the event queue to a new list, so that we only modify mEventQueue on
michael@0 1390 // the original thread where it was created.
michael@0 1391 final LinkedList<GeckoEvent> eventQueueCopy = new LinkedList<GeckoEvent>(mEventQueue);
michael@0 1392 mEventQueue.clear();
michael@0 1393
michael@0 1394 ThreadUtils.getBackgroundHandler().post(new Runnable() {
michael@0 1395 @Override
michael@0 1396 public void run() {
michael@0 1397 mHomeConfig.save(newConfigState);
michael@0 1398
michael@0 1399 // Send pending events after the new config is saved.
michael@0 1400 sendEventsToGecko(eventQueueCopy);
michael@0 1401 }
michael@0 1402 });
michael@0 1403
michael@0 1404 return newConfigState;
michael@0 1405 }
michael@0 1406
michael@0 1407 /**
michael@0 1408 * Saves the current {@code Editor} state synchronously in the
michael@0 1409 * current thread.
michael@0 1410 *
michael@0 1411 * @return the resulting {@code State} instance.
michael@0 1412 */
michael@0 1413 public State commit() {
michael@0 1414 ThreadUtils.assertOnThread(mOriginalThread);
michael@0 1415
michael@0 1416 final State newConfigState =
michael@0 1417 new State(mHomeConfig, makeOrderedCopy(false), isDefault());
michael@0 1418
michael@0 1419 // This is a synchronous blocking operation, hence no
michael@0 1420 // need to deep copy the current PanelConfig instances.
michael@0 1421 mHomeConfig.save(newConfigState);
michael@0 1422
michael@0 1423 // Send pending events after the new config is saved.
michael@0 1424 sendEventsToGecko(mEventQueue);
michael@0 1425 mEventQueue.clear();
michael@0 1426
michael@0 1427 return newConfigState;
michael@0 1428 }
michael@0 1429
michael@0 1430 /**
michael@0 1431 * Returns whether the {@code Editor} represents the default
michael@0 1432 * {@code HomeConfig} configuration without any unsaved changes.
michael@0 1433 */
michael@0 1434 public boolean isDefault() {
michael@0 1435 ThreadUtils.assertOnThread(mOriginalThread);
michael@0 1436
michael@0 1437 return (!mHasChanged && mIsFromDefault);
michael@0 1438 }
michael@0 1439
michael@0 1440 public boolean isEmpty() {
michael@0 1441 return mConfigMap.isEmpty();
michael@0 1442 }
michael@0 1443
michael@0 1444 private void sendEventsToGecko(List<GeckoEvent> events) {
michael@0 1445 for (GeckoEvent e : events) {
michael@0 1446 GeckoAppShell.sendEventToGecko(e);
michael@0 1447 }
michael@0 1448 }
michael@0 1449
michael@0 1450 private class EditorIterator implements Iterator<PanelConfig> {
michael@0 1451 private final Iterator<String> mOrderIterator;
michael@0 1452
michael@0 1453 public EditorIterator() {
michael@0 1454 mOrderIterator = mConfigOrder.iterator();
michael@0 1455 }
michael@0 1456
michael@0 1457 @Override
michael@0 1458 public boolean hasNext() {
michael@0 1459 return mOrderIterator.hasNext();
michael@0 1460 }
michael@0 1461
michael@0 1462 @Override
michael@0 1463 public PanelConfig next() {
michael@0 1464 final String panelId = mOrderIterator.next();
michael@0 1465 return mConfigMap.get(panelId);
michael@0 1466 }
michael@0 1467
michael@0 1468 @Override
michael@0 1469 public void remove() {
michael@0 1470 throw new UnsupportedOperationException("Can't 'remove' from on Editor iterator.");
michael@0 1471 }
michael@0 1472 }
michael@0 1473
michael@0 1474 @Override
michael@0 1475 public Iterator<PanelConfig> iterator() {
michael@0 1476 ThreadUtils.assertOnThread(mOriginalThread);
michael@0 1477
michael@0 1478 return new EditorIterator();
michael@0 1479 }
michael@0 1480 }
michael@0 1481
michael@0 1482 public interface OnReloadListener {
michael@0 1483 public void onReload();
michael@0 1484 }
michael@0 1485
michael@0 1486 public interface HomeConfigBackend {
michael@0 1487 public State load();
michael@0 1488 public void save(State configState);
michael@0 1489 public String getLocale();
michael@0 1490 public void setOnReloadListener(OnReloadListener listener);
michael@0 1491 }
michael@0 1492
michael@0 1493 // UUIDs used to create PanelConfigs for default built-in panels
michael@0 1494 private static final String TOP_SITES_PANEL_ID = "4becc86b-41eb-429a-a042-88fe8b5a094e";
michael@0 1495 private static final String BOOKMARKS_PANEL_ID = "7f6d419a-cd6c-4e34-b26f-f68b1b551907";
michael@0 1496 private static final String READING_LIST_PANEL_ID = "20f4549a-64ad-4c32-93e4-1dcef792733b";
michael@0 1497 private static final String HISTORY_PANEL_ID = "f134bf20-11f7-4867-ab8b-e8e705d7fbe8";
michael@0 1498
michael@0 1499 private final HomeConfigBackend mBackend;
michael@0 1500
michael@0 1501 public HomeConfig(HomeConfigBackend backend) {
michael@0 1502 mBackend = backend;
michael@0 1503 }
michael@0 1504
michael@0 1505 public State load() {
michael@0 1506 final State configState = mBackend.load();
michael@0 1507 configState.setHomeConfig(this);
michael@0 1508
michael@0 1509 return configState;
michael@0 1510 }
michael@0 1511
michael@0 1512 public String getLocale() {
michael@0 1513 return mBackend.getLocale();
michael@0 1514 }
michael@0 1515
michael@0 1516 public void save(State configState) {
michael@0 1517 mBackend.save(configState);
michael@0 1518 }
michael@0 1519
michael@0 1520 public void setOnReloadListener(OnReloadListener listener) {
michael@0 1521 mBackend.setOnReloadListener(listener);
michael@0 1522 }
michael@0 1523
michael@0 1524 public static PanelConfig createBuiltinPanelConfig(Context context, PanelType panelType) {
michael@0 1525 return createBuiltinPanelConfig(context, panelType, EnumSet.noneOf(PanelConfig.Flags.class));
michael@0 1526 }
michael@0 1527
michael@0 1528 public static PanelConfig createBuiltinPanelConfig(Context context, PanelType panelType, EnumSet<PanelConfig.Flags> flags) {
michael@0 1529 int titleId = 0;
michael@0 1530 String id = null;
michael@0 1531
michael@0 1532 switch(panelType) {
michael@0 1533 case TOP_SITES:
michael@0 1534 titleId = R.string.home_top_sites_title;
michael@0 1535 id = TOP_SITES_PANEL_ID;
michael@0 1536 break;
michael@0 1537
michael@0 1538 case BOOKMARKS:
michael@0 1539 titleId = R.string.bookmarks_title;
michael@0 1540 id = BOOKMARKS_PANEL_ID;
michael@0 1541 break;
michael@0 1542
michael@0 1543 case HISTORY:
michael@0 1544 titleId = R.string.home_history_title;
michael@0 1545 id = HISTORY_PANEL_ID;
michael@0 1546 break;
michael@0 1547
michael@0 1548 case READING_LIST:
michael@0 1549 titleId = R.string.reading_list_title;
michael@0 1550 id = READING_LIST_PANEL_ID;
michael@0 1551 break;
michael@0 1552
michael@0 1553 case DYNAMIC:
michael@0 1554 throw new IllegalArgumentException("createBuiltinPanelConfig() is only for built-in panels");
michael@0 1555 }
michael@0 1556
michael@0 1557 return new PanelConfig(panelType, context.getString(titleId), id, flags);
michael@0 1558 }
michael@0 1559
michael@0 1560 public static HomeConfig getDefault(Context context) {
michael@0 1561 return new HomeConfig(new HomeConfigPrefsBackend(context));
michael@0 1562 }
michael@0 1563 }

mercurial