[automerger skipped] Merge "Make mCurrentPipTaskToken null when app crashes" into udc-dev am: 2a2a1b1266 am: faa6dcf40d -s ours
am skip reason: Merged-In Id869f78dbf85f009509082181590e224a6d7f893 with SHA-1 931efa54c0 is already in history
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/24044846
Change-Id: I2f5c1f942e119391eb10b28b7125b96bff2753bd
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index a4dd065..d352be1 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -212,14 +212,7 @@
@GuardedBy("mLock")
private boolean mFoldedDeviceState;
- private final CameraManager.DeviceStateListener mFoldStateListener =
- new CameraManager.DeviceStateListener() {
- @Override
- public final void onDeviceStateChanged(boolean folded) {
- synchronized (mLock) {
- mFoldedDeviceState = folded;
- }
- }};
+ private CameraManager.DeviceStateListener mFoldStateListener;
private static final String TAG = "CameraCharacteristics";
@@ -245,7 +238,18 @@
/**
* Return the device state listener for this Camera characteristics instance
*/
- CameraManager.DeviceStateListener getDeviceStateListener() { return mFoldStateListener; }
+ CameraManager.DeviceStateListener getDeviceStateListener() {
+ if (mFoldStateListener == null) {
+ mFoldStateListener = new CameraManager.DeviceStateListener() {
+ @Override
+ public final void onDeviceStateChanged(boolean folded) {
+ synchronized (mLock) {
+ mFoldedDeviceState = folded;
+ }
+ }};
+ }
+ return mFoldStateListener;
+ }
/**
* Overrides the property value
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 51501b5..85f8ca6 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -1836,6 +1836,7 @@
ctx.getSystemService(DeviceStateManager.class).registerCallback(
new HandlerExecutor(mDeviceStateHandler), mFoldStateListener);
} catch (IllegalStateException e) {
+ mFoldStateListener = null;
Log.v(TAG, "Failed to register device state listener!");
Log.v(TAG, "Device state dependent characteristics updates will not be" +
"functional!");
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index e23bbc6..d3bde4b 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.content.Context;
+import android.graphics.ImageFormat;
import android.hardware.ICameraService;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
@@ -1478,6 +1479,12 @@
}
}
+ // Allow RAW formats, even when not advertised.
+ if (inputFormat == ImageFormat.RAW_PRIVATE || inputFormat == ImageFormat.RAW10
+ || inputFormat == ImageFormat.RAW12 || inputFormat == ImageFormat.RAW_SENSOR) {
+ return true;
+ }
+
if (validFormat == false) {
return false;
}
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 01977f6..6195443 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -103,6 +103,7 @@
private static final int MSG_UDFPS_POINTER_DOWN = 108;
private static final int MSG_UDFPS_POINTER_UP = 109;
private static final int MSG_POWER_BUTTON_PRESSED = 110;
+ private static final int MSG_UDFPS_OVERLAY_SHOWN = 111;
/**
* @hide
@@ -121,6 +122,24 @@
public @interface EnrollReason {}
/**
+ * Udfps ui event of overlay is shown on the screen.
+ * @hide
+ */
+ public static final int UDFPS_UI_OVERLAY_SHOWN = 1;
+ /**
+ * Udfps ui event of the udfps UI being ready (e.g. HBM illumination is enabled).
+ * @hide
+ */
+ public static final int UDFPS_UI_READY = 2;
+
+ /**
+ * @hide
+ */
+ @IntDef({UDFPS_UI_OVERLAY_SHOWN, UDFPS_UI_READY})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UdfpsUiEvent{}
+
+ /**
* Request authentication with any single sensor.
* @hide
*/
@@ -475,12 +494,17 @@
/**
* Called when a pointer down event has occurred.
*/
- public void onPointerDown(int sensorId){ }
+ public void onUdfpsPointerDown(int sensorId){ }
/**
* Called when a pointer up event has occurred.
*/
- public void onPointerUp(int sensorId){ }
+ public void onUdfpsPointerUp(int sensorId){ }
+
+ /**
+ * Called when udfps overlay is shown.
+ */
+ public void onUdfpsOverlayShown() { }
}
/**
@@ -1112,14 +1136,14 @@
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
- public void onUiReady(long requestId, int sensorId) {
+ public void onUdfpsUiEvent(@UdfpsUiEvent int event, long requestId, int sensorId) {
if (mService == null) {
- Slog.w(TAG, "onUiReady: no fingerprint service");
+ Slog.w(TAG, "onUdfpsUiEvent: no fingerprint service");
return;
}
try {
- mService.onUiReady(requestId, sensorId);
+ mService.onUdfpsUiEvent(event, requestId, sensorId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1365,6 +1389,8 @@
case MSG_POWER_BUTTON_PRESSED:
sendPowerPressed();
break;
+ case MSG_UDFPS_OVERLAY_SHOWN:
+ sendUdfpsOverlayShown();
default:
Slog.w(TAG, "Unknown message: " + msg.what);
@@ -1489,7 +1515,7 @@
}
if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onPointerDown(sensorId);
+ mEnrollmentCallback.onUdfpsPointerDown(sensorId);
}
}
@@ -1500,7 +1526,7 @@
mAuthenticationCallback.onUdfpsPointerUp(sensorId);
}
if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onPointerUp(sensorId);
+ mEnrollmentCallback.onUdfpsPointerUp(sensorId);
}
}
@@ -1512,6 +1538,12 @@
}
}
+ private void sendUdfpsOverlayShown() {
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onUdfpsOverlayShown();
+ }
+ }
+
/**
* @hide
*/
@@ -1787,6 +1819,11 @@
public void onUdfpsPointerUp(int sensorId) {
mHandler.obtainMessage(MSG_UDFPS_POINTER_UP, sensorId, 0).sendToTarget();
}
+
+ @Override
+ public void onUdfpsOverlayShown() {
+ mHandler.obtainMessage(MSG_UDFPS_OVERLAY_SHOWN).sendToTarget();
+ }
};
}
diff --git a/core/java/android/hardware/fingerprint/FingerprintServiceReceiver.java b/core/java/android/hardware/fingerprint/FingerprintServiceReceiver.java
index a9779b5..89d710d 100644
--- a/core/java/android/hardware/fingerprint/FingerprintServiceReceiver.java
+++ b/core/java/android/hardware/fingerprint/FingerprintServiceReceiver.java
@@ -75,4 +75,9 @@
public void onUdfpsPointerUp(int sensorId) throws RemoteException {
}
+
+ @Override
+ public void onUdfpsOverlayShown() throws RemoteException {
+
+ }
}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index ec5749e..ff2f313 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -193,7 +193,7 @@
// Notifies about the fingerprint UI being ready (e.g. HBM illumination is enabled).
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
- void onUiReady(long requestId, int sensorId);
+ void onUdfpsUiEvent(int event, long requestId, int sensorId);
// Sets the controller for managing the UDFPS overlay.
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
diff --git a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
index 9cea1fe..91a32d7 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
@@ -32,4 +32,5 @@
void onChallengeGenerated(int sensorId, int userId, long challenge);
void onUdfpsPointerDown(int sensorId);
void onUdfpsPointerUp(int sensorId);
+ void onUdfpsOverlayShown();
}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index a95ce64..7f53cb4 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -771,7 +771,7 @@
Zygote.applyInvokeWithSystemProperty(parsedArgs);
if (Zygote.nativeSupportsMemoryTagging()) {
- String mode = SystemProperties.get("arm64.memtag.process.system_server", "");
+ String mode = SystemProperties.get("persist.arm64.memtag.system_server", "");
if (mode.isEmpty()) {
/* The system server has ASYNC MTE by default, in order to allow
* system services to specify their own MTE level later, as you
diff --git a/core/res/res/layout/shutdown_dialog.xml b/core/res/res/layout/shutdown_dialog.xml
index ec67aa8..726c255 100644
--- a/core/res/res/layout/shutdown_dialog.xml
+++ b/core/res/res/layout/shutdown_dialog.xml
@@ -40,7 +40,7 @@
android:fontFamily="@string/config_headlineFontFamily"/>
<TextView
- android:id="@+id/text2"
+ android:id="@id/text2"
android:layout_width="wrap_content"
android:layout_height="32sp"
android:text="@string/shutdown_progress"
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index fd74185..4ae54a0 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -168,8 +168,9 @@
<bool name="ignore_emergency_number_routing_from_db">false</bool>
<java-symbol type="bool" name="ignore_emergency_number_routing_from_db" />
- <!-- Whether "Virtual DSDA", i.e. in-call IMS connectivity can be provided on both subs with
- only single logical modem, by using its data connection in addition to cellular IMS. -->
- <bool name="config_enable_virtual_dsda">false</bool>
- <java-symbol type="bool" name="config_enable_virtual_dsda" />
+ <!-- Boolean indicating whether allow sending null to modem to clear the previous initial attach
+ data profile -->
+ <bool name="allow_clear_initial_attach_data_profile">false</bool>
+ <java-symbol type="bool" name="allow_clear_initial_attach_data_profile" />
+
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 8762e0c..2c72f45 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1795,6 +1795,8 @@
<string name="biometric_error_user_canceled">Authentication canceled</string>
<!-- Message shown by the biometric dialog when biometric is not recognized -->
<string name="biometric_not_recognized">Not recognized</string>
+ <!-- Message shown by the biometric dialog when face is not recognized [CHAR LIMIT=50] -->
+ <string name="biometric_face_not_recognized">Face not recognized</string>
<!-- Message shown when biometric authentication has been canceled [CHAR LIMIT=50] -->
<string name="biometric_error_canceled">Authentication canceled</string>
<!-- Message returned to applications if BiometricPrompt setAllowDeviceCredentials is enabled but no pin, pattern, or password is set. [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1ccee27..da0ba87 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2571,6 +2571,7 @@
<java-symbol type="string" name="biometric_error_hw_unavailable" />
<java-symbol type="string" name="biometric_error_user_canceled" />
<java-symbol type="string" name="biometric_not_recognized" />
+ <java-symbol type="string" name="biometric_face_not_recognized" />
<java-symbol type="string" name="biometric_error_canceled" />
<java-symbol type="string" name="biometric_error_device_not_secured" />
<java-symbol type="string" name="biometric_error_generic" />
@@ -5021,6 +5022,7 @@
<java-symbol type="bool" name="config_batteryStatsResetOnUnplugHighBatteryLevel" />
<java-symbol type="bool" name="config_batteryStatsResetOnUnplugAfterSignificantCharge" />
+
<java-symbol name="materialColorOnSecondaryFixedVariant" type="attr"/>
<java-symbol name="materialColorOnTertiaryFixedVariant" type="attr"/>
<java-symbol name="materialColorSurfaceContainerLowest" type="attr"/>
diff --git a/packages/SettingsLib/Spa/tests/Android.bp b/packages/SettingsLib/Spa/tests/Android.bp
index b4c67cc..f9e64ae 100644
--- a/packages/SettingsLib/Spa/tests/Android.bp
+++ b/packages/SettingsLib/Spa/tests/Android.bp
@@ -31,7 +31,6 @@
"SpaLib",
"SpaLibTestUtils",
"androidx.compose.runtime_runtime",
- "androidx.lifecycle_lifecycle-runtime-testing",
"androidx.test.ext.junit",
"androidx.test.runner",
"mockito-target-minus-junit4",
diff --git a/packages/SettingsLib/Spa/testutils/Android.bp b/packages/SettingsLib/Spa/testutils/Android.bp
index 2c1e1c2..e4d56cc 100644
--- a/packages/SettingsLib/Spa/testutils/Android.bp
+++ b/packages/SettingsLib/Spa/testutils/Android.bp
@@ -29,6 +29,7 @@
"androidx.compose.runtime_runtime",
"androidx.compose.ui_ui-test-junit4",
"androidx.compose.ui_ui-test-manifest",
+ "androidx.lifecycle_lifecycle-runtime-testing",
"mockito",
"truth-prebuilt",
],
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicSummary.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicSummary.java
index c9d9b57..5b7899b 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicSummary.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicSummary.java
@@ -16,7 +16,7 @@
package com.android.settingslib.drawer;
-/** Interface for {@link SwitchController} whose instances support dynamic summary */
+/** Interface for {@link EntryController} whose instances support dynamic summary */
public interface DynamicSummary {
/** @return the dynamic summary text */
String getDynamicSummary();
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicTitle.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicTitle.java
index af711dd..cb15773 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicTitle.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicTitle.java
@@ -16,7 +16,7 @@
package com.android.settingslib.drawer;
-/** Interface for {@link SwitchController} whose instances support dynamic title */
+/** Interface for {@link EntryController} whose instances support dynamic title */
public interface DynamicTitle {
/** @return the dynamic title text */
String getDynamicTitle();
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntriesProvider.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntriesProvider.java
new file mode 100644
index 0000000..1c14c0a
--- /dev/null
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntriesProvider.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2023 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.settingslib.drawer;
+
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.ProviderInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An abstract class for injecting entries to Settings.
+ */
+public abstract class EntriesProvider extends ContentProvider {
+ private static final String TAG = "EntriesProvider";
+
+ public static final String METHOD_GET_ENTRY_DATA = "getEntryData";
+ public static final String METHOD_GET_PROVIDER_ICON = "getProviderIcon";
+ public static final String METHOD_GET_DYNAMIC_TITLE = "getDynamicTitle";
+ public static final String METHOD_GET_DYNAMIC_SUMMARY = "getDynamicSummary";
+ public static final String METHOD_IS_CHECKED = "isChecked";
+ public static final String METHOD_ON_CHECKED_CHANGED = "onCheckedChanged";
+
+ /**
+ * @deprecated use {@link #METHOD_GET_ENTRY_DATA} instead.
+ */
+ @Deprecated
+ public static final String METHOD_GET_SWITCH_DATA = "getSwitchData";
+
+ public static final String EXTRA_ENTRY_DATA = "entry_data";
+ public static final String EXTRA_SWITCH_CHECKED_STATE = "checked_state";
+ public static final String EXTRA_SWITCH_SET_CHECKED_ERROR = "set_checked_error";
+ public static final String EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE = "set_checked_error_message";
+
+ /**
+ * @deprecated use {@link #EXTRA_ENTRY_DATA} instead.
+ */
+ @Deprecated
+ public static final String EXTRA_SWITCH_DATA = "switch_data";
+
+ private String mAuthority;
+ private final Map<String, EntryController> mControllerMap = new LinkedHashMap<>();
+ private final List<Bundle> mEntryDataList = new ArrayList<>();
+
+ /**
+ * Get a list of {@link EntryController} for this provider.
+ */
+ protected abstract List<? extends EntryController> createEntryControllers();
+
+ protected EntryController getController(String key) {
+ return mControllerMap.get(key);
+ }
+
+ @Override
+ public void attachInfo(Context context, ProviderInfo info) {
+ mAuthority = info.authority;
+ Log.i(TAG, mAuthority);
+ super.attachInfo(context, info);
+ }
+
+ @Override
+ public boolean onCreate() {
+ final List<? extends EntryController> controllers = createEntryControllers();
+ if (controllers == null || controllers.isEmpty()) {
+ throw new IllegalArgumentException();
+ }
+
+ for (EntryController controller : controllers) {
+ final String key = controller.getKey();
+ if (TextUtils.isEmpty(key)) {
+ throw new NullPointerException("Entry key cannot be null: "
+ + controller.getClass().getSimpleName());
+ } else if (mControllerMap.containsKey(key)) {
+ throw new IllegalArgumentException("Entry key " + key + " is duplicated by: "
+ + controller.getClass().getSimpleName());
+ }
+
+ controller.setAuthority(mAuthority);
+ mControllerMap.put(key, controller);
+ if (!(controller instanceof PrimarySwitchController)) {
+ mEntryDataList.add(controller.getBundle());
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public Bundle call(String method, String uriString, Bundle extras) {
+ final Bundle bundle = new Bundle();
+ final String key = extras != null
+ ? extras.getString(META_DATA_PREFERENCE_KEYHINT)
+ : null;
+ if (TextUtils.isEmpty(key)) {
+ switch (method) {
+ case METHOD_GET_ENTRY_DATA:
+ bundle.putParcelableList(EXTRA_ENTRY_DATA, mEntryDataList);
+ return bundle;
+ case METHOD_GET_SWITCH_DATA:
+ bundle.putParcelableList(EXTRA_SWITCH_DATA, mEntryDataList);
+ return bundle;
+ default:
+ return null;
+ }
+ }
+
+ final EntryController controller = mControllerMap.get(key);
+ if (controller == null) {
+ return null;
+ }
+
+ switch (method) {
+ case METHOD_GET_ENTRY_DATA:
+ case METHOD_GET_SWITCH_DATA:
+ if (!(controller instanceof PrimarySwitchController)) {
+ return controller.getBundle();
+ }
+ break;
+ case METHOD_GET_PROVIDER_ICON:
+ if (controller instanceof ProviderIcon) {
+ return ((ProviderIcon) controller).getProviderIcon();
+ }
+ break;
+ case METHOD_GET_DYNAMIC_TITLE:
+ if (controller instanceof DynamicTitle) {
+ bundle.putString(META_DATA_PREFERENCE_TITLE,
+ ((DynamicTitle) controller).getDynamicTitle());
+ return bundle;
+ }
+ break;
+ case METHOD_GET_DYNAMIC_SUMMARY:
+ if (controller instanceof DynamicSummary) {
+ bundle.putString(META_DATA_PREFERENCE_SUMMARY,
+ ((DynamicSummary) controller).getDynamicSummary());
+ return bundle;
+ }
+ break;
+ case METHOD_IS_CHECKED:
+ if (controller instanceof ProviderSwitch) {
+ bundle.putBoolean(EXTRA_SWITCH_CHECKED_STATE,
+ ((ProviderSwitch) controller).isSwitchChecked());
+ return bundle;
+ }
+ break;
+ case METHOD_ON_CHECKED_CHANGED:
+ if (controller instanceof ProviderSwitch) {
+ return onSwitchCheckedChanged(extras.getBoolean(EXTRA_SWITCH_CHECKED_STATE),
+ (ProviderSwitch) controller);
+ }
+ break;
+ }
+ return null;
+ }
+
+ private Bundle onSwitchCheckedChanged(boolean checked, ProviderSwitch controller) {
+ final boolean success = controller.onSwitchCheckedChanged(checked);
+ final Bundle bundle = new Bundle();
+ bundle.putBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR, !success);
+ if (success) {
+ if (controller instanceof DynamicSummary) {
+ ((EntryController) controller).notifySummaryChanged(getContext());
+ }
+ } else {
+ bundle.putString(EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE,
+ controller.getSwitchErrorMessage(checked));
+ }
+ return bundle;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException();
+ }
+}
+
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntryController.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntryController.java
new file mode 100644
index 0000000..5d6e6a3
--- /dev/null
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntryController.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2023 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.settingslib.drawer;
+
+import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_DYNAMIC_SUMMARY;
+import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_DYNAMIC_TITLE;
+import static com.android.settingslib.drawer.TileUtils.EXTRA_CATEGORY_KEY;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_HINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_TINTABLE;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_PENDING_INTENT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY_URI;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE_URI;
+
+import android.app.PendingIntent;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
+
+/**
+ * A controller that manages events for switch.
+ */
+public abstract class EntryController {
+
+ private String mAuthority;
+
+ /**
+ * Returns the key for this switch.
+ */
+ public abstract String getKey();
+
+ /**
+ * Returns the {@link MetaData} for this switch.
+ */
+ protected abstract MetaData getMetaData();
+
+ /**
+ * Notify registered observers that title was updated and attempt to sync changes.
+ */
+ public void notifyTitleChanged(Context context) {
+ if (this instanceof DynamicTitle) {
+ notifyChanged(context, METHOD_GET_DYNAMIC_TITLE);
+ }
+ }
+
+ /**
+ * Notify registered observers that summary was updated and attempt to sync changes.
+ */
+ public void notifySummaryChanged(Context context) {
+ if (this instanceof DynamicSummary) {
+ notifyChanged(context, METHOD_GET_DYNAMIC_SUMMARY);
+ }
+ }
+
+ void setAuthority(String authority) {
+ mAuthority = authority;
+ }
+
+ Bundle getBundle() {
+ final MetaData metaData = getMetaData();
+ if (metaData == null) {
+ throw new NullPointerException("Should not return null in getMetaData()");
+ }
+
+ final Bundle bundle = metaData.build();
+ final String uriString = new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(mAuthority)
+ .build()
+ .toString();
+ bundle.putString(META_DATA_PREFERENCE_KEYHINT, getKey());
+ if (this instanceof ProviderIcon) {
+ bundle.putString(META_DATA_PREFERENCE_ICON_URI, uriString);
+ }
+ if (this instanceof DynamicTitle) {
+ bundle.putString(META_DATA_PREFERENCE_TITLE_URI, uriString);
+ }
+ if (this instanceof DynamicSummary) {
+ bundle.putString(META_DATA_PREFERENCE_SUMMARY_URI, uriString);
+ }
+ if (this instanceof ProviderSwitch) {
+ bundle.putString(META_DATA_PREFERENCE_SWITCH_URI, uriString);
+ }
+ return bundle;
+ }
+
+ private void notifyChanged(Context context, String method) {
+ final Uri uri = TileUtils.buildUri(mAuthority, method, getKey());
+ context.getContentResolver().notifyChange(uri, null);
+ }
+
+ /**
+ * Collects all meta data of the item.
+ */
+ protected static class MetaData {
+ private String mCategory;
+ private int mOrder;
+ @DrawableRes
+ private int mIcon;
+ private int mIconBackgroundHint;
+ private int mIconBackgroundArgb;
+ private Boolean mIconTintable;
+ @StringRes
+ private int mTitleId;
+ private String mTitle;
+ @StringRes
+ private int mSummaryId;
+ private String mSummary;
+ private PendingIntent mPendingIntent;
+
+ /**
+ * @param category the category of the switch. This value must be from {@link CategoryKey}.
+ */
+ public MetaData(@NonNull String category) {
+ mCategory = category;
+ }
+
+ /**
+ * Set the order of the item that should be displayed on screen. Bigger value items displays
+ * closer on top.
+ */
+ public MetaData setOrder(int order) {
+ mOrder = order;
+ return this;
+ }
+
+ /** Set the icon that should be displayed for the item. */
+ public MetaData setIcon(@DrawableRes int icon) {
+ mIcon = icon;
+ return this;
+ }
+
+ /** Set the icon background color. The value may or may not be used by Settings app. */
+ public MetaData setIconBackgoundHint(int hint) {
+ mIconBackgroundHint = hint;
+ return this;
+ }
+
+ /** Set the icon background color as raw ARGB. */
+ public MetaData setIconBackgoundArgb(int argb) {
+ mIconBackgroundArgb = argb;
+ return this;
+ }
+
+ /** Specify whether the icon is tintable. */
+ public MetaData setIconTintable(boolean tintable) {
+ mIconTintable = tintable;
+ return this;
+ }
+
+ /** Set the title that should be displayed for the item. */
+ public MetaData setTitle(@StringRes int id) {
+ mTitleId = id;
+ return this;
+ }
+
+ /** Set the title that should be displayed for the item. */
+ public MetaData setTitle(String title) {
+ mTitle = title;
+ return this;
+ }
+
+ /** Set the summary text that should be displayed for the item. */
+ public MetaData setSummary(@StringRes int id) {
+ mSummaryId = id;
+ return this;
+ }
+
+ /** Set the summary text that should be displayed for the item. */
+ public MetaData setSummary(String summary) {
+ mSummary = summary;
+ return this;
+ }
+
+ public MetaData setPendingIntent(PendingIntent pendingIntent) {
+ mPendingIntent = pendingIntent;
+ return this;
+ }
+
+ protected Bundle build() {
+ final Bundle bundle = new Bundle();
+ bundle.putString(EXTRA_CATEGORY_KEY, mCategory);
+
+ if (mOrder != 0) {
+ bundle.putInt(META_DATA_KEY_ORDER, mOrder);
+ }
+
+ if (mIcon != 0) {
+ bundle.putInt(META_DATA_PREFERENCE_ICON, mIcon);
+ }
+ if (mIconBackgroundHint != 0) {
+ bundle.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_HINT, mIconBackgroundHint);
+ }
+ if (mIconBackgroundArgb != 0) {
+ bundle.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB, mIconBackgroundArgb);
+ }
+ if (mIconTintable != null) {
+ bundle.putBoolean(META_DATA_PREFERENCE_ICON_TINTABLE, mIconTintable);
+ }
+
+ if (mTitleId != 0) {
+ bundle.putInt(META_DATA_PREFERENCE_TITLE, mTitleId);
+ } else if (mTitle != null) {
+ bundle.putString(META_DATA_PREFERENCE_TITLE, mTitle);
+ }
+
+ if (mSummaryId != 0) {
+ bundle.putInt(META_DATA_PREFERENCE_SUMMARY, mSummaryId);
+ } else if (mSummary != null) {
+ bundle.putString(META_DATA_PREFERENCE_SUMMARY, mSummary);
+ }
+
+ if (mPendingIntent != null) {
+ bundle.putParcelable(META_DATA_PREFERENCE_PENDING_INTENT, mPendingIntent);
+ }
+
+ return bundle;
+ }
+ }
+}
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderIcon.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderIcon.java
index 2945d5c1..3aa6fcb 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderIcon.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderIcon.java
@@ -19,7 +19,7 @@
import android.os.Bundle;
/**
- * Interface for {@link SwitchController} whose instances support icon provided from the content
+ * Interface for {@link EntryController} whose instances support icon provided from the content
* provider
*/
public interface ProviderIcon {
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderSwitch.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderSwitch.java
new file mode 100644
index 0000000..47eb31c
--- /dev/null
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderSwitch.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 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.settingslib.drawer;
+
+/**
+ * Interface for {@link EntryController} whose instances support switch widget provided from the
+ * content provider
+ */
+public interface ProviderSwitch {
+ /**
+ * Returns the checked state of this switch.
+ */
+ boolean isSwitchChecked();
+
+ /**
+ * Called when the checked state of this switch is changed.
+ *
+ * @return true if the checked state was successfully changed, otherwise false
+ */
+ boolean onSwitchCheckedChanged(boolean checked);
+
+ /**
+ * Returns the error message which will be toasted when {@link #onSwitchCheckedChanged} returns
+ * false.
+ */
+ String getSwitchErrorMessage(boolean attemptedChecked);
+}
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderTile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderTile.java
index 54da585..b775e93 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderTile.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderTile.java
@@ -75,7 +75,7 @@
if (infoList != null && !infoList.isEmpty()) {
final ProviderInfo providerInfo = infoList.get(0).providerInfo;
mComponentInfo = providerInfo;
- setMetaData(TileUtils.getSwitchDataFromProvider(context, providerInfo.authority,
+ setMetaData(TileUtils.getEntryDataFromProvider(context, providerInfo.authority,
mKey));
} else {
Log.e(TAG, "Cannot find package info for "
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchController.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchController.java
index 23669b2..a1a4e58 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchController.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchController.java
@@ -16,38 +16,16 @@
package com.android.settingslib.drawer;
-import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_DYNAMIC_SUMMARY;
-import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_DYNAMIC_TITLE;
-import static com.android.settingslib.drawer.SwitchesProvider.METHOD_IS_CHECKED;
-import static com.android.settingslib.drawer.TileUtils.EXTRA_CATEGORY_KEY;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_HINT;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_TINTABLE;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY_URI;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE_URI;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.net.Uri;
-import android.os.Bundle;
-
-import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
-import androidx.annotation.StringRes;
/**
* A controller that manages events for switch.
+ *
+ * @deprecated Use {@link EntriesProvider} with {@link ProviderSwitch} instead.
*/
-public abstract class SwitchController {
+@Deprecated
+public abstract class SwitchController extends EntryController implements ProviderSwitch {
- private String mAuthority;
/**
* Returns the key for this switch.
@@ -55,11 +33,6 @@
public abstract String getSwitchKey();
/**
- * Returns the {@link MetaData} for this switch.
- */
- protected abstract MetaData getMetaData();
-
- /**
* Returns the checked state of this switch.
*/
protected abstract boolean isChecked();
@@ -76,181 +49,41 @@
*/
protected abstract String getErrorMessage(boolean attemptedChecked);
- /**
- * Notify registered observers that title was updated and attempt to sync changes.
- */
- public void notifyTitleChanged(Context context) {
- if (this instanceof DynamicTitle) {
- notifyChanged(context, METHOD_GET_DYNAMIC_TITLE);
- }
+ @Override
+ public String getKey() {
+ return getSwitchKey();
+ }
+
+ @Override
+ public boolean isSwitchChecked() {
+ return isChecked();
+ }
+
+ @Override
+ public boolean onSwitchCheckedChanged(boolean checked) {
+ return onCheckedChanged(checked);
+ }
+
+ @Override
+ public String getSwitchErrorMessage(boolean attemptedChecked) {
+ return getErrorMessage(attemptedChecked);
}
/**
- * Notify registered observers that summary was updated and attempt to sync changes.
+ * Same as {@link EntryController.MetaData}, for backwards compatibility purpose.
+ *
+ * @deprecated Use {@link EntryController.MetaData} instead.
*/
- public void notifySummaryChanged(Context context) {
- if (this instanceof DynamicSummary) {
- notifyChanged(context, METHOD_GET_DYNAMIC_SUMMARY);
- }
- }
-
- /**
- * Notify registered observers that checked state was updated and attempt to sync changes.
- */
- public void notifyCheckedChanged(Context context) {
- notifyChanged(context, METHOD_IS_CHECKED);
- }
-
- void setAuthority(String authority) {
- mAuthority = authority;
- }
-
- Bundle getBundle() {
- final MetaData metaData = getMetaData();
- if (metaData == null) {
- throw new NullPointerException("Should not return null in getMetaData()");
- }
-
- final Bundle bundle = metaData.build();
- final String uriString = new Uri.Builder()
- .scheme(ContentResolver.SCHEME_CONTENT)
- .authority(mAuthority)
- .build()
- .toString();
- bundle.putString(META_DATA_PREFERENCE_KEYHINT, getSwitchKey());
- bundle.putString(META_DATA_PREFERENCE_SWITCH_URI, uriString);
- if (this instanceof ProviderIcon) {
- bundle.putString(META_DATA_PREFERENCE_ICON_URI, uriString);
- }
- if (this instanceof DynamicTitle) {
- bundle.putString(META_DATA_PREFERENCE_TITLE_URI, uriString);
- }
- if (this instanceof DynamicSummary) {
- bundle.putString(META_DATA_PREFERENCE_SUMMARY_URI, uriString);
- }
- return bundle;
- }
-
- private void notifyChanged(Context context, String method) {
- final Uri uri = TileUtils.buildUri(mAuthority, method, getSwitchKey());
- context.getContentResolver().notifyChange(uri, null);
- }
-
- /**
- * Collects all meta data of the item.
- */
- protected static class MetaData {
- private String mCategory;
- private int mOrder;
- @DrawableRes
- private int mIcon;
- private int mIconBackgroundHint;
- private int mIconBackgroundArgb;
- private Boolean mIconTintable;
- @StringRes
- private int mTitleId;
- private String mTitle;
- @StringRes
- private int mSummaryId;
- private String mSummary;
-
+ @Deprecated
+ protected static class MetaData extends EntryController.MetaData {
/**
* @param category the category of the switch. This value must be from {@link CategoryKey}.
+ *
+ * @deprecated Use {@link EntryController.MetaData} instead.
*/
+ @Deprecated
public MetaData(@NonNull String category) {
- mCategory = category;
- }
-
- /**
- * Set the order of the item that should be displayed on screen. Bigger value items displays
- * closer on top.
- */
- public MetaData setOrder(int order) {
- mOrder = order;
- return this;
- }
-
- /** Set the icon that should be displayed for the item. */
- public MetaData setIcon(@DrawableRes int icon) {
- mIcon = icon;
- return this;
- }
-
- /** Set the icon background color. The value may or may not be used by Settings app. */
- public MetaData setIconBackgoundHint(int hint) {
- mIconBackgroundHint = hint;
- return this;
- }
-
- /** Set the icon background color as raw ARGB. */
- public MetaData setIconBackgoundArgb(int argb) {
- mIconBackgroundArgb = argb;
- return this;
- }
-
- /** Specify whether the icon is tintable. */
- public MetaData setIconTintable(boolean tintable) {
- mIconTintable = tintable;
- return this;
- }
-
- /** Set the title that should be displayed for the item. */
- public MetaData setTitle(@StringRes int id) {
- mTitleId = id;
- return this;
- }
-
- /** Set the title that should be displayed for the item. */
- public MetaData setTitle(String title) {
- mTitle = title;
- return this;
- }
-
- /** Set the summary text that should be displayed for the item. */
- public MetaData setSummary(@StringRes int id) {
- mSummaryId = id;
- return this;
- }
-
- /** Set the summary text that should be displayed for the item. */
- public MetaData setSummary(String summary) {
- mSummary = summary;
- return this;
- }
-
- private Bundle build() {
- final Bundle bundle = new Bundle();
- bundle.putString(EXTRA_CATEGORY_KEY, mCategory);
-
- if (mOrder != 0) {
- bundle.putInt(META_DATA_KEY_ORDER, mOrder);
- }
-
- if (mIcon != 0) {
- bundle.putInt(META_DATA_PREFERENCE_ICON, mIcon);
- }
- if (mIconBackgroundHint != 0) {
- bundle.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_HINT, mIconBackgroundHint);
- }
- if (mIconBackgroundArgb != 0) {
- bundle.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB, mIconBackgroundArgb);
- }
- if (mIconTintable != null) {
- bundle.putBoolean(META_DATA_PREFERENCE_ICON_TINTABLE, mIconTintable);
- }
-
- if (mTitleId != 0) {
- bundle.putInt(META_DATA_PREFERENCE_TITLE, mTitleId);
- } else if (mTitle != null) {
- bundle.putString(META_DATA_PREFERENCE_TITLE, mTitle);
- }
-
- if (mSummaryId != 0) {
- bundle.putInt(META_DATA_PREFERENCE_SUMMARY, mSummaryId);
- } else if (mSummary != null) {
- bundle.putString(META_DATA_PREFERENCE_SUMMARY, mSummary);
- }
- return bundle;
+ super(category);
}
}
}
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchesProvider.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchesProvider.java
index f2b3e30..ad00ced 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchesProvider.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchesProvider.java
@@ -16,46 +16,15 @@
package com.android.settingslib.drawer;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
-
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.pm.ProviderInfo;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Map;
/**
* An abstract class for injecting switches to Settings.
+ *
+ * @deprecated Use {@link EntriesProvider} instead.
*/
-public abstract class SwitchesProvider extends ContentProvider {
- private static final String TAG = "SwitchesProvider";
-
- public static final String METHOD_GET_SWITCH_DATA = "getSwitchData";
- public static final String METHOD_GET_PROVIDER_ICON = "getProviderIcon";
- public static final String METHOD_GET_DYNAMIC_TITLE = "getDynamicTitle";
- public static final String METHOD_GET_DYNAMIC_SUMMARY = "getDynamicSummary";
- public static final String METHOD_IS_CHECKED = "isChecked";
- public static final String METHOD_ON_CHECKED_CHANGED = "onCheckedChanged";
-
- public static final String EXTRA_SWITCH_DATA = "switch_data";
- public static final String EXTRA_SWITCH_CHECKED_STATE = "checked_state";
- public static final String EXTRA_SWITCH_SET_CHECKED_ERROR = "set_checked_error";
- public static final String EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE = "set_checked_error_message";
-
- private String mAuthority;
- private final Map<String, SwitchController> mControllerMap = new LinkedHashMap<>();
- private final List<Bundle> mSwitchDataList = new ArrayList<>();
+@Deprecated
+public abstract class SwitchesProvider extends EntriesProvider {
/**
* Get a list of {@link SwitchController} for this provider.
@@ -63,129 +32,7 @@
protected abstract List<SwitchController> createSwitchControllers();
@Override
- public void attachInfo(Context context, ProviderInfo info) {
- mAuthority = info.authority;
- Log.i(TAG, mAuthority);
- super.attachInfo(context, info);
- }
-
- @Override
- public boolean onCreate() {
- final List<SwitchController> controllers = createSwitchControllers();
- if (controllers == null || controllers.isEmpty()) {
- throw new IllegalArgumentException();
- }
-
- controllers.forEach(controller -> {
- final String key = controller.getSwitchKey();
- if (TextUtils.isEmpty(key)) {
- throw new NullPointerException("Switch key cannot be null: "
- + controller.getClass().getSimpleName());
- } else if (mControllerMap.containsKey(key)) {
- throw new IllegalArgumentException("Switch key " + key + " is duplicated by: "
- + controller.getClass().getSimpleName());
- }
-
- controller.setAuthority(mAuthority);
- mControllerMap.put(key, controller);
- if (!(controller instanceof PrimarySwitchController)) {
- mSwitchDataList.add(controller.getBundle());
- }
- });
- return true;
- }
-
- @Override
- public Bundle call(String method, String uriString, Bundle extras) {
- final Bundle bundle = new Bundle();
- final String key = extras != null
- ? extras.getString(META_DATA_PREFERENCE_KEYHINT)
- : null;
- if (TextUtils.isEmpty(key)) {
- if (METHOD_GET_SWITCH_DATA.equals(method)) {
- bundle.putParcelableList(EXTRA_SWITCH_DATA, mSwitchDataList);
- return bundle;
- }
- return null;
- }
-
- final SwitchController controller = mControllerMap.get(key);
- if (controller == null) {
- return null;
- }
-
- switch (method) {
- case METHOD_GET_SWITCH_DATA:
- if (!(controller instanceof PrimarySwitchController)) {
- return controller.getBundle();
- }
- break;
- case METHOD_GET_PROVIDER_ICON:
- if (controller instanceof ProviderIcon) {
- return ((ProviderIcon) controller).getProviderIcon();
- }
- break;
- case METHOD_GET_DYNAMIC_TITLE:
- if (controller instanceof DynamicTitle) {
- bundle.putString(META_DATA_PREFERENCE_TITLE,
- ((DynamicTitle) controller).getDynamicTitle());
- return bundle;
- }
- break;
- case METHOD_GET_DYNAMIC_SUMMARY:
- if (controller instanceof DynamicSummary) {
- bundle.putString(META_DATA_PREFERENCE_SUMMARY,
- ((DynamicSummary) controller).getDynamicSummary());
- return bundle;
- }
- break;
- case METHOD_IS_CHECKED:
- bundle.putBoolean(EXTRA_SWITCH_CHECKED_STATE, controller.isChecked());
- return bundle;
- case METHOD_ON_CHECKED_CHANGED:
- return onCheckedChanged(extras.getBoolean(EXTRA_SWITCH_CHECKED_STATE), controller);
- }
- return null;
- }
-
- private Bundle onCheckedChanged(boolean checked, SwitchController controller) {
- final boolean success = controller.onCheckedChanged(checked);
- final Bundle bundle = new Bundle();
- bundle.putBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR, !success);
- if (success) {
- if (controller instanceof DynamicSummary) {
- controller.notifySummaryChanged(getContext());
- }
- } else {
- bundle.putString(EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE,
- controller.getErrorMessage(checked));
- }
- return bundle;
- }
-
- @Override
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
- String sortOrder) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getType(Uri uri) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- throw new UnsupportedOperationException();
+ protected List<? extends EntryController> createEntryControllers() {
+ return createSwitchControllers();
}
}
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
index a0c8ac4..00dd8cc 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
@@ -19,6 +19,7 @@
import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_PROFILE;
import static com.android.settingslib.drawer.TileUtils.META_DATA_NEW_TASK;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_GROUP_KEY;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
@@ -29,6 +30,7 @@
import static com.android.settingslib.drawer.TileUtils.PROFILE_ALL;
import static com.android.settingslib.drawer.TileUtils.PROFILE_PRIMARY;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ComponentInfo;
@@ -47,6 +49,7 @@
import java.util.ArrayList;
import java.util.Comparator;
+import java.util.HashMap;
/**
* Description of a single dashboard tile that the user can select.
@@ -60,6 +63,8 @@
*/
public ArrayList<UserHandle> userHandle = new ArrayList<>();
+ public HashMap<UserHandle, PendingIntent> pendingIntentMap = new HashMap<>();
+
@VisibleForTesting
long mLastUpdateTime;
private final String mComponentPackage;
@@ -186,6 +191,13 @@
}
/**
+ * Check whether tile has a pending intent.
+ */
+ public boolean hasPendingIntent() {
+ return !pendingIntentMap.isEmpty();
+ }
+
+ /**
* Title of the tile that is shown to the user.
*/
public CharSequence getTitle(Context context) {
@@ -395,6 +407,76 @@
return TextUtils.equals(profile, PROFILE_PRIMARY);
}
+ /**
+ * Returns whether the tile belongs to another group / category.
+ */
+ public boolean hasGroupKey() {
+ return mMetaData != null
+ && !TextUtils.isEmpty(mMetaData.getString(META_DATA_PREFERENCE_GROUP_KEY));
+ }
+
+ /**
+ * Returns the group / category key this tile belongs to.
+ */
+ public String getGroupKey() {
+ return (mMetaData == null) ? null : mMetaData.getString(META_DATA_PREFERENCE_GROUP_KEY);
+ }
+
+ /**
+ * The type of the tile.
+ */
+ public enum Type {
+ /**
+ * A preference that can be tapped on to open a new page.
+ */
+ ACTION,
+
+ /**
+ * A preference that can be tapped on to open an external app.
+ */
+ EXTERNAL_ACTION,
+
+ /**
+ * A preference that shows an on / off switch that can be toggled by the user.
+ */
+ SWITCH,
+
+ /**
+ * A preference with both an on / off switch, and a tappable area that can perform an
+ * action.
+ */
+ SWITCH_WITH_ACTION,
+
+ /**
+ * A preference category with a title that can be used to group multiple preferences
+ * together.
+ */
+ GROUP;
+ }
+
+ /**
+ * Returns the type of the tile.
+ *
+ * @see Type
+ */
+ public Type getType() {
+ boolean hasExternalAction = hasPendingIntent();
+ boolean hasAction = hasExternalAction || this instanceof ActivityTile;
+ boolean hasSwitch = hasSwitch();
+
+ if (hasSwitch && hasAction) {
+ return Type.SWITCH_WITH_ACTION;
+ } else if (hasSwitch) {
+ return Type.SWITCH;
+ } else if (hasExternalAction) {
+ return Type.EXTERNAL_ACTION;
+ } else if (hasAction) {
+ return Type.ACTION;
+ } else {
+ return Type.GROUP;
+ }
+ }
+
public static final Comparator<Tile> TILE_COMPARATOR =
(lhs, rhs) -> rhs.getOrder() - lhs.getOrder();
}
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
index acc0087..e46db75 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
@@ -113,6 +113,12 @@
public static final String META_DATA_PREFERENCE_KEYHINT = "com.android.settings.keyhint";
/**
+ * Name of the meta-data item that can be set in the AndroidManifest.xml or in the content
+ * provider to specify the key of a group / category where this preference belongs to.
+ */
+ public static final String META_DATA_PREFERENCE_GROUP_KEY = "com.android.settings.group_key";
+
+ /**
* Order of the item that should be displayed on screen. Bigger value items displays closer on
* top.
*/
@@ -202,6 +208,13 @@
"com.android.settings.switch_uri";
/**
+ * Name of the meta-data item that can be set from the content provider providing the intent
+ * that will be executed when the user taps on the preference.
+ */
+ public static final String META_DATA_PREFERENCE_PENDING_INTENT =
+ "com.android.settings.pending_intent";
+
+ /**
* Value for {@link #META_DATA_KEY_PROFILE}. When the device has a managed profile,
* the app will always be run in the primary profile.
*
@@ -331,12 +344,12 @@
continue;
}
final ProviderInfo providerInfo = resolved.providerInfo;
- final List<Bundle> switchData = getSwitchDataFromProvider(context,
+ final List<Bundle> entryData = getEntryDataFromProvider(context,
providerInfo.authority);
- if (switchData == null || switchData.isEmpty()) {
+ if (entryData == null || entryData.isEmpty()) {
continue;
}
- for (Bundle metaData : switchData) {
+ for (Bundle metaData : entryData) {
loadTile(user, addedCache, defaultCategory, outTiles, intent, metaData,
providerInfo);
}
@@ -386,27 +399,43 @@
if (!tile.userHandle.contains(user)) {
tile.userHandle.add(user);
}
+ if (metaData.containsKey(META_DATA_PREFERENCE_PENDING_INTENT)) {
+ tile.pendingIntentMap.put(
+ user, metaData.getParcelable(META_DATA_PREFERENCE_PENDING_INTENT));
+ }
if (!outTiles.contains(tile)) {
outTiles.add(tile);
}
}
- /** Returns the switch data of the key specified from the provider */
+ /** Returns the entry data of the key specified from the provider */
// TODO(b/144732809): rearrange methods by access level modifiers
- static Bundle getSwitchDataFromProvider(Context context, String authority, String key) {
+ static Bundle getEntryDataFromProvider(Context context, String authority, String key) {
final Map<String, IContentProvider> providerMap = new ArrayMap<>();
- final Uri uri = buildUri(authority, SwitchesProvider.METHOD_GET_SWITCH_DATA, key);
- return getBundleFromUri(context, uri, providerMap, null /* bundle */);
+ final Uri uri = buildUri(authority, EntriesProvider.METHOD_GET_ENTRY_DATA, key);
+ Bundle result = getBundleFromUri(context, uri, providerMap, null /* bundle */);
+ if (result == null) {
+ Uri fallbackUri = buildUri(authority, EntriesProvider.METHOD_GET_SWITCH_DATA, key);
+ result = getBundleFromUri(context, fallbackUri, providerMap, null /* bundle */);
+ }
+ return result;
}
- /** Returns all switch data from the provider */
- private static List<Bundle> getSwitchDataFromProvider(Context context, String authority) {
+ /** Returns all entry data from the provider */
+ private static List<Bundle> getEntryDataFromProvider(Context context, String authority) {
final Map<String, IContentProvider> providerMap = new ArrayMap<>();
- final Uri uri = buildUri(authority, SwitchesProvider.METHOD_GET_SWITCH_DATA);
+ final Uri uri = buildUri(authority, EntriesProvider.METHOD_GET_ENTRY_DATA);
final Bundle result = getBundleFromUri(context, uri, providerMap, null /* bundle */);
- return result != null
- ? result.getParcelableArrayList(SwitchesProvider.EXTRA_SWITCH_DATA)
- : null;
+ if (result != null) {
+ return result.getParcelableArrayList(EntriesProvider.EXTRA_ENTRY_DATA);
+ } else {
+ Uri fallbackUri = buildUri(authority, EntriesProvider.METHOD_GET_SWITCH_DATA);
+ Bundle fallbackResult =
+ getBundleFromUri(context, fallbackUri, providerMap, null /* bundle */);
+ return fallbackResult != null
+ ? fallbackResult.getParcelableArrayList(EntriesProvider.EXTRA_SWITCH_DATA)
+ : null;
+ }
}
/**
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index 058065e..bac6306 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -519,12 +519,9 @@
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Daugiau laiko."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Mažiau laiko."</string>
<string name="cancel" msgid="5665114069455378395">"Atšaukti"</string>
- <!-- no translation found for next (2699398661093607009) -->
- <skip />
- <!-- no translation found for back (5554327870352703710) -->
- <skip />
- <!-- no translation found for save (3745809743277153149) -->
- <skip />
+ <string name="next" msgid="2699398661093607009">"Kitas"</string>
+ <string name="back" msgid="5554327870352703710">"Atgal"</string>
+ <string name="save" msgid="3745809743277153149">"Išsaugoti"</string>
<string name="okay" msgid="949938843324579502">"Gerai"</string>
<string name="done" msgid="381184316122520313">"Atlikta"</string>
<string name="alarms_and_reminders_label" msgid="6918395649731424294">"Signalai ir priminimai"</string>
@@ -579,12 +576,9 @@
<string name="user_add_user_title" msgid="5457079143694924885">"Pridėti naują naudotoją?"</string>
<string name="user_add_user_message_long" msgid="1527434966294733380">"Galite bendrinti šį įrenginį su kitais žmonėmis sukūrę papildomų naudotojų. Kiekvienam naudotojui suteikiama atskira erdvė, kurią jie gali tinkinti naudodami programas, ekrano foną ir kt. Be to, naudotojai gali koreguoti įrenginio nustatymus, pvz., „Wi‑Fi“, kurie taikomi visiems.\n\nKai pridedate naują naudotoją, šis asmuo turi nusistatyti savo erdvę.\n\nBet kuris naudotojas gali atnaujinti visų kitų naudotojų programas. Pasiekiamumo nustatymai ir paslaugos gali nebūti perkeltos naujam naudotojui."</string>
<string name="user_add_user_message_short" msgid="3295959985795716166">"Kai pridedate naują naudotoją, šis asmuo turi nustatyti savo vietą.\n\nBet kuris naudotojas gali atnaujinti visų kitų naudotojų programas."</string>
- <!-- no translation found for user_grant_admin_title (5157031020083343984) -->
- <skip />
- <!-- no translation found for user_grant_admin_message (1673791931033486709) -->
- <skip />
- <!-- no translation found for user_grant_admin_button (5441486731331725756) -->
- <skip />
+ <string name="user_grant_admin_title" msgid="5157031020083343984">"Nustatyti šį naudotoją kaip administratorių?"</string>
+ <string name="user_grant_admin_message" msgid="1673791931033486709">"Administratoriai turi specialių privilegijų, kurių kiti naudotojai neturi. Administratorius gali tvarkyti visus naudotojus, atnaujinti ar iš naujo nustatyti šį įrenginį, keisti nustatymus, peržiūrėti visas įdiegtas programas ir suteikti administratoriaus privilegijas kitiems naudotojams arba jas panaikinti."</string>
+ <string name="user_grant_admin_button" msgid="5441486731331725756">"Nustatyti kaip administratorių"</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"Nustatyti naudotoją dabar?"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"Įsitikinkite, kad asmuo gali paimti įrenginį ir nustatyti savo vietą"</string>
<string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"Nustatyti profilį dabar?"</string>
@@ -616,10 +610,8 @@
<string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Bus pradėta nauja svečio sesija ir iš esamos sesijos bus ištrintos visos programos ir duomenys"</string>
<string name="guest_exit_dialog_title" msgid="1846494656849381804">"Išeiti iš svečio režimo?"</string>
<string name="guest_exit_dialog_message" msgid="1743218864242719783">"Bus ištrintos esamos svečio sesijos programos ir duomenys"</string>
- <!-- no translation found for grant_admin (4323199171790522574) -->
- <skip />
- <!-- no translation found for not_grant_admin (3557849576157702485) -->
- <skip />
+ <string name="grant_admin" msgid="4323199171790522574">"Taip, nustatyti kaip administratorių"</string>
+ <string name="not_grant_admin" msgid="3557849576157702485">"Ne, nenustatyti kaip administratoriaus"</string>
<string name="guest_exit_dialog_button" msgid="1736401897067442044">"Išeiti"</string>
<string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Išsaugoti svečio veiklą?"</string>
<string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Galite išsaugoti esamos sesijos veiklą arba ištrinti visas programas ir duomenis"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 7cf8968..052840d 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -519,12 +519,9 @@
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"增加时间。"</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"减少时间。"</string>
<string name="cancel" msgid="5665114069455378395">"取消"</string>
- <!-- no translation found for next (2699398661093607009) -->
- <skip />
- <!-- no translation found for back (5554327870352703710) -->
- <skip />
- <!-- no translation found for save (3745809743277153149) -->
- <skip />
+ <string name="next" msgid="2699398661093607009">"继续"</string>
+ <string name="back" msgid="5554327870352703710">"返回"</string>
+ <string name="save" msgid="3745809743277153149">"保存"</string>
<string name="okay" msgid="949938843324579502">"确定"</string>
<string name="done" msgid="381184316122520313">"完成"</string>
<string name="alarms_and_reminders_label" msgid="6918395649731424294">"闹钟和提醒"</string>
@@ -579,12 +576,9 @@
<string name="user_add_user_title" msgid="5457079143694924885">"要添加新用户吗?"</string>
<string name="user_add_user_message_long" msgid="1527434966294733380">"创建新用户后,您就能够与其他人共用此设备。每位用户都有自己的专属空间,而且在自己的个人空间内还可以自行安装自己想要的应用、设置壁纸等。此外,用户还可以调整会影响所有用户的设备设置(例如 WLAN 设置)。\n\n当您添加新用户后,该用户需要自行设置个人空间。\n\n任何用户都可以为所有其他用户更新应用。无障碍功能设置和服务可能无法转移给新用户。"</string>
<string name="user_add_user_message_short" msgid="3295959985795716166">"当您添加新用户后,该用户需要自行设置个人空间。\n\n任何用户都可以为所有其他用户更新应用。"</string>
- <!-- no translation found for user_grant_admin_title (5157031020083343984) -->
- <skip />
- <!-- no translation found for user_grant_admin_message (1673791931033486709) -->
- <skip />
- <!-- no translation found for user_grant_admin_button (5441486731331725756) -->
- <skip />
+ <string name="user_grant_admin_title" msgid="5157031020083343984">"将此用户设为管理员?"</string>
+ <string name="user_grant_admin_message" msgid="1673791931033486709">"管理员拥有其他用户没有的特殊权限。管理员可以管理所有用户、更新或重置此设备、修改设置、查看所有已安装的应用,以及授予或撤消其他用户的管理员权限。"</string>
+ <string name="user_grant_admin_button" msgid="5441486731331725756">"设为管理员"</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"要现在设置该用户吗?"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"请让相应用户操作设备并设置他们自己的空间。"</string>
<string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"要立即设置个人资料吗?"</string>
@@ -616,10 +610,8 @@
<string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"此操作会开始新的访客会话,并删除当前会话中的所有应用和数据"</string>
<string name="guest_exit_dialog_title" msgid="1846494656849381804">"要退出访客模式吗?"</string>
<string name="guest_exit_dialog_message" msgid="1743218864242719783">"此操作会删除当前访客会话中的所有应用和数据"</string>
- <!-- no translation found for grant_admin (4323199171790522574) -->
- <skip />
- <!-- no translation found for not_grant_admin (3557849576157702485) -->
- <skip />
+ <string name="grant_admin" msgid="4323199171790522574">"是,将其设为管理员"</string>
+ <string name="not_grant_admin" msgid="3557849576157702485">"不,不要将其设为管理员"</string>
<string name="guest_exit_dialog_button" msgid="1736401897067442044">"退出"</string>
<string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"要保存访客活动记录吗?"</string>
<string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"您可以保存当前会话中的活动记录,也可以删除所有应用和数据"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 2e6bb53..f522fd1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -583,7 +583,7 @@
*/
public void setName(String name) {
// Prevent getName() to be set to null if setName(null) is called
- if (name == null || TextUtils.equals(name, getName())) {
+ if (TextUtils.isEmpty(name) || TextUtils.equals(name, getName())) {
return;
}
mDevice.setAlias(name);
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
index 09abc39..9ee8a32 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
@@ -209,8 +209,7 @@
}
final ComponentName cn = intent.getComponent();
final String key = cn != null ? cn.flattenToString() : intent.getAction();
- return logSettingsTileClick(key + (isWorkProfile ? "/work" : "/personal"),
- sourceMetricsCategory);
+ return logSettingsTileClickWithProfile(key, sourceMetricsCategory, isWorkProfile);
}
/**
@@ -226,4 +225,20 @@
clicked(sourceMetricsCategory, logKey);
return true;
}
+
+ /**
+ * Logs an event when the setting key is clicked with a specific profile from Profile select
+ * dialog.
+ *
+ * @return true if the key is loggable, otherwise false
+ */
+ public boolean logSettingsTileClickWithProfile(String logKey, int sourceMetricsCategory,
+ boolean isWorkProfile) {
+ if (TextUtils.isEmpty(logKey)) {
+ // Not loggable
+ return false;
+ }
+ clicked(sourceMetricsCategory, logKey + (isWorkProfile ? "/work" : "/personal"));
+ return true;
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 6444f3b..4b61ff1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -1015,6 +1015,13 @@
}
@Test
+ public void setName_setDeviceNameIsEmpty() {
+ mCachedDevice.setName("");
+
+ verify(mDevice, never()).setAlias(any());
+ }
+
+ @Test
public void getProfileConnectionState_nullProfile_returnDisconnected() {
assertThat(mCachedDevice.getProfileConnectionState(null)).isEqualTo(
BluetoothProfile.STATE_DISCONNECTED);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
index 3352d86..dd8d54a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
@@ -203,4 +203,24 @@
assertThat(loggable).isFalse();
verifyNoMoreInteractions(mLogWriter);
}
+
+ @Test
+ public void logSettingsTileClickWithProfile_isPersonalProfile_shouldTagPersonal() {
+ final String key = "abc";
+ final boolean loggable = mProvider.logSettingsTileClickWithProfile(key,
+ MetricsEvent.SETTINGS_GESTURES, false);
+
+ assertThat(loggable).isTrue();
+ verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, "abc/personal");
+ }
+
+ @Test
+ public void logSettingsTileClickWithProfile_isWorkProfile_shouldTagWork() {
+ final String key = "abc";
+ final boolean loggable = mProvider.logSettingsTileClickWithProfile(key,
+ MetricsEvent.SETTINGS_GESTURES, true);
+
+ assertThat(loggable).isTrue();
+ verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, "abc/work");
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java
index aa6b0bf..4d2b1ae 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java
@@ -17,19 +17,23 @@
import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_PROFILE;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_GROUP_KEY;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI;
import static com.android.settingslib.drawer.TileUtils.PROFILE_ALL;
import static com.android.settingslib.drawer.TileUtils.PROFILE_PRIMARY;
import static com.google.common.truth.Truth.assertThat;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
+import android.os.UserHandle;
import org.junit.Before;
import org.junit.Test;
@@ -191,4 +195,65 @@
assertThat(tile.getTitle(RuntimeEnvironment.application)).isNull();
}
+
+ @Test
+ public void hasPendingIntent_empty_returnsFalse() {
+ final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+ assertThat(tile.hasPendingIntent()).isFalse();
+ }
+
+ @Test
+ public void hasPendingIntent_notEmpty_returnsTrue() {
+ final Tile tile = new ActivityTile(mActivityInfo, "category");
+ tile.pendingIntentMap.put(
+ UserHandle.CURRENT, PendingIntent.getActivity(mContext, 0, new Intent(), 0));
+
+ assertThat(tile.hasPendingIntent()).isTrue();
+ }
+
+ @Test
+ public void hasGroupKey_empty_returnsFalse() {
+ final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+ assertThat(tile.hasGroupKey()).isFalse();
+ }
+
+ @Test
+ public void hasGroupKey_notEmpty_returnsTrue() {
+ mActivityInfo.metaData.putString(META_DATA_PREFERENCE_GROUP_KEY, "test_key");
+ final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+ assertThat(tile.hasGroupKey()).isTrue();
+ }
+
+ @Test
+ public void getGroupKey_empty_returnsNull() {
+ final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+ assertThat(tile.getGroupKey()).isNull();
+ }
+
+ @Test
+ public void getGroupKey_notEmpty_returnsValue() {
+ mActivityInfo.metaData.putString(META_DATA_PREFERENCE_GROUP_KEY, "test_key");
+ final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+ assertThat(tile.getGroupKey()).isEqualTo("test_key");
+ }
+
+ @Test
+ public void getType_withoutSwitch_returnsAction() {
+ final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+ assertThat(tile.getType()).isEqualTo(Tile.Type.ACTION);
+ }
+
+ @Test
+ public void getType_withSwitch_returnsSwitchWithAction() {
+ mActivityInfo.metaData.putString(META_DATA_PREFERENCE_SWITCH_URI, "test://testabc/");
+ final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+ assertThat(tile.getType()).isEqualTo(Tile.Type.SWITCH_WITH_ACTION);
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/EntriesProviderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/EntriesProviderTest.java
new file mode 100644
index 0000000..a248330
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/EntriesProviderTest.java
@@ -0,0 +1,472 @@
+/*
+ * Copyright (C) 2023 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.settingslib.drawer;
+
+import static com.android.settingslib.drawer.EntriesProvider.EXTRA_ENTRY_DATA;
+import static com.android.settingslib.drawer.EntriesProvider.EXTRA_SWITCH_CHECKED_STATE;
+import static com.android.settingslib.drawer.EntriesProvider.EXTRA_SWITCH_DATA;
+import static com.android.settingslib.drawer.EntriesProvider.EXTRA_SWITCH_SET_CHECKED_ERROR;
+import static com.android.settingslib.drawer.EntriesProvider.EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_GET_DYNAMIC_SUMMARY;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_GET_DYNAMIC_TITLE;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_GET_ENTRY_DATA;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_GET_PROVIDER_ICON;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_GET_SWITCH_DATA;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_IS_CHECKED;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_ON_CHECKED_CHANGED;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_PENDING_INTENT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ProviderInfo;
+import android.os.Bundle;
+
+import com.android.settingslib.drawer.EntryController.MetaData;
+import com.android.settingslib.drawer.PrimarySwitchControllerTest.TestPrimarySwitchController;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public class EntriesProviderTest {
+
+ @Rule
+ public final ExpectedException thrown = ExpectedException.none();
+
+ private Context mContext;
+ private ProviderInfo mProviderInfo;
+
+ private TestEntriesProvider mEntriesProvider;
+
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application;
+ mEntriesProvider = new TestEntriesProvider();
+ mProviderInfo = new ProviderInfo();
+ mProviderInfo.authority = "auth";
+ }
+
+ @Test
+ public void attachInfo_noController_shouldThrowIllegalArgumentException() {
+ thrown.expect(IllegalArgumentException.class);
+
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+ }
+
+ @Test
+ public void attachInfo_NoKeyInController_shouldThrowNullPointerException() {
+ thrown.expect(NullPointerException.class);
+ final TestEntryController controller = new TestEntryController();
+ mEntriesProvider.addController(controller);
+
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+ }
+
+ @Test
+ public void attachInfo_NoMetaDataInController_shouldThrowNullPointerException() {
+ thrown.expect(NullPointerException.class);
+ final TestEntryController controller = new TestEntryController();
+ controller.setKey("123");
+ mEntriesProvider.addController(controller);
+
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+ }
+
+ @Test
+ public void attachInfo_duplicateKey_shouldThrowIllegalArgumentException() {
+ thrown.expect(IllegalArgumentException.class);
+ final TestEntryController controller1 = new TestEntryController();
+ final TestEntryController controller2 = new TestEntryController();
+ controller1.setKey("123");
+ controller2.setKey("123");
+ controller1.setMetaData(new MetaData("category"));
+ controller2.setMetaData(new MetaData("category"));
+ mEntriesProvider.addController(controller1);
+ mEntriesProvider.addController(controller2);
+
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+ }
+
+ @Test
+ public void attachInfo_hasDifferentControllers_shouldNotThrowException() {
+ final TestEntryController controller1 = new TestEntryController();
+ final TestEntryController controller2 = new TestEntryController();
+ controller1.setKey("123");
+ controller2.setKey("456");
+ controller1.setMetaData(new MetaData("category"));
+ controller2.setMetaData(new MetaData("category"));
+ mEntriesProvider.addController(controller1);
+ mEntriesProvider.addController(controller2);
+
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+ }
+
+ @Test
+ public void getEntryData_shouldNotReturnPrimarySwitchData() {
+ final EntryController controller = new TestPrimarySwitchController("123");
+ mEntriesProvider.addController(controller);
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+ final Bundle switchData = mEntriesProvider.call(METHOD_GET_ENTRY_DATA, "uri",
+ null /* extras*/);
+
+ final ArrayList<Bundle> dataList = switchData.getParcelableArrayList(EXTRA_ENTRY_DATA);
+ assertThat(dataList).isEmpty();
+ }
+
+ @Test
+ public void getEntryData_shouldReturnDataList() {
+ final TestEntryController controller = new TestEntryController();
+ final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ controller.setKey("123");
+ controller.setMetaData(new MetaData("category").setPendingIntent(pendingIntent));
+ mEntriesProvider.addController(controller);
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+ final Bundle entryData = mEntriesProvider.call(METHOD_GET_ENTRY_DATA, "uri",
+ null /* extras*/);
+
+ final ArrayList<Bundle> dataList = entryData.getParcelableArrayList(EXTRA_ENTRY_DATA);
+ assertThat(dataList).hasSize(1);
+ assertThat(dataList.get(0).getString(META_DATA_PREFERENCE_KEYHINT)).isEqualTo("123");
+ assertThat(dataList.get(0).getParcelable(META_DATA_PREFERENCE_PENDING_INTENT,
+ PendingIntent.class))
+ .isEqualTo(pendingIntent);
+ }
+
+ @Test
+ public void getSwitchData_shouldReturnDataList() {
+ final TestEntryController controller = new TestEntryController();
+ final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ controller.setKey("123");
+ controller.setMetaData(new MetaData("category").setPendingIntent(pendingIntent));
+ mEntriesProvider.addController(controller);
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+ final Bundle entryData = mEntriesProvider.call(METHOD_GET_SWITCH_DATA, "uri",
+ null /* extras*/);
+
+ final ArrayList<Bundle> dataList = entryData.getParcelableArrayList(EXTRA_SWITCH_DATA);
+ assertThat(dataList).hasSize(1);
+ assertThat(dataList.get(0).getString(META_DATA_PREFERENCE_KEYHINT)).isEqualTo("123");
+ assertThat(dataList.get(0).getParcelable(META_DATA_PREFERENCE_PENDING_INTENT,
+ PendingIntent.class))
+ .isEqualTo(pendingIntent);
+ }
+
+ @Test
+ public void getEntryDataByKey_shouldReturnData() {
+ final Bundle extras = new Bundle();
+ extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+ final TestEntryController controller = new TestEntryController();
+ controller.setKey("123");
+ controller.setMetaData(new MetaData("category"));
+ mEntriesProvider.addController(controller);
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+ final Bundle entryData = mEntriesProvider.call(METHOD_GET_ENTRY_DATA, "uri", extras);
+
+ assertThat(entryData.getString(META_DATA_PREFERENCE_KEYHINT)).isEqualTo("123");
+ }
+
+ @Test
+ public void getSwitchDataByKey_shouldReturnData() {
+ final Bundle extras = new Bundle();
+ extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+ final TestEntryController controller = new TestEntryController();
+ controller.setKey("123");
+ controller.setMetaData(new MetaData("category"));
+ mEntriesProvider.addController(controller);
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+ final Bundle entryData = mEntriesProvider.call(METHOD_GET_SWITCH_DATA, "uri", extras);
+
+ assertThat(entryData.getString(META_DATA_PREFERENCE_KEYHINT)).isEqualTo("123");
+ }
+
+ @Test
+ public void isSwitchChecked_shouldReturnCheckedState() {
+ final Bundle extras = new Bundle();
+ extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+ final TestSwitchController controller = new TestSwitchController();
+ controller.setKey("123");
+ controller.setMetaData(new MetaData("category"));
+ mEntriesProvider.addController(controller);
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+ controller.setSwitchChecked(true);
+ Bundle result = mEntriesProvider.call(METHOD_IS_CHECKED, "uri", extras);
+
+ assertThat(result.getBoolean(EXTRA_SWITCH_CHECKED_STATE)).isTrue();
+
+ controller.setSwitchChecked(false);
+ result = mEntriesProvider.call(METHOD_IS_CHECKED, "uri", extras);
+
+ assertThat(result.getBoolean(EXTRA_SWITCH_CHECKED_STATE)).isFalse();
+ }
+
+ @Test
+ public void getProviderIcon_noImplementInterface_shouldReturnNull() {
+ final Bundle extras = new Bundle();
+ extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+ final TestEntryController controller = new TestEntryController();
+ controller.setKey("123");
+ controller.setMetaData(new MetaData("category"));
+ mEntriesProvider.addController(controller);
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+ final Bundle iconBundle = mEntriesProvider.call(METHOD_GET_PROVIDER_ICON, "uri", extras);
+
+ assertThat(iconBundle).isNull();
+ }
+
+ @Test
+ public void getProviderIcon_implementInterface_shouldReturnIcon() {
+ final Bundle extras = new Bundle();
+ extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+ final TestEntryController controller = new TestDynamicController();
+ controller.setKey("123");
+ controller.setMetaData(new MetaData("category"));
+ mEntriesProvider.addController(controller);
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+ final Bundle iconBundle = mEntriesProvider.call(METHOD_GET_PROVIDER_ICON, "uri", extras);
+
+ assertThat(iconBundle).isEqualTo(TestDynamicController.ICON_BUNDLE);
+ }
+
+ @Test
+ public void getDynamicTitle_noImplementInterface_shouldReturnNull() {
+ final Bundle extras = new Bundle();
+ extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+ final TestEntryController controller = new TestEntryController();
+ controller.setKey("123");
+ controller.setMetaData(new MetaData("category"));
+ mEntriesProvider.addController(controller);
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+ final Bundle result = mEntriesProvider.call(METHOD_GET_DYNAMIC_TITLE, "uri", extras);
+
+ assertThat(result).isNull();
+ }
+
+ @Test
+ public void getDynamicTitle_implementInterface_shouldReturnTitle() {
+ final Bundle extras = new Bundle();
+ extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+ final TestEntryController controller = new TestDynamicController();
+ controller.setKey("123");
+ controller.setMetaData(new MetaData("category"));
+ mEntriesProvider.addController(controller);
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+ final Bundle result = mEntriesProvider.call(METHOD_GET_DYNAMIC_TITLE, "uri", extras);
+
+ assertThat(result.getString(META_DATA_PREFERENCE_TITLE))
+ .isEqualTo(TestDynamicController.TITLE);
+ }
+
+ @Test
+ public void getDynamicSummary_noImplementInterface_shouldReturnNull() {
+ final Bundle extras = new Bundle();
+ extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+ final TestEntryController controller = new TestEntryController();
+ controller.setKey("123");
+ controller.setMetaData(new MetaData("category"));
+ mEntriesProvider.addController(controller);
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+ final Bundle result = mEntriesProvider.call(METHOD_GET_DYNAMIC_SUMMARY, "uri", extras);
+
+ assertThat(result).isNull();
+ }
+
+ @Test
+ public void getDynamicSummary_implementInterface_shouldReturnSummary() {
+ final Bundle extras = new Bundle();
+ extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+ final TestEntryController controller = new TestDynamicController();
+ controller.setKey("123");
+ controller.setMetaData(new MetaData("category"));
+ mEntriesProvider.addController(controller);
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+ final Bundle result = mEntriesProvider.call(METHOD_GET_DYNAMIC_SUMMARY, "uri", extras);
+
+ assertThat(result.getString(META_DATA_PREFERENCE_SUMMARY))
+ .isEqualTo(TestDynamicController.SUMMARY);
+ }
+
+ @Test
+ public void onSwitchCheckedChangedSuccess_shouldReturnNoError() {
+ final Bundle extras = new Bundle();
+ extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+ final TestSwitchController controller = new TestSwitchController();
+ controller.setKey("123");
+ controller.setMetaData(new MetaData("category"));
+ mEntriesProvider.addController(controller);
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+ final Bundle result = mEntriesProvider.call(METHOD_ON_CHECKED_CHANGED, "uri", extras);
+
+ assertThat(result.getBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR)).isFalse();
+ }
+
+ @Test
+ public void onSwitchCheckedChangedFailed_shouldReturnErrorMessage() {
+ final Bundle extras = new Bundle();
+ extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+ final TestSwitchController controller = new TestSwitchController();
+ controller.setKey("123");
+ controller.setMetaData(new MetaData("category"));
+ controller.setSwitchErrorMessage("error");
+ mEntriesProvider.addController(controller);
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+ final Bundle result = mEntriesProvider.call(METHOD_ON_CHECKED_CHANGED, "uri", extras);
+
+ assertThat(result.getBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR)).isTrue();
+ assertThat(result.getString(EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE)).isEqualTo("error");
+ }
+
+ private static class TestEntriesProvider extends EntriesProvider {
+
+ private List<EntryController> mControllers;
+
+ @Override
+ protected List<EntryController> createEntryControllers() {
+ return mControllers;
+ }
+
+ void addController(EntryController controller) {
+ if (mControllers == null) {
+ mControllers = new ArrayList<>();
+ }
+ mControllers.add(controller);
+ }
+ }
+
+ private static class TestEntryController extends EntryController {
+
+ private String mKey;
+ private MetaData mMetaData;
+
+ @Override
+ public String getKey() {
+ return mKey;
+ }
+
+ @Override
+ protected MetaData getMetaData() {
+ return mMetaData;
+ }
+
+ void setKey(String key) {
+ mKey = key;
+ }
+
+ void setMetaData(MetaData metaData) {
+ mMetaData = metaData;
+ }
+ }
+
+ private static class TestSwitchController extends EntryController implements ProviderSwitch {
+
+ private String mKey;
+ private MetaData mMetaData;
+ private boolean mChecked;
+ private String mErrorMsg;
+
+ @Override
+ public String getKey() {
+ return mKey;
+ }
+
+ @Override
+ protected MetaData getMetaData() {
+ return mMetaData;
+ }
+
+ @Override
+ public boolean isSwitchChecked() {
+ return mChecked;
+ }
+
+ @Override
+ public boolean onSwitchCheckedChanged(boolean checked) {
+ return mErrorMsg == null ? true : false;
+ }
+
+ @Override
+ public String getSwitchErrorMessage(boolean attemptedChecked) {
+ return mErrorMsg;
+ }
+
+ void setKey(String key) {
+ mKey = key;
+ }
+
+ void setMetaData(MetaData metaData) {
+ mMetaData = metaData;
+ }
+
+ void setSwitchChecked(boolean checked) {
+ mChecked = checked;
+ }
+
+ void setSwitchErrorMessage(String errorMsg) {
+ mErrorMsg = errorMsg;
+ }
+ }
+
+ private static class TestDynamicController extends TestEntryController
+ implements ProviderIcon, DynamicTitle, DynamicSummary {
+
+ static final String TITLE = "title";
+ static final String SUMMARY = "summary";
+ static final Bundle ICON_BUNDLE = new Bundle();
+
+ @Override
+ public Bundle getProviderIcon() {
+ return ICON_BUNDLE;
+ }
+
+ @Override
+ public String getDynamicTitle() {
+ return TITLE;
+ }
+
+ @Override
+ public String getDynamicSummary() {
+ return SUMMARY;
+ }
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java
index abfb407..80f9efb 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java
@@ -17,20 +17,24 @@
import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_PROFILE;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_GROUP_KEY;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
import static com.android.settingslib.drawer.TileUtils.PROFILE_ALL;
import static com.android.settingslib.drawer.TileUtils.PROFILE_PRIMARY;
import static com.google.common.truth.Truth.assertThat;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
+import android.os.UserHandle;
import org.junit.Before;
import org.junit.Rule;
@@ -173,13 +177,93 @@
assertThat(tile.mLastUpdateTime).isNotEqualTo(staleTimeStamp);
}
+ @Test
+ public void hasPendingIntent_empty_returnsFalse() {
+ final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+ assertThat(tile.hasPendingIntent()).isFalse();
+ }
+
+ @Test
+ public void hasPendingIntent_notEmpty_returnsTrue() {
+ final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+ tile.pendingIntentMap.put(
+ UserHandle.CURRENT, PendingIntent.getActivity(mContext, 0, new Intent(), 0));
+
+ assertThat(tile.hasPendingIntent()).isTrue();
+ }
+
+ @Test
+ public void hasGroupKey_empty_returnsFalse() {
+ final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+ assertThat(tile.hasGroupKey()).isFalse();
+ }
+
+ @Test
+ public void hasGroupKey_notEmpty_returnsTrue() {
+ mMetaData.putString(META_DATA_PREFERENCE_GROUP_KEY, "test_key");
+ final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+ assertThat(tile.hasGroupKey()).isTrue();
+ }
+
+ @Test
+ public void getGroupKey_empty_returnsNull() {
+ final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+ assertThat(tile.getGroupKey()).isNull();
+ }
+
+ @Test
+ public void getGroupKey_notEmpty_returnsValue() {
+ mMetaData.putString(META_DATA_PREFERENCE_GROUP_KEY, "test_key");
+ final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+ assertThat(tile.getGroupKey()).isEqualTo("test_key");
+ }
+
+ @Test
+ public void getType_withSwitch_returnsSwitch() {
+ mMetaData.putString(META_DATA_PREFERENCE_SWITCH_URI, "test://testabc/");
+ final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+ assertThat(tile.getType()).isEqualTo(Tile.Type.SWITCH);
+ }
+
+ @Test
+ public void getType_withSwitchAndPendingIntent_returnsSwitchWithAction() {
+ mMetaData.putString(META_DATA_PREFERENCE_SWITCH_URI, "test://testabc/");
+ final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+ tile.pendingIntentMap.put(
+ UserHandle.CURRENT, PendingIntent.getActivity(mContext, 0, new Intent(), 0));
+
+ assertThat(tile.getType()).isEqualTo(Tile.Type.SWITCH_WITH_ACTION);
+ }
+
+ @Test
+ public void getType_withPendingIntent_returnsExternalAction() {
+ final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+ tile.pendingIntentMap.put(
+ UserHandle.CURRENT, PendingIntent.getActivity(mContext, 0, new Intent(), 0));
+
+ assertThat(tile.getType()).isEqualTo(Tile.Type.EXTERNAL_ACTION);
+ }
+
+ @Test
+ public void getType_withoutSwitchAndPendingIntent_returnsGroup() {
+ final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+ assertThat(tile.getType()).isEqualTo(Tile.Type.GROUP);
+ }
+
@Implements(TileUtils.class)
private static class ShadowTileUtils {
private static Bundle sMetaData;
@Implementation
- protected static Bundle getSwitchDataFromProvider(Context context, String authority,
+ protected static Bundle getEntryDataFromProvider(Context context, String authority,
String key) {
return sMetaData;
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
index 906e06e..2086466 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -21,6 +21,7 @@
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_PENDING_INTENT;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY_URI;
import static com.android.settingslib.drawer.TileUtils.PROFILE_ALL;
@@ -40,6 +41,7 @@
import static org.robolectric.RuntimeEnvironment.application;
import android.app.ActivityManager;
+import android.app.PendingIntent;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -350,6 +352,53 @@
assertThat(outTiles).isEmpty();
}
+ @Test
+ public void loadTilesForAction_multipleUserProfiles_updatesUserHandle() {
+ Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
+ List<Tile> outTiles = new ArrayList<>();
+ List<ResolveInfo> info = new ArrayList<>();
+ ResolveInfo resolveInfo = newInfo(true, null /* category */, null, URI_GET_ICON,
+ URI_GET_SUMMARY, null, 123, PROFILE_ALL);
+ info.add(resolveInfo);
+
+ when(mPackageManager.queryIntentActivitiesAsUser(any(Intent.class), anyInt(), anyInt()))
+ .thenReturn(info);
+
+ TileUtils.loadTilesForAction(mContext, UserHandle.CURRENT, IA_SETTINGS_ACTION,
+ addedCache, null /* defaultCategory */, outTiles, false /* requiresSettings */);
+ TileUtils.loadTilesForAction(mContext, new UserHandle(10), IA_SETTINGS_ACTION,
+ addedCache, null /* defaultCategory */, outTiles, false /* requiresSettings */);
+
+ assertThat(outTiles).hasSize(1);
+ assertThat(outTiles.get(0).userHandle)
+ .containsExactly(UserHandle.CURRENT, new UserHandle(10));
+ }
+
+ @Test
+ public void loadTilesForAction_withPendingIntent_updatesPendingIntentMap() {
+ Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
+ List<Tile> outTiles = new ArrayList<>();
+ List<ResolveInfo> info = new ArrayList<>();
+ ResolveInfo resolveInfo = newInfo(true, null /* category */, null, URI_GET_ICON,
+ URI_GET_SUMMARY, null, 123, PROFILE_ALL);
+ PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ resolveInfo.activityInfo.metaData
+ .putParcelable(META_DATA_PREFERENCE_PENDING_INTENT, pendingIntent);
+ info.add(resolveInfo);
+
+ when(mPackageManager.queryIntentActivitiesAsUser(any(Intent.class), anyInt(), anyInt()))
+ .thenReturn(info);
+
+ TileUtils.loadTilesForAction(mContext, UserHandle.CURRENT, IA_SETTINGS_ACTION,
+ addedCache, null /* defaultCategory */, outTiles, false /* requiresSettings */);
+ TileUtils.loadTilesForAction(mContext, new UserHandle(10), IA_SETTINGS_ACTION,
+ addedCache, null /* defaultCategory */, outTiles, false /* requiresSettings */);
+
+ assertThat(outTiles).hasSize(1);
+ assertThat(outTiles.get(0).pendingIntentMap).containsExactly(
+ UserHandle.CURRENT, pendingIntent, new UserHandle(10), pendingIntent);
+ }
+
public static ResolveInfo newInfo(boolean systemApp, String category) {
return newInfo(systemApp, category, null);
}
@@ -424,7 +473,7 @@
private static Bundle sMetaData;
@Implementation
- protected static List<Bundle> getSwitchDataFromProvider(Context context, String authority) {
+ protected static List<Bundle> getEntryDataFromProvider(Context context, String authority) {
return Arrays.asList(sMetaData);
}
diff --git a/packages/SystemUI/res/layout/biometric_prompt_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_layout.xml
index 05ff1b1..8eb62ec 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_layout.xml
@@ -39,6 +39,7 @@
android:singleLine="true"
android:marqueeRepeatLimit="1"
android:ellipsize="marquee"
+ android:importantForAccessibility="no"
style="@style/TextAppearance.AuthCredential.Subtitle"/>
<TextView
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index cd8f04d..ed4b91c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -737,7 +737,7 @@
});
mUseCredentialButton.setOnClickListener((view) -> {
- startTransitionToCredentialUI();
+ startTransitionToCredentialUI(false /* isError */);
});
mConfirmButton.setOnClickListener((view) -> {
@@ -768,9 +768,12 @@
/**
* Kicks off the animation process and invokes the callback.
+ *
+ * @param isError if this was triggered due to an error and not a user action (unused,
+ * previously for haptics).
*/
@Override
- public void startTransitionToCredentialUI() {
+ public void startTransitionToCredentialUI(boolean isError) {
updateSize(AuthDialog.SIZE_LARGE);
mCallback.onAction(Callback.ACTION_USE_DEVICE_CREDENTIAL);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricViewAdapter.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricViewAdapter.kt
index 631511c..68db564 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricViewAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricViewAdapter.kt
@@ -38,7 +38,7 @@
fun onHelp(@BiometricAuthenticator.Modality modality: Int, help: String)
- fun startTransitionToCredentialUI()
+ fun startTransitionToCredentialUI(isError: Boolean)
fun requestLayout()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 7f70685..7a2f244 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -374,7 +374,6 @@
if (Utils.isBiometricAllowed(config.mPromptInfo)) {
mPromptSelectorInteractorProvider.get().useBiometricsForAuthentication(
config.mPromptInfo,
- config.mRequireConfirmation,
config.mUserId,
config.mOperationId,
new BiometricModalities(fpProps, faceProps));
@@ -802,9 +801,9 @@
}
@Override
- public void animateToCredentialUI() {
+ public void animateToCredentialUI(boolean isError) {
if (mBiometricView != null) {
- mBiometricView.startTransitionToCredentialUI();
+ mBiometricView.startTransitionToCredentialUI(isError);
} else {
Log.e(TAG, "animateToCredentialUI(): mBiometricView is null");
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 57f1928..96b1447 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -85,7 +85,6 @@
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.data.repository.BiometricType;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
@@ -185,18 +184,6 @@
private final @Background DelayableExecutor mBackgroundExecutor;
private final DisplayInfo mCachedDisplayInfo = new DisplayInfo();
- private final VibratorHelper mVibratorHelper;
-
- private void vibrateSuccess(int modality) {
- mVibratorHelper.vibrateAuthSuccess(
- getClass().getSimpleName() + ", modality = " + modality + "BP::success");
- }
-
- private void vibrateError(int modality) {
- mVibratorHelper.vibrateAuthError(
- getClass().getSimpleName() + ", modality = " + modality + "BP::error");
- }
-
@VisibleForTesting
final TaskStackListener mTaskStackListener = new TaskStackListener() {
@Override
@@ -776,7 +763,6 @@
@NonNull InteractionJankMonitor jankMonitor,
@Main Handler handler,
@Background DelayableExecutor bgExecutor,
- @NonNull VibratorHelper vibrator,
@NonNull UdfpsUtils udfpsUtils) {
mContext = context;
mFeatureFlags = featureFlags;
@@ -798,7 +784,6 @@
mUdfpsEnrolledForUser = new SparseBooleanArray();
mSfpsEnrolledForUser = new SparseBooleanArray();
mFaceEnrolledForUser = new SparseBooleanArray();
- mVibratorHelper = vibrator;
mUdfpsUtils = udfpsUtils;
mApplicationCoroutineScope = applicationCoroutineScope;
@@ -987,8 +972,6 @@
public void onBiometricAuthenticated(@Modality int modality) {
if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: ");
- vibrateSuccess(modality);
-
if (mCurrentDialog != null) {
mCurrentDialog.onAuthenticationSucceeded(modality);
} else {
@@ -1048,6 +1031,18 @@
return false;
}
+ private String getNotRecognizedString(@Modality int modality) {
+ final int messageRes;
+ final int userId = mCurrentDialogArgs.argi1;
+ if (isFaceAuthEnrolled(userId) && isFingerprintEnrolled(userId)) {
+ messageRes = modality == TYPE_FACE
+ ? R.string.biometric_face_not_recognized
+ : R.string.fingerprint_error_not_match;
+ } else {
+ messageRes = R.string.biometric_not_recognized;
+ }
+ return mContext.getString(messageRes);
+ }
private String getErrorString(@Modality int modality, int error, int vendorCode) {
switch (modality) {
@@ -1073,8 +1068,6 @@
Log.d(TAG, String.format("onBiometricError(%d, %d, %d)", modality, error, vendorCode));
}
- vibrateError(modality);
-
final boolean isLockout = (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT)
|| (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT);
@@ -1091,10 +1084,11 @@
if (mCurrentDialog != null) {
if (mCurrentDialog.isAllowDeviceCredentials() && isLockout) {
if (DEBUG) Log.d(TAG, "onBiometricError, lockout");
- mCurrentDialog.animateToCredentialUI();
+ mCurrentDialog.animateToCredentialUI(true /* isError */);
} else if (isSoftError) {
- final String errorMessage = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED)
- ? mContext.getString(R.string.biometric_not_recognized)
+ final String errorMessage = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED
+ || error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT)
+ ? getNotRecognizedString(modality)
: getErrorString(modality, error, vendorCode);
if (DEBUG) Log.d(TAG, "onBiometricError, soft error: " + errorMessage);
// The camera privacy error can return before the prompt initializes its state,
@@ -1204,8 +1198,11 @@
final PromptInfo promptInfo = (PromptInfo) args.arg1;
final int[] sensorIds = (int[]) args.arg3;
+
+ // TODO(b/251476085): remove these unused parameters (replaced with SSOT elsewhere)
final boolean credentialAllowed = (boolean) args.arg4;
final boolean requireConfirmation = (boolean) args.arg5;
+
final int userId = args.argi1;
final String opPackageName = (String) args.arg6;
final long operationId = args.argl1;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
index b6eabfa..3cfc6f2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
@@ -162,7 +162,7 @@
/**
* Animate to credential UI. Typically called after biometric is locked out.
*/
- void animateToCredentialUI();
+ void animateToCredentialUI(boolean isError);
/**
* @return true if device credential is allowed.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
index 6db266f..9d8dcc1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
@@ -21,6 +21,8 @@
import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FACE_REENROLL_DIALOG;
import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -30,6 +32,9 @@
import android.content.IntentFilter;
import android.hardware.biometrics.BiometricFaceConstants;
import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.biometrics.BiometricStateListener;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.FingerprintManager;
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
@@ -42,7 +47,6 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-
import java.util.Optional;
import javax.inject.Inject;
@@ -69,6 +73,8 @@
private final NotificationManager mNotificationManager;
private final BiometricNotificationBroadcastReceiver mBroadcastReceiver;
private final FingerprintReEnrollNotification mFingerprintReEnrollNotification;
+ private final FingerprintManager mFingerprintManager;
+ private final FaceManager mFaceManager;
private NotificationChannel mNotificationChannel;
private boolean mFaceNotificationQueued;
private boolean mFingerprintNotificationQueued;
@@ -119,14 +125,29 @@
}
};
+ private final BiometricStateListener mFaceStateListener = new BiometricStateListener() {
+ @Override
+ public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
+ mNotificationManager.cancelAsUser(TAG, FACE_NOTIFICATION_ID, UserHandle.CURRENT);
+ }
+ };
+
+ private final BiometricStateListener mFingerprintStateListener = new BiometricStateListener() {
+ @Override
+ public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
+ mNotificationManager.cancelAsUser(TAG, FINGERPRINT_NOTIFICATION_ID, UserHandle.CURRENT);
+ }
+ };
@Inject
- public BiometricNotificationService(Context context,
- KeyguardUpdateMonitor keyguardUpdateMonitor,
- KeyguardStateController keyguardStateController,
- Handler handler, NotificationManager notificationManager,
- BiometricNotificationBroadcastReceiver biometricNotificationBroadcastReceiver,
- Optional<FingerprintReEnrollNotification> fingerprintReEnrollNotification) {
+ public BiometricNotificationService(@NonNull Context context,
+ @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
+ @NonNull KeyguardStateController keyguardStateController,
+ @NonNull Handler handler, @NonNull NotificationManager notificationManager,
+ @NonNull BiometricNotificationBroadcastReceiver biometricNotificationBroadcastReceiver,
+ @NonNull Optional<FingerprintReEnrollNotification> fingerprintReEnrollNotification,
+ @Nullable FingerprintManager fingerprintManager,
+ @Nullable FaceManager faceManager) {
mContext = context;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mKeyguardStateController = keyguardStateController;
@@ -135,6 +156,8 @@
mBroadcastReceiver = biometricNotificationBroadcastReceiver;
mFingerprintReEnrollNotification = fingerprintReEnrollNotification.orElse(
new FingerprintReEnrollNotificationImpl());
+ mFingerprintManager = fingerprintManager;
+ mFaceManager = faceManager;
}
@Override
@@ -148,9 +171,16 @@
intentFilter.addAction(ACTION_SHOW_FACE_REENROLL_DIALOG);
mContext.registerReceiver(mBroadcastReceiver, intentFilter,
Context.RECEIVER_EXPORTED_UNAUDITED);
+ if (mFingerprintManager != null) {
+ mFingerprintManager.registerBiometricStateListener(mFingerprintStateListener);
+ }
+ if (mFaceManager != null) {
+ mFaceManager.registerBiometricStateListener(mFaceStateListener);
+ }
}
private void queueFaceReenrollNotification() {
+ Log.d(TAG, "Face re-enroll notification queued.");
mFaceNotificationQueued = true;
final String title = mContext.getString(R.string.face_re_enroll_notification_title);
final String content = mContext.getString(
@@ -163,6 +193,7 @@
}
private void queueFingerprintReenrollNotification() {
+ Log.d(TAG, "Fingerprint re-enroll notification queued.");
mFingerprintNotificationQueued = true;
final String title = mContext.getString(R.string.fingerprint_re_enroll_notification_title);
final String content = mContext.getString(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 10e45da..16d12bb 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -188,6 +188,8 @@
@Nullable private VelocityTracker mVelocityTracker;
// The ID of the pointer for which ACTION_DOWN has occurred. -1 means no pointer is active.
private int mActivePointerId = -1;
+ // Whether a pointer has been pilfered for current gesture
+ private boolean mPointerPilfered = false;
// The timestamp of the most recent touch log.
private long mTouchLogTime;
// The timestamp of the most recent log of a touch InteractionEvent.
@@ -354,7 +356,8 @@
UdfpsController.this.mAlternateTouchProvider.onUiReady();
} else {
final long requestId = (mOverlay != null) ? mOverlay.getRequestId() : 0L;
- UdfpsController.this.mFingerprintManager.onUiReady(requestId, sensorId);
+ UdfpsController.this.mFingerprintManager.onUdfpsUiEvent(
+ FingerprintManager.UDFPS_UI_READY, requestId, sensorId);
}
}
}
@@ -557,6 +560,11 @@
|| mPrimaryBouncerInteractor.isInTransit()) {
return false;
}
+ if (event.getAction() == MotionEvent.ACTION_DOWN
+ || event.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
+ // Reset on ACTION_DOWN, start of new gesture
+ mPointerPilfered = false;
+ }
final TouchProcessorResult result = mTouchProcessor.processTouch(event, mActivePointerId,
mOverlayParams);
@@ -636,10 +644,11 @@
shouldPilfer = true;
}
- // Execute the pilfer
- if (shouldPilfer) {
+ // Pilfer only once per gesture
+ if (shouldPilfer && !mPointerPilfered) {
mInputManager.pilferPointers(
mOverlay.getOverlayView().getViewRootImpl().getInputToken());
+ mPointerPilfered = true;
}
return processedTouch.getTouchData().isWithinBounds(mOverlayParams.getNativeSensorBounds());
@@ -958,6 +967,10 @@
mOnFingerDown = false;
mAttemptedToDismissKeyguard = false;
mOrientationListener.enable();
+ if (mFingerprintManager != null) {
+ mFingerprintManager.onUdfpsUiEvent(FingerprintManager.UDFPS_UI_OVERLAY_SHOWN,
+ overlay.getRequestId(), mSensorProps.sensorId);
+ }
} else {
Log.v(TAG, "showUdfpsOverlay | the overlay is already showing");
}
@@ -1099,7 +1112,8 @@
mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
});
} else {
- mFingerprintManager.onUiReady(requestId, mSensorProps.sensorId);
+ mFingerprintManager.onUdfpsUiEvent(FingerprintManager.UDFPS_UI_READY, requestId,
+ mSensorProps.sensorId);
mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
index ddf1457..a5e846a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -17,6 +17,8 @@
package com.android.systemui.biometrics.dagger
import com.android.settingslib.udfps.UdfpsUtils
+import com.android.systemui.biometrics.data.repository.FaceSettingsRepository
+import com.android.systemui.biometrics.data.repository.FaceSettingsRepositoryImpl
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepositoryImpl
import com.android.systemui.biometrics.data.repository.PromptRepository
@@ -47,6 +49,10 @@
@Binds
@SysUISingleton
+ fun faceSettings(impl: FaceSettingsRepositoryImpl): FaceSettingsRepository
+
+ @Binds
+ @SysUISingleton
fun biometricPromptRepository(impl: PromptRepositoryImpl): PromptRepository
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepository.kt
new file mode 100644
index 0000000..3d5ed82
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepository.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 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.systemui.biometrics.data.repository
+
+import android.os.Handler
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.settings.SecureSettings
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+
+/**
+ * Repository for the global state of users Face Unlock preferences.
+ *
+ * Largely a wrapper around [SecureSettings]'s proxy to Settings.Secure.
+ */
+interface FaceSettingsRepository {
+
+ /** Get Settings for the given user [id]. */
+ fun forUser(id: Int?): FaceUserSettingsRepository
+}
+
+@SysUISingleton
+class FaceSettingsRepositoryImpl
+@Inject
+constructor(
+ @Main private val mainHandler: Handler,
+ private val secureSettings: SecureSettings,
+) : FaceSettingsRepository {
+
+ private val userSettings = ConcurrentHashMap<Int, FaceUserSettingsRepository>()
+
+ override fun forUser(id: Int?): FaceUserSettingsRepository =
+ if (id != null) {
+ userSettings.computeIfAbsent(id) { _ ->
+ FaceUserSettingsRepositoryImpl(id, mainHandler, secureSettings).also { repo ->
+ repo.start()
+ }
+ }
+ } else {
+ FaceUserSettingsRepositoryImpl.Empty
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceUserSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceUserSettingsRepository.kt
new file mode 100644
index 0000000..68c4a10
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceUserSettingsRepository.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2023 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.systemui.biometrics.data.repository
+
+import android.database.ContentObserver
+import android.os.Handler
+import android.provider.Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.settings.SecureSettings
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.flowOf
+
+/** Settings for a user. */
+interface FaceUserSettingsRepository {
+ /** The user's id. */
+ val userId: Int
+
+ /** If BiometricPrompt should always require confirmation (overrides app's preference). */
+ val alwaysRequireConfirmationInApps: Flow<Boolean>
+}
+
+class FaceUserSettingsRepositoryImpl(
+ override val userId: Int,
+ @Main private val mainHandler: Handler,
+ private val secureSettings: SecureSettings,
+) : FaceUserSettingsRepository {
+
+ /** Indefinitely subscribe to user preference changes. */
+ fun start() {
+ watch(
+ FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
+ _alwaysRequireConfirmationInApps,
+ )
+ }
+
+ private var _alwaysRequireConfirmationInApps = MutableStateFlow(false)
+ override val alwaysRequireConfirmationInApps: Flow<Boolean> =
+ _alwaysRequireConfirmationInApps.asStateFlow()
+
+ /** Defaults to use when no user is specified. */
+ object Empty : FaceUserSettingsRepository {
+ override val userId = -1
+ override val alwaysRequireConfirmationInApps = flowOf(false)
+ }
+
+ private fun watch(
+ key: String,
+ toUpdate: MutableStateFlow<Boolean>,
+ defaultValue: Boolean = false,
+ ) = secureSettings.watch(userId, mainHandler, key, defaultValue) { v -> toUpdate.value = v }
+}
+
+private fun SecureSettings.watch(
+ userId: Int,
+ handler: Handler,
+ key: String,
+ defaultValue: Boolean = false,
+ onChange: (Boolean) -> Unit,
+) {
+ fun fetch(): Boolean = getIntForUser(key, if (defaultValue) 1 else 0, userId) > 0
+
+ registerContentObserverForUser(
+ key,
+ false /* notifyForDescendants */,
+ object : ContentObserver(handler) {
+ override fun onChange(selfChange: Boolean) = onChange(fetch())
+ },
+ userId
+ )
+
+ onChange(fetch())
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
index b4dc272..b35fbbc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 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.systemui.biometrics.data.repository
import android.hardware.biometrics.PromptInfo
@@ -12,6 +28,10 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
/**
* A repository for the global state of BiometricPrompt.
@@ -40,7 +60,7 @@
*
* Note: overlaps/conflicts with [PromptInfo.isConfirmationRequested], which needs clean up.
*/
- val isConfirmationRequired: StateFlow<Boolean>
+ val isConfirmationRequired: Flow<Boolean>
/** Update the prompt configuration, which should be set before [isShowing]. */
fun setPrompt(
@@ -48,7 +68,6 @@
userId: Int,
gatekeeperChallenge: Long?,
kind: PromptKind,
- requireConfirmation: Boolean = false,
)
/** Unset the prompt info. */
@@ -56,8 +75,12 @@
}
@SysUISingleton
-class PromptRepositoryImpl @Inject constructor(private val authController: AuthController) :
- PromptRepository {
+class PromptRepositoryImpl
+@Inject
+constructor(
+ private val faceSettings: FaceSettingsRepository,
+ private val authController: AuthController,
+) : PromptRepository {
override val isShowing: Flow<Boolean> = conflatedCallbackFlow {
val callback =
@@ -85,21 +108,30 @@
private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.Biometric())
override val kind = _kind.asStateFlow()
- private val _isConfirmationRequired: MutableStateFlow<Boolean> = MutableStateFlow(false)
- override val isConfirmationRequired = _isConfirmationRequired.asStateFlow()
+ private val _faceSettings =
+ _userId.map { id -> faceSettings.forUser(id) }.distinctUntilChanged()
+ private val _faceSettingAlwaysRequireConfirmation =
+ _faceSettings.flatMapLatest { it.alwaysRequireConfirmationInApps }.distinctUntilChanged()
+
+ private val _isConfirmationRequired = _promptInfo.map { it?.isConfirmationRequested ?: false }
+ override val isConfirmationRequired =
+ combine(_isConfirmationRequired, _faceSettingAlwaysRequireConfirmation) {
+ appRequiresConfirmation,
+ forceRequireConfirmation ->
+ forceRequireConfirmation || appRequiresConfirmation
+ }
+ .distinctUntilChanged()
override fun setPrompt(
promptInfo: PromptInfo,
userId: Int,
gatekeeperChallenge: Long?,
kind: PromptKind,
- requireConfirmation: Boolean,
) {
_kind.value = kind
_userId.value = userId
_challenge.value = gatekeeperChallenge
_promptInfo.value = promptInfo
- _isConfirmationRequired.value = requireConfirmation
}
override fun unsetPrompt() {
@@ -107,7 +139,6 @@
_userId.value = null
_challenge.value = null
_kind.value = PromptKind.Biometric()
- _isConfirmationRequired.value = false
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
index e6e07f9..be99dd9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
@@ -59,13 +59,15 @@
*/
val credentialKind: Flow<PromptKind>
- /** If the API caller requested explicit confirmation after successful authentication. */
- val isConfirmationRequested: Flow<Boolean>
+ /**
+ * If the API caller or the user's personal preferences require explicit confirmation after
+ * successful authentication.
+ */
+ val isConfirmationRequired: Flow<Boolean>
/** Use biometrics for authentication. */
fun useBiometricsForAuthentication(
promptInfo: PromptInfo,
- requireConfirmation: Boolean,
userId: Int,
challenge: Long,
modalities: BiometricModalities,
@@ -114,10 +116,8 @@
}
}
- override val isConfirmationRequested: Flow<Boolean> =
- promptRepository.promptInfo
- .map { info -> info?.isConfirmationRequested ?: false }
- .distinctUntilChanged()
+ override val isConfirmationRequired: Flow<Boolean> =
+ promptRepository.isConfirmationRequired.distinctUntilChanged()
override val isCredentialAllowed: Flow<Boolean> =
promptRepository.promptInfo
@@ -142,7 +142,6 @@
override fun useBiometricsForAuthentication(
promptInfo: PromptInfo,
- requireConfirmation: Boolean,
userId: Int,
challenge: Long,
modalities: BiometricModalities
@@ -152,7 +151,6 @@
userId = userId,
gatekeeperChallenge = challenge,
kind = PromptKind.Biometric(modalities),
- requireConfirmation = requireConfirmation,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModality.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModality.kt
rename to packages/SystemUI/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt
index 3197c09..fb580ca 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModality.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.biometrics.domain.model
+package com.android.systemui.biometrics.shared.model
import android.hardware.biometrics.BiometricAuthenticator
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 8486c3f..e5a4d1a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -46,9 +46,9 @@
import com.android.systemui.biometrics.AuthPanelController
import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.domain.model.BiometricModalities
-import com.android.systemui.biometrics.domain.model.BiometricModality
-import com.android.systemui.biometrics.domain.model.asBiometricModality
+import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.biometrics.shared.model.PromptKind
+import com.android.systemui.biometrics.shared.model.asBiometricModality
import com.android.systemui.biometrics.ui.BiometricPromptLayout
import com.android.systemui.biometrics.ui.viewmodel.FingerprintStartMode
import com.android.systemui.biometrics.ui.viewmodel.PromptMessage
@@ -158,7 +158,7 @@
view.updateFingerprintAffordanceSize(iconController)
}
if (iconController is HackyCoexIconController) {
- iconController.faceMode = !viewModel.isConfirmationRequested.first()
+ iconController.faceMode = !viewModel.isConfirmationRequired.first()
}
// the icon controller must be created before this happens for the legacy
@@ -339,7 +339,13 @@
launch {
delay(authState.delay)
- legacyCallback.onAction(Callback.ACTION_AUTHENTICATED)
+ legacyCallback.onAction(
+ if (authState.isAuthenticatedAndExplicitlyConfirmed) {
+ Callback.ACTION_AUTHENTICATED_AND_CONFIRMED
+ } else {
+ Callback.ACTION_AUTHENTICATED
+ }
+ )
}
}
}
@@ -390,7 +396,6 @@
private var lifecycleScope: CoroutineScope? = null
private var modalities: BiometricModalities = BiometricModalities()
- private var faceFailedAtLeastOnce = false
private var legacyCallback: Callback? = null
override var legacyIconController: AuthIconController? = null
@@ -470,19 +475,15 @@
viewModel.ensureFingerprintHasStarted(isDelayed = true)
applicationScope.launch {
- val suppress =
- modalities.hasFaceAndFingerprint &&
- (failedModality == BiometricModality.Face) &&
- faceFailedAtLeastOnce
- if (failedModality == BiometricModality.Face) {
- faceFailedAtLeastOnce = true
- }
-
viewModel.showTemporaryError(
failureReason,
messageAfterError = modalities.asDefaultHelpMessage(applicationContext),
authenticateAfterError = modalities.hasFingerprint,
- suppressIfErrorShowing = suppress,
+ suppressIf = { currentMessage ->
+ modalities.hasFaceAndFingerprint &&
+ failedModality == BiometricModality.Face &&
+ currentMessage.isError
+ },
failedModality = failedModality,
)
}
@@ -495,11 +496,10 @@
}
applicationScope.launch {
- val suppress =
- modalities.hasFaceAndFingerprint && (errorModality == BiometricModality.Face)
viewModel.showTemporaryError(
error,
- suppressIfErrorShowing = suppress,
+ messageAfterError = modalities.asDefaultHelpMessage(applicationContext),
+ authenticateAfterError = modalities.hasFingerprint,
)
delay(BiometricPrompt.HIDE_DIALOG_DELAY.toLong())
legacyCallback?.onAction(Callback.ACTION_ERROR)
@@ -512,9 +512,12 @@
}
applicationScope.launch {
- viewModel.showTemporaryHelp(
+ // help messages from the HAL should be displayed as temporary (i.e. soft) errors
+ viewModel.showTemporaryError(
help,
- messageAfterHelp = modalities.asDefaultHelpMessage(applicationContext),
+ messageAfterError = modalities.asDefaultHelpMessage(applicationContext),
+ authenticateAfterError = modalities.hasFingerprint,
+ hapticFeedback = false,
)
}
}
@@ -527,7 +530,7 @@
else -> false
}
- override fun startTransitionToCredentialUI() {
+ override fun startTransitionToCredentialUI(isError: Boolean) {
applicationScope.launch {
viewModel.onSwitchToCredential()
legacyCallback?.onAction(Callback.ACTION_USE_DEVICE_CREDENTIAL)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index 1dffa80..1a286cf 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -28,6 +28,7 @@
import android.widget.TextView
import androidx.core.animation.addListener
import androidx.core.view.doOnLayout
+import androidx.core.view.isGone
import androidx.lifecycle.lifecycleScope
import com.android.systemui.R
import com.android.systemui.biometrics.AuthDialog
@@ -78,9 +79,11 @@
// cache the original position of the icon view (as done in legacy view)
// this must happen before any size changes can be made
- var iconHolderOriginalY = 0f
view.doOnLayout {
- iconHolderOriginalY = iconHolderView.y
+ // TODO(b/251476085): this old way of positioning has proven itself unreliable
+ // remove this and associated thing like (UdfpsDialogMeasureAdapter) and
+ // pin to the physical sensor
+ val iconHolderOriginalY = iconHolderView.y
// bind to prompt
// TODO(b/251476085): migrate the legacy panel controller and simplify this
@@ -141,7 +144,11 @@
listOf(
iconHolderView.asVerticalAnimator(
duration = duration.toLong(),
- toY = iconHolderOriginalY,
+ toY =
+ iconHolderOriginalY -
+ viewsToHideWhenSmall
+ .filter { it.isGone }
+ .sumOf { it.height },
),
viewsToFadeInOnSizeChange.asFadeInAnimator(
duration = duration.toLong(),
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt
index 9cb91b3..2f9557f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt
@@ -16,7 +16,7 @@
package com.android.systemui.biometrics.ui.viewmodel
-import com.android.systemui.biometrics.domain.model.BiometricModality
+import com.android.systemui.biometrics.shared.model.BiometricModality
/**
* The authenticated state with the [authenticatedModality] (when [isAuthenticated]) with an
@@ -29,10 +29,16 @@
val needsUserConfirmation: Boolean = false,
val delay: Long = 0,
) {
+ private var wasConfirmed = false
+
/** If authentication was successful and the user has confirmed (or does not need to). */
val isAuthenticatedAndConfirmed: Boolean
get() = isAuthenticated && !needsUserConfirmation
+ /** Same as [isAuthenticatedAndConfirmed] but only true if the user clicked a confirm button. */
+ val isAuthenticatedAndExplicitlyConfirmed: Boolean
+ get() = isAuthenticated && wasConfirmed
+
/** If a successful authentication has not occurred. */
val isNotAuthenticated: Boolean
get() = !isAuthenticated
@@ -45,12 +51,16 @@
val isAuthenticatedByFingerprint: Boolean
get() = isAuthenticated && authenticatedModality == BiometricModality.Fingerprint
- /** Copies this state, but toggles [needsUserConfirmation] to false. */
- fun asConfirmed(): PromptAuthState =
+ /**
+ * Copies this state, but toggles [needsUserConfirmation] to false and ensures that
+ * [isAuthenticatedAndExplicitlyConfirmed] is true.
+ */
+ fun asExplicitlyConfirmed(): PromptAuthState =
PromptAuthState(
- isAuthenticated = isAuthenticated,
- authenticatedModality = authenticatedModality,
- needsUserConfirmation = false,
- delay = delay,
- )
+ isAuthenticated = isAuthenticated,
+ authenticatedModality = authenticatedModality,
+ needsUserConfirmation = false,
+ delay = delay,
+ )
+ .apply { wasConfirmed = true }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptMessage.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptMessage.kt
index 219da71..50f4911 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptMessage.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptMessage.kt
@@ -33,9 +33,9 @@
else -> ""
}
- /** If this is an [Error] or [Help] message. */
- val isErrorOrHelp: Boolean
- get() = this is Error || this is Help
+ /** If this is an [Error]. */
+ val isError: Boolean
+ get() = this is Error
/** An error message. */
data class Error(val errorMessage: String) : PromptMessage
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 2f8ed09..8a2e405 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -20,8 +20,9 @@
import com.android.systemui.biometrics.AuthBiometricView
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
import com.android.systemui.biometrics.domain.model.BiometricModalities
-import com.android.systemui.biometrics.domain.model.BiometricModality
+import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.biometrics.shared.model.PromptKind
+import com.android.systemui.statusbar.VibratorHelper
import javax.inject.Inject
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
@@ -41,6 +42,7 @@
@Inject
constructor(
private val interactor: PromptSelectorInteractor,
+ private val vibrator: VibratorHelper,
) {
/** The set of modalities available for this prompt */
val modalities: Flow<BiometricModalities> =
@@ -61,8 +63,11 @@
/** If the user has successfully authenticated and confirmed (when explicitly required). */
val isAuthenticated: Flow<PromptAuthState> = _isAuthenticated.asStateFlow()
- /** If the API caller requested explicit confirmation after successful authentication. */
- val isConfirmationRequested: Flow<Boolean> = interactor.isConfirmationRequested
+ /**
+ * If the API caller or the user's personal preferences require explicit confirmation after
+ * successful authentication.
+ */
+ val isConfirmationRequired: Flow<Boolean> = interactor.isConfirmationRequired
/** The kind of credential the user has. */
val credentialKind: Flow<PromptKind> = interactor.credentialKind
@@ -91,7 +96,7 @@
_forceLargeSize,
_forceMediumSize,
modalities,
- interactor.isConfirmationRequested,
+ interactor.isConfirmationRequired,
fingerprintStartMode,
) { forceLarge, forceMedium, modalities, confirmationRequired, fpStartMode ->
when {
@@ -202,37 +207,43 @@
private var messageJob: Job? = null
/**
- * Show a temporary error [message] associated with an optional [failedModality].
+ * Show a temporary error [message] associated with an optional [failedModality] and play
+ * [hapticFeedback].
*
- * An optional [messageAfterError] will be shown via [showAuthenticating] when
- * [authenticateAfterError] is set (or via [showHelp] when not set) after the error is
- * dismissed.
+ * The [messageAfterError] will be shown via [showAuthenticating] when [authenticateAfterError]
+ * is set (or via [showHelp] when not set) after the error is dismissed.
*
- * The error is ignored if the user has already authenticated and it is treated as
- * [onSilentError] if [suppressIfErrorShowing] is set and an error message is already showing.
+ * The error is ignored if the user has already authenticated or if [suppressIf] is true given
+ * the currently showing [PromptMessage].
*/
suspend fun showTemporaryError(
message: String,
- messageAfterError: String = "",
- authenticateAfterError: Boolean = false,
- suppressIfErrorShowing: Boolean = false,
+ messageAfterError: String,
+ authenticateAfterError: Boolean,
+ suppressIf: (PromptMessage) -> Boolean = { false },
+ hapticFeedback: Boolean = true,
failedModality: BiometricModality = BiometricModality.None,
) = coroutineScope {
if (_isAuthenticated.value.isAuthenticated) {
return@coroutineScope
}
- if (_message.value.isErrorOrHelp && suppressIfErrorShowing) {
- onSilentError(failedModality)
+
+ _canTryAgainNow.value = supportsRetry(failedModality)
+
+ if (suppressIf(_message.value)) {
return@coroutineScope
}
_isAuthenticating.value = false
_isAuthenticated.value = PromptAuthState(false)
_forceMediumSize.value = true
- _canTryAgainNow.value = supportsRetry(failedModality)
_message.value = PromptMessage.Error(message)
_legacyState.value = AuthBiometricView.STATE_ERROR
+ if (hapticFeedback) {
+ vibrator.error(failedModality)
+ }
+
messageJob?.cancel()
messageJob = launch {
delay(BiometricPrompt.HIDE_DIALOG_DELAY.toLong())
@@ -245,18 +256,6 @@
}
/**
- * Call instead of [showTemporaryError] if an error from the HAL should be silently ignored to
- * enable retry (if the [failedModality] supports retrying).
- *
- * Ignored if the user has already authenticated.
- */
- private fun onSilentError(failedModality: BiometricModality = BiometricModality.None) {
- if (_isAuthenticated.value.isNotAuthenticated) {
- _canTryAgainNow.value = supportsRetry(failedModality)
- }
- }
-
- /**
* Call to ensure the fingerprint sensor has started. Either when the dialog is first shown
* (most cases) or when it should be enabled after a first error (coex implicit flow).
*/
@@ -373,6 +372,10 @@
AuthBiometricView.STATE_AUTHENTICATED
}
+ if (!needsUserConfirmation) {
+ vibrator.success(modality)
+ }
+
messageJob?.cancel()
messageJob = null
@@ -383,18 +386,18 @@
private suspend fun needsExplicitConfirmation(modality: BiometricModality): Boolean {
val availableModalities = modalities.first()
- val confirmationRequested = interactor.isConfirmationRequested.first()
+ val confirmationRequired = isConfirmationRequired.first()
if (availableModalities.hasFaceAndFingerprint) {
// coex only needs confirmation when face is successful, unless it happens on the
// first attempt (i.e. without failure) before fingerprint scanning starts
+ val fingerprintStarted = fingerprintStartMode.first() != FingerprintStartMode.Pending
if (modality == BiometricModality.Face) {
- return (fingerprintStartMode.first() != FingerprintStartMode.Pending) ||
- confirmationRequested
+ return fingerprintStarted || confirmationRequired
}
}
if (availableModalities.hasFaceOnly) {
- return confirmationRequested
+ return confirmationRequired
}
// fingerprint only never requires confirmation
return false
@@ -409,15 +412,16 @@
fun confirmAuthenticated() {
val authState = _isAuthenticated.value
if (authState.isNotAuthenticated) {
- "Cannot show authenticated after authenticated"
Log.w(TAG, "Cannot confirm authenticated when not authenticated")
return
}
- _isAuthenticated.value = authState.asConfirmed()
+ _isAuthenticated.value = authState.asExplicitlyConfirmed()
_message.value = PromptMessage.Empty
_legacyState.value = AuthBiometricView.STATE_AUTHENTICATED
+ vibrator.success(authState.authenticatedModality)
+
messageJob?.cancel()
messageJob = null
}
@@ -431,6 +435,12 @@
_forceLargeSize.value = true
}
+ private fun VibratorHelper.success(modality: BiometricModality) =
+ vibrateAuthSuccess("$TAG, modality = $modality BP::success")
+
+ private fun VibratorHelper.error(modality: BiometricModality = BiometricModality.None) =
+ vibrateAuthError("$TAG, modality = $modality BP::error")
+
companion object {
private const val TAG = "PromptViewModel"
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
index a431a59..a90980f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
@@ -16,6 +16,7 @@
package com.android.systemui.dagger;
+import com.android.systemui.globalactions.ShutdownUiModule;
import com.android.systemui.keyguard.CustomizationProvider;
import com.android.systemui.statusbar.NotificationInsetsModule;
import com.android.systemui.statusbar.QsFrameTranslateModule;
@@ -31,6 +32,7 @@
DependencyProvider.class,
NotificationInsetsModule.class,
QsFrameTranslateModule.class,
+ ShutdownUiModule.class,
SystemUIBinder.class,
SystemUIModule.class,
SystemUICoreStartableModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 366056a..5087b8b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -221,7 +221,7 @@
// TODO(b/267722622): Tracking Bug
@JvmField
val WALLPAPER_PICKER_UI_FOR_AIWP =
- unreleasedFlag(
+ releasedFlag(
229,
"wallpaper_picker_ui_for_aiwp"
)
@@ -662,7 +662,7 @@
@JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag(2200, "udfps_new_touch_detection")
@JvmField val UDFPS_ELLIPSE_DETECTION = releasedFlag(2201, "udfps_ellipse_detection")
// TODO(b/278622168): Tracking Bug
- @JvmField val BIOMETRIC_BP_STRONG = unreleasedFlag(2202, "biometric_bp_strong")
+ @JvmField val BIOMETRIC_BP_STRONG = releasedFlag(2202, "biometric_bp_strong")
// 2300 - stylus
@JvmField val TRACK_STYLUS_EVER_USED = releasedFlag(2300, "track_stylus_ever_used")
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index 290bf0d..c5027cc 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -15,27 +15,12 @@
package com.android.systemui.globalactions;
import static android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS;
-import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-import android.annotation.Nullable;
-import android.annotation.StringRes;
-import android.app.Dialog;
import android.content.Context;
-import android.os.PowerManager;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.Window;
-import android.view.WindowManager;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-import com.android.internal.R;
-import com.android.settingslib.Utils;
import com.android.systemui.plugins.GlobalActions;
-import com.android.systemui.scrim.ScrimDrawable;
import com.android.systemui.statusbar.BlurUtils;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -50,12 +35,14 @@
private final CommandQueue mCommandQueue;
private final GlobalActionsDialogLite mGlobalActionsDialog;
private boolean mDisabled;
+ private ShutdownUi mShutdownUi;
@Inject
public GlobalActionsImpl(Context context, CommandQueue commandQueue,
GlobalActionsDialogLite globalActionsDialog, BlurUtils blurUtils,
KeyguardStateController keyguardStateController,
- DeviceProvisionedController deviceProvisionedController) {
+ DeviceProvisionedController deviceProvisionedController,
+ ShutdownUi shutdownUi) {
mContext = context;
mGlobalActionsDialog = globalActionsDialog;
mKeyguardStateController = keyguardStateController;
@@ -63,6 +50,7 @@
mCommandQueue = commandQueue;
mBlurUtils = blurUtils;
mCommandQueue.addCallback(this);
+ mShutdownUi = shutdownUi;
}
@Override
@@ -80,103 +68,8 @@
@Override
public void showShutdownUi(boolean isReboot, String reason) {
- ScrimDrawable background = new ScrimDrawable();
-
- final Dialog d = new Dialog(mContext,
- com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);
-
- d.setOnShowListener(dialog -> {
- if (mBlurUtils.supportsBlursOnWindows()) {
- int backgroundAlpha = (int) (ScrimController.BUSY_SCRIM_ALPHA * 255);
- background.setAlpha(backgroundAlpha);
- mBlurUtils.applyBlur(d.getWindow().getDecorView().getViewRootImpl(),
- (int) mBlurUtils.blurRadiusOfRatio(1), backgroundAlpha == 255);
- } else {
- float backgroundAlpha = mContext.getResources().getFloat(
- com.android.systemui.R.dimen.shutdown_scrim_behind_alpha);
- background.setAlpha((int) (backgroundAlpha * 255));
- }
- });
-
- // Window initialization
- Window window = d.getWindow();
- window.requestFeature(Window.FEATURE_NO_TITLE);
- window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
- // Inflate the decor view, so the attributes below are not overwritten by the theme.
- window.getDecorView();
- window.getAttributes().width = ViewGroup.LayoutParams.MATCH_PARENT;
- window.getAttributes().height = ViewGroup.LayoutParams.MATCH_PARENT;
- window.getAttributes().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
- window.getAttributes().setFitInsetsTypes(0 /* types */);
- window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
- window.addFlags(
- WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
- | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
- | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
- | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
- window.setBackgroundDrawable(background);
- window.setWindowAnimations(com.android.systemui.R.style.Animation_ShutdownUi);
-
- d.setContentView(R.layout.shutdown_dialog);
- d.setCancelable(false);
-
- int color;
- if (mBlurUtils.supportsBlursOnWindows()) {
- color = Utils.getColorAttrDefaultColor(mContext,
- com.android.systemui.R.attr.wallpaperTextColor);
- } else {
- color = mContext.getResources().getColor(
- com.android.systemui.R.color.global_actions_shutdown_ui_text);
- }
-
- ProgressBar bar = d.findViewById(R.id.progress);
- bar.getIndeterminateDrawable().setTint(color);
-
- TextView reasonView = d.findViewById(R.id.text1);
- TextView messageView = d.findViewById(R.id.text2);
-
- reasonView.setTextColor(color);
- messageView.setTextColor(color);
-
- messageView.setText(getRebootMessage(isReboot, reason));
- String rebootReasonMessage = getReasonMessage(reason);
- if (rebootReasonMessage != null) {
- reasonView.setVisibility(View.VISIBLE);
- reasonView.setText(rebootReasonMessage);
- }
-
- d.show();
+ mShutdownUi.showShutdownUi(isReboot, reason);
}
-
- @StringRes
- private int getRebootMessage(boolean isReboot, @Nullable String reason) {
- if (reason != null && reason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
- return R.string.reboot_to_update_reboot;
- } else if (reason != null && reason.equals(PowerManager.REBOOT_RECOVERY)) {
- return R.string.reboot_to_reset_message;
- } else if (isReboot) {
- return R.string.reboot_to_reset_message;
- } else {
- return R.string.shutdown_progress;
- }
- }
-
- @Nullable
- private String getReasonMessage(@Nullable String reason) {
- if (reason != null && reason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
- return mContext.getString(R.string.reboot_to_update_title);
- } else if (reason != null && reason.equals(PowerManager.REBOOT_RECOVERY)) {
- return mContext.getString(R.string.reboot_to_reset_title);
- } else {
- return null;
- }
- }
-
@Override
public void disable(int displayId, int state1, int state2, boolean animate) {
final boolean disabled = (state2 & DISABLE2_GLOBAL_ACTIONS) != 0;
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java b/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java
new file mode 100644
index 0000000..68dc1b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2023 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.systemui.globalactions;
+
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.app.Dialog;
+import android.content.Context;
+import android.os.PowerManager;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.R;
+import com.android.settingslib.Utils;
+import com.android.systemui.scrim.ScrimDrawable;
+import com.android.systemui.statusbar.BlurUtils;
+import com.android.systemui.statusbar.phone.ScrimController;
+
+/**
+ * Provides the UI shown during system shutdown.
+ */
+public class ShutdownUi {
+
+ private Context mContext;
+ private BlurUtils mBlurUtils;
+ public ShutdownUi(Context context, BlurUtils blurUtils) {
+ mContext = context;
+ mBlurUtils = blurUtils;
+ }
+
+ /**
+ * Display the shutdown UI.
+ * @param isReboot Whether the device will be rebooting after this shutdown.
+ * @param reason Cause for the shutdown.
+ */
+ public void showShutdownUi(boolean isReboot, String reason) {
+ ScrimDrawable background = new ScrimDrawable();
+
+ final Dialog d = new Dialog(mContext,
+ com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);
+
+ d.setOnShowListener(dialog -> {
+ if (mBlurUtils.supportsBlursOnWindows()) {
+ int backgroundAlpha = (int) (ScrimController.BUSY_SCRIM_ALPHA * 255);
+ background.setAlpha(backgroundAlpha);
+ mBlurUtils.applyBlur(d.getWindow().getDecorView().getViewRootImpl(),
+ (int) mBlurUtils.blurRadiusOfRatio(1), backgroundAlpha == 255);
+ } else {
+ float backgroundAlpha = mContext.getResources().getFloat(
+ com.android.systemui.R.dimen.shutdown_scrim_behind_alpha);
+ background.setAlpha((int) (backgroundAlpha * 255));
+ }
+ });
+
+ // Window initialization
+ Window window = d.getWindow();
+ window.requestFeature(Window.FEATURE_NO_TITLE);
+ window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+ // Inflate the decor view, so the attributes below are not overwritten by the theme.
+ window.getDecorView();
+ window.getAttributes().width = ViewGroup.LayoutParams.MATCH_PARENT;
+ window.getAttributes().height = ViewGroup.LayoutParams.MATCH_PARENT;
+ window.getAttributes().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
+ window.getAttributes().setFitInsetsTypes(0 /* types */);
+ window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+ window.addFlags(
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+ window.setBackgroundDrawable(background);
+ window.setWindowAnimations(com.android.systemui.R.style.Animation_ShutdownUi);
+
+ d.setContentView(getShutdownDialogContent(isReboot));
+ d.setCancelable(false);
+
+ int color;
+ if (mBlurUtils.supportsBlursOnWindows()) {
+ color = Utils.getColorAttrDefaultColor(mContext,
+ com.android.systemui.R.attr.wallpaperTextColor);
+ } else {
+ color = mContext.getResources().getColor(
+ com.android.systemui.R.color.global_actions_shutdown_ui_text);
+ }
+
+ ProgressBar bar = d.findViewById(R.id.progress);
+ bar.getIndeterminateDrawable().setTint(color);
+
+ TextView reasonView = d.findViewById(R.id.text1);
+ TextView messageView = d.findViewById(R.id.text2);
+
+ reasonView.setTextColor(color);
+ messageView.setTextColor(color);
+
+ messageView.setText(getRebootMessage(isReboot, reason));
+ String rebootReasonMessage = getReasonMessage(reason);
+ if (rebootReasonMessage != null) {
+ reasonView.setVisibility(View.VISIBLE);
+ reasonView.setText(rebootReasonMessage);
+ }
+
+ d.show();
+ }
+
+ /**
+ * Returns the layout resource to use for UI while shutting down.
+ * @param isReboot Whether this is a reboot or a shutdown.
+ * @return
+ */
+ public int getShutdownDialogContent(boolean isReboot) {
+ return R.layout.shutdown_dialog;
+ }
+
+ @StringRes
+ @VisibleForTesting int getRebootMessage(boolean isReboot, @Nullable String reason) {
+ if (reason != null && reason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
+ return R.string.reboot_to_update_reboot;
+ } else if (reason != null && reason.equals(PowerManager.REBOOT_RECOVERY)) {
+ return R.string.reboot_to_reset_message;
+ } else if (isReboot) {
+ return R.string.reboot_to_reset_message;
+ } else {
+ return R.string.shutdown_progress;
+ }
+ }
+
+ @Nullable
+ @VisibleForTesting String getReasonMessage(@Nullable String reason) {
+ if (reason != null && reason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
+ return mContext.getString(R.string.reboot_to_update_title);
+ } else if (reason != null && reason.equals(PowerManager.REBOOT_RECOVERY)) {
+ return mContext.getString(R.string.reboot_to_reset_title);
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUiModule.kt b/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUiModule.kt
new file mode 100644
index 0000000..b7285da
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUiModule.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 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.systemui.globalactions
+
+import android.content.Context
+import com.android.systemui.statusbar.BlurUtils
+import dagger.Module
+import dagger.Provides
+
+/** Provides the UI shown during system shutdown. */
+@Module
+class ShutdownUiModule {
+ /** Shutdown UI provider. */
+ @Provides
+ fun provideShutdownUi(context: Context?, blurUtils: BlurUtils?): ShutdownUi {
+ return ShutdownUi(context, blurUtils)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
index 640adcc..5e489b0 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
@@ -20,15 +20,14 @@
import com.android.systemui.dagger.DependencyProvider;
import com.android.systemui.dagger.SysUIComponent;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.SystemUIBinder;
import com.android.systemui.dagger.SystemUIModule;
+import com.android.systemui.globalactions.ShutdownUiModule;
+import com.android.systemui.keyguard.dagger.KeyguardModule;
+import com.android.systemui.recents.RecentsModule;
import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule;
import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
import com.android.systemui.statusbar.notification.row.NotificationRowModule;
-import com.android.systemui.keyguard.dagger.KeyguardModule;
-import com.android.systemui.recents.RecentsModule;
-
import dagger.Subcomponent;
/**
@@ -43,6 +42,7 @@
NotificationRowModule.class,
NotificationsModule.class,
RecentsModule.class,
+ ShutdownUiModule.class,
SystemUIModule.class,
TvSystemUIBinder.class,
TVSystemUICoreStartableModule.class,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index d31a86a..e3e6130 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -51,6 +51,7 @@
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -99,6 +100,8 @@
lateinit var windowToken: IBinder
@Mock
lateinit var interactionJankMonitor: InteractionJankMonitor
+ @Mock
+ lateinit var vibrator: VibratorHelper
// TODO(b/278622168): remove with flag
open val useNewBiometricPrompt = false
@@ -325,7 +328,7 @@
authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK or
BiometricManager.Authenticators.DEVICE_CREDENTIAL
)
- container.animateToCredentialUI()
+ container.animateToCredentialUI(false)
waitForIdleSync()
assertThat(container.hasCredentialView()).isTrue()
@@ -514,7 +517,7 @@
{ authBiometricFingerprintViewModel },
{ promptSelectorInteractor },
{ bpCredentialInteractor },
- PromptViewModel(promptSelectorInteractor),
+ PromptViewModel(promptSelectorInteractor, vibrator),
{ credentialViewModel },
Handler(TestableLooper.get(this).looper),
fakeExecutor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index b9f92a0..0a73a9e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -191,6 +191,10 @@
private ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> mFaceAuthenticatorsRegisteredCaptor;
@Captor
private ArgumentCaptor<BiometricStateListener> mBiometricStateCaptor;
+ @Captor
+ private ArgumentCaptor<Integer> mModalityCaptor;
+ @Captor
+ private ArgumentCaptor<String> mMessageCaptor;
@Mock
private Resources mResources;
@@ -202,9 +206,6 @@
private TestableAuthController mAuthController;
private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
- @Mock
- private VibratorHelper mVibratorHelper;
-
@Before
public void setup() throws RemoteException {
// TODO(b/278622168): remove with flag
@@ -267,7 +268,6 @@
true /* supportsSelfIllumination */,
true /* resetLockoutRequireHardwareAuthToken */));
when(mFaceManager.getSensorPropertiesInternal()).thenReturn(faceProps);
- when(mVibratorHelper.hasVibrator()).thenReturn(true);
mAuthController = new TestableAuthController(mContextSpy);
@@ -482,16 +482,63 @@
BiometricConstants.BIOMETRIC_PAUSED_REJECTED,
0 /* vendorCode */);
- ArgumentCaptor<Integer> modalityCaptor = ArgumentCaptor.forClass(Integer.class);
- ArgumentCaptor<String> messageCaptor = ArgumentCaptor.forClass(String.class);
- verify(mDialog1).onAuthenticationFailed(modalityCaptor.capture(), messageCaptor.capture());
+ verify(mDialog1).onAuthenticationFailed(mModalityCaptor.capture(), mMessageCaptor.capture());
- assertEquals(modalityCaptor.getValue().intValue(), modality);
- assertEquals(messageCaptor.getValue(),
+ assertEquals(mModalityCaptor.getValue().intValue(), modality);
+ assertEquals(mMessageCaptor.getValue(),
mContext.getString(R.string.biometric_not_recognized));
}
@Test
+ public void testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected_withPaused() {
+ testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected(
+ BiometricConstants.BIOMETRIC_PAUSED_REJECTED);
+ }
+
+ @Test
+ public void testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected_withTimeout() {
+ testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected(
+ BiometricConstants.BIOMETRIC_ERROR_TIMEOUT);
+ }
+
+ private void testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected(int error) {
+ final int modality = BiometricAuthenticator.TYPE_FACE;
+ final int userId = 0;
+
+ enrollFingerprintAndFace(userId);
+
+ showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
+
+ mAuthController.onBiometricError(modality, error, 0 /* vendorCode */);
+
+ verify(mDialog1).onAuthenticationFailed(mModalityCaptor.capture(), mMessageCaptor.capture());
+
+ assertThat(mModalityCaptor.getValue().intValue()).isEqualTo(modality);
+ assertThat(mMessageCaptor.getValue()).isEqualTo(
+ mContext.getString(R.string.biometric_face_not_recognized));
+ }
+
+ @Test
+ public void testOnAuthenticationFailedInvoked_whenFingerprintAuthRejected() {
+ final int modality = BiometricAuthenticator.TYPE_FINGERPRINT;
+ final int userId = 0;
+
+ enrollFingerprintAndFace(userId);
+
+ showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
+
+ mAuthController.onBiometricError(modality,
+ BiometricConstants.BIOMETRIC_PAUSED_REJECTED,
+ 0 /* vendorCode */);
+
+ verify(mDialog1).onAuthenticationFailed(mModalityCaptor.capture(), mMessageCaptor.capture());
+
+ assertThat(mModalityCaptor.getValue().intValue()).isEqualTo(modality);
+ assertThat(mMessageCaptor.getValue()).isEqualTo(
+ mContext.getString(R.string.fingerprint_error_not_match));
+ }
+
+ @Test
public void testOnAuthenticationFailedInvoked_whenBiometricTimedOut() {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
final int modality = BiometricAuthenticator.TYPE_FACE;
@@ -499,13 +546,11 @@
final int vendorCode = 0;
mAuthController.onBiometricError(modality, error, vendorCode);
- ArgumentCaptor<Integer> modalityCaptor = ArgumentCaptor.forClass(Integer.class);
- ArgumentCaptor<String> messageCaptor = ArgumentCaptor.forClass(String.class);
- verify(mDialog1).onAuthenticationFailed(modalityCaptor.capture(), messageCaptor.capture());
+ verify(mDialog1).onAuthenticationFailed(mModalityCaptor.capture(), mMessageCaptor.capture());
- assertEquals(modalityCaptor.getValue().intValue(), modality);
- assertEquals(messageCaptor.getValue(),
- FaceManager.getErrorString(mContext, error, vendorCode));
+ assertThat(mModalityCaptor.getValue().intValue()).isEqualTo(modality);
+ assertThat(mMessageCaptor.getValue()).isEqualTo(
+ mContext.getString(R.string.biometric_not_recognized));
}
@Test
@@ -515,12 +560,10 @@
final String helpMessage = "help";
mAuthController.onBiometricHelp(modality, helpMessage);
- ArgumentCaptor<Integer> modalityCaptor = ArgumentCaptor.forClass(Integer.class);
- ArgumentCaptor<String> messageCaptor = ArgumentCaptor.forClass(String.class);
- verify(mDialog1).onHelp(modalityCaptor.capture(), messageCaptor.capture());
+ verify(mDialog1).onHelp(mModalityCaptor.capture(), mMessageCaptor.capture());
- assertEquals(modalityCaptor.getValue().intValue(), modality);
- assertEquals(messageCaptor.getValue(), helpMessage);
+ assertThat(mModalityCaptor.getValue().intValue()).isEqualTo(modality);
+ assertThat(mMessageCaptor.getValue()).isEqualTo(helpMessage);
}
@Test
@@ -531,12 +574,10 @@
final int vendorCode = 0;
mAuthController.onBiometricError(modality, error, vendorCode);
- ArgumentCaptor<Integer> modalityCaptor = ArgumentCaptor.forClass(Integer.class);
- ArgumentCaptor<String> messageCaptor = ArgumentCaptor.forClass(String.class);
- verify(mDialog1).onError(modalityCaptor.capture(), messageCaptor.capture());
+ verify(mDialog1).onError(mModalityCaptor.capture(), mMessageCaptor.capture());
- assertEquals(modalityCaptor.getValue().intValue(), modality);
- assertEquals(messageCaptor.getValue(),
+ assertThat(mModalityCaptor.getValue().intValue()).isEqualTo(modality);
+ assertThat(mMessageCaptor.getValue()).isEqualTo(
FaceManager.getErrorString(mContext, error, vendorCode));
}
@@ -550,7 +591,7 @@
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
verify(mDialog1, never()).onError(anyInt(), anyString());
- verify(mDialog1).animateToCredentialUI();
+ verify(mDialog1).animateToCredentialUI(eq(true));
}
@Test
@@ -563,7 +604,7 @@
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
verify(mDialog1, never()).onError(anyInt(), anyString());
- verify(mDialog1).animateToCredentialUI();
+ verify(mDialog1).animateToCredentialUI(eq(true));
}
@Test
@@ -578,7 +619,7 @@
mAuthController.onBiometricError(modality, error, vendorCode);
verify(mDialog1).onError(
eq(modality), eq(FaceManager.getErrorString(mContext, error, vendorCode)));
- verify(mDialog1, never()).animateToCredentialUI();
+ verify(mDialog1, never()).animateToCredentialUI(eq(true));
}
@Test
@@ -593,7 +634,7 @@
mAuthController.onBiometricError(modality, error, vendorCode);
verify(mDialog1).onError(
eq(modality), eq(FaceManager.getErrorString(mContext, error, vendorCode)));
- verify(mDialog1, never()).animateToCredentialUI();
+ verify(mDialog1, never()).animateToCredentialUI(eq(true));
}
@Test
@@ -998,6 +1039,31 @@
return HAT;
}
+ private void enrollFingerprintAndFace(final int userId) {
+
+ // Enroll fingerprint
+ verify(mFingerprintManager).registerBiometricStateListener(
+ mBiometricStateCaptor.capture());
+ assertFalse(mAuthController.isFingerprintEnrolled(userId));
+
+ mBiometricStateCaptor.getValue().onEnrollmentsChanged(userId,
+ 1 /* sensorId */, true /* hasEnrollments */);
+ waitForIdleSync();
+
+ assertTrue(mAuthController.isFingerprintEnrolled(userId));
+
+ // Enroll face
+ verify(mFaceManager).registerBiometricStateListener(
+ mBiometricStateCaptor.capture());
+ assertFalse(mAuthController.isFaceAuthEnrolled(userId));
+
+ mBiometricStateCaptor.getValue().onEnrollmentsChanged(userId,
+ 2 /* sensorId */, true /* hasEnrollments */);
+ waitForIdleSync();
+
+ assertTrue(mAuthController.isFaceAuthEnrolled(userId));
+ }
+
private final class TestableAuthController extends AuthController {
private int mBuildCount = 0;
private PromptInfo mLastBiometricPromptInfo;
@@ -1012,7 +1078,7 @@
() -> mBiometricPromptCredentialInteractor, () -> mPromptSelectionInteractor,
() -> mCredentialViewModel, () -> mPromptViewModel,
mInteractionJankMonitor, mHandler,
- mBackgroundExecutor, mVibratorHelper, mUdfpsUtils);
+ mBackgroundExecutor, mUdfpsUtils);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java
index 38c9caf..9cb3b1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java
@@ -30,7 +30,11 @@
import android.app.NotificationManager;
import android.hardware.biometrics.BiometricFaceConstants;
import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.biometrics.BiometricStateListener;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.FingerprintManager;
import android.os.Handler;
+import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -69,6 +73,10 @@
Optional<FingerprintReEnrollNotification> mFingerprintReEnrollNotificationOptional;
@Mock
FingerprintReEnrollNotification mFingerprintReEnrollNotification;
+ @Mock
+ FingerprintManager mFingerprintManager;
+ @Mock
+ FaceManager mFaceManager;
private static final String TAG = "BiometricNotificationService";
private static final int FACE_NOTIFICATION_ID = 1;
@@ -81,6 +89,8 @@
private TestableLooper mLooper;
private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
private KeyguardStateController.Callback mKeyguardStateControllerCallback;
+ private BiometricStateListener mFaceStateListener;
+ private BiometricStateListener mFingerprintStateListener;
@Before
public void setUp() {
@@ -99,25 +109,37 @@
mKeyguardUpdateMonitor, mKeyguardStateController, handler,
mNotificationManager,
broadcastReceiver,
- mFingerprintReEnrollNotificationOptional);
+ mFingerprintReEnrollNotificationOptional,
+ mFingerprintManager,
+ mFaceManager);
biometricNotificationService.start();
ArgumentCaptor<KeyguardUpdateMonitorCallback> updateMonitorCallbackArgumentCaptor =
ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
ArgumentCaptor<KeyguardStateController.Callback> stateControllerCallbackArgumentCaptor =
ArgumentCaptor.forClass(KeyguardStateController.Callback.class);
+ ArgumentCaptor<BiometricStateListener> faceStateListenerArgumentCaptor =
+ ArgumentCaptor.forClass(BiometricStateListener.class);
+ ArgumentCaptor<BiometricStateListener> fingerprintStateListenerArgumentCaptor =
+ ArgumentCaptor.forClass(BiometricStateListener.class);
verify(mKeyguardUpdateMonitor).registerCallback(
updateMonitorCallbackArgumentCaptor.capture());
verify(mKeyguardStateController).addCallback(
stateControllerCallbackArgumentCaptor.capture());
+ verify(mFaceManager).registerBiometricStateListener(
+ faceStateListenerArgumentCaptor.capture());
+ verify(mFingerprintManager).registerBiometricStateListener(
+ fingerprintStateListenerArgumentCaptor.capture());
+ mFaceStateListener = faceStateListenerArgumentCaptor.getValue();
+ mFingerprintStateListener = fingerprintStateListenerArgumentCaptor.getValue();
mKeyguardUpdateMonitorCallback = updateMonitorCallbackArgumentCaptor.getValue();
mKeyguardStateControllerCallback = stateControllerCallbackArgumentCaptor.getValue();
}
@Test
- public void testShowFingerprintReEnrollNotification() {
+ public void testShowFingerprintReEnrollNotification_onAcquiredReEnroll() {
when(mKeyguardStateController.isShowing()).thenReturn(false);
mKeyguardUpdateMonitorCallback.onBiometricHelp(
@@ -139,7 +161,7 @@
.isEqualTo(ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG);
}
@Test
- public void testShowFaceReEnrollNotification() {
+ public void testShowFaceReEnrollNotification_onErrorReEnroll() {
when(mKeyguardStateController.isShowing()).thenReturn(false);
mKeyguardUpdateMonitorCallback.onBiometricError(
@@ -161,4 +183,52 @@
.isEqualTo(ACTION_SHOW_FACE_REENROLL_DIALOG);
}
+ @Test
+ public void testCancelReEnrollmentNotification_onFaceEnrollmentStateChange() {
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
+
+ mKeyguardUpdateMonitorCallback.onBiometricError(
+ BiometricFaceConstants.BIOMETRIC_ERROR_RE_ENROLL,
+ "Testing Face Re-enrollment" /* errString */,
+ BiometricSourceType.FACE
+ );
+ mKeyguardStateControllerCallback.onKeyguardShowingChanged();
+
+ mLooper.moveTimeForward(SHOW_NOTIFICATION_DELAY_MS);
+ mLooper.processAllMessages();
+
+ verify(mNotificationManager).notifyAsUser(eq(TAG), eq(FACE_NOTIFICATION_ID),
+ mNotificationArgumentCaptor.capture(), any());
+
+ mFaceStateListener.onEnrollmentsChanged(0 /* userId */, 0 /* sensorId */,
+ false /* hasEnrollments */);
+
+ verify(mNotificationManager).cancelAsUser(eq(TAG), eq(FACE_NOTIFICATION_ID),
+ eq(UserHandle.CURRENT));
+ }
+
+ @Test
+ public void testCancelReEnrollmentNotification_onFingerprintEnrollmentStateChange() {
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
+
+ mKeyguardUpdateMonitorCallback.onBiometricHelp(
+ FINGERPRINT_ACQUIRED_RE_ENROLL,
+ "Testing Fingerprint Re-enrollment" /* errString */,
+ BiometricSourceType.FINGERPRINT
+ );
+ mKeyguardStateControllerCallback.onKeyguardShowingChanged();
+
+ mLooper.moveTimeForward(SHOW_NOTIFICATION_DELAY_MS);
+ mLooper.processAllMessages();
+
+ verify(mNotificationManager).notifyAsUser(eq(TAG), eq(FINGERPRINT_NOTIFICATION_ID),
+ mNotificationArgumentCaptor.capture(), any());
+
+ mFingerprintStateListener.onEnrollmentsChanged(0 /* userId */, 0 /* sensorId */,
+ false /* hasEnrollments */);
+
+ verify(mNotificationManager).cancelAsUser(eq(TAG), eq(FINGERPRINT_NOTIFICATION_ID),
+ eq(UserHandle.CURRENT));
+ }
+
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 7834191..7578cc7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -442,6 +442,16 @@
}
@Test
+ public void showUdfpsOverlay_callsListener() throws RemoteException {
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+ mFgExecutor.runAllReady();
+
+ verify(mFingerprintManager).onUdfpsUiEvent(FingerprintManager.UDFPS_UI_OVERLAY_SHOWN,
+ TEST_REQUEST_ID, mOpticalProps.sensorId);
+ }
+
+ @Test
public void testSubscribesToOrientationChangesWhenShowingOverlay() throws Exception {
mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
@@ -761,17 +771,20 @@
inOrder.verify(mAlternateTouchProvider).onUiReady();
inOrder.verify(mLatencyTracker).onActionEnd(
eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
- verify(mFingerprintManager, never()).onUiReady(anyLong(), anyInt());
+ verify(mFingerprintManager, never()).onUdfpsUiEvent(
+ eq(FingerprintManager.UDFPS_UI_READY), anyLong(), anyInt());
} else {
InOrder inOrder = inOrder(mFingerprintManager, mLatencyTracker);
- inOrder.verify(mFingerprintManager).onUiReady(eq(TEST_REQUEST_ID),
+ inOrder.verify(mFingerprintManager).onUdfpsUiEvent(
+ eq(FingerprintManager.UDFPS_UI_READY), eq(TEST_REQUEST_ID),
eq(testParams.sensorProps.sensorId));
inOrder.verify(mLatencyTracker).onActionEnd(
eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
verify(mAlternateTouchProvider, never()).onUiReady();
}
} else {
- verify(mFingerprintManager, never()).onUiReady(anyLong(), anyInt());
+ verify(mFingerprintManager, never()).onUdfpsUiEvent(
+ eq(FingerprintManager.UDFPS_UI_READY), anyLong(), anyInt());
verify(mAlternateTouchProvider, never()).onUiReady();
verify(mLatencyTracker, never()).onActionEnd(
eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
@@ -1327,12 +1340,22 @@
// WHEN ACTION_DOWN is received
when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
processorResultDown);
- MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
- mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+ MotionEvent event = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
mBiometricExecutor.runAllReady();
- downEvent.recycle();
- // THEN the touch is pilfered
+ // WHEN ACTION_MOVE is received after
+ final TouchProcessorResult processorResultUnchanged =
+ new TouchProcessorResult.ProcessedTouch(
+ InteractionEvent.UNCHANGED, 1 /* pointerId */, touchData);
+ when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+ processorResultUnchanged);
+ event.setAction(ACTION_MOVE);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
+ mBiometricExecutor.runAllReady();
+ event.recycle();
+
+ // THEN only pilfer once on the initial down
verify(mInputManager).pilferPointers(any());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt
new file mode 100644
index 0000000..0df4fbf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2023 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.systemui.biometrics.data.repository
+
+import android.database.ContentObserver
+import android.os.Handler
+import android.provider.Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.mockito.captureMany
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.settings.SecureSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+private const val USER_ID = 8
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class FaceSettingsRepositoryImplTest : SysuiTestCase() {
+
+ @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+ private val testScope = TestScope()
+
+ @Mock private lateinit var mainHandler: Handler
+ @Mock private lateinit var secureSettings: SecureSettings
+
+ private lateinit var repository: FaceSettingsRepositoryImpl
+
+ @Before
+ fun setup() {
+ repository = FaceSettingsRepositoryImpl(mainHandler, secureSettings)
+ }
+
+ @Test
+ fun createsOneRepositoryPerUser() =
+ testScope.runTest {
+ val userRepo = repository.forUser(USER_ID)
+
+ assertThat(userRepo.userId).isEqualTo(USER_ID)
+
+ assertThat(repository.forUser(USER_ID)).isSameInstanceAs(userRepo)
+ assertThat(repository.forUser(USER_ID + 1)).isNotSameInstanceAs(userRepo)
+ }
+
+ @Test
+ fun startsRepoImmediatelyWithAllSettingKeys() =
+ testScope.runTest {
+ val userRepo = repository.forUser(USER_ID)
+
+ val keys =
+ captureMany<String> {
+ verify(secureSettings)
+ .registerContentObserverForUser(capture(), anyBoolean(), any(), eq(USER_ID))
+ }
+
+ assertThat(keys).containsExactly(FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION)
+ }
+
+ @Test
+ fun forwardsSettingsValues() = runTest {
+ val userRepo = repository.forUser(USER_ID)
+
+ val intAsBooleanSettings =
+ listOf(
+ FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION to
+ collectLastValue(userRepo.alwaysRequireConfirmationInApps)
+ )
+
+ for ((setting, accessor) in intAsBooleanSettings) {
+ val observer =
+ withArgCaptor<ContentObserver> {
+ verify(secureSettings)
+ .registerContentObserverForUser(
+ eq(setting),
+ anyBoolean(),
+ capture(),
+ eq(USER_ID)
+ )
+ }
+
+ for (value in listOf(true, false)) {
+ secureSettings.mockIntSetting(setting, if (value) 1 else 0)
+ observer.onChange(false)
+ assertThat(accessor()).isEqualTo(value)
+ }
+ }
+ }
+
+ private fun SecureSettings.mockIntSetting(key: String, value: Int) {
+ whenever(getIntForUser(eq(key), anyInt(), eq(USER_ID))).thenReturn(value)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
index 4836af6..ec7ce63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 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.systemui.biometrics.data.repository
import android.hardware.biometrics.PromptInfo
@@ -5,12 +21,16 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.shared.model.PromptKind
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -21,61 +41,109 @@
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
+private const val USER_ID = 9
+private const val CHALLENGE = 90L
+
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class PromptRepositoryImplTest : SysuiTestCase() {
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+ private val testScope = TestScope()
+ private val faceSettings = FakeFaceSettingsRepository()
+
@Mock private lateinit var authController: AuthController
private lateinit var repository: PromptRepositoryImpl
@Before
fun setup() {
- repository = PromptRepositoryImpl(authController)
+ repository = PromptRepositoryImpl(faceSettings, authController)
}
@Test
- fun isShowing() = runBlockingTest {
- whenever(authController.isShowing).thenReturn(true)
+ fun isShowing() =
+ testScope.runTest {
+ whenever(authController.isShowing).thenReturn(true)
- val values = mutableListOf<Boolean>()
- val job = launch { repository.isShowing.toList(values) }
- assertThat(values).containsExactly(true)
+ val values = mutableListOf<Boolean>()
+ val job = launch { repository.isShowing.toList(values) }
+ runCurrent()
- withArgCaptor<AuthController.Callback> {
- verify(authController).addCallback(capture())
+ assertThat(values).containsExactly(true)
- value.onBiometricPromptShown()
- assertThat(values).containsExactly(true, true)
+ withArgCaptor<AuthController.Callback> {
+ verify(authController).addCallback(capture())
- value.onBiometricPromptDismissed()
- assertThat(values).containsExactly(true, true, false).inOrder()
+ value.onBiometricPromptShown()
+ runCurrent()
+ assertThat(values).containsExactly(true, true)
- job.cancel()
- verify(authController).removeCallback(eq(value))
+ value.onBiometricPromptDismissed()
+ runCurrent()
+ assertThat(values).containsExactly(true, true, false).inOrder()
+
+ job.cancel()
+ runCurrent()
+ verify(authController).removeCallback(eq(value))
+ }
}
- }
@Test
- fun setsAndUnsetsPrompt() = runBlockingTest {
- val kind = PromptKind.Pin
- val uid = 8
- val challenge = 90L
- val promptInfo = PromptInfo()
+ fun isConfirmationRequired_whenNotForced() =
+ testScope.runTest {
+ faceSettings.setUserSettings(USER_ID, alwaysRequireConfirmationInApps = false)
+ val isConfirmationRequired by collectLastValue(repository.isConfirmationRequired)
- repository.setPrompt(promptInfo, uid, challenge, kind)
+ for (case in listOf(true, false)) {
+ repository.setPrompt(
+ PromptInfo().apply { isConfirmationRequested = case },
+ USER_ID,
+ CHALLENGE,
+ PromptKind.Biometric()
+ )
- assertThat(repository.kind.value).isEqualTo(kind)
- assertThat(repository.userId.value).isEqualTo(uid)
- assertThat(repository.challenge.value).isEqualTo(challenge)
- assertThat(repository.promptInfo.value).isSameInstanceAs(promptInfo)
+ assertThat(isConfirmationRequired).isEqualTo(case)
+ }
+ }
- repository.unsetPrompt()
+ @Test
+ fun isConfirmationRequired_whenForced() =
+ testScope.runTest {
+ faceSettings.setUserSettings(USER_ID, alwaysRequireConfirmationInApps = true)
+ val isConfirmationRequired by collectLastValue(repository.isConfirmationRequired)
- assertThat(repository.promptInfo.value).isNull()
- assertThat(repository.userId.value).isNull()
- assertThat(repository.challenge.value).isNull()
- }
+ for (case in listOf(true, false)) {
+ repository.setPrompt(
+ PromptInfo().apply { isConfirmationRequested = case },
+ USER_ID,
+ CHALLENGE,
+ PromptKind.Biometric()
+ )
+
+ assertThat(isConfirmationRequired).isTrue()
+ }
+ }
+
+ @Test
+ fun setsAndUnsetsPrompt() =
+ testScope.runTest {
+ val kind = PromptKind.Pin
+ val promptInfo = PromptInfo()
+
+ repository.setPrompt(promptInfo, USER_ID, CHALLENGE, kind)
+
+ assertThat(repository.kind.value).isEqualTo(kind)
+ assertThat(repository.userId.value).isEqualTo(USER_ID)
+ assertThat(repository.challenge.value).isEqualTo(CHALLENGE)
+ assertThat(repository.promptInfo.value).isSameInstanceAs(promptInfo)
+
+ repository.unsetPrompt()
+
+ assertThat(repository.promptInfo.value).isNull()
+ assertThat(repository.userId.value).isNull()
+ assertThat(repository.challenge.value).isNull()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
index a62ea3b..81cbaea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
@@ -106,17 +106,11 @@
val currentPrompt by collectLastValue(interactor.prompt)
val credentialKind by collectLastValue(interactor.credentialKind)
val isCredentialAllowed by collectLastValue(interactor.isCredentialAllowed)
- val isExplicitConfirmationRequired by collectLastValue(interactor.isConfirmationRequested)
+ val isExplicitConfirmationRequired by collectLastValue(interactor.isConfirmationRequired)
assertThat(currentPrompt).isNull()
- interactor.useBiometricsForAuthentication(
- info,
- confirmationRequired,
- USER_ID,
- CHALLENGE,
- modalities
- )
+ interactor.useBiometricsForAuthentication(info, USER_ID, CHALLENGE, modalities)
assertThat(currentPrompt).isNotNull()
assertThat(currentPrompt?.title).isEqualTo(TITLE)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
index 689bb00..278a43e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
@@ -18,7 +18,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.domain.model.BiometricModality
+import com.android.systemui.biometrics.shared.model.BiometricModality
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -33,6 +33,7 @@
with(PromptAuthState(isAuthenticated = false)) {
assertThat(isNotAuthenticated).isTrue()
assertThat(isAuthenticatedAndConfirmed).isFalse()
+ assertThat(isAuthenticatedAndExplicitlyConfirmed).isFalse()
assertThat(isAuthenticatedByFace).isFalse()
assertThat(isAuthenticatedByFingerprint).isFalse()
}
@@ -43,6 +44,7 @@
with(PromptAuthState(isAuthenticated = true)) {
assertThat(isNotAuthenticated).isFalse()
assertThat(isAuthenticatedAndConfirmed).isTrue()
+ assertThat(isAuthenticatedAndExplicitlyConfirmed).isFalse()
assertThat(isAuthenticatedByFace).isFalse()
assertThat(isAuthenticatedByFingerprint).isFalse()
}
@@ -50,10 +52,12 @@
with(PromptAuthState(isAuthenticated = true, needsUserConfirmation = true)) {
assertThat(isNotAuthenticated).isFalse()
assertThat(isAuthenticatedAndConfirmed).isFalse()
+ assertThat(isAuthenticatedAndExplicitlyConfirmed).isFalse()
assertThat(isAuthenticatedByFace).isFalse()
assertThat(isAuthenticatedByFingerprint).isFalse()
- assertThat(asConfirmed().isAuthenticatedAndConfirmed).isTrue()
+ assertThat(asExplicitlyConfirmed().isAuthenticatedAndConfirmed).isTrue()
+ assertThat(asExplicitlyConfirmed().isAuthenticatedAndExplicitlyConfirmed).isTrue()
}
}
@@ -64,6 +68,7 @@
) {
assertThat(isNotAuthenticated).isFalse()
assertThat(isAuthenticatedAndConfirmed).isTrue()
+ assertThat(isAuthenticatedAndExplicitlyConfirmed).isFalse()
assertThat(isAuthenticatedByFace).isTrue()
assertThat(isAuthenticatedByFingerprint).isFalse()
}
@@ -79,6 +84,7 @@
) {
assertThat(isNotAuthenticated).isFalse()
assertThat(isAuthenticatedAndConfirmed).isTrue()
+ assertThat(isAuthenticatedAndExplicitlyConfirmed).isFalse()
assertThat(isAuthenticatedByFace).isFalse()
assertThat(isAuthenticatedByFingerprint).isTrue()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 3ba6004..91140a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -27,11 +27,14 @@
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
import com.android.systemui.biometrics.domain.model.BiometricModalities
-import com.android.systemui.biometrics.domain.model.BiometricModality
import com.android.systemui.biometrics.extractAuthenticatorTypes
import com.android.systemui.biometrics.faceSensorPropertiesInternal
import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
+import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
@@ -45,6 +48,9 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
private const val USER_ID = 4
@@ -58,6 +64,7 @@
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
@Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var vibrator: VibratorHelper
private val testScope = TestScope()
private val promptRepository = FakePromptRepository()
@@ -70,11 +77,11 @@
selector = PromptSelectorInteractorImpl(promptRepository, lockPatternUtils)
selector.resetPrompt()
- viewModel = PromptViewModel(selector)
+ viewModel = PromptViewModel(selector, vibrator)
}
@Test
- fun `start idle and show authenticating`() =
+ fun start_idle_and_show_authenticating() =
runGenericTest(doNotStart = true) {
val expectedSize =
if (testCase.shouldStartAsImplicitFlow) PromptSize.SMALL else PromptSize.MEDIUM
@@ -107,7 +114,7 @@
}
@Test
- fun `shows authenticated - no errors`() = runGenericTest {
+ fun shows_authenticated_with_no_errors() = runGenericTest {
// this case can't happen until fingerprint is started
// trigger it now since no error has occurred in this test
val forceError = testCase.isCoex && testCase.authenticatedByFingerprint
@@ -124,6 +131,24 @@
)
}
+ @Test
+ fun play_haptic_on_confirm_when_confirmation_required_otherwise_on_authenticated() =
+ runGenericTest {
+ val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
+
+ viewModel.showAuthenticated(testCase.authenticatedModality, 1_000L)
+
+ verify(vibrator, if (expectConfirmation) never() else times(1))
+ .vibrateAuthSuccess(any())
+
+ if (expectConfirmation) {
+ viewModel.confirmAuthenticated()
+ }
+
+ verify(vibrator).vibrateAuthSuccess(any())
+ verify(vibrator, never()).vibrateAuthError(any())
+ }
+
private suspend fun TestScope.showAuthenticated(
authenticatedModality: BiometricModality,
expectConfirmation: Boolean,
@@ -172,7 +197,7 @@
}
@Test
- fun `shows temporary errors`() = runGenericTest {
+ fun shows_temporary_errors() = runGenericTest {
val checkAtEnd = suspend { assertButtonsVisible(negative = true) }
showTemporaryErrors(restart = false) { checkAtEnd() }
@@ -180,6 +205,32 @@
showTemporaryErrors(restart = true) { checkAtEnd() }
}
+ @Test
+ fun plays_haptic_on_errors() = runGenericTest {
+ viewModel.showTemporaryError(
+ "so sad",
+ messageAfterError = "",
+ authenticateAfterError = false,
+ hapticFeedback = true,
+ )
+
+ verify(vibrator).vibrateAuthError(any())
+ verify(vibrator, never()).vibrateAuthSuccess(any())
+ }
+
+ @Test
+ fun plays_haptic_on_errors_unless_skipped() = runGenericTest {
+ viewModel.showTemporaryError(
+ "still sad",
+ messageAfterError = "",
+ authenticateAfterError = false,
+ hapticFeedback = false,
+ )
+
+ verify(vibrator, never()).vibrateAuthError(any())
+ verify(vibrator, never()).vibrateAuthSuccess(any())
+ }
+
private suspend fun TestScope.showTemporaryErrors(
restart: Boolean,
helpAfterError: String = "",
@@ -233,7 +284,7 @@
}
@Test
- fun `no errors or temporary help after authenticated`() = runGenericTest {
+ fun no_errors_or_temporary_help_after_authenticated() = runGenericTest {
val authenticating by collectLastValue(viewModel.isAuthenticating)
val authenticated by collectLastValue(viewModel.isAuthenticated)
val message by collectLastValue(viewModel.message)
@@ -249,7 +300,13 @@
assertThat(canTryAgain).isFalse()
}
- val errorJob = launch { viewModel.showTemporaryError("error") }
+ val errorJob = launch {
+ viewModel.showTemporaryError(
+ "error",
+ messageAfterError = "",
+ authenticateAfterError = false,
+ )
+ }
verifyNoError()
errorJob.join()
verifyNoError()
@@ -268,16 +325,70 @@
assertThat(messageIsShowing).isTrue()
}
- // @Test
- fun `suppress errors`() = runGenericTest {
- val errorMessage = "woot"
- val message by collectLastValue(viewModel.message)
+ @Test
+ fun suppress_temporary_error() = runGenericTest {
+ val messages by collectValues(viewModel.message)
- val errorJob = launch { viewModel.showTemporaryError(errorMessage) }
+ for (error in listOf("never", "see", "me")) {
+ launch {
+ viewModel.showTemporaryError(
+ error,
+ messageAfterError = "or me",
+ authenticateAfterError = false,
+ suppressIf = { _ -> true },
+ )
+ }
+ }
+
+ testScheduler.advanceUntilIdle()
+ assertThat(messages).containsExactly(PromptMessage.Empty)
}
@Test
- fun `authenticated at most once`() = runGenericTest {
+ fun suppress_temporary_error_when_already_showing_when_requested() =
+ suppress_temporary_error_when_already_showing(suppress = true)
+
+ @Test
+ fun do_not_suppress_temporary_error_when_already_showing_when_not_requested() =
+ suppress_temporary_error_when_already_showing(suppress = false)
+
+ private fun suppress_temporary_error_when_already_showing(suppress: Boolean) = runGenericTest {
+ val errors = listOf("woot", "oh yeah", "nope")
+ val afterSuffix = "(after)"
+ val expectedErrorMessage = if (suppress) errors.first() else errors.last()
+ val messages by collectValues(viewModel.message)
+
+ for (error in errors) {
+ launch {
+ viewModel.showTemporaryError(
+ error,
+ messageAfterError = "$error $afterSuffix",
+ authenticateAfterError = false,
+ suppressIf = { currentMessage -> suppress && currentMessage.isError },
+ )
+ }
+ }
+
+ testScheduler.runCurrent()
+ assertThat(messages)
+ .containsExactly(
+ PromptMessage.Empty,
+ PromptMessage.Error(expectedErrorMessage),
+ )
+ .inOrder()
+
+ testScheduler.advanceUntilIdle()
+ assertThat(messages)
+ .containsExactly(
+ PromptMessage.Empty,
+ PromptMessage.Error(expectedErrorMessage),
+ PromptMessage.Help("$expectedErrorMessage $afterSuffix"),
+ )
+ .inOrder()
+ }
+
+ @Test
+ fun authenticated_at_most_once() = runGenericTest {
val authenticating by collectLastValue(viewModel.isAuthenticating)
val authenticated by collectLastValue(viewModel.isAuthenticated)
@@ -293,7 +404,7 @@
}
@Test
- fun `authenticating cannot restart after authenticated`() = runGenericTest {
+ fun authenticating_cannot_restart_after_authenticated() = runGenericTest {
val authenticating by collectLastValue(viewModel.isAuthenticating)
val authenticated by collectLastValue(viewModel.isAuthenticated)
@@ -309,7 +420,7 @@
}
@Test
- fun `confirm authentication`() = runGenericTest {
+ fun confirm_authentication() = runGenericTest {
val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
viewModel.showAuthenticated(testCase.authenticatedModality, 0)
@@ -341,7 +452,7 @@
}
@Test
- fun `cannot confirm unless authenticated`() = runGenericTest {
+ fun cannot_confirm_unless_authenticated() = runGenericTest {
val authenticating by collectLastValue(viewModel.isAuthenticating)
val authenticated by collectLastValue(viewModel.isAuthenticated)
@@ -360,7 +471,7 @@
}
@Test
- fun `shows help - before authenticated`() = runGenericTest {
+ fun shows_help_before_authenticated() = runGenericTest {
val helpMessage = "please help yourself to some cookies"
val message by collectLastValue(viewModel.message)
val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
@@ -379,7 +490,7 @@
}
@Test
- fun `shows help - after authenticated`() = runGenericTest {
+ fun shows_help_after_authenticated() = runGenericTest {
val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
val helpMessage = "more cookies please"
val authenticating by collectLastValue(viewModel.isAuthenticating)
@@ -409,7 +520,7 @@
}
@Test
- fun `retries after failure`() = runGenericTest {
+ fun retries_after_failure() = runGenericTest {
val errorMessage = "bad"
val helpMessage = "again?"
val expectTryAgainButton = testCase.isFaceOnly
@@ -455,7 +566,7 @@
}
@Test
- fun `switch to credential fallback`() = runGenericTest {
+ fun switch_to_credential_fallback() = runGenericTest {
val size by collectLastValue(viewModel.size)
// TODO(b/251476085): remove Spaghetti, migrate logic, and update this test
@@ -631,7 +742,6 @@
}
useBiometricsForAuthentication(
info,
- requireConfirmation,
USER_ID,
CHALLENGE,
BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java
new file mode 100644
index 0000000..9d9b263
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 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.systemui.globalactions;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNull;
+
+import android.os.PowerManager;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.BlurUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ShutdownUiTest extends SysuiTestCase {
+
+ ShutdownUi mShutdownUi;
+ @Mock
+ BlurUtils mBlurUtils;
+
+ @Before
+ public void setUp() throws Exception {
+ mShutdownUi = new ShutdownUi(getContext(), mBlurUtils);
+ }
+
+ @Test
+ public void getRebootMessage_update() {
+ int messageId = mShutdownUi.getRebootMessage(true, PowerManager.REBOOT_RECOVERY_UPDATE);
+ assertEquals(messageId, R.string.reboot_to_update_reboot);
+ }
+
+ @Test
+ public void getRebootMessage_rebootDefault() {
+ int messageId = mShutdownUi.getRebootMessage(true, "anything-else");
+ assertEquals(messageId, R.string.reboot_to_reset_message);
+ }
+
+ @Test
+ public void getRebootMessage_shutdown() {
+ int messageId = mShutdownUi.getRebootMessage(false, "anything-else");
+ assertEquals(messageId, R.string.shutdown_progress);
+ }
+
+ @Test
+ public void getReasonMessage_update() {
+ String message = mShutdownUi.getReasonMessage(PowerManager.REBOOT_RECOVERY_UPDATE);
+ assertEquals(message, mContext.getString(R.string.reboot_to_update_title));
+ }
+
+ @Test
+ public void getReasonMessage_rebootDefault() {
+ String message = mShutdownUi.getReasonMessage(PowerManager.REBOOT_RECOVERY);
+ assertEquals(message, mContext.getString(R.string.reboot_to_reset_title));
+ }
+
+ @Test
+ public void getRebootMessage_defaultToNone() {
+ String message = mShutdownUi.getReasonMessage("anything-else");
+ assertNull(message);
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFaceSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFaceSettingsRepository.kt
new file mode 100644
index 0000000..af2706e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFaceSettingsRepository.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 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.systemui.biometrics.data.repository
+
+import kotlinx.coroutines.flow.flowOf
+
+/** Fake settings for tests. */
+class FakeFaceSettingsRepository : FaceSettingsRepository {
+
+ private val userRepositories = mutableMapOf<Int, FaceUserSettingsRepository>()
+
+ /** Add fixed settings for a user. */
+ fun setUserSettings(userId: Int, alwaysRequireConfirmationInApps: Boolean = false) {
+ userRepositories[userId] =
+ object : FaceUserSettingsRepository {
+ override val userId = userId
+ override val alwaysRequireConfirmationInApps =
+ flowOf(alwaysRequireConfirmationInApps)
+ }
+ }
+
+ override fun forUser(id: Int?) = userRepositories[id] ?: FaceUserSettingsRepositoryImpl.Empty
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
index d270700..42ec8fed 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
@@ -31,13 +31,20 @@
userId: Int,
gatekeeperChallenge: Long?,
kind: PromptKind,
- requireConfirmation: Boolean,
+ ) = setPrompt(promptInfo, userId, gatekeeperChallenge, kind, forceConfirmation = false)
+
+ fun setPrompt(
+ promptInfo: PromptInfo,
+ userId: Int,
+ gatekeeperChallenge: Long?,
+ kind: PromptKind,
+ forceConfirmation: Boolean = false,
) {
_promptInfo.value = promptInfo
_userId.value = userId
_challenge.value = gatekeeperChallenge
_kind.value = kind
- _isConfirmationRequired.value = requireConfirmation
+ _isConfirmationRequired.value = promptInfo.isConfirmationRequested || forceConfirmation
}
override fun unsetPrompt() {
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index e4a5a3e..ca15dd7 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -2166,7 +2166,7 @@
@Override
public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
synchronized (mUidObserverLock) {
- if (ActivityManager.isProcStateBackground(procState)) {
+ if (procState != ActivityManager.PROCESS_STATE_TOP) {
disableGameMode(uid);
return;
}
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 8ef2a1b..cb5e7f1 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -21,7 +21,10 @@
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR_BASE;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE;
+import static com.android.server.biometrics.BiometricSensor.STATE_CANCELING;
+import static com.android.server.biometrics.BiometricSensor.STATE_UNKNOWN;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTHENTICATED_PENDING_SYSUI;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_CALLED;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_IDLE;
@@ -439,6 +442,13 @@
return false;
}
+ final boolean errorLockout = error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
+ || error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
+ if (errorLockout) {
+ cancelAllSensors(sensor -> Utils.isAtLeastStrength(sensorIdToStrength(sensorId),
+ sensor.getCurrentStrength()));
+ }
+
mErrorEscrow = error;
mVendorCodeEscrow = vendorCode;
@@ -477,8 +487,6 @@
case STATE_AUTH_STARTED:
case STATE_AUTH_STARTED_UI_SHOWING: {
- final boolean errorLockout = error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
- || error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
if (isAllowDeviceCredential() && errorLockout) {
// SystemUI handles transition from biometric to device credential.
mState = STATE_SHOWING_DEVICE_CREDENTIAL;
@@ -675,7 +683,9 @@
}
private boolean pauseSensorIfSupported(int sensorId) {
- if (sensorIdToModality(sensorId) == TYPE_FACE) {
+ boolean isSensorCancelling = sensorIdToState(sensorId) == STATE_CANCELING;
+ // If the sensor is locked out, canceling sensors operation is handled in onErrorReceived()
+ if (sensorIdToModality(sensorId) == TYPE_FACE && !isSensorCancelling) {
cancelAllSensors(sensor -> sensor.id == sensorId);
return true;
}
@@ -948,6 +958,27 @@
return TYPE_NONE;
}
+ private @BiometricSensor.SensorState int sensorIdToState(int sensorId) {
+ for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
+ if (sensorId == sensor.id) {
+ return sensor.getSensorState();
+ }
+ }
+ Slog.e(TAG, "Unknown sensor: " + sensorId);
+ return STATE_UNKNOWN;
+ }
+
+ @BiometricManager.Authenticators.Types
+ private int sensorIdToStrength(int sensorId) {
+ for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
+ if (sensorId == sensor.id) {
+ return sensor.getCurrentStrength();
+ }
+ }
+ Slog.e(TAG, "Unknown sensor: " + sensorId);
+ return BIOMETRIC_CONVENIENCE;
+ }
+
private String getAcquiredMessageForSensor(int sensorId, int acquiredInfo, int vendorCode) {
final @Modality int modality = sensorIdToModality(sensorId);
switch (modality) {
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java b/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
new file mode 100644
index 0000000..6727fbc
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 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.biometrics;
+
+/**
+ * Interface for biometric operations to get camera privacy state.
+ */
+public interface BiometricSensorPrivacy {
+ /* Returns true if privacy is enabled and camera access is disabled. */
+ boolean isCameraPrivacyEnabled();
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacyImpl.java b/services/core/java/com/android/server/biometrics/BiometricSensorPrivacyImpl.java
new file mode 100644
index 0000000..b6701da
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/BiometricSensorPrivacyImpl.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 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.biometrics;
+
+import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
+
+import android.annotation.Nullable;
+import android.hardware.SensorPrivacyManager;
+
+public class BiometricSensorPrivacyImpl implements
+ BiometricSensorPrivacy {
+ private final SensorPrivacyManager mSensorPrivacyManager;
+
+ public BiometricSensorPrivacyImpl(@Nullable SensorPrivacyManager sensorPrivacyManager) {
+ mSensorPrivacyManager = sensorPrivacyManager;
+ }
+
+ @Override
+ public boolean isCameraPrivacyEnabled() {
+ return mSensorPrivacyManager != null && mSensorPrivacyManager
+ .isSensorPrivacyEnabled(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, CAMERA);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 0942d85..1fa97a3 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -33,6 +33,7 @@
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
+import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
@@ -124,6 +125,8 @@
AuthSession mAuthSession;
private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final BiometricSensorPrivacy mBiometricSensorPrivacy;
+
/**
* Tracks authenticatorId invalidation. For more details, see
* {@link com.android.server.biometrics.sensors.InvalidationRequesterClient}.
@@ -933,7 +936,7 @@
return PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors,
userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */,
- getContext());
+ getContext(), mBiometricSensorPrivacy);
}
/**
@@ -1026,6 +1029,11 @@
public UserManager getUserManager(Context context) {
return context.getSystemService(UserManager.class);
}
+
+ public BiometricSensorPrivacy getBiometricSensorPrivacy(Context context) {
+ return new BiometricSensorPrivacyImpl(context.getSystemService(
+ SensorPrivacyManager.class));
+ }
}
/**
@@ -1054,6 +1062,7 @@
mRequestCounter = mInjector.getRequestGenerator();
mBiometricContext = injector.getBiometricContext(context);
mUserManager = injector.getUserManager(context);
+ mBiometricSensorPrivacy = injector.getBiometricSensorPrivacy(context);
try {
injector.getActivityManagerService().registerUserSwitchObserver(
@@ -1290,7 +1299,7 @@
final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager,
mDevicePolicyManager, mSettingObserver, mSensors, userId, promptInfo,
opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists(),
- getContext());
+ getContext(), mBiometricSensorPrivacy);
final Pair<Integer, Integer> preAuthStatus = preAuthInfo.getPreAuthenticateStatus();
@@ -1300,9 +1309,7 @@
+ promptInfo.isIgnoreEnrollmentState());
// BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED is added so that BiometricPrompt can
// be shown for this case.
- if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS
- || preAuthStatus.second
- == BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED) {
+ if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS) {
// If BIOMETRIC_WEAK or BIOMETRIC_STRONG are allowed, but not enrolled, but
// CREDENTIAL is requested and available, set the bundle to only request
// CREDENTIAL.
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index 3813fd1..e6f25cb 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -27,7 +27,6 @@
import android.app.admin.DevicePolicyManager;
import android.app.trust.ITrustManager;
import android.content.Context;
-import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.PromptInfo;
@@ -73,13 +72,16 @@
final Context context;
private final boolean mBiometricRequested;
private final int mBiometricStrengthRequested;
+ private final BiometricSensorPrivacy mBiometricSensorPrivacy;
+
private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested,
boolean credentialRequested, List<BiometricSensor> eligibleSensors,
List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable,
boolean confirmationRequested, boolean ignoreEnrollmentState, int userId,
- Context context) {
+ Context context, BiometricSensorPrivacy biometricSensorPrivacy) {
mBiometricRequested = biometricRequested;
mBiometricStrengthRequested = biometricStrengthRequested;
+ mBiometricSensorPrivacy = biometricSensorPrivacy;
this.credentialRequested = credentialRequested;
this.eligibleSensors = eligibleSensors;
@@ -96,7 +98,8 @@
BiometricService.SettingObserver settingObserver,
List<BiometricSensor> sensors,
int userId, PromptInfo promptInfo, String opPackageName,
- boolean checkDevicePolicyManager, Context context)
+ boolean checkDevicePolicyManager, Context context,
+ BiometricSensorPrivacy biometricSensorPrivacy)
throws RemoteException {
final boolean confirmationRequested = promptInfo.isConfirmationRequested();
@@ -124,7 +127,7 @@
checkDevicePolicyManager, requestedStrength,
promptInfo.getAllowedSensorIds(),
promptInfo.isIgnoreEnrollmentState(),
- context);
+ biometricSensorPrivacy);
Slog.d(TAG, "Package: " + opPackageName
+ " Sensor ID: " + sensor.id
@@ -138,7 +141,7 @@
//
// Note: if only a certain sensor is required and the privacy is enabled,
// canAuthenticate() will return false.
- if (status == AUTHENTICATOR_OK || status == BIOMETRIC_SENSOR_PRIVACY_ENABLED) {
+ if (status == AUTHENTICATOR_OK) {
eligibleSensors.add(sensor);
} else {
ineligibleSensors.add(new Pair<>(sensor, status));
@@ -148,7 +151,7 @@
return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested,
eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested,
- promptInfo.isIgnoreEnrollmentState(), userId, context);
+ promptInfo.isIgnoreEnrollmentState(), userId, context, biometricSensorPrivacy);
}
/**
@@ -165,7 +168,7 @@
BiometricSensor sensor, int userId, String opPackageName,
boolean checkDevicePolicyManager, int requestedStrength,
@NonNull List<Integer> requestedSensorIds,
- boolean ignoreEnrollmentState, Context context) {
+ boolean ignoreEnrollmentState, BiometricSensorPrivacy biometricSensorPrivacy) {
if (!requestedSensorIds.isEmpty() && !requestedSensorIds.contains(sensor.id)) {
return BIOMETRIC_NO_HARDWARE;
@@ -191,12 +194,10 @@
&& !ignoreEnrollmentState) {
return BIOMETRIC_NOT_ENROLLED;
}
- final SensorPrivacyManager sensorPrivacyManager = context
- .getSystemService(SensorPrivacyManager.class);
- if (sensorPrivacyManager != null && sensor.modality == TYPE_FACE) {
- if (sensorPrivacyManager
- .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId)) {
+ if (biometricSensorPrivacy != null && sensor.modality == TYPE_FACE) {
+ if (biometricSensorPrivacy.isCameraPrivacyEnabled()) {
+ //Camera privacy is enabled as the access is disabled
return BIOMETRIC_SENSOR_PRIVACY_ENABLED;
}
}
@@ -266,17 +267,30 @@
}
private Pair<BiometricSensor, Integer> calculateErrorByPriority() {
- // If the caller requested STRONG, and the device contains both STRONG and non-STRONG
- // sensors, prioritize BIOMETRIC_NOT_ENROLLED over the weak sensor's
- // BIOMETRIC_INSUFFICIENT_STRENGTH error. Pretty sure we can always prioritize
- // BIOMETRIC_NOT_ENROLLED over any other error (unless of course its calculation is
- // wrong, in which case we should fix that instead).
+ Pair<BiometricSensor, Integer> sensorNotEnrolled = null;
+ Pair<BiometricSensor, Integer> sensorLockout = null;
for (Pair<BiometricSensor, Integer> pair : ineligibleSensors) {
+ int status = pair.second;
+ if (status == BIOMETRIC_LOCKOUT_TIMED || status == BIOMETRIC_LOCKOUT_PERMANENT) {
+ sensorLockout = pair;
+ }
if (pair.second == BIOMETRIC_NOT_ENROLLED) {
- return pair;
+ sensorNotEnrolled = pair;
}
}
+ // If there is a sensor locked out, prioritize lockout over other sensor's error.
+ // See b/286923477.
+ if (sensorLockout != null) {
+ return sensorLockout;
+ }
+
+ // If the caller requested STRONG, and the device contains both STRONG and non-STRONG
+ // sensors, prioritize BIOMETRIC_NOT_ENROLLED over the weak sensor's
+ // BIOMETRIC_INSUFFICIENT_STRENGTH error.
+ if (sensorNotEnrolled != null) {
+ return sensorNotEnrolled;
+ }
return ineligibleSensors.get(0);
}
@@ -292,13 +306,9 @@
@AuthenticatorStatus final int status;
@BiometricAuthenticator.Modality int modality = TYPE_NONE;
- final SensorPrivacyManager sensorPrivacyManager = context
- .getSystemService(SensorPrivacyManager.class);
-
boolean cameraPrivacyEnabled = false;
- if (sensorPrivacyManager != null) {
- cameraPrivacyEnabled = sensorPrivacyManager
- .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId);
+ if (mBiometricSensorPrivacy != null) {
+ cameraPrivacyEnabled = mBiometricSensorPrivacy.isCameraPrivacyEnabled();
}
if (mBiometricRequested && credentialRequested) {
@@ -315,7 +325,7 @@
// and the face sensor privacy is enabled then return
// BIOMETRIC_SENSOR_PRIVACY_ENABLED.
//
- // Note: This sensor will still be eligible for calls to authenticate.
+ // Note: This sensor will not be eligible for calls to authenticate.
status = BIOMETRIC_SENSOR_PRIVACY_ENABLED;
} else {
status = AUTHENTICATOR_OK;
@@ -340,7 +350,7 @@
// If the only modality requested is face and the privacy is enabled
// then return BIOMETRIC_SENSOR_PRIVACY_ENABLED.
//
- // Note: This sensor will still be eligible for calls to authenticate.
+ // Note: This sensor will not be eligible for calls to authenticate.
status = BIOMETRIC_SENSOR_PRIVACY_ENABLED;
} else {
status = AUTHENTICATOR_OK;
diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
index 46c77e8..aa6a0f1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
@@ -210,4 +210,8 @@
public boolean isInterruptable() {
return true;
}
+
+ public boolean isAlreadyCancelled() {
+ return mAlreadyCancelled;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 05ca6e4..6ac1631 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -266,8 +266,12 @@
}
} else {
if (isBackgroundAuth) {
- Slog.e(TAG, "cancelling due to background auth");
- cancel();
+ Slog.e(TAG, "Sending cancel to client(Due to background auth)");
+ if (mTaskStackListener != null) {
+ mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+ }
+ sendCancelOnly(getListener());
+ mCallback.onClientFinished(this, false);
} else {
// Allow system-defined limit of number of attempts before giving up
if (mShouldUseLockoutTracker) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index 7fa4d6c..78c3808 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -268,6 +268,14 @@
return;
}
+ if (mCurrentOperation.isAcquisitionOperation()) {
+ AcquisitionClient client = (AcquisitionClient) mCurrentOperation.getClientMonitor();
+ if (client.isAlreadyCancelled()) {
+ mCurrentOperation.cancel(mHandler, mInternalCallback);
+ return;
+ }
+ }
+
if (mGestureAvailabilityDispatcher != null && mCurrentOperation.isAcquisitionOperation()) {
mGestureAvailabilityDispatcher.markSensorActive(
mCurrentOperation.getSensorId(), true /* active */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
index 2e1a363..1a682a9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
@@ -170,6 +170,12 @@
}
}
+ public void onUdfpsOverlayShown() throws RemoteException {
+ if (mFingerprintServiceReceiver != null) {
+ mFingerprintServiceReceiver.onUdfpsOverlayShown();
+ }
+ }
+
// Face-specific callbacks for FaceManager only
/**
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 7ae31b2..3fce3cc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -43,7 +43,6 @@
import com.android.server.biometrics.log.OperationContextExt;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.AuthenticationClient;
-import com.android.server.biometrics.sensors.BiometricNotificationUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
@@ -240,9 +239,6 @@
vendorCode,
getTargetUserId()));
- if (error == BiometricConstants.BIOMETRIC_ERROR_RE_ENROLL) {
- BiometricNotificationUtils.showReEnrollmentNotification(getContext());
- }
super.onError(error, vendorCode);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index ece35c5..3d6a156 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -929,17 +929,19 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
- public void onUiReady(long requestId, int sensorId) {
- super.onUiReady_enforcePermission();
+ public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event, long requestId,
+ int sensorId) {
+ super.onUdfpsUiEvent_enforcePermission();
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
- Slog.w(TAG, "No matching provider for onUiReady, sensorId: " + sensorId);
+ Slog.w(TAG, "No matching provider for onUdfpsUiEvent, sensorId: " + sensorId);
return;
}
- provider.onUiReady(requestId, sensorId);
+ provider.onUdfpsUiEvent(event, requestId, sensorId);
}
+
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 004af2c..26701c1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -130,7 +130,7 @@
void onPointerUp(long requestId, int sensorId, PointerContext pc);
- void onUiReady(long requestId, int sensorId);
+ void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event, long requestId, int sensorId);
void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java
index da7163a..dce0175 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java
@@ -17,6 +17,7 @@
package com.android.server.biometrics.sensors.fingerprint;
import android.hardware.biometrics.fingerprint.PointerContext;
+import android.hardware.fingerprint.FingerprintManager;
import com.android.server.biometrics.sensors.BaseClientMonitor;
@@ -28,6 +29,6 @@
public interface Udfps {
void onPointerDown(PointerContext pc);
void onPointerUp(PointerContext pc);
- void onUiReady();
+ void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event);
boolean isPointerDown();
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
index 135eccf..ec1eeb1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
@@ -114,6 +114,11 @@
public void onUdfpsPointerUp(int sensorId) {
}
+
+ @Override
+ public void onUdfpsOverlayShown() {
+
+ }
};
BiometricTestSessionImpl(@NonNull Context context, int sensorId,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 2bfc239..3fc36b6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -27,6 +27,7 @@
import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
+import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlay;
@@ -363,9 +364,11 @@
}
@Override
- public void onUiReady() {
+ public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) {
try {
- getFreshDaemon().getSession().onUiReady();
+ if (event == FingerprintManager.UDFPS_UI_READY) {
+ getFreshDaemon().getSession().onUiReady();
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index c2ca78e..d35469c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -265,11 +265,20 @@
}
@Override
- public void onUiReady() {
+ public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) {
try {
- getFreshDaemon().getSession().onUiReady();
+ switch (event) {
+ case FingerprintManager.UDFPS_UI_OVERLAY_SHOWN:
+ getListener().onUdfpsOverlayShown();
+ break;
+ case FingerprintManager.UDFPS_UI_READY:
+ getFreshDaemon().getSession().onUiReady();
+ break;
+ default:
+ Slog.w(TAG, "No matching event for onUdfpsUiEvent");
+ }
} catch (RemoteException e) {
- Slog.e(TAG, "Unable to send UI ready", e);
+ Slog.e(TAG, "Unable to send onUdfpsUiEvent", e);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 58ece89..3e33ef6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -669,14 +669,15 @@
}
@Override
- public void onUiReady(long requestId, int sensorId) {
+ public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event, long requestId,
+ int sensorId) {
mFingerprintSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(
requestId, (client) -> {
if (!(client instanceof Udfps)) {
- Slog.e(getTag(), "onUiReady received during client: " + client);
+ Slog.e(getTag(), "onUdfpsUiEvent received during client: " + client);
return;
}
- ((Udfps) client).onUiReady();
+ ((Udfps) client).onUdfpsUiEvent(event);
});
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
index 86a9f79..c20a9eb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
@@ -115,6 +115,11 @@
public void onUdfpsPointerUp(int sensorId) {
}
+
+ @Override
+ public void onUdfpsOverlayShown() {
+
+ }
};
BiometricTestSessionImpl(@NonNull Context context, int sensorId,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 9e6f4e4..1cbbf89 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -829,13 +829,14 @@
}
@Override
- public void onUiReady(long requestId, int sensorId) {
+ public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event, long requestId,
+ int sensorId) {
mScheduler.getCurrentClientIfMatches(requestId, (client) -> {
if (!(client instanceof Udfps)) {
- Slog.w(TAG, "onUiReady received during client: " + client);
+ Slog.w(TAG, "onUdfpsUiEvent received during client: " + client);
return;
}
- ((Udfps) client).onUiReady();
+ ((Udfps) client).onUdfpsUiEvent(event);
});
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index d22aef8..2a62338 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -27,6 +27,7 @@
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
+import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlay;
@@ -273,7 +274,7 @@
}
@Override
- public void onUiReady() {
+ public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) {
// Unsupported in HIDL.
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index 362c820..ed0a201 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -25,6 +25,7 @@
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
+import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
@@ -130,7 +131,7 @@
}
@Override
- public void onUiReady() {
+ public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) {
// Unsupported in HIDL.
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index 78039ef..c2b7944 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -186,7 +186,7 @@
}
@Override
- public void onUiReady() {
+ public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) {
// Unsupported in HIDL.
}
}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 75709fbb..d647757 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -223,11 +223,11 @@
private final ShortTermModel mShortTermModel;
private final ShortTermModel mPausedShortTermModel;
- // Controls High Brightness Mode.
- private HighBrightnessModeController mHbmController;
+ // Controls Brightness range (including High Brightness Mode).
+ private final BrightnessRangeController mBrightnessRangeController;
// Throttles (caps) maximum allowed brightness
- private BrightnessThrottler mBrightnessThrottler;
+ private final BrightnessThrottler mBrightnessThrottler;
private boolean mIsBrightnessThrottled;
// Context-sensitive brightness configurations require keeping track of the foreground app's
@@ -257,7 +257,8 @@
HysteresisLevels screenBrightnessThresholds,
HysteresisLevels ambientBrightnessThresholdsIdle,
HysteresisLevels screenBrightnessThresholdsIdle, Context context,
- HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
+ BrightnessRangeController brightnessModeController,
+ BrightnessThrottler brightnessThrottler,
BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
int ambientLightHorizonLong, float userLux, float userBrightness) {
this(new Injector(), callbacks, looper, sensorManager, lightSensor,
@@ -267,7 +268,7 @@
darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig,
ambientBrightnessThresholds, screenBrightnessThresholds,
ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, context,
- hbmController, brightnessThrottler, idleModeBrightnessMapper,
+ brightnessModeController, brightnessThrottler, idleModeBrightnessMapper,
ambientLightHorizonShort, ambientLightHorizonLong, userLux, userBrightness
);
}
@@ -283,7 +284,8 @@
HysteresisLevels screenBrightnessThresholds,
HysteresisLevels ambientBrightnessThresholdsIdle,
HysteresisLevels screenBrightnessThresholdsIdle, Context context,
- HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
+ BrightnessRangeController brightnessModeController,
+ BrightnessThrottler brightnessThrottler,
BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
int ambientLightHorizonLong, float userLux, float userBrightness) {
mInjector = injector;
@@ -326,7 +328,7 @@
mPendingForegroundAppPackageName = null;
mForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
mPendingForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
- mHbmController = hbmController;
+ mBrightnessRangeController = brightnessModeController;
mBrightnessThrottler = brightnessThrottler;
mInteractiveModeBrightnessMapper = interactiveModeBrightnessMapper;
mIdleModeBrightnessMapper = idleModeBrightnessMapper;
@@ -607,10 +609,11 @@
pw.println();
pw.println(" mInteractiveMapper=");
- mInteractiveModeBrightnessMapper.dump(pw, mHbmController.getNormalBrightnessMax());
+ mInteractiveModeBrightnessMapper.dump(pw,
+ mBrightnessRangeController.getNormalBrightnessMax());
if (mIdleModeBrightnessMapper != null) {
pw.println(" mIdleMapper=");
- mIdleModeBrightnessMapper.dump(pw, mHbmController.getNormalBrightnessMax());
+ mIdleModeBrightnessMapper.dump(pw, mBrightnessRangeController.getNormalBrightnessMax());
}
pw.println();
@@ -736,7 +739,7 @@
mAmbientDarkeningThreshold =
mAmbientBrightnessThresholds.getDarkeningThreshold(lux);
}
- mHbmController.onAmbientLuxChange(mAmbientLux);
+ mBrightnessRangeController.onAmbientLuxChange(mAmbientLux);
// If the short term model was invalidated and the change is drastic enough, reset it.
@@ -976,9 +979,9 @@
// Clamps values with float range [0.0-1.0]
private float clampScreenBrightness(float value) {
- final float minBrightness = Math.min(mHbmController.getCurrentBrightnessMin(),
+ final float minBrightness = Math.min(mBrightnessRangeController.getCurrentBrightnessMin(),
mBrightnessThrottler.getBrightnessCap());
- final float maxBrightness = Math.min(mHbmController.getCurrentBrightnessMax(),
+ final float maxBrightness = Math.min(mBrightnessRangeController.getCurrentBrightnessMax(),
mBrightnessThrottler.getBrightnessCap());
return MathUtils.constrain(value, minBrightness, maxBrightness);
}
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
new file mode 100644
index 0000000..47cde15
--- /dev/null
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2023 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.display;
+
+import android.hardware.display.BrightnessInfo;
+import android.os.IBinder;
+
+import java.io.PrintWriter;
+import java.util.function.BooleanSupplier;
+
+class BrightnessRangeController {
+
+ private static final boolean NBM_FEATURE_FLAG = false;
+
+ private final HighBrightnessModeController mHbmController;
+ private final NormalBrightnessModeController mNormalBrightnessModeController =
+ new NormalBrightnessModeController();
+
+ private final Runnable mModeChangeCallback;
+
+ BrightnessRangeController(HighBrightnessModeController hbmController,
+ Runnable modeChangeCallback) {
+ mHbmController = hbmController;
+ mModeChangeCallback = modeChangeCallback;
+ }
+
+
+ void dump(PrintWriter pw) {
+ mHbmController.dump(pw);
+ }
+
+ void onAmbientLuxChange(float ambientLux) {
+ applyChanges(
+ () -> mNormalBrightnessModeController.onAmbientLuxChange(ambientLux),
+ () -> mHbmController.onAmbientLuxChange(ambientLux)
+ );
+ }
+
+ float getNormalBrightnessMax() {
+ return mHbmController.getNormalBrightnessMax();
+ }
+
+ void loadFromConfig(HighBrightnessModeMetadata hbmMetadata, IBinder token,
+ DisplayDeviceInfo info, DisplayDeviceConfig displayDeviceConfig) {
+ applyChanges(
+ () -> mNormalBrightnessModeController.resetNbmData(
+ displayDeviceConfig.getLuxThrottlingData()),
+ () -> {
+ mHbmController.setHighBrightnessModeMetadata(hbmMetadata);
+ mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId,
+ displayDeviceConfig.getHighBrightnessModeData(),
+ displayDeviceConfig::getHdrBrightnessFromSdr);
+ }
+ );
+ }
+
+ void stop() {
+ mHbmController.stop();
+ }
+
+ void setAutoBrightnessEnabled(int state) {
+ applyChanges(
+ () -> mNormalBrightnessModeController.setAutoBrightnessState(state),
+ () -> mHbmController.setAutoBrightnessEnabled(state)
+ );
+ }
+
+ void onBrightnessChanged(float brightness, float unthrottledBrightness,
+ @BrightnessInfo.BrightnessMaxReason int throttlingReason) {
+ mHbmController.onBrightnessChanged(brightness, unthrottledBrightness, throttlingReason);
+ }
+
+ float getCurrentBrightnessMin() {
+ return mHbmController.getCurrentBrightnessMin();
+ }
+
+
+ float getCurrentBrightnessMax() {
+ if (NBM_FEATURE_FLAG && mHbmController.getHighBrightnessMode()
+ == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF) {
+ return Math.min(mHbmController.getCurrentBrightnessMax(),
+ mNormalBrightnessModeController.getCurrentBrightnessMax());
+ }
+ return mHbmController.getCurrentBrightnessMax();
+ }
+
+ int getHighBrightnessMode() {
+ return mHbmController.getHighBrightnessMode();
+ }
+
+ float getHdrBrightnessValue() {
+ return mHbmController.getHdrBrightnessValue();
+ }
+
+ float getTransitionPoint() {
+ return mHbmController.getTransitionPoint();
+ }
+
+ private void applyChanges(BooleanSupplier nbmChangesFunc, Runnable hbmChangesFunc) {
+ if (NBM_FEATURE_FLAG) {
+ boolean nbmTransitionChanged = nbmChangesFunc.getAsBoolean();
+ hbmChangesFunc.run();
+ // if nbm transition changed - trigger callback
+ // HighBrightnessModeController handles sending changes itself
+ if (nbmTransitionChanged) {
+ mModeChangeCallback.run();
+ }
+ } else {
+ hbmChangesFunc.run();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 7a797dd..7ccfb44 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -41,6 +41,7 @@
import com.android.internal.display.BrightnessSynchronizer;
import com.android.server.display.config.AutoBrightness;
import com.android.server.display.config.BlockingZoneConfig;
+import com.android.server.display.config.BrightnessLimitMap;
import com.android.server.display.config.BrightnessThresholds;
import com.android.server.display.config.BrightnessThrottlingMap;
import com.android.server.display.config.BrightnessThrottlingPoint;
@@ -51,8 +52,11 @@
import com.android.server.display.config.HbmTiming;
import com.android.server.display.config.HighBrightnessMode;
import com.android.server.display.config.IntegerArray;
+import com.android.server.display.config.LuxThrottling;
import com.android.server.display.config.NitsMap;
+import com.android.server.display.config.NonNegativeFloatToFloatPoint;
import com.android.server.display.config.Point;
+import com.android.server.display.config.PredefinedBrightnessLimitNames;
import com.android.server.display.config.RefreshRateConfigs;
import com.android.server.display.config.RefreshRateRange;
import com.android.server.display.config.RefreshRateThrottlingMap;
@@ -219,6 +223,22 @@
* <allowInLowPowerMode>false</allowInLowPowerMode>
* </highBrightnessMode>
*
+ * <luxThrottling>
+ * <brightnessLimitMap>
+ * <type>default</type>
+ * <map>
+ * <point>
+ * <first>5000</first>
+ * <second>0.3</second>
+ * </point>
+ * <point>
+ * <first>5000</first>
+ * <second>0.3</second>
+ * </point>
+ * </map>
+ * </brightnessPeakMap>
+ * </luxThrottling>
+ *
* <quirks>
* <quirk>canSetBrightnessViaHwc</quirk>
* </quirks>
@@ -693,6 +713,9 @@
private final Map<String, SparseArray<SurfaceControl.RefreshRateRange>>
mRefreshRateThrottlingMap = new HashMap<>();
+ private final Map<BrightnessLimitMapType, Map<Float, Float>>
+ mLuxThrottlingData = new HashMap<>();
+
@Nullable
private HostUsiVersion mHostUsiVersion;
@@ -1344,6 +1367,11 @@
return hbmData;
}
+ @NonNull
+ public Map<BrightnessLimitMapType, Map<Float, Float>> getLuxThrottlingData() {
+ return mLuxThrottlingData;
+ }
+
public List<RefreshRateLimitation> getRefreshRateLimitations() {
return mRefreshRateLimitations;
}
@@ -1530,6 +1558,7 @@
+ ", mBrightnessDefault=" + mBrightnessDefault
+ ", mQuirks=" + mQuirks
+ ", isHbmEnabled=" + mIsHighBrightnessModeEnabled
+ + ", mLuxThrottlingData=" + mLuxThrottlingData
+ ", mHbmData=" + mHbmData
+ ", mSdrToHdrRatioSpline=" + mSdrToHdrRatioSpline
+ ", mThermalBrightnessThrottlingDataMapByThrottlingId="
@@ -1676,6 +1705,7 @@
loadBrightnessMap(config);
loadThermalThrottlingConfig(config);
loadHighBrightnessModeData(config);
+ loadLuxThrottling(config);
loadQuirks(config);
loadBrightnessRamps(config);
loadAmbientLightSensorFromDdc(config);
@@ -2428,6 +2458,54 @@
}
}
+ private void loadLuxThrottling(DisplayConfiguration config) {
+ LuxThrottling cfg = config.getLuxThrottling();
+ if (cfg != null) {
+ HighBrightnessMode hbm = config.getHighBrightnessMode();
+ float hbmTransitionPoint = hbm != null ? hbm.getTransitionPoint_all().floatValue()
+ : PowerManager.BRIGHTNESS_MAX;
+ List<BrightnessLimitMap> limitMaps = cfg.getBrightnessLimitMap();
+ for (BrightnessLimitMap map : limitMaps) {
+ PredefinedBrightnessLimitNames type = map.getType();
+ BrightnessLimitMapType mappedType = BrightnessLimitMapType.convert(type);
+ if (mappedType == null) {
+ Slog.wtf(TAG, "Invalid NBM config: unsupported map type=" + type);
+ continue;
+ }
+ if (mLuxThrottlingData.containsKey(mappedType)) {
+ Slog.wtf(TAG, "Invalid NBM config: duplicate map type=" + mappedType);
+ continue;
+ }
+ Map<Float, Float> luxToTransitionPointMap = new HashMap<>();
+
+ List<NonNegativeFloatToFloatPoint> points = map.getMap().getPoint();
+ for (NonNegativeFloatToFloatPoint point : points) {
+ float lux = point.getFirst().floatValue();
+ float maxBrightness = point.getSecond().floatValue();
+ if (maxBrightness > hbmTransitionPoint) {
+ Slog.wtf(TAG,
+ "Invalid NBM config: maxBrightness is greater than hbm"
+ + ".transitionPoint. type="
+ + type + "; lux=" + lux + "; maxBrightness="
+ + maxBrightness);
+ continue;
+ }
+ if (luxToTransitionPointMap.containsKey(lux)) {
+ Slog.wtf(TAG,
+ "Invalid NBM config: duplicate lux key. type=" + type + "; lux="
+ + lux);
+ continue;
+ }
+ luxToTransitionPointMap.put(lux,
+ mBacklightToBrightnessSpline.interpolate(maxBrightness));
+ }
+ if (!luxToTransitionPointMap.isEmpty()) {
+ mLuxThrottlingData.put(mappedType, luxToTransitionPointMap);
+ }
+ }
+ }
+ }
+
private void loadBrightnessRamps(DisplayConfiguration config) {
// Priority 1: Value in the display device config (float)
// Priority 2: Value in the config.xml (int)
@@ -3155,4 +3233,19 @@
}
}
}
+
+ public enum BrightnessLimitMapType {
+ DEFAULT, ADAPTIVE;
+
+ @Nullable
+ private static BrightnessLimitMapType convert(PredefinedBrightnessLimitNames type) {
+ switch (type) {
+ case _default:
+ return DEFAULT;
+ case adaptive:
+ return ADAPTIVE;
+ }
+ return null;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 9d31572..f6e074f 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -445,7 +445,7 @@
private final ColorDisplayServiceInternal mCdsi;
private float[] mNitsRange;
- private final HighBrightnessModeController mHbmController;
+ private final BrightnessRangeController mBrightnessRangeController;
private final HighBrightnessModeMetadata mHighBrightnessModeMetadata;
private final BrightnessThrottler mBrightnessThrottler;
@@ -654,8 +654,19 @@
loadBrightnessRampRates();
mSkipScreenOnBrightnessRamp = resources.getBoolean(
com.android.internal.R.bool.config_skipScreenOnBrightnessRamp);
+ Runnable modeChangeCallback = () -> {
+ sendUpdatePowerState();
+ postBrightnessChangeRunnable();
+ // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern.
+ if (mAutomaticBrightnessController != null) {
+ mAutomaticBrightnessController.update();
+ }
+ };
- mHbmController = createHbmControllerLocked();
+ HighBrightnessModeController hbmController = createHbmControllerLocked(modeChangeCallback);
+
+ mBrightnessRangeController = new BrightnessRangeController(hbmController,
+ modeChangeCallback);
mBrightnessThrottler = createBrightnessThrottlerLocked();
@@ -802,7 +813,7 @@
@Override
public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux) {
- mHbmController.onAmbientLuxChange(ambientLux);
+ mBrightnessRangeController.onAmbientLuxChange(ambientLux);
if (nits < 0) {
mBrightnessToFollow = leadDisplayBrightness;
} else {
@@ -1039,17 +1050,7 @@
mBrightnessRampIncreaseMaxTimeMillis,
mBrightnessRampDecreaseMaxTimeMillis);
}
- mHbmController.setHighBrightnessModeMetadata(hbmMetadata);
- mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId,
- mDisplayDeviceConfig.getHighBrightnessModeData(),
- new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
- @Override
- public float getHdrBrightnessFromSdr(
- float sdrBrightness, float maxDesiredHdrSdrRatio) {
- return mDisplayDeviceConfig.getHdrBrightnessFromSdr(
- sdrBrightness, maxDesiredHdrSdrRatio);
- }
- });
+ mBrightnessRangeController.loadFromConfig(hbmMetadata, token, info, mDisplayDeviceConfig);
mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(
mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId(),
mThermalBrightnessThrottlingDataId, mUniqueDisplayId);
@@ -1264,7 +1265,7 @@
darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp,
ambientBrightnessThresholds, screenBrightnessThresholds,
ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, mContext,
- mHbmController, mBrightnessThrottler, mIdleModeBrightnessMapper,
+ mBrightnessRangeController, mBrightnessThrottler, mIdleModeBrightnessMapper,
mDisplayDeviceConfig.getAmbientHorizonShort(),
mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userBrightness);
@@ -1364,7 +1365,7 @@
/** Clean up all resources that are accessed via the {@link #mHandler} thread. */
private void cleanupHandlerThreadAfterStop() {
setProximitySensorEnabled(false);
- mHbmController.stop();
+ mBrightnessRangeController.stop();
mBrightnessThrottler.stop();
mHandler.removeCallbacksAndMessages(null);
@@ -1647,7 +1648,7 @@
mShouldResetShortTermModel);
mShouldResetShortTermModel = false;
}
- mHbmController.setAutoBrightnessEnabled(mUseAutoBrightness
+ mBrightnessRangeController.setAutoBrightnessEnabled(mUseAutoBrightness
? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
: AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
@@ -1820,7 +1821,7 @@
// here instead of having HbmController listen to the brightness setting because certain
// brightness sources (such as an app override) are not saved to the setting, but should be
// reflected in HBM calculations.
- mHbmController.onBrightnessChanged(brightnessState, unthrottledBrightnessState,
+ mBrightnessRangeController.onBrightnessChanged(brightnessState, unthrottledBrightnessState,
mBrightnessThrottler.getBrightnessMaxReason());
// Animate the screen brightness when the screen is on or dozing.
@@ -1874,13 +1875,14 @@
float sdrAnimateValue = animateValue;
// TODO(b/216365040): The decision to prevent HBM for HDR in low power mode should be
// done in HighBrightnessModeController.
- if (mHbmController.getHighBrightnessMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
+ if (mBrightnessRangeController.getHighBrightnessMode()
+ == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
&& (mBrightnessReasonTemp.getModifier() & BrightnessReason.MODIFIER_DIMMED) == 0
&& (mBrightnessReasonTemp.getModifier() & BrightnessReason.MODIFIER_LOW_POWER)
== 0) {
// We want to scale HDR brightness level with the SDR level, we also need to restore
// SDR brightness immediately when entering dim or low power mode.
- animateValue = mHbmController.getHdrBrightnessValue();
+ animateValue = mBrightnessRangeController.getHdrBrightnessValue();
}
final float currentBrightness = mPowerState.getScreenBrightness();
@@ -1942,8 +1944,8 @@
mTempBrightnessEvent.setBrightness(brightnessState);
mTempBrightnessEvent.setPhysicalDisplayId(mUniqueDisplayId);
mTempBrightnessEvent.setReason(mBrightnessReason);
- mTempBrightnessEvent.setHbmMax(mHbmController.getCurrentBrightnessMax());
- mTempBrightnessEvent.setHbmMode(mHbmController.getHighBrightnessMode());
+ mTempBrightnessEvent.setHbmMax(mBrightnessRangeController.getCurrentBrightnessMax());
+ mTempBrightnessEvent.setHbmMode(mBrightnessRangeController.getHighBrightnessMode());
mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags()
| (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0)
| (mPowerRequest.lowPowerMode ? BrightnessEvent.FLAG_LOW_POWER_MODE : 0));
@@ -2104,9 +2106,11 @@
private boolean saveBrightnessInfo(float brightness, float adjustedBrightness) {
synchronized (mCachedBrightnessInfo) {
- final float minBrightness = Math.min(mHbmController.getCurrentBrightnessMin(),
+ final float minBrightness = Math.min(
+ mBrightnessRangeController.getCurrentBrightnessMin(),
mBrightnessThrottler.getBrightnessCap());
- final float maxBrightness = Math.min(mHbmController.getCurrentBrightnessMax(),
+ final float maxBrightness = Math.min(
+ mBrightnessRangeController.getCurrentBrightnessMax(),
mBrightnessThrottler.getBrightnessCap());
boolean changed = false;
@@ -2124,10 +2128,10 @@
maxBrightness);
changed |=
mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.hbmMode,
- mHbmController.getHighBrightnessMode());
+ mBrightnessRangeController.getHighBrightnessMode());
changed |=
mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.hbmTransitionPoint,
- mHbmController.getTransitionPoint());
+ mBrightnessRangeController.getTransitionPoint());
changed |=
mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason,
mBrightnessThrottler.getBrightnessMaxReason());
@@ -2137,10 +2141,13 @@
}
void postBrightnessChangeRunnable() {
- mHandler.post(mOnBrightnessChangeRunnable);
+ if (!mHandler.hasCallbacks(mOnBrightnessChangeRunnable)) {
+ mHandler.post(mOnBrightnessChangeRunnable);
+ }
}
- private HighBrightnessModeController createHbmControllerLocked() {
+ private HighBrightnessModeController createHbmControllerLocked(
+ Runnable modeChangeCallback) {
final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
final IBinder displayToken =
@@ -2159,15 +2166,7 @@
return mDisplayDeviceConfig.getHdrBrightnessFromSdr(
sdrBrightness, maxDesiredHdrSdrRatio);
}
- },
- () -> {
- sendUpdatePowerState();
- postBrightnessChangeRunnable();
- // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern.
- if (mAutomaticBrightnessController != null) {
- mAutomaticBrightnessController.update();
- }
- }, mHighBrightnessModeMetadata, mContext);
+ }, modeChangeCallback, mHighBrightnessModeMetadata, mContext);
}
private BrightnessThrottler createBrightnessThrottlerLocked() {
@@ -2328,8 +2327,8 @@
if (Float.isNaN(value)) {
value = PowerManager.BRIGHTNESS_MIN;
}
- return MathUtils.constrain(value,
- mHbmController.getCurrentBrightnessMin(), mHbmController.getCurrentBrightnessMax());
+ return MathUtils.constrain(value, mBrightnessRangeController.getCurrentBrightnessMin(),
+ mBrightnessRangeController.getCurrentBrightnessMax());
}
// Checks whether the brightness is within the valid brightness range, not including off.
@@ -3003,8 +3002,8 @@
mScreenOffBrightnessSensorController.dump(pw);
}
- if (mHbmController != null) {
- mHbmController.dump(pw);
+ if (mBrightnessRangeController != null) {
+ mBrightnessRangeController.dump(pw);
}
if (mBrightnessThrottler != null) {
@@ -3471,7 +3470,8 @@
HysteresisLevels screenBrightnessThresholds,
HysteresisLevels ambientBrightnessThresholdsIdle,
HysteresisLevels screenBrightnessThresholdsIdle, Context context,
- HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
+ BrightnessRangeController brightnessRangeController,
+ BrightnessThrottler brightnessThrottler,
BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
int ambientLightHorizonLong, float userLux, float userBrightness) {
return new AutomaticBrightnessController(callbacks, looper, sensorManager, lightSensor,
@@ -3480,9 +3480,9 @@
brighteningLightDebounceConfig, darkeningLightDebounceConfig,
resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds,
screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
- screenBrightnessThresholdsIdle, context, hbmController, brightnessThrottler,
- idleModeBrightnessMapper, ambientLightHorizonShort, ambientLightHorizonLong,
- userLux, userBrightness);
+ screenBrightnessThresholdsIdle, context, brightnessRangeController,
+ brightnessThrottler, idleModeBrightnessMapper, ambientLightHorizonShort,
+ ambientLightHorizonLong, userLux, userBrightness);
}
BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 41e4671d..2e8c342 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -376,8 +376,7 @@
private final ColorDisplayServiceInternal mCdsi;
private float[] mNitsRange;
- private final HighBrightnessModeController mHbmController;
- private final HighBrightnessModeMetadata mHighBrightnessModeMetadata;
+ private final BrightnessRangeController mBrightnessRangeController;
private final BrightnessThrottler mBrightnessThrottler;
@@ -489,7 +488,6 @@
mDisplayPowerProximityStateController = mInjector.getDisplayPowerProximityStateController(
mWakelockController, mDisplayDeviceConfig, mHandler.getLooper(),
() -> updatePowerState(), mDisplayId, mSensorManager);
- mHighBrightnessModeMetadata = hbmMetadata;
mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController);
mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(context, mDisplayId);
mTag = "DisplayPowerController2[" + mDisplayId + "]";
@@ -532,9 +530,22 @@
mSkipScreenOnBrightnessRamp = resources.getBoolean(
R.bool.config_skipScreenOnBrightnessRamp);
- mHbmController = createHbmControllerLocked();
+ Runnable modeChangeCallback = () -> {
+ sendUpdatePowerState();
+ postBrightnessChangeRunnable();
+ // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern.
+ if (mAutomaticBrightnessController != null) {
+ mAutomaticBrightnessController.update();
+ }
+ };
+ HighBrightnessModeController hbmController = createHbmControllerLocked(hbmMetadata,
+ modeChangeCallback);
mBrightnessThrottler = createBrightnessThrottlerLocked();
+
+ mBrightnessRangeController = new BrightnessRangeController(hbmController,
+ modeChangeCallback);
+
mDisplayBrightnessController =
new DisplayBrightnessController(context, null,
mDisplayId, mLogicalDisplay.getDisplayInfoLocked().brightnessDefault,
@@ -848,17 +859,8 @@
mBrightnessRampIncreaseMaxTimeMillis,
mBrightnessRampDecreaseMaxTimeMillis);
}
- mHbmController.setHighBrightnessModeMetadata(hbmMetadata);
- mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId,
- mDisplayDeviceConfig.getHighBrightnessModeData(),
- new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
- @Override
- public float getHdrBrightnessFromSdr(
- float sdrBrightness, float maxDesiredHdrSdrRatio) {
- return mDisplayDeviceConfig.getHdrBrightnessFromSdr(
- sdrBrightness, maxDesiredHdrSdrRatio);
- }
- });
+
+ mBrightnessRangeController.loadFromConfig(hbmMetadata, token, info, mDisplayDeviceConfig);
mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(
mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId(),
mThermalBrightnessThrottlingDataId, mUniqueDisplayId);
@@ -1076,7 +1078,7 @@
darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp,
ambientBrightnessThresholds, screenBrightnessThresholds,
ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, mContext,
- mHbmController, mBrightnessThrottler, mIdleModeBrightnessMapper,
+ mBrightnessRangeController, mBrightnessThrottler, mIdleModeBrightnessMapper,
mDisplayDeviceConfig.getAmbientHorizonShort(),
mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userBrightness);
mDisplayBrightnessController.setAutomaticBrightnessController(
@@ -1180,7 +1182,7 @@
/** Clean up all resources that are accessed via the {@link #mHandler} thread. */
private void cleanupHandlerThreadAfterStop() {
mDisplayPowerProximityStateController.cleanup();
- mHbmController.stop();
+ mBrightnessRangeController.stop();
mBrightnessThrottler.stop();
mHandler.removeCallbacksAndMessages(null);
@@ -1295,7 +1297,7 @@
&& (mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged()
|| userSetBrightnessChanged);
- mHbmController.setAutoBrightnessEnabled(mAutomaticBrightnessStrategy
+ mBrightnessRangeController.setAutoBrightnessEnabled(mAutomaticBrightnessStrategy
.shouldUseAutoBrightness()
? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
: AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
@@ -1452,7 +1454,7 @@
// here instead of having HbmController listen to the brightness setting because certain
// brightness sources (such as an app override) are not saved to the setting, but should be
// reflected in HBM calculations.
- mHbmController.onBrightnessChanged(brightnessState, unthrottledBrightnessState,
+ mBrightnessRangeController.onBrightnessChanged(brightnessState, unthrottledBrightnessState,
mBrightnessThrottler.getBrightnessMaxReason());
// Animate the screen brightness when the screen is on or dozing.
@@ -1509,13 +1511,14 @@
float sdrAnimateValue = animateValue;
// TODO(b/216365040): The decision to prevent HBM for HDR in low power mode should be
// done in HighBrightnessModeController.
- if (mHbmController.getHighBrightnessMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
+ if (mBrightnessRangeController.getHighBrightnessMode()
+ == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
&& (mBrightnessReasonTemp.getModifier() & BrightnessReason.MODIFIER_DIMMED) == 0
&& (mBrightnessReasonTemp.getModifier() & BrightnessReason.MODIFIER_LOW_POWER)
== 0) {
// We want to scale HDR brightness level with the SDR level, we also need to restore
// SDR brightness immediately when entering dim or low power mode.
- animateValue = mHbmController.getHdrBrightnessValue();
+ animateValue = mBrightnessRangeController.getHdrBrightnessValue();
}
final float currentBrightness = mPowerState.getScreenBrightness();
@@ -1579,8 +1582,8 @@
mTempBrightnessEvent.setBrightness(brightnessState);
mTempBrightnessEvent.setPhysicalDisplayId(mUniqueDisplayId);
mTempBrightnessEvent.setReason(mBrightnessReason);
- mTempBrightnessEvent.setHbmMax(mHbmController.getCurrentBrightnessMax());
- mTempBrightnessEvent.setHbmMode(mHbmController.getHighBrightnessMode());
+ mTempBrightnessEvent.setHbmMax(mBrightnessRangeController.getCurrentBrightnessMax());
+ mTempBrightnessEvent.setHbmMode(mBrightnessRangeController.getHighBrightnessMode());
mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags()
| (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0)
| (mPowerRequest.lowPowerMode ? BrightnessEvent.FLAG_LOW_POWER_MODE : 0));
@@ -1750,9 +1753,11 @@
private boolean saveBrightnessInfo(float brightness, float adjustedBrightness) {
synchronized (mCachedBrightnessInfo) {
- final float minBrightness = Math.min(mHbmController.getCurrentBrightnessMin(),
+ final float minBrightness = Math.min(
+ mBrightnessRangeController.getCurrentBrightnessMin(),
mBrightnessThrottler.getBrightnessCap());
- final float maxBrightness = Math.min(mHbmController.getCurrentBrightnessMax(),
+ final float maxBrightness = Math.min(
+ mBrightnessRangeController.getCurrentBrightnessMax(),
mBrightnessThrottler.getBrightnessCap());
boolean changed = false;
@@ -1770,10 +1775,10 @@
maxBrightness);
changed |=
mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.hbmMode,
- mHbmController.getHighBrightnessMode());
+ mBrightnessRangeController.getHighBrightnessMode());
changed |=
mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.hbmTransitionPoint,
- mHbmController.getTransitionPoint());
+ mBrightnessRangeController.getTransitionPoint());
changed |=
mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason,
mBrightnessThrottler.getBrightnessMaxReason());
@@ -1783,10 +1788,13 @@
}
void postBrightnessChangeRunnable() {
- mHandler.post(mOnBrightnessChangeRunnable);
+ if (!mHandler.hasCallbacks(mOnBrightnessChangeRunnable)) {
+ mHandler.post(mOnBrightnessChangeRunnable);
+ }
}
- private HighBrightnessModeController createHbmControllerLocked() {
+ private HighBrightnessModeController createHbmControllerLocked(
+ HighBrightnessModeMetadata hbmMetadata, Runnable modeChangeCallback) {
final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
final IBinder displayToken =
@@ -1798,22 +1806,9 @@
final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
return new HighBrightnessModeController(mHandler, info.width, info.height, displayToken,
displayUniqueId, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData,
- new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
- @Override
- public float getHdrBrightnessFromSdr(
- float sdrBrightness, float maxDesiredHdrSdrRatio) {
- return mDisplayDeviceConfig.getHdrBrightnessFromSdr(
- sdrBrightness, maxDesiredHdrSdrRatio);
- }
- },
- () -> {
- sendUpdatePowerState();
- postBrightnessChangeRunnable();
- // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern.
- if (mAutomaticBrightnessController != null) {
- mAutomaticBrightnessController.update();
- }
- }, mHighBrightnessModeMetadata, mContext);
+ (sdrBrightness, maxDesiredHdrSdrRatio) ->
+ mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness,
+ maxDesiredHdrSdrRatio), modeChangeCallback, hbmMetadata, mContext);
}
private BrightnessThrottler createBrightnessThrottlerLocked() {
@@ -1960,8 +1955,8 @@
if (Float.isNaN(value)) {
value = PowerManager.BRIGHTNESS_MIN;
}
- return MathUtils.constrain(value,
- mHbmController.getCurrentBrightnessMin(), mHbmController.getCurrentBrightnessMax());
+ return MathUtils.constrain(value, mBrightnessRangeController.getCurrentBrightnessMin(),
+ mBrightnessRangeController.getCurrentBrightnessMax());
}
private void animateScreenBrightness(float target, float sdrTarget, float rate) {
@@ -2195,7 +2190,7 @@
@Override
public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux) {
- mHbmController.onAmbientLuxChange(ambientLux);
+ mBrightnessRangeController.onAmbientLuxChange(ambientLux);
if (nits < 0) {
mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness);
} else {
@@ -2374,8 +2369,8 @@
dumpRbcEvents(pw);
- if (mHbmController != null) {
- mHbmController.dump(pw);
+ if (mBrightnessRangeController != null) {
+ mBrightnessRangeController.dump(pw);
}
if (mBrightnessThrottler != null) {
@@ -2840,7 +2835,8 @@
HysteresisLevels screenBrightnessThresholds,
HysteresisLevels ambientBrightnessThresholdsIdle,
HysteresisLevels screenBrightnessThresholdsIdle, Context context,
- HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
+ BrightnessRangeController brightnessModeController,
+ BrightnessThrottler brightnessThrottler,
BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
int ambientLightHorizonLong, float userLux, float userBrightness) {
return new AutomaticBrightnessController(callbacks, looper, sensorManager, lightSensor,
@@ -2849,9 +2845,9 @@
brighteningLightDebounceConfig, darkeningLightDebounceConfig,
resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds,
screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
- screenBrightnessThresholdsIdle, context, hbmController, brightnessThrottler,
- idleModeBrightnessMapper, ambientLightHorizonShort, ambientLightHorizonLong,
- userLux, userBrightness);
+ screenBrightnessThresholdsIdle, context, brightnessModeController,
+ brightnessThrottler, idleModeBrightnessMapper, ambientLightHorizonShort,
+ ambientLightHorizonLong, userLux, userBrightness);
}
BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index c7c0fab..7701bc6 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -701,11 +701,15 @@
maxDisplayMode == null ? mInfo.width : maxDisplayMode.getPhysicalWidth();
final int maxHeight =
maxDisplayMode == null ? mInfo.height : maxDisplayMode.getPhysicalHeight();
- mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res,
- mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
- mInfo.roundedCorners = RoundedCorners.fromResources(
- res, mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
+ // We cannot determine cutouts and rounded corners of external displays.
+ if (mStaticDisplayInfo.isInternal) {
+ mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res,
+ mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
+ mInfo.roundedCorners = RoundedCorners.fromResources(
+ res, mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
+ }
+
mInfo.installOrientation = mStaticDisplayInfo.installOrientation;
mInfo.displayShape = DisplayShape.fromResources(
diff --git a/services/core/java/com/android/server/display/NormalBrightnessModeController.java b/services/core/java/com/android/server/display/NormalBrightnessModeController.java
new file mode 100644
index 0000000..dbabc24
--- /dev/null
+++ b/services/core/java/com/android/server/display/NormalBrightnessModeController.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 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.display;
+
+import android.annotation.NonNull;
+import android.os.PowerManager;
+
+import com.android.server.display.DisplayDeviceConfig.BrightnessLimitMapType;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Limits brightness for normal-brightness mode, based on ambient lux
+ **/
+class NormalBrightnessModeController {
+ @NonNull
+ private Map<BrightnessLimitMapType, Map<Float, Float>> mMaxBrightnessLimits = new HashMap<>();
+ private float mAmbientLux = Float.MAX_VALUE;
+ private boolean mAutoBrightnessEnabled = false;
+
+ // brightness limit in normal brightness mode, based on ambient lux.
+ private float mMaxBrightness = PowerManager.BRIGHTNESS_MAX;
+
+ boolean onAmbientLuxChange(float ambientLux) {
+ mAmbientLux = ambientLux;
+ return recalculateMaxBrightness();
+ }
+
+ boolean setAutoBrightnessState(int state) {
+ boolean isEnabled = state == AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
+ if (isEnabled != mAutoBrightnessEnabled) {
+ mAutoBrightnessEnabled = isEnabled;
+ return recalculateMaxBrightness();
+ }
+ return false;
+ }
+
+ float getCurrentBrightnessMax() {
+ return mMaxBrightness;
+ }
+
+ boolean resetNbmData(
+ @NonNull Map<BrightnessLimitMapType, Map<Float, Float>> maxBrightnessLimits) {
+ mMaxBrightnessLimits = maxBrightnessLimits;
+ return recalculateMaxBrightness();
+ }
+
+ private boolean recalculateMaxBrightness() {
+ float foundAmbientBoundary = Float.MAX_VALUE;
+ float foundMaxBrightness = PowerManager.BRIGHTNESS_MAX;
+
+ Map<Float, Float> maxBrightnessPoints = null;
+
+ if (mAutoBrightnessEnabled) {
+ maxBrightnessPoints = mMaxBrightnessLimits.get(BrightnessLimitMapType.ADAPTIVE);
+ }
+
+ if (maxBrightnessPoints == null) {
+ maxBrightnessPoints = mMaxBrightnessLimits.get(BrightnessLimitMapType.DEFAULT);
+ }
+
+ if (maxBrightnessPoints != null) {
+ for (Map.Entry<Float, Float> brightnessPoint : maxBrightnessPoints.entrySet()) {
+ float ambientBoundary = brightnessPoint.getKey();
+ // find ambient lux upper boundary closest to current ambient lux
+ if (ambientBoundary > mAmbientLux && ambientBoundary < foundAmbientBoundary) {
+ foundMaxBrightness = brightnessPoint.getValue();
+ foundAmbientBoundary = ambientBoundary;
+ }
+ }
+ }
+
+ if (mMaxBrightness != foundMaxBrightness) {
+ mMaxBrightness = foundMaxBrightness;
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
index b4613a7..efb3622 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
@@ -171,7 +171,7 @@
true, /* disableAod */
true, /* disableLaunchBoost */
true, /* disableOptionalSensors */
- true, /* disableVibration */
+ false, /* disableVibration */
false, /* enableAdjustBrightness */
false, /* enableDataSaver */
true, /* enableFirewall */
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index 778951a..98ee98b 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -248,7 +248,10 @@
Slog.e(TAG, "WM sent Transaction to organized, but never received" +
" commit callback. Application ANR likely to follow.");
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- onCommitted(merged);
+ synchronized (mWm.mGlobalLock) {
+ onCommitted(merged.mNativeObject != 0
+ ? merged : mWm.mTransactionFactory.get());
+ }
}
};
CommitCallback callback = new CommitCallback();
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index f96ca58..7104a80 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -46,6 +46,8 @@
<xs:annotation name="nonnull"/>
<xs:annotation name="final"/>
</xs:element>
+ <xs:element type="luxThrottling" name="luxThrottling" minOccurs="0"
+ maxOccurs="1"/>
<xs:element type="highBrightnessMode" name="highBrightnessMode" minOccurs="0"
maxOccurs="1"/>
<xs:element type="displayQuirks" name="quirks" minOccurs="0" maxOccurs="1"/>
@@ -137,6 +139,39 @@
</xs:sequence>
</xs:complexType>
+ <xs:complexType name="luxThrottling">
+ <xs:sequence>
+ <xs:element name="brightnessLimitMap" type="brightnessLimitMap"
+ maxOccurs="unbounded">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="brightnessLimitMap">
+ <xs:sequence>
+ <xs:element name="type" type="PredefinedBrightnessLimitNames">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ <!-- lux level from light sensor to screen brightness recommended max value map.
+ Screen brightness recommended max value is to highBrightnessMode.transitionPoint and must be below that -->
+ <xs:element name="map" type="nonNegativeFloatToFloatMap">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
+ <!-- Predefined type names as defined by DisplayDeviceConfig.BrightnessLimitMapType -->
+ <xs:simpleType name="PredefinedBrightnessLimitNames">
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="default"/>
+ <xs:enumeration value="adaptive"/>
+ </xs:restriction>
+ </xs:simpleType>
+
<xs:complexType name="highBrightnessMode">
<xs:all>
<xs:element name="transitionPoint" type="nonNegativeDecimal" minOccurs="1"
@@ -575,4 +610,27 @@
<xs:annotation name="final"/>
</xs:element>
</xs:complexType>
+
+ <!-- generic types -->
+ <xs:complexType name="nonNegativeFloatToFloatPoint">
+ <xs:sequence>
+ <xs:element name="first" type="nonNegativeDecimal">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ <xs:element name="second" type="nonNegativeDecimal">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="nonNegativeFloatToFloatMap">
+ <xs:sequence>
+ <xs:element name="point" type="nonNegativeFloatToFloatPoint" maxOccurs="unbounded">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
</xs:schema>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index ad6434e..507c9dc 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -26,6 +26,14 @@
method public final java.util.List<com.android.server.display.config.DisplayBrightnessPoint> getDisplayBrightnessPoint();
}
+ public class BrightnessLimitMap {
+ ctor public BrightnessLimitMap();
+ method @NonNull public final com.android.server.display.config.NonNegativeFloatToFloatMap getMap();
+ method @NonNull public final com.android.server.display.config.PredefinedBrightnessLimitNames getType();
+ method public final void setMap(@NonNull com.android.server.display.config.NonNegativeFloatToFloatMap);
+ method public final void setType(@NonNull com.android.server.display.config.PredefinedBrightnessLimitNames);
+ }
+
public class BrightnessThresholds {
ctor public BrightnessThresholds();
method public final com.android.server.display.config.ThresholdPoints getBrightnessThresholdPoints();
@@ -89,6 +97,7 @@
method public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholdsIdle();
method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode();
method public final com.android.server.display.config.SensorDetails getLightSensor();
+ method public com.android.server.display.config.LuxThrottling getLuxThrottling();
method @Nullable public final String getName();
method public final com.android.server.display.config.SensorDetails getProxSensor();
method public com.android.server.display.config.DisplayQuirks getQuirks();
@@ -115,6 +124,7 @@
method public final void setDisplayBrightnessChangeThresholdsIdle(com.android.server.display.config.Thresholds);
method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode);
method public final void setLightSensor(com.android.server.display.config.SensorDetails);
+ method public void setLuxThrottling(com.android.server.display.config.LuxThrottling);
method public final void setName(@Nullable String);
method public final void setProxSensor(com.android.server.display.config.SensorDetails);
method public void setQuirks(com.android.server.display.config.DisplayQuirks);
@@ -173,6 +183,11 @@
method public java.util.List<java.math.BigInteger> getItem();
}
+ public class LuxThrottling {
+ ctor public LuxThrottling();
+ method @NonNull public final java.util.List<com.android.server.display.config.BrightnessLimitMap> getBrightnessLimitMap();
+ }
+
public class NitsMap {
ctor public NitsMap();
method public String getInterpolation();
@@ -180,6 +195,19 @@
method public void setInterpolation(String);
}
+ public class NonNegativeFloatToFloatMap {
+ ctor public NonNegativeFloatToFloatMap();
+ method @NonNull public final java.util.List<com.android.server.display.config.NonNegativeFloatToFloatPoint> getPoint();
+ }
+
+ public class NonNegativeFloatToFloatPoint {
+ ctor public NonNegativeFloatToFloatPoint();
+ method @NonNull public final java.math.BigDecimal getFirst();
+ method @NonNull public final java.math.BigDecimal getSecond();
+ method public final void setFirst(@NonNull java.math.BigDecimal);
+ method public final void setSecond(@NonNull java.math.BigDecimal);
+ }
+
public class Point {
ctor public Point();
method @NonNull public final java.math.BigDecimal getNits();
@@ -188,6 +216,12 @@
method public final void setValue(@NonNull java.math.BigDecimal);
}
+ public enum PredefinedBrightnessLimitNames {
+ method public String getRawName();
+ enum_constant public static final com.android.server.display.config.PredefinedBrightnessLimitNames _default;
+ enum_constant public static final com.android.server.display.config.PredefinedBrightnessLimitNames adaptive;
+ }
+
public class RefreshRateConfigs {
ctor public RefreshRateConfigs();
method public final java.math.BigInteger getDefaultPeakRefreshRate();
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index 32243f0..212a243 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -2221,7 +2221,7 @@
String[] packages = {mPackageName};
when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
}
@@ -2238,12 +2238,12 @@
doAnswer(inv -> powerState.put(inv.getArgument(0), inv.getArgument(1)))
.when(mMockPowerManager).setPowerMode(anyInt(), anyBoolean());
gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
assertTrue(powerState.get(Mode.GAME));
gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
gameManagerService.mUidObserver.onUidStateChanged(
- somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0);
assertTrue(powerState.get(Mode.GAME));
gameManagerService.mUidObserver.onUidStateChanged(
somePackageId, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
@@ -2260,13 +2260,13 @@
int somePackageId = DEFAULT_PACKAGE_UID + 1;
when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2);
gameManagerService.mUidObserver.onUidStateChanged(
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+ gameManagerService.mUidObserver.onUidStateChanged(
+ somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+ gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
gameManagerService.mUidObserver.onUidStateChanged(
somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
- gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
- gameManagerService.mUidObserver.onUidStateChanged(
- somePackageId, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
}
@@ -2277,9 +2277,9 @@
String[] packages = {mPackageName};
when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index 2def023..de5e6d7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -815,7 +815,7 @@
any(HysteresisLevels.class),
any(HysteresisLevels.class),
eq(mContext),
- any(HighBrightnessModeController.class),
+ any(BrightnessRangeController.class),
any(BrightnessThrottler.class),
isNull(),
anyInt(),
@@ -1064,7 +1064,7 @@
HysteresisLevels screenBrightnessThresholds,
HysteresisLevels ambientBrightnessThresholdsIdle,
HysteresisLevels screenBrightnessThresholdsIdle, Context context,
- HighBrightnessModeController hbmController,
+ BrightnessRangeController brightnessRangeController,
BrightnessThrottler brightnessThrottler,
BrightnessMappingStrategy idleModeBrightnessMapper,
int ambientLightHorizonShort, int ambientLightHorizonLong, float userLux,
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
index 6c80f7b..95bc26a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -819,7 +819,7 @@
any(HysteresisLevels.class),
any(HysteresisLevels.class),
eq(mContext),
- any(HighBrightnessModeController.class),
+ any(BrightnessRangeController.class),
any(BrightnessThrottler.class),
isNull(),
anyInt(),
@@ -1038,7 +1038,7 @@
HysteresisLevels screenBrightnessThresholds,
HysteresisLevels ambientBrightnessThresholdsIdle,
HysteresisLevels screenBrightnessThresholdsIdle, Context context,
- HighBrightnessModeController hbmController,
+ BrightnessRangeController brightnessRangeController,
BrightnessThrottler brightnessThrottler,
BrightnessMappingStrategy idleModeBrightnessMapper,
int ambientLightHorizonShort, int ambientLightHorizonLong, float userLux,
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index b7dbaf9..f89f73c9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -37,6 +37,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -1009,6 +1010,72 @@
0.001f);
}
+ @Test
+ public void test_getDisplayDeviceInfoLocked_internalDisplay_usesCutoutAndCorners()
+ throws Exception {
+ setupCutoutAndRoundedCorners();
+ FakeDisplay display = new FakeDisplay(PORT_A);
+ display.info.isInternal = true;
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+ // Turn on / initialize
+ Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
+ 0);
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ mListener.changedDisplays.clear();
+
+ DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+
+ assertThat(info.displayCutout).isNotNull();
+ assertThat(info.displayCutout.getBoundingRectTop()).isEqualTo(new Rect(507, 33, 573, 99));
+ assertThat(info.roundedCorners).isNotNull();
+ assertThat(info.roundedCorners.getRoundedCorner(0).getRadius()).isEqualTo(5);
+ }
+
+ @Test public void test_getDisplayDeviceInfoLocked_externalDisplay_doesNotUseCutoutOrCorners()
+ throws Exception {
+ setupCutoutAndRoundedCorners();
+ FakeDisplay display = new FakeDisplay(PORT_A);
+ display.info.isInternal = false;
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+ // Turn on / initialize
+ Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
+ 0);
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ mListener.changedDisplays.clear();
+
+ DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+
+ assertThat(info.displayCutout).isNull();
+ assertThat(info.roundedCorners).isNull();
+ }
+
+ private void setupCutoutAndRoundedCorners() {
+ String sampleCutout = "M 507,66\n"
+ + "a 33,33 0 1 0 66,0 33,33 0 1 0 -66,0\n"
+ + "Z\n"
+ + "@left\n";
+ // Setup some default cutout
+ when(mMockedResources.getString(
+ com.android.internal.R.string.config_mainBuiltInDisplayCutout))
+ .thenReturn(sampleCutout);
+ when(mMockedResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.rounded_corner_radius)).thenReturn(5);
+ }
+
private void assertDisplayDpi(DisplayDeviceInfo info, int expectedPort,
float expectedXdpi,
float expectedYDpi,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 662477d..8346050 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -26,6 +26,7 @@
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_CALLED;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_STARTED;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_STARTED_UI_SHOWING;
+import static com.android.server.biometrics.BiometricServiceStateProto.STATE_ERROR_PENDING_SYSUI;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -48,6 +49,7 @@
import android.app.trust.ITrustManager;
import android.content.Context;
import android.content.res.Resources;
+import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.ComponentInfoInternal;
@@ -104,6 +106,7 @@
@Mock private KeyStore mKeyStore;
@Mock private AuthSession.ClientDeathReceiver mClientDeathReceiver;
@Mock private BiometricFrameworkStatsLogger mBiometricFrameworkStatsLogger;
+ @Mock BiometricSensorPrivacy mBiometricSensorPrivacy;
private Random mRandom;
private IBinder mToken;
@@ -210,6 +213,40 @@
}
@Test
+ public void testOnErrorReceived_lockoutError() throws RemoteException {
+ setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_REAR);
+ setupFace(1 /* id */, false /* confirmationAlwaysRequired */,
+ mock(IBiometricAuthenticator.class));
+ final AuthSession session = createAuthSession(mSensors,
+ false /* checkDevicePolicyManager */,
+ Authenticators.BIOMETRIC_STRONG,
+ TEST_REQUEST_ID,
+ 0 /* operationId */,
+ 0 /* userId */);
+ session.goToInitialState();
+ for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) {
+ assertEquals(BiometricSensor.STATE_WAITING_FOR_COOKIE, sensor.getSensorState());
+ session.onCookieReceived(
+ session.mPreAuthInfo.eligibleSensors.get(sensor.id).getCookie());
+ }
+ assertTrue(session.allCookiesReceived());
+ assertEquals(STATE_AUTH_STARTED, session.getState());
+
+ // Either of strong sensor's lockout should cancel both sensors.
+ final int cookie1 = session.mPreAuthInfo.eligibleSensors.get(0).getCookie();
+ session.onErrorReceived(0, cookie1, BiometricConstants.BIOMETRIC_ERROR_LOCKOUT, 0);
+ for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) {
+ assertEquals(BiometricSensor.STATE_CANCELING, sensor.getSensorState());
+ }
+ assertEquals(STATE_ERROR_PENDING_SYSUI, session.getState());
+
+ // If the sensor is STATE_CANCELING, delayed onAuthenticationRejected() shouldn't change the
+ // session state to STATE_AUTH_PAUSED.
+ session.onAuthenticationRejected(1);
+ assertEquals(STATE_ERROR_PENDING_SYSUI, session.getState());
+ }
+
+ @Test
public void testCancelReducesAppetiteForCookies() throws Exception {
setupFace(0 /* id */, false /* confirmationAlwaysRequired */,
mock(IBiometricAuthenticator.class));
@@ -571,7 +608,8 @@
promptInfo,
TEST_PACKAGE,
checkDevicePolicyManager,
- mContext);
+ mContext,
+ mBiometricSensorPrivacy);
}
private AuthSession createAuthSession(List<BiometricSensor> sensors,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 67be376..41f7dbc 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -18,7 +18,6 @@
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.Authenticators;
-import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTHENTICATED_PENDING_SYSUI;
@@ -183,6 +182,10 @@
.thenReturn(ERROR_HW_UNAVAILABLE);
when(mResources.getString(R.string.biometric_not_recognized))
.thenReturn(ERROR_NOT_RECOGNIZED);
+ when(mResources.getString(R.string.biometric_face_not_recognized))
+ .thenReturn(ERROR_NOT_RECOGNIZED);
+ when(mResources.getString(R.string.fingerprint_error_not_match))
+ .thenReturn(ERROR_NOT_RECOGNIZED);
when(mResources.getString(R.string.biometric_error_user_canceled))
.thenReturn(ERROR_USER_CANCELED);
@@ -903,6 +906,45 @@
}
@Test
+ public void testMultiBiometricAuth_whenLockoutTimed_sendsErrorAndModality()
+ throws Exception {
+ testMultiBiometricAuth_whenLockout(LockoutTracker.LOCKOUT_TIMED,
+ BiometricPrompt.BIOMETRIC_ERROR_LOCKOUT);
+ }
+
+ @Test
+ public void testMultiBiometricAuth_whenLockoutPermanent_sendsErrorAndModality()
+ throws Exception {
+ testMultiBiometricAuth_whenLockout(LockoutTracker.LOCKOUT_PERMANENT,
+ BiometricPrompt.BIOMETRIC_ERROR_LOCKOUT_PERMANENT);
+ }
+
+ private void testMultiBiometricAuth_whenLockout(@LockoutTracker.LockoutMode int lockoutMode,
+ int biometricPromptError) throws Exception {
+ final int[] modalities = new int[] {
+ TYPE_FINGERPRINT,
+ BiometricAuthenticator.TYPE_FACE,
+ };
+
+ final int[] strengths = new int[] {
+ Authenticators.BIOMETRIC_STRONG,
+ Authenticators.BIOMETRIC_STRONG,
+ };
+ setupAuthForMultiple(modalities, strengths);
+
+ when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt()))
+ .thenReturn(lockoutMode);
+ when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */, null /* authenticators */);
+ waitForIdle();
+
+ // The lockout error should be sent, instead of ERROR_NONE_ENROLLED. See b/286923477.
+ verify(mReceiver1).onError(eq(TYPE_FINGERPRINT),
+ eq(biometricPromptError), eq(0) /* vendorCode */);
+ }
+
+ @Test
public void testBiometricOrCredentialAuth_whenBiometricLockout_showsCredential()
throws Exception {
when(mTrustManager.isDeviceSecure(anyInt(), anyInt())).thenReturn(true);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
new file mode 100644
index 0000000..0c98c8d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2023 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.biometrics;
+
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+
+import static com.android.server.biometrics.sensors.LockoutTracker.LOCKOUT_NONE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.DevicePolicyManager;
+import android.app.trust.ITrustManager;
+import android.content.Context;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.IBiometricAuthenticator;
+import android.hardware.biometrics.PromptInfo;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class PreAuthInfoTest {
+ @Rule
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ private static final int SENSOR_ID_FACE = 1;
+ private static final String TEST_PACKAGE_NAME = "PreAuthInfoTestPackage";
+
+ @Mock
+ IBiometricAuthenticator mFaceAuthenticator;
+ @Mock
+ Context mContext;
+ @Mock
+ ITrustManager mTrustManager;
+ @Mock
+ DevicePolicyManager mDevicePolicyManager;
+ @Mock
+ BiometricService.SettingObserver mSettingObserver;
+ @Mock
+ BiometricSensorPrivacy mBiometricSensorPrivacyUtil;
+
+ @Before
+ public void setup() throws RemoteException {
+ when(mTrustManager.isDeviceSecure(anyInt(), anyInt())).thenReturn(true);
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(any(), anyInt()))
+ .thenReturn(KEYGUARD_DISABLE_FEATURES_NONE);
+ when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
+ when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
+ when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
+ when(mFaceAuthenticator.getLockoutModeForUser(anyInt()))
+ .thenReturn(LOCKOUT_NONE);
+ }
+
+ @Test
+ public void testFaceAuthentication_whenCameraPrivacyIsEnabled() throws Exception {
+ when(mBiometricSensorPrivacyUtil.isCameraPrivacyEnabled()).thenReturn(true);
+
+ BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
+ BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
+ @Override
+ boolean confirmationAlwaysRequired(int userId) {
+ return false;
+ }
+
+ @Override
+ boolean confirmationSupported() {
+ return false;
+ }
+ };
+ PromptInfo promptInfo = new PromptInfo();
+ promptInfo.setConfirmationRequested(false /* requireConfirmation */);
+ promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
+ promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
+ PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+ mSettingObserver, List.of(sensor),
+ 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricSensorPrivacyUtil);
+
+ assertThat(preAuthInfo.eligibleSensors).isEmpty();
+ }
+
+ @Test
+ public void testFaceAuthentication_whenCameraPrivacyIsDisabled() throws Exception {
+ when(mBiometricSensorPrivacyUtil.isCameraPrivacyEnabled()).thenReturn(false);
+
+ BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
+ BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
+ @Override
+ boolean confirmationAlwaysRequired(int userId) {
+ return false;
+ }
+
+ @Override
+ boolean confirmationSupported() {
+ return false;
+ }
+ };
+ PromptInfo promptInfo = new PromptInfo();
+ promptInfo.setConfirmationRequested(false /* requireConfirmation */);
+ promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
+ promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
+ PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+ mSettingObserver, List.of(sensor),
+ 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricSensorPrivacyUtil);
+
+ assertThat(preAuthInfo.eligibleSensors).hasSize(1);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index e9d8269..8929900 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -19,6 +19,8 @@
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_SUCCESS;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
@@ -405,6 +407,59 @@
testCancelsEnrollWhenRequestId(10L, 20, false /* started */);
}
+ @Test
+ public void testCancelAuthenticationClientWithoutStarting() {
+ final Supplier<Object> lazyDaemon = () -> mock(Object.class);
+ final TestHalClientMonitor client1 = new TestHalClientMonitor(mContext, mToken, lazyDaemon);
+ final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
+ final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, lazyDaemon,
+ mToken, callback, mBiometricContext);
+
+ //Schedule authentication client to the pending queue
+ mScheduler.scheduleClientMonitor(client1);
+ mScheduler.scheduleClientMonitor(client2);
+ waitForIdle();
+
+ assertThat(mScheduler.getCurrentClient()).isEqualTo(client1);
+
+ client2.cancel();
+ waitForIdle();
+
+ assertThat(client2.isAlreadyCancelled()).isTrue();
+
+ client1.getCallback().onClientFinished(client1, false);
+ waitForIdle();
+
+ assertThat(mScheduler.getCurrentClient()).isNull();
+ }
+
+ @Test
+ public void testCancelAuthenticationClientWithoutStarting_whenAppCrashes() {
+ final Supplier<Object> lazyDaemon = () -> mock(Object.class);
+ final TestHalClientMonitor client1 = new TestHalClientMonitor(mContext, mToken, lazyDaemon);
+ final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
+ final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, lazyDaemon,
+ mToken, callback, mBiometricContext);
+
+ //Schedule authentication client to the pending queue
+ mScheduler.scheduleClientMonitor(client1);
+ mScheduler.scheduleClientMonitor(client2);
+ waitForIdle();
+
+ assertThat(mScheduler.getCurrentClient()).isEqualTo(client1);
+
+ //App crashes
+ client2.binderDied();
+ waitForIdle();
+
+ assertThat(client2.isAlreadyCancelled()).isTrue();
+
+ client1.getCallback().onClientFinished(client1, false);
+ waitForIdle();
+
+ assertThat(mScheduler.getCurrentClient()).isNull();
+ }
+
private void testCancelsEnrollWhenRequestId(@Nullable Long requestId, long cancelRequestId,
boolean started) {
final Supplier<Object> lazyDaemon = () -> mock(Object.class);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index 3f20f8a..359d711 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -16,8 +16,10 @@
package com.android.server.biometrics.sensors.face.aidl;
+import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -183,7 +185,9 @@
client.onAuthenticated(new Face("friendly", 1 /* faceId */, 2 /* deviceId */),
true /* authenticated */, new ArrayList<>());
- verify(mCancellationSignal).cancel();
+ verify(mCancellationSignal, never()).cancel();
+ verify(mClientMonitorCallbackConverter)
+ .onError(anyInt(), anyInt(), eq(BIOMETRIC_ERROR_CANCELED), anyInt());
}
private FaceAuthenticationClient createClient() throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index f8f40fe..c383a96 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
+import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -399,7 +401,9 @@
mLooper.moveTimeForward(10);
mLooper.dispatchAll();
- verify(mCancellationSignal).cancel();
+ verify(mCancellationSignal, never()).cancel();
+ verify(mClientMonitorCallbackConverter)
+ .onError(anyInt(), anyInt(), eq(BIOMETRIC_ERROR_CANCELED), anyInt());
}
private FingerprintAuthenticationClient createClient() throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 962e867..a6acd60 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -85,7 +85,7 @@
@Mock HysteresisLevels mAmbientBrightnessThresholdsIdle;
@Mock HysteresisLevels mScreenBrightnessThresholdsIdle;
@Mock Handler mNoOpHandler;
- @Mock HighBrightnessModeController mHbmController;
+ @Mock BrightnessRangeController mBrightnessRangeController;
@Mock BrightnessThrottler mBrightnessThrottler;
@Before
@@ -134,12 +134,15 @@
DARKENING_LIGHT_DEBOUNCE_CONFIG, RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG,
mAmbientBrightnessThresholds, mScreenBrightnessThresholds,
mAmbientBrightnessThresholdsIdle, mScreenBrightnessThresholdsIdle,
- mContext, mHbmController, mBrightnessThrottler, mIdleBrightnessMappingStrategy,
- AMBIENT_LIGHT_HORIZON_SHORT, AMBIENT_LIGHT_HORIZON_LONG, userLux, userBrightness
+ mContext, mBrightnessRangeController, mBrightnessThrottler,
+ mIdleBrightnessMappingStrategy, AMBIENT_LIGHT_HORIZON_SHORT,
+ AMBIENT_LIGHT_HORIZON_LONG, userLux, userBrightness
);
- when(mHbmController.getCurrentBrightnessMax()).thenReturn(BRIGHTNESS_MAX_FLOAT);
- when(mHbmController.getCurrentBrightnessMin()).thenReturn(BRIGHTNESS_MIN_FLOAT);
+ when(mBrightnessRangeController.getCurrentBrightnessMax()).thenReturn(
+ BRIGHTNESS_MAX_FLOAT);
+ when(mBrightnessRangeController.getCurrentBrightnessMin()).thenReturn(
+ BRIGHTNESS_MIN_FLOAT);
// Disable brightness throttling by default. Individual tests can enable it as needed.
when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
when(mBrightnessThrottler.isThrottled()).thenReturn(false);
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 5837b21..708421d 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -52,6 +52,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -376,6 +377,116 @@
assertEquals(90, testMap.get(Temperature.THROTTLING_EMERGENCY).max, SMALL_DELTA);
}
+ @Test
+ public void testValidLuxThrottling() throws Exception {
+ setupDisplayDeviceConfigFromDisplayConfigFile();
+
+ Map<DisplayDeviceConfig.BrightnessLimitMapType, Map<Float, Float>> luxThrottlingData =
+ mDisplayDeviceConfig.getLuxThrottlingData();
+ assertEquals(2, luxThrottlingData.size());
+
+ Map<Float, Float> adaptiveOnBrightnessPoints = luxThrottlingData.get(
+ DisplayDeviceConfig.BrightnessLimitMapType.ADAPTIVE);
+ assertEquals(2, adaptiveOnBrightnessPoints.size());
+ assertEquals(0.3f, adaptiveOnBrightnessPoints.get(1000f), SMALL_DELTA);
+ assertEquals(0.5f, adaptiveOnBrightnessPoints.get(5000f), SMALL_DELTA);
+
+ Map<Float, Float> adaptiveOffBrightnessPoints = luxThrottlingData.get(
+ DisplayDeviceConfig.BrightnessLimitMapType.DEFAULT);
+ assertEquals(2, adaptiveOffBrightnessPoints.size());
+ assertEquals(0.35f, adaptiveOffBrightnessPoints.get(1500f), SMALL_DELTA);
+ assertEquals(0.55f, adaptiveOffBrightnessPoints.get(5500f), SMALL_DELTA);
+ }
+
+ @Test
+ public void testInvalidLuxThrottling() throws Exception {
+ setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getInvalidLuxThrottling()));
+
+ Map<DisplayDeviceConfig.BrightnessLimitMapType, Map<Float, Float>> luxThrottlingData =
+ mDisplayDeviceConfig.getLuxThrottlingData();
+ assertEquals(1, luxThrottlingData.size());
+
+ Map<Float, Float> adaptiveOnBrightnessPoints = luxThrottlingData.get(
+ DisplayDeviceConfig.BrightnessLimitMapType.ADAPTIVE);
+ assertEquals(1, adaptiveOnBrightnessPoints.size());
+ assertEquals(0.3f, adaptiveOnBrightnessPoints.get(1000f), SMALL_DELTA);
+ }
+
+ private String getValidLuxThrottling() {
+ return "<luxThrottling>\n"
+ + " <brightnessLimitMap>\n"
+ + " <type>adaptive</type>\n"
+ + " <map>\n"
+ + " <point>"
+ + " <first>1000</first>\n"
+ + " <second>0.3</second>\n"
+ + " </point>"
+ + " <point>"
+ + " <first>5000</first>\n"
+ + " <second>0.5</second>\n"
+ + " </point>"
+ + " </map>\n"
+ + " </brightnessLimitMap>\n"
+ + " <brightnessLimitMap>\n"
+ + " <type>default</type>\n"
+ + " <map>\n"
+ + " <point>"
+ + " <first>1500</first>\n"
+ + " <second>0.35</second>\n"
+ + " </point>"
+ + " <point>"
+ + " <first>5500</first>\n"
+ + " <second>0.55</second>\n"
+ + " </point>"
+ + " </map>\n"
+ + " </brightnessLimitMap>\n"
+ + "</luxThrottling>";
+ }
+
+ private String getInvalidLuxThrottling() {
+ return "<luxThrottling>\n"
+ + " <brightnessLimitMap>\n"
+ + " <type>adaptive</type>\n"
+ + " <map>\n"
+ + " <point>"
+ + " <first>1000</first>\n"
+ + " <second>0.3</second>\n"
+ + " </point>"
+ + " <point>" // second > hbm.transitionPoint, skipped
+ + " <first>1500</first>\n"
+ + " <second>0.9</second>\n"
+ + " </point>"
+ + " <point>" // same lux value, skipped
+ + " <first>1000</first>\n"
+ + " <second>0.5</second>\n"
+ + " </point>"
+ + " </map>\n"
+ + " </brightnessLimitMap>\n"
+ + " <brightnessLimitMap>\n" // Same type, skipped
+ + " <type>adaptive</type>\n"
+ + " <map>\n"
+ + " <point>"
+ + " <first>2000</first>\n"
+ + " <second>0.35</second>\n"
+ + " </point>"
+ + " <point>"
+ + " <first>6000</first>\n"
+ + " <second>0.55</second>\n"
+ + " </point>"
+ + " </map>\n"
+ + " </brightnessLimitMap>\n"
+ + " <brightnessLimitMap>\n" // Invalid points only, skipped
+ + " <type>default</type>\n"
+ + " <map>\n"
+ + " <point>"
+ + " <first>2500</first>\n"
+ + " <second>0.99</second>\n"
+ + " </point>"
+ + " </map>\n"
+ + " </brightnessLimitMap>\n"
+ + "</luxThrottling>";
+ }
+
private String getRefreshThermalThrottlingMaps() {
return "<refreshRateThrottlingMap>\n"
+ " <refreshRateThrottlingPoint>\n"
@@ -405,6 +516,10 @@
}
private String getContent() {
+ return getContent(getValidLuxThrottling());
+ }
+
+ private String getContent(String brightnessCapConfig) {
return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ "<displayConfiguration>\n"
+ "<name>Example Display</name>"
@@ -462,6 +577,7 @@
+ "</point>\n"
+ "</sdrHdrRatioMap>\n"
+ "</highBrightnessMode>\n"
+ + brightnessCapConfig
+ "<screenOffBrightnessSensor>\n"
+ "<type>sensor_12345</type>\n"
+ "<name>Sensor 12345</name>\n"
@@ -731,8 +847,12 @@
}
private void setupDisplayDeviceConfigFromDisplayConfigFile() throws IOException {
+ setupDisplayDeviceConfigFromDisplayConfigFile(getContent());
+ }
+
+ private void setupDisplayDeviceConfigFromDisplayConfigFile(String content) throws IOException {
Path tempFile = Files.createTempFile("display_config", ".tmp");
- Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8));
+ Files.write(tempFile, content.getBytes(StandardCharsets.UTF_8));
mDisplayDeviceConfig = new DisplayDeviceConfig(mContext);
mDisplayDeviceConfig.initFromFile(tempFile.toFile());
}
diff --git a/services/tests/servicestests/src/com/android/server/display/NormalBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/NormalBrightnessModeControllerTest.java
new file mode 100644
index 0000000..c379d6b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/NormalBrightnessModeControllerTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2023 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.display;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.PowerManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.annotations.Keep;
+import com.android.server.display.DisplayDeviceConfig.BrightnessLimitMapType;
+
+import com.google.common.collect.ImmutableMap;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+@SmallTest
+@RunWith(JUnitParamsRunner.class)
+public class NormalBrightnessModeControllerTest {
+ private static final float FLOAT_TOLERANCE = 0.001f;
+
+ private final NormalBrightnessModeController mController = new NormalBrightnessModeController();
+
+ @Keep
+ private static Object[][] brightnessData() {
+ return new Object[][]{
+ // no brightness config
+ {0, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, new HashMap<>(),
+ PowerManager.BRIGHTNESS_MAX},
+ {0, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, new HashMap<>(),
+ PowerManager.BRIGHTNESS_MAX},
+ {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, new HashMap<>(),
+ PowerManager.BRIGHTNESS_MAX},
+ // Auto brightness - on, config only for default
+ {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, ImmutableMap.of(
+ BrightnessLimitMapType.DEFAULT,
+ ImmutableMap.of(99f, 0.1f, 101f, 0.2f)
+ ), 0.2f},
+ // Auto brightness - off, config only for default
+ {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of(
+ BrightnessLimitMapType.DEFAULT,
+ ImmutableMap.of(99f, 0.1f, 101f, 0.2f)
+ ), 0.2f},
+ // Auto brightness - off, config only for adaptive
+ {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of(
+ BrightnessLimitMapType.ADAPTIVE,
+ ImmutableMap.of(99f, 0.1f, 101f, 0.2f)
+ ), PowerManager.BRIGHTNESS_MAX},
+ // Auto brightness - on, config only for adaptive
+ {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, ImmutableMap.of(
+ BrightnessLimitMapType.ADAPTIVE,
+ ImmutableMap.of(99f, 0.1f, 101f, 0.2f)
+ ), 0.2f},
+ // Auto brightness - on, config for both
+ {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, ImmutableMap.of(
+ BrightnessLimitMapType.DEFAULT,
+ ImmutableMap.of(99f, 0.1f, 101f, 0.2f),
+ BrightnessLimitMapType.ADAPTIVE,
+ ImmutableMap.of(99f, 0.3f, 101f, 0.4f)
+ ), 0.4f},
+ // Auto brightness - off, config for both
+ {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of(
+ BrightnessLimitMapType.DEFAULT,
+ ImmutableMap.of(99f, 0.1f, 101f, 0.2f),
+ BrightnessLimitMapType.ADAPTIVE,
+ ImmutableMap.of(99f, 0.3f, 101f, 0.4f)
+ ), 0.2f},
+ // Auto brightness - on, config for both, ambient high
+ {1000, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, ImmutableMap.of(
+ BrightnessLimitMapType.DEFAULT,
+ ImmutableMap.of(1000f, 0.1f, 2000f, 0.2f),
+ BrightnessLimitMapType.ADAPTIVE,
+ ImmutableMap.of(99f, 0.3f, 101f, 0.4f)
+ ), PowerManager.BRIGHTNESS_MAX},
+ };
+ }
+
+ @Test
+ @Parameters(method = "brightnessData")
+ public void testReturnsCorrectMaxBrightness(float ambientLux, int autoBrightnessState,
+ Map<BrightnessLimitMapType, Map<Float, Float>> maxBrightnessConfig,
+ float expectedBrightness) {
+ setupController(ambientLux, autoBrightnessState, maxBrightnessConfig);
+
+ assertEquals(expectedBrightness, mController.getCurrentBrightnessMax(), FLOAT_TOLERANCE);
+ }
+
+ private void setupController(float ambientLux, int autoBrightnessState,
+ Map<BrightnessLimitMapType, Map<Float, Float>> maxBrightnessConfig) {
+ mController.onAmbientLuxChange(ambientLux);
+ mController.setAutoBrightnessState(autoBrightnessState);
+ mController.resetNbmData(maxBrightnessConfig);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
index aa6ee09..0b13f9a 100644
--- a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
@@ -52,7 +52,7 @@
private static final int SOUND_TRIGGER_MODE = 0; // SOUND_TRIGGER_MODE_ALL_ENABLED
private static final int DEFAULT_SOUND_TRIGGER_MODE =
PowerManager.SOUND_TRIGGER_MODE_CRITICAL_ONLY;
- private static final String BATTERY_SAVER_CONSTANTS = "disable_vibration=true,"
+ private static final String BATTERY_SAVER_CONSTANTS = "disable_vibration=false,"
+ "advertise_is_enabled=true,"
+ "disable_animation=false,"
+ "enable_firewall=true,"
@@ -117,7 +117,7 @@
@SmallTest
public void testGetBatterySaverPolicy_PolicyVibration_DefaultValueCorrect() {
- testServiceDefaultValue_On(ServiceType.VIBRATION);
+ testServiceDefaultValue_Off(ServiceType.VIBRATION);
}
@SmallTest
@@ -211,7 +211,7 @@
private void verifyBatterySaverConstantsUpdated() {
final PowerSaveState vibrationState =
mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.VIBRATION);
- assertThat(vibrationState.batterySaverEnabled).isTrue();
+ assertThat(vibrationState.batterySaverEnabled).isFalse();
final PowerSaveState animationState =
mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.ANIMATION);
diff --git a/tests/UiBench/Android.bp b/tests/UiBench/Android.bp
index 90e61c5..0d2f2ef 100644
--- a/tests/UiBench/Android.bp
+++ b/tests/UiBench/Android.bp
@@ -24,6 +24,5 @@
"androidx.recyclerview_recyclerview",
"androidx.leanback_leanback",
],
- certificate: "platform",
test_suites: ["device-tests"],
}
diff --git a/tests/UiBench/AndroidManifest.xml b/tests/UiBench/AndroidManifest.xml
index 47211c5..4fc6ec7 100644
--- a/tests/UiBench/AndroidManifest.xml
+++ b/tests/UiBench/AndroidManifest.xml
@@ -18,7 +18,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.android.test.uibench">
- <uses-permission android:name="android.permission.INJECT_EVENTS" />
<application android:allowBackup="false"
android:theme="@style/Theme.AppCompat.Light.DarkActionBar"
diff --git a/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java b/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java
index 1b2c3c6..06b65a7 100644
--- a/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java
+++ b/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java
@@ -15,15 +15,11 @@
*/
package com.android.test.uibench;
-import android.app.Instrumentation;
+import android.content.Intent;
import android.os.Bundle;
-import android.os.Looper;
-import android.os.MessageQueue;
-import androidx.appcompat.app.AppCompatActivity;
-import android.view.KeyEvent;
import android.widget.EditText;
-import java.util.concurrent.Semaphore;
+import androidx.appcompat.app.AppCompatActivity;
/**
* Note: currently incomplete, complexity of input continuously grows, instead of looping
@@ -32,7 +28,13 @@
* Simulates typing continuously into an EditText.
*/
public class EditTextTypeActivity extends AppCompatActivity {
- Thread mThread;
+
+ /**
+ * Broadcast action: Used to notify UiBenchEditTextTypingMicrobenchmark test when the
+ * test activity was paused.
+ */
+ private static final String ACTION_CANCEL_TYPING_CALLBACK =
+ "com.android.uibench.action.CANCEL_TYPING_CALLBACK";
private static String sSeedText = "";
static {
@@ -46,9 +48,6 @@
sSeedText = builder.toString();
}
- final Object mLock = new Object();
- boolean mShouldStop = false;
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -56,55 +55,13 @@
EditText editText = new EditText(this);
editText.setText(sSeedText);
setContentView(editText);
-
- final Instrumentation instrumentation = new Instrumentation();
- final Semaphore sem = new Semaphore(0);
- MessageQueue.IdleHandler handler = new MessageQueue.IdleHandler() {
- @Override
- public boolean queueIdle() {
- // TODO: consider other signaling approaches
- sem.release();
- return true;
- }
- };
- Looper.myQueue().addIdleHandler(handler);
- synchronized (mLock) {
- mShouldStop = false;
- }
- mThread = new Thread(new Runnable() {
- int codes[] = { KeyEvent.KEYCODE_H, KeyEvent.KEYCODE_E, KeyEvent.KEYCODE_L,
- KeyEvent.KEYCODE_L, KeyEvent.KEYCODE_O, KeyEvent.KEYCODE_SPACE };
- int i = 0;
- @Override
- public void run() {
- while (true) {
- try {
- sem.acquire();
- } catch (InterruptedException e) {
- // TODO, maybe
- }
- int code = codes[i % codes.length];
- if (i % 100 == 99) code = KeyEvent.KEYCODE_ENTER;
-
- synchronized (mLock) {
- if (mShouldStop) break;
- }
-
- // TODO: bit of a race here, since the event can arrive after pause/stop.
- // (Can't synchronize on key send, since it's synchronous.)
- instrumentation.sendKeyDownUpSync(code);
- i++;
- }
- }
- });
- mThread.start();
}
@Override
protected void onPause() {
- synchronized (mLock) {
- mShouldStop = true;
- }
+ // Cancel the typing when the test activity was paused.
+ sendBroadcast(new Intent(ACTION_CANCEL_TYPING_CALLBACK).addFlags(
+ Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY));
super.onPause();
}
}