From ca475a5d3772788aa7133cdd4cbed1a2f2bcf2ea Mon Sep 17 00:00:00 2001 From: changbetty Date: Thu, 12 Jan 2023 14:37:46 +0000 Subject: [PATCH 1/3] [LE Audio] To add the vibrate when the scan is successful Bug: 242023882 Test: manual test Change-Id: Iabff5fb81e307222fb33541e4bf0008830321d3e --- .../bluetooth/QrCodeScanModeFragment.java | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/src/com/android/settings/bluetooth/QrCodeScanModeFragment.java b/src/com/android/settings/bluetooth/QrCodeScanModeFragment.java index 0253aa6b748..767461bba7e 100644 --- a/src/com/android/settings/bluetooth/QrCodeScanModeFragment.java +++ b/src/com/android/settings/bluetooth/QrCodeScanModeFragment.java @@ -26,6 +26,8 @@ import android.graphics.SurfaceTexture; import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.os.VibrationEffect; +import android.os.Vibrator; import android.util.Log; import android.util.Size; import android.view.LayoutInflater; @@ -45,6 +47,8 @@ import com.android.settingslib.bluetooth.BluetoothBroadcastUtils; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.qrcode.QrCamera; +import java.time.Duration; + public class QrCodeScanModeFragment extends InstrumentedFragment implements TextureView.SurfaceTextureListener, QrCamera.ScannerCallback { @@ -61,6 +65,8 @@ public class QrCodeScanModeFragment extends InstrumentedFragment implements private static final long SHOW_ERROR_MESSAGE_INTERVAL = 10000; private static final long SHOW_SUCCESS_SQUARE_INTERVAL = 1000; + private static final Duration VIBRATE_DURATION_QR_CODE_RECOGNITION = Duration.ofMillis(3); + private boolean mIsGroupOp; private int mCornerRadius; private BluetoothDevice mSink; @@ -209,16 +215,39 @@ public class QrCodeScanModeFragment extends InstrumentedFragment implements break; case MESSAGE_SCAN_BROADCAST_SUCCESS: + /* TODO(b/265281156) : Move the logic to BluetoothFindBroadcastsFragment. + * We only pass the QR code string to the previous page. + */ mController.addSource(mSink, mBroadcastMetadata, mIsGroupOp); - updateSummary(); - mSummary.sendAccessibilityEvent( - AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + notifyUserForQrCodeRecognition(); break; default: } } }; + private void notifyUserForQrCodeRecognition() { + if (mCamera != null) { + mCamera.stop(); + } + + mErrorMessage.setVisibility(View.INVISIBLE); + + triggerVibrationForQrCodeRecognition(getContext()); + + getActivity().finish(); + } + + private static void triggerVibrationForQrCodeRecognition(Context context) { + Vibrator vibrator = context.getSystemService(Vibrator.class); + if (vibrator == null) { + return; + } + vibrator.vibrate(VibrationEffect.createOneShot( + VIBRATE_DURATION_QR_CODE_RECOGNITION.toMillis(), + VibrationEffect.DEFAULT_AMPLITUDE)); + } + private void showErrorMessage(@StringRes int messageResId) { final Message message = mHandler.obtainMessage(MESSAGE_SHOW_ERROR_MESSAGE, getString(messageResId)); From af880ef71804ff868c334d7426fb416646c38d47 Mon Sep 17 00:00:00 2001 From: Milton Wu Date: Thu, 12 Jan 2023 23:05:47 +0800 Subject: [PATCH 2/3] Add FingerprintEnrollProgressViewModelTest Bug: 259664912 Test: m RunSettingsRoboTests ROBOTEST_FILTER=FingerprintEnrollProgressViewModelTest Change-Id: I10605a9f179c20c3a1ba23373485855a0cbb7099 --- .../com/android/settings/biometrics2/OWNERS | 1 + ...ingerprintEnrollProgressViewModelTest.java | 179 ++++++++++++++++++ .../testutils/InstantTaskExecutorRule.java | 59 ++++++ 3 files changed, 239 insertions(+) create mode 100644 tests/robotests/src/com/android/settings/biometrics2/OWNERS create mode 100644 tests/robotests/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModelTest.java create mode 100644 tests/robotests/src/com/android/settings/testutils/InstantTaskExecutorRule.java diff --git a/tests/robotests/src/com/android/settings/biometrics2/OWNERS b/tests/robotests/src/com/android/settings/biometrics2/OWNERS new file mode 100644 index 00000000000..a257ed861ad --- /dev/null +++ b/tests/robotests/src/com/android/settings/biometrics2/OWNERS @@ -0,0 +1 @@ +include /src/com/android/settings/biometrics/OWNERS diff --git a/tests/robotests/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModelTest.java b/tests/robotests/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModelTest.java new file mode 100644 index 00000000000..89626def4c3 --- /dev/null +++ b/tests/robotests/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModelTest.java @@ -0,0 +1,179 @@ +/* + * 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.settings.biometrics2.ui.viewmodel; + +import static android.hardware.fingerprint.FingerprintManager.ENROLL_ENROLL; +import static android.hardware.fingerprint.FingerprintManager.ENROLL_FIND_SENSOR; +import static android.hardware.fingerprint.FingerprintManager.EnrollReason; +import static android.hardware.fingerprint.FingerprintManager.EnrollmentCallback; + +import static com.android.settings.biometrics2.ui.model.EnrollmentProgress.INITIAL_STEPS; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.only; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Application; +import android.content.res.Resources; +import android.os.CancellationSignal; + +import com.android.settings.R; +import com.android.settings.biometrics.fingerprint.FingerprintUpdater; +import com.android.settings.biometrics2.ui.model.EnrollmentProgress; +import com.android.settings.testutils.InstantTaskExecutorRule; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class FingerprintEnrollProgressViewModelTest { + + @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + @Rule public final InstantTaskExecutorRule mTaskExecutorRule = new InstantTaskExecutorRule(); + + @Mock private Application mApplication; + @Mock private Resources mResources; + @Mock private FingerprintUpdater mFingerprintUpdater; + + private FingerprintEnrollProgressViewModel mViewModel; + + @Before + public void setUp() { + when(mApplication.getResources()).thenReturn(mResources); + when(mResources.getBoolean(R.bool.enrollment_message_display_controller_flag)) + .thenReturn(false); + mViewModel = new FingerprintEnrollProgressViewModel(mApplication, mFingerprintUpdater); + } + + @Test + public void testStartEnrollment() { + @EnrollReason final int enrollReason = ENROLL_FIND_SENSOR; + final int userId = 334; + final byte[] token = new byte[] { 1, 2, 3 }; + mViewModel.setToken(token); + mViewModel.setUserId(userId); + + // Start enrollment + final boolean ret = mViewModel.startEnrollment(enrollReason); + + assertThat(ret).isTrue(); + verify(mFingerprintUpdater, only()).enroll(eq(token), any(CancellationSignal.class), + eq(userId), any(EnrollmentCallback.class), eq(enrollReason)); + } + + @Test + public void testStartEnrollmentFailBecauseOfNoToken() { + // Start enrollment + final boolean ret = mViewModel.startEnrollment(ENROLL_FIND_SENSOR); + + assertThat(ret).isFalse(); + verify(mFingerprintUpdater, never()).enroll(any(byte[].class), + any(CancellationSignal.class), anyInt(), any(EnrollmentCallback.class), anyInt()); + } + + @Test + public void testCancelEnrollment() { + @EnrollReason final int enrollReason = ENROLL_ENROLL; + final int userId = 334; + final byte[] token = new byte[] { 1, 2, 3 }; + mViewModel.setToken(token); + mViewModel.setUserId(userId); + + final TestWrapper signalWrapper = new TestWrapper<>(); + doAnswer(invocation -> { + signalWrapper.mValue = invocation.getArgument(1); + return null; + }).when(mFingerprintUpdater).enroll(any(byte[].class), any(CancellationSignal.class), + eq(userId), any(EnrollmentCallback.class), anyInt()); + + // Start enrollment + final boolean ret = mViewModel.startEnrollment(enrollReason); + assertThat(ret).isTrue(); + assertThat(signalWrapper.mValue).isNotNull(); + + // Cancel enrollment + mViewModel.cancelEnrollment(); + + assertThat(signalWrapper.mValue.isCanceled()).isTrue(); + } + + @Test + public void testProgressUpdate() { + @EnrollReason final int enrollReason = ENROLL_ENROLL; + final int userId = 334; + final byte[] token = new byte[] { 1, 2, 3 }; + mViewModel.setToken(token); + mViewModel.setUserId(userId); + + final TestWrapper callbackWrapper = new TestWrapper<>(); + doAnswer(invocation -> { + callbackWrapper.mValue = invocation.getArgument(3); + return null; + }).when(mFingerprintUpdater).enroll(any(byte[].class), any(CancellationSignal.class), + eq(userId), any(EnrollmentCallback.class), anyInt()); + + // Start enrollment + final boolean ret = mViewModel.startEnrollment(enrollReason); + assertThat(ret).isTrue(); + assertThat(callbackWrapper.mValue).isNotNull(); + + // Update first progress + callbackWrapper.mValue.onEnrollmentProgress(25); + EnrollmentProgress progress = mViewModel.getProgressLiveData().getValue(); + assertThat(progress).isNotNull(); + assertThat(progress.getSteps()).isEqualTo(25); + // TODO(b/260957933) verify getRemaining() when it is really used + //assertThat(progress.getRemaining()).isEqualTo(25); + + // Update second progress + callbackWrapper.mValue.onEnrollmentProgress(20); + progress = mViewModel.getProgressLiveData().getValue(); + assertThat(progress).isNotNull(); + assertThat(progress.getSteps()).isEqualTo(25); + // TODO(b/260957933) verify getRemaining() when it is really used + //assertThat(progress.getRemaining()).isEqualTo(20); + + // Clear progress + mViewModel.clearProgressLiveData(); + progress = mViewModel.getProgressLiveData().getValue(); + assertThat(progress).isNotNull(); + assertThat(progress.getSteps()).isEqualTo(INITIAL_STEPS); + // TODO(b/260957933) verify getRemaining() when it is really used + //assertThat(progress.getRemaining()).isEqualTo(INITIAL_REMAINING); + } + + // TODO(b/260957933): FingerprintEnrollProgressViewModel::getErrorLiveData() and + // FingerprintEnrollProgressViewModel::getHelpLiveData() doesn't built into apk because no one + // uses it. We shall test it when new FingerprintEnrollEnrolling has used these 2 methods. + + private static class TestWrapper { + T mValue; + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/InstantTaskExecutorRule.java b/tests/robotests/src/com/android/settings/testutils/InstantTaskExecutorRule.java new file mode 100644 index 00000000000..e906cb89dc6 --- /dev/null +++ b/tests/robotests/src/com/android/settings/testutils/InstantTaskExecutorRule.java @@ -0,0 +1,59 @@ +/* + * 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.settings.testutils; + +import androidx.arch.core.executor.ArchTaskExecutor; +import androidx.arch.core.executor.TaskExecutor; + +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; + +/** + * A JUnit Test Rule that swaps the background executor used by the Architecture Components with a + * different one which executes each task synchronously. + * + * We can't refer it in prebuilt androidX library. + * Copied it from androidx/arch/core/executor/testing/InstantTaskExecutorRule.java + */ +public class InstantTaskExecutorRule extends TestWatcher { + @Override + protected void starting(Description description) { + super.starting(description); + ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() { + @Override + public void executeOnDiskIO(Runnable runnable) { + runnable.run(); + } + + @Override + public void postToMainThread(Runnable runnable) { + runnable.run(); + } + + @Override + public boolean isMainThread() { + return true; + } + }); + } + + @Override + protected void finished(Description description) { + super.finished(description); + ArchTaskExecutor.getInstance().setDelegate(null); + } +} From 8c5bcaa37495e1484dd83f5f389a2c54be84dc5d Mon Sep 17 00:00:00 2001 From: Jason Chang Date: Wed, 14 Dec 2022 10:59:00 +0000 Subject: [PATCH 3/3] (2/2) Fix settings face enrollment UI layout problem To extract face_enroll_education.xml layout dimensions to dimens.xml 1. Fix settings face enrollment UI truncate problem 2. Fix It's hard to tap "setup for limited vision or head motion" button Bug: 260653042 Bug: 263065347 Test: manually go to Settings > Security & privacy > Device lock > Face & Fingerprint Unlock > Face Unlock, then start enroll Face, In second stage ("How to set up Face Unlock" page ), check the layout and lottie animation do not truncate. Test: m -j SettingsGoogleRoboTests RunSettingsGoogleRoboTests Test: m RunSettingsRoboTests ROBOTEST_FILTER= "com.android.settings.biometrics.face.FaceEnrollEducationTest" Test: m RunSettingsRoboTests ROBOTEST_FILTER= "com.android.settings.biometrics.face.FaceEnrollIntroductionTest" Change-Id: I7d7bad8b91dd505772e8f2b28cb4b872a4750cb4 --- res/drawable/face_enroll_icon_large.xml | 4 ++-- res/layout/face_enroll_education.xml | 12 ++++++++---- res/values/dimens.xml | 12 ++++++++++++ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/res/drawable/face_enroll_icon_large.xml b/res/drawable/face_enroll_icon_large.xml index 578a4a9b741..6d9b155e0ca 100644 --- a/res/drawable/face_enroll_icon_large.xml +++ b/res/drawable/face_enroll_icon_large.xml @@ -16,8 +16,8 @@ --> diff --git a/res/layout/face_enroll_education.xml b/res/layout/face_enroll_education.xml index fec6d538c7e..908dae4f7f1 100644 --- a/res/layout/face_enroll_education.xml +++ b/res/layout/face_enroll_education.xml @@ -39,7 +39,7 @@ + android:layout_marginTop="@dimen/face_enroll_education_lottie_frame_margin_top"> @@ -84,7 +85,7 @@ + android:layout_marginTop="@dimen/limited_vision_button_frame_margin_top">