Implemented Dark Mode on Autofill UI.

Test: manual verification using modified sample app
Test: atest CtsAutoFillServiceTestCases # to make sure it didn't break anything

Bug: 116457731
Fixes: 116180485

Change-Id: I0b7d5415e6b5b8874e27289bee4c8218d63dba13
This commit is contained in:
Felipe Leme
2018-09-24 11:07:56 -07:00
parent 44bf21d3bb
commit ff9ec38907
8 changed files with 125 additions and 23 deletions

View File

@@ -16,13 +16,18 @@
<resources>
<style name="Theme.DeviceDefault.Dialog.Alert" parent="Theme.Leanback.Dialog.Alert" />
<style name="Theme.DeviceDefault.Light.Dialog.Alert" parent="Theme.Leanback.Light.Dialog.Alert" />
<style name="Theme.DeviceDefault.Autofill" parent="Theme.Material">
<!-- TODO(b/116457731): remove colorBackground from colors_material.xml if not used anymore -->
<style name="Theme.DeviceDefault.Autofill" parent="Theme.Material">
<item name="colorBackground">@color/autofill_background_material_dark</item>
</style>
<style name="Theme.DeviceDefault.Autofill.Save" parent="Theme.Material.Panel">
<!-- TODO(b/116457731): remove colorBackground from colors_material.xml if not used anymore -->
<item name="colorBackground">@color/autofill_background_material_dark</item>
</style>
<!-- TV always use dark mode -->
<style name="Theme.DeviceDefault.Autofill.Light" parent="Theme.DeviceDefault.Autofill"/>
<style name="Theme.DeviceDefault.Light.Autofill.Save" parent="Theme.DeviceDefault.Autofill.Save"/>
<style name="Theme.DeviceDefault.Resolver" parent="Theme.Leanback.Resolver" />
</resources>

View File

@@ -3154,7 +3154,9 @@
<java-symbol type="integer" name="autofill_max_visible_datasets" />
<java-symbol type="style" name="Theme.DeviceDefault.Autofill" />
<java-symbol type="style" name="Theme.DeviceDefault.Light.Autofill" />
<java-symbol type="style" name="Theme.DeviceDefault.Autofill.Save" />
<java-symbol type="style" name="Theme.DeviceDefault.Light.Autofill.Save" />
<java-symbol type="dimen" name="notification_big_picture_max_height"/>
<java-symbol type="dimen" name="notification_big_picture_max_width"/>

View File

@@ -1681,11 +1681,14 @@ easier.
</style>
<!-- @hide DeviceDefault theme for the autofill FillUi -->
<style name="Theme.DeviceDefault.Autofill" parent="Theme.DeviceDefault.Light"/>
<!-- @hide DeviceDefault themes for the autofill FillUi -->
<style name="Theme.DeviceDefault.Autofill" />
<style name="Theme.DeviceDefault.Light.Autofill" />
<!-- @hide DeviceDefault theme for the autofill SaveUi -->
<style name="Theme.DeviceDefault.Autofill.Save" parent="Theme.DeviceDefault.Light.Panel"/>
<!-- @hide DeviceDefault theme for the autofill SaveUi. NOTE: it must be a .Panel so the dialog
is shown at the bottom of the screen -->
<style name="Theme.DeviceDefault.Autofill.Save" parent="Theme.DeviceDefault.Panel"/>
<style name="Theme.DeviceDefault.Light.Autofill.Save" parent="Theme.DeviceDefault.Light.Panel"/>
<!-- DeviceDefault theme for the default system theme. -->
<style name="Theme.DeviceDefault.System" parent="Theme.DeviceDefault.Light.DarkActionBar" />

View File

