Commit 3db87bf1 authored by Pavel Shmakov's avatar Pavel Shmakov Committed by Commit Bot

Apply correct color scheme to Custom Tabs bottom bar

RemoteViews#apply doesn't use the overriden configuration of the Context
passed into it. It instead creates a Context of remote app with default
Configuration. Because of this Custom Tabs bottom bar always follows
system settings. If COLOR_SCHEME_DARK or COLOR_SCHEME_LIGHT is set in
the intent, the bottom bar should instead follow this setting along with
the rest of the UI.

To fix this I suggest doing RemoteViews#apply manually: first inflate
the View just like RemoteViews#apply does, but with overridden
Configuration, then use RemoteViews#reapply to apply the Actions.

Bug: 966402
Change-Id: I8a89a4ee21baf5a759c4aabf0c7725430d609dc2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1626864Reviewed-by: default avatarYusuf Ozuysal <yusufo@chromium.org>
Reviewed-by: default avatarBecky Zhou <huayinz@chromium.org>
Commit-Queue: Pavel Shmakov <pshmakov@chromium.org>
Cr-Commit-Position: refs/heads/master@{#664346}
parent 347ba853
...@@ -899,6 +899,7 @@ chrome_java_sources = [ ...@@ -899,6 +899,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/night_mode/NightModeMetrics.java", "java/src/org/chromium/chrome/browser/night_mode/NightModeMetrics.java",
"java/src/org/chromium/chrome/browser/night_mode/NightModeStateProvider.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/night_mode/NightModeUtils.java",
"java/src/org/chromium/chrome/browser/night_mode/RemoteViewsWithNightModeInflater.java",
"java/src/org/chromium/chrome/browser/night_mode/SystemNightModeMonitor.java", "java/src/org/chromium/chrome/browser/night_mode/SystemNightModeMonitor.java",
"java/src/org/chromium/chrome/browser/notifications/ActionInfo.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/ChromeNotification.java",
......
...@@ -888,7 +888,7 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent ...@@ -888,7 +888,7 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent
protected CustomTabActivityComponent createComponent( protected CustomTabActivityComponent createComponent(
ChromeActivityCommonsModule commonsModule) { ChromeActivityCommonsModule commonsModule) {
CustomTabActivityModule customTabsModule = CustomTabActivityModule customTabsModule =
new CustomTabActivityModule(mIntentDataProvider); new CustomTabActivityModule(mIntentDataProvider, mNightModeStateController);
CustomTabActivityComponent component = CustomTabActivityComponent component =
ChromeApplication.getComponent().createCustomTabActivityComponent( ChromeApplication.getComponent().createCustomTabActivityComponent(
commonsModule, customTabsModule); commonsModule, customTabsModule);
......
...@@ -7,11 +7,9 @@ package org.chromium.chrome.browser.customtabs; ...@@ -7,11 +7,9 @@ package org.chromium.chrome.browser.customtabs;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException; import android.app.PendingIntent.CanceledException;
import android.content.Intent; import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.customtabs.CustomTabsIntent; import android.support.customtabs.CustomTabsIntent;
import android.view.InflateException;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.View.OnLayoutChangeListener; import android.view.View.OnLayoutChangeListener;
...@@ -21,7 +19,6 @@ import android.widget.ImageButton; ...@@ -21,7 +19,6 @@ import android.widget.ImageButton;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.RemoteViews; import android.widget.RemoteViews;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log; import org.chromium.base.Log;
import org.chromium.base.metrics.CachedMetrics; import org.chromium.base.metrics.CachedMetrics;
import org.chromium.chrome.R; import org.chromium.chrome.R;
...@@ -31,6 +28,8 @@ import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelManager.Over ...@@ -31,6 +28,8 @@ import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelManager.Over
import org.chromium.chrome.browser.dependency_injection.ActivityScope; import org.chromium.chrome.browser.dependency_injection.ActivityScope;
import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager; import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager.FullscreenListener; import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager.FullscreenListener;
import org.chromium.chrome.browser.night_mode.RemoteViewsWithNightModeInflater;
import org.chromium.chrome.browser.night_mode.SystemNightModeMonitor;
import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.Tab;
import org.chromium.ui.interpolators.BakedBezierInterpolator; import org.chromium.ui.interpolators.BakedBezierInterpolator;
...@@ -49,11 +48,15 @@ public class CustomTabBottomBarDelegate implements FullscreenListener { ...@@ -49,11 +48,15 @@ public class CustomTabBottomBarDelegate implements FullscreenListener {
private static final CachedMetrics.ActionEvent REMOTE_VIEWS_UPDATED = private static final CachedMetrics.ActionEvent REMOTE_VIEWS_UPDATED =
new CachedMetrics.ActionEvent("CustomTabsRemoteViewsUpdated"); new CachedMetrics.ActionEvent("CustomTabsRemoteViewsUpdated");
private static final int SLIDE_ANIMATION_DURATION_MS = 400; private static final int SLIDE_ANIMATION_DURATION_MS = 400;
private ChromeActivity mActivity;
private ChromeFullscreenManager mFullscreenManager; private final ChromeActivity mActivity;
private final ChromeFullscreenManager mFullscreenManager;
private final CustomTabIntentDataProvider mDataProvider;
private final CustomTabNightModeStateController mNightModeStateController;
private final SystemNightModeMonitor mSystemNightModeMonitor;
private ViewGroup mBottomBarView; private ViewGroup mBottomBarView;
@Nullable private View mBottomBarContentView; @Nullable private View mBottomBarContentView;
private CustomTabIntentDataProvider mDataProvider;
private PendingIntent mClickPendingIntent; private PendingIntent mClickPendingIntent;
private int[] mClickableIDs; private int[] mClickableIDs;
private boolean mShowShadow = true; private boolean mShowShadow = true;
...@@ -76,10 +79,14 @@ public class CustomTabBottomBarDelegate implements FullscreenListener { ...@@ -76,10 +79,14 @@ public class CustomTabBottomBarDelegate implements FullscreenListener {
@Inject @Inject
public CustomTabBottomBarDelegate(ChromeActivity activity, public CustomTabBottomBarDelegate(ChromeActivity activity,
CustomTabIntentDataProvider dataProvider, ChromeFullscreenManager fullscreenManager) { CustomTabIntentDataProvider dataProvider, ChromeFullscreenManager fullscreenManager,
CustomTabNightModeStateController nightModeStateController,
SystemNightModeMonitor systemNightModeMonitor) {
mActivity = activity; mActivity = activity;
mDataProvider = dataProvider; mDataProvider = dataProvider;
mFullscreenManager = fullscreenManager; mFullscreenManager = fullscreenManager;
mNightModeStateController = nightModeStateController;
mSystemNightModeMonitor = systemNightModeMonitor;
fullscreenManager.addListener(this); fullscreenManager.addListener(this);
} }
...@@ -267,14 +274,11 @@ public class CustomTabBottomBarDelegate implements FullscreenListener { ...@@ -267,14 +274,11 @@ public class CustomTabBottomBarDelegate implements FullscreenListener {
} }
private boolean showRemoteViews(RemoteViews remoteViews) { private boolean showRemoteViews(RemoteViews remoteViews) {
final View inflatedView; final View inflatedView = RemoteViewsWithNightModeInflater.inflate(remoteViews,
try { getBottomBarView(), mNightModeStateController.isInNightMode(),
inflatedView = mSystemNightModeMonitor.isSystemNightModeOn());
remoteViews.apply(ContextUtils.getApplicationContext(), getBottomBarView());
} catch (RemoteViews.ActionException | InflateException | Resources.NotFoundException e) { if (inflatedView == null) return false;
Log.e(TAG, "Failed to inflate the RemoteViews", e);
return false;
}
if (mClickableIDs != null && mClickPendingIntent != null) { if (mClickableIDs != null && mClickPendingIntent != null) {
for (int id : mClickableIDs) { for (int id : mClickableIDs) {
......
...@@ -21,7 +21,7 @@ import org.chromium.chrome.browser.util.IntentUtils; ...@@ -21,7 +21,7 @@ import org.chromium.chrome.browser.util.IntentUtils;
/** /**
* Maintains and provides the night mode state for {@link CustomTabActivity}. * Maintains and provides the night mode state for {@link CustomTabActivity}.
*/ */
class CustomTabNightModeStateController public class CustomTabNightModeStateController
implements Destroyable, NightModeStateProvider, SystemNightModeMonitor.Observer { implements Destroyable, NightModeStateProvider, SystemNightModeMonitor.Observer {
private final ObserverList<Observer> mObservers = new ObserverList<>(); private final ObserverList<Observer> mObservers = new ObserverList<>();
......
...@@ -6,6 +6,7 @@ package org.chromium.chrome.browser.customtabs.dependency_injection; ...@@ -6,6 +6,7 @@ package org.chromium.chrome.browser.customtabs.dependency_injection;
import org.chromium.chrome.browser.browserservices.ClientAppDataRegister; import org.chromium.chrome.browser.browserservices.ClientAppDataRegister;
import org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider; import org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider;
import org.chromium.chrome.browser.customtabs.CustomTabNightModeStateController;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
...@@ -16,9 +17,12 @@ import dagger.Provides; ...@@ -16,9 +17,12 @@ import dagger.Provides;
@Module @Module
public class CustomTabActivityModule { public class CustomTabActivityModule {
private final CustomTabIntentDataProvider mIntentDataProvider; private final CustomTabIntentDataProvider mIntentDataProvider;
private final CustomTabNightModeStateController mNightModeController;
public CustomTabActivityModule(CustomTabIntentDataProvider intentDataProvider) { public CustomTabActivityModule(CustomTabIntentDataProvider intentDataProvider,
CustomTabNightModeStateController nightModeController) {
mIntentDataProvider = intentDataProvider; mIntentDataProvider = intentDataProvider;
mNightModeController = nightModeController;
} }
@Provides @Provides
...@@ -30,4 +34,9 @@ public class CustomTabActivityModule { ...@@ -30,4 +34,9 @@ public class CustomTabActivityModule {
public ClientAppDataRegister provideClientAppDataRegister() { public ClientAppDataRegister provideClientAppDataRegister() {
return new ClientAppDataRegister(); return new ClientAppDataRegister();
} }
@Provides
public CustomTabNightModeStateController provideNightModeController() {
return mNightModeController;
}
} }
...@@ -14,6 +14,7 @@ import org.chromium.base.ContextUtils; ...@@ -14,6 +14,7 @@ import org.chromium.base.ContextUtils;
import org.chromium.chrome.browser.WarmupManager; import org.chromium.chrome.browser.WarmupManager;
import org.chromium.chrome.browser.browserservices.permissiondelegation.TrustedWebActivityPermissionStore; import org.chromium.chrome.browser.browserservices.permissiondelegation.TrustedWebActivityPermissionStore;
import org.chromium.chrome.browser.init.ChromeBrowserInitializer; import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
import org.chromium.chrome.browser.night_mode.SystemNightModeMonitor;
import org.chromium.chrome.browser.notifications.channels.SiteChannelsManager; import org.chromium.chrome.browser.notifications.channels.SiteChannelsManager;
import org.chromium.chrome.browser.preferences.ChromePreferenceManager; import org.chromium.chrome.browser.preferences.ChromePreferenceManager;
import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.profiles.Profile;
...@@ -78,4 +79,9 @@ public class ChromeAppModule { ...@@ -78,4 +79,9 @@ public class ChromeAppModule {
// so we can't make it injectable. // so we can't make it injectable.
return new TrustedWebActivityServiceConnectionManager(context); return new TrustedWebActivityServiceConnectionManager(context);
} }
@Provides
public SystemNightModeMonitor provideSystemNightModeMonitor() {
return SystemNightModeMonitor.getInstance();
}
} }
...@@ -5,9 +5,11 @@ ...@@ -5,9 +5,11 @@
package org.chromium.chrome.browser.night_mode; package org.chromium.chrome.browser.night_mode;
import android.app.Activity; import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.os.Build; import android.os.Build;
import android.support.annotation.StyleRes; import android.support.annotation.StyleRes;
import android.view.ContextThemeWrapper;
import org.chromium.chrome.browser.ChromeBaseAppCompatActivity; import org.chromium.chrome.browser.ChromeBaseAppCompatActivity;
...@@ -78,4 +80,26 @@ public class NightModeUtils { ...@@ -78,4 +80,26 @@ public class NightModeUtils {
config.uiMode = nightMode | (config.uiMode & ~Configuration.UI_MODE_NIGHT_MASK); config.uiMode = nightMode | (config.uiMode & ~Configuration.UI_MODE_NIGHT_MASK);
return true; return true;
} }
/**
* Wraps a {@link Context} into one having a resource configuration with the given night mode
* setting.
* @param context {@link Context} to wrap.
* @param themeResId Theme resource to use with {@link ContextThemeWrapper}.
* @param nightMode Whether to apply night mode.
* @return Wrapped {@link Context}.
*/
public static Context wrapContextWithNightModeConfig(Context context, @StyleRes int themeResId,
boolean nightMode) {
ContextThemeWrapper wrapper = new ContextThemeWrapper(context, themeResId);
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.
config.fontScale = 0;
int nightModeFlag = nightMode ? Configuration.UI_MODE_NIGHT_YES
: Configuration.UI_MODE_NIGHT_NO;
config.uiMode = nightModeFlag | (config.uiMode & ~Configuration.UI_MODE_NIGHT_MASK);
wrapper.applyOverrideConfiguration(config);
return wrapper;
}
} }
// 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.content.Context;
import android.content.ContextWrapper;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.support.annotation.Nullable;
import android.view.InflateException;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RemoteViews;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
/**
* Performs inflation of {@link RemoteViews} taking into account the local night mode.
* {@link RemoteViews#apply} always uses resource configuration corresponding to system
* settings, see https://buganizer.corp.google.com/issues/133424086, http://crbug.com/1626864.
*/
public class RemoteViewsWithNightModeInflater {
private static final String TAG = "RemoteViewsInflater";
/**
* Inflates the RemoteViews.
*
* @param remoteViews {@link RemoteViews} to inflate.
* @param parent Parent {@link ViewGroup} to use for inflation
* @param isInLocalNightMode Whether night mode is enabled for the current Activity.
* @param isInSystemNightMode Whether night mode is enabled in system settings.
* @return Inflated View or null in case of failure.
*/
@Nullable
public static View inflate(RemoteViews remoteViews, @Nullable ViewGroup parent,
boolean isInLocalNightMode, boolean isInSystemNightMode) {
if (isInLocalNightMode == isInSystemNightMode) {
// RemoteViews#apply will use the resource configuration corresponding to system
// settings.
return inflateNormally(remoteViews, parent);
}
View view = inflateWithEnforcedDarkMode(remoteViews, parent, isInLocalNightMode);
if (view == null) {
view = inflateNormally(remoteViews, parent);
assert view == null : "Failed to inflate valid RemoteViews with enforced dark mode";
}
return view;
}
@Nullable
private static View inflateNormally(RemoteViews remoteViews, ViewGroup parent) {
try {
return remoteViews.apply(ContextUtils.getApplicationContext(), parent);
} catch (RemoteViews.ActionException | InflateException | Resources.NotFoundException e) {
Log.e(TAG, "Failed to inflate the RemoteViews", e);
return null;
}
}
@Nullable
private static View inflateWithEnforcedDarkMode(RemoteViews remoteViews, ViewGroup parent,
boolean isInLocalNightMode) {
// This is a modified version of RemoteViews#apply. RemoteViews#apply performs two steps:
// 1. Inflate the View using the context of the remote app.
// 2. Apply the Actions to the inflated View (actions are requested by remote app using
// various setters, such as RemoteViews#setTextViewText).
//
// The context used at step 1 does not respect the Configuration override of the Context
// we pass as an argument of apply().
//
// Here we perform step 1 manually, overriding the Configuration just before inflating.
// Then we perform step 2 using RemoteViews#reapply on an already inflated View.
try {
final Context contextForResources =
getContextForResources(remoteViews, isInLocalNightMode);
// App context must be used instead of activity context to avoid the support library
// bug, see https://crbug.com/783834
Context appContext = ContextUtils.getApplicationContext();
Context contextForRemoteViews = new RemoteViewsContextWrapper(appContext,
contextForResources);
LayoutInflater inflater =
LayoutInflater.from(appContext).cloneInContext(contextForRemoteViews);
View view = inflater.inflate(remoteViews.getLayoutId(), parent, false);
remoteViews.reapply(appContext, view);
return view;
} catch (RemoteViews.ActionException | InflateException | Resources.NotFoundException
| PackageManager.NameNotFoundException e) {
Log.e(TAG, "Failed to inflate the RemoteViews", e);
return null;
}
}
private static Context getContextForResources(RemoteViews remoteViews,
boolean isInLocalNightMode) throws PackageManager.NameNotFoundException {
Context appContext = ContextUtils.getApplicationContext();
String remotePackage = remoteViews.getPackage();
if (appContext.getPackageName().equals(remotePackage)) return appContext;
Context remoteContext =
appContext.createPackageContext(remotePackage, Context.CONTEXT_RESTRICTED);
// This line is what makes the difference with RemoteViews#apply.
Context contextWithEnforcedNightMode = NightModeUtils.wrapContextWithNightModeConfig(
remoteContext, 0 /*themeResId*/, isInLocalNightMode);
return contextWithEnforcedNightMode;
}
// Copied from RemoteViews
private static class RemoteViewsContextWrapper extends ContextWrapper {
private final Context mContextForResources;
RemoteViewsContextWrapper(Context context, Context contextForResources) {
super(context);
mContextForResources = contextForResources;
}
@Override
public Resources getResources() {
return mContextForResources.getResources();
}
@Override
public Resources.Theme getTheme() {
return mContextForResources.getTheme();
}
@Override
public String getPackageName() {
return mContextForResources.getPackageName();
}
}
}
...@@ -5,6 +5,7 @@ include_rules = { ...@@ -5,6 +5,7 @@ include_rules = {
specific_include_rules = { specific_include_rules = {
'Tab\.java': [ 'Tab\.java': [
"-chrome", "-chrome",
"+chrome/android/java/src/org/chromium/chrome/browser/night_mode",
"+chrome/android/java/src/org/chromium/chrome/browser/tab", "+chrome/android/java/src/org/chromium/chrome/browser/tab",
"-components", "-components",
"+components/embedder_support/android/java/src/org/chromium/components/embedder_support/view", "+components/embedder_support/android/java/src/org/chromium/components/embedder_support/view",
......
...@@ -8,7 +8,6 @@ import android.annotation.SuppressLint; ...@@ -8,7 +8,6 @@ import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Rect; import android.graphics.Rect;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
...@@ -17,7 +16,6 @@ import android.support.annotation.IntDef; ...@@ -17,7 +16,6 @@ import android.support.annotation.IntDef;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.ContextThemeWrapper;
import android.view.View; import android.view.View;
import android.view.View.OnAttachStateChangeListener; import android.view.View.OnAttachStateChangeListener;
import android.view.ViewGroup; import android.view.ViewGroup;
...@@ -47,6 +45,7 @@ import org.chromium.chrome.browser.native_page.FrozenNativePage; ...@@ -47,6 +45,7 @@ import org.chromium.chrome.browser.native_page.FrozenNativePage;
import org.chromium.chrome.browser.native_page.NativePage; import org.chromium.chrome.browser.native_page.NativePage;
import org.chromium.chrome.browser.native_page.NativePageAssassin; import org.chromium.chrome.browser.native_page.NativePageAssassin;
import org.chromium.chrome.browser.native_page.NativePageFactory; import org.chromium.chrome.browser.native_page.NativePageFactory;
import org.chromium.chrome.browser.night_mode.NightModeUtils;
import org.chromium.chrome.browser.offlinepages.OfflinePageUtils; import org.chromium.chrome.browser.offlinepages.OfflinePageUtils;
import org.chromium.chrome.browser.prerender.ExternalPrerenderHandler; import org.chromium.chrome.browser.prerender.ExternalPrerenderHandler;
import org.chromium.chrome.browser.previews.PreviewsAndroidBridge; import org.chromium.chrome.browser.previews.PreviewsAndroidBridge;
...@@ -312,16 +311,9 @@ public class Tab ...@@ -312,16 +311,9 @@ public class Tab
// getting the wrong night mode state when application context inherits a system UI mode // getting the wrong night mode state when application context inherits a system UI mode
// different from the UI mode we need. // different from the UI mode we need.
// TODO(https://crbug.com/938641): Remove this once Tab UIs are all inflated from activity. // TODO(https://crbug.com/938641): Remove this once Tab UIs are all inflated from activity.
ContextThemeWrapper themeWrapper = new ContextThemeWrapper( mThemedApplicationContext = NightModeUtils.wrapContextWithNightModeConfig(
ContextUtils.getApplicationContext(), ChromeActivity.getThemeId()); ContextUtils.getApplicationContext(), ChromeActivity.getThemeId(),
Configuration config = new Configuration(); false /*nightMode*/);
// 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.
config.fontScale = 0;
config.uiMode = Configuration.UI_MODE_NIGHT_NO
| (config.uiMode & ~Configuration.UI_MODE_NIGHT_MASK);
themeWrapper.applyOverrideConfiguration(config);
mThemedApplicationContext = themeWrapper;
mWindowAndroid = window; mWindowAndroid = window;
mLaunchType = launchType; mLaunchType = launchType;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment