Fix context menu and floating bar styles for dark mode

+ Set alertDialogTheme so that context menu can inherit
+ Use applyOverideConfiguration for ChromeActivity except CCT
+ Add force apply style to workaround wrong styles

Bug: 935731
Change-Id: I08959b089040bf0b294bef8de0b0018867cfee10
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1512783
Commit-Queue: Becky Zhou <huayinz@chromium.org>
Reviewed-by: Theresa <twellington@chromium.org>
Cr-Commit-Position: refs/heads/master@{#639741}
diff --git a/chrome/android/java/res/values-v17/styles.xml b/chrome/android/java/res/values-v17/styles.xml
index a812dc6..0bf3825 100644
--- a/chrome/android/java/res/values-v17/styles.xml
+++ b/chrome/android/java/res/values-v17/styles.xml
@@ -45,7 +45,11 @@
         <item name="spinnerStyle">@style/SpinnerStyle</item>
 
         <!-- Popup styles -->
+        <!-- Set android popup menu attributes for context menu styles because the context menus are
+             OS-dependent. -->
         <item name="android:popupMenuStyle">@style/PopupMenuStyle</item>
+        <item name="android:textAppearanceLargePopupMenu">@style/TextAppearance.BlackTitle1</item>
+        <item name="android:textAppearanceSmallPopupMenu">@style/TextAppearance.BlackTitle1</item>
         <item name="android:contextPopupMenuStyle" tools:targetApi="24">@style/PopupMenuStyle</item>
     </style>
 
diff --git a/chrome/android/java/res/values-v21/styles.xml b/chrome/android/java/res/values-v21/styles.xml
index 294196d..285b68b 100644
--- a/chrome/android/java/res/values-v21/styles.xml
+++ b/chrome/android/java/res/values-v21/styles.xml
@@ -8,6 +8,14 @@
         <item name="android:popupElevation">0dp</item>
     </style>
 
+    <style name="Base.V21.Theme.Chromium" parent="Base.V17.Theme.Chromium">
+        <!-- Set android alert dialog attributes because the context menu dialog is
+             OS-dependent. Not setting alertDialogTheme pre-v21 because the window background
+             causes bad visual states with alert dialog list. -->
+        <item name="android:alertDialogTheme">@style/Theme.Chromium.AlertDialog</item>
+    </style>
+    <style name="Base.Theme.Chromium" parent="Base.V21.Theme.Chromium" />
+
     <!-- Preferences -->
     <style name="Theme.Chromium.Preferences" parent="Base.Theme.Chromium.Preferences">
         <item name="android:divider">@null</item>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
index 90e71f3..d40c8d4e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
@@ -1952,7 +1952,7 @@
         // default behavior of recreating the activity. Note that if UI mode night changes, with or
         // without other changes, we will still recreate() until we get a callback from the
         // ChromeBaseAppCompatActivity#onNightModeStateChanged or the overridden method in
-        // sub-classes.
+        // sub-classes if necessary.
         if (didChangeNonVrUiMode(mUiMode, newConfig.uiMode)
                 && !didChangeUiModeNight(mUiMode, newConfig.uiMode)) {
             recreate();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeBaseAppCompatActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeBaseAppCompatActivity.java
index e73a8df..eab14af 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeBaseAppCompatActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeBaseAppCompatActivity.java
@@ -4,12 +4,17 @@
 
 package org.chromium.chrome.browser;
 
+import android.content.Context;
+import android.content.res.Configuration;
 import android.os.Bundle;
+import android.support.annotation.CallSuper;
 import android.support.annotation.Nullable;
+import android.support.annotation.StyleRes;
 import android.support.v7.app.AppCompatActivity;
 
 import org.chromium.chrome.browser.night_mode.GlobalNightModeStateController;
 import org.chromium.chrome.browser.night_mode.NightModeStateProvider;
+import org.chromium.chrome.browser.night_mode.NightModeUtils;
 
 /**
  * A subclass of {@link AppCompatActivity} that maintains states applied to all activities in
@@ -18,10 +23,24 @@
 public class ChromeBaseAppCompatActivity
         extends AppCompatActivity implements NightModeStateProvider.Observer {
     private NightModeStateProvider mNightModeStateProvider;
+    private @StyleRes int mThemeResId;
+
+    @Override
+    protected void attachBaseContext(Context newBase) {
+        super.attachBaseContext(newBase);
+        mNightModeStateProvider = createNightModeStateProvider();
+
+        Configuration config = new Configuration();
+        // Pre-Android O, fontScale gets initialized to 1 in the constructor. Set it to 0 so
+        // that applyOverrideConfiguration() does not interpret it as an overridden value.
+        // https://crbug.com/834191
+        config.fontScale = 0;
+        if (applyOverrides(newBase, config)) applyOverrideConfiguration(config);
+    }
 
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
-        mNightModeStateProvider = createNightModeStateProvider();
+        initializeNightModeStateProvider();
         mNightModeStateProvider.addObserver(this);
         super.onCreate(savedInstanceState);
     }
@@ -32,6 +51,33 @@
         super.onDestroy();
     }
 
+    @Override
+    public void setTheme(@StyleRes int resid) {
+        super.setTheme(resid);
+        mThemeResId = resid;
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        NightModeUtils.updateConfigurationForNightMode(this, newConfig, mThemeResId);
+    }
+
+    /**
+     * Called during {@link #attachBaseContext(Context)} to allow configuration overrides to be
+     * applied. If this methods return true, the overrides will be applied using
+     * {@link #applyOverrideConfiguration(Configuration)}.
+     * @param baseContext The base {@link Context} attached to this class.
+     * @param overrideConfig The {@link Configuration} that will be passed to
+     *                       @link #applyOverrideConfiguration(Configuration)} if necessary.
+     * @return True if any configuration overrides were applied, and false otherwise.
+     */
+    @CallSuper
+    protected boolean applyOverrides(Context baseContext, Configuration overrideConfig) {
+        return NightModeUtils.applyOverridesForNightMode(
+                getNightModeStateProvider(), overrideConfig);
+    }
+
     /**
      * @return The {@link NightModeStateProvider} that provides the state of night mode.
      */
@@ -47,6 +93,13 @@
         return GlobalNightModeStateController.getInstance();
     }
 
+    /**
+     * Initializes the initial night mode state. This will be called at the beginning of
+     * {@link #onCreate(Bundle)} so that the correct theme can be applied for the initial night mode
+     * state.
+     */
+    protected void initializeNightModeStateProvider() {}
+
     // NightModeStateProvider.Observer implementation.
     @Override
     public void onNightModeStateChanged() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
index 5976cf9..95dc3416 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
@@ -160,6 +160,8 @@
 
     private ActivityTabTaskDescriptionHelper mTaskDescriptionHelper;
 
+    private CustomTabNightModeStateController mNightModeStateController;
+
     /**
      * Return true when the activity has been launched in a separate task. The default behavior is
      * to reuse the same task and put the activity on top of the previous one (i.e hiding it). A
@@ -309,7 +311,13 @@
 
     @Override
     protected NightModeStateProvider createNightModeStateProvider() {
-        return new CustomTabNightModeStateController(getDelegate(), getIntent());
+        mNightModeStateController = new CustomTabNightModeStateController();
+        return mNightModeStateController;
+    }
+
+    @Override
+    protected void initializeNightModeStateProvider() {
+        mNightModeStateController.initialize(getDelegate(), getIntent());
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabNightModeStateController.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabNightModeStateController.java
index c7448dd..ac7133ac 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabNightModeStateController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabNightModeStateController.java
@@ -22,9 +22,15 @@
      * and {@link CustomTabsIntent#COLOR_SCHEME_DARK} should be considered - fall back to the
      * system status for {@link CustomTabsIntent#COLOR_SCHEME_SYSTEM} when enabled.
      */
-    private final int mRequestedColorScheme;
+    private int mRequestedColorScheme;
 
-    CustomTabNightModeStateController(AppCompatDelegate delegate, Intent intent) {
+    /**
+     * Initializes the initial night mode state.
+     * @param delegate The {@link AppCompatDelegate} that controls night mode state in support
+     *                 library.
+     * @param intent  The {@link Intent} to retrieve information about the initial state.
+     */
+    void initialize(AppCompatDelegate delegate, Intent intent) {
         if (!FeatureUtilities.isNightModeForCustomTabsAvailable()) {
             mRequestedColorScheme = CustomTabsIntent.COLOR_SCHEME_SYSTEM;
             return;
@@ -64,4 +70,11 @@
 
     @Override
     public void removeObserver(@NonNull Observer observer) {}
+
+    @Override
+    public boolean shouldOverrideConfiguration() {
+        // Don't override configuration because the initial night mode state is only available
+        // during CustomTabActivity#onCreate().
+        return false;
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitializationActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitializationActivity.java
index fa6ca955..2c5f3ec 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitializationActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitializationActivity.java
@@ -5,7 +5,6 @@
 package org.chromium.chrome.browser.init;
 
 import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Configuration;
@@ -113,29 +112,21 @@
         mLifecycleDispatcher.dispatchOnDestroy();
     }
 
-    @CallSuper
     @Override
-    @TargetApi(Build.VERSION_CODES.N)
-    protected void attachBaseContext(Context newBase) {
-        super.attachBaseContext(newBase);
+    @CallSuper
+    protected boolean applyOverrides(Context baseContext, Configuration overrideConfig) {
+        super.applyOverrides(baseContext, overrideConfig);
 
         // We override the smallestScreenWidthDp here for two reasons:
         // 1. To prevent multi-window from hiding the tabstrip when on a tablet.
         // 2. To ensure mIsTablet only needs to be set once. Since the override lasts for the life
         //    of the activity, it will never change via onConfigurationUpdated().
         // See crbug.com/588838, crbug.com/662338, crbug.com/780593.
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            DisplayAndroid display = DisplayAndroid.getNonMultiDisplay(newBase);
-            int targetSmallestScreenWidthDp =
-                    DisplayUtil.pxToDp(display, DisplayUtil.getSmallestWidth(display));
-            Configuration config = new Configuration();
-            // Pre-Android O, fontScale gets initialized to 1 in the constructor. Set it to 0 so
-            // that applyOverrideConfiguration() does not interpret it as an overridden value.
-            // https://crbug.com/834191
-            config.fontScale = 0;
-            config.smallestScreenWidthDp = targetSmallestScreenWidthDp;
-            applyOverrideConfiguration(config);
-        }
+        DisplayAndroid display = DisplayAndroid.getNonMultiDisplay(baseContext);
+        int targetSmallestScreenWidthDp =
+                DisplayUtil.pxToDp(display, DisplayUtil.getSmallestWidth(display));
+        overrideConfig.smallestScreenWidthDp = targetSmallestScreenWidthDp;
+        return true;
     }
 
     @CallSuper
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/night_mode/NightModeStateProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/night_mode/NightModeStateProvider.java
index 397b436..b670cf18 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/night_mode/NightModeStateProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/night_mode/NightModeStateProvider.java
@@ -4,6 +4,9 @@
 
 package org.chromium.chrome.browser.night_mode;
 
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Configuration;
 import android.support.annotation.NonNull;
 
 /**
@@ -24,4 +27,16 @@
 
     /** @param observer The {@link Observer} to be unregistered to this provider. */
     void removeObserver(@NonNull Observer observer);
+
+    /**
+     * @return Whether or not {@link Configuration#uiMode} should be overridden for night mode by
+     *         {@link Activity#applyOverrideConfiguration(Configuration)}. This is applicable when
+     *         an Activity configures whether night mode is enabled (e.g. through a user setting)
+     *         rather than relying on the Application context UI night mode.
+     *         Note that if night mode state is initialized after
+     *         {@link Activity#attachBaseContext(Context)}, this should return false.
+     */
+    default boolean shouldOverrideConfiguration() {
+        return true;
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/night_mode/NightModeUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/night_mode/NightModeUtils.java
new file mode 100644
index 0000000..63e4aac
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/night_mode/NightModeUtils.java
@@ -0,0 +1,72 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.night_mode;
+
+import android.app.Activity;
+import android.content.res.Configuration;
+import android.os.Build;
+import android.support.annotation.StyleRes;
+
+import org.chromium.chrome.browser.ChromeBaseAppCompatActivity;
+
+/**
+ * Helper methods for supporting night mode.
+ */
+public class NightModeUtils {
+    /**
+     * Updates configuration for night mode to ensure night mode settings are applied properly.
+     * Should be called anytime the Activity's configuration changes (e.g. from
+     * {@link Activity#onConfigurationChanged(Configuration)}) if uiMode was not overridden on
+     * the configuration during activity initialization
+     * (see {@link #applyOverridesForNightMode(NightModeStateProvider, Configuration)}).
+     * @param activity The {@link ChromeBaseAppCompatActivity} that needs to be updated.
+     * @param newConfig The new {@link Configuration} from
+     *                  {@link Activity#onConfigurationChanged(Configuration)}.
+     * @param themeResId The {@link StyleRes} for the theme of the specified activity.
+     */
+    public static void updateConfigurationForNightMode(ChromeBaseAppCompatActivity activity,
+            Configuration newConfig, @StyleRes int themeResId) {
+        final int uiNightMode = activity.getNightModeStateProvider().isInNightMode()
+                ? Configuration.UI_MODE_NIGHT_YES
+                : Configuration.UI_MODE_NIGHT_NO;
+
+        if (uiNightMode == (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)) return;
+
+        // This is to fix styles on floating action bar when the new configuration UI mode doesn't
+        // match the actual UI mode we need, and NightModeStateProvider#shouldOverrideConfiguration
+        // returns false. May check if this is needed on newer version of support library.
+        // See https://crbug.com/935731.
+        if (themeResId != 0) {
+            // Re-apply theme so that the correct configuration is used.
+            activity.setTheme(themeResId);
+
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+                // On M+ setTheme only applies if the themeResId actually changes. Force applying
+                // the styles so that the correct styles are used.
+                activity.getTheme().applyStyle(themeResId, true);
+            }
+        }
+    }
+
+    /**
+     * @param provider The {@link NightModeStateProvider} that provides the night mode state.
+     * @param config The {@link Configuration} on which UI night mode should be overridden if
+     *               necessary.
+     * @return True if UI night mode is overridden on the provided {@code config}, and false
+     *         otherwise.
+     */
+    public static boolean applyOverridesForNightMode(
+            NightModeStateProvider provider, Configuration config) {
+        if (!provider.shouldOverrideConfiguration()) return false;
+
+        // Override uiMode so that UIs created by the DecorView (e.g. context menu, floating
+        // action bar) get the correct theme. May check if this is needed on newer version
+        // of support library. See https://crbug.com/935731.
+        final int nightMode = provider.isInNightMode() ? Configuration.UI_MODE_NIGHT_YES
+                                                       : Configuration.UI_MODE_NIGHT_NO;
+        config.uiMode = nightMode | (config.uiMode & ~Configuration.UI_MODE_NIGHT_MASK);
+        return true;
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabThemeColorHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabThemeColorHelper.java
index 78c375d..4ccbb1f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabThemeColorHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabThemeColorHelper.java
@@ -8,8 +8,6 @@
 
 import org.chromium.base.UserData;
 import org.chromium.base.VisibleForTesting;
-import org.chromium.chrome.browser.ChromeActivity;
-import org.chromium.chrome.browser.lifecycle.StartStopWithNativeObserver;
 import org.chromium.chrome.browser.util.ColorUtils;
 import org.chromium.components.security_state.ConnectionSecurityLevel;
 import org.chromium.content_public.browser.NavigationHandle;
@@ -17,8 +15,7 @@
 /**
  * Manages theme color used for {@link Tab}. Destroyed together with the tab.
  */
-public class TabThemeColorHelper
-        extends EmptyTabObserver implements UserData, StartStopWithNativeObserver {
+public class TabThemeColorHelper extends EmptyTabObserver implements UserData {
     private static final Class<TabThemeColorHelper> USER_DATA_KEY = TabThemeColorHelper.class;
     private final Tab mTab;
 
@@ -57,7 +54,6 @@
         mTab = tab;
         mDefaultColor = calculateDefaultColor();
         mColor = calculateThemeColor(false);
-        updateActivityStateObserver(tab.getActivity() != null);
         tab.addObserver(this);
     }
 
@@ -188,36 +184,11 @@
 
     @Override
     public void onActivityAttachmentChanged(Tab tab, boolean isAttached) {
-        updateActivityStateObserver(isAttached);
         updateDefaultColor();
     }
 
     @Override
     public void onDestroyed(Tab tab) {
-        updateActivityStateObserver(false);
         tab.removeObserver(this);
     }
-
-    // StartStopWithNativeObserver implementation.
-    @Override
-    public void onStartWithNative() {
-        // We need to update the default color because the resource could be retrieved before the UI
-        // mode on ChromeActivity is properly updated during onStart (e.g. open custom tab in
-        // browser).
-        updateDefaultColor();
-    }
-
-    @Override
-    public void onStopWithNative() {}
-
-    private void updateActivityStateObserver(boolean isAttachedToActivity) {
-        ChromeActivity activity = mTab.getActivity();
-        if (activity == null) return;
-
-        if (isAttachedToActivity) {
-            activity.getLifecycleDispatcher().register(this);
-        } else {
-            activity.getLifecycleDispatcher().unregister(this);
-        }
-    }
 }
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index 17626655..25b341f 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -933,6 +933,7 @@
   "java/src/org/chromium/chrome/browser/nfc/BeamProvider.java",
   "java/src/org/chromium/chrome/browser/night_mode/GlobalNightModeStateController.java",
   "java/src/org/chromium/chrome/browser/night_mode/NightModeStateProvider.java",
+  "java/src/org/chromium/chrome/browser/night_mode/NightModeUtils.java",
   "java/src/org/chromium/chrome/browser/notifications/ActionInfo.java",
   "java/src/org/chromium/chrome/browser/notifications/ChromeNotification.java",
   "java/src/org/chromium/chrome/browser/notifications/ChromeNotificationBuilder.java",