diff --git a/mobile/android/base/home/HomeConfig.java b/mobile/android/base/home/HomeConfig.java index 6f299321d98..561dbefeb3b 100644 --- a/mobile/android/base/home/HomeConfig.java +++ b/mobile/android/base/home/HomeConfig.java @@ -18,7 +18,7 @@ import java.util.ArrayList; import java.util.EnumSet; import java.util.List; -final class HomeConfig { +public final class HomeConfig { /** * Used to determine what type of HomeFragment subclass to use when creating * a given panel. With the exception of DYNAMIC, all of these types correspond @@ -100,11 +100,14 @@ final class HomeConfig { private static final String JSON_KEY_LAYOUT = "layout"; private static final String JSON_KEY_VIEWS = "views"; private static final String JSON_KEY_DEFAULT = "default"; + private static final String JSON_KEY_DISABLED = "disabled"; private static final int IS_DEFAULT = 1; + private static final int IS_DISABLED = 1; public enum Flags { - DEFAULT_PANEL + DEFAULT_PANEL, + DISABLED_PANEL } public PanelConfig(JSONObject json) throws JSONException, IllegalArgumentException { @@ -140,6 +143,11 @@ final class HomeConfig { mFlags.add(Flags.DEFAULT_PANEL); } + final boolean isDisabled = (json.optInt(JSON_KEY_DISABLED, -1) == IS_DISABLED); + if (isDisabled) { + mFlags.add(Flags.DISABLED_PANEL); + } + validate(); } @@ -158,6 +166,21 @@ final class HomeConfig { validate(); } + public PanelConfig(PanelConfig panelConfig) { + mType = panelConfig.mType; + mTitle = panelConfig.mTitle; + mId = panelConfig.mId; + mLayoutType = panelConfig.mLayoutType; + + mViews = new ArrayList(); + for (ViewConfig viewConfig : panelConfig.mViews) { + mViews.add(new ViewConfig(viewConfig)); + } + mFlags = panelConfig.mFlags.clone(); + + validate(); + } + public PanelConfig(PanelType type, String title, String id) { this(type, title, id, EnumSet.noneOf(Flags.class)); } @@ -171,9 +194,9 @@ final class HomeConfig { mType = type; mTitle = title; mId = id; - mFlags = flags; mLayoutType = layoutType; mViews = views; + mFlags = flags; validate(); } @@ -232,6 +255,26 @@ final class HomeConfig { return mFlags.contains(Flags.DEFAULT_PANEL); } + public void setIsDefault(boolean isDefault) { + if (isDefault) { + mFlags.add(Flags.DEFAULT_PANEL); + } else { + mFlags.remove(Flags.DEFAULT_PANEL); + } + } + + public boolean isDisabled() { + return mFlags.contains(Flags.DISABLED_PANEL); + } + + public void setIsDisabled(boolean isDisabled) { + if (isDisabled) { + mFlags.add(Flags.DISABLED_PANEL); + } else { + mFlags.remove(Flags.DISABLED_PANEL); + } + } + public JSONObject toJSON() throws JSONException { final JSONObject json = new JSONObject(); @@ -260,6 +303,10 @@ final class HomeConfig { json.put(JSON_KEY_DEFAULT, IS_DEFAULT); } + if (mFlags.contains(Flags.DISABLED_PANEL)) { + json.put(JSON_KEY_DISABLED, IS_DISABLED); + } + return json; } @@ -415,6 +462,13 @@ final class HomeConfig { validate(); } + public ViewConfig(ViewConfig viewConfig) { + mType = viewConfig.mType; + mDatasetId = viewConfig.mDatasetId; + + validate(); + } + public ViewConfig(ViewType type, String datasetId) { mType = type; mDatasetId = datasetId; diff --git a/mobile/android/base/home/HomePager.java b/mobile/android/base/home/HomePager.java index 9a784fbc189..781f0a8e2d8 100644 --- a/mobile/android/base/home/HomePager.java +++ b/mobile/android/base/home/HomePager.java @@ -28,6 +28,7 @@ import android.view.MotionEvent; import android.view.ViewGroup; import android.view.View; +import java.util.ArrayList; import java.util.EnumSet; import java.util.List; @@ -296,14 +297,21 @@ public class HomePager extends ViewPager { // in the pager. setAdapter(null); + // Only keep enabled panels. + final List enabledPanels = new ArrayList(); + + for (PanelConfig panelConfig : panelConfigs) { + if (!panelConfig.isDisabled()) { + enabledPanels.add(panelConfig); + } + } + // Update the adapter with the new panel configs - adapter.update(panelConfigs); + adapter.update(enabledPanels); - // Hide the tab strip if the new configuration contains - // no panels for some reason. - final int count = (panelConfigs != null ? panelConfigs.size() : 0); + // Hide the tab strip if the new configuration contains no panels. + final int count = enabledPanels.size(); mTabStrip.setVisibility(count > 0 ? View.VISIBLE : View.INVISIBLE); - // Re-install the adapter with the final state // in the pager. setAdapter(adapter); @@ -316,7 +324,7 @@ public class HomePager extends ViewPager { mInitialPanelId = null; } else { for (int i = 0; i < count; i++) { - final PanelConfig panelConfig = panelConfigs.get(i); + final PanelConfig panelConfig = enabledPanels.get(i); if (panelConfig.isDefault()) { setCurrentItem(i, false); break; diff --git a/mobile/android/base/locales/en-US/android_strings.dtd b/mobile/android/base/locales/en-US/android_strings.dtd index f330c49703c..739d5aaeac6 100644 --- a/mobile/android/base/locales/en-US/android_strings.dtd +++ b/mobile/android/base/locales/en-US/android_strings.dtd @@ -85,6 +85,9 @@ + + + @@ -158,6 +161,9 @@ size. --> + + + diff --git a/mobile/android/base/moz.build b/mobile/android/base/moz.build index b3f175e09a5..01d92b2bc1a 100644 --- a/mobile/android/base/moz.build +++ b/mobile/android/base/moz.build @@ -276,6 +276,8 @@ gbjar.sources += [ 'preferences/GeckoPreferences.java', 'preferences/LinkPreference.java', 'preferences/MultiChoicePreference.java', + 'preferences/PanelsPreference.java', + 'preferences/PanelsPreferenceCategory.java', 'preferences/PrivateDataPreference.java', 'preferences/SearchEnginePreference.java', 'preferences/SearchPreferenceCategory.java', diff --git a/mobile/android/base/preferences/CustomListCategory.java b/mobile/android/base/preferences/CustomListCategory.java index b3571c1811e..ee5a46bef25 100644 --- a/mobile/android/base/preferences/CustomListCategory.java +++ b/mobile/android/base/preferences/CustomListCategory.java @@ -34,7 +34,7 @@ public abstract class CustomListCategory extends PreferenceCategory { * Set the default to some available list item. Used if the current default is removed or * disabled. */ - private void setFallbackDefault() { + protected void setFallbackDefault() { if (getPreferenceCount() > 0) { CustomListPreference aItem = (CustomListPreference) getPreference(0); setDefault(aItem); @@ -62,7 +62,10 @@ public abstract class CustomListCategory extends PreferenceCategory { * @param item The intended new default. */ public void setDefault(CustomListPreference item) { - mDefaultReference.setIsDefault(false); + if (mDefaultReference != null) { + mDefaultReference.setIsDefault(false); + } + item.setIsDefault(true); mDefaultReference = item; } diff --git a/mobile/android/base/preferences/PanelsPreference.java b/mobile/android/base/preferences/PanelsPreference.java new file mode 100644 index 00000000000..0feef66a8bc --- /dev/null +++ b/mobile/android/base/preferences/PanelsPreference.java @@ -0,0 +1,122 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.preferences; + +import android.content.Context; +import android.content.res.Resources; +import android.preference.Preference; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.mozilla.gecko.R; + +public class PanelsPreference extends CustomListPreference { + protected String LOGTAG = "PanelsPreference"; + + private static final int INDEX_SHOW_BUTTON = 1; + private static final int INDEX_REMOVE_BUTTON = 2; + + private final String LABEL_HIDE; + private final String LABEL_SHOW; + + protected boolean mIsHidden = false; + + public PanelsPreference(Context context, CustomListCategory parentCategory) { + super(context, parentCategory); + + Resources res = getContext().getResources(); + LABEL_HIDE = res.getString(R.string.pref_panels_hide); + LABEL_SHOW = res.getString(R.string.pref_panels_show); + } + + @Override + protected int getPreferenceLayoutResource() { + return R.layout.preference_panels; + } + + @Override + protected void onBindView(View view) { + super.onBindView(view); + + // Override view handling so we can grey out "hidden" PanelPreferences. + view.setEnabled(!mIsHidden); + + if (view instanceof ViewGroup) { + final ViewGroup group = (ViewGroup) view; + for (int i = 0; i < group.getChildCount(); i++) { + group.getChildAt(i).setEnabled(!mIsHidden); + } + } + } + + @Override + protected String[] getDialogStrings() { + Resources res = getContext().getResources(); + // XXX: Don't provide the "Remove" string for now, because we only support built-in + // panels, which can only be disabled. + return new String[] { LABEL_SET_AS_DEFAULT, + LABEL_HIDE }; + } + + @Override + public void setIsDefault(boolean isDefault) { + mIsDefault = isDefault; + if (isDefault) { + setSummary(LABEL_IS_DEFAULT); + if (mIsHidden) { + // Unhide the panel if it's being set as the default. + setHidden(false); + } + } else { + setSummary(""); + } + } + + @Override + protected void onDialogIndexClicked(int index) { + switch(index) { + case INDEX_SET_DEFAULT_BUTTON: + mParentCategory.setDefault(this); + break; + + case INDEX_SHOW_BUTTON: + ((PanelsPreferenceCategory) mParentCategory).setHidden(this, !mIsHidden); + break; + + case INDEX_REMOVE_BUTTON: + mParentCategory.uninstall(this); + break; + + default: + Log.w(LOGTAG, "Selected index out of range: " + index); + } + } + + @Override + protected void configureShownDialog() { + super.configureShownDialog(); + + // Handle Show/Hide buttons. + final TextView hideButton = (TextView) mDialog.getListView().getChildAt(INDEX_SHOW_BUTTON); + hideButton.setText(mIsHidden ? LABEL_SHOW : LABEL_HIDE); + } + + public void setHidden(boolean toHide) { + if (toHide) { + setIsDefault(false); + } + + if (mIsHidden != toHide) { + mIsHidden = toHide; + notifyChanged(); + } + } + + public boolean isHidden() { + return mIsHidden; + } +} diff --git a/mobile/android/base/preferences/PanelsPreferenceCategory.java b/mobile/android/base/preferences/PanelsPreferenceCategory.java new file mode 100644 index 00000000000..ceecced16bb --- /dev/null +++ b/mobile/android/base/preferences/PanelsPreferenceCategory.java @@ -0,0 +1,242 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.preferences; + +import android.content.Context; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceCategory; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +import org.mozilla.gecko.home.HomeConfig; +import org.mozilla.gecko.home.HomeConfig.PanelConfig; +import org.mozilla.gecko.util.ThreadUtils; +import org.mozilla.gecko.util.UiAsyncTask; + +public class PanelsPreferenceCategory extends CustomListCategory { + public static final String LOGTAG = "PanelsPrefCategory"; + + protected HomeConfig mHomeConfig; + protected final List mPanelConfigs = new ArrayList(); + + protected UiAsyncTask> mLoadTask; + protected UiAsyncTask mSaveTask; + + public PanelsPreferenceCategory(Context context) { + super(context); + initConfig(context); + } + + public PanelsPreferenceCategory(Context context, AttributeSet attrs) { + super(context, attrs); + initConfig(context); + } + + public PanelsPreferenceCategory(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initConfig(context); + } + + protected void initConfig(Context context) { + mHomeConfig = HomeConfig.getDefault(context); + } + + @Override + public void onAttachedToActivity() { + super.onAttachedToActivity(); + + loadHomeConfig(); + } + + /** + * Load the Home Panels config and populate the preferences screen and maintain local state. + */ + private void loadHomeConfig() { + mLoadTask = new UiAsyncTask>(ThreadUtils.getBackgroundHandler()) { + @Override + public List doInBackground(Void... params) { + return mHomeConfig.load(); + } + + @Override + public void onPostExecute(List panelConfigs) { + displayPanelConfig(panelConfigs); + } + }; + mLoadTask.execute(); + } + + private void displayPanelConfig(List panelConfigs) { + for (PanelConfig panelConfig: panelConfigs) { + // Populate our local copy of the panels. + mPanelConfigs.add(panelConfig); + + // Create and add the pref. + final PanelsPreference pref = new PanelsPreference(getContext(), PanelsPreferenceCategory.this); + pref.setTitle(panelConfig.getTitle()); + pref.setKey(panelConfig.getId()); + // XXX: Pull icon from PanelInfo. + addPreference(pref); + + if (panelConfig.isDefault()) { + mDefaultReference = pref; + pref.setIsDefault(true); + } + + if (panelConfig.isDisabled()) { + pref.setHidden(true); + } + } + } + + /** + * Update HomeConfig off the main thread. + * + * @param panelConfigs Configuration to be saved + */ + private void saveHomeConfig() { + final List panelConfigs = makeConfigListDeepCopy(); + mSaveTask = new UiAsyncTask(ThreadUtils.getBackgroundHandler()) { + @Override + public Void doInBackground(Void... params) { + mHomeConfig.save(panelConfigs); + return null; + } + }; + mSaveTask.execute(); + } + + private List makeConfigListDeepCopy() { + List copiedList = new ArrayList(); + for (PanelConfig panelConfig : mPanelConfigs) { + copiedList.add(new PanelConfig(panelConfig)); + } + return copiedList; + } + + @Override + public void setDefault(CustomListPreference pref) { + super.setDefault(pref); + updateConfigDefault(); + saveHomeConfig(); + } + + @Override + protected void onPrepareForRemoval() { + if (mLoadTask != null) { + mLoadTask.cancel(true); + } + + if (mSaveTask != null) { + mSaveTask.cancel(true); + } + } + + /** + * Update the local HomeConfig default state from mDefaultReference. + */ + private void updateConfigDefault() { + String mId = null; + if (mDefaultReference != null) { + mId = mDefaultReference.getKey(); + } + + for (PanelConfig panelConfig : mPanelConfigs) { + if (TextUtils.equals(panelConfig.getId(), mId)) { + panelConfig.setIsDefault(true); + panelConfig.setIsDisabled(false); + } else { + panelConfig.setIsDefault(false); + } + } + } + + @Override + public void uninstall(CustomListPreference pref) { + super.uninstall(pref); + // This could change the default, so update the local version of the config. + updateConfigDefault(); + + final String mId = pref.getKey(); + PanelConfig toRemove = null; + for (PanelConfig panelConfig : mPanelConfigs) { + if (TextUtils.equals(panelConfig.getId(), mId)) { + toRemove = panelConfig; + break; + } + } + mPanelConfigs.remove(toRemove); + + saveHomeConfig(); + } + + /** + * Update the hide/show state of the preference and save the HomeConfig + * changes. + * + * @param pref Preference to update + * @param toHide New hidden state of the preference + */ + protected void setHidden(PanelsPreference pref, boolean toHide) { + pref.setHidden(toHide); + ensureDefaultForHide(pref, toHide); + + final String mId = pref.getKey(); + for (PanelConfig panelConfig : mPanelConfigs) { + if (TextUtils.equals(panelConfig.getId(), mId)) { + 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(); + } + } + } + + /** + * When the default panel is removed or disabled, find an enabled panel + * if possible and set it as mDefaultReference. + */ + @Override + protected void setFallbackDefault() { + for (int i = 0; i < getPreferenceCount(); i++) { + final PanelsPreference pref = (PanelsPreference) getPreference(i); + if (!pref.isHidden()) { + super.setDefault(pref); + return; + } + } + mDefaultReference = null; + } +} diff --git a/mobile/android/base/resources/layout/preference_panels.xml b/mobile/android/base/resources/layout/preference_panels.xml new file mode 100644 index 00000000000..37d03adbe41 --- /dev/null +++ b/mobile/android/base/resources/layout/preference_panels.xml @@ -0,0 +1,29 @@ + + + + + + + + + + diff --git a/mobile/android/base/resources/xml-v11/preferences_customize.xml b/mobile/android/base/resources/xml-v11/preferences_customize.xml index 2485724feea..38f8d4707c5 100644 --- a/mobile/android/base/resources/xml-v11/preferences_customize.xml +++ b/mobile/android/base/resources/xml-v11/preferences_customize.xml @@ -15,6 +15,13 @@ android:value="preferences_search"/> + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/android/base/strings.xml.in b/mobile/android/base/strings.xml.in index c5d50e1fab1..82892384a10 100644 --- a/mobile/android/base/strings.xml.in +++ b/mobile/android/base/strings.xml.in @@ -108,6 +108,9 @@ &pref_developer_remotedebugging; &pref_developer_remotedebugging_docs; + &pref_category_home; + &pref_category_home_panels; + &pref_header_customize; &pref_header_display; &pref_header_privacy_short; @@ -173,9 +176,11 @@ &pref_dialog_default; &pref_dialog_remove; - &pref_search_last_toast; + &pref_panels_show; + &pref_panels_hide; + &datareporting_notification_title; &datareporting_notification_action_long; &datareporting_notification_action; diff --git a/mobile/android/base/tests/testSettingsMenuItems.java b/mobile/android/base/tests/testSettingsMenuItems.java index 8eaf60f7f7e..f23407a50e5 100644 --- a/mobile/android/base/tests/testSettingsMenuItems.java +++ b/mobile/android/base/tests/testSettingsMenuItems.java @@ -32,6 +32,7 @@ public class testSettingsMenuItems extends PixelTest { // Customize menu items. String[][] OPTIONS_CUSTOMIZE = { { "Search settings", "", "Show search suggestions", "Installed search engines"}, + { "Home", "", "Panels" }, { "Import from Android", "", "Bookmarks", "History", "Import" }, { "Tabs", "Don't restore after quitting " + BRAND_NAME, "Always restore", "Don't restore after quitting " + BRAND_NAME }, };