Merge "Add WearSafetySource (Without Listener)" into main
This commit is contained in:
committed by
Android (Google) Code Review
commit
de048f3005
@@ -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]-->
|
||||
|
@@ -28,6 +28,7 @@ import android.os.RemoteException;
|
||||
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 @@ public class ActiveUnlockContentListener {
|
||||
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 @@ public class ActiveUnlockContentListener {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@@ -21,8 +21,8 @@ import android.content.Context;
|
||||
/** 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(
|
||||
|
@@ -155,10 +155,17 @@ public class ActiveUnlockStatusUtils {
|
||||
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.
|
||||
*/
|
||||
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 @@ public class ActiveUnlockStatusUtils {
|
||||
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) {
|
||||
|
@@ -21,8 +21,8 @@ import android.content.Context;
|
||||
/** 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(
|
||||
|
@@ -66,7 +66,7 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
|
||||
@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;
|
||||
|
||||
|
@@ -131,6 +131,7 @@ public final class LockScreenSafetySource {
|
||||
if (Flags.biometricsOnboardingEducation()) {
|
||||
FaceSafetySource.onBiometricsChanged(context);
|
||||
FingerprintSafetySource.onBiometricsChanged(context);
|
||||
WearSafetySource.onBiometricsChanged(context);
|
||||
} else {
|
||||
BiometricsSafetySource.onBiometricsChanged(context);
|
||||
}
|
||||
|
@@ -86,6 +86,9 @@ public class SafetySourceBroadcastReceiver extends BroadcastReceiver {
|
||||
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 @@ public class SafetySourceBroadcastReceiver extends BroadcastReceiver {
|
||||
PrivateSpaceSafetySource.setSafetySourceData(context, safetyEvent);
|
||||
FaceSafetySource.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(
|
||||
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)
|
||||
.setSafetySourceData(
|
||||
any(), eq(FingerprintSafetySource.SAFETY_SOURCE_ID), any(), any());
|
||||
verify(mSafetyCenterManagerWrapper)
|
||||
.setSafetySourceData(
|
||||
any(), eq(WearSafetySource.SAFETY_SOURCE_ID), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@@ -245,6 +245,25 @@ public class SafetySourceBroadcastReceiverTest {
|
||||
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
|
||||
public void onReceive_onRefresh_withFingerprintUnlockSourceId_setsFingerprintUnlockData() {
|
||||
when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
|
||||
@@ -332,7 +351,7 @@ public class SafetySourceBroadcastReceiverTest {
|
||||
|
||||
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();
|
||||
|
||||
@@ -353,6 +372,11 @@ public class SafetySourceBroadcastReceiverTest {
|
||||
.anyMatch(
|
||||
id -> id.equals(FingerprintSafetySource.SAFETY_SOURCE_ID)))
|
||||
.isTrue();
|
||||
assertThat(
|
||||
safetySourceIdList.stream()
|
||||
.anyMatch(
|
||||
id -> id.equals(WearSafetySource.SAFETY_SOURCE_ID)))
|
||||
.isTrue();
|
||||
assertThat(
|
||||
safetySourceIdList.stream()
|
||||
.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