Add WearSafetySource (Without Listener)
Bug: 389841524 Test: TreeHugger Test: manual Test: atest SafetySourceBroadcastReceiverTest Test: atest LockScreenSafetySourceTest Test: atest ActiveUnlockStatusUtilsTest Test: atest WearSafetySourceTest Flag: com.android.settings.flags.biometrics_onboarding_education Change-Id: I7f4b41bf33d1e0fb7988f756a466e4d80bcec25e
This commit is contained in:
@@ -1054,6 +1054,7 @@
|
|||||||
<!-- Watch unlock enrollment and settings --><skip />
|
<!-- Watch unlock enrollment and settings --><skip />
|
||||||
<!-- Title shown for menu item that launches watch unlock settings. [CHAR LIMIT=40] -->
|
<!-- 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_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]-->
|
<!-- 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>
|
<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]-->
|
<!-- Introduction shown in fingerprint page to explain that watch unlock can be used if fingerprint isn't recognized. [CHAR LIMIT=NONE]-->
|
||||||
|
@@ -28,6 +28,7 @@ import android.os.RemoteException;
|
|||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.android.settingslib.utils.ThreadUtils;
|
import com.android.settingslib.utils.ThreadUtils;
|
||||||
@@ -76,17 +77,22 @@ public class ActiveUnlockContentListener {
|
|||||||
mContentKey = contentKey;
|
mContentKey = contentKey;
|
||||||
String authority = new ActiveUnlockStatusUtils(mContext).getAuthority();
|
String authority = new ActiveUnlockStatusUtils(mContext).getAuthority();
|
||||||
if (authority != null) {
|
if (authority != null) {
|
||||||
mUri = new Uri.Builder()
|
mUri = getUri(authority);
|
||||||
.scheme(ContentResolver.SCHEME_CONTENT)
|
|
||||||
.authority(authority)
|
|
||||||
.appendPath(CONTENT_PROVIDER_PATH)
|
|
||||||
.build();
|
|
||||||
} else {
|
} else {
|
||||||
mUri = null;
|
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. */
|
/** Returns true if start listening for updates from the ContentProvider, false otherwise. */
|
||||||
public synchronized boolean subscribe() {
|
public synchronized boolean subscribe() {
|
||||||
if (mSubscribed || mUri == null) {
|
if (mSubscribed || mUri == null) {
|
||||||
@@ -123,25 +129,40 @@ public class ActiveUnlockContentListener {
|
|||||||
Log.e(mLogTag, "Uri null when trying to fetch content");
|
Log.e(mLogTag, "Uri null when trying to fetch content");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ContentResolver contentResolver = mContext.getContentResolver();
|
|
||||||
ContentProviderClient client = contentResolver.acquireContentProviderClient(mUri);
|
@Nullable String newValue = getContentFromUri(
|
||||||
Bundle bundle;
|
mContext, mUri, mLogTag, mMethodName, mContentKey);
|
||||||
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);
|
|
||||||
if (!TextUtils.equals(mContent, newValue)) {
|
if (!TextUtils.equals(mContent, newValue)) {
|
||||||
mContent = newValue;
|
mContent = newValue;
|
||||||
mContentChangedListener.onContentChanged(mContent);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -21,8 +21,8 @@ import android.content.Context;
|
|||||||
/** Listens to device name updates from the content provider and fetches the latest value. */
|
/** Listens to device name updates from the content provider and fetches the latest value. */
|
||||||
public class ActiveUnlockDeviceNameListener {
|
public class ActiveUnlockDeviceNameListener {
|
||||||
private static final String TAG = "ActiveUnlockDeviceNameListener";
|
private static final String TAG = "ActiveUnlockDeviceNameListener";
|
||||||
private static final String METHOD_NAME = "getDeviceName";
|
static final String METHOD_NAME = "getDeviceName";
|
||||||
private static final String DEVICE_NAME_KEY = "com.android.settings.active_unlock.device_name";
|
static final String DEVICE_NAME_KEY = "com.android.settings.active_unlock.device_name";
|
||||||
|
|
||||||
private final ActiveUnlockContentListener mActiveUnlockContentListener;
|
private final ActiveUnlockContentListener mActiveUnlockContentListener;
|
||||||
public ActiveUnlockDeviceNameListener(
|
public ActiveUnlockDeviceNameListener(
|
||||||
|
@@ -155,10 +155,17 @@ public class ActiveUnlockStatusUtils {
|
|||||||
return BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
|
return BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
* 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 faceAllowed = Utils.hasFaceHardware(mContext);
|
||||||
final boolean fingerprintAllowed = Utils.hasFingerprintHardware(mContext);
|
final boolean fingerprintAllowed = Utils.hasFingerprintHardware(mContext);
|
||||||
return mContext.getString(getTitleRes(faceAllowed, fingerprintAllowed));
|
return mContext.getString(getTitleRes(faceAllowed, fingerprintAllowed));
|
||||||
@@ -264,6 +271,30 @@ public class ActiveUnlockStatusUtils {
|
|||||||
return mContext.getString(getUseBiometricTitleRes(faceAllowed, fingerprintAllowed));
|
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
|
@StringRes
|
||||||
private static int getUseBiometricTitleRes(
|
private static int getUseBiometricTitleRes(
|
||||||
boolean isFaceAllowed, boolean isFingerprintAllowed) {
|
boolean isFaceAllowed, boolean isFingerprintAllowed) {
|
||||||
|
@@ -21,8 +21,8 @@ import android.content.Context;
|
|||||||
/** Listens to summary updates from the content provider and fetches the latest value. */
|
/** Listens to summary updates from the content provider and fetches the latest value. */
|
||||||
public class ActiveUnlockSummaryListener {
|
public class ActiveUnlockSummaryListener {
|
||||||
private static final String TAG = "ActiveUnlockSummaryListener";
|
private static final String TAG = "ActiveUnlockSummaryListener";
|
||||||
private static final String METHOD_NAME = "getSummary";
|
static final String METHOD_NAME = "getSummary";
|
||||||
private static final String SUMMARY_KEY = "com.android.settings.summary";
|
static final String SUMMARY_KEY = "com.android.settings.summary";
|
||||||
|
|
||||||
private final ActiveUnlockContentListener mContentListener;
|
private final ActiveUnlockContentListener mContentListener;
|
||||||
public ActiveUnlockSummaryListener(
|
public ActiveUnlockSummaryListener(
|
||||||
|
@@ -66,7 +66,7 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
|
|||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final int CONFIRM_REQUEST = 2001;
|
static final int CONFIRM_REQUEST = 2001;
|
||||||
private static final int CHOOSE_LOCK_REQUEST = 2002;
|
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
|
@VisibleForTesting
|
||||||
static final int BIOMETRIC_AUTH_REQUEST = 2004;
|
static final int BIOMETRIC_AUTH_REQUEST = 2004;
|
||||||
|
|
||||||
|
@@ -131,6 +131,7 @@ public final class LockScreenSafetySource {
|
|||||||
if (Flags.biometricsOnboardingEducation()) {
|
if (Flags.biometricsOnboardingEducation()) {
|
||||||
FaceSafetySource.onBiometricsChanged(context);
|
FaceSafetySource.onBiometricsChanged(context);
|
||||||
FingerprintSafetySource.onBiometricsChanged(context);
|
FingerprintSafetySource.onBiometricsChanged(context);
|
||||||
|
WearSafetySource.onBiometricsChanged(context);
|
||||||
} else {
|
} else {
|
||||||
BiometricsSafetySource.onBiometricsChanged(context);
|
BiometricsSafetySource.onBiometricsChanged(context);
|
||||||
}
|
}
|
||||||
|
@@ -86,6 +86,9 @@ public class SafetySourceBroadcastReceiver extends BroadcastReceiver {
|
|||||||
if (sourceIds.contains(FingerprintSafetySource.SAFETY_SOURCE_ID)) {
|
if (sourceIds.contains(FingerprintSafetySource.SAFETY_SOURCE_ID)) {
|
||||||
FingerprintSafetySource.setSafetySourceData(context, safetyEvent);
|
FingerprintSafetySource.setSafetySourceData(context, safetyEvent);
|
||||||
}
|
}
|
||||||
|
if (sourceIds.contains(WearSafetySource.SAFETY_SOURCE_ID)) {
|
||||||
|
WearSafetySource.setSafetySourceData(context, safetyEvent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void refreshAllSafetySources(Context context, SafetyEvent safetyEvent) {
|
private static void refreshAllSafetySources(Context context, SafetyEvent safetyEvent) {
|
||||||
@@ -95,5 +98,6 @@ public class SafetySourceBroadcastReceiver extends BroadcastReceiver {
|
|||||||
PrivateSpaceSafetySource.setSafetySourceData(context, safetyEvent);
|
PrivateSpaceSafetySource.setSafetySourceData(context, safetyEvent);
|
||||||
FaceSafetySource.setSafetySourceData(context, safetyEvent);
|
FaceSafetySource.setSafetySourceData(context, safetyEvent);
|
||||||
FingerprintSafetySource.setSafetySourceData(context, safetyEvent);
|
FingerprintSafetySource.setSafetySourceData(context, safetyEvent);
|
||||||
|
WearSafetySource.setSafetySourceData(context, safetyEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
147
src/com/android/settings/safetycenter/WearSafetySource.java
Normal file
147
src/com/android/settings/safetycenter/WearSafetySource.java
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -243,4 +243,11 @@ public class ActiveUnlockStatusUtilsTest {
|
|||||||
.isEqualTo(mApplicationContext.getString(
|
.isEqualTo(mApplicationContext.getString(
|
||||||
R.string.biometric_settings_use_watch_for));
|
R.string.biometric_settings_use_watch_for));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getTitleForActiveUnlockOnly_returnsTile() {
|
||||||
|
assertThat(mActiveUnlockStatusUtils.getTitleForActiveUnlockOnly())
|
||||||
|
.isEqualTo(mApplicationContext.getString(
|
||||||
|
R.string.security_settings_activeunlock));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -527,6 +527,9 @@ public class LockScreenSafetySourceTest {
|
|||||||
verify(mSafetyCenterManagerWrapper)
|
verify(mSafetyCenterManagerWrapper)
|
||||||
.setSafetySourceData(
|
.setSafetySourceData(
|
||||||
any(), eq(FingerprintSafetySource.SAFETY_SOURCE_ID), any(), any());
|
any(), eq(FingerprintSafetySource.SAFETY_SOURCE_ID), any(), any());
|
||||||
|
verify(mSafetyCenterManagerWrapper)
|
||||||
|
.setSafetySourceData(
|
||||||
|
any(), eq(WearSafetySource.SAFETY_SOURCE_ID), any(), any());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@@ -245,6 +245,25 @@ public class SafetySourceBroadcastReceiverTest {
|
|||||||
assertThat(captor.getValue()).isEqualTo(FaceSafetySource.SAFETY_SOURCE_ID);
|
assertThat(captor.getValue()).isEqualTo(FaceSafetySource.SAFETY_SOURCE_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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
|
@Test
|
||||||
public void onReceive_onRefresh_withFingerprintUnlockSourceId_setsFingerprintUnlockData() {
|
public void onReceive_onRefresh_withFingerprintUnlockSourceId_setsFingerprintUnlockData() {
|
||||||
when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
|
when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
|
||||||
@@ -332,7 +351,7 @@ public class SafetySourceBroadcastReceiverTest {
|
|||||||
|
|
||||||
new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent);
|
new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent);
|
||||||
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
|
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
|
||||||
verify(mSafetyCenterManagerWrapper, times(5))
|
verify(mSafetyCenterManagerWrapper, times(6))
|
||||||
.setSafetySourceData(any(), captor.capture(), any(), any());
|
.setSafetySourceData(any(), captor.capture(), any(), any());
|
||||||
List<String> safetySourceIdList = captor.getAllValues();
|
List<String> safetySourceIdList = captor.getAllValues();
|
||||||
|
|
||||||
@@ -353,6 +372,11 @@ public class SafetySourceBroadcastReceiverTest {
|
|||||||
.anyMatch(
|
.anyMatch(
|
||||||
id -> id.equals(FingerprintSafetySource.SAFETY_SOURCE_ID)))
|
id -> id.equals(FingerprintSafetySource.SAFETY_SOURCE_ID)))
|
||||||
.isTrue();
|
.isTrue();
|
||||||
|
assertThat(
|
||||||
|
safetySourceIdList.stream()
|
||||||
|
.anyMatch(
|
||||||
|
id -> id.equals(WearSafetySource.SAFETY_SOURCE_ID)))
|
||||||
|
.isTrue();
|
||||||
assertThat(
|
assertThat(
|
||||||
safetySourceIdList.stream()
|
safetySourceIdList.stream()
|
||||||
.anyMatch(
|
.anyMatch(
|
||||||
|
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user