michael@0: /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.home; michael@0: michael@0: import org.mozilla.gecko.GeckoAppShell; michael@0: import org.mozilla.gecko.GeckoEvent; michael@0: import org.mozilla.gecko.R; michael@0: import org.mozilla.gecko.util.ThreadUtils; michael@0: michael@0: import org.json.JSONArray; michael@0: import org.json.JSONException; michael@0: import org.json.JSONObject; michael@0: michael@0: import android.content.Context; michael@0: import android.os.Parcel; michael@0: import android.os.Parcelable; michael@0: import android.text.TextUtils; michael@0: michael@0: import java.util.ArrayList; michael@0: import java.util.Collections; michael@0: import java.util.EnumSet; michael@0: import java.util.HashMap; michael@0: import java.util.Iterator; michael@0: import java.util.LinkedList; michael@0: import java.util.List; michael@0: import java.util.Map; michael@0: michael@0: public final class HomeConfig { michael@0: /** michael@0: * Used to determine what type of HomeFragment subclass to use when creating michael@0: * a given panel. With the exception of DYNAMIC, all of these types correspond michael@0: * to a default set of built-in panels. The DYNAMIC panel type is used by michael@0: * third-party services to create panels with varying types of content. michael@0: */ michael@0: public static enum PanelType implements Parcelable { michael@0: TOP_SITES("top_sites", TopSitesPanel.class), michael@0: BOOKMARKS("bookmarks", BookmarksPanel.class), michael@0: HISTORY("history", HistoryPanel.class), michael@0: READING_LIST("reading_list", ReadingListPanel.class), michael@0: DYNAMIC("dynamic", DynamicPanel.class); michael@0: michael@0: private final String mId; michael@0: private final Class mPanelClass; michael@0: michael@0: PanelType(String id, Class panelClass) { michael@0: mId = id; michael@0: mPanelClass = panelClass; michael@0: } michael@0: michael@0: public static PanelType fromId(String id) { michael@0: if (id == null) { michael@0: throw new IllegalArgumentException("Could not convert null String to PanelType"); michael@0: } michael@0: michael@0: for (PanelType panelType : PanelType.values()) { michael@0: if (TextUtils.equals(panelType.mId, id.toLowerCase())) { michael@0: return panelType; michael@0: } michael@0: } michael@0: michael@0: throw new IllegalArgumentException("Could not convert String id to PanelType"); michael@0: } michael@0: michael@0: @Override michael@0: public String toString() { michael@0: return mId; michael@0: } michael@0: michael@0: public Class getPanelClass() { michael@0: return mPanelClass; michael@0: } michael@0: michael@0: @Override michael@0: public int describeContents() { michael@0: return 0; michael@0: } michael@0: michael@0: @Override michael@0: public void writeToParcel(Parcel dest, int flags) { michael@0: dest.writeInt(ordinal()); michael@0: } michael@0: michael@0: public static final Creator CREATOR = new Creator() { michael@0: @Override michael@0: public PanelType createFromParcel(final Parcel source) { michael@0: return PanelType.values()[source.readInt()]; michael@0: } michael@0: michael@0: @Override michael@0: public PanelType[] newArray(final int size) { michael@0: return new PanelType[size]; michael@0: } michael@0: }; michael@0: } michael@0: michael@0: public static class PanelConfig implements Parcelable { michael@0: private final PanelType mType; michael@0: private final String mTitle; michael@0: private final String mId; michael@0: private final LayoutType mLayoutType; michael@0: private final List mViews; michael@0: private final AuthConfig mAuthConfig; michael@0: private final EnumSet mFlags; michael@0: michael@0: private static final String JSON_KEY_TYPE = "type"; michael@0: private static final String JSON_KEY_TITLE = "title"; michael@0: private static final String JSON_KEY_ID = "id"; michael@0: private static final String JSON_KEY_LAYOUT = "layout"; michael@0: private static final String JSON_KEY_VIEWS = "views"; michael@0: private static final String JSON_KEY_AUTH_CONFIG = "authConfig"; michael@0: private static final String JSON_KEY_DEFAULT = "default"; michael@0: private static final String JSON_KEY_DISABLED = "disabled"; michael@0: michael@0: public enum Flags { michael@0: DEFAULT_PANEL, michael@0: DISABLED_PANEL michael@0: } michael@0: michael@0: public PanelConfig(JSONObject json) throws JSONException, IllegalArgumentException { michael@0: final String panelType = json.optString(JSON_KEY_TYPE, null); michael@0: if (TextUtils.isEmpty(panelType)) { michael@0: mType = PanelType.DYNAMIC; michael@0: } else { michael@0: mType = PanelType.fromId(panelType); michael@0: } michael@0: michael@0: mTitle = json.getString(JSON_KEY_TITLE); michael@0: mId = json.getString(JSON_KEY_ID); michael@0: michael@0: final String layoutTypeId = json.optString(JSON_KEY_LAYOUT, null); michael@0: if (layoutTypeId != null) { michael@0: mLayoutType = LayoutType.fromId(layoutTypeId); michael@0: } else { michael@0: mLayoutType = null; michael@0: } michael@0: michael@0: final JSONArray jsonViews = json.optJSONArray(JSON_KEY_VIEWS); michael@0: if (jsonViews != null) { michael@0: mViews = new ArrayList(); michael@0: michael@0: final int viewCount = jsonViews.length(); michael@0: for (int i = 0; i < viewCount; i++) { michael@0: final JSONObject jsonViewConfig = (JSONObject) jsonViews.get(i); michael@0: final ViewConfig viewConfig = new ViewConfig(i, jsonViewConfig); michael@0: mViews.add(viewConfig); michael@0: } michael@0: } else { michael@0: mViews = null; michael@0: } michael@0: michael@0: final JSONObject jsonAuthConfig = json.optJSONObject(JSON_KEY_AUTH_CONFIG); michael@0: if (jsonAuthConfig != null) { michael@0: mAuthConfig = new AuthConfig(jsonAuthConfig); michael@0: } else { michael@0: mAuthConfig = null; michael@0: } michael@0: michael@0: mFlags = EnumSet.noneOf(Flags.class); michael@0: michael@0: if (json.optBoolean(JSON_KEY_DEFAULT, false)) { michael@0: mFlags.add(Flags.DEFAULT_PANEL); michael@0: } michael@0: michael@0: if (json.optBoolean(JSON_KEY_DISABLED, false)) { michael@0: mFlags.add(Flags.DISABLED_PANEL); michael@0: } michael@0: michael@0: validate(); michael@0: } michael@0: michael@0: @SuppressWarnings("unchecked") michael@0: public PanelConfig(Parcel in) { michael@0: mType = (PanelType) in.readParcelable(getClass().getClassLoader()); michael@0: mTitle = in.readString(); michael@0: mId = in.readString(); michael@0: mLayoutType = (LayoutType) in.readParcelable(getClass().getClassLoader()); michael@0: michael@0: mViews = new ArrayList(); michael@0: in.readTypedList(mViews, ViewConfig.CREATOR); michael@0: michael@0: mAuthConfig = (AuthConfig) in.readParcelable(getClass().getClassLoader()); michael@0: michael@0: mFlags = (EnumSet) in.readSerializable(); michael@0: michael@0: validate(); michael@0: } michael@0: michael@0: public PanelConfig(PanelConfig panelConfig) { michael@0: mType = panelConfig.mType; michael@0: mTitle = panelConfig.mTitle; michael@0: mId = panelConfig.mId; michael@0: mLayoutType = panelConfig.mLayoutType; michael@0: michael@0: mViews = new ArrayList(); michael@0: List viewConfigs = panelConfig.mViews; michael@0: if (viewConfigs != null) { michael@0: for (ViewConfig viewConfig : viewConfigs) { michael@0: mViews.add(new ViewConfig(viewConfig)); michael@0: } michael@0: } michael@0: michael@0: mAuthConfig = panelConfig.mAuthConfig; michael@0: mFlags = panelConfig.mFlags.clone(); michael@0: michael@0: validate(); michael@0: } michael@0: michael@0: public PanelConfig(PanelType type, String title, String id) { michael@0: this(type, title, id, EnumSet.noneOf(Flags.class)); michael@0: } michael@0: michael@0: public PanelConfig(PanelType type, String title, String id, EnumSet flags) { michael@0: this(type, title, id, null, null, null, flags); michael@0: } michael@0: michael@0: public PanelConfig(PanelType type, String title, String id, LayoutType layoutType, michael@0: List views, AuthConfig authConfig, EnumSet flags) { michael@0: mType = type; michael@0: mTitle = title; michael@0: mId = id; michael@0: mLayoutType = layoutType; michael@0: mViews = views; michael@0: mAuthConfig = authConfig; michael@0: mFlags = flags; michael@0: michael@0: validate(); michael@0: } michael@0: michael@0: private void validate() { michael@0: if (mType == null) { michael@0: throw new IllegalArgumentException("Can't create PanelConfig with null type"); michael@0: } michael@0: michael@0: if (TextUtils.isEmpty(mTitle)) { michael@0: throw new IllegalArgumentException("Can't create PanelConfig with empty title"); michael@0: } michael@0: michael@0: if (TextUtils.isEmpty(mId)) { michael@0: throw new IllegalArgumentException("Can't create PanelConfig with empty id"); michael@0: } michael@0: michael@0: if (mLayoutType == null && mType == PanelType.DYNAMIC) { michael@0: throw new IllegalArgumentException("Can't create a dynamic PanelConfig with null layout type"); michael@0: } michael@0: michael@0: if ((mViews == null || mViews.size() == 0) && mType == PanelType.DYNAMIC) { michael@0: throw new IllegalArgumentException("Can't create a dynamic PanelConfig with no views"); michael@0: } michael@0: michael@0: if (mFlags == null) { michael@0: throw new IllegalArgumentException("Can't create PanelConfig with null flags"); michael@0: } michael@0: } michael@0: michael@0: public PanelType getType() { michael@0: return mType; michael@0: } michael@0: michael@0: public String getTitle() { michael@0: return mTitle; michael@0: } michael@0: michael@0: public String getId() { michael@0: return mId; michael@0: } michael@0: michael@0: public LayoutType getLayoutType() { michael@0: return mLayoutType; michael@0: } michael@0: michael@0: public int getViewCount() { michael@0: return (mViews != null ? mViews.size() : 0); michael@0: } michael@0: michael@0: public ViewConfig getViewAt(int index) { michael@0: return (mViews != null ? mViews.get(index) : null); michael@0: } michael@0: michael@0: public boolean isDynamic() { michael@0: return (mType == PanelType.DYNAMIC); michael@0: } michael@0: michael@0: public boolean isDefault() { michael@0: return mFlags.contains(Flags.DEFAULT_PANEL); michael@0: } michael@0: michael@0: private void setIsDefault(boolean isDefault) { michael@0: if (isDefault) { michael@0: mFlags.add(Flags.DEFAULT_PANEL); michael@0: } else { michael@0: mFlags.remove(Flags.DEFAULT_PANEL); michael@0: } michael@0: } michael@0: michael@0: public boolean isDisabled() { michael@0: return mFlags.contains(Flags.DISABLED_PANEL); michael@0: } michael@0: michael@0: private void setIsDisabled(boolean isDisabled) { michael@0: if (isDisabled) { michael@0: mFlags.add(Flags.DISABLED_PANEL); michael@0: } else { michael@0: mFlags.remove(Flags.DISABLED_PANEL); michael@0: } michael@0: } michael@0: michael@0: public AuthConfig getAuthConfig() { michael@0: return mAuthConfig; michael@0: } michael@0: michael@0: public JSONObject toJSON() throws JSONException { michael@0: final JSONObject json = new JSONObject(); michael@0: michael@0: json.put(JSON_KEY_TYPE, mType.toString()); michael@0: json.put(JSON_KEY_TITLE, mTitle); michael@0: json.put(JSON_KEY_ID, mId); michael@0: michael@0: if (mLayoutType != null) { michael@0: json.put(JSON_KEY_LAYOUT, mLayoutType.toString()); michael@0: } michael@0: michael@0: if (mViews != null) { michael@0: final JSONArray jsonViews = new JSONArray(); michael@0: michael@0: final int viewCount = mViews.size(); michael@0: for (int i = 0; i < viewCount; i++) { michael@0: final ViewConfig viewConfig = mViews.get(i); michael@0: final JSONObject jsonViewConfig = viewConfig.toJSON(); michael@0: jsonViews.put(jsonViewConfig); michael@0: } michael@0: michael@0: json.put(JSON_KEY_VIEWS, jsonViews); michael@0: } michael@0: michael@0: if (mAuthConfig != null) { michael@0: json.put(JSON_KEY_AUTH_CONFIG, mAuthConfig.toJSON()); michael@0: } michael@0: michael@0: if (mFlags.contains(Flags.DEFAULT_PANEL)) { michael@0: json.put(JSON_KEY_DEFAULT, true); michael@0: } michael@0: michael@0: if (mFlags.contains(Flags.DISABLED_PANEL)) { michael@0: json.put(JSON_KEY_DISABLED, true); michael@0: } michael@0: michael@0: return json; michael@0: } michael@0: michael@0: @Override michael@0: public boolean equals(Object o) { michael@0: if (o == null) { michael@0: return false; michael@0: } michael@0: michael@0: if (this == o) { michael@0: return true; michael@0: } michael@0: michael@0: if (!(o instanceof PanelConfig)) { michael@0: return false; michael@0: } michael@0: michael@0: final PanelConfig other = (PanelConfig) o; michael@0: return mId.equals(other.mId); michael@0: } michael@0: michael@0: @Override michael@0: public int describeContents() { michael@0: return 0; michael@0: } michael@0: michael@0: @Override michael@0: public void writeToParcel(Parcel dest, int flags) { michael@0: dest.writeParcelable(mType, 0); michael@0: dest.writeString(mTitle); michael@0: dest.writeString(mId); michael@0: dest.writeParcelable(mLayoutType, 0); michael@0: dest.writeTypedList(mViews); michael@0: dest.writeParcelable(mAuthConfig, 0); michael@0: dest.writeSerializable(mFlags); michael@0: } michael@0: michael@0: public static final Creator CREATOR = new Creator() { michael@0: @Override michael@0: public PanelConfig createFromParcel(final Parcel in) { michael@0: return new PanelConfig(in); michael@0: } michael@0: michael@0: @Override michael@0: public PanelConfig[] newArray(final int size) { michael@0: return new PanelConfig[size]; michael@0: } michael@0: }; michael@0: } michael@0: michael@0: public static enum LayoutType implements Parcelable { michael@0: FRAME("frame"); michael@0: michael@0: private final String mId; michael@0: michael@0: LayoutType(String id) { michael@0: mId = id; michael@0: } michael@0: michael@0: public static LayoutType fromId(String id) { michael@0: if (id == null) { michael@0: throw new IllegalArgumentException("Could not convert null String to LayoutType"); michael@0: } michael@0: michael@0: for (LayoutType layoutType : LayoutType.values()) { michael@0: if (TextUtils.equals(layoutType.mId, id.toLowerCase())) { michael@0: return layoutType; michael@0: } michael@0: } michael@0: michael@0: throw new IllegalArgumentException("Could not convert String id to LayoutType"); michael@0: } michael@0: michael@0: @Override michael@0: public String toString() { michael@0: return mId; michael@0: } michael@0: michael@0: @Override michael@0: public int describeContents() { michael@0: return 0; michael@0: } michael@0: michael@0: @Override michael@0: public void writeToParcel(Parcel dest, int flags) { michael@0: dest.writeInt(ordinal()); michael@0: } michael@0: michael@0: public static final Creator CREATOR = new Creator() { michael@0: @Override michael@0: public LayoutType createFromParcel(final Parcel source) { michael@0: return LayoutType.values()[source.readInt()]; michael@0: } michael@0: michael@0: @Override michael@0: public LayoutType[] newArray(final int size) { michael@0: return new LayoutType[size]; michael@0: } michael@0: }; michael@0: } michael@0: michael@0: public static enum ViewType implements Parcelable { michael@0: LIST("list"), michael@0: GRID("grid"); michael@0: michael@0: private final String mId; michael@0: michael@0: ViewType(String id) { michael@0: mId = id; michael@0: } michael@0: michael@0: public static ViewType fromId(String id) { michael@0: if (id == null) { michael@0: throw new IllegalArgumentException("Could not convert null String to ViewType"); michael@0: } michael@0: michael@0: for (ViewType viewType : ViewType.values()) { michael@0: if (TextUtils.equals(viewType.mId, id.toLowerCase())) { michael@0: return viewType; michael@0: } michael@0: } michael@0: michael@0: throw new IllegalArgumentException("Could not convert String id to ViewType"); michael@0: } michael@0: michael@0: @Override michael@0: public String toString() { michael@0: return mId; michael@0: } michael@0: michael@0: @Override michael@0: public int describeContents() { michael@0: return 0; michael@0: } michael@0: michael@0: @Override michael@0: public void writeToParcel(Parcel dest, int flags) { michael@0: dest.writeInt(ordinal()); michael@0: } michael@0: michael@0: public static final Creator CREATOR = new Creator() { michael@0: @Override michael@0: public ViewType createFromParcel(final Parcel source) { michael@0: return ViewType.values()[source.readInt()]; michael@0: } michael@0: michael@0: @Override michael@0: public ViewType[] newArray(final int size) { michael@0: return new ViewType[size]; michael@0: } michael@0: }; michael@0: } michael@0: michael@0: public static enum ItemType implements Parcelable { michael@0: ARTICLE("article"), michael@0: IMAGE("image"); michael@0: michael@0: private final String mId; michael@0: michael@0: ItemType(String id) { michael@0: mId = id; michael@0: } michael@0: michael@0: public static ItemType fromId(String id) { michael@0: if (id == null) { michael@0: throw new IllegalArgumentException("Could not convert null String to ItemType"); michael@0: } michael@0: michael@0: for (ItemType itemType : ItemType.values()) { michael@0: if (TextUtils.equals(itemType.mId, id.toLowerCase())) { michael@0: return itemType; michael@0: } michael@0: } michael@0: michael@0: throw new IllegalArgumentException("Could not convert String id to ItemType"); michael@0: } michael@0: michael@0: @Override michael@0: public String toString() { michael@0: return mId; michael@0: } michael@0: michael@0: @Override michael@0: public int describeContents() { michael@0: return 0; michael@0: } michael@0: michael@0: @Override michael@0: public void writeToParcel(Parcel dest, int flags) { michael@0: dest.writeInt(ordinal()); michael@0: } michael@0: michael@0: public static final Creator CREATOR = new Creator() { michael@0: @Override michael@0: public ItemType createFromParcel(final Parcel source) { michael@0: return ItemType.values()[source.readInt()]; michael@0: } michael@0: michael@0: @Override michael@0: public ItemType[] newArray(final int size) { michael@0: return new ItemType[size]; michael@0: } michael@0: }; michael@0: } michael@0: michael@0: public static enum ItemHandler implements Parcelable { michael@0: BROWSER("browser"), michael@0: INTENT("intent"); michael@0: michael@0: private final String mId; michael@0: michael@0: ItemHandler(String id) { michael@0: mId = id; michael@0: } michael@0: michael@0: public static ItemHandler fromId(String id) { michael@0: if (id == null) { michael@0: throw new IllegalArgumentException("Could not convert null String to ItemHandler"); michael@0: } michael@0: michael@0: for (ItemHandler itemHandler : ItemHandler.values()) { michael@0: if (TextUtils.equals(itemHandler.mId, id.toLowerCase())) { michael@0: return itemHandler; michael@0: } michael@0: } michael@0: michael@0: throw new IllegalArgumentException("Could not convert String id to ItemHandler"); michael@0: } michael@0: michael@0: @Override michael@0: public String toString() { michael@0: return mId; michael@0: } michael@0: michael@0: @Override michael@0: public int describeContents() { michael@0: return 0; michael@0: } michael@0: michael@0: @Override michael@0: public void writeToParcel(Parcel dest, int flags) { michael@0: dest.writeInt(ordinal()); michael@0: } michael@0: michael@0: public static final Creator CREATOR = new Creator() { michael@0: @Override michael@0: public ItemHandler createFromParcel(final Parcel source) { michael@0: return ItemHandler.values()[source.readInt()]; michael@0: } michael@0: michael@0: @Override michael@0: public ItemHandler[] newArray(final int size) { michael@0: return new ItemHandler[size]; michael@0: } michael@0: }; michael@0: } michael@0: michael@0: public static class ViewConfig implements Parcelable { michael@0: private final int mIndex; michael@0: private final ViewType mType; michael@0: private final String mDatasetId; michael@0: private final ItemType mItemType; michael@0: private final ItemHandler mItemHandler; michael@0: private final String mBackImageUrl; michael@0: private final String mFilter; michael@0: private final EmptyViewConfig mEmptyViewConfig; michael@0: private final EnumSet mFlags; michael@0: michael@0: private static final String JSON_KEY_TYPE = "type"; michael@0: private static final String JSON_KEY_DATASET = "dataset"; michael@0: private static final String JSON_KEY_ITEM_TYPE = "itemType"; michael@0: private static final String JSON_KEY_ITEM_HANDLER = "itemHandler"; michael@0: private static final String JSON_KEY_BACK_IMAGE_URL = "backImageUrl"; michael@0: private static final String JSON_KEY_FILTER = "filter"; michael@0: private static final String JSON_KEY_EMPTY = "empty"; michael@0: private static final String JSON_KEY_REFRESH_ENABLED = "refreshEnabled"; michael@0: michael@0: public enum Flags { michael@0: REFRESH_ENABLED michael@0: } michael@0: michael@0: public ViewConfig(int index, JSONObject json) throws JSONException, IllegalArgumentException { michael@0: mIndex = index; michael@0: mType = ViewType.fromId(json.getString(JSON_KEY_TYPE)); michael@0: mDatasetId = json.getString(JSON_KEY_DATASET); michael@0: mItemType = ItemType.fromId(json.getString(JSON_KEY_ITEM_TYPE)); michael@0: mItemHandler = ItemHandler.fromId(json.getString(JSON_KEY_ITEM_HANDLER)); michael@0: mBackImageUrl = json.optString(JSON_KEY_BACK_IMAGE_URL, null); michael@0: mFilter = json.optString(JSON_KEY_FILTER, null); michael@0: michael@0: final JSONObject jsonEmptyViewConfig = json.optJSONObject(JSON_KEY_EMPTY); michael@0: if (jsonEmptyViewConfig != null) { michael@0: mEmptyViewConfig = new EmptyViewConfig(jsonEmptyViewConfig); michael@0: } else { michael@0: mEmptyViewConfig = null; michael@0: } michael@0: michael@0: mFlags = EnumSet.noneOf(Flags.class); michael@0: if (json.optBoolean(JSON_KEY_REFRESH_ENABLED, false)) { michael@0: mFlags.add(Flags.REFRESH_ENABLED); michael@0: } michael@0: michael@0: validate(); michael@0: } michael@0: michael@0: @SuppressWarnings("unchecked") michael@0: public ViewConfig(Parcel in) { michael@0: mIndex = in.readInt(); michael@0: mType = (ViewType) in.readParcelable(getClass().getClassLoader()); michael@0: mDatasetId = in.readString(); michael@0: mItemType = (ItemType) in.readParcelable(getClass().getClassLoader()); michael@0: mItemHandler = (ItemHandler) in.readParcelable(getClass().getClassLoader()); michael@0: mBackImageUrl = in.readString(); michael@0: mFilter = in.readString(); michael@0: mEmptyViewConfig = (EmptyViewConfig) in.readParcelable(getClass().getClassLoader()); michael@0: mFlags = (EnumSet) in.readSerializable(); michael@0: michael@0: validate(); michael@0: } michael@0: michael@0: public ViewConfig(ViewConfig viewConfig) { michael@0: mIndex = viewConfig.mIndex; michael@0: mType = viewConfig.mType; michael@0: mDatasetId = viewConfig.mDatasetId; michael@0: mItemType = viewConfig.mItemType; michael@0: mItemHandler = viewConfig.mItemHandler; michael@0: mBackImageUrl = viewConfig.mBackImageUrl; michael@0: mFilter = viewConfig.mFilter; michael@0: mEmptyViewConfig = viewConfig.mEmptyViewConfig; michael@0: mFlags = viewConfig.mFlags.clone(); michael@0: michael@0: validate(); michael@0: } michael@0: michael@0: public ViewConfig(int index, ViewType type, String datasetId, ItemType itemType, michael@0: ItemHandler itemHandler, String backImageUrl, String filter, michael@0: EmptyViewConfig emptyViewConfig, EnumSet flags) { michael@0: mIndex = index; michael@0: mType = type; michael@0: mDatasetId = datasetId; michael@0: mItemType = itemType; michael@0: mItemHandler = itemHandler; michael@0: mBackImageUrl = backImageUrl; michael@0: mFilter = filter; michael@0: mEmptyViewConfig = emptyViewConfig; michael@0: mFlags = flags; michael@0: michael@0: validate(); michael@0: } michael@0: michael@0: private void validate() { michael@0: if (mType == null) { michael@0: throw new IllegalArgumentException("Can't create ViewConfig with null type"); michael@0: } michael@0: michael@0: if (TextUtils.isEmpty(mDatasetId)) { michael@0: throw new IllegalArgumentException("Can't create ViewConfig with empty dataset ID"); michael@0: } michael@0: michael@0: if (mItemType == null) { michael@0: throw new IllegalArgumentException("Can't create ViewConfig with null item type"); michael@0: } michael@0: michael@0: if (mItemHandler == null) { michael@0: throw new IllegalArgumentException("Can't create ViewConfig with null item handler"); michael@0: } michael@0: michael@0: if (mFlags == null) { michael@0: throw new IllegalArgumentException("Can't create ViewConfig with null flags"); michael@0: } michael@0: } michael@0: michael@0: public int getIndex() { michael@0: return mIndex; michael@0: } michael@0: michael@0: public ViewType getType() { michael@0: return mType; michael@0: } michael@0: michael@0: public String getDatasetId() { michael@0: return mDatasetId; michael@0: } michael@0: michael@0: public ItemType getItemType() { michael@0: return mItemType; michael@0: } michael@0: michael@0: public ItemHandler getItemHandler() { michael@0: return mItemHandler; michael@0: } michael@0: michael@0: public String getBackImageUrl() { michael@0: return mBackImageUrl; michael@0: } michael@0: michael@0: public String getFilter() { michael@0: return mFilter; michael@0: } michael@0: michael@0: public EmptyViewConfig getEmptyViewConfig() { michael@0: return mEmptyViewConfig; michael@0: } michael@0: michael@0: public boolean isRefreshEnabled() { michael@0: return mFlags.contains(Flags.REFRESH_ENABLED); michael@0: } michael@0: michael@0: public JSONObject toJSON() throws JSONException { michael@0: final JSONObject json = new JSONObject(); michael@0: michael@0: json.put(JSON_KEY_TYPE, mType.toString()); michael@0: json.put(JSON_KEY_DATASET, mDatasetId); michael@0: json.put(JSON_KEY_ITEM_TYPE, mItemType.toString()); michael@0: json.put(JSON_KEY_ITEM_HANDLER, mItemHandler.toString()); michael@0: michael@0: if (!TextUtils.isEmpty(mBackImageUrl)) { michael@0: json.put(JSON_KEY_BACK_IMAGE_URL, mBackImageUrl); michael@0: } michael@0: michael@0: if (!TextUtils.isEmpty(mFilter)) { michael@0: json.put(JSON_KEY_FILTER, mFilter); michael@0: } michael@0: michael@0: if (mEmptyViewConfig != null) { michael@0: json.put(JSON_KEY_EMPTY, mEmptyViewConfig.toJSON()); michael@0: } michael@0: michael@0: if (mFlags.contains(Flags.REFRESH_ENABLED)) { michael@0: json.put(JSON_KEY_REFRESH_ENABLED, true); michael@0: } michael@0: michael@0: return json; michael@0: } michael@0: michael@0: @Override michael@0: public int describeContents() { michael@0: return 0; michael@0: } michael@0: michael@0: @Override michael@0: public void writeToParcel(Parcel dest, int flags) { michael@0: dest.writeInt(mIndex); michael@0: dest.writeParcelable(mType, 0); michael@0: dest.writeString(mDatasetId); michael@0: dest.writeParcelable(mItemType, 0); michael@0: dest.writeParcelable(mItemHandler, 0); michael@0: dest.writeString(mBackImageUrl); michael@0: dest.writeString(mFilter); michael@0: dest.writeParcelable(mEmptyViewConfig, 0); michael@0: dest.writeSerializable(mFlags); michael@0: } michael@0: michael@0: public static final Creator CREATOR = new Creator() { michael@0: @Override michael@0: public ViewConfig createFromParcel(final Parcel in) { michael@0: return new ViewConfig(in); michael@0: } michael@0: michael@0: @Override michael@0: public ViewConfig[] newArray(final int size) { michael@0: return new ViewConfig[size]; michael@0: } michael@0: }; michael@0: } michael@0: michael@0: public static class EmptyViewConfig implements Parcelable { michael@0: private final String mText; michael@0: private final String mImageUrl; michael@0: michael@0: private static final String JSON_KEY_TEXT = "text"; michael@0: private static final String JSON_KEY_IMAGE_URL = "imageUrl"; michael@0: michael@0: public EmptyViewConfig(JSONObject json) throws JSONException, IllegalArgumentException { michael@0: mText = json.optString(JSON_KEY_TEXT, null); michael@0: mImageUrl = json.optString(JSON_KEY_IMAGE_URL, null); michael@0: } michael@0: michael@0: @SuppressWarnings("unchecked") michael@0: public EmptyViewConfig(Parcel in) { michael@0: mText = in.readString(); michael@0: mImageUrl = in.readString(); michael@0: } michael@0: michael@0: public EmptyViewConfig(EmptyViewConfig emptyViewConfig) { michael@0: mText = emptyViewConfig.mText; michael@0: mImageUrl = emptyViewConfig.mImageUrl; michael@0: } michael@0: michael@0: public EmptyViewConfig(String text, String imageUrl) { michael@0: mText = text; michael@0: mImageUrl = imageUrl; michael@0: } michael@0: michael@0: public String getText() { michael@0: return mText; michael@0: } michael@0: michael@0: public String getImageUrl() { michael@0: return mImageUrl; michael@0: } michael@0: michael@0: public JSONObject toJSON() throws JSONException { michael@0: final JSONObject json = new JSONObject(); michael@0: michael@0: json.put(JSON_KEY_TEXT, mText); michael@0: json.put(JSON_KEY_IMAGE_URL, mImageUrl); michael@0: michael@0: return json; michael@0: } michael@0: michael@0: @Override michael@0: public int describeContents() { michael@0: return 0; michael@0: } michael@0: michael@0: @Override michael@0: public void writeToParcel(Parcel dest, int flags) { michael@0: dest.writeString(mText); michael@0: dest.writeString(mImageUrl); michael@0: } michael@0: michael@0: public static final Creator CREATOR = new Creator() { michael@0: @Override michael@0: public EmptyViewConfig createFromParcel(final Parcel in) { michael@0: return new EmptyViewConfig(in); michael@0: } michael@0: michael@0: @Override michael@0: public EmptyViewConfig[] newArray(final int size) { michael@0: return new EmptyViewConfig[size]; michael@0: } michael@0: }; michael@0: } michael@0: michael@0: public static class AuthConfig implements Parcelable { michael@0: private final String mMessageText; michael@0: private final String mButtonText; michael@0: private final String mImageUrl; michael@0: michael@0: private static final String JSON_KEY_MESSAGE_TEXT = "messageText"; michael@0: private static final String JSON_KEY_BUTTON_TEXT = "buttonText"; michael@0: private static final String JSON_KEY_IMAGE_URL = "imageUrl"; michael@0: michael@0: public AuthConfig(JSONObject json) throws JSONException, IllegalArgumentException { michael@0: mMessageText = json.optString(JSON_KEY_MESSAGE_TEXT); michael@0: mButtonText = json.optString(JSON_KEY_BUTTON_TEXT); michael@0: mImageUrl = json.optString(JSON_KEY_IMAGE_URL, null); michael@0: } michael@0: michael@0: @SuppressWarnings("unchecked") michael@0: public AuthConfig(Parcel in) { michael@0: mMessageText = in.readString(); michael@0: mButtonText = in.readString(); michael@0: mImageUrl = in.readString(); michael@0: michael@0: validate(); michael@0: } michael@0: michael@0: public AuthConfig(AuthConfig authConfig) { michael@0: mMessageText = authConfig.mMessageText; michael@0: mButtonText = authConfig.mButtonText; michael@0: mImageUrl = authConfig.mImageUrl; michael@0: michael@0: validate(); michael@0: } michael@0: michael@0: public AuthConfig(String messageText, String buttonText, String imageUrl) { michael@0: mMessageText = messageText; michael@0: mButtonText = buttonText; michael@0: mImageUrl = imageUrl; michael@0: michael@0: validate(); michael@0: } michael@0: michael@0: private void validate() { michael@0: if (mMessageText == null) { michael@0: throw new IllegalArgumentException("Can't create AuthConfig with null message text"); michael@0: } michael@0: michael@0: if (mButtonText == null) { michael@0: throw new IllegalArgumentException("Can't create AuthConfig with null button text"); michael@0: } michael@0: } michael@0: michael@0: public String getMessageText() { michael@0: return mMessageText; michael@0: } michael@0: michael@0: public String getButtonText() { michael@0: return mButtonText; michael@0: } michael@0: michael@0: public String getImageUrl() { michael@0: return mImageUrl; michael@0: } michael@0: michael@0: public JSONObject toJSON() throws JSONException { michael@0: final JSONObject json = new JSONObject(); michael@0: michael@0: json.put(JSON_KEY_MESSAGE_TEXT, mMessageText); michael@0: json.put(JSON_KEY_BUTTON_TEXT, mButtonText); michael@0: json.put(JSON_KEY_IMAGE_URL, mImageUrl); michael@0: michael@0: return json; michael@0: } michael@0: michael@0: @Override michael@0: public int describeContents() { michael@0: return 0; michael@0: } michael@0: michael@0: @Override michael@0: public void writeToParcel(Parcel dest, int flags) { michael@0: dest.writeString(mMessageText); michael@0: dest.writeString(mButtonText); michael@0: dest.writeString(mImageUrl); michael@0: } michael@0: michael@0: public static final Creator CREATOR = new Creator() { michael@0: @Override michael@0: public AuthConfig createFromParcel(final Parcel in) { michael@0: return new AuthConfig(in); michael@0: } michael@0: michael@0: @Override michael@0: public AuthConfig[] newArray(final int size) { michael@0: return new AuthConfig[size]; michael@0: } michael@0: }; michael@0: } michael@0: /** michael@0: * Immutable representation of the current state of {@code HomeConfig}. michael@0: * This is what HomeConfig returns from a load() call and takes as michael@0: * input to save a new state. michael@0: * michael@0: * Users of {@code State} should use an {@code Iterator} to iterate michael@0: * through the contained {@code PanelConfig} instances. michael@0: * michael@0: * {@code State} is immutable i.e. you can't add, remove, or update michael@0: * contained elements directly. You have to use an {@code Editor} to michael@0: * change the state, which can be created through the {@code edit()} michael@0: * method. michael@0: */ michael@0: public static class State implements Iterable { michael@0: private HomeConfig mHomeConfig; michael@0: private final List mPanelConfigs; michael@0: private final boolean mIsDefault; michael@0: michael@0: State(List panelConfigs, boolean isDefault) { michael@0: this(null, panelConfigs, isDefault); michael@0: } michael@0: michael@0: private State(HomeConfig homeConfig, List panelConfigs, boolean isDefault) { michael@0: mHomeConfig = homeConfig; michael@0: mPanelConfigs = Collections.unmodifiableList(panelConfigs); michael@0: mIsDefault = isDefault; michael@0: } michael@0: michael@0: private void setHomeConfig(HomeConfig homeConfig) { michael@0: if (mHomeConfig != null) { michael@0: throw new IllegalStateException("Can't set HomeConfig more than once"); michael@0: } michael@0: michael@0: mHomeConfig = homeConfig; michael@0: } michael@0: michael@0: @Override michael@0: public Iterator iterator() { michael@0: return mPanelConfigs.iterator(); michael@0: } michael@0: michael@0: /** michael@0: * Returns whether this {@code State} instance represents the default michael@0: * {@code HomeConfig} configuration or not. michael@0: */ michael@0: public boolean isDefault() { michael@0: return mIsDefault; michael@0: } michael@0: michael@0: /** michael@0: * Creates an {@code Editor} for this state. michael@0: */ michael@0: public Editor edit() { michael@0: return new Editor(mHomeConfig, this); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * {@code Editor} allows you to make changes to a {@code State}. You michael@0: * can create {@code Editor} by calling {@code edit()} on the target michael@0: * {@code State} instance. michael@0: * michael@0: * {@code Editor} works on a copy of the {@code State} that originated michael@0: * it. This means that adding, removing, or updating panels in an michael@0: * {@code Editor} will never change the {@code State} which you michael@0: * created the {@code Editor} from. Calling {@code commit()} or michael@0: * {@code apply()} will cause the new {@code State} instance to be michael@0: * created and saved using the {@code HomeConfig} instance that michael@0: * created the source {@code State}. michael@0: * michael@0: * {@code Editor} is *not* thread-safe. You can only make calls on it michael@0: * from the thread where it was originally created. It will throw an michael@0: * exception if you don't follow this invariant. michael@0: */ michael@0: public static class Editor implements Iterable { michael@0: private final HomeConfig mHomeConfig; michael@0: private final Map mConfigMap; michael@0: private final List mConfigOrder; michael@0: private final List mEventQueue; michael@0: private final Thread mOriginalThread; michael@0: michael@0: private PanelConfig mDefaultPanel; michael@0: private int mEnabledCount; michael@0: michael@0: private boolean mHasChanged; michael@0: private final boolean mIsFromDefault; michael@0: michael@0: private Editor(HomeConfig homeConfig, State configState) { michael@0: mHomeConfig = homeConfig; michael@0: mOriginalThread = Thread.currentThread(); michael@0: mConfigMap = new HashMap(); michael@0: mConfigOrder = new LinkedList(); michael@0: mEventQueue = new LinkedList(); michael@0: mEnabledCount = 0; michael@0: michael@0: mHasChanged = false; michael@0: mIsFromDefault = configState.isDefault(); michael@0: michael@0: initFromState(configState); michael@0: } michael@0: michael@0: /** michael@0: * Initialize the initial state of the editor from the given michael@0: * {@sode State}. A HashMap is used to represent the list of michael@0: * panels as it provides fast access, and a LinkedList is used to michael@0: * keep track of order. We keep a reference to the default panel michael@0: * and the number of enabled panels to avoid iterating through the michael@0: * map every time we need those. michael@0: * michael@0: * @param configState The source State to load the editor from. michael@0: */ michael@0: private void initFromState(State configState) { michael@0: for (PanelConfig panelConfig : configState) { michael@0: final PanelConfig panelCopy = new PanelConfig(panelConfig); michael@0: michael@0: if (!panelCopy.isDisabled()) { michael@0: mEnabledCount++; michael@0: } michael@0: michael@0: if (panelCopy.isDefault()) { michael@0: if (mDefaultPanel == null) { michael@0: mDefaultPanel = panelCopy; michael@0: } else { michael@0: throw new IllegalStateException("Multiple default panels in HomeConfig state"); michael@0: } michael@0: } michael@0: michael@0: final String panelId = panelConfig.getId(); michael@0: mConfigOrder.add(panelId); michael@0: mConfigMap.put(panelId, panelCopy); michael@0: } michael@0: michael@0: // We should always have a defined default panel if there's michael@0: // at least one enabled panel around. michael@0: if (mEnabledCount > 0 && mDefaultPanel == null) { michael@0: throw new IllegalStateException("Default panel in HomeConfig state is undefined"); michael@0: } michael@0: } michael@0: michael@0: private PanelConfig getPanelOrThrow(String panelId) { michael@0: final PanelConfig panelConfig = mConfigMap.get(panelId); michael@0: if (panelConfig == null) { michael@0: throw new IllegalStateException("Tried to access non-existing panel: " + panelId); michael@0: } michael@0: michael@0: return panelConfig; michael@0: } michael@0: michael@0: private boolean isCurrentDefaultPanel(PanelConfig panelConfig) { michael@0: if (mDefaultPanel == null) { michael@0: return false; michael@0: } michael@0: michael@0: return mDefaultPanel.equals(panelConfig); michael@0: } michael@0: michael@0: private void findNewDefault() { michael@0: // Pick the first panel that is neither disabled nor currently michael@0: // set as default. michael@0: for (PanelConfig panelConfig : mConfigMap.values()) { michael@0: if (!panelConfig.isDefault() && !panelConfig.isDisabled()) { michael@0: setDefault(panelConfig.getId()); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: mDefaultPanel = null; michael@0: } michael@0: michael@0: /** michael@0: * Makes an ordered list of PanelConfigs that can be references michael@0: * or deep copied objects. michael@0: * michael@0: * @param deepCopy true to make deep-copied objects michael@0: * @return ordered List of PanelConfigs michael@0: */ michael@0: private List makeOrderedCopy(boolean deepCopy) { michael@0: final List copiedList = new ArrayList(mConfigOrder.size()); michael@0: for (String panelId : mConfigOrder) { michael@0: PanelConfig panelConfig = mConfigMap.get(panelId); michael@0: if (deepCopy) { michael@0: panelConfig = new PanelConfig(panelConfig); michael@0: } michael@0: copiedList.add(panelConfig); michael@0: } michael@0: michael@0: return copiedList; michael@0: } michael@0: michael@0: private void setPanelIsDisabled(PanelConfig panelConfig, boolean disabled) { michael@0: if (panelConfig.isDisabled() == disabled) { michael@0: return; michael@0: } michael@0: michael@0: panelConfig.setIsDisabled(disabled); michael@0: mEnabledCount += (disabled ? -1 : 1); michael@0: } michael@0: michael@0: /** michael@0: * Gets the ID of the current default panel. michael@0: */ michael@0: public String getDefaultPanelId() { michael@0: ThreadUtils.assertOnThread(mOriginalThread); michael@0: michael@0: if (mDefaultPanel == null) { michael@0: return null; michael@0: } michael@0: michael@0: return mDefaultPanel.getId(); michael@0: } michael@0: michael@0: /** michael@0: * Set a new default panel. michael@0: * michael@0: * @param panelId the ID of the new default panel. michael@0: */ michael@0: public void setDefault(String panelId) { michael@0: ThreadUtils.assertOnThread(mOriginalThread); michael@0: michael@0: final PanelConfig panelConfig = getPanelOrThrow(panelId); michael@0: if (isCurrentDefaultPanel(panelConfig)) { michael@0: return; michael@0: } michael@0: michael@0: if (mDefaultPanel != null) { michael@0: mDefaultPanel.setIsDefault(false); michael@0: } michael@0: michael@0: panelConfig.setIsDefault(true); michael@0: setPanelIsDisabled(panelConfig, false); michael@0: michael@0: mDefaultPanel = panelConfig; michael@0: mHasChanged = true; michael@0: } michael@0: michael@0: /** michael@0: * Toggles disabled state for a panel. michael@0: * michael@0: * @param panelId the ID of the target panel. michael@0: * @param disabled true to disable the panel. michael@0: */ michael@0: public void setDisabled(String panelId, boolean disabled) { michael@0: ThreadUtils.assertOnThread(mOriginalThread); michael@0: michael@0: final PanelConfig panelConfig = getPanelOrThrow(panelId); michael@0: if (panelConfig.isDisabled() == disabled) { michael@0: return; michael@0: } michael@0: michael@0: setPanelIsDisabled(panelConfig, disabled); michael@0: michael@0: if (disabled) { michael@0: if (isCurrentDefaultPanel(panelConfig)) { michael@0: panelConfig.setIsDefault(false); michael@0: findNewDefault(); michael@0: } michael@0: } else if (mEnabledCount == 1) { michael@0: setDefault(panelId); michael@0: } michael@0: michael@0: mHasChanged = true; michael@0: } michael@0: michael@0: /** michael@0: * Adds a new {@code PanelConfig}. It will do nothing if the michael@0: * {@code Editor} already contains a panel with the same ID. michael@0: * michael@0: * @param panelConfig the {@code PanelConfig} instance to be added. michael@0: * @return true if the item has been added. michael@0: */ michael@0: public boolean install(PanelConfig panelConfig) { michael@0: ThreadUtils.assertOnThread(mOriginalThread); michael@0: michael@0: if (panelConfig == null) { michael@0: throw new IllegalStateException("Can't install a null panel"); michael@0: } michael@0: michael@0: if (!panelConfig.isDynamic()) { michael@0: throw new IllegalStateException("Can't install a built-in panel: " + panelConfig.getId()); michael@0: } michael@0: michael@0: if (panelConfig.isDisabled()) { michael@0: throw new IllegalStateException("Can't install a disabled panel: " + panelConfig.getId()); michael@0: } michael@0: michael@0: boolean installed = false; michael@0: michael@0: final String id = panelConfig.getId(); michael@0: if (!mConfigMap.containsKey(id)) { michael@0: mConfigMap.put(id, panelConfig); michael@0: mConfigOrder.add(id); michael@0: michael@0: mEnabledCount++; michael@0: if (mEnabledCount == 1 || panelConfig.isDefault()) { michael@0: setDefault(panelConfig.getId()); michael@0: } michael@0: michael@0: installed = true; michael@0: michael@0: // Add an event to the queue if a new panel is sucessfully installed. michael@0: mEventQueue.add(GeckoEvent.createBroadcastEvent("HomePanels:Installed", panelConfig.getId())); michael@0: } michael@0: michael@0: mHasChanged = true; michael@0: return installed; michael@0: } michael@0: michael@0: /** michael@0: * Removes an existing panel. michael@0: * michael@0: * @return true if the item has been removed. michael@0: */ michael@0: public boolean uninstall(String panelId) { michael@0: ThreadUtils.assertOnThread(mOriginalThread); michael@0: michael@0: final PanelConfig panelConfig = mConfigMap.get(panelId); michael@0: if (panelConfig == null) { michael@0: return false; michael@0: } michael@0: michael@0: if (!panelConfig.isDynamic()) { michael@0: throw new IllegalStateException("Can't uninstall a built-in panel: " + panelConfig.getId()); michael@0: } michael@0: michael@0: mConfigMap.remove(panelId); michael@0: mConfigOrder.remove(panelId); michael@0: michael@0: if (!panelConfig.isDisabled()) { michael@0: mEnabledCount--; michael@0: } michael@0: michael@0: if (isCurrentDefaultPanel(panelConfig)) { michael@0: findNewDefault(); michael@0: } michael@0: michael@0: // Add an event to the queue if a panel is succesfully uninstalled. michael@0: mEventQueue.add(GeckoEvent.createBroadcastEvent("HomePanels:Uninstalled", panelId)); michael@0: michael@0: mHasChanged = true; michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Moves panel associated with panelId to the specified position. michael@0: * michael@0: * @param panelId Id of panel michael@0: * @param destIndex Destination position michael@0: * @return true if move succeeded michael@0: */ michael@0: public boolean moveTo(String panelId, int destIndex) { michael@0: ThreadUtils.assertOnThread(mOriginalThread); michael@0: michael@0: if (!mConfigOrder.contains(panelId)) { michael@0: return false; michael@0: } michael@0: michael@0: mConfigOrder.remove(panelId); michael@0: mConfigOrder.add(destIndex, panelId); michael@0: mHasChanged = true; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Replaces an existing panel with a new {@code PanelConfig} instance. michael@0: * michael@0: * @return true if the item has been updated. michael@0: */ michael@0: public boolean update(PanelConfig panelConfig) { michael@0: ThreadUtils.assertOnThread(mOriginalThread); michael@0: michael@0: if (panelConfig == null) { michael@0: throw new IllegalStateException("Can't update a null panel"); michael@0: } michael@0: michael@0: boolean updated = false; michael@0: michael@0: final String id = panelConfig.getId(); michael@0: if (mConfigMap.containsKey(id)) { michael@0: final PanelConfig oldPanelConfig = mConfigMap.put(id, panelConfig); michael@0: michael@0: // The disabled and default states can't never be michael@0: // changed by an update operation. michael@0: panelConfig.setIsDefault(oldPanelConfig.isDefault()); michael@0: panelConfig.setIsDisabled(oldPanelConfig.isDisabled()); michael@0: michael@0: updated = true; michael@0: } michael@0: michael@0: mHasChanged = true; michael@0: return updated; michael@0: } michael@0: michael@0: /** michael@0: * Saves the current {@code Editor} state asynchronously in the michael@0: * background thread. michael@0: * michael@0: * @return the resulting {@code State} instance. michael@0: */ michael@0: public State apply() { michael@0: ThreadUtils.assertOnThread(mOriginalThread); michael@0: michael@0: // We're about to save the current state in the background thread michael@0: // so we should use a deep copy of the PanelConfig instances to michael@0: // avoid saving corrupted state. michael@0: final State newConfigState = michael@0: new State(mHomeConfig, makeOrderedCopy(true), isDefault()); michael@0: michael@0: // Copy the event queue to a new list, so that we only modify mEventQueue on michael@0: // the original thread where it was created. michael@0: final LinkedList eventQueueCopy = new LinkedList(mEventQueue); michael@0: mEventQueue.clear(); michael@0: michael@0: ThreadUtils.getBackgroundHandler().post(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: mHomeConfig.save(newConfigState); michael@0: michael@0: // Send pending events after the new config is saved. michael@0: sendEventsToGecko(eventQueueCopy); michael@0: } michael@0: }); michael@0: michael@0: return newConfigState; michael@0: } michael@0: michael@0: /** michael@0: * Saves the current {@code Editor} state synchronously in the michael@0: * current thread. michael@0: * michael@0: * @return the resulting {@code State} instance. michael@0: */ michael@0: public State commit() { michael@0: ThreadUtils.assertOnThread(mOriginalThread); michael@0: michael@0: final State newConfigState = michael@0: new State(mHomeConfig, makeOrderedCopy(false), isDefault()); michael@0: michael@0: // This is a synchronous blocking operation, hence no michael@0: // need to deep copy the current PanelConfig instances. michael@0: mHomeConfig.save(newConfigState); michael@0: michael@0: // Send pending events after the new config is saved. michael@0: sendEventsToGecko(mEventQueue); michael@0: mEventQueue.clear(); michael@0: michael@0: return newConfigState; michael@0: } michael@0: michael@0: /** michael@0: * Returns whether the {@code Editor} represents the default michael@0: * {@code HomeConfig} configuration without any unsaved changes. michael@0: */ michael@0: public boolean isDefault() { michael@0: ThreadUtils.assertOnThread(mOriginalThread); michael@0: michael@0: return (!mHasChanged && mIsFromDefault); michael@0: } michael@0: michael@0: public boolean isEmpty() { michael@0: return mConfigMap.isEmpty(); michael@0: } michael@0: michael@0: private void sendEventsToGecko(List events) { michael@0: for (GeckoEvent e : events) { michael@0: GeckoAppShell.sendEventToGecko(e); michael@0: } michael@0: } michael@0: michael@0: private class EditorIterator implements Iterator { michael@0: private final Iterator mOrderIterator; michael@0: michael@0: public EditorIterator() { michael@0: mOrderIterator = mConfigOrder.iterator(); michael@0: } michael@0: michael@0: @Override michael@0: public boolean hasNext() { michael@0: return mOrderIterator.hasNext(); michael@0: } michael@0: michael@0: @Override michael@0: public PanelConfig next() { michael@0: final String panelId = mOrderIterator.next(); michael@0: return mConfigMap.get(panelId); michael@0: } michael@0: michael@0: @Override michael@0: public void remove() { michael@0: throw new UnsupportedOperationException("Can't 'remove' from on Editor iterator."); michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public Iterator iterator() { michael@0: ThreadUtils.assertOnThread(mOriginalThread); michael@0: michael@0: return new EditorIterator(); michael@0: } michael@0: } michael@0: michael@0: public interface OnReloadListener { michael@0: public void onReload(); michael@0: } michael@0: michael@0: public interface HomeConfigBackend { michael@0: public State load(); michael@0: public void save(State configState); michael@0: public String getLocale(); michael@0: public void setOnReloadListener(OnReloadListener listener); michael@0: } michael@0: michael@0: // UUIDs used to create PanelConfigs for default built-in panels michael@0: private static final String TOP_SITES_PANEL_ID = "4becc86b-41eb-429a-a042-88fe8b5a094e"; michael@0: private static final String BOOKMARKS_PANEL_ID = "7f6d419a-cd6c-4e34-b26f-f68b1b551907"; michael@0: private static final String READING_LIST_PANEL_ID = "20f4549a-64ad-4c32-93e4-1dcef792733b"; michael@0: private static final String HISTORY_PANEL_ID = "f134bf20-11f7-4867-ab8b-e8e705d7fbe8"; michael@0: michael@0: private final HomeConfigBackend mBackend; michael@0: michael@0: public HomeConfig(HomeConfigBackend backend) { michael@0: mBackend = backend; michael@0: } michael@0: michael@0: public State load() { michael@0: final State configState = mBackend.load(); michael@0: configState.setHomeConfig(this); michael@0: michael@0: return configState; michael@0: } michael@0: michael@0: public String getLocale() { michael@0: return mBackend.getLocale(); michael@0: } michael@0: michael@0: public void save(State configState) { michael@0: mBackend.save(configState); michael@0: } michael@0: michael@0: public void setOnReloadListener(OnReloadListener listener) { michael@0: mBackend.setOnReloadListener(listener); michael@0: } michael@0: michael@0: public static PanelConfig createBuiltinPanelConfig(Context context, PanelType panelType) { michael@0: return createBuiltinPanelConfig(context, panelType, EnumSet.noneOf(PanelConfig.Flags.class)); michael@0: } michael@0: michael@0: public static PanelConfig createBuiltinPanelConfig(Context context, PanelType panelType, EnumSet flags) { michael@0: int titleId = 0; michael@0: String id = null; michael@0: michael@0: switch(panelType) { michael@0: case TOP_SITES: michael@0: titleId = R.string.home_top_sites_title; michael@0: id = TOP_SITES_PANEL_ID; michael@0: break; michael@0: michael@0: case BOOKMARKS: michael@0: titleId = R.string.bookmarks_title; michael@0: id = BOOKMARKS_PANEL_ID; michael@0: break; michael@0: michael@0: case HISTORY: michael@0: titleId = R.string.home_history_title; michael@0: id = HISTORY_PANEL_ID; michael@0: break; michael@0: michael@0: case READING_LIST: michael@0: titleId = R.string.reading_list_title; michael@0: id = READING_LIST_PANEL_ID; michael@0: break; michael@0: michael@0: case DYNAMIC: michael@0: throw new IllegalArgumentException("createBuiltinPanelConfig() is only for built-in panels"); michael@0: } michael@0: michael@0: return new PanelConfig(panelType, context.getString(titleId), id, flags); michael@0: } michael@0: michael@0: public static HomeConfig getDefault(Context context) { michael@0: return new HomeConfig(new HomeConfigPrefsBackend(context)); michael@0: } michael@0: }