diff --git a/res/values/strings.xml b/res/values/strings.xml index 31fb9e2cbb9..740629550aa 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1208,8 +1208,22 @@ Private Space Hide apps in a private folder + + Hide apps in a private folder that only you can access - Unlock using screen lock + Private Space lock + + You can unlock Private Space the same way you unlock your device, or choose a different lock + + Use device screen lock + + Face & Fingerprint Unlock + + Tap to set up + + Same as device screen lock + + Choose a new lock for Private Space? Hide when locked diff --git a/res/xml/private_space_settings.xml b/res/xml/private_space_settings.xml index eb9fdae4ac5..48835fc8bec 100644 --- a/res/xml/private_space_settings.xml +++ b/res/xml/private_space_settings.xml @@ -22,13 +22,25 @@ android:title="@string/private_space_title" settings:searchable="false"> + + + + - - \ No newline at end of file + diff --git a/res/xml/privatespace_one_lock.xml b/res/xml/privatespace_one_lock.xml new file mode 100644 index 00000000000..e078c17b002 --- /dev/null +++ b/res/xml/privatespace_one_lock.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + diff --git a/src/com/android/settings/privatespace/UseOneLockController.java b/src/com/android/settings/privatespace/UseOneLockController.java deleted file mode 100644 index a94db57a814..00000000000 --- a/src/com/android/settings/privatespace/UseOneLockController.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2023 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.privatespace; - -import android.content.Context; - -import com.android.settings.core.TogglePreferenceController; - -/** Represents the preference controller for using the same lock as the screen lock */ -public class UseOneLockController extends TogglePreferenceController { - public UseOneLockController(Context context, String preferenceKey) { - super(context, preferenceKey); - } - - @Override - public int getAvailabilityStatus() { - return AVAILABLE; - } - - @Override - public boolean isChecked() { - // TODO(b/293569406) Need to save this to a persistent store, maybe like SettingsProvider - return false; - } - - @Override - public boolean setChecked(boolean isChecked) { - // TODO(b/293569406) Need to save this to a persistent store, maybe like SettingsProvider - return true; - } - - @Override - public int getSliceHighlightMenuRes() { - return 0; - } -} diff --git a/src/com/android/settings/privatespace/onelock/FaceFingerprintUnlockController.java b/src/com/android/settings/privatespace/onelock/FaceFingerprintUnlockController.java new file mode 100644 index 00000000000..e97626118d0 --- /dev/null +++ b/src/com/android/settings/privatespace/onelock/FaceFingerprintUnlockController.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2023 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.privatespace.onelock; + +import android.content.Context; +import android.text.TextUtils; + +import androidx.preference.Preference; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settingslib.core.AbstractPreferenceController; + +/** Represents the preference controller to enroll biometrics for private space lock. */ +public class FaceFingerprintUnlockController extends AbstractPreferenceController { + private static final String KEY_SET_UNSET_FACE_FINGERPRINT = "private_space_biometrics"; + + public FaceFingerprintUnlockController(Context context, SettingsPreferenceFragment host) { + super(context); + } + + @Override + public boolean isAvailable() { + return false; + } + + @Override + public String getPreferenceKey() { + return KEY_SET_UNSET_FACE_FINGERPRINT; + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + return TextUtils.equals(preference.getKey(), getPreferenceKey()); + } + + @Override + public void updateState(Preference preference) { + //TODO(b/308862923) : Add condition to check and enable when separate private lock is set. + preference.setSummary(mContext.getString(R.string.lock_settings_profile_unified_summary)); + preference.setEnabled(false); + } +} diff --git a/src/com/android/settings/privatespace/onelock/PrivateSpaceLockController.java b/src/com/android/settings/privatespace/onelock/PrivateSpaceLockController.java new file mode 100644 index 00000000000..2783c1c2816 --- /dev/null +++ b/src/com/android/settings/privatespace/onelock/PrivateSpaceLockController.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2023 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.privatespace.onelock; + +import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; +import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN; +import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN; +import static com.android.settings.password.ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.UserHandle; +import android.os.UserManager; +import android.text.TextUtils; +import android.util.Log; + +import androidx.preference.Preference; + +import com.android.internal.widget.LockPatternUtils; +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.Utils; +import com.android.settings.core.SubSettingLauncher; +import com.android.settings.overlay.FeatureFactory; +import com.android.settings.password.ChooseLockGeneric; +import com.android.settings.privatespace.PrivateSpaceMaintainer; +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.transition.SettingsTransitionHelper; + + +/** Represents the preference controller for changing private space lock. */ +public class PrivateSpaceLockController extends AbstractPreferenceController { + private static final String TAG = "PrivateSpaceLockContr"; + private static final String KEY_CHANGE_PROFILE_LOCK = + "change_private_space_lock"; + + private final SettingsPreferenceFragment mHost; + private final UserManager mUserManager; + private final LockPatternUtils mLockPatternUtils; + private final int mProfileUserId; + + public PrivateSpaceLockController(Context context, SettingsPreferenceFragment host) { + super(context); + mUserManager = context.getSystemService(UserManager.class); + mLockPatternUtils = FeatureFactory.getFeatureFactory() + .getSecurityFeatureProvider() + .getLockPatternUtils(context); + mHost = host; + UserHandle privateProfileHandle = PrivateSpaceMaintainer.getInstance(context) + .getPrivateProfileHandle(); + if (privateProfileHandle != null) { + mProfileUserId = privateProfileHandle.getIdentifier(); + } else { + mProfileUserId = -1; + Log.e(TAG, "Private profile user handle is not expected to be null."); + } + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public String getPreferenceKey() { + return KEY_CHANGE_PROFILE_LOCK; + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) { + return false; + } + //Checks if the profile is in quiet mode and show a dialog to unpause the profile. + if (Utils.startQuietModeDialogIfNecessary(mContext, mUserManager, + mProfileUserId)) { + return false; + } + final Bundle extras = new Bundle(); + extras.putInt(Intent.EXTRA_USER_ID, mProfileUserId); + extras.putBoolean(HIDE_INSECURE_OPTIONS, true); + new SubSettingLauncher(mContext) + .setDestination(ChooseLockGeneric.ChooseLockGenericFragment.class.getName()) + .setSourceMetricsCategory(mHost.getMetricsCategory()) + .setArguments(extras) + .setExtras(extras) + .setTransitionType(SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE) + .launch(); + return true; + } + + @Override + public void updateState(Preference preference) { + if (mLockPatternUtils.isSeparateProfileChallengeEnabled(mProfileUserId)) { + preference.setSummary( + mContext.getString(getCredentialTypeResId(mProfileUserId))); + preference.setEnabled(true); + } else { + preference.setSummary(mContext.getString( + R.string.lock_settings_profile_unified_summary)); + preference.setEnabled(false); + } + } + + private int getCredentialTypeResId(int userId) { + int credentialType = mLockPatternUtils.getCredentialTypeForUser(userId); + switch (credentialType) { + case CREDENTIAL_TYPE_PATTERN : + return R.string.unlock_set_unlock_mode_pattern; + case CREDENTIAL_TYPE_PIN: + return R.string.unlock_set_unlock_mode_pin; + case CREDENTIAL_TYPE_PASSWORD: + return R.string.unlock_set_unlock_mode_password; + default: + // This is returned for CREDENTIAL_TYPE_NONE + return R.string.unlock_set_unlock_mode_off; + } + } +} diff --git a/src/com/android/settings/privatespace/onelock/UseOneLockController.java b/src/com/android/settings/privatespace/onelock/UseOneLockController.java new file mode 100644 index 00000000000..5c461e00f5d --- /dev/null +++ b/src/com/android/settings/privatespace/onelock/UseOneLockController.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2023 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.privatespace.onelock; + +import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; +import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN; +import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN; + +import android.content.Context; +import android.os.UserHandle; +import android.util.Log; + +import com.android.internal.widget.LockPatternUtils; +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.overlay.FeatureFactory; +import com.android.settings.privatespace.PrivateSpaceMaintainer; + +/** Represents the preference controller for using the same lock as the screen lock */ +public class UseOneLockController extends BasePreferenceController { + private static final String TAG = "UseOneLockController"; + private final LockPatternUtils mLockPatternUtils; + private final PrivateSpaceMaintainer mPrivateSpaceMaintainer; + + public UseOneLockController(Context context, String preferenceKey) { + super(context, preferenceKey); + mPrivateSpaceMaintainer = PrivateSpaceMaintainer.getInstance(mContext); + mLockPatternUtils = FeatureFactory.getFeatureFactory() + .getSecurityFeatureProvider() + .getLockPatternUtils(context); + } + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public int getSliceHighlightMenuRes() { + return 0; + } + + @Override + public CharSequence getSummary() { + UserHandle privateProfileHandle = mPrivateSpaceMaintainer.getPrivateProfileHandle(); + if (privateProfileHandle != null) { + int privateUserId = privateProfileHandle.getIdentifier(); + if (mLockPatternUtils.isSeparateProfileChallengeEnabled(privateUserId)) { + return mContext.getString(getCredentialTypeResId(privateUserId)); + } + } else { + Log.w(TAG, "Did not find Private Space."); + } + return mContext.getString(R.string.private_space_screen_lock_summary); + } + + private int getCredentialTypeResId(int userId) { + int credentialType = mLockPatternUtils.getCredentialTypeForUser(userId); + switch (credentialType) { + case CREDENTIAL_TYPE_PATTERN: + return R.string.unlock_set_unlock_mode_pattern; + case CREDENTIAL_TYPE_PIN: + return R.string.unlock_set_unlock_mode_pin; + case CREDENTIAL_TYPE_PASSWORD: + return R.string.unlock_set_unlock_mode_password; + default: + // This is returned for CREDENTIAL_TYPE_NONE + return R.string.unlock_set_unlock_mode_off; + } + } +} diff --git a/src/com/android/settings/privatespace/onelock/UseOneLockControllerSwitch.java b/src/com/android/settings/privatespace/onelock/UseOneLockControllerSwitch.java new file mode 100644 index 00000000000..218b8705750 --- /dev/null +++ b/src/com/android/settings/privatespace/onelock/UseOneLockControllerSwitch.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2023 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.privatespace.onelock; + +import static com.android.settings.privatespace.PrivateSpaceSetupActivity.EXTRA_ACTION_TYPE; +import static com.android.settings.privatespace.PrivateSpaceSetupActivity.SET_LOCK_ACTION; +import static com.android.settings.privatespace.onelock.UseOneLockSettingsFragment.UNIFY_PRIVATE_LOCK_WITH_DEVICE_REQUEST; +import static com.android.settings.privatespace.onelock.UseOneLockSettingsFragment.UNUNIFY_PRIVATE_LOCK_FROM_DEVICE_REQUEST; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.Log; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.LockscreenCredential; +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.Utils; +import com.android.settings.core.SubSettingLauncher; +import com.android.settings.overlay.FeatureFactory; +import com.android.settings.password.ChooseLockGeneric; +import com.android.settings.password.ChooseLockSettingsHelper; +import com.android.settings.privatespace.PrivateProfileContextHelperActivity; +import com.android.settings.privatespace.PrivateSpaceMaintainer; +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.transition.SettingsTransitionHelper; +import com.android.settingslib.widget.MainSwitchPreference; + +/** Represents the preference controller for using the same lock as the screen lock */ +public class UseOneLockControllerSwitch extends AbstractPreferenceController + implements Preference.OnPreferenceChangeListener { + private static final String TAG = "UseOneLockSwitch"; + private static final String KEY_UNIFICATION = "private_lock_unification"; + private final String mPreferenceKey; + private final SettingsPreferenceFragment mHost; + private final LockPatternUtils mLockPatternUtils; + private final UserManager mUserManager; + private final int mProfileUserId; + private final UserHandle mUserHandle; + private LockscreenCredential mCurrentDevicePassword; + private LockscreenCredential mCurrentProfilePassword; + private MainSwitchPreference mUnifyProfile; + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mUnifyProfile = screen.findPreference(mPreferenceKey); + } + public UseOneLockControllerSwitch(Context context, SettingsPreferenceFragment host) { + this(context, host, KEY_UNIFICATION); + } + + public UseOneLockControllerSwitch(Context context, SettingsPreferenceFragment host, + String key) { + super(context); + mHost = host; + mUserManager = context.getSystemService(UserManager.class); + mLockPatternUtils = FeatureFactory.getFeatureFactory().getSecurityFeatureProvider() + .getLockPatternUtils(context); + mUserHandle = PrivateSpaceMaintainer.getInstance(context).getPrivateProfileHandle(); + mProfileUserId = mUserHandle != null ? mUserHandle.getIdentifier() : -1; + mCurrentDevicePassword = LockscreenCredential.createNone(); + mCurrentProfilePassword = LockscreenCredential.createNone(); + this.mPreferenceKey = key; + } + + @Override + public String getPreferenceKey() { + return mPreferenceKey; + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public boolean onPreferenceChange(Preference preference, Object value) { + //Checks if the profile is in quiet mode and show a dialog to unpause the profile. + if (Utils.startQuietModeDialogIfNecessary(mContext, mUserManager, mProfileUserId)) { + return false; + } + final boolean useOneLock = (Boolean) value; + if (useOneLock) { + startUnification(); + } else { + showAlertDialog(); + } + return true; + } + + @Override + public void updateState(Preference preference) { + if (mUnifyProfile != null) { + final boolean separate = + mLockPatternUtils.isSeparateProfileChallengeEnabled(mProfileUserId); + mUnifyProfile.setChecked(!separate); + } + } + + /** Method to handle onActivityResult */ + public boolean handleActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == UNUNIFY_PRIVATE_LOCK_FROM_DEVICE_REQUEST + && resultCode == Activity.RESULT_OK) { + mCurrentDevicePassword = + data.getParcelableExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); + separateLocks(); + return true; + } else if (requestCode == UNIFY_PRIVATE_LOCK_WITH_DEVICE_REQUEST + && resultCode == Activity.RESULT_OK) { + mCurrentProfilePassword = + data.getParcelableExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); + unifyLocks(); + return true; + } + return false; + } + + private void separateLocks() { + final Bundle extras = new Bundle(); + extras.putInt(Intent.EXTRA_USER_ID, mProfileUserId); + extras.putParcelable(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, mCurrentDevicePassword); + new SubSettingLauncher(mContext) + .setDestination(ChooseLockGeneric.ChooseLockGenericFragment.class.getName()) + .setSourceMetricsCategory(mHost.getMetricsCategory()) + .setArguments(extras) + .setTransitionType(SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE) + .launch(); + } + + /** Unify primary and profile locks. */ + public void startUnification() { + // Confirm profile lock + final ChooseLockSettingsHelper.Builder builder = + new ChooseLockSettingsHelper.Builder(mHost.getActivity(), mHost); + final boolean launched = builder.setRequestCode(UNIFY_PRIVATE_LOCK_WITH_DEVICE_REQUEST) + .setReturnCredentials(true) + .setUserId(mProfileUserId) + .show(); + if (!launched) { + // If profile has no lock, go straight to unification. + unifyLocks(); + } + } + + private void unifyLocks() { + unifyKeepingDeviceLock(); + if (mCurrentDevicePassword != null) { + mCurrentDevicePassword.zeroize(); + mCurrentDevicePassword = null; + } + if (mCurrentProfilePassword != null) { + mCurrentProfilePassword.zeroize(); + mCurrentProfilePassword = null; + } + } + + private void unifyKeepingDeviceLock() { + mLockPatternUtils.setSeparateProfileChallengeEnabled(mProfileUserId, false, + mCurrentProfilePassword); + } + + private void showAlertDialog() { + if (mUserHandle == null) { + Log.e(TAG, "Private profile user handle is not expected to be null"); + mUnifyProfile.setChecked(true); + return; + } + new AlertDialog.Builder(mContext) + .setMessage(R.string.private_space_new_lock_title) + .setPositiveButton( + R.string.privatespace_set_lock_label, + (dialog, which) -> { + Intent intent = new Intent(mContext, + PrivateProfileContextHelperActivity.class); + intent.putExtra(EXTRA_ACTION_TYPE, SET_LOCK_ACTION); + ((Activity) mContext).startActivityForResultAsUser(intent, + UNUNIFY_PRIVATE_LOCK_FROM_DEVICE_REQUEST, + /*Options*/ null, mUserHandle); + }) + .setNegativeButton(R.string.privatespace_cancel_label, + (DialogInterface dialog, int which) -> { + mUnifyProfile.setChecked(true); + dialog.dismiss(); + }) + .setOnCancelListener( + (DialogInterface dialog) -> { + mUnifyProfile.setChecked(true); + dialog.dismiss(); + }) + .show(); + } +} diff --git a/src/com/android/settings/privatespace/onelock/UseOneLockSettingsFragment.java b/src/com/android/settings/privatespace/onelock/UseOneLockSettingsFragment.java new file mode 100644 index 00000000000..36f84489118 --- /dev/null +++ b/src/com/android/settings/privatespace/onelock/UseOneLockSettingsFragment.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2023 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.privatespace.onelock; + +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.content.Intent; + +import androidx.annotation.Nullable; + +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settingslib.core.AbstractPreferenceController; + +import java.util.ArrayList; +import java.util.List; + +public class UseOneLockSettingsFragment extends DashboardFragment { + private static final String TAG = "UseOneLockSettings"; + public static final int UNIFY_PRIVATE_LOCK_WITH_DEVICE_REQUEST = 1; + public static final int UNUNIFY_PRIVATE_LOCK_FROM_DEVICE_REQUEST = 2; + + @Override + public int getMetricsCategory() { + return SettingsEnums.PRIVATE_SPACE_SETTINGS; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.privatespace_one_lock; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected List createPreferenceControllers(Context context) { + final List controllers = new ArrayList<>(); + controllers.add(new UseOneLockControllerSwitch(context, this)); + controllers.add(new PrivateSpaceLockController(context, this)); + controllers.add(new FaceFingerprintUnlockController(context, this)); + return controllers; + } + + @Override + public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + if (use(UseOneLockControllerSwitch.class) + .handleActivityResult(requestCode, resultCode, data)) { + return; + } + super.onActivityResult(requestCode, resultCode, data); + } +} diff --git a/tests/unit/src/com/android/settings/privatespace/PrivateSpaceLockControllerTest.java b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceLockControllerTest.java new file mode 100644 index 00000000000..0d9db7e477b --- /dev/null +++ b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceLockControllerTest.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2023 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.privatespace; + +import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; +import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN; +import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.Flags; +import android.platform.test.flag.junit.SetFlagsRule; + +import androidx.preference.Preference; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.internal.widget.LockPatternUtils; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.privatespace.onelock.PrivateSpaceLockController; +import com.android.settings.testutils.FakeFeatureFactory; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class PrivateSpaceLockControllerTest { + @Mock + private Context mContext; + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + @Mock SettingsPreferenceFragment mSettingsPreferenceFragment; + @Mock + LockPatternUtils mLockPatternUtils; + + private Preference mPreference; + private PrivateSpaceLockController mPrivateSpaceLockController; + + /** Required setup before a test. */ + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = ApplicationProvider.getApplicationContext(); + final String preferenceKey = "unlock_set_or_change_private_lock"; + + mPreference = new Preference(ApplicationProvider.getApplicationContext()); + mPreference.setKey(preferenceKey); + + final FakeFeatureFactory featureFactory = FakeFeatureFactory.setupForTest(); + when(featureFactory.securityFeatureProvider.getLockPatternUtils(mContext)) + .thenReturn(mLockPatternUtils); + + mPrivateSpaceLockController = new PrivateSpaceLockController(mContext, + mSettingsPreferenceFragment); + } + + /** Tests that the controller is always available. */ + @Test + public void getAvailabilityStatus_returnsAvailable() { + mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE); + + assertThat(mPrivateSpaceLockController.isAvailable()).isEqualTo(true); + } + + /** Tests that preference is disabled and summary says same as device lock. */ + @Test + public void getSummary_whenScreenLock() { + doReturn(false).when(mLockPatternUtils).isSeparateProfileChallengeEnabled(anyInt()); + mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE); + + mPrivateSpaceLockController.updateState(mPreference); + assertThat(mPreference.isEnabled()).isFalse(); + assertThat(mPreference.getSummary().toString()).isEqualTo("Same as device screen lock"); + } + + /** Tests that preference is enabled and summary is Pattern. */ + @Test + public void getSummary_whenProfileLockPattern() { + doReturn(true) + .when(mLockPatternUtils).isSeparateProfileChallengeEnabled(anyInt()); + doReturn(CREDENTIAL_TYPE_PATTERN) + .when(mLockPatternUtils).getCredentialTypeForUser(anyInt()); + mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE); + + mPrivateSpaceLockController.updateState(mPreference); + assertThat(mPreference.isEnabled()).isTrue(); + assertThat(mPreference.getSummary().toString()).isEqualTo("Pattern"); + } + + /** Tests that preference is enabled and summary is Pin. */ + @Test + public void getSummary_whenProfileLockPin() { + doReturn(true).when(mLockPatternUtils).isSeparateProfileChallengeEnabled(anyInt()); + doReturn(CREDENTIAL_TYPE_PIN).when(mLockPatternUtils).getCredentialTypeForUser(anyInt()); + mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE); + + mPrivateSpaceLockController.updateState(mPreference); + assertThat(mPreference.isEnabled()).isTrue(); + assertThat(mPreference.getSummary().toString()).isEqualTo("PIN"); + } + + /** Tests that preference is enabled and summary is Password. */ + @Test + public void getSummary_whenProfileLockPassword() { + doReturn(true) + .when(mLockPatternUtils).isSeparateProfileChallengeEnabled(anyInt()); + doReturn(CREDENTIAL_TYPE_PASSWORD) + .when(mLockPatternUtils).getCredentialTypeForUser(anyInt()); + mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE); + + mPrivateSpaceLockController.updateState(mPreference); + assertThat(mPreference.isEnabled()).isTrue(); + assertThat(mPreference.getSummary().toString()).isEqualTo("Password"); + } +} diff --git a/tests/unit/src/com/android/settings/privatespace/UseOneLockControllerTest.java b/tests/unit/src/com/android/settings/privatespace/UseOneLockControllerTest.java index e7ebb37faf6..744a8ec8882 100644 --- a/tests/unit/src/com/android/settings/privatespace/UseOneLockControllerTest.java +++ b/tests/unit/src/com/android/settings/privatespace/UseOneLockControllerTest.java @@ -16,36 +16,105 @@ package com.android.settings.privatespace; +import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; +import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN; +import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN; import static com.android.settings.core.BasePreferenceController.AVAILABLE; + import static com.google.common.truth.Truth.assertThat; -import android.content.Context; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.when; +import android.content.Context; +import android.os.Flags; +import android.platform.test.flag.junit.SetFlagsRule; + +import androidx.preference.Preference; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.android.internal.widget.LockPatternUtils; +import com.android.settings.privatespace.onelock.UseOneLockController; +import com.android.settings.testutils.FakeFeatureFactory; + import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public class UseOneLockControllerTest { @Mock private Context mContext; + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private UseOneLockController mUseOneLockController; + private Preference mPreference; + + @Mock + LockPatternUtils mLockPatternUtils; /** Required setup before a test. */ @Before public void setUp() { + MockitoAnnotations.initMocks(this); mContext = ApplicationProvider.getApplicationContext(); final String preferenceKey = "private_space_use_one_lock"; + mPreference = new Preference(mContext); + final FakeFeatureFactory featureFactory = FakeFeatureFactory.setupForTest(); + when(featureFactory.securityFeatureProvider.getLockPatternUtils(mContext)) + .thenReturn(mLockPatternUtils); mUseOneLockController = new UseOneLockController(mContext, preferenceKey); + } /** Tests that the controller is always available. */ @Test public void getAvailabilityStatus_returnsAvailable() { + mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE); + assertThat(mUseOneLockController.getAvailabilityStatus()).isEqualTo(AVAILABLE); } + + + /** Tests that summary in controller is Pattern. */ + @Test + public void getSummary_whenProfileLockPattern() { + doReturn(true) + .when(mLockPatternUtils).isSeparateProfileChallengeEnabled(anyInt()); + doReturn(CREDENTIAL_TYPE_PATTERN) + .when(mLockPatternUtils).getCredentialTypeForUser(anyInt()); + mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE); + + mUseOneLockController.updateState(mPreference); + assertThat(mUseOneLockController.getSummary().toString()).isEqualTo("Pattern"); + } + + /** Tests that summary in controller is PIN. */ + @Test + public void getSummary_whenProfileLockPin() { + doReturn(true) + .when(mLockPatternUtils).isSeparateProfileChallengeEnabled(anyInt()); + doReturn(CREDENTIAL_TYPE_PIN).when(mLockPatternUtils).getCredentialTypeForUser(anyInt()); + mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE); + + mUseOneLockController.updateState(mPreference); + assertThat(mUseOneLockController.getSummary().toString()).isEqualTo("PIN"); + } + + /** Tests that summary in controller is Password. */ + @Test + public void getSummary_whenProfileLockPassword() { + doReturn(true) + .when(mLockPatternUtils).isSeparateProfileChallengeEnabled(anyInt()); + doReturn(CREDENTIAL_TYPE_PASSWORD) + .when(mLockPatternUtils).getCredentialTypeForUser(anyInt()); + mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE); + + mUseOneLockController.updateState(mPreference); + assertThat(mUseOneLockController.getSummary().toString()).isEqualTo("Password"); + } }