diff --git a/core/java/android/hardware/display/AmbientDisplayConfiguration.java b/core/java/android/hardware/display/AmbientDisplayConfiguration.java index ece5c28884fae..7dc1eaabdc9c8 100644 --- a/core/java/android/hardware/display/AmbientDisplayConfiguration.java +++ b/core/java/android/hardware/display/AmbientDisplayConfiguration.java @@ -137,6 +137,11 @@ public class AmbientDisplayConfiguration { return mContext.getResources().getString(R.string.config_dozeLongPressSensorType); } + /** {@hide} */ + public String udfpsLongPressSensorType() { + return mContext.getResources().getString(R.string.config_dozeUdfpsLongPressSensorType); + } + /** {@hide} */ public boolean pulseOnLongPressEnabled(int user) { return pulseOnLongPressAvailable() && boolSettingDefaultOff( diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 550162a242dc8..6b670075ec1fa 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2127,6 +2127,9 @@ + + + false 300 @@ -4082,7 +4085,7 @@ - M3.5,2 v0 H1.33 C0.6,2 0,2.6 0,3.33 V13v5.67 C0,19.4 0.6,20 1.33,20 h9.33 C11.4,20 12,19.4 12,18.67 V13V3.33 C12,2.6 11.4,2 10.67,2 H8.5 V0 H3.5 z M2,18v-7V4h8v9v5H2L2,18z + M3.5,2 v0 H1.33 C0.6,2 0,2.6 0,3.33 V13v5.67 C0,19.4 0.6,20 1.33,20 h9.33 C11.4,20 12,19.4 12,18.67 V13V3.33 C12,2.6 11.4,2 10.67,2 H8.5 V0 H3.5 z M2,18v-7V4h8v9v5H2L2,18z @string/config_batterymeterPerimeterPath diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 35ce780d34080..f102077a9e903 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3484,6 +3484,7 @@ + diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index ea18b11413ef4..b29eff6c23ea3 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -57,6 +57,7 @@ import com.android.systemui.statusbar.CommandQueue; import java.util.List; import javax.inject.Inject; +import javax.inject.Provider; /** * Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the @@ -72,6 +73,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, private final CommandQueue mCommandQueue; private final StatusBarStateController mStatusBarStateController; private final Injector mInjector; + private final Provider mUdfpsControllerFactory; // TODO: These should just be saved from onSaveState private SomeArgs mCurrentDialogArgs; @@ -237,6 +239,34 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, } } + /** + * Requests fingerprint scan. + * + * @param screenX X position of long press + * @param screenY Y position of long press + */ + public void onAodInterrupt(int screenX, int screenY) { + if (mUdfpsController == null) { + return; + } + mUdfpsController.onAodInterrupt(screenX, screenY); + } + + /** + * Cancel a fingerprint scan. + * + * The sensor that triggers an AOD interrupt for fingerprint doesn't give + * ACTION_UP/ACTION_CANCEL events, so the scan needs to be cancelled manually. This should be + * called when authentication either succeeds or fails. Failing to cancel the scan will leave + * the screen in high brightness mode. + */ + private void onCancelAodInterrupt() { + if (mUdfpsController == null) { + return; + } + mUdfpsController.onCancelAodInterrupt(); + } + private void sendResultAndCleanUp(@DismissedReason int reason, @Nullable byte[] credentialAttestation) { if (mReceiver == null) { @@ -263,17 +293,21 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, @Inject public AuthController(Context context, CommandQueue commandQueue, - StatusBarStateController statusBarStateController) { - this(context, commandQueue, statusBarStateController, new Injector()); + StatusBarStateController statusBarStateController, + Provider udfpsControllerFactory) { + this(context, commandQueue, statusBarStateController, new Injector(), + udfpsControllerFactory); } @VisibleForTesting AuthController(Context context, CommandQueue commandQueue, - StatusBarStateController statusBarStateController, Injector injector) { + StatusBarStateController statusBarStateController, Injector injector, + Provider udfpsControllerFactory) { super(context); mCommandQueue = commandQueue; mStatusBarStateController = statusBarStateController; mInjector = injector; + mUdfpsControllerFactory = udfpsControllerFactory; IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); @@ -294,7 +328,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, fpm.getSensorProperties(); for (FingerprintSensorProperties props : fingerprintSensorProperties) { if (props.sensorType == FingerprintSensorProperties.TYPE_UDFPS) { - mUdfpsController = new UdfpsController(mContext, mStatusBarStateController); + mUdfpsController = mUdfpsControllerFactory.get(); break; } } @@ -341,6 +375,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, @Override public void onBiometricAuthenticated() { mCurrentDialog.onAuthenticationSucceeded(); + onCancelAodInterrupt(); } @Override @@ -390,6 +425,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, if (DEBUG) Log.d(TAG, "onBiometricError, hard error: " + errorMessage); mCurrentDialog.onError(errorMessage); } + onCancelAodInterrupt(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 82fb80892ab16..06c190f1964c9 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -46,6 +46,8 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import java.io.FileWriter; import java.io.IOException; +import javax.inject.Inject; + /** * Shows and hides the under-display fingerprint sensor (UDFPS) overlay, handles UDFPS touch events, * and coordinates triggering of the high-brightness mode (HBM). @@ -54,6 +56,7 @@ class UdfpsController implements DozeReceiver { private static final String TAG = "UdfpsController"; // Gamma approximation for the sRGB color space. private static final float DISPLAY_GAMMA = 2.2f; + private static final long AOD_INTERRUPT_TIMEOUT_MILLIS = 1000; private final FingerprintManager mFingerprintManager; private final WindowManager mWindowManager; @@ -80,6 +83,13 @@ class UdfpsController implements DozeReceiver { private final float mDefaultBrightness; private boolean mIsOverlayShowing; + // The fingerprint AOD trigger doesn't provide an ACTION_UP/ACTION_CANCEL event to tell us when + // to turn off high brightness mode. To get around this limitation, the state of the AOD + // interrupt is being tracked and a timeout is used as a last resort to turn off high brightness + // mode. + private boolean mIsAodInterruptActive; + private final Runnable mAodInterruptTimeoutAction = this::onCancelAodInterrupt; + public class UdfpsOverlayController extends IUdfpsOverlayController.Stub { @Override public void showUdfpsOverlay() { @@ -126,6 +136,7 @@ class UdfpsController implements DozeReceiver { } }; + @Inject UdfpsController(@NonNull Context context, @NonNull StatusBarStateController statusBarStateController) { mFingerprintManager = context.getSystemService(FingerprintManager.class); @@ -240,6 +251,40 @@ class UdfpsController implements DozeReceiver { return BrightnessSynchronizer.brightnessFloatToInt(scrimOpacity); } + /** + * Request fingerprint scan. + * + * This is intented to be called in response to a sensor that triggers an AOD interrupt for the + * fingerprint sensor. + */ + void onAodInterrupt(int screenX, int screenY) { + if (mIsAodInterruptActive) { + return; + } + mIsAodInterruptActive = true; + // Since the sensor that triggers the AOD interrupt doesn't provide ACTION_UP/ACTION_CANCEL, + // we need to be careful about not letting the screen accidentally remain in high brightness + // mode. As a mitigation, queue a call to cancel the fingerprint scan. + mHandler.postDelayed(mAodInterruptTimeoutAction, AOD_INTERRUPT_TIMEOUT_MILLIS); + // using a hard-coded value for major and minor until it is available from the sensor + onFingerDown(screenX, screenY, 13.0f, 13.0f); + } + + /** + * Cancel fingerprint scan. + * + * This is intented to be called after the fingerprint scan triggered by the AOD interrupt + * either succeeds or fails. + */ + void onCancelAodInterrupt() { + if (!mIsAodInterruptActive) { + return; + } + mHandler.removeCallbacks(mAodInterruptTimeoutAction); + mIsAodInterruptActive = false; + onFingerUp(); + } + private void onFingerDown(int x, int y, float minor, float major) { mView.setScrimAlpha(computeScrimOpacity()); mView.showScrimAndDot(); diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java index 99d2651ae9eaf..424a8246b2786 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java @@ -339,6 +339,7 @@ public class DozeLog implements Dumpable { case PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN: return "wakelockscreen"; case REASON_SENSOR_WAKE_UP: return "wakeup"; case REASON_SENSOR_TAP: return "tap"; + case REASON_SENSOR_UDFPS_LONG_PRESS: return "udfps"; default: throw new IllegalArgumentException("invalid reason: " + pulseReason); } } @@ -347,7 +348,8 @@ public class DozeLog implements Dumpable { @IntDef({PULSE_REASON_NONE, PULSE_REASON_INTENT, PULSE_REASON_NOTIFICATION, PULSE_REASON_SENSOR_SIGMOTION, REASON_SENSOR_PICKUP, REASON_SENSOR_DOUBLE_TAP, PULSE_REASON_SENSOR_LONG_PRESS, PULSE_REASON_DOCKING, REASON_SENSOR_WAKE_UP, - PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN, REASON_SENSOR_TAP}) + PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN, REASON_SENSOR_TAP, + REASON_SENSOR_UDFPS_LONG_PRESS}) public @interface Reason {} public static final int PULSE_REASON_NONE = -1; public static final int PULSE_REASON_INTENT = 0; @@ -360,6 +362,7 @@ public class DozeLog implements Dumpable { public static final int REASON_SENSOR_WAKE_UP = 7; public static final int PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN = 8; public static final int REASON_SENSOR_TAP = 9; + public static final int REASON_SENSOR_UDFPS_LONG_PRESS = 10; - public static final int TOTAL_REASONS = 10; + public static final int TOTAL_REASONS = 11; } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java index 524d9c8536b84..028870f3815e2 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java @@ -150,6 +150,15 @@ public class DozeSensors { true /* reports touch coordinates */, true /* touchscreen */, dozeLog), + new TriggerSensor( + findSensorWithType(config.udfpsLongPressSensorType()), + Settings.Secure.DOZE_PULSE_ON_LONG_PRESS, + false /* settingDef */, + true /* configured */, + DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS, + true /* reports touch coordinates */, + true /* touchscreen */, + dozeLog), new PluginSensor( new SensorManagerPlugin.Sensor(TYPE_WAKE_DISPLAY), Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE, diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index 8364b486c8d74..45e5c614ea586 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -39,6 +39,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLoggerImpl; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.Dependency; +import com.android.systemui.biometrics.AuthController; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dock.DockManager; import com.android.systemui.doze.dagger.DozeScope; @@ -93,6 +94,7 @@ public class DozeTriggers implements DozeMachine.Part { private final DockManager mDockManager; private final ProximitySensor.ProximityCheck mProxCheck; private final BroadcastDispatcher mBroadcastDispatcher; + private final AuthController mAuthController; private long mNotificationPulseTime; private boolean mPulsePending; @@ -165,7 +167,7 @@ public class DozeTriggers implements DozeMachine.Part { WakeLock wakeLock, DockManager dockManager, ProximitySensor proximitySensor, ProximitySensor.ProximityCheck proxCheck, DozeLog dozeLog, BroadcastDispatcher broadcastDispatcher, - SecureSettings secureSettings) { + SecureSettings secureSettings, AuthController authController) { mContext = context; mDozeHost = dozeHost; mConfig = config; @@ -181,6 +183,7 @@ public class DozeTriggers implements DozeMachine.Part { mProxCheck = proxCheck; mDozeLog = dozeLog; mBroadcastDispatcher = broadcastDispatcher; + mAuthController = authController; } @Override @@ -256,6 +259,7 @@ public class DozeTriggers implements DozeMachine.Part { boolean isLongPress = pulseReason == DozeLog.PULSE_REASON_SENSOR_LONG_PRESS; boolean isWakeDisplay = pulseReason == DozeLog.REASON_SENSOR_WAKE_UP; boolean isWakeLockScreen = pulseReason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN; + boolean isUdfpsLongPress = pulseReason == DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS; boolean wakeEvent = rawValues != null && rawValues.length > 0 && rawValues[0] != 0; if (isWakeDisplay) { @@ -281,6 +285,11 @@ public class DozeTriggers implements DozeMachine.Part { gentleWakeUp(pulseReason); } else if (isPickup) { gentleWakeUp(pulseReason); + } else if (isUdfpsLongPress) { + gentleWakeUp(pulseReason); + // Since the gesture won't be received by the UDFPS view, manually inject an + // event. + mAuthController.onAodInterrupt((int) screenX, (int) screenY); } else { mDozeHost.extendPulse(pulseReason); } 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 c8566c599108e..7cebc9ff63457 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -46,6 +46,7 @@ import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; +import android.hardware.fingerprint.FingerprintSensorProperties; import android.os.Bundle; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; @@ -69,6 +70,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Random; +import javax.inject.Provider; + @RunWith(AndroidTestingRunner.class) @RunWithLooper @SmallTest @@ -82,6 +85,14 @@ public class AuthControllerTest extends SysuiTestCase { private AuthDialog mDialog1; @Mock private AuthDialog mDialog2; + @Mock + private CommandQueue mCommandQueue; + @Mock + private StatusBarStateController mStatusBarStateController; + @Mock + private FingerprintManager mFingerprintManager; + @Mock + private UdfpsController mUdfpsController; private TestableAuthController mAuthController; @@ -104,8 +115,16 @@ public class AuthControllerTest extends SysuiTestCase { when(mDialog1.isAllowDeviceCredentials()).thenReturn(false); when(mDialog2.isAllowDeviceCredentials()).thenReturn(false); - mAuthController = new TestableAuthController(context, mock(CommandQueue.class), - mock(StatusBarStateController.class), new MockInjector()); + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + FingerprintSensorProperties prop = new FingerprintSensorProperties( + 1, FingerprintSensorProperties.TYPE_UDFPS, true, 1); + List props = new ArrayList<>(); + props.add(prop); + when(mFingerprintManager.getSensorProperties()).thenReturn(props); + + mAuthController = new TestableAuthController(context, mCommandQueue, + mStatusBarStateController, new MockInjector(), + () -> mUdfpsController); mAuthController.start(); } @@ -463,6 +482,27 @@ public class AuthControllerTest extends SysuiTestCase { eq(null) /* credentialAttestation */); } + @Test + public void testOnAodInterrupt() { + final int pos = 10; + mAuthController.onAodInterrupt(pos, pos); + verify(mUdfpsController).onAodInterrupt(eq(pos), eq(pos)); + } + + @Test + public void testOnBiometricAuthenticated_OnCancelAodInterrupt() { + showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FINGERPRINT); + mAuthController.onBiometricAuthenticated(); + verify(mUdfpsController).onCancelAodInterrupt(); + } + + @Test + public void testOnBiometricError_OnCancelAodInterrupt() { + showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FINGERPRINT); + mAuthController.onBiometricError(0, 0, 0); + verify(mUdfpsController).onCancelAodInterrupt(); + } + // Helpers private void showDialog(int authenticators, int biometricModality) { @@ -504,8 +544,10 @@ public class AuthControllerTest extends SysuiTestCase { private PromptInfo mLastBiometricPromptInfo; TestableAuthController(Context context, CommandQueue commandQueue, - StatusBarStateController statusBarStateController, Injector injector) { - super(context, commandQueue, statusBarStateController, injector); + StatusBarStateController statusBarStateController, Injector injector, + Provider udfpsControllerFactory) { + super(context, commandQueue, statusBarStateController, injector, + udfpsControllerFactory); } @Override @@ -536,7 +578,7 @@ public class AuthControllerTest extends SysuiTestCase { @Override FingerprintManager getFingerprintManager(Context context) { - return mock(FingerprintManager.class); + return mFingerprintManager; } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java index c591c1bd42bcf..9fd9b470a83b6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java @@ -59,6 +59,7 @@ public class DozeConfigurationUtil { when(config.doubleTapSensorType()).thenReturn(null); when(config.tapSensorType()).thenReturn(null); when(config.longPressSensorType()).thenReturn(null); + when(config.udfpsLongPressSensorType()).thenReturn(null); when(config.tapGestureEnabled(anyInt())).thenReturn(true); when(config.tapSensorAvailable()).thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java index 1ed58714fb9f2..3ae02a469095d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java @@ -37,6 +37,7 @@ import android.view.Display; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.biometrics.AuthController; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dock.DockManager; import com.android.systemui.statusbar.phone.DozeParameters; @@ -76,6 +77,8 @@ public class DozeTriggersTest extends SysuiTestCase { private DockManager mDockManager; @Mock private ProximitySensor.ProximityCheck mProximityCheck; + @Mock + private AuthController mAuthController; private DozeTriggers mTriggers; private FakeSensorManager mSensors; @@ -100,7 +103,8 @@ public class DozeTriggersTest extends SysuiTestCase { mTriggers = new DozeTriggers(mContext, mHost, mAlarmManager, config, parameters, asyncSensorManager, wakeLock, mDockManager, mProximitySensor, - mProximityCheck, mock(DozeLog.class), mBroadcastDispatcher, new FakeSettings()); + mProximityCheck, mock(DozeLog.class), mBroadcastDispatcher, new FakeSettings(), + mAuthController); mTriggers.setDozeMachine(mMachine); waitForSensorManager(); } @@ -186,6 +190,15 @@ public class DozeTriggersTest extends SysuiTestCase { mTriggers.onSensor(DozeLog.REASON_SENSOR_TAP, 100, 100, null); } + @Test + public void testOnSensor_Fingerprint() { + final int screenX = 100; + final int screenY = 100; + final int reason = DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS; + mTriggers.onSensor(reason, screenX, screenY, null); + verify(mAuthController).onAodInterrupt(eq(screenX), eq(screenY)); + } + private void waitForSensorManager() { mExecutor.runAllReady(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java index 37ccac0b23b33..23c093033ae04 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java @@ -179,7 +179,8 @@ public class DozeServiceHostTest extends SysuiTestCase { HashSet reasonsThatDontPulse = new HashSet<>( Arrays.asList(DozeLog.REASON_SENSOR_PICKUP, DozeLog.REASON_SENSOR_DOUBLE_TAP, - DozeLog.REASON_SENSOR_TAP)); + DozeLog.REASON_SENSOR_TAP, + DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS)); doAnswer(invocation -> { DozeHost.PulseCallback callback = invocation.getArgument(0);