@@ -43,6 +43,8 @@ import android.widget.Toast;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.LocalServices;
import com.android.server.UiModeManagerInternal;
import com.android.server.UiThread;
import com.android.server.autofill.Helper;
@@ -69,6 +71,7 @@ public final class AutoFillUI {
private final MetricsLogger mMetricsLogger = new MetricsLogger();
private final @NonNull OverlayControl mOverlayControl;
private final @NonNull UiModeManagerInternal mUiModeMgr;
public interface AutoFillUiCallback {
void authenticate(int requestId, int datasetIndex, @NonNull IntentSender intent,
@@ -86,6 +89,7 @@ public final class AutoFillUI {
public AutoFillUI(@NonNull Context context) {
mContext = context;
mOverlayControl = new OverlayControl(context);
mUiModeMgr = LocalServices.getService(UiModeManagerInternal.class);
}
public void setCallback(@NonNull AutoFillUiCallback callback) {
@@ -193,7 +197,9 @@ public final class AutoFillUI {
}
hideAllUiThread(callback);
mFillUi = new FillUi(mContext, response, focusedId,
filterText, mOverlayControl, serviceLabel, serviceIcon, new FillUi.Callback() {
filterText, mOverlayControl, serviceLabel, serviceIcon,
mUiModeMgr.isNightMode(),
new FillUi.Callback() {
@Override
public void onResponsePicked(FillResponse response) {
log.setType(MetricsEvent.TYPE_DETAIL);
@@ -332,7 +338,7 @@ public final class AutoFillUI {
}
mMetricsLogger.write(log);
}
}, isUpdate, compatMode);
}, mUiModeMgr.isNightMode(), isUpdate, compatMode);
});
}
@@ -368,6 +374,7 @@ public final class AutoFillUI {
pw.println("Autofill UI");
final String prefix = " ";
final String prefix2 = " ";
pw.print(prefix); pw.print("Night mode: "); pw.println(mUiModeMgr.isNightMode());
if (mFillUi != null) {
pw.print(prefix); pw.println("showsFillUi: true");
mFillUi.dump(pw, prefix2);

View File

@@ -73,7 +73,10 @@ import java.util.stream.Collectors;
final class FillUi {
private static final String TAG = "FillUi";
private static final int THEME_ID = com.android.internal.R.style.Theme_DeviceDefault_Autofill;
private static final int THEME_ID_LIGHT =
com.android.internal.R.style.Theme_DeviceDefault_Light_Autofill;
private static final int THEME_ID_DARK =
com.android.internal.R.style.Theme_DeviceDefault_Autofill;
private static final TypedValue sTempTypedValue = new TypedValue();
@@ -117,6 +120,8 @@ final class FillUi {
private boolean mDestroyed;
private final int mThemeId;
public static boolean isFullScreen(Context context) {
if (sFullScreenMode != null) {
if (sVerbose) Slog.v(TAG, "forcing full-screen mode to " + sFullScreenMode);
@@ -128,10 +133,13 @@ final class FillUi {
FillUi(@NonNull Context context, @NonNull FillResponse response,
@NonNull AutofillId focusedViewId, @NonNull @Nullable String filterText,
@NonNull OverlayControl overlayControl, @NonNull CharSequence serviceLabel,
@NonNull Drawable serviceIcon, @NonNull Callback callback) {
@NonNull Drawable serviceIcon, boolean nightMode, @NonNull Callback callback) {
if (sVerbose) Slog.v(TAG, "nightMode: " + nightMode);
mThemeId = nightMode ? THEME_ID_DARK : THEME_ID_LIGHT;
mCallback = callback;
mFullScreen = isFullScreen(context);
mContext = new ContextThemeWrapper(context, THEME_ID);
mContext = new ContextThemeWrapper(context, mThemeId);
final LayoutInflater inflater = LayoutInflater.from(mContext);
final RemoteViews headerPresentation = response.getHeader();
@@ -216,7 +224,7 @@ final class FillUi {
ViewGroup container = decor.findViewById(R.id.autofill_dataset_picker);
final View content;
try {
response.getPresentation().setApplyTheme(THEME_ID);
response.getPresentation().setApplyTheme(mThemeId);
content = response.getPresentation().apply(mContext, decor, interceptionHandler);
container.addView(content);
} catch (RuntimeException e) {
@@ -257,7 +265,7 @@ final class FillUi {
RemoteViews.OnClickHandler clickBlocker = null;
if (headerPresentation != null) {
clickBlocker = newClickBlocker();
headerPresentation.setApplyTheme(THEME_ID);
headerPresentation.setApplyTheme(mThemeId);
mHeader = headerPresentation.apply(mContext, null, clickBlocker);
final LinearLayout headerContainer =
decor.findViewById(R.id.autofill_dataset_header);
@@ -275,7 +283,7 @@ final class FillUi {
if (clickBlocker == null) { // already set for header
clickBlocker = newClickBlocker();
}
footerPresentation.setApplyTheme(THEME_ID);
footerPresentation.setApplyTheme(mThemeId);
mFooter = footerPresentation.apply(mContext, null, clickBlocker);
// Footer not supported on some platform e.g. TV
if (sVerbose) Slog.v(TAG, "adding footer");
@@ -302,7 +310,7 @@ final class FillUi {
final View view;
try {
if (sVerbose) Slog.v(TAG, "setting remote view for " + focusedViewId);
presentation.setApplyTheme(THEME_ID);
presentation.setApplyTheme(mThemeId);
view = presentation.apply(mContext, null, interceptionHandler);
} catch (RuntimeException e) {
Slog.e(TAG, "Error inflating remote views", e);
@@ -732,6 +740,18 @@ final class FillUi {
pw.print(prefix); pw.print("mContentWidth: "); pw.println(mContentWidth);
pw.print(prefix); pw.print("mContentHeight: "); pw.println(mContentHeight);
pw.print(prefix); pw.print("mDestroyed: "); pw.println(mDestroyed);
pw.print(prefix); pw.print("theme id: "); pw.print(mThemeId);
switch (mThemeId) {
case THEME_ID_DARK:
pw.println(" (dark)");
break;
case THEME_ID_LIGHT:
pw.println(" (light)");
break;
default:
pw.println("(UNKNOWN_MODE)");
break;
}
if (mWindow != null) {
pw.print(prefix); pw.print("mWindow: ");
final String prefix2 = prefix + " ";

View File

@@ -72,9 +72,11 @@ import java.util.ArrayList;
*/
final class SaveUi {
private static final String TAG = "AutofillSaveUi";
private static final String TAG = "SaveUi";
private static final int THEME_ID =
private static final int THEME_ID_LIGHT =
com.android.internal.R.style.Theme_DeviceDefault_Light_Autofill_Save;
private static final int THEME_ID_DARK =
com.android.internal.R.style.Theme_DeviceDefault_Autofill_Save;
public interface OnSaveListener {
@@ -144,6 +146,7 @@ final class SaveUi {
private final String mServicePackageName;
private final ComponentName mComponentName;
private final boolean mCompatMode;
private final int mThemeId;
private boolean mDestroyed;
@@ -152,7 +155,9 @@ final class SaveUi {
@Nullable String servicePackageName, @NonNull ComponentName componentName,
@NonNull SaveInfo info, @NonNull ValueFinder valueFinder,
@NonNull OverlayControl overlayControl, @NonNull OnSaveListener listener,
boolean isUpdate, boolean compatMode) {
boolean nightMode, boolean isUpdate, boolean compatMode) {
if (sVerbose) Slog.v(TAG, "nightMode: " + nightMode);
mThemeId = nightMode ? THEME_ID_DARK : THEME_ID_LIGHT;
mPendingUi= pendingUi;
mListener = new OneActionThenDestroyListener(listener);
mOverlayControl = overlayControl;
@@ -160,7 +165,7 @@ final class SaveUi {
mComponentName = componentName;
mCompatMode = compatMode;
context = new ContextThemeWrapper(context, THEME_ID);
context = new ContextThemeWrapper(context, mThemeId);
final LayoutInflater inflater = LayoutInflater.from(context);
final View view = inflater.inflate(R.layout.autofill_save, null);
@@ -250,7 +255,7 @@ final class SaveUi {
}
yesButton.setOnClickListener((v) -> mListener.onSave());
mDialog = new Dialog(context, THEME_ID);
mDialog = new Dialog(context, mThemeId);
mDialog.setContentView(view);
// Dialog can be dismissed when touched outside, but the negative listener should not be
@@ -337,7 +342,7 @@ final class SaveUi {
try {
// Create the remote view peer.
template.setApplyTheme(THEME_ID);
template.setApplyTheme(mThemeId);
final View customSubtitleView = template.apply(context, null, handler);
// Apply batch updates (if any).
@@ -556,7 +561,18 @@ final class SaveUi {
pw.print(prefix); pw.print("service: "); pw.println(mServicePackageName);
pw.print(prefix); pw.print("app: "); pw.println(mComponentName.toShortString());
pw.print(prefix); pw.print("compat mode: "); pw.println(mCompatMode);
pw.print(prefix); pw.print("theme id: "); pw.print(mThemeId);
switch (mThemeId) {
case THEME_ID_DARK:
pw.println(" (dark)");
break;
case THEME_ID_LIGHT:
pw.println(" (light)");
break;
default:
pw.println("(UNKNOWN_MODE)");
break;
}
final View view = mDialog.getWindow().getDecorView();
final int[] loc = view.getLocationOnScreen();
pw.print(prefix); pw.print("coordinates: ");

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2018 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 com.android.server;
/**
* UiModeManager local system service interface.
*
* @hide Only for use within the system server.
*/
public abstract class UiModeManagerInternal {
public abstract boolean isNightMode();
}

View File

@@ -108,6 +108,8 @@ final class UiModeManagerService extends SystemService {
private PowerManager.WakeLock mWakeLock;
private final LocalService mLocalService = new LocalService();
public UiModeManagerService(Context context) {
super(context);
}
@@ -242,6 +244,7 @@ final class UiModeManagerService extends SystemService {
}, TAG + ".onStart");
publishBinderService(Context.UI_MODE_SERVICE, mService);
publishLocalService(UiModeManagerInternal.class, mLocalService);
}
private final IUiModeManager.Stub mService = new IUiModeManager.Stub() {
@@ -367,7 +370,8 @@ final class UiModeManagerService extends SystemService {
pw.println("Current UI Mode Service state:");
pw.print(" mDockState="); pw.print(mDockState);
pw.print(" mLastBroadcastState="); pw.println(mLastBroadcastState);
pw.print(" mNightMode="); pw.print(mNightMode);
pw.print(" mNightMode="); pw.print(mNightMode); pw.print(" (");
pw.print(Shell.nightModeToStr(mNightMode)); pw.print(") ");
pw.print(" mNightModeLocked="); pw.print(mNightModeLocked);
pw.print(" mCarModeEnabled="); pw.print(mCarModeEnabled);
pw.print(" mComputedNightMode="); pw.print(mComputedNightMode);
@@ -839,4 +843,22 @@ final class UiModeManagerService extends SystemService {
}
}
}
public final class LocalService extends UiModeManagerInternal {
@Override
public boolean isNightMode() {
synchronized (mLock) {
final boolean isIt = (mConfiguration.uiMode & Configuration.UI_MODE_NIGHT_YES) != 0;
if (LOG) {
Slog.d(TAG,
"LocalService.isNightMode(): mNightMode=" + mNightMode
+ "; mComputedNightMode=" + mComputedNightMode
+ "; uiMode=" + mConfiguration.uiMode
+ "; isIt=" + isIt);
}
return isIt;
}
}
}
}