From 16148d2c8cfc53e92ba966a1e06942a38779cd9d Mon Sep 17 00:00:00 2001 From: Evan Laird Date: Mon, 10 Jan 2022 19:44:18 +0000 Subject: [PATCH 01/13] Add setting for showing the vibrate icon in status bar Test: manual Bug: 220144337 Change-Id: I2d389d71dd9402353b058039464495a1d33ef0e9 Merged-In: I2d389d71dd9402353b058039464495a1d33ef0e9 --- res/values/strings.xml | 3 ++ res/xml/other_sound_settings.xml | 5 +++ res/xml/sound_settings.xml | 6 +++ .../settings/notification/SoundSettings.java | 4 ++ .../VibrateIconPreferenceController.java | 42 +++++++++++++++++++ 5 files changed, 60 insertions(+) create mode 100644 src/com/android/settings/notification/VibrateIconPreferenceController.java diff --git a/res/values/strings.xml b/res/values/strings.xml index f7be31c15f2..666ec5220c6 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -8497,6 +8497,9 @@ Touch sounds + + Always show icon when in vibrate mode + Touch vibration diff --git a/res/xml/other_sound_settings.xml b/res/xml/other_sound_settings.xml index 7b3f362eec8..35444b3692f 100644 --- a/res/xml/other_sound_settings.xml +++ b/res/xml/other_sound_settings.xml @@ -44,6 +44,11 @@ android:key="touch_sounds" android:title="@string/touch_sounds_title" /> + + + + + + Date: Wed, 16 Feb 2022 21:52:21 +0800 Subject: [PATCH 02/13] Cache mechanism for Storage page with work profile Apply a cache mechanism to prevent the flicker problem from entering Storage page. Bug: 191117970 Test: manual test 1) Create a work profile with TestDPC 2) The loading spinner will be shown when entering Storage page after the first boot. 3) Back to Settings homepage and switch back to Storage page, the loading spinner shouldn't be shown. 4) The preference order shouldn't be updated after changing the storage size. Change-Id: I518679f90c63e12f61f08d08d5ed3f3900825be2 Merged-In: I518679f90c63e12f61f08d08d5ed3f3900825be2 (cherry picked from commit cda892df85162b154e9fbc3f11df45adae08511e) --- .../ProfileSelectStorageFragment.java | 24 +++++++-- .../deviceinfo/StorageCategoryFragment.java | 52 ++++++++++++++++--- 2 files changed, 66 insertions(+), 10 deletions(-) diff --git a/src/com/android/settings/dashboard/profileselector/ProfileSelectStorageFragment.java b/src/com/android/settings/dashboard/profileselector/ProfileSelectStorageFragment.java index c4ff91b767b..57988c5d0dc 100644 --- a/src/com/android/settings/dashboard/profileselector/ProfileSelectStorageFragment.java +++ b/src/com/android/settings/dashboard/profileselector/ProfileSelectStorageFragment.java @@ -20,6 +20,7 @@ import android.app.Activity; import android.app.settings.SettingsEnums; import android.content.Context; import android.os.Bundle; +import android.os.UserHandle; import android.os.storage.DiskInfo; import android.os.storage.StorageEventListener; import android.os.storage.StorageManager; @@ -36,6 +37,7 @@ import com.android.settings.deviceinfo.StorageCategoryFragment; import com.android.settings.deviceinfo.VolumeOptionMenuController; import com.android.settings.deviceinfo.storage.AutomaticStorageManagementSwitchPreferenceController; import com.android.settings.deviceinfo.storage.DiskInitFragment; +import com.android.settings.deviceinfo.storage.StorageCacheHelper; import com.android.settings.deviceinfo.storage.StorageEntry; import com.android.settings.deviceinfo.storage.StorageSelectionPreferenceController; import com.android.settings.deviceinfo.storage.StorageUsageProgressBarPreferenceController; @@ -71,6 +73,8 @@ public class ProfileSelectStorageFragment extends ProfileSelectFragment { private StorageSelectionPreferenceController mStorageSelectionController; private StorageUsageProgressBarPreferenceController mStorageUsageProgressBarController; private VolumeOptionMenuController mOptionMenuController; + private boolean mIsLoadedFromCache; + private StorageCacheHelper mStorageCacheHelper; private final StorageEventListener mStorageEventListener = new StorageEventListener() { @Override @@ -246,11 +250,20 @@ public class ProfileSelectStorageFragment extends ProfileSelectFragment { } initializeOptionsMenu(activity); + + if (mStorageCacheHelper.hasCachedSizeInfo()) { + mIsLoadedFromCache = true; + mStorageEntries.clear(); + mStorageEntries.addAll( + StorageUtils.getAllStorageEntries(getContext(), mStorageManager)); + refreshUi(); + } } @Override public void onAttach(Context context) { super.onAttach(context); + mStorageCacheHelper = new StorageCacheHelper(getContext(), UserHandle.myUserId()); use(AutomaticStorageManagementSwitchPreferenceController.class).setFragmentManager( getFragmentManager()); mStorageSelectionController = use(StorageSelectionPreferenceController.class); @@ -281,9 +294,14 @@ public class ProfileSelectStorageFragment extends ProfileSelectFragment { public void onResume() { super.onResume(); - mStorageEntries.clear(); - mStorageEntries.addAll(StorageUtils.getAllStorageEntries(getContext(), mStorageManager)); - refreshUi(); + if (mIsLoadedFromCache) { + mIsLoadedFromCache = false; + } else { + mStorageEntries.clear(); + mStorageEntries.addAll( + StorageUtils.getAllStorageEntries(getContext(), mStorageManager)); + refreshUi(); + } mStorageManager.registerListener(mStorageEventListener); } diff --git a/src/com/android/settings/deviceinfo/StorageCategoryFragment.java b/src/com/android/settings/deviceinfo/StorageCategoryFragment.java index c8519873c9c..efdd16535e4 100644 --- a/src/com/android/settings/deviceinfo/StorageCategoryFragment.java +++ b/src/com/android/settings/deviceinfo/StorageCategoryFragment.java @@ -39,6 +39,7 @@ import com.android.settings.dashboard.DashboardFragment; import com.android.settings.dashboard.profileselector.ProfileSelectFragment; import com.android.settings.deviceinfo.storage.SecondaryUserController; import com.android.settings.deviceinfo.storage.StorageAsyncLoader; +import com.android.settings.deviceinfo.storage.StorageCacheHelper; import com.android.settings.deviceinfo.storage.StorageEntry; import com.android.settings.deviceinfo.storage.StorageItemPreferenceController; import com.android.settings.deviceinfo.storage.UserIconLoader; @@ -90,6 +91,8 @@ public class StorageCategoryFragment extends DashboardFragment private boolean mIsWorkProfile; private int mUserId; private Preference mFreeUpSpacePreference; + private boolean mIsLoadedFromCache; + private StorageCacheHelper mStorageCacheHelper; /** * Refresh UI for specified storageEntry. @@ -111,14 +114,23 @@ public class StorageCategoryFragment extends DashboardFragment mPreferenceController.setVolume(null); return; } + if (mStorageCacheHelper.hasCachedSizeInfo() && mSelectedStorageEntry.isPrivate()) { + StorageCacheHelper.StorageCache cachedData = mStorageCacheHelper.retrieveCachedSize(); + mPreferenceController.setVolume(mSelectedStorageEntry.getVolumeInfo()); + mPreferenceController.setUsedSize(cachedData.usedSize); + mPreferenceController.setTotalSize(cachedData.totalSize); + } if (mSelectedStorageEntry.isPrivate()) { mStorageInfo = null; mAppsResult = null; - maybeSetLoading(isQuotaSupported()); - - // To prevent flicker, sets null volume to hide category preferences. - // onReceivedSizes will setVolume with the volume of selected storage. - mPreferenceController.setVolume(null); + if (mStorageCacheHelper.hasCachedSizeInfo()) { + mPreferenceController.onLoadFinished(mAppsResult, mUserId); + } else { + maybeSetLoading(isQuotaSupported()); + // To prevent flicker, sets null volume to hide category preferences. + // onReceivedSizes will setVolume with the volume of selected storage. + mPreferenceController.setVolume(null); + } // Stats data is only available on private volumes. getLoaderManager().restartLoader(STORAGE_JOB_ID, Bundle.EMPTY, this); @@ -141,6 +153,15 @@ public class StorageCategoryFragment extends DashboardFragment } initializePreference(); + + if (mStorageCacheHelper.hasCachedSizeInfo()) { + mIsLoadedFromCache = true; + if (mSelectedStorageEntry != null) { + refreshUi(mSelectedStorageEntry); + } + updateSecondaryUserControllers(mSecondaryUsers, mAppsResult); + setSecondaryUsersVisible(true); + } } private void initializePreference() { @@ -156,6 +177,7 @@ public class StorageCategoryFragment extends DashboardFragment mIsWorkProfile = getArguments().getInt(ProfileSelectFragment.EXTRA_PROFILE) == ProfileSelectFragment.ProfileType.WORK; mUserId = Utils.getCurrentUserId(mUserManager, mIsWorkProfile); + mStorageCacheHelper = new StorageCacheHelper(getContext(), mUserId); super.onAttach(context); } @@ -164,11 +186,25 @@ public class StorageCategoryFragment extends DashboardFragment public void onResume() { super.onResume(); - if (mSelectedStorageEntry != null) { - refreshUi(mSelectedStorageEntry); + if (mIsLoadedFromCache) { + mIsLoadedFromCache = false; + } else { + if (mSelectedStorageEntry != null) { + refreshUi(mSelectedStorageEntry); + } } } + @Override + public void onPause() { + super.onPause(); + // Destroy the data loaders to prevent unnecessary data loading when switching back to the + // page. + getLoaderManager().destroyLoader(STORAGE_JOB_ID); + getLoaderManager().destroyLoader(ICON_JOB_ID); + getLoaderManager().destroyLoader(VOLUME_SIZE_JOB_ID); + } + @Override public void onSaveInstanceState(Bundle outState) { outState.putParcelable(SELECTED_STORAGE_ENTRY_KEY, mSelectedStorageEntry); @@ -188,6 +224,8 @@ public class StorageCategoryFragment extends DashboardFragment mPreferenceController.setVolume(mSelectedStorageEntry.getVolumeInfo()); mPreferenceController.setUsedSize(privateUsedBytes); mPreferenceController.setTotalSize(mStorageInfo.totalBytes); + // Cache total size infor and used size info + mStorageCacheHelper.cacheTotalSizeAndUsedSize(mStorageInfo.totalBytes, privateUsedBytes); for (int i = 0, size = mSecondaryUsers.size(); i < size; i++) { final AbstractPreferenceController controller = mSecondaryUsers.get(i); if (controller instanceof SecondaryUserController) { From 51b6eb3260ca7da67dd91f80e0f8b2a4f2d461ca Mon Sep 17 00:00:00 2001 From: Jan Tomljanovic Date: Wed, 23 Feb 2022 21:45:53 +0000 Subject: [PATCH 03/13] Trigger SafetyCenter update on each Fingerprint settting change. Test: atest SettingsUnitTests Bug: 215518850 Change-Id: I44f9e6dbdffa128d57a23fbe97ee875a549c3cd2 --- .../fingerprint/FingerprintEnrollSidecar.java | 7 +- .../fingerprint/FingerprintRemoveSidecar.java | 9 +- .../fingerprint/FingerprintSettings.java | 6 +- .../fingerprint/FingerprintUpdater.java | 121 ++++++++++++ .../fingerprint/FingerprintUpdaterTest.java | 185 ++++++++++++++++++ 5 files changed, 317 insertions(+), 11 deletions(-) create mode 100644 src/com/android/settings/biometrics/fingerprint/FingerprintUpdater.java create mode 100644 tests/unit/src/com/android/settings/biometrics/fingerprint/FingerprintUpdaterTest.java diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollSidecar.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollSidecar.java index 40348d47a71..d8ecd206bd3 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollSidecar.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollSidecar.java @@ -22,7 +22,6 @@ import android.hardware.fingerprint.FingerprintManager; import android.util.Log; import com.android.settings.R; -import com.android.settings.Utils; import com.android.settings.biometrics.BiometricEnrollSidecar; /** @@ -31,13 +30,13 @@ import com.android.settings.biometrics.BiometricEnrollSidecar; public class FingerprintEnrollSidecar extends BiometricEnrollSidecar { private static final String TAG = "FingerprintEnrollSidecar"; - private FingerprintManager mFingerprintManager; + private FingerprintUpdater mFingerprintUpdater; private @FingerprintManager.EnrollReason int mEnrollReason; @Override public void onAttach(Activity activity) { super.onAttach(activity); - mFingerprintManager = Utils.getFingerprintManagerOrNull(activity); + mFingerprintUpdater = new FingerprintUpdater(activity); } @Override @@ -51,7 +50,7 @@ public class FingerprintEnrollSidecar extends BiometricEnrollSidecar { return; } - mFingerprintManager.enroll(mToken, mEnrollmentCancel, mUserId, mEnrollmentCallback, + mFingerprintUpdater.enroll(mToken, mEnrollmentCancel, mUserId, mEnrollmentCallback, mEnrollReason); } diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintRemoveSidecar.java b/src/com/android/settings/biometrics/fingerprint/FingerprintRemoveSidecar.java index b356103f83f..134462d10c1 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintRemoveSidecar.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintRemoveSidecar.java @@ -21,7 +21,6 @@ import android.app.settings.SettingsEnums; import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintManager; import android.os.Bundle; -import android.os.UserHandle; import android.util.Log; import com.android.settings.core.InstrumentedFragment; @@ -38,7 +37,7 @@ public class FingerprintRemoveSidecar extends InstrumentedFragment { private Listener mListener; private Fingerprint mFingerprintRemoving; private Queue mFingerprintsRemoved; - FingerprintManager mFingerprintManager; + private FingerprintUpdater mFingerprintUpdater; private class RemovalError { Fingerprint fingerprint; @@ -80,15 +79,15 @@ public class FingerprintRemoveSidecar extends InstrumentedFragment { return; } mFingerprintRemoving = fingerprint; - mFingerprintManager.remove(fingerprint, userId, mRemoveCallback);; + mFingerprintUpdater.remove(fingerprint, userId, mRemoveCallback); } public FingerprintRemoveSidecar() { mFingerprintsRemoved = new LinkedList<>(); } - public void setFingerprintManager(FingerprintManager fingerprintManager) { - mFingerprintManager = fingerprintManager; + public void setFingerprintUpdater(FingerprintUpdater fingerprintUpdater) { + mFingerprintUpdater = fingerprintUpdater; } @Override diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java index 50e17809bd9..abc6d53a63b 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java @@ -17,9 +17,9 @@ package com.android.settings.biometrics.fingerprint; -import static android.app.admin.DevicePolicyResources.Strings.UNDEFINED; import static android.app.admin.DevicePolicyResources.Strings.Settings.FINGERPRINT_UNLOCK_DISABLED_EXPLANATION; import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.UNDEFINED; import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME; @@ -137,6 +137,7 @@ public class FingerprintSettings extends SubSettings { protected static final boolean DEBUG = false; private FingerprintManager mFingerprintManager; + private FingerprintUpdater mFingerprintUpdater; private List mSensorProperties; private boolean mInFingerprintLockout; private byte[] mToken; @@ -299,6 +300,7 @@ public class FingerprintSettings extends SubSettings { Activity activity = getActivity(); mFingerprintManager = Utils.getFingerprintManagerOrNull(activity); + mFingerprintUpdater = new FingerprintUpdater(activity, mFingerprintManager); mSensorProperties = mFingerprintManager.getSensorPropertiesInternal(); mToken = getIntent().getByteArrayExtra( @@ -322,7 +324,7 @@ public class FingerprintSettings extends SubSettings { getFragmentManager().beginTransaction() .add(mRemovalSidecar, TAG_REMOVAL_SIDECAR).commit(); } - mRemovalSidecar.setFingerprintManager(mFingerprintManager); + mRemovalSidecar.setFingerprintUpdater(mFingerprintUpdater); mRemovalSidecar.setListener(mRemovalListener); RenameDialog renameDialog = (RenameDialog) getFragmentManager(). diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintUpdater.java b/src/com/android/settings/biometrics/fingerprint/FingerprintUpdater.java new file mode 100644 index 00000000000..75d8f7b80b2 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintUpdater.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2022 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.biometrics.fingerprint; + +import android.content.Context; +import android.hardware.fingerprint.Fingerprint; +import android.hardware.fingerprint.FingerprintManager; +import android.os.CancellationSignal; + +import androidx.annotation.Nullable; + +import com.android.settings.Utils; +import com.android.settings.safetycenter.BiometricsSafetySource; + +/** + * Responsible for making {@link FingerprintManager#enroll} and {@link FingerprintManager#remove} + * calls and thus updating the fingerprint setting. + */ +public class FingerprintUpdater { + + private final Context mContext; + private final FingerprintManager mFingerprintManager; + + public FingerprintUpdater(Context context) { + mContext = context; + mFingerprintManager = Utils.getFingerprintManagerOrNull(context); + } + + public FingerprintUpdater(Context context, FingerprintManager fingerprintManager) { + mContext = context; + mFingerprintManager = fingerprintManager; + } + + /** Wrapper around the {@link FingerprintManager#enroll} method. */ + public void enroll(byte [] hardwareAuthToken, CancellationSignal cancel, int userId, + FingerprintManager.EnrollmentCallback callback, + @FingerprintManager.EnrollReason int enrollReason) { + mFingerprintManager.enroll(hardwareAuthToken, cancel, userId, + new NotifyingEnrollmentCallback(mContext, callback), enrollReason); + } + + /** Wrapper around the {@link FingerprintManager#remove} method. */ + public void remove(Fingerprint fp, int userId, FingerprintManager.RemovalCallback callback) { + mFingerprintManager.remove(fp, userId, new NotifyingRemovalCallback(mContext, callback)); + } + + /** + * Decorator of the {@link FingerprintManager.EnrollmentCallback} class that notifies other + * interested parties that a fingerprint setting has changed. + */ + private static class NotifyingEnrollmentCallback + extends FingerprintManager.EnrollmentCallback { + + private final Context mContext; + private final FingerprintManager.EnrollmentCallback mCallback; + + NotifyingEnrollmentCallback(Context context, + FingerprintManager.EnrollmentCallback callback) { + mContext = context; + mCallback = callback; + } + + @Override + public void onEnrollmentError(int errMsgId, CharSequence errString) { + mCallback.onEnrollmentError(errMsgId, errString); + } + + @Override + public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { + mCallback.onEnrollmentHelp(helpMsgId, helpString); + } + + @Override + public void onEnrollmentProgress(int remaining) { + mCallback.onEnrollmentProgress(remaining); + if (remaining == 0) { + BiometricsSafetySource.sendSafetyData(mContext); // biometrics data changed + } + } + } + + /** + * Decorator of the {@link FingerprintManager.RemovalCallback} class that notifies other + * interested parties that a fingerprint setting has changed. + */ + private static class NotifyingRemovalCallback extends FingerprintManager.RemovalCallback { + + private final Context mContext; + private final FingerprintManager.RemovalCallback mCallback; + + NotifyingRemovalCallback(Context context, FingerprintManager.RemovalCallback callback) { + mContext = context; + mCallback = callback; + } + + @Override + public void onRemovalError(Fingerprint fp, int errMsgId, CharSequence errString) { + mCallback.onRemovalError(fp, errMsgId, errString); + } + + @Override + public void onRemovalSucceeded(@Nullable Fingerprint fp, int remaining) { + mCallback.onRemovalSucceeded(fp, remaining); + BiometricsSafetySource.sendSafetyData(mContext); // biometrics data changed + } + } +} diff --git a/tests/unit/src/com/android/settings/biometrics/fingerprint/FingerprintUpdaterTest.java b/tests/unit/src/com/android/settings/biometrics/fingerprint/FingerprintUpdaterTest.java new file mode 100644 index 00000000000..62435b4f8b2 --- /dev/null +++ b/tests/unit/src/com/android/settings/biometrics/fingerprint/FingerprintUpdaterTest.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2022 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.biometrics.fingerprint; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.hardware.fingerprint.Fingerprint; +import android.hardware.fingerprint.FingerprintManager; +import android.os.CancellationSignal; + +import androidx.annotation.Nullable; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.settings.safetycenter.SafetyCenterManagerWrapper; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class FingerprintUpdaterTest { + + private static final byte[] HARDWARE_AUTH_TOKEN = new byte[] {0}; + private static final CancellationSignal CANCELLATION_SIGNAL = new CancellationSignal(); + private static final int USER_ID = 0; + private static final int ENROLL_REASON = 0; + private static final int ERR_MSG_ID = 0; + private static final int HELP_MSG_ID = 0; + private static final String HELP_STRING = ""; + private static final String ERR_STRING = ""; + private static final Fingerprint FINGERPRINT = + new Fingerprint(/* name= */"", /* fingerId */ 0, /* deviceId= */ 0L); + + @Mock private FingerprintManager mFingerprintManager; + @Mock private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper; + + private FingerprintUpdater mFingerprintUpdater; + private Context mContext; + private FingerprintManager.EnrollmentCallback mEnrollmentCallback; + private FingerprintManager.RemovalCallback mRemovalCallback; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = ApplicationProvider.getApplicationContext(); + mFingerprintUpdater = new FingerprintUpdater(mContext, mFingerprintManager); + mEnrollmentCallback = spy(new TestEntrollmentCallback()); + mRemovalCallback = spy(new TestRemovalCallback()); + SafetyCenterManagerWrapper.sInstance = mSafetyCenterManagerWrapper; + } + + @Test + public void enroll_onEnrollmentCallbacks_triggerGivenCallback() { + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(FingerprintManager.EnrollmentCallback.class); + mFingerprintUpdater.enroll(HARDWARE_AUTH_TOKEN, CANCELLATION_SIGNAL, USER_ID, + mEnrollmentCallback, ENROLL_REASON); + verify(mFingerprintManager).enroll( + same(HARDWARE_AUTH_TOKEN), + same(CANCELLATION_SIGNAL), + eq(USER_ID), + callbackCaptor.capture(), + eq(ENROLL_REASON)); + FingerprintManager.EnrollmentCallback callback = callbackCaptor.getValue(); + + callback.onEnrollmentError(ERR_MSG_ID, ERR_STRING); + callback.onEnrollmentProgress(/* remaining= */ 2); + callback.onEnrollmentHelp(HELP_MSG_ID, HELP_STRING); + + verify(mEnrollmentCallback).onEnrollmentError(ERR_MSG_ID, ERR_STRING); + verify(mEnrollmentCallback).onEnrollmentProgress(/* remaining= */ 2); + verify(mEnrollmentCallback).onEnrollmentHelp(HELP_MSG_ID, HELP_STRING); + } + + @Test + public void enroll_onEnrollmentSuccess_invokedInteractionWithSafetyCenter() { + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(FingerprintManager.EnrollmentCallback.class); + mFingerprintUpdater.enroll(HARDWARE_AUTH_TOKEN, CANCELLATION_SIGNAL, USER_ID, + mEnrollmentCallback, ENROLL_REASON); + verify(mFingerprintManager).enroll( + same(HARDWARE_AUTH_TOKEN), + same(CANCELLATION_SIGNAL), + eq(USER_ID), + callbackCaptor.capture(), + eq(ENROLL_REASON)); + FingerprintManager.EnrollmentCallback callback = callbackCaptor.getValue(); + + callback.onEnrollmentProgress(/* remaining= */ 0); + + verify(mSafetyCenterManagerWrapper).isEnabled(mContext); + } + + @Test + public void enroll_onEnrollmentNotYetFinished_didntInvokeInteractionWithSafetyCenter() { + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(FingerprintManager.EnrollmentCallback.class); + mFingerprintUpdater.enroll(HARDWARE_AUTH_TOKEN, CANCELLATION_SIGNAL, USER_ID, + mEnrollmentCallback, ENROLL_REASON); + verify(mFingerprintManager).enroll( + same(HARDWARE_AUTH_TOKEN), + same(CANCELLATION_SIGNAL), + eq(USER_ID), + callbackCaptor.capture(), + eq(ENROLL_REASON)); + FingerprintManager.EnrollmentCallback callback = callbackCaptor.getValue(); + + callback.onEnrollmentProgress(/* remaining= */ 1); + + verify(mSafetyCenterManagerWrapper, never()).isEnabled(any()); + } + + @Test + public void remove_onRemovalCallbacks_triggerGivenCallback() { + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(FingerprintManager.RemovalCallback.class); + mFingerprintUpdater.remove(FINGERPRINT, USER_ID, mRemovalCallback); + verify(mFingerprintManager) + .remove(same(FINGERPRINT), eq(USER_ID), callbackCaptor.capture()); + FingerprintManager.RemovalCallback callback = callbackCaptor.getValue(); + + callback.onRemovalSucceeded(FINGERPRINT, /* remaining= */ 1); + callback.onRemovalError(FINGERPRINT, ERR_MSG_ID, ERR_STRING); + + verify(mRemovalCallback).onRemovalSucceeded(any(), eq(1)); + verify(mRemovalCallback).onRemovalError(FINGERPRINT, ERR_MSG_ID, ERR_STRING); + } + + @Test + public void remove_onRemovalSuccess_invokedInteractionWithSafetyCenter() { + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(FingerprintManager.RemovalCallback.class); + mFingerprintUpdater.remove(FINGERPRINT, USER_ID, mRemovalCallback); + verify(mFingerprintManager) + .remove(same(FINGERPRINT), eq(USER_ID), callbackCaptor.capture()); + FingerprintManager.RemovalCallback callback = callbackCaptor.getValue(); + + callback.onRemovalSucceeded(FINGERPRINT, /* remaining= */ 0); + + verify(mSafetyCenterManagerWrapper).isEnabled(mContext); + } + + public static class TestEntrollmentCallback extends FingerprintManager.EnrollmentCallback { + @Override + public void onEnrollmentError(int errMsgId, CharSequence errString) {} + + @Override + public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {} + + @Override + public void onEnrollmentProgress(int remaining) {} + } + + public static class TestRemovalCallback extends FingerprintManager.RemovalCallback { + @Override + public void onRemovalError(Fingerprint fp, int errMsgId, CharSequence errString) {} + + @Override + public void onRemovalSucceeded(@Nullable Fingerprint fp, int remaining) {} + } +} From aad5f295b0f29209ae13a9bb77e092aad5ea51f4 Mon Sep 17 00:00:00 2001 From: Jan Tomljanovic Date: Thu, 24 Feb 2022 12:42:21 +0000 Subject: [PATCH 04/13] Trigger SafetyCenter update on each Face setting change. Test: atest SettingsUnitTests Bug: 215518850 Change-Id: I7a55fd5368c9aad5329448732125d4e43688eced --- .../biometrics/face/FaceEnrollSidecar.java | 8 +- ...tingsRemoveButtonPreferenceController.java | 4 +- .../settings/biometrics/face/FaceUpdater.java | 137 +++++++++ .../contextualcards/FaceReEnrollDialog.java | 5 +- .../biometrics/face/FaceUpdaterTest.java | 277 ++++++++++++++++++ 5 files changed, 424 insertions(+), 7 deletions(-) create mode 100644 src/com/android/settings/biometrics/face/FaceUpdater.java create mode 100644 tests/unit/src/com/android/settings/biometrics/face/FaceUpdaterTest.java diff --git a/src/com/android/settings/biometrics/face/FaceEnrollSidecar.java b/src/com/android/settings/biometrics/face/FaceEnrollSidecar.java index 6d2c301a71f..de713dd3386 100644 --- a/src/com/android/settings/biometrics/face/FaceEnrollSidecar.java +++ b/src/com/android/settings/biometrics/face/FaceEnrollSidecar.java @@ -19,9 +19,7 @@ package com.android.settings.biometrics.face; import android.app.Activity; import android.app.settings.SettingsEnums; import android.hardware.face.FaceManager; -import android.os.UserHandle; -import com.android.settings.Utils; import com.android.settings.biometrics.BiometricEnrollSidecar; import java.util.Arrays; @@ -33,7 +31,7 @@ public class FaceEnrollSidecar extends BiometricEnrollSidecar { private final int[] mDisabledFeatures; - private FaceManager mFaceManager; + private FaceUpdater mFaceUpdater; public FaceEnrollSidecar(int[] disabledFeatures) { mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length); @@ -42,13 +40,13 @@ public class FaceEnrollSidecar extends BiometricEnrollSidecar { @Override public void onAttach(Activity activity) { super.onAttach(activity); - mFaceManager = Utils.getFaceManagerOrNull(activity); + mFaceUpdater = new FaceUpdater(activity); } @Override public void startEnrollment() { super.startEnrollment(); - mFaceManager.enroll(mUserId, mToken, mEnrollmentCancel, + mFaceUpdater.enroll(mUserId, mToken, mEnrollmentCancel, mEnrollmentCallback, mDisabledFeatures); } diff --git a/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java index 616b736b8d6..7db59584899 100644 --- a/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java +++ b/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java @@ -103,6 +103,7 @@ public class FaceSettingsRemoveButtonPreferenceController extends BasePreference private final MetricsFeatureProvider mMetricsFeatureProvider; private final Context mContext; private final FaceManager mFaceManager; + private final FaceUpdater mFaceUpdater; private final FaceManager.RemovalCallback mRemovalCallback = new FaceManager.RemovalCallback() { @Override public void onRemovalError(Face face, int errMsgId, CharSequence errString) { @@ -144,7 +145,7 @@ public class FaceSettingsRemoveButtonPreferenceController extends BasePreference } // Remove the first/only face - mFaceManager.remove(faces.get(0), mUserId, mRemovalCallback); + mFaceUpdater.remove(faces.get(0), mUserId, mRemovalCallback); } else { mButton.setEnabled(true); mRemoving = false; @@ -157,6 +158,7 @@ public class FaceSettingsRemoveButtonPreferenceController extends BasePreference mContext = context; mFaceManager = context.getSystemService(FaceManager.class); mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider(); + mFaceUpdater = new FaceUpdater(context, mFaceManager); } public FaceSettingsRemoveButtonPreferenceController(Context context) { diff --git a/src/com/android/settings/biometrics/face/FaceUpdater.java b/src/com/android/settings/biometrics/face/FaceUpdater.java new file mode 100644 index 00000000000..bb77caefb27 --- /dev/null +++ b/src/com/android/settings/biometrics/face/FaceUpdater.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2022 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.biometrics.face; + +import android.content.Context; +import android.hardware.face.Face; +import android.hardware.face.FaceEnrollCell; +import android.hardware.face.FaceManager; +import android.os.CancellationSignal; +import android.view.Surface; + +import androidx.annotation.Nullable; + +import com.android.settings.Utils; +import com.android.settings.safetycenter.BiometricsSafetySource; + +/** + * Responsible for making {@link FaceManager#enroll} and {@link FaceManager#remove} calls and thus + * updating the face setting. + */ +public class FaceUpdater { + + private final Context mContext; + private final FaceManager mFaceManager; + + public FaceUpdater(Context context) { + mContext = context; + mFaceManager = Utils.getFaceManagerOrNull(context); + } + + public FaceUpdater(Context context, FaceManager faceManager) { + mContext = context; + mFaceManager = faceManager; + } + + /** Wrapper around the {@link FaceManager#enroll} method. */ + public void enroll(int userId, byte[] hardwareAuthToken, CancellationSignal cancel, + FaceManager.EnrollmentCallback callback, int[] disabledFeatures) { + mFaceManager.enroll(userId, hardwareAuthToken, cancel, + new NotifyingEnrollmentCallback(mContext, callback), disabledFeatures); + } + + /** Wrapper around the {@link FaceManager#enroll} method. */ + public void enroll(int userId, byte[] hardwareAuthToken, CancellationSignal cancel, + FaceManager.EnrollmentCallback callback, int[] disabledFeatures, + @Nullable Surface previewSurface, boolean debugConsent) { + mFaceManager.enroll(userId, hardwareAuthToken, cancel, + new NotifyingEnrollmentCallback(mContext, callback), disabledFeatures, + previewSurface, debugConsent); + } + + /** Wrapper around the {@link FaceManager#remove} method. */ + public void remove(Face face, int userId, FaceManager.RemovalCallback callback) { + mFaceManager.remove(face, userId, new NotifyingRemovalCallback(mContext, callback)); + } + + /** + * Decorator of the {@link FaceManager.EnrollmentCallback} class that notifies other + * interested parties that a face setting has changed. + */ + private static class NotifyingEnrollmentCallback + extends FaceManager.EnrollmentCallback { + + private final Context mContext; + private final FaceManager.EnrollmentCallback mCallback; + + NotifyingEnrollmentCallback(Context context, + FaceManager.EnrollmentCallback callback) { + mContext = context; + mCallback = callback; + } + + @Override + public void onEnrollmentError(int errMsgId, CharSequence errString) { + mCallback.onEnrollmentError(errMsgId, errString); + } + + @Override + public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { + mCallback.onEnrollmentHelp(helpMsgId, helpString); + } + + @Override + public void onEnrollmentFrame(int helpCode, @Nullable CharSequence helpMessage, + @Nullable FaceEnrollCell cell, int stage, float pan, float tilt, float distance) { + mCallback.onEnrollmentFrame(helpCode, helpMessage, cell, stage, pan, tilt, distance); + } + + @Override + public void onEnrollmentProgress(int remaining) { + mCallback.onEnrollmentProgress(remaining); + if (remaining == 0) { + BiometricsSafetySource.sendSafetyData(mContext); // biometrics data changed + } + } + } + + /** + * Decorator of the {@link FaceManager.RemovalCallback} class that notifies other + * interested parties that a face setting has changed. + */ + private static class NotifyingRemovalCallback extends FaceManager.RemovalCallback { + + private final Context mContext; + private final FaceManager.RemovalCallback mCallback; + + NotifyingRemovalCallback(Context context, FaceManager.RemovalCallback callback) { + mContext = context; + mCallback = callback; + } + + @Override + public void onRemovalError(Face fp, int errMsgId, CharSequence errString) { + mCallback.onRemovalError(fp, errMsgId, errString); + } + + @Override + public void onRemovalSucceeded(@Nullable Face fp, int remaining) { + mCallback.onRemovalSucceeded(fp, remaining); + BiometricsSafetySource.sendSafetyData(mContext); // biometrics data changed + } + } +} diff --git a/src/com/android/settings/homepage/contextualcards/FaceReEnrollDialog.java b/src/com/android/settings/homepage/contextualcards/FaceReEnrollDialog.java index e778e8c7ca4..a8239241bf5 100644 --- a/src/com/android/settings/homepage/contextualcards/FaceReEnrollDialog.java +++ b/src/com/android/settings/homepage/contextualcards/FaceReEnrollDialog.java @@ -30,6 +30,7 @@ import com.android.internal.app.AlertActivity; import com.android.internal.app.AlertController; import com.android.settings.R; import com.android.settings.Utils; +import com.android.settings.biometrics.face.FaceUpdater; import com.android.settings.homepage.contextualcards.slices.FaceSetupSlice; /** @@ -43,6 +44,7 @@ public class FaceReEnrollDialog extends AlertActivity implements private static final String BIOMETRIC_ENROLL_ACTION = "android.settings.BIOMETRIC_ENROLL"; private FaceManager mFaceManager; + private FaceUpdater mFaceUpdater; /** * The type of re-enrollment that has been requested, * see {@link Settings.Secure#FACE_UNLOCK_RE_ENROLL} for more details. @@ -67,6 +69,7 @@ public class FaceReEnrollDialog extends AlertActivity implements alertParams.mPositiveButtonListener = this; mFaceManager = Utils.getFaceManagerOrNull(getApplicationContext()); + mFaceUpdater = new FaceUpdater(getApplicationContext(), mFaceManager); final Context context = getApplicationContext(); mReEnrollType = FaceSetupSlice.getReEnrollSetting(context, getUserId()); @@ -96,7 +99,7 @@ public class FaceReEnrollDialog extends AlertActivity implements if (mFaceManager == null || !mFaceManager.hasEnrolledTemplates(userId)) { finish(); } - mFaceManager.remove(new Face("", 0, 0), userId, new FaceManager.RemovalCallback() { + mFaceUpdater.remove(new Face("", 0, 0), userId, new FaceManager.RemovalCallback() { @Override public void onRemovalError(Face face, int errMsgId, CharSequence errString) { super.onRemovalError(face, errMsgId, errString); diff --git a/tests/unit/src/com/android/settings/biometrics/face/FaceUpdaterTest.java b/tests/unit/src/com/android/settings/biometrics/face/FaceUpdaterTest.java new file mode 100644 index 00000000000..a49b4a6c170 --- /dev/null +++ b/tests/unit/src/com/android/settings/biometrics/face/FaceUpdaterTest.java @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2022 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.biometrics.face; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.hardware.face.Face; +import android.hardware.face.FaceEnrollCell; +import android.hardware.face.FaceEnrollStages; +import android.hardware.face.FaceManager; +import android.os.CancellationSignal; +import android.view.Surface; + +import androidx.annotation.Nullable; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.settings.safetycenter.SafetyCenterManagerWrapper; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class FaceUpdaterTest { + + private static final byte[] HARDWARE_AUTH_TOKEN = new byte[] {0}; + private static final CancellationSignal CANCELLATION_SIGNAL = new CancellationSignal(); + private static final int USER_ID = 0; + private static final int ERR_MSG_ID = 0; + private static final int HELP_MSG_ID = 0; + private static final String HELP_STRING = ""; + private static final String ERR_STRING = ""; + private static final Face FACE = + new Face(/* name= */"", /* faceId */ 0, /* deviceId= */ 0L); + private static final int[] DISABLED_FEATURES = new int[] {0}; + private static final boolean DEBUG_CONSENT = false; + private static final Surface PREVIEW_SURFACE = new Surface(); + private static final int HELP_CODE = 0; + private static final CharSequence HELP_MESSAGE = ""; + private static final FaceEnrollCell CELL = + new FaceEnrollCell(/* x= */ 0, /* y= */ 0, /* z= */ 0); + private static final int STAGE = FaceEnrollStages.UNKNOWN; + private static final float PAN = 0; + private static final float TILT = 0; + private static final float DISTANCE = 0; + + + @Mock private FaceManager mFaceManager; + @Mock private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper; + + private FaceUpdater mFaceUpdater; + private Context mContext; + private FaceManager.EnrollmentCallback mEnrollmentCallback; + private FaceManager.RemovalCallback mRemovalCallback; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = ApplicationProvider.getApplicationContext(); + mFaceUpdater = new FaceUpdater(mContext, mFaceManager); + mEnrollmentCallback = spy(new TestEnrollmentCallback()); + mRemovalCallback = spy(new TestRemovalCallback()); + SafetyCenterManagerWrapper.sInstance = mSafetyCenterManagerWrapper; + } + + @Test + public void enroll_firstVersion_onEnrollmentCallbacks_triggerGivenCallback() { + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(FaceManager.EnrollmentCallback.class); + mFaceUpdater.enroll(USER_ID, HARDWARE_AUTH_TOKEN, CANCELLATION_SIGNAL, mEnrollmentCallback, + DISABLED_FEATURES); + verify(mFaceManager).enroll( + eq(USER_ID), + same(HARDWARE_AUTH_TOKEN), + same(CANCELLATION_SIGNAL), + callbackCaptor.capture(), + same(DISABLED_FEATURES)); + FaceManager.EnrollmentCallback callback = callbackCaptor.getValue(); + + callback.onEnrollmentError(ERR_MSG_ID, ERR_STRING); + callback.onEnrollmentProgress(/* remaining= */ 2); + callback.onEnrollmentHelp(HELP_MSG_ID, HELP_STRING); + callback.onEnrollmentFrame(HELP_CODE, HELP_MESSAGE, CELL, STAGE, PAN, TILT, DISTANCE); + + verify(mEnrollmentCallback, atLeast(1)).onEnrollmentError(ERR_MSG_ID, ERR_STRING); + verify(mEnrollmentCallback, atLeast(1)).onEnrollmentProgress(/* remaining= */ 2); + verify(mEnrollmentCallback, atLeast(1)).onEnrollmentHelp(HELP_MSG_ID, HELP_STRING); + verify(mEnrollmentCallback, atLeast(1)) + .onEnrollmentFrame(HELP_CODE, HELP_MESSAGE, CELL, STAGE, PAN, TILT, DISTANCE); + } + + @Test + public void enroll_firstVersion_onEnrollmentSuccess_invokedInteractionWithSafetyCenter() { + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(FaceManager.EnrollmentCallback.class); + mFaceUpdater.enroll(USER_ID, HARDWARE_AUTH_TOKEN, CANCELLATION_SIGNAL, mEnrollmentCallback, + DISABLED_FEATURES); + verify(mFaceManager).enroll( + eq(USER_ID), + same(HARDWARE_AUTH_TOKEN), + same(CANCELLATION_SIGNAL), + callbackCaptor.capture(), + same(DISABLED_FEATURES)); + FaceManager.EnrollmentCallback callback = callbackCaptor.getValue(); + + callback.onEnrollmentProgress(/* remaining= */ 0); + + verify(mSafetyCenterManagerWrapper).isEnabled(mContext); + } + + @Test + public void enroll_firstVersion_onEnrollmentNotYetFinished_didntInvokeInteractionWithSafetyCenter() { + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(FaceManager.EnrollmentCallback.class); + mFaceUpdater.enroll(USER_ID, HARDWARE_AUTH_TOKEN, CANCELLATION_SIGNAL, mEnrollmentCallback, + DISABLED_FEATURES); + verify(mFaceManager).enroll( + eq(USER_ID), + same(HARDWARE_AUTH_TOKEN), + same(CANCELLATION_SIGNAL), + callbackCaptor.capture(), + same(DISABLED_FEATURES)); + FaceManager.EnrollmentCallback callback = callbackCaptor.getValue(); + + callback.onEnrollmentProgress(/* remaining= */ 1); + + verify(mSafetyCenterManagerWrapper, never()).isEnabled(any()); + } + + @Test + public void enroll_secondVersion_onEnrollmentCallbacks_triggerGivenCallback() { + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(FaceManager.EnrollmentCallback.class); + mFaceUpdater.enroll(USER_ID, HARDWARE_AUTH_TOKEN, CANCELLATION_SIGNAL, mEnrollmentCallback, + DISABLED_FEATURES, PREVIEW_SURFACE, DEBUG_CONSENT); + verify(mFaceManager).enroll( + eq(USER_ID), + same(HARDWARE_AUTH_TOKEN), + same(CANCELLATION_SIGNAL), + callbackCaptor.capture(), + same(DISABLED_FEATURES), + same(PREVIEW_SURFACE), + eq(DEBUG_CONSENT)); + FaceManager.EnrollmentCallback callback = callbackCaptor.getValue(); + + callback.onEnrollmentError(ERR_MSG_ID, ERR_STRING); + callback.onEnrollmentProgress(/* remaining= */ 2); + callback.onEnrollmentHelp(HELP_MSG_ID, HELP_STRING); + callback.onEnrollmentFrame(HELP_CODE, HELP_MESSAGE, CELL, STAGE, PAN, TILT, DISTANCE); + + verify(mEnrollmentCallback, atLeast(1)).onEnrollmentError(ERR_MSG_ID, ERR_STRING); + verify(mEnrollmentCallback, atLeast(1)).onEnrollmentProgress(/* remaining= */ 2); + verify(mEnrollmentCallback, atLeast(1)).onEnrollmentHelp(HELP_MSG_ID, HELP_STRING); + verify(mEnrollmentCallback, atLeast(1)) + .onEnrollmentFrame(HELP_CODE, HELP_MESSAGE, CELL, STAGE, PAN, TILT, DISTANCE); + } + + @Test + public void enroll_secondVersion_onEnrollmentSuccess_invokedInteractionWithSafetyCenter() { + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(FaceManager.EnrollmentCallback.class); + mFaceUpdater.enroll(USER_ID, HARDWARE_AUTH_TOKEN, CANCELLATION_SIGNAL, mEnrollmentCallback, + DISABLED_FEATURES, PREVIEW_SURFACE, DEBUG_CONSENT); + verify(mFaceManager).enroll( + eq(USER_ID), + same(HARDWARE_AUTH_TOKEN), + same(CANCELLATION_SIGNAL), + callbackCaptor.capture(), + same(DISABLED_FEATURES), + same(PREVIEW_SURFACE), + eq(DEBUG_CONSENT)); + FaceManager.EnrollmentCallback callback = callbackCaptor.getValue(); + + callback.onEnrollmentProgress(/* remaining= */ 0); + + verify(mSafetyCenterManagerWrapper).isEnabled(mContext); + } + + @Test + public void enroll_secondVersion_onEnrollmentNotYetFinished_didntInvokeInteractionWithSafetyCenter() { + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(FaceManager.EnrollmentCallback.class); + mFaceUpdater.enroll(USER_ID, HARDWARE_AUTH_TOKEN, CANCELLATION_SIGNAL, mEnrollmentCallback, + DISABLED_FEATURES, PREVIEW_SURFACE, DEBUG_CONSENT); + verify(mFaceManager).enroll( + eq(USER_ID), + same(HARDWARE_AUTH_TOKEN), + same(CANCELLATION_SIGNAL), + callbackCaptor.capture(), + same(DISABLED_FEATURES), + same(PREVIEW_SURFACE), + eq(DEBUG_CONSENT)); + FaceManager.EnrollmentCallback callback = callbackCaptor.getValue(); + + callback.onEnrollmentProgress(/* remaining= */ 1); + + verify(mSafetyCenterManagerWrapper, never()).isEnabled(any()); + } + + @Test + public void remove_onRemovalCallbacks_triggerGivenCallback() { + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(FaceManager.RemovalCallback.class); + mFaceUpdater.remove(FACE, USER_ID, mRemovalCallback); + verify(mFaceManager) + .remove(same(FACE), eq(USER_ID), callbackCaptor.capture()); + FaceManager.RemovalCallback callback = callbackCaptor.getValue(); + + callback.onRemovalSucceeded(FACE, /* remaining= */ 1); + callback.onRemovalError(FACE, ERR_MSG_ID, ERR_STRING); + + verify(mRemovalCallback).onRemovalSucceeded(any(), eq(1)); + verify(mRemovalCallback).onRemovalError(FACE, ERR_MSG_ID, ERR_STRING); + } + + @Test + public void remove_onRemovalSuccess_invokedInteractionWithSafetyCenter() { + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(FaceManager.RemovalCallback.class); + mFaceUpdater.remove(FACE, USER_ID, mRemovalCallback); + verify(mFaceManager) + .remove(same(FACE), eq(USER_ID), callbackCaptor.capture()); + FaceManager.RemovalCallback callback = callbackCaptor.getValue(); + + callback.onRemovalSucceeded(FACE, /* remaining= */ 0); + + verify(mSafetyCenterManagerWrapper).isEnabled(mContext); + } + + public static class TestEnrollmentCallback extends FaceManager.EnrollmentCallback { + @Override + public void onEnrollmentError(int errMsgId, CharSequence errString) {} + + @Override + public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {} + + @Override + public void onEnrollmentProgress(int remaining) {} + + @Override + public void onEnrollmentFrame(int helpCode, @Nullable CharSequence helpMessage, + @Nullable FaceEnrollCell cell, int stage, float pan, float tilt, float distance) {} + } + + public static class TestRemovalCallback extends FaceManager.RemovalCallback { + @Override + public void onRemovalError(Face fp, int errMsgId, CharSequence errString) {} + + @Override + public void onRemovalSucceeded(@Nullable Face fp, int remaining) {} + } +} From eb89d146f286eaddcde603cd448ed21587cc1e0d Mon Sep 17 00:00:00 2001 From: Adam Bookatz Date: Thu, 24 Feb 2022 16:45:28 -0800 Subject: [PATCH 05/13] Fallback Home doesn't restart on keyboard change Fallback Home gets launched multiple times due to CONFIG_KEYBOARD_HIDDEN, which is irrelevant for it. So we mark the activity to not be restarted on this config change. Test: manual Bug: 215439232 Change-Id: I558091979840a6b664e39f6b8bd22957d1b2b729 --- AndroidManifest.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 0279eec6987..5877b132f92 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2885,7 +2885,8 @@ android:screenOrientation="nosensor" android:taskAffinity="com.android.settings.FallbackHome" android:exported="true" - android:theme="@style/FallbackHome"> + android:theme="@style/FallbackHome" + android:configChanges="keyboardHidden"> From 2b5bf34eda231a2a285269d4b491804ea36192d3 Mon Sep 17 00:00:00 2001 From: Yuri Ufimtsev Date: Wed, 23 Feb 2022 14:01:37 +0000 Subject: [PATCH 06/13] Provide data for BiometricsSafetySource Test: atest SettingsUnitTests Bug: 215517420 Bug: 221449065 Change-Id: Id635a02443295757aab277971c4d95a9a62f5576 --- .../biometrics/BiometricNavigationUtils.java | 62 ++- .../safetycenter/BiometricsSafetySource.java | 90 +++- .../BiometricNavigationUtilsTest.java | 101 ++++- .../BiometricsSafetySourceTest.java | 416 +++++++++++++++++- .../SafetySourceBroadcastReceiverTest.java | 20 +- 5 files changed, 663 insertions(+), 26 deletions(-) diff --git a/src/com/android/settings/biometrics/BiometricNavigationUtils.java b/src/com/android/settings/biometrics/BiometricNavigationUtils.java index e4f2b7f3511..b747fafa1da 100644 --- a/src/com/android/settings/biometrics/BiometricNavigationUtils.java +++ b/src/com/android/settings/biometrics/BiometricNavigationUtils.java @@ -19,14 +19,17 @@ package com.android.settings.biometrics; import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME; import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY; +import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; -import com.android.settings.Utils; +import com.android.internal.app.UnlaunchableAppActivity; import com.android.settings.core.SettingsBaseActivity; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.transition.SettingsTransitionHelper; /** @@ -41,15 +44,62 @@ public class BiometricNavigationUtils { * for managed profile, otherwise shows a dialog to disable the Quiet Mode. * * @param className The class name of Settings screen to launch. - * @param extras Extras to put into the launching {@link Intent}. + * @param extras Extras to put into the launching {@link Intent}. * @return true if the Settings screen is launching. */ public boolean launchBiometricSettings(Context context, String className, Bundle extras) { - final UserManager userManager = UserManager.get(context); - if (Utils.startQuietModeDialogIfNecessary(context, userManager, mUserId)) { + final Intent quietModeDialogIntent = getQuietModeDialogIntent(context); + if (quietModeDialogIntent != null) { + context.startActivity(quietModeDialogIntent); return false; } + context.startActivity(getSettingsPageIntent(className, extras)); + return true; + } + /** + * Returns {@link Intent} to launch an appropriate Settings screen. + * + *

If the Setting is disabled by admin, returns {@link Intent} to launch an explanation. + * If Quiet Mode is enabled for managed profile, returns {@link Intent} to launch a dialog + * to disable the Quiet Mode. Otherwise, returns {@link Intent} to launch the Settings screen. + * + * @param className The class name of Settings screen to launch. + * @param enforcedAdmin Details of admin account that disables changing the setting. + * @param extras Extras to put into the result {@link Intent}. + */ + public Intent getBiometricSettingsIntent(Context context, String className, + EnforcedAdmin enforcedAdmin, Bundle extras) { + if (enforcedAdmin != null) { + return getRestrictedDialogIntent(context, enforcedAdmin); + } + final Intent quietModeDialogIntent = getQuietModeDialogIntent(context); + return quietModeDialogIntent != null ? quietModeDialogIntent + : getSettingsPageIntent(className, extras); + } + + private Intent getQuietModeDialogIntent(Context context) { + final UserManager userManager = UserManager.get(context); + if (userManager.isQuietModeEnabled(UserHandle.of(mUserId))) { + return UnlaunchableAppActivity.createInQuietModeDialogIntent(mUserId); + } + return null; + } + + private Intent getRestrictedDialogIntent(Context context, EnforcedAdmin enforcedAdmin) { + final Intent intent = RestrictedLockUtils + .getShowAdminSupportDetailsIntent(context, enforcedAdmin); + int targetUserId = mUserId; + if (enforcedAdmin.user != null && RestrictedLockUtils + .isCurrentUserOrProfile(context, enforcedAdmin.user.getIdentifier())) { + targetUserId = enforcedAdmin.user.getIdentifier(); + } + intent.putExtra(DevicePolicyManager.EXTRA_RESTRICTION, enforcedAdmin.enforcedRestriction); + intent.putExtra(Intent.EXTRA_USER_ID, targetUserId); + return intent; + } + + private Intent getSettingsPageIntent(String className, Bundle extras) { final Intent intent = new Intent(); intent.setClassName(SETTINGS_PACKAGE_NAME, className); if (!extras.isEmpty()) { @@ -59,7 +109,7 @@ public class BiometricNavigationUtils { intent.putExtra(Intent.EXTRA_USER_ID, mUserId); intent.putExtra(SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE, SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE); - context.startActivity(intent); - return true; + + return intent; } } diff --git a/src/com/android/settings/safetycenter/BiometricsSafetySource.java b/src/com/android/settings/safetycenter/BiometricsSafetySource.java index bfe2fb07d52..6a93bda03c4 100644 --- a/src/com/android/settings/safetycenter/BiometricsSafetySource.java +++ b/src/com/android/settings/safetycenter/BiometricsSafetySource.java @@ -16,14 +16,30 @@ package com.android.settings.safetycenter; +import android.app.PendingIntent; import android.content.Context; +import android.content.Intent; +import android.hardware.face.FaceManager; +import android.hardware.fingerprint.FingerprintManager; +import android.os.Bundle; +import android.safetycenter.SafetySourceData; +import android.safetycenter.SafetySourceStatus; + +import com.android.settings.R; +import com.android.settings.Utils; +import com.android.settings.biometrics.BiometricNavigationUtils; +import com.android.settings.biometrics.combination.CombinedBiometricStatusUtils; +import com.android.settings.biometrics.face.FaceStatusUtils; +import com.android.settings.biometrics.fingerprint.FingerprintStatusUtils; +import com.android.settingslib.RestrictedLockUtils; /** Combined Biometrics Safety Source for Safety Center. */ public final class BiometricsSafetySource { public static final String SAFETY_SOURCE_ID = "Biometrics"; - private BiometricsSafetySource() {} + private BiometricsSafetySource() { + } /** Sends biometric safety data to Safety Center. */ public static void sendSafetyData(Context context) { @@ -31,7 +47,75 @@ public final class BiometricsSafetySource { return; } - // TODO(b/215517420): Send biometric data to Safety Center if there are biometrics available - // on this device. + final BiometricNavigationUtils biometricNavigationUtils = new BiometricNavigationUtils(); + final CombinedBiometricStatusUtils combinedBiometricStatusUtils = + new CombinedBiometricStatusUtils(context); + + if (combinedBiometricStatusUtils.isAvailable()) { + final RestrictedLockUtils.EnforcedAdmin disablingAdmin = + combinedBiometricStatusUtils.getDisablingAdmin(); + sendBiometricSafetySourceData(context, + context.getString(R.string.security_settings_biometric_preference_title), + combinedBiometricStatusUtils.getSummary(), + biometricNavigationUtils.getBiometricSettingsIntent(context, + combinedBiometricStatusUtils.getSettingsClassName(), disablingAdmin, + Bundle.EMPTY), + disablingAdmin == null /* enabled */); + return; + } + + final FaceManager faceManager = Utils.getFaceManagerOrNull(context); + final FaceStatusUtils faceStatusUtils = new FaceStatusUtils(context, faceManager); + + if (faceStatusUtils.isAvailable()) { + final RestrictedLockUtils.EnforcedAdmin disablingAdmin = + faceStatusUtils.getDisablingAdmin(); + sendBiometricSafetySourceData(context, + context.getString(R.string.security_settings_face_preference_title), + faceStatusUtils.getSummary(), + biometricNavigationUtils.getBiometricSettingsIntent(context, + faceStatusUtils.getSettingsClassName(), disablingAdmin, + Bundle.EMPTY), + disablingAdmin == null /* enabled */); + return; + } + + final FingerprintManager fingerprintManager = Utils.getFingerprintManagerOrNull(context); + final FingerprintStatusUtils fingerprintStatusUtils = new FingerprintStatusUtils(context, + fingerprintManager); + + if (fingerprintStatusUtils.isAvailable()) { + final RestrictedLockUtils.EnforcedAdmin disablingAdmin = + fingerprintStatusUtils.getDisablingAdmin(); + sendBiometricSafetySourceData(context, + context.getString(R.string.security_settings_fingerprint_preference_title), + fingerprintStatusUtils.getSummary(), + biometricNavigationUtils.getBiometricSettingsIntent(context, + fingerprintStatusUtils.getSettingsClassName(), disablingAdmin, + Bundle.EMPTY), + disablingAdmin == null /* enabled */); + } + } + + private static void sendBiometricSafetySourceData(Context context, String title, String summary, + Intent clickIntent, boolean enabled) { + final PendingIntent pendingIntent = createPendingIntent(context, clickIntent); + + final SafetySourceStatus status = new SafetySourceStatus.Builder(title, summary, + SafetySourceStatus.STATUS_LEVEL_NONE, pendingIntent) + .setEnabled(enabled).build(); + final SafetySourceData safetySourceData = new SafetySourceData.Builder(SAFETY_SOURCE_ID) + .setStatus(status).build(); + + SafetyCenterManagerWrapper.get().sendSafetyCenterUpdate(context, safetySourceData); + } + + private static PendingIntent createPendingIntent(Context context, Intent intent) { + return PendingIntent + .getActivity( + context, + 0 /* requestCode */, + intent, + PendingIntent.FLAG_IMMUTABLE); } } diff --git a/tests/unit/src/com/android/settings/biometrics/BiometricNavigationUtilsTest.java b/tests/unit/src/com/android/settings/biometrics/BiometricNavigationUtilsTest.java index 3e6ac093542..c767c32dec6 100644 --- a/tests/unit/src/com/android/settings/biometrics/BiometricNavigationUtilsTest.java +++ b/tests/unit/src/com/android/settings/biometrics/BiometricNavigationUtilsTest.java @@ -24,14 +24,20 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.os.UserHandle; import android.os.UserManager; +import android.provider.Settings; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -44,6 +50,8 @@ public class BiometricNavigationUtilsTest { private static final String SETTINGS_CLASS_NAME = "SettingsClassName"; private static final String EXTRA_KEY = "EXTRA_KEY"; + private static final ComponentName COMPONENT_NAME = new ComponentName("package", "class"); + private static final int ADMIN_USER_ID = 2; @Mock private UserManager mUserManager; @@ -60,7 +68,7 @@ public class BiometricNavigationUtilsTest { } @Test - public void openBiometricSettings_quietMode_launchesQuiteModeDialog() { + public void launchBiometricSettings_quietMode_launchesQuiteModeDialog() { when(mUserManager.isQuietModeEnabled(any())).thenReturn(true); mBiometricNavigationUtils.launchBiometricSettings(mContext, SETTINGS_CLASS_NAME, @@ -70,7 +78,7 @@ public class BiometricNavigationUtilsTest { } @Test - public void openBiometricSettings_quietMode_returnsFalse() { + public void launchBiometricSettings_quietMode_returnsFalse() { when(mUserManager.isQuietModeEnabled(any())).thenReturn(true); assertThat(mBiometricNavigationUtils.launchBiometricSettings( @@ -78,7 +86,7 @@ public class BiometricNavigationUtilsTest { } @Test - public void openBiometricSettings_noQuietMode_emptyExtras_launchesFragmentWithoutExtras() { + public void launchBiometricSettings_noQuietMode_emptyExtras_launchesFragmentWithoutExtras() { when(mUserManager.isQuietModeEnabled(any())).thenReturn(false); mBiometricNavigationUtils.launchBiometricSettings( @@ -88,7 +96,7 @@ public class BiometricNavigationUtilsTest { } @Test - public void openBiometricSettings_noQuietMode_emptyExtras_returnsTrue() { + public void launchBiometricSettings_noQuietMode_emptyExtras_returnsTrue() { when(mUserManager.isQuietModeEnabled(any())).thenReturn(false); assertThat(mBiometricNavigationUtils.launchBiometricSettings( @@ -96,7 +104,7 @@ public class BiometricNavigationUtilsTest { } @Test - public void openBiometricSettings_noQuietMode_withExtras_launchesFragmentWithExtras() { + public void launchBiometricSettings_noQuietMode_withExtras_launchesFragmentWithExtras() { when(mUserManager.isQuietModeEnabled(any())).thenReturn(false); final Bundle extras = createNotEmptyExtras(); @@ -106,13 +114,79 @@ public class BiometricNavigationUtilsTest { } @Test - public void openBiometricSettings_noQuietMode_withExtras_returnsTrue() { + public void launchBiometricSettings_noQuietMode_withExtras_returnsTrue() { when(mUserManager.isQuietModeEnabled(any())).thenReturn(false); assertThat(mBiometricNavigationUtils.launchBiometricSettings( mContext, SETTINGS_CLASS_NAME, createNotEmptyExtras())).isTrue(); } + @Test + public void getBiometricSettingsIntent_quietMode_returnsQuiteModeDialogIntent() { + when(mUserManager.isQuietModeEnabled(any())).thenReturn(true); + + final Intent intent = mBiometricNavigationUtils.getBiometricSettingsIntent( + mContext, SETTINGS_CLASS_NAME, null /* enforcedAdmin */, Bundle.EMPTY); + + assertQuietModeDialogIntent(intent); + } + + @Test + public void getBiometricSettingsIntent_noQuietMode_emptyExtras_returnsSettingsIntent() { + when(mUserManager.isQuietModeEnabled(any())).thenReturn(false); + + final Intent intent = mBiometricNavigationUtils.getBiometricSettingsIntent( + mContext, SETTINGS_CLASS_NAME, null /* enforcedAdmin */, Bundle.EMPTY); + + assertSettingsPageIntent(intent, false /* shouldContainExtras */); + } + + @Test + public void getBiometricSettingsIntent_noQuietMode_withExtras_returnsSettingsIntent() { + when(mUserManager.isQuietModeEnabled(any())).thenReturn(false); + + final Intent intent = mBiometricNavigationUtils.getBiometricSettingsIntent( + mContext, SETTINGS_CLASS_NAME, null /* enforcedAdmin */, createNotEmptyExtras()); + + assertSettingsPageIntent(intent, true /* shouldContainExtras */); + } + + @Test + public void getBiometricSettingsIntent_whenDisabledByAdmin_quietMode_returnsBlockedIntent() { + when(mUserManager.isQuietModeEnabled(any())).thenReturn(true); + final EnforcedAdmin enforcedAdmin = new EnforcedAdmin( + COMPONENT_NAME, UserHandle.of(ADMIN_USER_ID)); + + final Intent intent = mBiometricNavigationUtils.getBiometricSettingsIntent( + mContext, SETTINGS_CLASS_NAME, enforcedAdmin, Bundle.EMPTY); + + assertBlockedByAdminDialogIntent(intent); + } + + @Test + public void getBiometricSettingsIntent_whenDisabledByAdmin_emptyExtras_returnsBlockedIntent() { + when(mUserManager.isQuietModeEnabled(any())).thenReturn(false); + final EnforcedAdmin enforcedAdmin = new EnforcedAdmin( + COMPONENT_NAME, UserHandle.of(ADMIN_USER_ID)); + + final Intent intent = mBiometricNavigationUtils.getBiometricSettingsIntent( + mContext, SETTINGS_CLASS_NAME, enforcedAdmin, Bundle.EMPTY); + + assertBlockedByAdminDialogIntent(intent); + } + + @Test + public void getBiometricSettingsIntent_whenDisabledByAdmin_withExtras_returnsBlockedIntent() { + when(mUserManager.isQuietModeEnabled(any())).thenReturn(false); + final EnforcedAdmin enforcedAdmin = new EnforcedAdmin( + COMPONENT_NAME, UserHandle.of(ADMIN_USER_ID)); + + final Intent intent = mBiometricNavigationUtils.getBiometricSettingsIntent( + mContext, SETTINGS_CLASS_NAME, enforcedAdmin, Bundle.EMPTY); + + assertBlockedByAdminDialogIntent(intent); + } + private Bundle createNotEmptyExtras() { final Bundle bundle = new Bundle(); bundle.putInt(EXTRA_KEY, 0); @@ -124,17 +198,32 @@ public class BiometricNavigationUtilsTest { verify(mContext).startActivity(intentCaptor.capture()); Intent intent = intentCaptor.getValue(); + assertQuietModeDialogIntent(intent); + } + + private void assertQuietModeDialogIntent(Intent intent) { assertThat(intent.getComponent().getPackageName()) .isEqualTo("android"); assertThat(intent.getComponent().getClassName()) .isEqualTo("com.android.internal.app.UnlaunchableAppActivity"); } + private void assertBlockedByAdminDialogIntent(Intent intent) { + assertThat(intent.getAction()).isEqualTo(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS); + assertThat( + (ComponentName) intent.getParcelableExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN)) + .isEqualTo(COMPONENT_NAME); + } + private void assertSettingsPageLaunchRequested(boolean shouldContainExtras) { ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mContext).startActivity(intentCaptor.capture()); Intent intent = intentCaptor.getValue(); + assertSettingsPageIntent(intent, shouldContainExtras); + } + + private void assertSettingsPageIntent(Intent intent, boolean shouldContainExtras) { assertThat(intent.getComponent().getPackageName()) .isEqualTo("com.android.settings"); assertThat(intent.getComponent().getClassName()) diff --git a/tests/unit/src/com/android/settings/safetycenter/BiometricsSafetySourceTest.java b/tests/unit/src/com/android/settings/safetycenter/BiometricsSafetySourceTest.java index 2627d245d96..4a91e8faa19 100644 --- a/tests/unit/src/com/android/settings/safetycenter/BiometricsSafetySourceTest.java +++ b/tests/unit/src/com/android/settings/safetycenter/BiometricsSafetySourceTest.java @@ -16,35 +16,84 @@ package com.android.settings.safetycenter; +import static android.provider.Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS; + +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.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.hardware.face.FaceManager; +import android.hardware.fingerprint.Fingerprint; +import android.hardware.fingerprint.FingerprintManager; +import android.os.UserHandle; +import android.safetycenter.SafetySourceData; +import android.safetycenter.SafetySourceStatus; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.android.settings.Settings; +import com.android.settings.biometrics.face.FaceEnrollIntroduction; +import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroduction; +import com.android.settings.biometrics.fingerprint.FingerprintSettings; +import com.android.settings.testutils.ResourcesUtils; + import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + @RunWith(AndroidJUnit4.class) public class BiometricsSafetySourceTest { + private static final ComponentName COMPONENT_NAME = + new ComponentName("package", "class"); + private static final UserHandle USER_HANDLE = new UserHandle(UserHandle.myUserId()); + private Context mApplicationContext; + @Mock + private PackageManager mPackageManager; + @Mock + private DevicePolicyManager mDevicePolicyManager; + @Mock + private FingerprintManager mFingerprintManager; + @Mock + private FaceManager mFaceManager; @Mock private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mApplicationContext = ApplicationProvider.getApplicationContext(); + mApplicationContext = spy(ApplicationProvider.getApplicationContext()); + when(mApplicationContext.getPackageManager()).thenReturn(mPackageManager); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)).thenReturn(true); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true); + when(mDevicePolicyManager.getProfileOwnerOrDeviceOwnerSupervisionComponent(USER_HANDLE)) + .thenReturn(COMPONENT_NAME); + when(mApplicationContext.getSystemService(Context.FINGERPRINT_SERVICE)) + .thenReturn(mFingerprintManager); + when(mApplicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE)) + .thenReturn(mDevicePolicyManager); + when(mApplicationContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mFaceManager); SafetyCenterManagerWrapper.sInstance = mSafetyCenterManagerWrapper; } @@ -63,12 +112,371 @@ public class BiometricsSafetySourceTest { } @Test - // TODO(b/215517420): Adapt this test when method is implemented. - public void sendSafetyData_whenSafetyCenterIsEnabled_sendsNoData() { - when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + public void sendSafetyData_whenSafetyCenterIsEnabled_withoutBiometrics_sendsNoData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(false); + when(mFingerprintManager.isHardwareDetected()).thenReturn(false); + when(mFaceManager.isHardwareDetected()).thenReturn(false); BiometricsSafetySource.sendSafetyData(mApplicationContext); verify(mSafetyCenterManagerWrapper, never()).sendSafetyCenterUpdate(any(), any()); } + + @Test + public void sendSafetyData_withFingerprintNotEnrolled_whenDisabledByAdmin_sendsData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(false); + when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(false); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)) + .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT); + + BiometricsSafetySource.sendSafetyData(mApplicationContext); + + assertSafetySourceDisabledDataSentWithSingularSummary( + "security_settings_fingerprint_preference_title", + "security_settings_fingerprint_preference_summary_none"); + } + + @Test + public void sendSafetyData_withFingerprintNotEnrolled_whenNotDisabledByAdmin_sendsData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(false); + when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(false); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)).thenReturn(0); + + BiometricsSafetySource.sendSafetyData(mApplicationContext); + + assertSafetySourceEnabledDataSentWithSingularSummary( + "security_settings_fingerprint_preference_title", + "security_settings_fingerprint_preference_summary_none", + FingerprintEnrollIntroduction.class.getName()); + } + + @Test + public void sendSafetyData_withFingerprintsEnrolled_whenDisabledByAdmin_sendsData() { + final int enrolledFingerprintsCount = 2; + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(false); + when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(true); + when(mFingerprintManager.getEnrolledFingerprints(anyInt())) + .thenReturn(createFingerprintList(enrolledFingerprintsCount)); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)) + .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT); + + BiometricsSafetySource.sendSafetyData(mApplicationContext); + + assertSafetySourceDisabledDataSentWithPluralSummary( + "security_settings_fingerprint_preference_title", + "security_settings_fingerprint_preference_summary", + enrolledFingerprintsCount); + } + + @Test + public void sendSafetyData_withFingerprintsEnrolled_whenNotDisabledByAdmin_sendsData() { + final int enrolledFingerprintsCount = 2; + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(false); + when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(true); + when(mFingerprintManager.getEnrolledFingerprints(anyInt())) + .thenReturn(createFingerprintList(enrolledFingerprintsCount)); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)).thenReturn(0); + + BiometricsSafetySource.sendSafetyData(mApplicationContext); + + assertSafetySourceEnabledDataSentWithPluralSummary( + "security_settings_fingerprint_preference_title", + "security_settings_fingerprint_preference_summary", enrolledFingerprintsCount, + FingerprintSettings.class.getName()); + } + + @Test + public void sendSafetyData_withFaceNotEnrolled_whenDisabledByAdmin_sendsData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFingerprintManager.isHardwareDetected()).thenReturn(false); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)) + .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FACE); + + BiometricsSafetySource.sendSafetyData(mApplicationContext); + + assertSafetySourceDisabledDataSentWithSingularSummary( + "security_settings_face_preference_title", + "security_settings_face_preference_summary_none"); + } + + @Test + public void sendSafetyData_withFaceNotEnrolled_whenNotDisabledByAdmin_sendsData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFingerprintManager.isHardwareDetected()).thenReturn(false); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)).thenReturn(0); + + BiometricsSafetySource.sendSafetyData(mApplicationContext); + + assertSafetySourceEnabledDataSentWithSingularSummary( + "security_settings_face_preference_title", + "security_settings_face_preference_summary_none", + FaceEnrollIntroduction.class.getName()); + } + + @Test + public void sendSafetyData_withFaceEnrolled_whenDisabledByAdmin_sendsData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFingerprintManager.isHardwareDetected()).thenReturn(false); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)) + .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FACE); + + BiometricsSafetySource.sendSafetyData(mApplicationContext); + + assertSafetySourceDisabledDataSentWithSingularSummary( + "security_settings_face_preference_title", + "security_settings_face_preference_summary"); + } + + @Test + public void sendSafetyData_withFaceEnrolled_whenNotDisabledByAdmin_sendsData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFingerprintManager.isHardwareDetected()).thenReturn(false); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)).thenReturn(0); + + BiometricsSafetySource.sendSafetyData(mApplicationContext); + + assertSafetySourceEnabledDataSentWithSingularSummary( + "security_settings_face_preference_title", + "security_settings_face_preference_summary", + Settings.FaceSettingsActivity.class.getName()); + } + + @Test + public void sandSafetyData_withFaceAndFingerprint_whenBothNotDisabledByAdmin_sendsData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)).thenReturn(0); + + BiometricsSafetySource.sendSafetyData(mApplicationContext); + + assertSafetySourceEnabledDataSentWithSingularSummary( + "security_settings_biometric_preference_title", + "security_settings_biometric_preference_summary_none_enrolled", + Settings.CombinedBiometricSettingsActivity.class.getName()); + } + + @Test + public void sandSafetyData_withFaceAndFingerprint_whenFaceDisabledByAdmin_sendsData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)) + .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FACE); + + BiometricsSafetySource.sendSafetyData(mApplicationContext); + + assertSafetySourceEnabledDataSentWithSingularSummary( + "security_settings_biometric_preference_title", + "security_settings_biometric_preference_summary_none_enrolled", + Settings.CombinedBiometricSettingsActivity.class.getName()); + } + + @Test + public void sandSafetyData_withFaceAndFingerprint_whenFingerprintDisabledByAdmin_sendsData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)) + .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT); + + BiometricsSafetySource.sendSafetyData(mApplicationContext); + + assertSafetySourceEnabledDataSentWithSingularSummary( + "security_settings_biometric_preference_title", + "security_settings_biometric_preference_summary_none_enrolled", + Settings.CombinedBiometricSettingsActivity.class.getName()); + } + + @Test + public void sandSafetyData_withFaceAndFingerprint_whenBothDisabledByAdmin_sendsData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)) + .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FACE + | DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT); + + BiometricsSafetySource.sendSafetyData(mApplicationContext); + + assertSafetySourceDisabledDataSentWithSingularSummary( + "security_settings_biometric_preference_title", + "security_settings_biometric_preference_summary_none_enrolled"); + } + + @Test + public void sandSafetyData_withFaceAndFingerprint_whenFaceEnrolled_withMpFingers_sendsData() { + final int enrolledFingerprintsCount = 2; + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true); + when(mFingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn( + createFingerprintList(enrolledFingerprintsCount)); + + BiometricsSafetySource.sendSafetyData(mApplicationContext); + + assertSafetySourceEnabledDataSentWithSingularSummary( + "security_settings_biometric_preference_title", + "security_settings_biometric_preference_summary_both_fp_multiple", + Settings.CombinedBiometricSettingsActivity.class.getName()); + } + + @Test + public void sandSafetyData_withFaceAndFingerprint_whenFaceEnrolled_withOneFinger_sendsData() { + final int enrolledFingerprintsCount = 1; + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true); + when(mFingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn( + createFingerprintList(enrolledFingerprintsCount)); + + BiometricsSafetySource.sendSafetyData(mApplicationContext); + + assertSafetySourceEnabledDataSentWithSingularSummary( + "security_settings_biometric_preference_title", + "security_settings_biometric_preference_summary_both_fp_single", + Settings.CombinedBiometricSettingsActivity.class.getName()); + } + + @Test + public void sandSafetyData_withFaceAndFingerprint_whenFaceEnrolled_withNoFingers_sendsData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true); + when(mFingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn( + Collections.emptyList()); + + BiometricsSafetySource.sendSafetyData(mApplicationContext); + + assertSafetySourceEnabledDataSentWithSingularSummary( + "security_settings_biometric_preference_title", + "security_settings_face_preference_summary", + Settings.CombinedBiometricSettingsActivity.class.getName()); + } + + @Test + public void sandSafetyData_withFaceAndFingerprint_whenNoFaceEnrolled_withFingers_sendsData() { + final int enrolledFingerprintsCount = 1; + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false); + when(mFingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn( + createFingerprintList(enrolledFingerprintsCount)); + + BiometricsSafetySource.sendSafetyData(mApplicationContext); + + assertSafetySourceEnabledDataSentWithPluralSummary( + "security_settings_biometric_preference_title", + "security_settings_fingerprint_preference_summary", enrolledFingerprintsCount, + Settings.CombinedBiometricSettingsActivity.class.getName()); + } + + private void assertSafetySourceDisabledDataSentWithSingularSummary(String expectedTitleResName, + String expectedSummaryResName) { + assertSafetySourceDisabledDataSent( + ResourcesUtils.getResourcesString(mApplicationContext, expectedTitleResName), + ResourcesUtils.getResourcesString(mApplicationContext, expectedSummaryResName) + ); + } + + private void assertSafetySourceEnabledDataSentWithSingularSummary(String expectedTitleResName, + String expectedSummaryResName, + String expectedSettingsClassName) { + assertSafetySourceEnabledDataSent( + ResourcesUtils.getResourcesString(mApplicationContext, expectedTitleResName), + ResourcesUtils.getResourcesString(mApplicationContext, expectedSummaryResName), + expectedSettingsClassName + ); + } + + private void assertSafetySourceDisabledDataSentWithPluralSummary(String expectedTitleResName, + String expectedSummaryResName, int expectedSummaryQuantity) { + final int stringResId = ResourcesUtils.getResourcesId( + ApplicationProvider.getApplicationContext(), "plurals", + expectedSummaryResName); + assertSafetySourceDisabledDataSent( + ResourcesUtils.getResourcesString(mApplicationContext, expectedTitleResName), + mApplicationContext.getResources().getQuantityString(stringResId, + expectedSummaryQuantity /* quantity */, + expectedSummaryQuantity /* formatArgs */) + ); + } + + private void assertSafetySourceEnabledDataSentWithPluralSummary(String expectedTitleResName, + String expectedSummaryResName, int expectedSummaryQuantity, + String expectedSettingsClassName) { + final int stringResId = ResourcesUtils.getResourcesId( + ApplicationProvider.getApplicationContext(), "plurals", + expectedSummaryResName); + assertSafetySourceEnabledDataSent( + ResourcesUtils.getResourcesString(mApplicationContext, expectedTitleResName), + mApplicationContext.getResources().getQuantityString(stringResId, + expectedSummaryQuantity /* quantity */, + expectedSummaryQuantity /* formatArgs */), + expectedSettingsClassName + ); + } + + private void assertSafetySourceDisabledDataSent(String expectedTitle, String expectedSummary) { + ArgumentCaptor captor = ArgumentCaptor.forClass(SafetySourceData.class); + verify(mSafetyCenterManagerWrapper).sendSafetyCenterUpdate(any(), captor.capture()); + SafetySourceData safetySourceData = captor.getValue(); + SafetySourceStatus safetySourceStatus = safetySourceData.getStatus(); + + assertThat(safetySourceData.getId()).isEqualTo(BiometricsSafetySource.SAFETY_SOURCE_ID); + assertThat(safetySourceStatus.getTitle().toString()).isEqualTo(expectedTitle); + assertThat(safetySourceStatus.getSummary().toString()).isEqualTo(expectedSummary); + assertThat(safetySourceStatus.isEnabled()).isFalse(); + final Intent clickIntent = safetySourceStatus.getPendingIntent().getIntent(); + assertThat(clickIntent).isNotNull(); + assertThat(clickIntent.getAction()).isEqualTo(ACTION_SHOW_ADMIN_SUPPORT_DETAILS); + } + + private void assertSafetySourceEnabledDataSent(String expectedTitle, String expectedSummary, + String expectedSettingsClassName) { + ArgumentCaptor captor = ArgumentCaptor.forClass(SafetySourceData.class); + verify(mSafetyCenterManagerWrapper).sendSafetyCenterUpdate(any(), captor.capture()); + SafetySourceData safetySourceData = captor.getValue(); + SafetySourceStatus safetySourceStatus = safetySourceData.getStatus(); + + assertThat(safetySourceData.getId()).isEqualTo(BiometricsSafetySource.SAFETY_SOURCE_ID); + assertThat(safetySourceStatus.getTitle().toString()).isEqualTo(expectedTitle); + assertThat(safetySourceStatus.getSummary().toString()).isEqualTo(expectedSummary); + assertThat(safetySourceStatus.isEnabled()).isTrue(); + final Intent clickIntent = safetySourceStatus.getPendingIntent().getIntent(); + assertThat(clickIntent).isNotNull(); + assertThat(clickIntent.getComponent().getPackageName()) + .isEqualTo("com.android.settings"); + assertThat(clickIntent.getComponent().getClassName()) + .isEqualTo(expectedSettingsClassName); + } + + + private List createFingerprintList(int size) { + final List fingerprintList = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + fingerprintList.add(new Fingerprint("fingerprint" + i, 0, 0)); + } + return fingerprintList; + } } diff --git a/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java b/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java index f2a28ff04c7..f042c2279fa 100644 --- a/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java +++ b/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java @@ -45,6 +45,8 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.List; + @RunWith(AndroidJUnit4.class) public class SafetySourceBroadcastReceiverTest { @@ -149,9 +151,12 @@ public class SafetySourceBroadcastReceiverTest { new String[]{ BiometricsSafetySource.SAFETY_SOURCE_ID }); new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent); + ArgumentCaptor captor = ArgumentCaptor.forClass(SafetySourceData.class); + verify(mSafetyCenterManagerWrapper, times(1)) + .sendSafetyCenterUpdate(any(), captor.capture()); + SafetySourceData safetySourceData = captor.getValue(); - // TODO(b/215517420): Update this test when BiometricSafetySource is implemented. - verify(mSafetyCenterManagerWrapper, never()).sendSafetyCenterUpdate(any(), any()); + assertThat(safetySourceData.getId()).isEqualTo(BiometricsSafetySource.SAFETY_SOURCE_ID); } @Test @@ -159,14 +164,15 @@ public class SafetySourceBroadcastReceiverTest { when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); Intent intent = new Intent().setAction(Intent.ACTION_BOOT_COMPLETED); - // TODO(b/215517420): Update this test when BiometricSafetySource is implemented to test - // that biometrics data is also sent. new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent); ArgumentCaptor captor = ArgumentCaptor.forClass(SafetySourceData.class); - verify(mSafetyCenterManagerWrapper, times(1)) + verify(mSafetyCenterManagerWrapper, times(2)) .sendSafetyCenterUpdate(any(), captor.capture()); - SafetySourceData safetySourceData = captor.getValue(); + List safetySourceDataList = captor.getAllValues(); - assertThat(safetySourceData.getId()).isEqualTo(LockScreenSafetySource.SAFETY_SOURCE_ID); + assertThat(safetySourceDataList.stream().anyMatch( + data -> data.getId().equals(LockScreenSafetySource.SAFETY_SOURCE_ID))).isTrue(); + assertThat(safetySourceDataList.stream().anyMatch( + data -> data.getId().equals(BiometricsSafetySource.SAFETY_SOURCE_ID))).isTrue(); } } From 3f67f87efe7c69d4d655e7bf9e895249795b6133 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Mon, 28 Feb 2022 17:24:16 -0800 Subject: [PATCH 07/13] Use Build.VERSION.RELEASE_OR_PREVIEW_DISPLAY for user visible versions Use Build.VERSION.RELEASE_OR_PREVIEW_DISPLAY which will show a user friendly version name for preview releases, and the release version for final releases. Bug: 221950960 Test: manual Change-Id: I3a7e6a3c996d52614b89888afd35da8cff458b43 --- .../FirmwareVersionDetailPreferenceController.java | 2 +- .../firmwareversion/FirmwareVersionPreferenceController.java | 2 +- src/com/android/settings/deviceinfo/storage/StorageUtils.java | 2 +- .../settings/system/SystemUpdatePreferenceController.java | 2 +- .../FirmwareVersionDetailPreferenceControllerTest.java | 2 +- .../FirmwareVersionPreferenceControllerTest.java | 2 +- .../settings/system/SystemUpdatePreferenceControllerTest.java | 4 ++-- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionDetailPreferenceController.java b/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionDetailPreferenceController.java index d6cf442b11c..880843eac32 100644 --- a/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionDetailPreferenceController.java +++ b/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionDetailPreferenceController.java @@ -75,7 +75,7 @@ public class FirmwareVersionDetailPreferenceController extends BasePreferenceCon @Override public CharSequence getSummary() { - return Build.VERSION.RELEASE_OR_CODENAME; + return Build.VERSION.RELEASE_OR_PREVIEW_DISPLAY; } @Override diff --git a/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionPreferenceController.java b/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionPreferenceController.java index eefa1f98aa9..6cf02a06cb9 100644 --- a/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionPreferenceController.java +++ b/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionPreferenceController.java @@ -34,6 +34,6 @@ public class FirmwareVersionPreferenceController extends BasePreferenceControlle @Override public CharSequence getSummary() { - return Build.VERSION.RELEASE_OR_CODENAME; + return Build.VERSION.RELEASE_OR_PREVIEW_DISPLAY; } } diff --git a/src/com/android/settings/deviceinfo/storage/StorageUtils.java b/src/com/android/settings/deviceinfo/storage/StorageUtils.java index 9b52fe803b1..22ad8548a88 100644 --- a/src/com/android/settings/deviceinfo/storage/StorageUtils.java +++ b/src/com/android/settings/deviceinfo/storage/StorageUtils.java @@ -219,7 +219,7 @@ public class StorageUtils { public Dialog onCreateDialog(Bundle savedInstanceState) { return new AlertDialog.Builder(getActivity()) .setMessage(getContext().getString(R.string.storage_detail_dialog_system, - Build.VERSION.RELEASE_OR_CODENAME)) + Build.VERSION.RELEASE_OR_PREVIEW_DISPLAY)) .setPositiveButton(android.R.string.ok, null) .create(); } diff --git a/src/com/android/settings/system/SystemUpdatePreferenceController.java b/src/com/android/settings/system/SystemUpdatePreferenceController.java index 92819d6ddf3..b2a22ff4a13 100644 --- a/src/com/android/settings/system/SystemUpdatePreferenceController.java +++ b/src/com/android/settings/system/SystemUpdatePreferenceController.java @@ -89,7 +89,7 @@ public class SystemUpdatePreferenceController extends BasePreferenceController { @Override public CharSequence getSummary() { CharSequence summary = mContext.getString(R.string.android_version_summary, - Build.VERSION.RELEASE_OR_CODENAME); + Build.VERSION.RELEASE_OR_PREVIEW_DISPLAY); final FutureTask bundleFutureTask = new FutureTask<>( // Put the API call in a future to avoid StrictMode violation. () -> mUpdateManager.retrieveSystemUpdateInfo()); diff --git a/tests/robotests/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionDetailPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionDetailPreferenceControllerTest.java index 9fca65d683d..2d68014db03 100644 --- a/tests/robotests/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionDetailPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionDetailPreferenceControllerTest.java @@ -63,7 +63,7 @@ public class FirmwareVersionDetailPreferenceControllerTest { @Test public void getSummary_shouldGetBuildVersion() { - assertThat(mController.getSummary()).isEqualTo(Build.VERSION.RELEASE_OR_CODENAME); + assertThat(mController.getSummary()).isEqualTo(Build.VERSION.RELEASE_OR_PREVIEW_DISPLAY); } @Test diff --git a/tests/robotests/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionPreferenceControllerTest.java index 903b88b8bde..a0f59a56642 100644 --- a/tests/robotests/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionPreferenceControllerTest.java @@ -65,6 +65,6 @@ public class FirmwareVersionPreferenceControllerTest { public void updatePreference_shouldSetSummaryToBuildNumber() { mController.updateState(mPreference); - assertThat(mPreference.getSummary()).isEqualTo(Build.VERSION.RELEASE_OR_CODENAME); + assertThat(mPreference.getSummary()).isEqualTo(Build.VERSION.RELEASE_OR_PREVIEW_DISPLAY); } } diff --git a/tests/robotests/src/com/android/settings/system/SystemUpdatePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/system/SystemUpdatePreferenceControllerTest.java index 95c95bb929e..61aa294fd9b 100644 --- a/tests/robotests/src/com/android/settings/system/SystemUpdatePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/system/SystemUpdatePreferenceControllerTest.java @@ -139,7 +139,7 @@ public class SystemUpdatePreferenceControllerTest { assertThat(mPreference.getSummary()) .isEqualTo(mContext.getString(R.string.android_version_summary, - Build.VERSION.RELEASE_OR_CODENAME)); + Build.VERSION.RELEASE_OR_PREVIEW_DISPLAY)); } @Test @@ -168,4 +168,4 @@ public class SystemUpdatePreferenceControllerTest { assertThat(mPreference.getSummary()) .isEqualTo(mContext.getString(R.string.android_version_pending_update_summary)); } -} \ No newline at end of file +} From 427906c29eaef498fc2a00eeb56bb5eecb9a2982 Mon Sep 17 00:00:00 2001 From: tom hsu Date: Mon, 14 Feb 2022 22:12:43 +0800 Subject: [PATCH 08/13] [Tetheirng] Show a dialog indicate tethering is unsupported by carrier. Bug: 161206517 Test: manual Change-Id: I45a2e42367850ab6fc901fe508671677d843b793 --- AndroidManifest.xml | 12 +++++ res/values/strings.xml | 4 ++ ...therProvisioningCarrierDialogActivity.java | 47 +++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 src/com/android/settings/network/TetherProvisioningCarrierDialogActivity.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index f06d38f50ff..0d1821ad037 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -572,6 +572,18 @@ + + + + + + + Turning hotspot on\u2026 Turning off hotspot\u2026 + + Tethering unavailable + + Contact your carrier for details %1$s is active diff --git a/src/com/android/settings/network/TetherProvisioningCarrierDialogActivity.java b/src/com/android/settings/network/TetherProvisioningCarrierDialogActivity.java new file mode 100644 index 00000000000..2506229802f --- /dev/null +++ b/src/com/android/settings/network/TetherProvisioningCarrierDialogActivity.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2022 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.network; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.os.Bundle; + +import com.android.settings.R; + +/** + * To show a dialog to indicate tethering is unsupported by carrier. + */ +public class TetherProvisioningCarrierDialogActivity extends Activity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + new AlertDialog.Builder(this) + .setTitle(R.string.wifi_tether_carrier_unsupport_dialog_title) + .setMessage(R.string.wifi_tether_carrier_unsupport_dialog_content) + .setCancelable(false) + .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + finish(); + } + }) + .show(); + } +} + From 32c1fc76d8b069e90d5e771b47acfd8a645c38f4 Mon Sep 17 00:00:00 2001 From: Tsung-Mao Fang Date: Tue, 1 Mar 2022 17:11:10 +0800 Subject: [PATCH 09/13] Fix unable to scroll to the bottom page We should set padding for root view instead of margin. Test: Verify the behavior is correct in single pane and two panes mode. Fix: 221422906 Change-Id: I17a63be6e536ffcf39744b2e98dc1ea2723ebf1d --- .../settings/homepage/SettingsHomepageActivity.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java index 051a54ad9cc..73c473a27ff 100644 --- a/src/com/android/settings/homepage/SettingsHomepageActivity.java +++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java @@ -32,7 +32,6 @@ import android.util.ArraySet; import android.util.FeatureFlagUtils; import android.util.Log; import android.view.View; -import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.widget.FrameLayout; @@ -236,12 +235,8 @@ public class SettingsHomepageActivity extends FragmentActivity implements ViewCompat.setOnApplyWindowInsetsListener(findViewById(android.R.id.content), (v, windowInsets) -> { Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); - // Apply the insets as a margin to the view. Here the system is setting - // only the top dimensions. - ViewGroup.MarginLayoutParams mlp = - (ViewGroup.MarginLayoutParams) v.getLayoutParams(); - mlp.topMargin = insets.top; - v.setLayoutParams(mlp); + // Apply the insets paddings to the view. + v.setPadding(insets.left, insets.top, insets.right, insets.bottom); // Return CONSUMED if you don't want the window insets to keep being // passed down to descendant views. From 81da0689ecbac3c77ad57dfa8a4536c1dc516c19 Mon Sep 17 00:00:00 2001 From: Beverly Date: Tue, 1 Mar 2022 14:34:34 +0000 Subject: [PATCH 10/13] Don't show the "When to show" PreferenceCategory If the ambient display isn't available. This includes AOD wakeup gestures. Test: manual Fixes: 195413949 Change-Id: Ief1876e231288274a35db2fb185ac92864450d48 --- res/xml/security_lockscreen_settings.xml | 1 + ...DisplayWhenToShowPreferenceController.java | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 src/com/android/settings/display/AmbientDisplayWhenToShowPreferenceController.java diff --git a/res/xml/security_lockscreen_settings.xml b/res/xml/security_lockscreen_settings.xml index b61f4586f31..3bd84f81ae3 100644 --- a/res/xml/security_lockscreen_settings.xml +++ b/res/xml/security_lockscreen_settings.xml @@ -79,6 +79,7 @@ Date: Thu, 24 Feb 2022 13:52:13 +0000 Subject: [PATCH 11/13] Add warning card for Safety Center when no screen lock is set Test: atest SettingsUnitTests Bug: 218868097 Change-Id: I4cbc50559dd1d32b5b916a19f7d0b2b27d67510d --- res/values/strings.xml | 7 +++ .../safetycenter/LockScreenSafetySource.java | 28 +++++++++- .../LockScreenSafetySourceTest.java | 54 +++++++++++++++++++ 3 files changed, 87 insertions(+), 2 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 3363f929c25..43c1acd4808 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1301,6 +1301,13 @@ Encrypted + + Set a screen lock + + For added security, set a PIN, pattern, or password for this device. + + Set screen lock + diff --git a/src/com/android/settings/safetycenter/LockScreenSafetySource.java b/src/com/android/settings/safetycenter/LockScreenSafetySource.java index 4b92e0691ea..d0108210476 100644 --- a/src/com/android/settings/safetycenter/LockScreenSafetySource.java +++ b/src/com/android/settings/safetycenter/LockScreenSafetySource.java @@ -22,6 +22,7 @@ import android.content.Context; import android.content.Intent; import android.os.UserHandle; import android.safetycenter.SafetySourceData; +import android.safetycenter.SafetySourceIssue; import android.safetycenter.SafetySourceStatus; import android.safetycenter.SafetySourceStatus.IconAction; @@ -34,6 +35,9 @@ import com.android.settingslib.RestrictedLockUtilsInternal; public final class LockScreenSafetySource { public static final String SAFETY_SOURCE_ID = "LockScreen"; + public static final String NO_SCREEN_LOCK_ISSUE_ID = "NoScreenLockIssue"; + public static final String NO_SCREEN_LOCK_ISSUE_TYPE_ID = "NoScreenLockIssueType"; + public static final String SET_SCREEN_LOCK_ACTION_ID = "SetScreenLockAction"; private LockScreenSafetySource() { } @@ -67,8 +71,12 @@ public final class LockScreenSafetySource { .setEnabled( !screenLockPreferenceDetailsUtils.isPasswordQualityManaged(userId, admin)) .setIconAction(gearMenuIconAction).build(); - final SafetySourceData safetySourceData = new SafetySourceData.Builder( - SAFETY_SOURCE_ID).setStatus(status).build(); + final SafetySourceData.Builder safetySourceDataBuilder = new SafetySourceData.Builder( + SAFETY_SOURCE_ID).setStatus(status); + if (!screenLockPreferenceDetailsUtils.isLockPatternSecure()) { + safetySourceDataBuilder.addIssue(createNoScreenLockIssue(context, pendingIntent)); + } + final SafetySourceData safetySourceData = safetySourceDataBuilder.build(); SafetyCenterManagerWrapper.get().sendSafetyCenterUpdate(context, safetySourceData); } @@ -97,4 +105,20 @@ public final class LockScreenSafetySource { intent, PendingIntent.FLAG_IMMUTABLE); } + + private static SafetySourceIssue createNoScreenLockIssue(Context context, + PendingIntent pendingIntent) { + final SafetySourceIssue.Action action = new SafetySourceIssue.Action.Builder( + SET_SCREEN_LOCK_ACTION_ID, + context.getString(R.string.no_screen_lock_issue_action_label), + pendingIntent).build(); + return new SafetySourceIssue.Builder( + NO_SCREEN_LOCK_ISSUE_ID, + context.getString(R.string.no_screen_lock_issue_title), + context.getString(R.string.no_screen_lock_issue_summary), + SafetySourceStatus.STATUS_LEVEL_RECOMMENDATION, + NO_SCREEN_LOCK_ISSUE_TYPE_ID) + .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DEVICE) + .addAction(action).build(); + } } diff --git a/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java b/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java index 125f044f06b..90a24aa5a11 100644 --- a/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java +++ b/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.Intent; import android.safetycenter.SafetySourceData; +import android.safetycenter.SafetySourceIssue; import android.safetycenter.SafetySourceStatus; import android.safetycenter.SafetySourceStatus.IconAction; @@ -163,6 +164,59 @@ public class LockScreenSafetySourceTest { .isEqualTo(SafetySourceStatus.STATUS_LEVEL_RECOMMENDATION); } + @Test + public void sendSafetyData_whenLockPatternIsSecure_sendsNoIssues() { + whenScreenLockIsEnabled(); + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mScreenLockPreferenceDetailsUtils.isLockPatternSecure()).thenReturn(true); + + LockScreenSafetySource.sendSafetyData(mApplicationContext, + mScreenLockPreferenceDetailsUtils); + + ArgumentCaptor captor = ArgumentCaptor.forClass(SafetySourceData.class); + verify(mSafetyCenterManagerWrapper).sendSafetyCenterUpdate(any(), captor.capture()); + SafetySourceData safetySourceData = captor.getValue(); + + assertThat(safetySourceData.getIssues()).isEmpty(); + } + + @Test + public void sendSafetyData_whenLockPatternIsNotSecure_sendsIssue() { + whenScreenLockIsEnabled(); + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mScreenLockPreferenceDetailsUtils.isLockPatternSecure()).thenReturn(false); + + LockScreenSafetySource.sendSafetyData(mApplicationContext, + mScreenLockPreferenceDetailsUtils); + + ArgumentCaptor captor = ArgumentCaptor.forClass(SafetySourceData.class); + verify(mSafetyCenterManagerWrapper).sendSafetyCenterUpdate(any(), captor.capture()); + SafetySourceData safetySourceData = captor.getValue(); + + assertThat(safetySourceData.getIssues()).hasSize(1); + SafetySourceIssue issue = safetySourceData.getIssues().get(0); + assertThat(issue.getId()).isEqualTo(LockScreenSafetySource.NO_SCREEN_LOCK_ISSUE_ID); + assertThat(issue.getTitle().toString()).isEqualTo( + ResourcesUtils.getResourcesString(mApplicationContext, + "no_screen_lock_issue_title")); + assertThat(issue.getSummary().toString()).isEqualTo( + ResourcesUtils.getResourcesString(mApplicationContext, + "no_screen_lock_issue_summary")); + assertThat(issue.getSeverityLevel()).isEqualTo( + SafetySourceStatus.STATUS_LEVEL_RECOMMENDATION); + assertThat(issue.getIssueTypeId()).isEqualTo( + LockScreenSafetySource.NO_SCREEN_LOCK_ISSUE_TYPE_ID); + assertThat(issue.getIssueCategory()).isEqualTo(SafetySourceIssue.ISSUE_CATEGORY_DEVICE); + assertThat(issue.getActions()).hasSize(1); + SafetySourceIssue.Action action = issue.getActions().get(0); + assertThat(action.getId()).isEqualTo(LockScreenSafetySource.SET_SCREEN_LOCK_ACTION_ID); + assertThat(action.getLabel().toString()).isEqualTo( + ResourcesUtils.getResourcesString(mApplicationContext, + "no_screen_lock_issue_action_label")); + assertThat(action.getPendingIntent().getIntent().getAction()) + .isEqualTo(FAKE_ACTION_CHOOSE_LOCK_GENERIC_FRAGMENT); + } + @Test public void sendSafetyData_whenPasswordQualityIsManaged_sendsDisabled() { whenScreenLockIsEnabled(); From f97f5f7005688c5298b2f7ef947804913dfdee77 Mon Sep 17 00:00:00 2001 From: Edgar Wang Date: Tue, 19 Oct 2021 02:09:26 +0800 Subject: [PATCH 12/13] Update workprofile TabLayout for Material Next style 1. Upgrade ViewPager to ViewPager2 which provided better support. 2. Apply new TabLayout style Bug: 193249384 Bug: 195655281 Bug: 193249173 Bug: 179738624 Test: manual Change-Id: I905ee82f315316c2bf4b73bf5581423878e2c3e7 --- res/layout/preference_list_fragment.xml | 22 +---- .../ProfileSelectFragment.java | 89 ++++++------------- 2 files changed, 29 insertions(+), 82 deletions(-) diff --git a/res/layout/preference_list_fragment.xml b/res/layout/preference_list_fragment.xml index 03ba735db97..6dfec46fab1 100644 --- a/res/layout/preference_list_fragment.xml +++ b/res/layout/preference_list_fragment.xml @@ -20,7 +20,6 @@ + style="@style/SettingsLibTabsStyle"/> - - + diff --git a/src/com/android/settings/dashboard/profileselector/ProfileSelectFragment.java b/src/com/android/settings/dashboard/profileselector/ProfileSelectFragment.java index f7a309effc1..47eaf845b75 100644 --- a/src/com/android/settings/dashboard/profileselector/ProfileSelectFragment.java +++ b/src/com/android/settings/dashboard/profileselector/ProfileSelectFragment.java @@ -23,12 +23,9 @@ import static android.content.Intent.EXTRA_USER_ID; import android.annotation.IntDef; import android.app.Activity; import android.app.admin.DevicePolicyManager; -import android.content.Context; -import android.content.res.ColorStateList; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; -import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -37,9 +34,9 @@ import android.widget.LinearLayout; import androidx.annotation.VisibleForTesting; import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentStatePagerAdapter; import androidx.recyclerview.widget.RecyclerView; -import androidx.viewpager.widget.ViewPager; +import androidx.viewpager2.adapter.FragmentStateAdapter; +import androidx.viewpager2.widget.ViewPager2; import com.android.settings.R; import com.android.settings.SettingsActivity; @@ -47,10 +44,10 @@ import com.android.settings.Utils; import com.android.settings.dashboard.DashboardFragment; import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayoutMediator; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.Locale; /** * Base fragment class for profile settings. @@ -111,14 +108,15 @@ public abstract class ProfileSelectFragment extends DashboardFragment { if (titleResId > 0) { activity.setTitle(titleResId); } - final int selectedTab = convertPosition(getTabId(activity, getArguments())); + final int selectedTab = getTabId(activity, getArguments()); final View tabContainer = mContentView.findViewById(R.id.tab_container); - final ViewPager viewPager = tabContainer.findViewById(R.id.view_pager); + final ViewPager2 viewPager = tabContainer.findViewById(R.id.view_pager); viewPager.setAdapter(new ProfileSelectFragment.ViewPagerAdapter(this)); final TabLayout tabs = tabContainer.findViewById(R.id.tabs); - tabs.setupWithViewPager(viewPager); - setupTabTextColor(tabs); + new TabLayoutMediator(tabs, viewPager, + (tab, position) -> tab.setText(getPageTitle(position)) + ).attach(); tabContainer.setVisibility(View.VISIBLE); final TabLayout.Tab tab = tabs.getTabAt(selectedTab); tab.select(); @@ -135,30 +133,6 @@ public abstract class ProfileSelectFragment extends DashboardFragment { return mContentView; } - /** - * TabLayout uses ColorStateList of 2 states, selected state and empty state. - * It's expected to use textColorSecondary default state color as empty state tabTextColor. - * - * However, TabLayout uses textColorSecondary by a not expected state. - * This method sets tabTextColor with the color of expected textColorSecondary state. - */ - private void setupTabTextColor(TabLayout tabLayout) { - final ColorStateList defaultColorStateList = tabLayout.getTabTextColors(); - final ColorStateList resultColorStateList = new ColorStateList( - new int[][]{ - new int[]{android.R.attr.state_selected}, - new int[]{} - }, - new int[] { - defaultColorStateList.getColorForState(new int[]{android.R.attr.state_selected}, - Utils.getColorAttrDefaultColor(getContext(), - com.android.internal.R.attr.colorAccentPrimaryVariant)), - Utils.getColorAttrDefaultColor(getContext(), android.R.attr.textColorSecondary) - } - ); - tabLayout.setTabTextColors(resultColorStateList); - } - @Override public int getMetricsCategory() { return METRICS_CATEGORY_UNKNOWN; @@ -210,47 +184,36 @@ public abstract class ProfileSelectFragment extends DashboardFragment { return PERSONAL_TAB; } - static class ViewPagerAdapter extends FragmentStatePagerAdapter { + private CharSequence getPageTitle(int position) { + final DevicePolicyManager devicePolicyManager = + getContext().getSystemService(DevicePolicyManager.class); + + if (position == WORK_TAB) { + return devicePolicyManager.getString(WORK_CATEGORY_HEADER, + () -> getContext().getString(R.string.category_work)); + } + + return devicePolicyManager.getString(PERSONAL_CATEGORY_HEADER, + () -> getContext().getString(R.string.category_personal)); + } + + static class ViewPagerAdapter extends FragmentStateAdapter { private final Fragment[] mChildFragments; - private final Context mContext; ViewPagerAdapter(ProfileSelectFragment fragment) { - super(fragment.getChildFragmentManager()); - mContext = fragment.getContext(); + super(fragment); mChildFragments = fragment.getFragments(); } @Override - public Fragment getItem(int position) { - return mChildFragments[convertPosition(position)]; + public Fragment createFragment(int position) { + return mChildFragments[position]; } @Override - public int getCount() { + public int getItemCount() { return mChildFragments.length; } - - @Override - public CharSequence getPageTitle(int position) { - DevicePolicyManager devicePolicyManager = - mContext.getSystemService(DevicePolicyManager.class); - - if (convertPosition(position) == WORK_TAB) { - return devicePolicyManager.getString(WORK_CATEGORY_HEADER, - () -> mContext.getString(R.string.category_work)); - } - - return devicePolicyManager.getString(PERSONAL_CATEGORY_HEADER, - () -> mContext.getString(R.string.category_personal)); - } - } - - private static int convertPosition(int index) { - if (TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) - == View.LAYOUT_DIRECTION_RTL) { - return 1 - index; - } - return index; } } From 9ba80e76b38ce8c23e29dd15372c8688451b8c83 Mon Sep 17 00:00:00 2001 From: Arc Wang Date: Fri, 25 Feb 2022 12:35:20 +0800 Subject: [PATCH 13/13] Only show 2-pane deep link when homepage is available DeepLinkHomepageActivity is disabled by default and may be enabled after receiving the PRE_BOOT_COMPLETED broadcast. On some virtual devices, DeepLinkHomepageActivity may not be enabled for test cases. This is a fallback option to show a full screen Activity to pass tests. Bug: 221149428 Test: make RunSettingsRoboTests ROBOTEST_FILTER=SettingsActivityTest Change-Id: I4bbe785176fe3fe8831484141aff7367e4ca25ce --- src/com/android/settings/SettingsActivity.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java index d9dc590c708..37d4a4e99eb 100644 --- a/src/com/android/settings/SettingsActivity.java +++ b/src/com/android/settings/SettingsActivity.java @@ -24,6 +24,7 @@ import static com.android.settings.applications.appinfo.AppButtonsPreferenceCont import android.app.ActionBar; import android.app.ActivityManager; +import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -253,8 +254,7 @@ public class SettingsActivity extends SettingsBaseActivity getMetaData(); final Intent intent = getIntent(); - if (shouldShowTwoPaneDeepLink(intent)) { - launchHomepageForTwoPaneDeepLink(intent); + if (shouldShowTwoPaneDeepLink(intent) && tryStartTwoPaneDeepLink(intent)) { finishAndRemoveTask(); super.onCreate(savedState); return; @@ -412,7 +412,7 @@ public class SettingsActivity extends SettingsBaseActivity return trampolineIntent; } - private void launchHomepageForTwoPaneDeepLink(Intent intent) { + private boolean tryStartTwoPaneDeepLink(Intent intent) { final Intent trampolineIntent; if (intent.getBooleanExtra(EXTRA_IS_FROM_SLICE, false)) { // Get menu key for slice deep link case. @@ -426,7 +426,14 @@ public class SettingsActivity extends SettingsBaseActivity } else { trampolineIntent = getTrampolineIntent(intent, mHighlightMenuKey); } - startActivity(trampolineIntent); + + try { + startActivity(trampolineIntent); + } catch (ActivityNotFoundException e) { + Log.e(LOG_TAG, "Deep link homepage is not available to show 2-pane UI"); + return false; + } + return true; } private boolean shouldShowTwoPaneDeepLink(Intent intent) {