Bug 1205124 - use AppCompatActivity for GeckoPreferences r=sebastian
authorAndrzej Hunt <andrzej@ahunt.org>
Mon, 28 Mar 2016 14:32:07 -0700
changeset 292797 9fb7908df083b09946413585d48d02bba09de934
parent 292796 922682fb9d2af91a2eaea50c65a6fabfd716a56c
child 292798 ecf79242ed01d7a8372bbb8a654c0d3e43d8c1e0
push id74979
push usercbook@mozilla.com
push dateTue, 12 Apr 2016 11:53:25 +0000
treeherdermozilla-inbound@cb2b8b7bc47f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssebastian
bugs1205124
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1205124 - use AppCompatActivity for GeckoPreferences r=sebastian This patch introduces consistent theming for all supported devices (Android 4+). Previously we had Jellybean theming on Android 4, and Lollipop theming on 5+. Note that dialogs will be based on the native device dialogs (i.e. Jellybean on 4, Lollipop on 5+). This is because we use native Fragments (and not support library Fragments), which inflate their own (native) Dialogs. Introducing consistent dialogs would involve replacing the use of native Fragments (and native PreferenceFragments), and replacing our self-inflated dialogs. (We have a few self-built dialogs - these could easily be switched to the AppCompat AlertDialog, but then we'd have a mix of new and old dialogs on Android 4 devices, since PreferenceFragment constructed dialogs would still be the native Android dialogs - overall this would be a worse experience.) It is also usable on 2.3. There, we retain GB theming, however the checkboxes are replaced with orange checkboxes (as on 4+). MozReview-Commit-ID: AXbyAfpvfKi
mobile/android/base/java/org/mozilla/gecko/preferences/AppCompatPreferenceActivity.java
mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferenceFragment.java
mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java
mobile/android/base/moz.build
mobile/android/base/resources/values-v11/themes.xml
mobile/android/base/resources/values-v21/themes.xml
mobile/android/base/resources/values/themes.xml
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/preferences/AppCompatPreferenceActivity.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.mozilla.gecko.preferences;
+
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.Nullable;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatDelegate;
+import android.support.v7.widget.Toolbar;
+import android.view.MenuInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls
+ * to be used with AppCompat.
+ *
+ * This technique can be used with an {@link android.app.Activity} class, not just
+ * {@link android.preference.PreferenceActivity}.
+ *
+ * This class was directly imported (without any modifications) from Android SDK examples, at:
+ * https://android.googlesource.com/platform/development/+/master/samples/Support7Demos/src/com/example/android/supportv7/app/AppCompatPreferenceActivity.java
+  */
+public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
+    private AppCompatDelegate mDelegate;
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        getDelegate().installViewFactory();
+        getDelegate().onCreate(savedInstanceState);
+        super.onCreate(savedInstanceState);
+    }
+    @Override
+    protected void onPostCreate(Bundle savedInstanceState) {
+        super.onPostCreate(savedInstanceState);
+        getDelegate().onPostCreate(savedInstanceState);
+    }
+    public ActionBar getSupportActionBar() {
+        return getDelegate().getSupportActionBar();
+    }
+    public void setSupportActionBar(@Nullable Toolbar toolbar) {
+        getDelegate().setSupportActionBar(toolbar);
+    }
+    @Override
+    public MenuInflater getMenuInflater() {
+        return getDelegate().getMenuInflater();
+    }
+    @Override
+    public void setContentView(@LayoutRes int layoutResID) {
+        getDelegate().setContentView(layoutResID);
+    }
+    @Override
+    public void setContentView(View view) {
+        getDelegate().setContentView(view);
+    }
+    @Override
+    public void setContentView(View view, ViewGroup.LayoutParams params) {
+        getDelegate().setContentView(view, params);
+    }
+    @Override
+    public void addContentView(View view, ViewGroup.LayoutParams params) {
+        getDelegate().addContentView(view, params);
+    }
+    @Override
+    protected void onPostResume() {
+        super.onPostResume();
+        getDelegate().onPostResume();
+    }
+    @Override
+    protected void onTitleChanged(CharSequence title, int color) {
+        super.onTitleChanged(title, color);
+        getDelegate().setTitle(title);
+    }
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        getDelegate().onConfigurationChanged(newConfig);
+    }
+    @Override
+    protected void onStop() {
+        super.onStop();
+        getDelegate().onStop();
+    }
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        getDelegate().onDestroy();
+    }
+    public void invalidateOptionsMenu() {
+        getDelegate().invalidateOptionsMenu();
+    }
+    private AppCompatDelegate getDelegate() {
+        if (mDelegate == null) {
+            mDelegate = AppCompatDelegate.create(this, null);
+        }
+        return mDelegate;
+    }
+}
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferenceFragment.java
+++ b/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferenceFragment.java
@@ -160,34 +160,27 @@ public class GeckoPreferenceFragment ext
 
     private void updateTitle() {
         final String newTitle = getTitle();
         if (newTitle == null) {
             Log.d(LOGTAG, "No new title to show.");
             return;
         }
 
-        final PreferenceActivity activity = (PreferenceActivity) getActivity();
+        final GeckoPreferences activity = (GeckoPreferences) getActivity();
         if (Versions.feature11Plus && activity.isMultiPane()) {
             // In a multi-pane activity, the title is "Settings", and the action
             // bar is along the top of the screen. We don't want to change those.
             activity.showBreadCrumbs(newTitle, newTitle);
-            ((GeckoPreferences) activity).switchToHeader(getHeader());
+            activity.switchToHeader(getHeader());
             return;
         }
 
         Log.v(LOGTAG, "Setting activity title to " + newTitle);
         activity.setTitle(newTitle);
-
-        if (Versions.feature14Plus) {
-            final ActionBar actionBar = activity.getActionBar();
-            if (actionBar != null) {
-                actionBar.setTitle(newTitle);
-            }
-        }
     }
 
     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
         accountLoaderCallbacks = new AccountLoaderCallbacks();
         getLoaderManager().initLoader(ACCOUNT_LOADER_ID, null, accountLoaderCallbacks);
     }
