diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 4df368bae18..78d729a0669 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -678,6 +678,18 @@ + + + + + + + + android:theme="@style/FallbackHome" + android:configChanges="keyboardHidden"> 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/res/values/strings.xml b/res/values/strings.xml index 3363f929c25..9454b5ce789 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 + @@ -2574,6 +2581,10 @@ Turning hotspot on\u2026 Turning off hotspot\u2026 + + Tethering unavailable + + Contact your carrier for details %1$s is active 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 @@ 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/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/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/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; } } 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) { 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 f59c8ab50d1..0820712a234 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/display/AmbientDisplayWhenToShowPreferenceController.java b/src/com/android/settings/display/AmbientDisplayWhenToShowPreferenceController.java new file mode 100644 index 00000000000..66b227196bd --- /dev/null +++ b/src/com/android/settings/display/AmbientDisplayWhenToShowPreferenceController.java @@ -0,0 +1,40 @@ +/* + * 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.display; + +import android.content.Context; +import android.hardware.display.AmbientDisplayConfiguration; + +import com.android.settings.core.BasePreferenceController; +import com.android.settings.core.PreferenceControllerMixin; + +/** + * Only show the "When to show" Doze preferences if there's an ambient display available. + */ +public class AmbientDisplayWhenToShowPreferenceController extends + BasePreferenceController implements PreferenceControllerMixin { + private final AmbientDisplayConfiguration mConfig; + + public AmbientDisplayWhenToShowPreferenceController(Context context, String key) { + super(context, key); + mConfig = new AmbientDisplayConfiguration(context); + } + + @Override + public int getAvailabilityStatus() { + return mConfig.ambientDisplayAvailable() ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + } +} 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. 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/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(); + } +} + 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/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/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 +} 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/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) {} + } +} 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) {} + } +} 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/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(); 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(); } }