Merge "Add WearSafetySource (Without Listener)" into main
diff --git a/res/values/strings.xml b/res/values/strings.xml
index cbe5640..60aab52 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1054,6 +1054,7 @@
<!-- Watch unlock enrollment and settings --><skip />
<!-- Title shown for menu item that launches watch unlock settings. [CHAR LIMIT=40] -->
<string name ="security_settings_activeunlock_preference_title">Watch Unlock</string>
+ <string name="security_settings_activeunlock">Watch</string>
<!-- Introduction shown in face and fingerprint page to introduce the biometric feature. [CHAR LIMIT=NONE]-->
<string name="biometric_settings_intro_with_activeunlock">When you set up Face Unlock and Fingerprint Unlock, your phone will ask for your fingerprint when you wear a mask or are in a dark area.\n\nWatch Unlock is another convenient way to unlock your phone, for example, when your fingers are wet or face isn\u2019t recognized.</string>
<!-- Introduction shown in fingerprint page to explain that watch unlock can be used if fingerprint isn't recognized. [CHAR LIMIT=NONE]-->
diff --git a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockContentListener.java b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockContentListener.java
index 8cc7d6a..42029ff 100644
--- a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockContentListener.java
+++ b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockContentListener.java
@@ -28,6 +28,7 @@
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settingslib.utils.ThreadUtils;
@@ -76,17 +77,22 @@
mContentKey = contentKey;
String authority = new ActiveUnlockStatusUtils(mContext).getAuthority();
if (authority != null) {
- mUri = new Uri.Builder()
- .scheme(ContentResolver.SCHEME_CONTENT)
- .authority(authority)
- .appendPath(CONTENT_PROVIDER_PATH)
- .build();
+ mUri = getUri(authority);
} else {
mUri = null;
}
}
+ /** Returns Active Unlock Uri. */
+ public static @NonNull Uri getUri(@NonNull String authority) {
+ return new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(authority)
+ .appendPath(CONTENT_PROVIDER_PATH)
+ .build();
+ }
+
/** Returns true if start listening for updates from the ContentProvider, false otherwise. */
public synchronized boolean subscribe() {
if (mSubscribed || mUri == null) {
@@ -123,25 +129,40 @@
Log.e(mLogTag, "Uri null when trying to fetch content");
return;
}
- ContentResolver contentResolver = mContext.getContentResolver();
- ContentProviderClient client = contentResolver.acquireContentProviderClient(mUri);
- Bundle bundle;
- try {
- bundle = client.call(mMethodName, null /* arg */, null /* extras */);
- } catch (RemoteException e) {
- Log.e(mLogTag, "Failed to call contentProvider", e);
- return;
- } finally {
- client.close();
- }
- if (bundle == null) {
- Log.e(mLogTag, "Null bundle returned from contentProvider");
- return;
- }
- String newValue = bundle.getString(mContentKey);
+
+ @Nullable String newValue = getContentFromUri(
+ mContext, mUri, mLogTag, mMethodName, mContentKey);
if (!TextUtils.equals(mContent, newValue)) {
mContent = newValue;
mContentChangedListener.onContentChanged(mContent);
}
}
+
+ /** Get the content from Uri. */
+ public static @Nullable String getContentFromUri(
+ @NonNull Context context,
+ @NonNull Uri uri,
+ @NonNull String logTag,
+ @NonNull String methodName,
+ @NonNull String contentKey) {
+ ContentResolver contentResolver = context.getContentResolver();
+ ContentProviderClient client = contentResolver.acquireContentProviderClient(uri);
+
+ @Nullable Bundle bundle = null;
+
+ try {
+ bundle = client.call(methodName, /* arg= */ null, /* extras = */ null);
+ } catch (RemoteException e) {
+ Log.e(logTag, "Failed to call contentProvider", e);
+ } finally {
+ client.close();
+ }
+
+ if (bundle == null) {
+ Log.e(logTag, "Null bundle returned from contentProvider");
+ return null;
+ }
+
+ return bundle.getString(contentKey);
+ }
}
diff --git a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockDeviceNameListener.java b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockDeviceNameListener.java
index 1badb0f..9e81762 100644
--- a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockDeviceNameListener.java
+++ b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockDeviceNameListener.java
@@ -21,8 +21,8 @@
/** Listens to device name updates from the content provider and fetches the latest value. */
public class ActiveUnlockDeviceNameListener {
private static final String TAG = "ActiveUnlockDeviceNameListener";
- private static final String METHOD_NAME = "getDeviceName";
- private static final String DEVICE_NAME_KEY = "com.android.settings.active_unlock.device_name";
+ static final String METHOD_NAME = "getDeviceName";
+ static final String DEVICE_NAME_KEY = "com.android.settings.active_unlock.device_name";
private final ActiveUnlockContentListener mActiveUnlockContentListener;
public ActiveUnlockDeviceNameListener(
diff --git a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtils.java b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtils.java
index 4ff2e90..66485d3 100644
--- a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtils.java
+++ b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtils.java
@@ -156,9 +156,16 @@
}
/**
+ * Returns the title of active unlock only.
+ */
+ public @NonNull String getTitleForActiveUnlockOnly() {
+ return mContext.getString(R.string.security_settings_activeunlock);
+ }
+
+ /**
* Returns the title of the combined biometric settings entity when active unlock is enabled.
*/
- public String getTitleForActiveUnlock() {
+ public @NonNull String getTitleForActiveUnlock() {
final boolean faceAllowed = Utils.hasFaceHardware(mContext);
final boolean fingerprintAllowed = Utils.hasFingerprintHardware(mContext);
return mContext.getString(getTitleRes(faceAllowed, fingerprintAllowed));
@@ -264,6 +271,30 @@
return mContext.getString(getUseBiometricTitleRes(faceAllowed, fingerprintAllowed));
}
+ /**
+ * Returns the summary from content provider.
+ */
+ @Nullable
+ public static String getSummaryFromContentProvider(
+ @NonNull Context context, @NonNull String authority, @NonNull String logTag) {
+ return ActiveUnlockContentListener.getContentFromUri(
+ context, ActiveUnlockContentListener.getUri(authority), logTag,
+ ActiveUnlockSummaryListener.METHOD_NAME,
+ ActiveUnlockSummaryListener.SUMMARY_KEY);
+ }
+
+ /**
+ * Returns the device name from content provider.
+ */
+ @Nullable
+ public static String getDeviceNameFromContentProvider(
+ @NonNull Context context, @NonNull String authority, @NonNull String logTag) {
+ return ActiveUnlockContentListener.getContentFromUri(
+ context, ActiveUnlockContentListener.getUri(authority), logTag,
+ ActiveUnlockDeviceNameListener.METHOD_NAME,
+ ActiveUnlockDeviceNameListener.DEVICE_NAME_KEY);
+ }
+
@StringRes
private static int getUseBiometricTitleRes(
boolean isFaceAllowed, boolean isFingerprintAllowed) {
diff --git a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockSummaryListener.java b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockSummaryListener.java
index bcffe62..38e137b 100644
--- a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockSummaryListener.java
+++ b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockSummaryListener.java
@@ -21,8 +21,8 @@
/** Listens to summary updates from the content provider and fetches the latest value. */
public class ActiveUnlockSummaryListener {
private static final String TAG = "ActiveUnlockSummaryListener";
- private static final String METHOD_NAME = "getSummary";
- private static final String SUMMARY_KEY = "com.android.settings.summary";
+ static final String METHOD_NAME = "getSummary";
+ static final String SUMMARY_KEY = "com.android.settings.summary";
private final ActiveUnlockContentListener mContentListener;
public ActiveUnlockSummaryListener(
diff --git a/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java b/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
index 1d8b7a1..ed4b713 100644
--- a/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
+++ b/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
@@ -66,7 +66,7 @@
@VisibleForTesting
static final int CONFIRM_REQUEST = 2001;
private static final int CHOOSE_LOCK_REQUEST = 2002;
- protected static final int ACTIVE_UNLOCK_REQUEST = 2003;
+ public static final int ACTIVE_UNLOCK_REQUEST = 2003;
@VisibleForTesting
static final int BIOMETRIC_AUTH_REQUEST = 2004;
diff --git a/src/com/android/settings/safetycenter/LockScreenSafetySource.java b/src/com/android/settings/safetycenter/LockScreenSafetySource.java
index 00a4c67..61f05f7 100644
--- a/src/com/android/settings/safetycenter/LockScreenSafetySource.java
+++ b/src/com/android/settings/safetycenter/LockScreenSafetySource.java
@@ -131,6 +131,7 @@
if (Flags.biometricsOnboardingEducation()) {
FaceSafetySource.onBiometricsChanged(context);
FingerprintSafetySource.onBiometricsChanged(context);
+ WearSafetySource.onBiometricsChanged(context);
} else {
BiometricsSafetySource.onBiometricsChanged(context);
}
diff --git a/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java b/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java
index a49b7e0..4cf40dd 100644
--- a/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java
+++ b/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java
@@ -86,6 +86,9 @@
if (sourceIds.contains(FingerprintSafetySource.SAFETY_SOURCE_ID)) {
FingerprintSafetySource.setSafetySourceData(context, safetyEvent);
}
+ if (sourceIds.contains(WearSafetySource.SAFETY_SOURCE_ID)) {
+ WearSafetySource.setSafetySourceData(context, safetyEvent);
+ }
}
private static void refreshAllSafetySources(Context context, SafetyEvent safetyEvent) {
@@ -95,5 +98,6 @@
PrivateSpaceSafetySource.setSafetySourceData(context, safetyEvent);
FaceSafetySource.setSafetySourceData(context, safetyEvent);
FingerprintSafetySource.setSafetySourceData(context, safetyEvent);
+ WearSafetySource.setSafetySourceData(context, safetyEvent);
}
}
diff --git a/src/com/android/settings/safetycenter/WearSafetySource.java b/src/com/android/settings/safetycenter/WearSafetySource.java
new file mode 100644
index 0000000..a345096
--- /dev/null
+++ b/src/com/android/settings/safetycenter/WearSafetySource.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2025 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.safetycenter;
+
+import static com.android.settings.biometrics.combination.BiometricsSettingsBase.ACTIVE_UNLOCK_REQUEST;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.os.UserManager;
+import android.safetycenter.SafetyEvent;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils;
+import com.android.settings.flags.Flags;
+
+/** Wear Safety Source for Safety Center. */
+public final class WearSafetySource {
+
+ private static final String TAG = "WearSafetySource";
+ public static final String SAFETY_SOURCE_ID = "AndroidWearUnlock";
+ private static boolean sIsTestingEnv = false;
+ private static String sSummaryForTesting = "";
+ private static boolean sHasEnrolledForTesting;
+
+ private WearSafetySource() {}
+
+ /** Sets test value for summary. */
+ @VisibleForTesting
+ public static void setSummaryForTesting(@NonNull String summary) {
+ sIsTestingEnv = true;
+ sSummaryForTesting = summary;
+ }
+
+ /** Sets test value for hasEnrolled. */
+ @VisibleForTesting
+ public static void setHasEnrolledForTesting(boolean hasEnrolled) {
+ sIsTestingEnv = true;
+ sHasEnrolledForTesting = hasEnrolled;
+ }
+
+ /** Sets biometric safety data for Safety Center. */
+ public static void setSafetySourceData(
+ @NonNull Context context, @NonNull SafetyEvent safetyEvent) {
+ if (!SafetyCenterManagerWrapper.get().isEnabled(context)) {
+ return;
+ }
+ if (!Flags.biometricsOnboardingEducation()) { // this source is effectively turned off
+ sendNullData(context, safetyEvent);
+ return;
+ }
+
+ // Handle private profile case.
+ UserManager userManager = UserManager.get(context);
+ if (android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()
+ && userManager.isPrivateProfile()) {
+ // SC always expects a response from the source if the broadcast has been sent for this
+ // source, therefore, we need to send a null SafetySourceData.
+ sendNullData(context, safetyEvent);
+ return;
+ }
+
+ ActiveUnlockStatusUtils activeUnlockStatusUtils = new ActiveUnlockStatusUtils(context);
+ if (!userManager.isProfile() && activeUnlockStatusUtils.isAvailable()) {
+ boolean hasEnrolled = false;
+ String summary = "";
+
+ if (sIsTestingEnv) {
+ hasEnrolled = sHasEnrolledForTesting;
+ summary = sSummaryForTesting;
+ } else {
+ String authority = new ActiveUnlockStatusUtils(context).getAuthority();
+ hasEnrolled = getHasEnrolledFromContentProvider(context, authority);
+ summary = getSummaryFromContentProvider(context, authority);
+ }
+
+ BiometricSourcesUtils.setBiometricSafetySourceData(
+ SAFETY_SOURCE_ID,
+ context,
+ activeUnlockStatusUtils.getTitleForActiveUnlockOnly(),
+ summary,
+ PendingIntent.getActivity(context, ACTIVE_UNLOCK_REQUEST,
+ activeUnlockStatusUtils.getIntent(),
+ PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT),
+ /* enabled= */ true,
+ hasEnrolled,
+ safetyEvent);
+ return;
+ }
+
+ sendNullData(context, safetyEvent);
+ }
+
+ private static void sendNullData(Context context, SafetyEvent safetyEvent) {
+ SafetyCenterManagerWrapper.get()
+ .setSafetySourceData(
+ context, SAFETY_SOURCE_ID, /* safetySourceData= */ null, safetyEvent);
+ }
+
+ /** Notifies Safety Center of a change in wear biometrics settings. */
+ public static void onBiometricsChanged(@NonNull Context context) {
+ setSafetySourceData(
+ context,
+ new SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED)
+ .build());
+ }
+
+ private static boolean getHasEnrolledFromContentProvider(
+ @NonNull Context context, @Nullable String authority) {
+ if (authority == null) {
+ return false;
+ }
+ return ActiveUnlockStatusUtils.getDeviceNameFromContentProvider(context, authority, TAG)
+ != null;
+ }
+
+ private static String getSummaryFromContentProvider(
+ @NonNull Context context, @Nullable String authority) {
+ if (authority == null) {
+ return "";
+ }
+ String summary = ActiveUnlockStatusUtils.getSummaryFromContentProvider(
+ context, authority, TAG);
+ if (summary == null) {
+ return "";
+ }
+ return summary;
+ }
+
+}
diff --git a/tests/robotests/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtilsTest.java b/tests/robotests/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtilsTest.java
index 563974d..6da6aa7 100644
--- a/tests/robotests/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtilsTest.java
@@ -243,4 +243,11 @@
.isEqualTo(mApplicationContext.getString(
R.string.biometric_settings_use_watch_for));
}
+
+ @Test
+ public void getTitleForActiveUnlockOnly_returnsTile() {
+ assertThat(mActiveUnlockStatusUtils.getTitleForActiveUnlockOnly())
+ .isEqualTo(mApplicationContext.getString(
+ R.string.security_settings_activeunlock));
+ }
}
diff --git a/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java b/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java
index f16113a..6e46d2b 100644
--- a/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java
+++ b/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java
@@ -527,6 +527,9 @@
verify(mSafetyCenterManagerWrapper)
.setSafetySourceData(
any(), eq(FingerprintSafetySource.SAFETY_SOURCE_ID), any(), any());
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(
+ any(), eq(WearSafetySource.SAFETY_SOURCE_ID), any(), any());
}
@Test
diff --git a/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java b/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java
index e65d041..836247c 100644
--- a/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java
+++ b/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java
@@ -246,6 +246,25 @@
}
@Test
+ public void onReceive_onRefresh_withWearUnlockSourceId_setsWearUnlockData() {
+ when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
+ Intent intent =
+ new Intent()
+ .setAction(ACTION_REFRESH_SAFETY_SOURCES)
+ .putExtra(
+ EXTRA_REFRESH_SAFETY_SOURCE_IDS,
+ new String[] {WearSafetySource.SAFETY_SOURCE_ID})
+ .putExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID, REFRESH_BROADCAST_ID);
+
+ new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent);
+ ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+ verify(mSafetyCenterManagerWrapper, times(1))
+ .setSafetySourceData(any(), captor.capture(), any(), any());
+
+ assertThat(captor.getValue()).isEqualTo(WearSafetySource.SAFETY_SOURCE_ID);
+ }
+
+ @Test
public void onReceive_onRefresh_withFingerprintUnlockSourceId_setsFingerprintUnlockData() {
when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
Intent intent =
@@ -332,7 +351,7 @@
new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent);
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
- verify(mSafetyCenterManagerWrapper, times(5))
+ verify(mSafetyCenterManagerWrapper, times(6))
.setSafetySourceData(any(), captor.capture(), any(), any());
List<String> safetySourceIdList = captor.getAllValues();
@@ -356,6 +375,11 @@
assertThat(
safetySourceIdList.stream()
.anyMatch(
+ id -> id.equals(WearSafetySource.SAFETY_SOURCE_ID)))
+ .isTrue();
+ assertThat(
+ safetySourceIdList.stream()
+ .anyMatch(
id -> id.equals(PrivateSpaceSafetySource.SAFETY_SOURCE_ID)))
.isTrue();
}
diff --git a/tests/unit/src/com/android/settings/safetycenter/WearSafetySourceTest.java b/tests/unit/src/com/android/settings/safetycenter/WearSafetySourceTest.java
new file mode 100644
index 0000000..c0c982d
--- /dev/null
+++ b/tests/unit/src/com/android/settings/safetycenter/WearSafetySourceTest.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2025 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.safetycenter;
+
+import static android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+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.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.UserHandle;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+import android.safetycenter.SafetyEvent;
+import android.safetycenter.SafetySourceData;
+import android.safetycenter.SafetySourceStatus;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils;
+import com.android.settings.flags.Flags;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.ResourcesUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+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 WearSafetySourceTest {
+
+ private static final ComponentName COMPONENT_NAME = new ComponentName("package", "class");
+ private static final UserHandle USER_HANDLE = new UserHandle(UserHandle.myUserId());
+ private static final SafetyEvent EVENT_SOURCE_STATE_CHANGED =
+ new SafetyEvent.Builder(SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build();
+ public static final String TARGET = "com.active.unlock.target";
+ public static final String PROVIDER = "com.active.unlock.provider";
+ public static final String TARGET_SETTING = "active_unlock_target";
+ public static final String PROVIDER_SETTING = "active_unlock_provider";
+ public static final String SUMMARY = "Wear Summary";
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ private Context mApplicationContext;
+
+ @Mock private PackageManager mPackageManager;
+ @Mock private DevicePolicyManager mDevicePolicyManager;
+ @Mock private FingerprintManager mFingerprintManager;
+ @Mock private LockPatternUtils mLockPatternUtils;
+ @Mock private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ 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);
+ FakeFeatureFactory featureFactory = FakeFeatureFactory.setupForTest();
+ when(featureFactory.securityFeatureProvider.getLockPatternUtils(mApplicationContext))
+ .thenReturn(mLockPatternUtils);
+ doReturn(true).when(mLockPatternUtils).isSecure(anyInt());
+ SafetyCenterManagerWrapper.sInstance = mSafetyCenterManagerWrapper;
+ }
+
+ @After
+ public void tearDown() {
+ SafetyCenterManagerWrapper.sInstance = null;
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void setSafetyData_whenSafetyCenterIsDisabled_doesNotSetData() {
+ when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(false);
+
+ WearSafetySource.setSafetySourceData(
+ mApplicationContext, EVENT_SOURCE_STATE_CHANGED);
+
+ verify(mSafetyCenterManagerWrapper, never())
+ .setSafetySourceData(any(), any(), any(), any());
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void setSafetySourceData_whenSeparateBiometricsFlagOff_setsNullData() {
+ when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
+
+ WearSafetySource.setSafetySourceData(
+ mApplicationContext, EVENT_SOURCE_STATE_CHANGED);
+
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(
+ any(), eq(WearSafetySource.SAFETY_SOURCE_ID), eq(null), any());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void setSafetySourceData_whenSafetyCenterIsEnabled_activeUnlockDisabled_setsNullData() {
+ disableActiveUnlock(mApplicationContext);
+ when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
+
+ WearSafetySource.setSafetySourceData(
+ mApplicationContext, EVENT_SOURCE_STATE_CHANGED);
+
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(
+ any(), eq(WearSafetySource.SAFETY_SOURCE_ID), eq(null), any());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void setSafetySourceData_setsDataWithCorrectSafetyEvent() {
+ when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
+
+ WearSafetySource.setSafetySourceData(
+ mApplicationContext, EVENT_SOURCE_STATE_CHANGED);
+
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(any(), any(), any(), eq(EVENT_SOURCE_STATE_CHANGED));
+ }
+
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void setSafetySourceData_withWearEnabled_whenWearEnrolled_setsData() {
+ enableActiveUnlock(mApplicationContext);
+ when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
+ when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+ when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(false);
+
+ WearSafetySource.setHasEnrolledForTesting(true);
+ WearSafetySource.setSummaryForTesting(SUMMARY);
+
+ WearSafetySource.setSafetySourceData(
+ mApplicationContext, EVENT_SOURCE_STATE_CHANGED);
+
+ assertSafetySourceEnabledDataSet(
+ ResourcesUtils.getResourcesString(mApplicationContext,
+ "security_settings_activeunlock"),
+ SUMMARY);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void setSafetySourceData_withWearEnabled_whenWearNotEnrolled_setsData() {
+ enableActiveUnlock(mApplicationContext);
+ when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
+ when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+ when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(false);
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)).thenReturn(0);
+
+ WearSafetySource.setHasEnrolledForTesting(false);
+ WearSafetySource.setSummaryForTesting(SUMMARY);
+
+ WearSafetySource.setSafetySourceData(
+ mApplicationContext, EVENT_SOURCE_STATE_CHANGED);
+
+ assertSafetySourceDisabledDataSet(
+ ResourcesUtils.getResourcesString(mApplicationContext,
+ "security_settings_activeunlock"),
+ SUMMARY);
+ }
+
+ private static void disableActiveUnlock(Context context) {
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_REMOTE_AUTH,
+ ActiveUnlockStatusUtils.CONFIG_FLAG_NAME,
+ /* value= */ null,
+ /* makeDefault=*/ false);
+ Settings.Secure.putString(context.getContentResolver(), TARGET_SETTING, null);
+ Settings.Secure.putString(context.getContentResolver(), PROVIDER_SETTING, null);
+ }
+
+ private static void enableActiveUnlock(Context context) {
+ Settings.Secure.putString(
+ context.getContentResolver(), TARGET_SETTING, TARGET);
+ Settings.Secure.putString(
+ context.getContentResolver(), PROVIDER_SETTING, PROVIDER);
+
+ PackageManager packageManager = context.getPackageManager();
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
+
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = new ActivityInfo();
+ resolveInfo.activityInfo.applicationInfo = applicationInfo;
+ when(packageManager.resolveActivity(any(), anyInt())).thenReturn(resolveInfo);
+
+ ProviderInfo providerInfo = new ProviderInfo();
+ providerInfo.authority = PROVIDER;
+ providerInfo.applicationInfo = applicationInfo;
+ when(packageManager.resolveContentProvider(anyString(), any())).thenReturn(providerInfo);
+
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_REMOTE_AUTH,
+ ActiveUnlockStatusUtils.CONFIG_FLAG_NAME,
+ "unlock_intent_layout",
+ false /* makeDefault */);
+ }
+
+ private void assertSafetySourceDisabledDataSet(String expectedTitle, String expectedSummary) {
+ ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(SafetySourceData.class);
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(
+ any(),
+ eq(WearSafetySource.SAFETY_SOURCE_ID),
+ captor.capture(),
+ any());
+ SafetySourceData safetySourceData = captor.getValue();
+ SafetySourceStatus safetySourceStatus = safetySourceData.getStatus();
+
+ assertThat(safetySourceStatus.getTitle().toString()).isEqualTo(expectedTitle);
+ assertThat(safetySourceStatus.getSummary().toString()).isEqualTo(expectedSummary);
+ assertThat(safetySourceStatus.isEnabled()).isTrue();
+ assertThat(safetySourceStatus.getSeverityLevel())
+ .isEqualTo(SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED);
+
+ Intent clickIntent = safetySourceStatus.getPendingIntent().getIntent();
+ assertThat(clickIntent).isNotNull();
+ assertThat(clickIntent.getAction()).isEqualTo(TARGET);
+ }
+
+ private void assertSafetySourceEnabledDataSet(
+ String expectedTitle, String expectedSummary) {
+ ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(SafetySourceData.class);
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(
+ any(),
+ eq(WearSafetySource.SAFETY_SOURCE_ID),
+ captor.capture(),
+ any());
+ SafetySourceData safetySourceData = captor.getValue();
+ SafetySourceStatus safetySourceStatus = safetySourceData.getStatus();
+
+ assertThat(safetySourceStatus.getTitle().toString()).isEqualTo(expectedTitle);
+ assertThat(safetySourceStatus.getSummary().toString()).isEqualTo(expectedSummary);
+ assertThat(safetySourceStatus.isEnabled()).isTrue();
+ assertThat(safetySourceStatus.getSeverityLevel())
+ .isEqualTo(SafetySourceData.SEVERITY_LEVEL_INFORMATION);
+ Intent clickIntent = safetySourceStatus.getPendingIntent().getIntent();
+ assertThat(clickIntent).isNotNull();
+ }
+}