Loading packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +44 −27 Original line number Diff line number Diff line Loading @@ -16,17 +16,16 @@ package com.android.systemui.biometrics; import android.annotation.NonNull; import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.SuppressLint; import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.PixelFormat; import android.graphics.Point; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.Handler; import android.os.Looper; import android.os.PowerManager; import android.os.UserHandle; import android.provider.Settings; Loading @@ -38,10 +37,16 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.BrightnessSynchronizer; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.DozeReceiver; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.settings.SystemSettings; import java.io.FileWriter; import java.io.IOException; Loading @@ -60,8 +65,8 @@ class UdfpsController implements DozeReceiver { private final FingerprintManager mFingerprintManager; private final WindowManager mWindowManager; private final ContentResolver mContentResolver; private final Handler mHandler; private final SystemSettings mSystemSettings; private final DelayableExecutor mFgExecutor; private final WindowManager.LayoutParams mLayoutParams; private final UdfpsView mView; // Debugfs path to control the high-brightness mode. Loading @@ -88,7 +93,7 @@ class UdfpsController implements DozeReceiver { // 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; @Nullable private Runnable mCancelAodTimeoutAction; public class UdfpsOverlayController extends IUdfpsOverlayController.Stub { @Override Loading Loading @@ -138,18 +143,27 @@ class UdfpsController implements DozeReceiver { @Inject UdfpsController(@NonNull Context context, @NonNull StatusBarStateController statusBarStateController) { mFingerprintManager = context.getSystemService(FingerprintManager.class); mWindowManager = context.getSystemService(WindowManager.class); mContentResolver = context.getContentResolver(); mHandler = new Handler(Looper.getMainLooper()); @Main Resources resources, LayoutInflater inflater, @Nullable FingerprintManager fingerprintManager, PowerManager powerManager, WindowManager windowManager, SystemSettings systemSettings, @NonNull StatusBarStateController statusBarStateController, @Main DelayableExecutor fgExecutor) { // The fingerprint manager is queried for UDFPS before this class is constructed, so the // fingerprint manager should never be null. mFingerprintManager = checkNotNull(fingerprintManager); mWindowManager = windowManager; mSystemSettings = systemSettings; mFgExecutor = fgExecutor; mLayoutParams = createLayoutParams(context); mView = (UdfpsView) LayoutInflater.from(context).inflate(R.layout.udfps_view, null, false); mView = (UdfpsView) inflater.inflate(R.layout.udfps_view, null, false); mHbmPath = context.getResources().getString(R.string.udfps_hbm_sysfs_path); mHbmEnableCommand = context.getResources().getString(R.string.udfps_hbm_enable_command); mHbmDisableCommand = context.getResources().getString(R.string.udfps_hbm_disable_command); mHbmPath = resources.getString(R.string.udfps_hbm_sysfs_path); mHbmEnableCommand = resources.getString(R.string.udfps_hbm_enable_command); mHbmDisableCommand = resources.getString(R.string.udfps_hbm_disable_command); mHbmSupported = !TextUtils.isEmpty(mHbmPath); mView.setHbmSupported(mHbmSupported); Loading @@ -157,11 +171,11 @@ class UdfpsController implements DozeReceiver { // This range only consists of the minimum and maximum values, which only cover // non-high-brightness mode. float[] nitsRange = toFloatArray(context.getResources().obtainTypedArray( float[] nitsRange = toFloatArray(resources.obtainTypedArray( com.android.internal.R.array.config_screenBrightnessNits)); // The last value of this range corresponds to the high-brightness mode. float[] nitsAutoBrightnessValues = toFloatArray(context.getResources().obtainTypedArray( float[] nitsAutoBrightnessValues = toFloatArray(resources.obtainTypedArray( com.android.internal.R.array.config_autoBrightnessDisplayValuesNits)); mHbmNits = nitsAutoBrightnessValues[nitsAutoBrightnessValues.length - 1]; Loading @@ -170,12 +184,12 @@ class UdfpsController implements DozeReceiver { // This range only consists of the minimum and maximum backlight values, which only apply // in non-high-brightness mode. float[] normalizedBacklightRange = normalizeBacklightRange( context.getResources().getIntArray( resources.getIntArray( com.android.internal.R.array.config_screenBrightnessBacklight)); mBacklightToNitsSpline = Spline.createSpline(normalizedBacklightRange, nitsRange); mNitsToHbmBacklightSpline = Spline.createSpline(hbmNitsRange, normalizedBacklightRange); mDefaultBrightness = obtainDefaultBrightness(context); mDefaultBrightness = obtainDefaultBrightness(powerManager); // TODO(b/160025856): move to the "dump" method. Log.v(TAG, String.format("ctor | mNitsRange: [%f, %f]", nitsRange[0], nitsRange[1])); Loading @@ -194,7 +208,7 @@ class UdfpsController implements DozeReceiver { } private void showUdfpsOverlay() { mHandler.post(() -> { mFgExecutor.execute(() -> { if (!mIsOverlayShowing) { try { Log.v(TAG, "showUdfpsOverlay | adding window"); Loading @@ -211,7 +225,7 @@ class UdfpsController implements DozeReceiver { } private void hideUdfpsOverlay() { mHandler.post(() -> { mFgExecutor.execute(() -> { if (mIsOverlayShowing) { Log.v(TAG, "hideUdfpsOverlay | removing window"); mView.setOnTouchListener(null); Loading @@ -228,7 +242,7 @@ class UdfpsController implements DozeReceiver { // Returns a value in the range of [0, 255]. private int computeScrimOpacity() { // Backlight setting can be NaN, -1.0f, and [0.0f, 1.0f]. float backlightSetting = Settings.System.getFloatForUser(mContentResolver, float backlightSetting = mSystemSettings.getFloatForUser( Settings.System.SCREEN_BRIGHTNESS_FLOAT, mDefaultBrightness, UserHandle.USER_CURRENT); Loading Loading @@ -265,7 +279,8 @@ class UdfpsController implements DozeReceiver { // 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); mCancelAodTimeoutAction = mFgExecutor.executeDelayed(this::onCancelAodInterrupt, 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); } Loading @@ -280,7 +295,10 @@ class UdfpsController implements DozeReceiver { if (!mIsAodInterruptActive) { return; } mHandler.removeCallbacks(mAodInterruptTimeoutAction); if (mCancelAodTimeoutAction != null) { mCancelAodTimeoutAction.run(); mCancelAodTimeoutAction = null; } mIsAodInterruptActive = false; onFingerUp(); } Loading Loading @@ -338,8 +356,7 @@ class UdfpsController implements DozeReceiver { return lp; } private static float obtainDefaultBrightness(Context context) { PowerManager powerManager = context.getSystemService(PowerManager.class); private static float obtainDefaultBrightness(PowerManager powerManager) { if (powerManager == null) { Log.e(TAG, "PowerManager is unavailable. Can't obtain default brightness."); return 0f; Loading packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java 0 → 100644 +210 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.res.Resources; import android.content.res.TypedArray; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.PowerManager; import android.os.RemoteException; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.WindowManager; import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.settings.FakeSettings; import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper public class UdfpsControllerTest extends SysuiTestCase { @Rule public MockitoRule rule = MockitoJUnit.rule(); // Unit under test private UdfpsController mUdfpsController; // Dependencies @Mock private Resources mResources; @Mock private LayoutInflater mLayoutInflater; @Mock private FingerprintManager mFingerprintManager; @Mock private PowerManager mPowerManager; @Mock private WindowManager mWindowManager; @Mock private StatusBarStateController mStatusBarStateController; private FakeSettings mSystemSettings; private FakeExecutor mFgExecutor; // Stuff for configuring mocks @Mock private UdfpsView mUdfpsView; @Mock private TypedArray mBrightnessValues; @Mock private TypedArray mBrightnessBacklight; // Capture listeners so that they can be used to send events @Captor private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor; private IUdfpsOverlayController mOverlayController; @Captor private ArgumentCaptor<UdfpsView.OnTouchListener> mTouchListenerCaptor; @Before public void setUp() { setUpResources(); when(mLayoutInflater.inflate(R.layout.udfps_view, null, false)).thenReturn(mUdfpsView); mSystemSettings = new FakeSettings(); mFgExecutor = new FakeExecutor(new FakeSystemClock()); mUdfpsController = new UdfpsController( mContext, mResources, mLayoutInflater, mFingerprintManager, mPowerManager, mWindowManager, mSystemSettings, mStatusBarStateController, mFgExecutor); verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture()); mOverlayController = mOverlayCaptor.getValue(); } private void setUpResources() { when(mBrightnessValues.length()).thenReturn(2); when(mBrightnessValues.getFloat(0, PowerManager.BRIGHTNESS_OFF_FLOAT)).thenReturn(1f); when(mBrightnessValues.getFloat(1, PowerManager.BRIGHTNESS_OFF_FLOAT)).thenReturn(2f); when(mResources.obtainTypedArray(com.android.internal.R.array.config_screenBrightnessNits)) .thenReturn(mBrightnessValues); when(mBrightnessBacklight.length()).thenReturn(2); when(mBrightnessBacklight.getFloat(0, PowerManager.BRIGHTNESS_OFF_FLOAT)).thenReturn(1f); when(mBrightnessBacklight.getFloat(1, PowerManager.BRIGHTNESS_OFF_FLOAT)).thenReturn(2f); when(mResources.obtainTypedArray( com.android.internal.R.array.config_autoBrightnessDisplayValuesNits)) .thenReturn(mBrightnessBacklight); when(mResources.getIntArray(com.android.internal.R.array.config_screenBrightnessBacklight)) .thenReturn(new int[]{1, 2}); } @Test public void dozeTimeTick() { mUdfpsController.dozeTimeTick(); verify(mUdfpsView).dozeTimeTick(); } @Test public void showUdfpsOverlay_addsViewToWindow() throws RemoteException { mOverlayController.showUdfpsOverlay(); mFgExecutor.runAllReady(); verify(mWindowManager).addView(eq(mUdfpsView), any()); } @Test public void hideUdfpsOverlay_removesViewFromWindow() throws RemoteException { mOverlayController.showUdfpsOverlay(); mOverlayController.hideUdfpsOverlay(); mFgExecutor.runAllReady(); verify(mWindowManager).removeView(eq(mUdfpsView)); } @Test public void fingerDown() throws RemoteException { // Configure UdfpsView to accept the ACTION_DOWN event when(mUdfpsView.isScrimShowing()).thenReturn(false); when(mUdfpsView.isValidTouch(anyFloat(), anyFloat(), anyFloat())).thenReturn(true); // GIVEN that the overlay is showing mOverlayController.showUdfpsOverlay(); mFgExecutor.runAllReady(); // WHEN ACTION_DOWN is received verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); event.recycle(); // THEN the event is passed to the FingerprintManager verify(mFingerprintManager).onFingerDown(eq(0), eq(0), eq(0f), eq(0f)); // AND the scrim and dot is shown verify(mUdfpsView).showScrimAndDot(); } @Test public void aodInterrupt() throws RemoteException { // GIVEN that the overlay is showing mOverlayController.showUdfpsOverlay(); mFgExecutor.runAllReady(); // WHEN fingerprint is requested because of AOD interrupt mUdfpsController.onAodInterrupt(0, 0); // THEN the event is passed to the FingerprintManager verify(mFingerprintManager).onFingerDown(eq(0), eq(0), anyFloat(), anyFloat()); // AND the scrim and dot is shown verify(mUdfpsView).showScrimAndDot(); } @Test public void cancelAodInterrupt() throws RemoteException { // GIVEN AOD interrupt mOverlayController.showUdfpsOverlay(); mFgExecutor.runAllReady(); mUdfpsController.onAodInterrupt(0, 0); // WHEN it is cancelled mUdfpsController.onCancelAodInterrupt(); // THEN the scrim and dot is hidden verify(mUdfpsView).hideScrimAndDot(); } @Test public void aodInterruptTimeout() throws RemoteException { // GIVEN AOD interrupt mOverlayController.showUdfpsOverlay(); mFgExecutor.runAllReady(); mUdfpsController.onAodInterrupt(0, 0); // WHEN it times out mFgExecutor.advanceClockToNext(); mFgExecutor.runAllReady(); // THEN the scrim and dot is hidden verify(mUdfpsView).hideScrimAndDot(); } } Loading
packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +44 −27 Original line number Diff line number Diff line Loading @@ -16,17 +16,16 @@ package com.android.systemui.biometrics; import android.annotation.NonNull; import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.SuppressLint; import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.PixelFormat; import android.graphics.Point; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.Handler; import android.os.Looper; import android.os.PowerManager; import android.os.UserHandle; import android.provider.Settings; Loading @@ -38,10 +37,16 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.BrightnessSynchronizer; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.DozeReceiver; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.settings.SystemSettings; import java.io.FileWriter; import java.io.IOException; Loading @@ -60,8 +65,8 @@ class UdfpsController implements DozeReceiver { private final FingerprintManager mFingerprintManager; private final WindowManager mWindowManager; private final ContentResolver mContentResolver; private final Handler mHandler; private final SystemSettings mSystemSettings; private final DelayableExecutor mFgExecutor; private final WindowManager.LayoutParams mLayoutParams; private final UdfpsView mView; // Debugfs path to control the high-brightness mode. Loading @@ -88,7 +93,7 @@ class UdfpsController implements DozeReceiver { // 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; @Nullable private Runnable mCancelAodTimeoutAction; public class UdfpsOverlayController extends IUdfpsOverlayController.Stub { @Override Loading Loading @@ -138,18 +143,27 @@ class UdfpsController implements DozeReceiver { @Inject UdfpsController(@NonNull Context context, @NonNull StatusBarStateController statusBarStateController) { mFingerprintManager = context.getSystemService(FingerprintManager.class); mWindowManager = context.getSystemService(WindowManager.class); mContentResolver = context.getContentResolver(); mHandler = new Handler(Looper.getMainLooper()); @Main Resources resources, LayoutInflater inflater, @Nullable FingerprintManager fingerprintManager, PowerManager powerManager, WindowManager windowManager, SystemSettings systemSettings, @NonNull StatusBarStateController statusBarStateController, @Main DelayableExecutor fgExecutor) { // The fingerprint manager is queried for UDFPS before this class is constructed, so the // fingerprint manager should never be null. mFingerprintManager = checkNotNull(fingerprintManager); mWindowManager = windowManager; mSystemSettings = systemSettings; mFgExecutor = fgExecutor; mLayoutParams = createLayoutParams(context); mView = (UdfpsView) LayoutInflater.from(context).inflate(R.layout.udfps_view, null, false); mView = (UdfpsView) inflater.inflate(R.layout.udfps_view, null, false); mHbmPath = context.getResources().getString(R.string.udfps_hbm_sysfs_path); mHbmEnableCommand = context.getResources().getString(R.string.udfps_hbm_enable_command); mHbmDisableCommand = context.getResources().getString(R.string.udfps_hbm_disable_command); mHbmPath = resources.getString(R.string.udfps_hbm_sysfs_path); mHbmEnableCommand = resources.getString(R.string.udfps_hbm_enable_command); mHbmDisableCommand = resources.getString(R.string.udfps_hbm_disable_command); mHbmSupported = !TextUtils.isEmpty(mHbmPath); mView.setHbmSupported(mHbmSupported); Loading @@ -157,11 +171,11 @@ class UdfpsController implements DozeReceiver { // This range only consists of the minimum and maximum values, which only cover // non-high-brightness mode. float[] nitsRange = toFloatArray(context.getResources().obtainTypedArray( float[] nitsRange = toFloatArray(resources.obtainTypedArray( com.android.internal.R.array.config_screenBrightnessNits)); // The last value of this range corresponds to the high-brightness mode. float[] nitsAutoBrightnessValues = toFloatArray(context.getResources().obtainTypedArray( float[] nitsAutoBrightnessValues = toFloatArray(resources.obtainTypedArray( com.android.internal.R.array.config_autoBrightnessDisplayValuesNits)); mHbmNits = nitsAutoBrightnessValues[nitsAutoBrightnessValues.length - 1]; Loading @@ -170,12 +184,12 @@ class UdfpsController implements DozeReceiver { // This range only consists of the minimum and maximum backlight values, which only apply // in non-high-brightness mode. float[] normalizedBacklightRange = normalizeBacklightRange( context.getResources().getIntArray( resources.getIntArray( com.android.internal.R.array.config_screenBrightnessBacklight)); mBacklightToNitsSpline = Spline.createSpline(normalizedBacklightRange, nitsRange); mNitsToHbmBacklightSpline = Spline.createSpline(hbmNitsRange, normalizedBacklightRange); mDefaultBrightness = obtainDefaultBrightness(context); mDefaultBrightness = obtainDefaultBrightness(powerManager); // TODO(b/160025856): move to the "dump" method. Log.v(TAG, String.format("ctor | mNitsRange: [%f, %f]", nitsRange[0], nitsRange[1])); Loading @@ -194,7 +208,7 @@ class UdfpsController implements DozeReceiver { } private void showUdfpsOverlay() { mHandler.post(() -> { mFgExecutor.execute(() -> { if (!mIsOverlayShowing) { try { Log.v(TAG, "showUdfpsOverlay | adding window"); Loading @@ -211,7 +225,7 @@ class UdfpsController implements DozeReceiver { } private void hideUdfpsOverlay() { mHandler.post(() -> { mFgExecutor.execute(() -> { if (mIsOverlayShowing) { Log.v(TAG, "hideUdfpsOverlay | removing window"); mView.setOnTouchListener(null); Loading @@ -228,7 +242,7 @@ class UdfpsController implements DozeReceiver { // Returns a value in the range of [0, 255]. private int computeScrimOpacity() { // Backlight setting can be NaN, -1.0f, and [0.0f, 1.0f]. float backlightSetting = Settings.System.getFloatForUser(mContentResolver, float backlightSetting = mSystemSettings.getFloatForUser( Settings.System.SCREEN_BRIGHTNESS_FLOAT, mDefaultBrightness, UserHandle.USER_CURRENT); Loading Loading @@ -265,7 +279,8 @@ class UdfpsController implements DozeReceiver { // 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); mCancelAodTimeoutAction = mFgExecutor.executeDelayed(this::onCancelAodInterrupt, 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); } Loading @@ -280,7 +295,10 @@ class UdfpsController implements DozeReceiver { if (!mIsAodInterruptActive) { return; } mHandler.removeCallbacks(mAodInterruptTimeoutAction); if (mCancelAodTimeoutAction != null) { mCancelAodTimeoutAction.run(); mCancelAodTimeoutAction = null; } mIsAodInterruptActive = false; onFingerUp(); } Loading Loading @@ -338,8 +356,7 @@ class UdfpsController implements DozeReceiver { return lp; } private static float obtainDefaultBrightness(Context context) { PowerManager powerManager = context.getSystemService(PowerManager.class); private static float obtainDefaultBrightness(PowerManager powerManager) { if (powerManager == null) { Log.e(TAG, "PowerManager is unavailable. Can't obtain default brightness."); return 0f; Loading
packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java 0 → 100644 +210 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.res.Resources; import android.content.res.TypedArray; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.PowerManager; import android.os.RemoteException; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.WindowManager; import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.settings.FakeSettings; import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper public class UdfpsControllerTest extends SysuiTestCase { @Rule public MockitoRule rule = MockitoJUnit.rule(); // Unit under test private UdfpsController mUdfpsController; // Dependencies @Mock private Resources mResources; @Mock private LayoutInflater mLayoutInflater; @Mock private FingerprintManager mFingerprintManager; @Mock private PowerManager mPowerManager; @Mock private WindowManager mWindowManager; @Mock private StatusBarStateController mStatusBarStateController; private FakeSettings mSystemSettings; private FakeExecutor mFgExecutor; // Stuff for configuring mocks @Mock private UdfpsView mUdfpsView; @Mock private TypedArray mBrightnessValues; @Mock private TypedArray mBrightnessBacklight; // Capture listeners so that they can be used to send events @Captor private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor; private IUdfpsOverlayController mOverlayController; @Captor private ArgumentCaptor<UdfpsView.OnTouchListener> mTouchListenerCaptor; @Before public void setUp() { setUpResources(); when(mLayoutInflater.inflate(R.layout.udfps_view, null, false)).thenReturn(mUdfpsView); mSystemSettings = new FakeSettings(); mFgExecutor = new FakeExecutor(new FakeSystemClock()); mUdfpsController = new UdfpsController( mContext, mResources, mLayoutInflater, mFingerprintManager, mPowerManager, mWindowManager, mSystemSettings, mStatusBarStateController, mFgExecutor); verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture()); mOverlayController = mOverlayCaptor.getValue(); } private void setUpResources() { when(mBrightnessValues.length()).thenReturn(2); when(mBrightnessValues.getFloat(0, PowerManager.BRIGHTNESS_OFF_FLOAT)).thenReturn(1f); when(mBrightnessValues.getFloat(1, PowerManager.BRIGHTNESS_OFF_FLOAT)).thenReturn(2f); when(mResources.obtainTypedArray(com.android.internal.R.array.config_screenBrightnessNits)) .thenReturn(mBrightnessValues); when(mBrightnessBacklight.length()).thenReturn(2); when(mBrightnessBacklight.getFloat(0, PowerManager.BRIGHTNESS_OFF_FLOAT)).thenReturn(1f); when(mBrightnessBacklight.getFloat(1, PowerManager.BRIGHTNESS_OFF_FLOAT)).thenReturn(2f); when(mResources.obtainTypedArray( com.android.internal.R.array.config_autoBrightnessDisplayValuesNits)) .thenReturn(mBrightnessBacklight); when(mResources.getIntArray(com.android.internal.R.array.config_screenBrightnessBacklight)) .thenReturn(new int[]{1, 2}); } @Test public void dozeTimeTick() { mUdfpsController.dozeTimeTick(); verify(mUdfpsView).dozeTimeTick(); } @Test public void showUdfpsOverlay_addsViewToWindow() throws RemoteException { mOverlayController.showUdfpsOverlay(); mFgExecutor.runAllReady(); verify(mWindowManager).addView(eq(mUdfpsView), any()); } @Test public void hideUdfpsOverlay_removesViewFromWindow() throws RemoteException { mOverlayController.showUdfpsOverlay(); mOverlayController.hideUdfpsOverlay(); mFgExecutor.runAllReady(); verify(mWindowManager).removeView(eq(mUdfpsView)); } @Test public void fingerDown() throws RemoteException { // Configure UdfpsView to accept the ACTION_DOWN event when(mUdfpsView.isScrimShowing()).thenReturn(false); when(mUdfpsView.isValidTouch(anyFloat(), anyFloat(), anyFloat())).thenReturn(true); // GIVEN that the overlay is showing mOverlayController.showUdfpsOverlay(); mFgExecutor.runAllReady(); // WHEN ACTION_DOWN is received verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); event.recycle(); // THEN the event is passed to the FingerprintManager verify(mFingerprintManager).onFingerDown(eq(0), eq(0), eq(0f), eq(0f)); // AND the scrim and dot is shown verify(mUdfpsView).showScrimAndDot(); } @Test public void aodInterrupt() throws RemoteException { // GIVEN that the overlay is showing mOverlayController.showUdfpsOverlay(); mFgExecutor.runAllReady(); // WHEN fingerprint is requested because of AOD interrupt mUdfpsController.onAodInterrupt(0, 0); // THEN the event is passed to the FingerprintManager verify(mFingerprintManager).onFingerDown(eq(0), eq(0), anyFloat(), anyFloat()); // AND the scrim and dot is shown verify(mUdfpsView).showScrimAndDot(); } @Test public void cancelAodInterrupt() throws RemoteException { // GIVEN AOD interrupt mOverlayController.showUdfpsOverlay(); mFgExecutor.runAllReady(); mUdfpsController.onAodInterrupt(0, 0); // WHEN it is cancelled mUdfpsController.onCancelAodInterrupt(); // THEN the scrim and dot is hidden verify(mUdfpsView).hideScrimAndDot(); } @Test public void aodInterruptTimeout() throws RemoteException { // GIVEN AOD interrupt mOverlayController.showUdfpsOverlay(); mFgExecutor.runAllReady(); mUdfpsController.onAodInterrupt(0, 0); // WHEN it times out mFgExecutor.advanceClockToNext(); mFgExecutor.runAllReady(); // THEN the scrim and dot is hidden verify(mUdfpsView).hideScrimAndDot(); } }