UDFPS on AOD

This change establishes a connection between the AOD interrupt and the fingerprint sensor.

Bug: 165267247
Test: manual - enter device from AOD with fingerprint
Test: manual - On AOD, fail fingerprint auth, check that HBM turns off
Change-Id: Ia8ca9a145590d8b5629a584807e110ca3600b813
This commit is contained in:
Robert Snoeberger
2020-08-24 14:17:53 -04:00
parent 0e3e197c50
commit 96037a4695
12 changed files with 183 additions and 15 deletions

View File

@@ -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(

View File

@@ -2127,6 +2127,9 @@
<!-- Type of the long press sensor. Empty if long press is not supported. -->
<string name="config_dozeLongPressSensorType" translatable="false"></string>
<!-- Type of the udfps long press sensor. Empty if long press is not supported. -->
<string name="config_dozeUdfpsLongPressSensorType" translatable="false"></string>
<!-- If the sensor that wakes up the lock screen is available or not. -->
<bool name="config_dozeWakeLockScreenSensorAvailable">false</bool>
<integer name="config_dozeWakeLockScreenDebounce">300</integer>
@@ -4082,7 +4085,7 @@
<!-- All of the paths defined for the batterymeter are defined on a 12x20 canvas, and must
be parsable by android.utill.PathParser -->
<string name="config_batterymeterPerimeterPath" translatable="false">
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>
<string name="config_batterymeterErrorPerimeterPath" translatable="false">@string/config_batterymeterPerimeterPath</string>
<string name="config_batterymeterFillMask" translatable="false">

View File

@@ -3484,6 +3484,7 @@
<java-symbol type="array" name="config_hideWhenDisabled_packageNames" />
<java-symbol type="string" name="config_dozeLongPressSensorType" />
<java-symbol type="string" name="config_dozeUdfpsLongPressSensorType" />
<java-symbol type="bool" name="config_dozeWakeLockScreenSensorAvailable" />
<java-symbol type="integer" name="config_dozeWakeLockScreenDebounce" />

View File

@@ -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<UdfpsController> 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<UdfpsController> udfpsControllerFactory) {
this(context, commandQueue, statusBarStateController, new Injector(),
udfpsControllerFactory);
}
@VisibleForTesting
AuthController(Context context, CommandQueue commandQueue,
StatusBarStateController statusBarStateController, Injector injector) {
StatusBarStateController statusBarStateController, Injector injector,
Provider<UdfpsController> 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

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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<FingerprintSensorProperties> 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<UdfpsController> 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;
}
}
}

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -179,7 +179,8 @@ public class DozeServiceHostTest extends SysuiTestCase {
HashSet<Integer> 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);