diff --git a/mobile/android/base/home/HomeConfig.java b/mobile/android/base/home/HomeConfig.java index 14ff45b94e87..7fcc92044fd0 100644 --- a/mobile/android/base/home/HomeConfig.java +++ b/mobile/android/base/home/HomeConfig.java @@ -6,6 +6,7 @@ package org.mozilla.gecko.home; import org.mozilla.gecko.R; +import org.mozilla.gecko.util.ThreadUtils; import org.json.JSONArray; import org.json.JSONException; @@ -17,7 +18,11 @@ import android.os.Parcelable; import android.text.TextUtils; import java.util.ArrayList; +import java.util.Collections; import java.util.EnumSet; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; public final class HomeConfig { @@ -265,7 +270,7 @@ public final class HomeConfig { return mFlags.contains(Flags.DEFAULT_PANEL); } - public void setIsDefault(boolean isDefault) { + private void setIsDefault(boolean isDefault) { if (isDefault) { mFlags.add(Flags.DEFAULT_PANEL); } else { @@ -277,7 +282,7 @@ public final class HomeConfig { return mFlags.contains(Flags.DISABLED_PANEL); } - public void setIsDisabled(boolean isDisabled) { + private void setIsDisabled(boolean isDisabled) { if (isDisabled) { mFlags.add(Flags.DISABLED_PANEL); } else { @@ -692,6 +697,374 @@ public final class HomeConfig { }; } + /** + * Immutable representation of the current state of {@code HomeConfig}. + * This is what HomeConfig returns from a load() call and takes as + * input to save a new state. + * + * Users of {@code State} should use an {@code Iterator} to iterate + * through the contained {@code PanelConfig} instances. + * + * {@code State} is immutable i.e. you can't add, remove, or update + * contained elements directly. You have to use an {@code Editor} to + * change the state, which can be created through the {@code edit()} + * method. + */ + public static class State implements Iterable { + private final HomeConfig mHomeConfig; + private final List mPanelConfigs; + + private State(HomeConfig homeConfig, List panelConfigs) { + mHomeConfig = homeConfig; + mPanelConfigs = Collections.unmodifiableList(panelConfigs); + } + + @Override + public Iterator iterator() { + return mPanelConfigs.iterator(); + } + + /** + * Creates an {@code Editor} for this state. + */ + public Editor edit() { + return new Editor(mHomeConfig, this); + } + } + + /** + * {@code Editor} allows you to make changes to a {@code State}. You + * can create {@code Editor} by calling {@code edit()} on the target + * {@code State} instance. + * + * {@code Editor} works on a copy of the {@code State} that originated + * it. This means that adding, removing, or updating panels in an + * {@code Editor} will never change the {@code State} which you + * created the {@code Editor} from. Calling {@code commit()} or + * {@code apply()} will cause the new {@code State} instance to be + * created and saved using the {@code HomeConfig} instance that + * created the source {@code State}. + * + * {@code Editor} is *not* thread-safe. You can only make calls on it + * from the thread where it was originally created. It will throw an + * exception if you don't follow this invariant. + */ + public static class Editor implements Iterable { + private final HomeConfig mHomeConfig; + private final HashMap mConfigMap; + private final Thread mOriginalThread; + + private PanelConfig mDefaultPanel; + private int mEnabledCount; + + private Editor(HomeConfig homeConfig, State configState) { + mHomeConfig = homeConfig; + mOriginalThread = Thread.currentThread(); + mConfigMap = new LinkedHashMap(); + mEnabledCount = 0; + + initFromState(configState); + } + + /** + * Initialize the initial state of the editor from the given + * {@sode State}. A LinkedHashMap is used to represent the list of + * panels as it provides fast access to specific panels from IDs + * while also being order-aware. We keep a reference to the + * default panel and the number of enabled panels to avoid iterating + * through the map every time we need those. + * + * @param configState The source State to load the editor from. + */ + private void initFromState(State configState) { + for (PanelConfig panelConfig : configState) { + final PanelConfig panelCopy = new PanelConfig(panelConfig); + + if (!panelCopy.isDisabled()) { + mEnabledCount++; + } + + if (panelCopy.isDefault()) { + if (mDefaultPanel == null) { + mDefaultPanel = panelCopy; + } else { + throw new IllegalStateException("Multiple default panels in HomeConfig state"); + } + } + + mConfigMap.put(panelConfig.getId(), panelCopy); + } + + // We should always have a defined default panel if there's + // at least one enabled panel around. + if (mEnabledCount > 0 && mDefaultPanel == null) { + throw new IllegalStateException("Default panel in HomeConfig state is undefined"); + } + } + + private PanelConfig getPanelOrThrow(String panelId) { + final PanelConfig panelConfig = mConfigMap.get(panelId); + if (panelConfig == null) { + throw new IllegalStateException("Tried to access non-existing panel: " + panelId); + } + + return panelConfig; + } + + private boolean isCurrentDefaultPanel(PanelConfig panelConfig) { + if (mDefaultPanel == null) { + return false; + } + + return mDefaultPanel.equals(panelConfig); + } + + private void findNewDefault() { + // Pick the first panel that is neither disabled nor currently + // set as default. + for (PanelConfig panelConfig : mConfigMap.values()) { + if (!panelConfig.isDefault() && !panelConfig.isDisabled()) { + setDefault(panelConfig.getId()); + return; + } + } + + mDefaultPanel = null; + } + + private List makeDeepCopy() { + List copiedList = new ArrayList(); + for (PanelConfig panelConfig : mConfigMap.values()) { + copiedList.add(new PanelConfig(panelConfig)); + } + + return copiedList; + } + + private void setPanelIsDisabled(PanelConfig panelConfig, boolean disabled) { + if (panelConfig.isDisabled() == disabled) { + return; + } + + panelConfig.setIsDisabled(disabled); + mEnabledCount += (disabled ? -1 : 1); + } + + /** + * Gets the ID of the current default panel. + */ + public String getDefaultPanelId() { + ThreadUtils.assertOnThread(mOriginalThread); + + if (mDefaultPanel == null) { + return null; + } + + return mDefaultPanel.getId(); + } + + /** + * Set a new default panel. + * + * @param panelId the ID of the new default panel. + */ + public void setDefault(String panelId) { + ThreadUtils.assertOnThread(mOriginalThread); + + final PanelConfig panelConfig = getPanelOrThrow(panelId); + if (isCurrentDefaultPanel(panelConfig)) { + return; + } + + if (mDefaultPanel != null) { + mDefaultPanel.setIsDefault(false); + } + + panelConfig.setIsDefault(true); + setPanelIsDisabled(panelConfig, false); + + mDefaultPanel = panelConfig; + } + + /** + * Toggles disabled state for a panel. + * + * @param panelId the ID of the target panel. + * @param disabled true to disable the panel. + */ + public void setDisabled(String panelId, boolean disabled) { + ThreadUtils.assertOnThread(mOriginalThread); + + final PanelConfig panelConfig = getPanelOrThrow(panelId); + if (panelConfig.isDisabled() == disabled) { + return; + } + + setPanelIsDisabled(panelConfig, disabled); + + if (disabled) { + if (isCurrentDefaultPanel(panelConfig)) { + panelConfig.setIsDefault(false); + findNewDefault(); + } + } else if (mEnabledCount == 1) { + setDefault(panelId); + } + } + + /** + * Adds a new {@code PanelConfig}. It will do nothing if the + * {@code Editor} already contains a panel with the same ID. + * + * @param panelConfig the {@code PanelConfig} instance to be added. + * @return true if the item has been added. + */ + public boolean install(PanelConfig panelConfig) { + ThreadUtils.assertOnThread(mOriginalThread); + + if (panelConfig == null) { + throw new IllegalStateException("Can't install a null panel"); + } + + if (!panelConfig.isDynamic()) { + throw new IllegalStateException("Can't install a built-in panel: " + panelConfig.getId()); + } + + if (panelConfig.isDisabled()) { + throw new IllegalStateException("Can't install a disabled panel: " + panelConfig.getId()); + } + + boolean installed = false; + + final String id = panelConfig.getId(); + if (!mConfigMap.containsKey(id)) { + mConfigMap.put(id, panelConfig); + + mEnabledCount++; + if (mEnabledCount == 1 || panelConfig.isDefault()) { + setDefault(panelConfig.getId()); + } + + installed = true; + } + + return installed; + } + + /** + * Removes an existing panel. + * + * @return true if the item has been removed. + */ + public boolean uninstall(String panelId) { + ThreadUtils.assertOnThread(mOriginalThread); + + final PanelConfig panelConfig = mConfigMap.get(panelId); + if (panelConfig == null) { + return false; + } + + if (!panelConfig.isDynamic()) { + throw new IllegalStateException("Can't uninstall a built-in panel: " + panelConfig.getId()); + } + + mConfigMap.remove(panelId); + + if (!panelConfig.isDisabled()) { + mEnabledCount--; + } + + if (isCurrentDefaultPanel(panelConfig)) { + findNewDefault(); + } + + return true; + } + + /** + * Replaces an existing panel with a new {@code PanelConfig} instance. + * + * @return true if the item has been updated. + */ + public boolean update(PanelConfig panelConfig) { + ThreadUtils.assertOnThread(mOriginalThread); + + if (panelConfig == null) { + throw new IllegalStateException("Can't update a null panel"); + } + + boolean updated = false; + + final String id = panelConfig.getId(); + if (mConfigMap.containsKey(id)) { + final PanelConfig oldPanelConfig = mConfigMap.put(id, panelConfig); + + // The disabled and default states can't never be + // changed by an update operation. + panelConfig.setIsDefault(oldPanelConfig.isDefault()); + panelConfig.setIsDisabled(oldPanelConfig.isDisabled()); + + updated = true; + } + + return updated; + } + + /** + * Saves the current {@code Editor} state asynchronously in the + * background thread. + * + * @return the resulting {@code State} instance. + */ + public State apply() { + ThreadUtils.assertOnThread(mOriginalThread); + + // We're about to save the current state in the background thread + // so we should use a deep copy of the PanelConfig instances to + // avoid saving corrupted state. + final State newConfigState = new State(mHomeConfig, makeDeepCopy()); + + ThreadUtils.getBackgroundHandler().post(new Runnable() { + @Override + public void run() { + mHomeConfig.save(newConfigState); + } + }); + + return newConfigState; + } + + /** + * Saves the current {@code Editor} state synchronously in the + * current thread. + * + * @return the resulting {@code State} instance. + */ + public State commit() { + ThreadUtils.assertOnThread(mOriginalThread); + + final State newConfigState = + new State(mHomeConfig, new ArrayList(mConfigMap.values())); + + // This is a synchronous blocking operation, hence no + // need to deep copy the current PanelConfig instances. + mHomeConfig.save(newConfigState); + + return newConfigState; + } + + public boolean isEmpty() { + return mConfigMap.isEmpty(); + } + + @Override + public Iterator iterator() { + ThreadUtils.assertOnThread(mOriginalThread); + + return mConfigMap.values().iterator(); + } + } + public interface OnChangeListener { public void onChange(); } @@ -715,16 +1088,17 @@ public final class HomeConfig { mBackend = backend; } - public List load() { - return mBackend.load(); + public State load() { + final List panelConfigs = mBackend.load(); + return new State(this, panelConfigs); } public String getLocale() { return mBackend.getLocale(); } - public void save(List panelConfigs) { - mBackend.save(panelConfigs); + public void save(State configState) { + mBackend.save(configState.mPanelConfigs); } public void setOnChangeListener(OnChangeListener listener) { diff --git a/mobile/android/base/home/HomeConfigInvalidator.java b/mobile/android/base/home/HomeConfigInvalidator.java index 4e35c0c28b9e..d01042316e89 100644 --- a/mobile/android/base/home/HomeConfigInvalidator.java +++ b/mobile/android/base/home/HomeConfigInvalidator.java @@ -189,36 +189,10 @@ public class HomeConfigInvalidator implements GeckoEventListener { Log.d(LOGTAG, "scheduleInvalidation: scheduled new invalidation: " + mode); } - /** - * Replace an element if a matching PanelConfig is - * present in the given list. - */ - private boolean replacePanelConfig(List panelConfigs, PanelConfig panelConfig) { - final int index = panelConfigs.indexOf(panelConfig); - if (index >= 0) { - panelConfigs.set(index, panelConfig); - Log.d(LOGTAG, "executePendingChanges: replaced position " + index + " with " + panelConfig.getId()); - - return true; - } - - return false; - } - - private PanelConfig findPanelConfigWithId(List panelConfigs, String panelId) { - for (PanelConfig panelConfig : panelConfigs) { - if (panelConfig.getId().equals(panelId)) { - return panelConfig; - } - } - - return null; - } - /** * Runs in the background thread. */ - private List executePendingChanges(List panelConfigs) { + private void executePendingChanges(HomeConfig.Editor editor) { boolean shouldRefresh = false; while (!mPendingChanges.isEmpty()) { @@ -227,17 +201,15 @@ public class HomeConfigInvalidator implements GeckoEventListener { switch (pendingChange.type) { case UNINSTALL: { final String panelId = (String) pendingChange.target; - final PanelConfig panelConfig = findPanelConfigWithId(panelConfigs, panelId); - if (panelConfig != null && panelConfigs.remove(panelConfig)) { - Log.d(LOGTAG, "executePendingChanges: removed panel " + panelConfig.getId()); + if (editor.uninstall(panelId)) { + Log.d(LOGTAG, "executePendingChanges: uninstalled panel " + panelId); } break; } case INSTALL: { final PanelConfig panelConfig = (PanelConfig) pendingChange.target; - if (!replacePanelConfig(panelConfigs, panelConfig)) { - panelConfigs.add(panelConfig); + if (editor.install(panelConfig)) { Log.d(LOGTAG, "executePendingChanges: added panel " + panelConfig.getId()); } break; @@ -245,8 +217,8 @@ public class HomeConfigInvalidator implements GeckoEventListener { case UPDATE: { final PanelConfig panelConfig = (PanelConfig) pendingChange.target; - if (!replacePanelConfig(panelConfigs, panelConfig)) { - Log.w(LOGTAG, "Tried to update non-existing panel " + panelConfig.getId()); + if (editor.update(panelConfig)) { + Log.w(LOGTAG, "executePendingChanges: updated panel " + panelConfig.getId()); } break; } @@ -258,23 +230,19 @@ public class HomeConfigInvalidator implements GeckoEventListener { } if (shouldRefresh) { - return executeRefresh(panelConfigs); - } else { - return panelConfigs; + executeRefresh(editor); } } /** * Runs in the background thread. */ - private List refreshFromPanelInfos(List panelConfigs, List panelInfos) { + private void refreshFromPanelInfos(HomeConfig.Editor editor, List panelInfos) { Log.d(LOGTAG, "refreshFromPanelInfos"); - final int count = panelConfigs.size(); - for (int i = 0; i < count; i++) { - final PanelConfig panelConfig = panelConfigs.get(i); - + for (PanelConfig panelConfig : editor) { PanelConfig refreshedPanelConfig = null; + if (panelConfig.isDynamic()) { for (PanelInfo panelInfo : panelInfos) { if (panelInfo.getId().equals(panelConfig.getId())) { @@ -290,31 +258,26 @@ public class HomeConfigInvalidator implements GeckoEventListener { if (refreshedPanelConfig == null) { Log.d(LOGTAG, "refreshFromPanelInfos: no refreshed panel, falling back: " + panelConfig.getId()); - refreshedPanelConfig = panelConfig; + continue; } - refreshedPanelConfig.setIsDefault(panelConfig.isDefault()); - refreshedPanelConfig.setIsDisabled(panelConfig.isDisabled()); - - Log.d(LOGTAG, "refreshFromPanelInfos: set " + i + " with " + refreshedPanelConfig.getId()); - panelConfigs.set(i, refreshedPanelConfig); + Log.d(LOGTAG, "refreshFromPanelInfos: refreshed panel " + refreshedPanelConfig.getId()); + editor.update(refreshedPanelConfig); } - - return panelConfigs; } /** * Runs in the background thread. */ - private List executeRefresh(List panelConfigs) { - if (panelConfigs.isEmpty()) { - return panelConfigs; + private void executeRefresh(HomeConfig.Editor editor) { + if (editor.isEmpty()) { + return; } Log.d(LOGTAG, "executeRefresh"); final Set ids = new HashSet(); - for (PanelConfig panelConfig : panelConfigs) { + for (PanelConfig panelConfig : editor) { ids.add(panelConfig.getId()); } @@ -339,11 +302,10 @@ public class HomeConfigInvalidator implements GeckoEventListener { panelRequestLock.wait(PANEL_INFO_TIMEOUT_MSEC); Log.d(LOGTAG, "executeRefresh: done fetching panel infos"); - return refreshFromPanelInfos(panelConfigs, latestPanelInfos); + refreshFromPanelInfos(editor, latestPanelInfos); } } catch (InterruptedException e) { Log.e(LOGTAG, "Failed to fetch panels from gecko", e); - return panelConfigs; } } @@ -353,7 +315,9 @@ public class HomeConfigInvalidator implements GeckoEventListener { private class InvalidationRunnable implements Runnable { @Override public void run() { - mHomeConfig.save(executePendingChanges(mHomeConfig.load())); + final HomeConfig.Editor editor = mHomeConfig.load().edit(); + executePendingChanges(editor); + editor.commit(); } }; } diff --git a/mobile/android/base/home/HomeConfigLoader.java b/mobile/android/base/home/HomeConfigLoader.java index 6fa75b5e4d84..8453be239f1f 100644 --- a/mobile/android/base/home/HomeConfigLoader.java +++ b/mobile/android/base/home/HomeConfigLoader.java @@ -13,9 +13,9 @@ import android.support.v4.content.AsyncTaskLoader; import java.util.List; -public class HomeConfigLoader extends AsyncTaskLoader> { +public class HomeConfigLoader extends AsyncTaskLoader { private final HomeConfig mConfig; - private List mPanelConfigs; + private HomeConfig.State mConfigState; public HomeConfigLoader(Context context, HomeConfig homeConfig) { super(context); @@ -23,32 +23,32 @@ public class HomeConfigLoader extends AsyncTaskLoader> { } @Override - public List loadInBackground() { + public HomeConfig.State loadInBackground() { return mConfig.load(); } @Override - public void deliverResult(List panelConfigs) { + public void deliverResult(HomeConfig.State configState) { if (isReset()) { - mPanelConfigs = null; + mConfigState = null; return; } - mPanelConfigs = panelConfigs; + mConfigState = configState; mConfig.setOnChangeListener(new ForceLoadChangeListener()); if (isStarted()) { - super.deliverResult(panelConfigs); + super.deliverResult(configState); } } @Override protected void onStartLoading() { - if (mPanelConfigs != null) { - deliverResult(mPanelConfigs); + if (mConfigState != null) { + deliverResult(mConfigState); } - if (takeContentChanged() || mPanelConfigs == null) { + if (takeContentChanged() || mConfigState == null) { forceLoad(); } } @@ -59,8 +59,8 @@ public class HomeConfigLoader extends AsyncTaskLoader> { } @Override - public void onCanceled(List panelConfigs) { - mPanelConfigs = null; + public void onCanceled(HomeConfig.State configState) { + mConfigState = null; } @Override @@ -70,7 +70,7 @@ public class HomeConfigLoader extends AsyncTaskLoader> { // Ensure the loader is stopped. onStopLoading(); - mPanelConfigs = null; + mConfigState = null; mConfig.setOnChangeListener(null); } diff --git a/mobile/android/base/home/HomePager.java b/mobile/android/base/home/HomePager.java index 4038eaeea501..a8d8663a09d1 100644 --- a/mobile/android/base/home/HomePager.java +++ b/mobile/android/base/home/HomePager.java @@ -250,7 +250,7 @@ public class HomePager extends ViewPager { mHomeBanner.setActive(active); } - private void updateUiFromPanelConfigs(List panelConfigs) { + private void updateUiFromConfigState(HomeConfig.State configState) { // We only care about the adapter if HomePager is currently // loaded, which means it's visible in the activity. if (!mLoaded) { @@ -270,7 +270,7 @@ public class HomePager extends ViewPager { // Only keep enabled panels. final List enabledPanels = new ArrayList(); - for (PanelConfig panelConfig : panelConfigs) { + for (PanelConfig panelConfig : configState) { if (!panelConfig.isDisabled()) { enabledPanels.add(panelConfig); } @@ -314,19 +314,19 @@ public class HomePager extends ViewPager { } } - private class ConfigLoaderCallbacks implements LoaderCallbacks> { + private class ConfigLoaderCallbacks implements LoaderCallbacks { @Override - public Loader> onCreateLoader(int id, Bundle args) { + public Loader onCreateLoader(int id, Bundle args) { return new HomeConfigLoader(mContext, mConfig); } @Override - public void onLoadFinished(Loader> loader, List panelConfigs) { - updateUiFromPanelConfigs(panelConfigs); + public void onLoadFinished(Loader loader, HomeConfig.State configState) { + updateUiFromConfigState(configState); } @Override - public void onLoaderReset(Loader> loader) { + public void onLoaderReset(Loader loader) { } } diff --git a/mobile/android/base/home/HomePanelPicker.java b/mobile/android/base/home/HomePanelPicker.java index 8274619cb4af..0e41e089b201 100644 --- a/mobile/android/base/home/HomePanelPicker.java +++ b/mobile/android/base/home/HomePanelPicker.java @@ -224,17 +224,17 @@ public class HomePanelPicker extends FragmentActivity { /** * Fetch installed Home panels and update the adapter for this activity. */ - private class ConfigLoaderCallbacks implements LoaderCallbacks> { + private class ConfigLoaderCallbacks implements LoaderCallbacks { @Override - public Loader> onCreateLoader(int id, Bundle args) { + public Loader onCreateLoader(int id, Bundle args) { final HomeConfig homeConfig = HomeConfig.getDefault(HomePanelPicker.this); return new HomeConfigLoader(HomePanelPicker.this, homeConfig); } @Override - public void onLoadFinished(Loader> loader, List panelConfigs) { + public void onLoadFinished(Loader loader, HomeConfig.State configState) { mCurrentPanelsIds = new ArrayList(); - for (PanelConfig panelConfig : panelConfigs) { + for (PanelConfig panelConfig : configState) { mCurrentPanelsIds.add(panelConfig.getId()); } @@ -242,6 +242,6 @@ public class HomePanelPicker extends FragmentActivity { } @Override - public void onLoaderReset(Loader> loader) {} + public void onLoaderReset(Loader loader) {} } } diff --git a/mobile/android/base/preferences/PanelsPreferenceCategory.java b/mobile/android/base/preferences/PanelsPreferenceCategory.java index da46623781be..d342357e0789 100644 --- a/mobile/android/base/preferences/PanelsPreferenceCategory.java +++ b/mobile/android/base/preferences/PanelsPreferenceCategory.java @@ -20,10 +20,9 @@ public class PanelsPreferenceCategory extends CustomListCategory { public static final String LOGTAG = "PanelsPrefCategory"; protected HomeConfig mHomeConfig; - protected List mPanelConfigs; + protected HomeConfig.Editor mConfigEditor; - protected UiAsyncTask> mLoadTask; - protected UiAsyncTask mSaveTask; + protected UiAsyncTask mLoadTask; public PanelsPreferenceCategory(Context context) { super(context); @@ -55,23 +54,23 @@ public class PanelsPreferenceCategory extends CustomListCategory { * Load the Home Panels config and populate the preferences screen and maintain local state. */ private void loadHomeConfig() { - mLoadTask = new UiAsyncTask>(ThreadUtils.getBackgroundHandler()) { + mLoadTask = new UiAsyncTask(ThreadUtils.getBackgroundHandler()) { @Override - public List doInBackground(Void... params) { + public HomeConfig.State doInBackground(Void... params) { return mHomeConfig.load(); } @Override - public void onPostExecute(List panelConfigs) { - mPanelConfigs = panelConfigs; - displayHomeConfig(); + public void onPostExecute(HomeConfig.State configState) { + mConfigEditor = configState.edit(); + displayHomeConfig(configState); } }; mLoadTask.execute(); } - private void displayHomeConfig() { - for (PanelConfig panelConfig : mPanelConfigs) { + private void displayHomeConfig(HomeConfig.State configState) { + for (PanelConfig panelConfig : configState) { // Create and add the pref. final PanelsPreference pref = new PanelsPreference(getContext(), PanelsPreferenceCategory.this); pref.setTitle(panelConfig.getTitle()); @@ -79,51 +78,47 @@ public class PanelsPreferenceCategory extends CustomListCategory { // XXX: Pull icon from PanelInfo. addPreference(pref); - if (panelConfig.isDefault()) { - mDefaultReference = pref; - pref.setIsDefault(true); - } - if (panelConfig.isDisabled()) { pref.setHidden(true); } } + + setDefaultFromConfig(); } - /** - * Update HomeConfig off the main thread. - * - * @param panelConfigs Configuration to be saved - */ - private void saveHomeConfig() { - if (mPanelConfigs == null) { + private void setDefaultFromConfig() { + final String defaultPanelId = mConfigEditor.getDefaultPanelId(); + if (defaultPanelId == null) { + mDefaultReference = null; return; } - final List panelConfigs = makeConfigListDeepCopy(); - mSaveTask = new UiAsyncTask(ThreadUtils.getBackgroundHandler()) { - @Override - public Void doInBackground(Void... params) { - mHomeConfig.save(panelConfigs); - return null; - } - }; - mSaveTask.execute(); - } + final int prefCount = getPreferenceCount(); - private List makeConfigListDeepCopy() { - List copiedList = new ArrayList(); - for (PanelConfig panelConfig : mPanelConfigs) { - copiedList.add(new PanelConfig(panelConfig)); + // First preference (index 0) is Preference to add panels. + for (int i = 1; i < prefCount; i++) { + final PanelsPreference pref = (PanelsPreference) getPreference(i); + + if (defaultPanelId.equals(pref.getKey())) { + super.setDefault(pref); + break; + } } - return copiedList; } @Override public void setDefault(CustomListPreference pref) { super.setDefault(pref); - updateConfigDefault(); - saveHomeConfig(); + + final String id = pref.getKey(); + + final String defaultPanelId = mConfigEditor.getDefaultPanelId(); + if (defaultPanelId != null && defaultPanelId.equals(id)) { + return; + } + + mConfigEditor.setDefault(id); + mConfigEditor.apply(); } @Override @@ -131,48 +126,14 @@ public class PanelsPreferenceCategory extends CustomListCategory { if (mLoadTask != null) { mLoadTask.cancel(true); } - - if (mSaveTask != null) { - mSaveTask.cancel(true); - } - } - - /** - * Update the local HomeConfig default state from mDefaultReference. - */ - private void updateConfigDefault() { - String id = null; - if (mDefaultReference != null) { - id = mDefaultReference.getKey(); - } - - for (PanelConfig panelConfig : mPanelConfigs) { - if (TextUtils.equals(panelConfig.getId(), id)) { - panelConfig.setIsDefault(true); - panelConfig.setIsDisabled(false); - } else { - panelConfig.setIsDefault(false); - } - } } @Override public void uninstall(CustomListPreference pref) { + mConfigEditor.uninstall(pref.getKey()); + mConfigEditor.apply(); + super.uninstall(pref); - // This could change the default, so update the local version of the config. - updateConfigDefault(); - - final String id = pref.getKey(); - PanelConfig toRemove = null; - for (PanelConfig panelConfig : mPanelConfigs) { - if (TextUtils.equals(panelConfig.getId(), id)) { - toRemove = panelConfig; - break; - } - } - mPanelConfigs.remove(toRemove); - - saveHomeConfig(); } /** @@ -183,43 +144,11 @@ public class PanelsPreferenceCategory extends CustomListCategory { * @param toHide New hidden state of the preference */ protected void setHidden(PanelsPreference pref, boolean toHide) { + mConfigEditor.setDisabled(pref.getKey(), toHide); + mConfigEditor.apply(); + pref.setHidden(toHide); - ensureDefaultForHide(pref, toHide); - - final String id = pref.getKey(); - for (PanelConfig panelConfig : mPanelConfigs) { - if (TextUtils.equals(panelConfig.getId(), id)) { - panelConfig.setIsDisabled(toHide); - break; - } - } - - saveHomeConfig(); - } - - /** - * Ensure a default is set (if possible) for hiding/showing a pref. - * If hiding, try to find an enabled pref to set as the default. - * If showing, set it as the default if there is no default currently. - * - * This updates the local HomeConfig state. - * - * @param pref Preference getting updated - * @param toHide Boolean of the new hidden state - */ - private void ensureDefaultForHide(PanelsPreference pref, boolean toHide) { - if (toHide) { - // Set a default if there is an enabled panel left. - if (pref == mDefaultReference) { - setFallbackDefault(); - updateConfigDefault(); - } - } else { - if (mDefaultReference == null) { - super.setDefault(pref); - updateConfigDefault(); - } - } + setDefaultFromConfig(); } /** @@ -228,15 +157,6 @@ public class PanelsPreferenceCategory extends CustomListCategory { */ @Override protected void setFallbackDefault() { - // First preference (index 0) is Preference to add panels. - final int prefsCount = getPreferenceCount(); - for (int i = 1; i < prefsCount; i++) { - final PanelsPreference pref = (PanelsPreference) getPreference(i); - if (!pref.isHidden()) { - super.setDefault(pref); - return; - } - } - mDefaultReference = null; + setDefaultFromConfig(); } }