@@ -240,17 +233,17 @@ public class GeckoPreferenceFragment ext
             resid = resources.getIdentifier(resourceName, "xml", packageName);
         }
 
         if (resid == 0) {
             // The resource was invalid. Use the default resource.
             Log.e(LOGTAG, "Failed to find resource: " + resourceName + ". Displaying default settings.");
 
             boolean isMultiPane = Versions.feature11Plus &&
-                                  ((PreferenceActivity) activity).isMultiPane();
+                                  ((GeckoPreferences) activity).isMultiPane();
             resid = isMultiPane ? R.xml.preferences_general_tablet : R.xml.preferences;
         }
 
         return resid;
     }
 
     @Override
     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java
+++ b/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java
@@ -43,17 +43,16 @@ import org.mozilla.gecko.util.EventCallb
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.InputOptionsUtils;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSObject;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.annotation.TargetApi;
-import android.app.ActionBar;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.Fragment;
 import android.app.FragmentManager;
 import android.app.NotificationManager;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -71,16 +70,17 @@ import android.preference.Preference;
 import android.preference.Preference.OnPreferenceChangeListener;
 import android.preference.Preference.OnPreferenceClickListener;
 import android.preference.PreferenceActivity;
 import android.preference.PreferenceGroup;
 import android.preference.SwitchPreference;
 import android.preference.TwoStatePreference;
 import android.support.design.widget.Snackbar;
 import android.support.design.widget.TextInputLayout;
+import android.support.v7.app.ActionBar;
 import android.text.Editable;
 import android.text.InputType;
 import android.text.TextUtils;
 import android.text.TextWatcher;
 import android.util.Log;
 import android.view.MenuItem;
 import android.view.View;
 import android.widget.AdapterView;
@@ -94,17 +94,17 @@ import org.json.JSONObject;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 
 public class GeckoPreferences
