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:
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 + " ";
|
||||
|
||||
@@ -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: ");
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user