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