-extends PreferenceActivity
+extends AppCompatPreferenceActivity
 implements
 GeckoActivityStatus,
 GeckoEventListener,
 NativeEventListener,
 OnPreferenceChangeListener,
 OnSharedPreferenceChangeListener
 {
     private static final String LOGTAG = "GeckoPreferences";
@@ -203,20 +203,17 @@ OnSharedPreferenceChangeListener
         }
     }
     private void updateActionBarTitle(int title) {
         if (Versions.feature14Plus) {
             final String newTitle = getString(title);
             if (newTitle != null) {
                 Log.v(LOGTAG, "Setting action bar title to " + newTitle);
 
-                final ActionBar actionBar = getActionBar();
-                if (actionBar != null) {
-                    actionBar.setTitle(newTitle);
-                }
+                setTitle(newTitle);
             }
         }
     }
 
     /**
      * We only call this method for pre-HC versions of Android.
      */
     private void updateTitleForPrefsResource(int res) {
@@ -240,17 +237,17 @@ OnSharedPreferenceChangeListener
     }
 
     private void onLocaleChanged(Locale newLocale) {
         Log.d(LOGTAG, "onLocaleChanged: " + newLocale);
 
         BrowserLocaleManager.getInstance().updateConfiguration(getApplicationContext(), newLocale);
         this.lastLocale = newLocale;
 
-        if (Versions.feature11Plus && isMultiPane()) {
+        if (isMultiPane()) {
             // This takes care of the left pane.
             invalidateHeaders();
 
             // Detach and reattach the current prefs pane so that it
             // reflects the new locale.
             final FragmentManager fragmentManager = getFragmentManager();
             int id = getResources().getIdentifier("android:id/prefs", null, null);
             final Fragment current = fragmentManager.findFragmentById(id);
@@ -313,42 +310,45 @@ OnSharedPreferenceChangeListener
         // Track this so we can decide whether to show locale options.
         // See also the workaround below for Bug 1015209.
         localeSwitchingIsEnabled = BrowserLocaleManager.getInstance().isEnabled();
 
         // For Android v11+ where we use Fragments (v11+ only due to bug 866352),
         // check that PreferenceActivity.EXTRA_SHOW_FRAGMENT has been set
         // (or set it) before super.onCreate() is called so Android can display
         // the correct Fragment resource.
-
+        // Note: this seems to only be required for non-multipane devices, multipane
+        // manages to automatically select the correct fragments.
         if (Versions.feature11Plus) {
             if (!getIntent().hasExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT)) {
                 // Set up the default fragment if there is no explicit fragment to show.
                 setupTopLevelFragmentIntent();
-
-                // This is the default header, because it's the first one.
-                // I know, this is an affront to all human decency. And yet.
-                setTitle(R.string.pref_header_general);
-            }
-
-            if (onIsMultiPane()) {
-                // So that Android doesn't put the fragment title (or nothing at
-                // all) in the action bar.
-                updateActionBarTitle(R.string.settings_title);
-
-                if (Build.VERSION.SDK_INT < 13) {
-                    // Affected by Bug 1015209 -- no detach/attach.
-                    // If we try rejigging fragments, we'll crash, so don't
-                    // enable locale switching at all.
-                    localeSwitchingIsEnabled = false;
-                }
             }
         }
 
+        // We must call this before setTitle to avoid crashes. Most devices don't seem to care
+        // (we used to call onCreate later), however the ASUS TF300T (running 4.2) crashes
+        // with an NPE in android.support.v7.app.AppCompatDelegateImplV7.ensureSubDecor(), and it's
+        // likely other strange devices (other Asus devices, some Samsungs) could do the same.
         super.onCreate(savedInstanceState);
+
+        if (Versions.feature11Plus && onIsMultiPane()) {
+            // So that Android doesn't put the fragment title (or nothing at
+            // all) in the action bar.
+            updateActionBarTitle(R.string.settings_title);
+
+            if (Build.VERSION.SDK_INT < 13) {
+                // Affected by Bug 1015209 -- no detach/attach.
+                // If we try rejigging fragments, we'll crash, so don't
+                // enable locale switching at all.
+                localeSwitchingIsEnabled = false;
+                throw new IllegalStateException("foobar");
+            }
+        }
+
         initActionBar();
 
         // Use setResourceToOpen to specify these extras.
         Bundle intentExtras = getIntent().getExtras();
 
         EventDispatcher.getInstance().registerGeckoThreadListener((GeckoEventListener) this,
             "Sanitize:Finished");
 
@@ -397,17 +397,17 @@ OnSharedPreferenceChangeListener
      * Declaring these attributes in XML does not work on some devices for an unknown reason
      * (e.g. the back button stops working or the logo disappears; see bug 1152314) so we
      * duplicate those attributes in code here. Note: the order of these calls matters.
      *
      * We keep the XML attributes because not all of these methods are available on pre-v14.
      */
     private void initActionBar() {
         if (Versions.feature14Plus) {
-            final ActionBar actionBar = getActionBar();
+            final ActionBar actionBar = getSupportActionBar();
             if (actionBar != null) {
                 actionBar.setHomeButtonEnabled(true);
                 actionBar.setDisplayHomeAsUpEnabled(true);
                 actionBar.setLogo(R.drawable.logo);
                 actionBar.setDisplayUseLogoEnabled(true);
             }
         }
     }
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -479,16 +479,17 @@ gbjar.sources += ['java/org/mozilla/geck
     'overlays/ui/SendTabTargetSelectedListener.java',
     'overlays/ui/ShareDialog.java',
     'permissions/PermissionBlock.java',
     'permissions/Permissions.java',
     'permissions/PermissionsHelper.java',
     'preferences/AlignRightLinkPreference.java',
     'preferences/AndroidImport.java',
     'preferences/AndroidImportPreference.java',
+    'preferences/AppCompatPreferenceActivity.java',
     'preferences/ClearOnShutdownPref.java',
     'preferences/CustomCheckBoxPreference.java',
     'preferences/CustomListCategory.java',
     'preferences/CustomListPreference.java',
     'preferences/DistroSharedPrefsImport.java',
     'preferences/FontSizePreference.java',
     'preferences/GeckoPreferenceFragment.java',
     'preferences/GeckoPreferences.java',
--- a/mobile/android/base/resources/values-v11/themes.xml
+++ b/mobile/android/base/resources/values-v11/themes.xml
@@ -19,20 +19,20 @@
         <item name="android:windowContentOverlay">@null</item>
         <item name="android:windowActionBar">false</item>
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowBackground">@android:color/transparent</item>
     </style>
 
     <style name="GeckoTitleDialogBase" parent="@android:style/Theme.Holo.Light.Dialog" />
 
-    <style name="GeckoPreferencesBase" parent="@android:style/Theme.Holo.Light">
+    <style name="GeckoPreferencesBase" parent="Gecko">
         <item name="android:windowContentOverlay">@null</item>
-        <item name="android:windowActionBar">true</item>
-        <item name="android:windowNoTitle">false</item>
+        <item name="windowActionBar">true</item>
+        <item name="windowNoTitle">false</item>
         <item name="android:actionBarStyle">@style/ActionBar.GeckoPreferences</item>
     </style>
 
     <!--
         Activity based themes for API 11+. This theme completely replaces
         GeckoAppBase from res/values/themes.xml on API 11+ devices.
     -->
     <style name="GeckoAppBase" parent="Gecko">
--- a/mobile/android/base/resources/values-v21/themes.xml
+++ b/mobile/android/base/resources/values-v21/themes.xml
@@ -15,20 +15,18 @@
         <item name="windowNoTitle">true</item>
         <item name="android:windowContentOverlay">@null</item>
         <item name="android:actionBarStyle">@style/GeckoActionBar</item>
         <!-- TODO (bug 1220863): Once we extend AppCompatActivity, set actionBarStyle. -->
         <item name="android:alertDialogTheme">@style/GeckoAlertDialog</item>
     </style>
 
     <style name="GeckoPreferencesBase" parent="Gecko">
-        <item name="android:windowActionBar">true</item>
-        <item name="android:windowNoTitle">false</item>
-        <item name="android:actionBarStyle">@style/ActionBar.GeckoPreferences</item>
-        <item name="android:actionBarTheme">@style/ActionBarThemeGeckoPreferences</item>
+        <item name="windowActionBar">true</item>
+        <item name="windowNoTitle">false</item>
     </style>
 
     <style name="GeckoAlertDialog" parent="@android:style/Theme.Material.Light.Dialog.Alert">
         <item name="android:colorAccent">@color/fennec_ui_orange</item>
     </style>
 
     <style name="ActionBar.FxAccountStatusActivity" parent="@android:style/Widget.Material.ActionBar.Solid">
         <item name="android:displayOptions">homeAsUp|showTitle</item>
--- a/mobile/android/base/resources/values/themes.xml
+++ b/mobile/android/base/resources/values/themes.xml
@@ -16,17 +16,17 @@
 
     <style name="GeckoDialogBase" parent="@android:style/Theme.Dialog">
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowContentOverlay">@null</item>
     </style>
 
     <style name="GeckoTitleDialogBase" parent="@android:style/Theme.Dialog" />
 
-    <style name="GeckoPreferencesBase" parent="@android:style/Theme.Light">
+    <style name="GeckoPreferencesBase" parent="Gecko">
         <item name="android:windowNoTitle">false</item>
         <item name="android:windowContentOverlay">@null</item>
     </style>
 
     <!--
         Application Theme. All customizations that are not specific
         to a particular API level can go here.
     -->