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();
+    }
+}