Merge "Use ColorDisplayManager for night display"
diff --git a/res/layout/face_enroll_introduction.xml b/res/layout/face_enroll_introduction.xml
index 69b00fe..4096925 100644
--- a/res/layout/face_enroll_introduction.xml
+++ b/res/layout/face_enroll_introduction.xml
@@ -85,12 +85,6 @@
android:layout_height="wrap_content"
FaceEnrollAccessibilitySwitch:messageText="@string/security_settings_face_enroll_introduction_accessibility_diversity"/>
- <com.android.settings.biometrics.face.FaceEnrollAccessibilityToggle
- android:id="@+id/toggle_vision"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- FaceEnrollAccessibilitySwitch:messageText="@string/security_settings_face_enroll_introduction_accessibility_vision"/>
-
</LinearLayout>
</FrameLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f77c10d..b1edbb4 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1430,6 +1430,16 @@
<!-- Title shown on security settings to allow the user to change their lockscreen password [CHAR LIMIT=22]-->
<string name="unlock_change_lock_password_title">Change unlock password</string>
+ <!-- Footer preference text in the screen lock type picker to indicate which app is requesting a new screen lock and that it requests for a strong PIN or password [CHAR LIMIT=NONE] -->
+ <string name="unlock_footer_high_complexity_requested"><xliff:g id="app_name" example="Gmail">%1$s</xliff:g> requests a strong PIN or password.</string>
+ <!-- Footer preference text in the screen lock type picker to indicate which app is requesting a new screen lock and that it requests for a medium strength PIN or password [CHAR LIMIT=NONE] -->
+ <string name="unlock_footer_medium_complexity_requested"><xliff:g id="app_name" example="Gmail">%1$s</xliff:g> requests a new PIN or password.</string>
+ <!-- Footer preference text in the screen lock type picker to indicate which app is requesting a new screen lock and it requests for any screen lock [CHAR LIMIT=NONE] -->
+ <string name="unlock_footer_low_complexity_requested"><xliff:g id="app_name" example="Gmail">%1$s</xliff:g> requests a new pattern, PIN or password.</string>
+ <!-- Footer preference text in the screen lock type picker to indicate which app is requesting a new screen lock [CHAR LIMIT=NONE] -->
+ <string name="unlock_footer_none_complexity_requested"><xliff:g id="app_name" example="Gmail">%1$s</xliff:g> requests a new screen lock.</string>
+
+
<!-- Message shown on the lock screen when the user incorrectly enters their lock and it counts towards the max attempts before their data on the device is wiped. [CHAR LIMIT=NONE] -->
<string name="lock_failed_attempts_before_wipe">Try again. Attempt <xliff:g id="current_attempts">%1$d</xliff:g> of <xliff:g id="total_attempts">%2$d</xliff:g>.</string>
diff --git a/src/com/android/settings/CredentialStorage.java b/src/com/android/settings/CredentialStorage.java
index 5ab543f..0485a0f 100644
--- a/src/com/android/settings/CredentialStorage.java
+++ b/src/com/android/settings/CredentialStorage.java
@@ -55,40 +55,7 @@
import sun.security.x509.AlgorithmId;
/**
- * CredentialStorage handles KeyStore reset, unlock, and install.
- *
- * CredentialStorage has a pretty convoluted state machine to migrate
- * from the old style separate keystore password to a new key guard
- * based password, as well as to deal with setting up the key guard if
- * necessary.
- *
- * KeyStore: UNINITALIZED
- * KeyGuard: OFF
- * Action: set up key guard
- * Notes: factory state
- *
- * KeyStore: UNINITALIZED
- * KeyGuard: ON
- * Action: confirm key guard
- * Notes: user had key guard but no keystore and upgraded from pre-ICS
- * OR user had key guard and pre-ICS keystore password which was then reset
- *
- * KeyStore: LOCKED
- * KeyGuard: OFF/ON
- * Action: confirm key guard
- * Notes: request normal unlock to unlock the keystore.
- * if unlock, ensure key guard before install.
- * if reset, treat as UNINITALIZED/OFF
- *
- * KeyStore: UNLOCKED
- * KeyGuard: OFF
- * Action: set up key guard
- * Notes: ensure key guard, then proceed
- *
- * KeyStore: UNLOCKED
- * keyguard: ON
- * Action: normal unlock/install
- * Notes: this is the common case
+ * CredentialStorage handles resetting and installing keys into KeyStore.
*/
public final class CredentialStorage extends FragmentActivity {
@@ -102,8 +69,7 @@
// lower than this, keystore should not be activated.
public static final int MIN_PASSWORD_QUALITY = DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
- private static final int CONFIRM_KEY_GUARD_REQUEST = 1;
- private static final int CONFIRM_CLEAR_SYSTEM_CREDENTIAL_REQUEST = 2;
+ private static final int CONFIRM_CLEAR_SYSTEM_CREDENTIAL_REQUEST = 1;
private final KeyStore mKeyStore = KeyStore.getInstance();
private LockPatternUtils mUtils;
@@ -133,75 +99,26 @@
if (ACTION_INSTALL.equals(action) && checkCallerIsCertInstallerOrSelfInProfile()) {
mInstallBundle = intent.getExtras();
}
- // ACTION_UNLOCK also handled here in addition to ACTION_INSTALL
- handleUnlockOrInstall();
+ handleInstall();
}
} else {
- // Users can set a screen lock if there is none even if they can't modify the
- // credentials store.
- if (ACTION_UNLOCK.equals(action) && mKeyStore.state() == KeyStore.State.UNINITIALIZED) {
- ensureKeyGuard();
- } else {
- finish();
- }
+ finish();
}
}
/**
- * Based on the current state of the KeyStore and key guard, try to
- * make progress on unlocking or installing to the keystore.
+ * Install credentials from mInstallBundle into Keystore.
*/
- private void handleUnlockOrInstall() {
+ private void handleInstall() {
// something already decided we are done, do not proceed
if (isFinishing()) {
return;
}
- switch (mKeyStore.state()) {
- case UNINITIALIZED: {
- ensureKeyGuard();
- return;
- }
- case LOCKED: {
- // Force key guard confirmation
- confirmKeyGuard(CONFIRM_KEY_GUARD_REQUEST);
- return;
- }
- case UNLOCKED: {
- if (!mUtils.isSecure(UserHandle.myUserId())) {
- final ConfigureKeyGuardDialog dialog = new ConfigureKeyGuardDialog();
- dialog.show(getSupportFragmentManager(), ConfigureKeyGuardDialog.TAG);
- return;
- }
- if (installIfAvailable()) {
- finish();
- }
- return;
- }
+ if (installIfAvailable()) {
+ finish();
}
}
- /**
- * Make sure the user enters the key guard to set or change the
- * keystore password. This can be used in UNINITIALIZED to set the
- * keystore password or UNLOCKED to change the password (as is the
- * case after unlocking with an old-style password).
- */
- private void ensureKeyGuard() {
- if (!mUtils.isSecure(UserHandle.myUserId())) {
- // key guard not setup, doing so will initialize keystore
- final ConfigureKeyGuardDialog dialog = new ConfigureKeyGuardDialog();
- dialog.show(getSupportFragmentManager(), ConfigureKeyGuardDialog.TAG);
- // will return to onResume after Activity
- return;
- }
- // force key guard confirmation
- if (confirmKeyGuard(CONFIRM_KEY_GUARD_REQUEST)) {
- // will return password value via onActivityResult
- return;
- }
- finish();
- }
-
private boolean isHardwareBackedKey(byte[] keyData) {
try {
final ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(keyData));
@@ -254,15 +171,7 @@
final String key = bundle.getString(Credentials.EXTRA_USER_PRIVATE_KEY_NAME);
final byte[] value = bundle.getByteArray(Credentials.EXTRA_USER_PRIVATE_KEY_DATA);
- int flags = KeyStore.FLAG_ENCRYPTED;
- if (uid == Process.WIFI_UID && isHardwareBackedKey(value)) {
- // Hardware backed keystore is secure enough to allow for WIFI stack
- // to enable access to secure networks without user intervention
- Log.d(TAG, "Saving private key with FLAG_NONE for WIFI_UID");
- flags = KeyStore.FLAG_NONE;
- }
-
- if (!mKeyStore.importKey(key, value, uid, flags)) {
+ if (!mKeyStore.importKey(key, value, uid, KeyStore.FLAG_NONE)) {
Log.e(TAG, "Failed to install " + key + " as uid " + uid);
return true;
}
@@ -475,20 +384,7 @@
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
- // Receive key guard password initiated by confirmKeyGuard.
- if (requestCode == CONFIRM_KEY_GUARD_REQUEST) {
- if (resultCode == Activity.RESULT_OK) {
- final String password = data.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
- if (!TextUtils.isEmpty(password)) {
- // success
- mKeyStore.unlock(password);
- // return to onResume
- return;
- }
- }
- // failed confirmation, bail
- finish();
- } else if (requestCode == CONFIRM_CLEAR_SYSTEM_CREDENTIAL_REQUEST) {
+ if (requestCode == CONFIRM_CLEAR_SYSTEM_CREDENTIAL_REQUEST) {
if (resultCode == Activity.RESULT_OK) {
new ResetKeyStoreAndKeyChain().execute();
return;
diff --git a/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java b/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java
index fc4f6ce..71bd02f 100644
--- a/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java
+++ b/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java
@@ -43,7 +43,6 @@
private static final String TAG = "FaceIntro";
private FaceManager mFaceManager;
- private FaceEnrollAccessibilityToggle mSwitchVision;
private FaceEnrollAccessibilityToggle mSwitchDiversity;
@Override
@@ -57,7 +56,6 @@
accessibilityLayout.setVisibility(View.VISIBLE);
});
- mSwitchVision = findViewById(R.id.toggle_vision);
mSwitchDiversity = findViewById(R.id.toggle_diversity);
mButtonFooterMixin = getLayout().getMixin(ButtonFooterMixin.class);
@@ -167,7 +165,6 @@
} else {
intent.setClass(this, FaceEnrollEnrolling.class);
}
- intent.putExtra(EXTRA_KEY_REQUIRE_VISION, mSwitchVision.isChecked());
intent.putExtra(EXTRA_KEY_REQUIRE_DIVERSITY, mSwitchDiversity.isChecked());
return intent;
}
diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
index c3d49e9..6ff4309 100644
--- a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
+++ b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
@@ -109,12 +109,14 @@
mCachedDevice = getCachedDevice(mDeviceAddress);
super.onAttach(context);
- if (FeatureFlagUtils.isEnabled(context, FeatureFlags.SLICE_INJECTION)) {
- final BluetoothFeatureProvider featureProvider = FeatureFactory.getFactory(context)
- .getBluetoothFeatureProvider(context);
- use(BlockingSlicePrefController.class).setSliceUri(
- featureProvider.getBluetoothDeviceSettingsUri(mDeviceAddress));
- }
+ final BluetoothFeatureProvider featureProvider = FeatureFactory.getFactory(
+ context).getBluetoothFeatureProvider(context);
+ final boolean injectionEnabled = FeatureFlagUtils.isEnabled(context,
+ FeatureFlags.SLICE_INJECTION);
+
+ use(BlockingSlicePrefController.class).setSliceUri(injectionEnabled
+ ? featureProvider.getBluetoothDeviceSettingsUri(mDeviceAddress)
+ : null);
}
@Override
diff --git a/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorker.java b/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorker.java
index ff26888..1602f56 100644
--- a/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorker.java
+++ b/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorker.java
@@ -30,16 +30,11 @@
private static final String TAG = "BluetoothUpdateWorker";
- private final Context mContext;
- private final Uri mUri;
private final LocalBluetoothManager mLocalBluetoothManager;
public BluetoothUpdateWorker(Context context, Uri uri) {
super(context, uri);
-
- mContext = context;
- mUri = uri;
- mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
+ mLocalBluetoothManager = Utils.getLocalBtManager(context);
}
@Override
@@ -89,8 +84,4 @@
int bluetoothProfile) {
notifySliceChange();
}
-
- private void notifySliceChange() {
- mContext.getContentResolver().notifyChange(mUri, null);
- }
}
\ No newline at end of file
diff --git a/src/com/android/settings/location/RecentLocationAccessPreferenceController.java b/src/com/android/settings/location/RecentLocationAccessPreferenceController.java
index 2d84e21..3adc489 100644
--- a/src/com/android/settings/location/RecentLocationAccessPreferenceController.java
+++ b/src/com/android/settings/location/RecentLocationAccessPreferenceController.java
@@ -13,6 +13,8 @@
*/
package com.android.settings.location;
+import static java.util.concurrent.TimeUnit.DAYS;
+
import android.Manifest;
import android.content.Context;
import android.content.Intent;
@@ -74,6 +76,7 @@
final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE);
intent.putExtra(Intent.EXTRA_PERMISSION_NAME,
Manifest.permission.ACCESS_FINE_LOCATION);
+ intent.putExtra(Intent.EXTRA_DURATION_MILLIS, DAYS.toMillis(1));
mContext.startActivity(intent);
});
}
diff --git a/src/com/android/settings/password/BiometricFragment.java b/src/com/android/settings/password/BiometricFragment.java
index 13ec543..171b61e 100644
--- a/src/com/android/settings/password/BiometricFragment.java
+++ b/src/com/android/settings/password/BiometricFragment.java
@@ -43,6 +43,7 @@
private static final String KEY_SUBTITLE = "subtitle";
private static final String KEY_DESCRIPTION = "description";
private static final String KEY_NEGATIVE_TEXT = "negative_text";
+ private static final String KEY_REQUIRE_CONFIRMATION = "require_confirmation";
// Re-set by the application. Should be done upon orientation changes, etc
private Executor mClientExecutor;
@@ -127,6 +128,7 @@
.setDescription(mPromptInfo.getDescription())
.setNegativeButton(mPromptInfo.getNegativeButtonText(), mClientExecutor,
mNegativeButtonListener)
+ .setRequireConfirmation(mPromptInfo.getRequireConfirmation())
.build();
mCancellationSignal = new CancellationSignal();
@@ -171,6 +173,10 @@
return mBundle.getCharSequence(KEY_NEGATIVE_TEXT);
}
+ public boolean getRequireConfirmation() {
+ return mBundle.getBoolean(KEY_REQUIRE_CONFIRMATION);
+ }
+
public static class Builder {
private final Bundle mBundle = new Bundle();
@@ -194,6 +200,11 @@
return this;
}
+ public Builder setRequireConfirmation(boolean requireConfirmation) {
+ mBundle.putBoolean(KEY_REQUIRE_CONFIRMATION, requireConfirmation);
+ return this;
+ }
+
public PromptInfo build() {
return new PromptInfo(mBundle);
}
diff --git a/src/com/android/settings/password/ChooseLockGeneric.java b/src/com/android/settings/password/ChooseLockGeneric.java
index ca96344..cae7d33 100644
--- a/src/com/android/settings/password/ChooseLockGeneric.java
+++ b/src/com/android/settings/password/ChooseLockGeneric.java
@@ -18,13 +18,20 @@
import static android.app.admin.DevicePolicyManager.ACTION_SET_NEW_PARENT_PROFILE_PASSWORD;
import static android.app.admin.DevicePolicyManager.ACTION_SET_NEW_PASSWORD;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
import static com.android.settings.password.ChooseLockPassword.ChooseLockPasswordFragment.RESULT_FINISHED;
+import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CALLER_APP_NAME;
+import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Activity;
import android.app.Dialog;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManager.PasswordComplexity;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
@@ -66,6 +73,8 @@
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.RestrictedPreference;
+import com.android.settingslib.widget.FooterPreference;
+import com.android.settingslib.widget.FooterPreferenceMixinCompat;
import java.util.List;
@@ -152,6 +161,14 @@
private UserManager mUserManager;
private ChooseLockGenericController mController;
+ /**
+ * From intent extra {@link ChooseLockSettingsHelper#EXTRA_KEY_REQUESTED_MIN_COMPLEXITY}.
+ */
+ @PasswordComplexity private int mRequestedMinComplexity;
+
+ /** From intent extra {@link ChooseLockSettingsHelper#EXTRA_KEY_CALLER_APP_NAME}. */
+ private String mCallerAppName = null;
+
protected boolean mForFingerprint = false;
protected boolean mForFace = false;
@@ -195,6 +212,10 @@
ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
mForFace = getActivity().getIntent().getBooleanExtra(
ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false);
+ mRequestedMinComplexity = getActivity().getIntent()
+ .getIntExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE);
+ mCallerAppName =
+ getActivity().getIntent().getStringExtra(EXTRA_KEY_CALLER_APP_NAME);
mForChangeCredRequiredForBoot = getArguments() != null && getArguments().getBoolean(
ChooseLockSettingsHelper.EXTRA_KEY_FOR_CHANGE_CRED_REQUIRED_FOR_BOOT);
mUserManager = UserManager.get(getActivity());
@@ -217,7 +238,8 @@
UserManager.get(getActivity()),
getArguments(),
getActivity().getIntent().getExtras()).getIdentifier();
- mController = new ChooseLockGenericController(getContext(), mUserId);
+ mController =
+ new ChooseLockGenericController(getContext(), mUserId, mRequestedMinComplexity);
if (ACTION_SET_NEW_PASSWORD.equals(chooseLockAction)
&& UserManager.get(getActivity()).isManagedProfile(mUserId)
&& mLockPatternUtils.isSeparateProfileChallengeEnabled(mUserId)) {
@@ -291,6 +313,9 @@
// Forward the target user id to ChooseLockGeneric.
chooseLockGenericIntent.putExtra(Intent.EXTRA_USER_ID, mUserId);
chooseLockGenericIntent.putExtra(CONFIRM_CREDENTIALS, !mPasswordConfirmed);
+ chooseLockGenericIntent.putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY,
+ mRequestedMinComplexity);
+ chooseLockGenericIntent.putExtra(EXTRA_KEY_CALLER_APP_NAME, mCallerAppName);
if (mUserPassword != null) {
chooseLockGenericIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD,
mUserPassword);
@@ -461,6 +486,13 @@
protected void addPreferences() {
addPreferencesFromResource(R.xml.security_settings_picker);
+ if (!TextUtils.isEmpty(mCallerAppName)) {
+ FooterPreferenceMixinCompat footerMixin =
+ new FooterPreferenceMixinCompat(this, getSettingsLifecycle());
+ FooterPreference footer = footerMixin.createFooterPreference();
+ footer.setTitle(getFooterString());
+ }
+
// Used for testing purposes
findPreference(ScreenLockType.NONE.preferenceKey).setViewId(R.id.lock_none);
findPreference(KEY_SKIP_FINGERPRINT).setViewId(R.id.lock_none);
@@ -469,6 +501,27 @@
findPreference(ScreenLockType.PASSWORD.preferenceKey).setViewId(R.id.lock_password);
}
+ private String getFooterString() {
+ @StringRes int stringId;
+ switch (mRequestedMinComplexity) {
+ case PASSWORD_COMPLEXITY_HIGH:
+ stringId = R.string.unlock_footer_high_complexity_requested;
+ break;
+ case PASSWORD_COMPLEXITY_MEDIUM:
+ stringId = R.string.unlock_footer_medium_complexity_requested;
+ break;
+ case PASSWORD_COMPLEXITY_LOW:
+ stringId = R.string.unlock_footer_low_complexity_requested;
+ break;
+ case PASSWORD_COMPLEXITY_NONE:
+ default:
+ stringId = R.string.unlock_footer_none_complexity_requested;
+ break;
+ }
+
+ return getResources().getString(stringId, mCallerAppName);
+ }
+
private void updatePreferenceText() {
if (mForFingerprint) {
setPreferenceTitle(ScreenLockType.PATTERN,
@@ -624,6 +677,7 @@
ChooseLockPassword.IntentBuilder builder =
new ChooseLockPassword.IntentBuilder(getContext())
.setPasswordQuality(quality)
+ .setRequestedMinComplexity(mRequestedMinComplexity)
.setForFingerprint(mForFingerprint)
.setForFace(mForFace)
.setUserId(mUserId);
diff --git a/src/com/android/settings/password/ChooseLockGenericController.java b/src/com/android/settings/password/ChooseLockGenericController.java
index eb7ff4e..91ca957 100644
--- a/src/com/android/settings/password/ChooseLockGenericController.java
+++ b/src/com/android/settings/password/ChooseLockGenericController.java
@@ -16,7 +16,11 @@
package com.android.settings.password;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+
import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManager.PasswordComplexity;
+import android.app.admin.PasswordMetrics;
import android.content.Context;
import android.os.UserHandle;
@@ -36,6 +40,7 @@
private final Context mContext;
private final int mUserId;
+ @PasswordComplexity private final int mRequestedMinComplexity;
private ManagedLockPasswordProvider mManagedPasswordProvider;
private DevicePolicyManager mDpm;
@@ -43,6 +48,19 @@
this(
context,
userId,
+ PASSWORD_COMPLEXITY_NONE);
+ }
+
+ /**
+ * @param requestedMinComplexity specifies the min password complexity to be taken into account
+ * when determining the available screen lock types
+ */
+ public ChooseLockGenericController(Context context, int userId,
+ @PasswordComplexity int requestedMinComplexity) {
+ this(
+ context,
+ userId,
+ requestedMinComplexity,
context.getSystemService(DevicePolicyManager.class),
ManagedLockPasswordProvider.get(context, userId));
}
@@ -51,21 +69,26 @@
ChooseLockGenericController(
Context context,
int userId,
+ @PasswordComplexity int requestedMinComplexity,
DevicePolicyManager dpm,
ManagedLockPasswordProvider managedLockPasswordProvider) {
mContext = context;
mUserId = userId;
+ mRequestedMinComplexity = requestedMinComplexity;
mManagedPasswordProvider = managedLockPasswordProvider;
mDpm = dpm;
}
/**
- * @return The higher quality of either the specified {@code quality} or the quality required
- * by {@link DevicePolicyManager#getPasswordQuality}.
+ * Returns the highest quality among the specified {@code quality}, the quality required by
+ * {@link DevicePolicyManager#getPasswordQuality}, and the quality required by min password
+ * complexity.
*/
public int upgradeQuality(int quality) {
- // Compare min allowed password quality
- return Math.max(quality, mDpm.getPasswordQuality(null, mUserId));
+ // Compare specified quality and dpm quality
+ int dpmUpgradedQuality = Math.max(quality, mDpm.getPasswordQuality(null, mUserId));
+ return Math.max(dpmUpgradedQuality,
+ PasswordMetrics.complexityLevelToMinQuality(mRequestedMinComplexity));
}
/**
diff --git a/src/com/android/settings/password/ChooseLockPassword.java b/src/com/android/settings/password/ChooseLockPassword.java
index 0ae664c..129f9ac 100644
--- a/src/com/android/settings/password/ChooseLockPassword.java
+++ b/src/com/android/settings/password/ChooseLockPassword.java
@@ -16,14 +16,18 @@
package com.android.settings.password;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
+import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY;
+
import android.app.Activity;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManager.PasswordComplexity;
import android.app.admin.PasswordMetrics;
import android.app.settings.SettingsEnums;
import android.content.Context;
@@ -56,6 +60,7 @@
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
import com.android.internal.widget.TextViewInputDisabler;
@@ -133,6 +138,11 @@
return this;
}
+ public IntentBuilder setRequestedMinComplexity(@PasswordComplexity int level) {
+ mIntent.putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, level);
+ return this;
+ }
+
public Intent build() {
return mIntent;
}
@@ -190,12 +200,10 @@
private int mPasswordMinNumeric = 0;
private int mPasswordMinNonLetter = 0;
private int mPasswordMinLengthToFulfillAllPolicies = 0;
+ private boolean mPasswordNumSequenceAllowed = true;
+ @PasswordComplexity private int mRequestedMinComplexity = PASSWORD_COMPLEXITY_NONE;
protected int mUserId;
private byte[] mPasswordHistoryHashFactor;
- /**
- * Password requirements that we need to verify.
- */
- private int[] mPasswordRequirements;
private LockPatternUtils mLockPatternUtils;
private SaveAndFinishWorker mSaveAndFinishWorker;
@@ -372,7 +380,13 @@
mForFingerprint = intent.getBooleanExtra(
ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
mForFace = intent.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false);
- processPasswordRequirements(intent);
+ mRequestedMinComplexity = intent.getIntExtra(
+ EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE);
+ mRequestedQuality = Math.max(
+ intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, mRequestedQuality),
+ mLockPatternUtils.getRequestedPasswordQuality(mUserId));
+
+ loadDpmPasswordRequirements();
mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity());
if (intent.getBooleanExtra(
@@ -504,31 +518,6 @@
}
private void setupPasswordRequirementsView(View view) {
- final List<Integer> passwordRequirements = new ArrayList<>();
- if (mPasswordMinUpperCase > 0) {
- passwordRequirements.add(MIN_UPPER_LETTERS_IN_PASSWORD);
- }
- if (mPasswordMinLowerCase > 0) {
- passwordRequirements.add(MIN_LOWER_LETTERS_IN_PASSWORD);
- }
- if (mPasswordMinLetters > 0) {
- if (mPasswordMinLetters > mPasswordMinUpperCase + mPasswordMinLowerCase) {
- passwordRequirements.add(MIN_LETTER_IN_PASSWORD);
- }
- }
- if (mPasswordMinNumeric > 0) {
- passwordRequirements.add(MIN_NUMBER_IN_PASSWORD);
- }
- if (mPasswordMinSymbols > 0) {
- passwordRequirements.add(MIN_SYMBOLS_IN_PASSWORD);
- }
- if (mPasswordMinNonLetter > 0) {
- if (mPasswordMinNonLetter > mPasswordMinNumeric + mPasswordMinSymbols) {
- passwordRequirements.add(MIN_NON_LETTER_IN_PASSWORD);
- }
- }
- // Convert list to array.
- mPasswordRequirements = passwordRequirements.stream().mapToInt(i -> i).toArray();
mPasswordRestrictionView = view.findViewById(R.id.password_requirements_view);
mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity()));
mPasswordRequirementAdapter = new PasswordRequirementAdapter();
@@ -603,13 +592,12 @@
/**
* Read the requirements from {@link DevicePolicyManager} and intent and aggregate them.
- *
- * @param intent the incoming intent
*/
- private void processPasswordRequirements(Intent intent) {
+ private void loadDpmPasswordRequirements() {
final int dpmPasswordQuality = mLockPatternUtils.getRequestedPasswordQuality(mUserId);
- mRequestedQuality = Math.max(intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY,
- mRequestedQuality), dpmPasswordQuality);
+ if (dpmPasswordQuality == PASSWORD_QUALITY_NUMERIC_COMPLEX) {
+ mPasswordNumSequenceAllowed = false;
+ }
mPasswordMinLength = Math.max(LockPatternUtils.MIN_LOCK_PASSWORD_SIZE,
mLockPatternUtils.getRequestedMinimumPasswordLength(mUserId));
mPasswordMaxLength = mLockPatternUtils.getMaximumPasswordLength(mRequestedQuality);
@@ -620,7 +608,7 @@
mPasswordMinSymbols = mLockPatternUtils.getRequestedPasswordMinimumSymbols(mUserId);
mPasswordMinNonLetter = mLockPatternUtils.getRequestedPasswordMinimumNonLetter(mUserId);
- // Modify the value based on dpm policy.
+ // Modify the value based on dpm policy
switch (dpmPasswordQuality) {
case PASSWORD_QUALITY_ALPHABETIC:
if (mPasswordMinLetters == 0) {
@@ -646,19 +634,88 @@
mPasswordMinSymbols = 0;
mPasswordMinNonLetter = 0;
}
+
mPasswordMinLengthToFulfillAllPolicies = getMinLengthToFulfillAllPolicies();
}
/**
+ * Merges the dpm requirements and the min complexity requirements.
+ *
+ * <p>Since there are more than one set of metrics to meet the min complexity requirement,
+ * and we are not hard-coding any one of them to be the requirements the user must fulfil,
+ * we are taking what the user has already entered into account when compiling the list of
+ * requirements from min complexity. Then we merge this list with the DPM requirements, and
+ * present the merged set as validation results to the user on the UI.
+ *
+ * <p>For example, suppose min complexity requires either ALPHABETIC(8+), or
+ * ALPHANUMERIC(6+). If the user has entered "a", the length requirement displayed on the UI
+ * would be 8. Then the user appends "1" to make it "a1". We now know the user is entering
+ * an alphanumeric password so we would update the min complexity required min length to 6.
+ * This might result in a little confusion for the user but the UI does not support showing
+ * multiple sets of requirements / validation results as options to users, this is the best
+ * we can do now.
+ */
+ private void mergeMinComplexityAndDpmRequirements(int userEnteredPasswordQuality) {
+ if (mRequestedMinComplexity == PASSWORD_COMPLEXITY_NONE) {
+ // dpm requirements are dominant if min complexity is none
+ return;
+ }
+
+ // reset dpm requirements
+ loadDpmPasswordRequirements();
+
+ PasswordMetrics minMetrics = PasswordMetrics.getMinimumMetrics(
+ mRequestedMinComplexity, userEnteredPasswordQuality, mRequestedQuality,
+ requiresNumeric(), requiresLettersOrSymbols());
+ mPasswordNumSequenceAllowed = mPasswordNumSequenceAllowed
+ && minMetrics.quality != PASSWORD_QUALITY_NUMERIC_COMPLEX;
+ mPasswordMinLength = Math.max(mPasswordMinLength, minMetrics.length);
+ mPasswordMinLetters = Math.max(mPasswordMinLetters, minMetrics.letters);
+ mPasswordMinUpperCase = Math.max(mPasswordMinUpperCase, minMetrics.upperCase);
+ mPasswordMinLowerCase = Math.max(mPasswordMinLowerCase, minMetrics.lowerCase);
+ mPasswordMinNumeric = Math.max(mPasswordMinNumeric, minMetrics.numeric);
+ mPasswordMinSymbols = Math.max(mPasswordMinSymbols, minMetrics.symbols);
+ mPasswordMinNonLetter = Math.max(mPasswordMinNonLetter, minMetrics.nonLetter);
+
+ if (minMetrics.quality == PASSWORD_QUALITY_ALPHABETIC) {
+ if (!requiresLettersOrSymbols()) {
+ mPasswordMinLetters = 1;
+ }
+ }
+ if (minMetrics.quality == PASSWORD_QUALITY_ALPHANUMERIC) {
+ if (!requiresLettersOrSymbols()) {
+ mPasswordMinLetters = 1;
+ }
+ if (!requiresNumeric()) {
+ mPasswordMinNumeric = 1;
+ }
+ }
+
+ mPasswordMinLengthToFulfillAllPolicies = getMinLengthToFulfillAllPolicies();
+ }
+
+ private boolean requiresLettersOrSymbols() {
+ // This is the condition for the password to be considered ALPHABETIC according to
+ // PasswordMetrics.computeForPassword()
+ return mPasswordMinLetters + mPasswordMinUpperCase
+ + mPasswordMinLowerCase + mPasswordMinSymbols + mPasswordMinNonLetter > 0;
+ }
+
+ private boolean requiresNumeric() {
+ return mPasswordMinNumeric > 0;
+ }
+
+ /**
* Validates PIN/Password and returns the validation result.
*
* @param password the raw password the user typed in
* @return the validation result.
*/
- private int validatePassword(String password) {
+ @VisibleForTesting
+ int validatePassword(String password) {
int errorCode = NO_ERROR;
final PasswordMetrics metrics = PasswordMetrics.computeForPassword(password);
-
+ mergeMinComplexityAndDpmRequirements(metrics.quality);
if (password.length() < mPasswordMinLength) {
if (mPasswordMinLength > mPasswordMinLengthToFulfillAllPolicies) {
@@ -668,14 +725,25 @@
errorCode |= TOO_LONG;
} else {
// The length requirements are fulfilled.
- final int dpmQuality = mLockPatternUtils.getRequestedPasswordQuality(mUserId);
- if (dpmQuality == PASSWORD_QUALITY_NUMERIC_COMPLEX &&
- metrics.numeric == password.length()) {
+ if (!mPasswordNumSequenceAllowed
+ && !requiresLettersOrSymbols()
+ && metrics.numeric == password.length()) {
// Check for repeated characters or sequences (e.g. '1234', '0000', '2468')
- // if DevicePolicyManager requires a complex numeric password. There can be
- // two cases in the UI: 1. User chooses to enroll a PIN, 2. User chooses to
- // enroll a password but enters a numeric-only pin. We should carry out the
- // sequence check in both cases.
+ // if DevicePolicyManager or min password complexity requires a complex numeric
+ // password. There can be two cases in the UI: 1. User chooses to enroll a
+ // PIN, 2. User chooses to enroll a password but enters a numeric-only pin. We
+ // should carry out the sequence check in both cases.
+ //
+ // Conditions for the !requiresLettersOrSymbols() to be necessary:
+ // - DPM requires NUMERIC_COMPLEX
+ // - min complexity not NONE, user picks PASSWORD type so ALPHABETIC or
+ // ALPHANUMERIC is required
+ // Imagine user has entered "12345678", if we don't skip the sequence check, the
+ // validation result would show both "requires a letter" and "sequence not
+ // allowed", while the only requirement the user needs to know is "requires a
+ // letter" because once the user has fulfilled the alphabetic requirement, the
+ // password would not be containing only digits so this check would not be
+ // performed anyway.
final int sequence = PasswordMetrics.maxLengthSequence(password);
if (sequence > PasswordMetrics.MAX_ALLOWED_SEQUENCE) {
errorCode |= CONTAIN_SEQUENTIAL_DIGITS;
@@ -706,43 +774,24 @@
}
}
- // Check the requirements one by one.
- for (int i = 0; i < mPasswordRequirements.length; i++) {
- int passwordRestriction = mPasswordRequirements[i];
- switch (passwordRestriction) {
- case MIN_LETTER_IN_PASSWORD:
- if (metrics.letters < mPasswordMinLetters) {
- errorCode |= NOT_ENOUGH_LETTER;
- }
- break;
- case MIN_UPPER_LETTERS_IN_PASSWORD:
- if (metrics.upperCase < mPasswordMinUpperCase) {
- errorCode |= NOT_ENOUGH_UPPER_CASE;
- }
- break;
- case MIN_LOWER_LETTERS_IN_PASSWORD:
- if (metrics.lowerCase < mPasswordMinLowerCase) {
- errorCode |= NOT_ENOUGH_LOWER_CASE;
- }
- break;
- case MIN_SYMBOLS_IN_PASSWORD:
- if (metrics.symbols < mPasswordMinSymbols) {
- errorCode |= NOT_ENOUGH_SYMBOLS;
- }
- break;
- case MIN_NUMBER_IN_PASSWORD:
- if (metrics.numeric < mPasswordMinNumeric) {
- errorCode |= NOT_ENOUGH_DIGITS;
- }
- break;
- case MIN_NON_LETTER_IN_PASSWORD:
- if (metrics.nonLetter < mPasswordMinNonLetter) {
- errorCode |= NOT_ENOUGH_NON_LETTER;
- }
- break;
- }
+ if (metrics.letters < mPasswordMinLetters) {
+ errorCode |= NOT_ENOUGH_LETTER;
}
-
+ if (metrics.upperCase < mPasswordMinUpperCase) {
+ errorCode |= NOT_ENOUGH_UPPER_CASE;
+ }
+ if (metrics.lowerCase < mPasswordMinLowerCase) {
+ errorCode |= NOT_ENOUGH_LOWER_CASE;
+ }
+ if (metrics.symbols < mPasswordMinSymbols) {
+ errorCode |= NOT_ENOUGH_SYMBOLS;
+ }
+ if (metrics.numeric < mPasswordMinNumeric) {
+ errorCode |= NOT_ENOUGH_DIGITS;
+ }
+ if (metrics.nonLetter < mPasswordMinNonLetter) {
+ errorCode |= NOT_ENOUGH_NON_LETTER;
+ }
return errorCode;
}
diff --git a/src/com/android/settings/password/ChooseLockSettingsHelper.java b/src/com/android/settings/password/ChooseLockSettingsHelper.java
index 8d0fa60..32e8eaf 100644
--- a/src/com/android/settings/password/ChooseLockSettingsHelper.java
+++ b/src/com/android/settings/password/ChooseLockSettingsHelper.java
@@ -46,6 +46,18 @@
public static final String EXTRA_KEY_FOR_CHANGE_CRED_REQUIRED_FOR_BOOT = "for_cred_req_boot";
/**
+ * Intent extra for passing the requested min password complexity to later steps in the set new
+ * screen lock flow.
+ */
+ public static final String EXTRA_KEY_REQUESTED_MIN_COMPLEXITY = "requested_min_complexity";
+
+ /**
+ * Intent extra for passing the label of the calling app to later steps in the set new screen
+ * lock flow.
+ */
+ public static final String EXTRA_KEY_CALLER_APP_NAME = "caller_app_name";
+
+ /**
* When invoked via {@link ConfirmLockPassword.InternalActivity}, this flag
* controls if we relax the enforcement of
* {@link Utils#enforceSameOwner(android.content.Context, int)}.
diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
index 5eb1f32..0d9b21d 100644
--- a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
+++ b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
@@ -136,6 +136,10 @@
Intent intent = getIntent();
mTitle = intent.getStringExtra(KeyguardManager.EXTRA_TITLE);
mDetails = intent.getStringExtra(KeyguardManager.EXTRA_DESCRIPTION);
+
+ final boolean requireConfirmation =
+ !intent.getBooleanExtra(KeyguardManager.EXTRA_USE_IMPLICIT, true);
+
String alternateButton = intent.getStringExtra(
KeyguardManager.EXTRA_ALTERNATE_BUTTON_LABEL);
boolean frp = KeyguardManager.ACTION_CONFIRM_FRP_CREDENTIAL.equals(intent.getAction());
@@ -170,7 +174,7 @@
&& !lockPatternUtils.isSeparateProfileChallengeEnabled(mUserId)) {
mCredentialMode = CREDENTIAL_MANAGED;
if (isBiometricAllowed(effectiveUserId)) {
- showBiometricPrompt();
+ showBiometricPrompt(requireConfirmation);
launchedBiometric = true;
} else {
showConfirmCredentials();
@@ -181,7 +185,7 @@
if (isBiometricAllowed(effectiveUserId)) {
// Don't need to check if biometrics / pin/pattern/pass are enrolled. It will go to
// onAuthenticationError and do the right thing automatically.
- showBiometricPrompt();
+ showBiometricPrompt(requireConfirmation);
launchedBiometric = true;
} else {
showConfirmCredentials();
@@ -242,7 +246,7 @@
&& !isBiometricDisabledByAdmin(effectiveUserId);
}
- private void showBiometricPrompt() {
+ private void showBiometricPrompt(boolean requireConfirmation) {
mBiometricManager.setActiveUser(mUserId);
mBiometricFragment = (BiometricFragment) getSupportFragmentManager()
@@ -255,6 +259,7 @@
.setSubtitle(mDetails)
.setNegativeButtonText(getResources()
.getString(R.string.confirm_device_credential_use_alternate_method))
+ .setRequireConfirmation(requireConfirmation)
.build();
mBiometricFragment = BiometricFragment.newInstance(info);
newFragment = true;
diff --git a/src/com/android/settings/password/PasswordUtils.java b/src/com/android/settings/password/PasswordUtils.java
new file mode 100644
index 0000000..5f118cf
--- /dev/null
+++ b/src/com/android/settings/password/PasswordUtils.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2019 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.password;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.settings.Utils;
+
+public final class PasswordUtils extends com.android.settingslib.Utils {
+
+ private static final String TAG = "Settings";
+
+ private static final String SETTINGS_PACKAGE_NAME = "com.android.settings";
+
+ /**
+ * Returns whether the uid which the activity with {@code activityToken} is launched from has
+ * been granted the {@code permission}.
+ */
+ public static boolean isCallingAppPermitted(Context context, IBinder activityToken,
+ String permission) {
+ try {
+ return context.checkPermission(permission, /* pid= */ -1,
+ ActivityManager.getService().getLaunchedFromUid(activityToken))
+ == PackageManager.PERMISSION_GRANTED;
+ } catch (RemoteException e) {
+ Log.v(TAG, "Could not talk to activity manager.", e);
+ return false;
+ }
+ }
+
+ /**
+ * Returns the label of the package which the activity with {@code activityToken} is launched
+ * from or {@code null} if it is launched from the settings app itself.
+ */
+ @Nullable
+ public static CharSequence getCallingAppLabel(Context context, IBinder activityToken) {
+ String pkg = getCallingAppPackageName(activityToken);
+ if (pkg == null || pkg.equals(SETTINGS_PACKAGE_NAME)) {
+ return null;
+ }
+
+ return Utils.getApplicationLabel(context, pkg);
+ }
+
+ /**
+ * Returns the package name which the activity with {@code activityToken} is launched from.
+ */
+ @Nullable
+ private static String getCallingAppPackageName(IBinder activityToken) {
+ String pkg = null;
+ try {
+ pkg = ActivityManager.getService().getLaunchedFromPackage(activityToken);
+ } catch (RemoteException e) {
+ Log.v(TAG, "Could not talk to activity manager.", e);
+ }
+ return pkg;
+ }
+
+ /** Crashes the calling application and provides it with {@code message}. */
+ public static void crashCallingApplication(IBinder activityToken, String message) {
+ IActivityManager am = ActivityManager.getService();
+ try {
+ int uid = am.getLaunchedFromUid(activityToken);
+ int userId = UserHandle.getUserId(uid);
+ am.crashApplication(
+ uid,
+ /* initialPid= */ -1,
+ getCallingAppPackageName(activityToken),
+ userId,
+ message);
+ } catch (RemoteException e) {
+ Log.v(TAG, "Could not talk to activity manager.", e);
+ }
+ }
+}
diff --git a/src/com/android/settings/password/SetNewPasswordActivity.java b/src/com/android/settings/password/SetNewPasswordActivity.java
index 99f67cb..8ea8514 100644
--- a/src/com/android/settings/password/SetNewPasswordActivity.java
+++ b/src/com/android/settings/password/SetNewPasswordActivity.java
@@ -16,13 +16,22 @@
package com.android.settings.password;
+import static android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY;
import static android.app.admin.DevicePolicyManager.ACTION_SET_NEW_PARENT_PROFILE_PASSWORD;
import static android.app.admin.DevicePolicyManager.ACTION_SET_NEW_PASSWORD;
+import static android.app.admin.DevicePolicyManager.EXTRA_PASSWORD_COMPLEXITY;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+
+import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CALLER_APP_NAME;
+import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY;
import android.app.Activity;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManager.PasswordComplexity;
+import android.app.admin.PasswordMetrics;
import android.content.Intent;
import android.os.Bundle;
+import android.os.IBinder;
import android.util.Log;
import com.android.settings.Utils;
@@ -37,6 +46,21 @@
private String mNewPasswordAction;
private SetNewPasswordController mSetNewPasswordController;
+ /**
+ * From intent extra {@link DevicePolicyManager#EXTRA_PASSWORD_COMPLEXITY}.
+ *
+ * <p>This is used only if caller has the required permission and activity is launched by
+ * {@link DevicePolicyManager#ACTION_SET_NEW_PASSWORD}.
+ */
+ private @PasswordComplexity int mRequestedMinComplexity = PASSWORD_COMPLEXITY_NONE;
+
+ /**
+ * Label of the app which launches this activity.
+ *
+ * <p>Value would be {@code null} if launched from settings app.
+ */
+ private String mCallerAppName = null;
+
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
@@ -48,6 +72,25 @@
finish();
return;
}
+
+ IBinder activityToken = getActivityToken();
+ mCallerAppName = (String) PasswordUtils.getCallingAppLabel(this, activityToken);
+ if (ACTION_SET_NEW_PASSWORD.equals(mNewPasswordAction)
+ && getIntent().hasExtra(EXTRA_PASSWORD_COMPLEXITY)) {
+ boolean hasPermission = PasswordUtils.isCallingAppPermitted(
+ this, activityToken, GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY);
+ if (hasPermission) {
+ mRequestedMinComplexity = PasswordMetrics.sanitizeComplexityLevel(getIntent()
+ .getIntExtra(EXTRA_PASSWORD_COMPLEXITY, PASSWORD_COMPLEXITY_NONE));
+ } else {
+ PasswordUtils.crashCallingApplication(activityToken,
+ "Must have permission " + GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY
+ + " to use extra " + EXTRA_PASSWORD_COMPLEXITY);
+ finish();
+ return;
+ }
+ }
+
mSetNewPasswordController = SetNewPasswordController.create(
this, this, getIntent(), getActivityToken());
mSetNewPasswordController.dispatchSetNewPasswordIntent();
@@ -60,6 +103,12 @@
: new Intent(this, ChooseLockGeneric.class);
intent.setAction(mNewPasswordAction);
intent.putExtras(chooseLockFingerprintExtras);
+ if (mCallerAppName != null) {
+ intent.putExtra(EXTRA_KEY_CALLER_APP_NAME, mCallerAppName);
+ }
+ if (mRequestedMinComplexity != PASSWORD_COMPLEXITY_NONE) {
+ intent.putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, mRequestedMinComplexity);
+ }
startActivity(intent);
finish();
}
diff --git a/src/com/android/settings/password/SetupChooseLockGeneric.java b/src/com/android/settings/password/SetupChooseLockGeneric.java
index a0f8bae..33c3edb 100644
--- a/src/com/android/settings/password/SetupChooseLockGeneric.java
+++ b/src/com/android/settings/password/SetupChooseLockGeneric.java
@@ -16,11 +16,17 @@
package com.android.settings.password;
+import static android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY;
+import static android.app.admin.DevicePolicyManager.EXTRA_PASSWORD_COMPLEXITY;
+
+import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY;
+
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.UserHandle;
import android.view.LayoutInflater;
import android.view.View;
@@ -48,6 +54,7 @@
* Other changes should be done to ChooseLockGeneric class instead and let this class inherit
* those changes.
*/
+// TODO(b/123225425): Restrict SetupChooseLockGeneric to be accessible by SUW only
public class SetupChooseLockGeneric extends ChooseLockGeneric {
private static final String KEY_UNLOCK_SET_DO_LATER = "unlock_set_do_later";
@@ -71,6 +78,20 @@
@Override
protected void onCreate(Bundle savedInstance) {
super.onCreate(savedInstance);
+
+ if(getIntent().hasExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY)) {
+ IBinder activityToken = getActivityToken();
+ boolean hasPermission = PasswordUtils.isCallingAppPermitted(
+ this, activityToken, GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY);
+ if (!hasPermission) {
+ PasswordUtils.crashCallingApplication(activityToken,
+ "Must have permission " + GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY
+ + " to use extra " + EXTRA_PASSWORD_COMPLEXITY);
+ finish();
+ return;
+ }
+ }
+
LinearLayout layout = (LinearLayout) findViewById(R.id.content_parent);
layout.setFitsSystemWindows(false);
}
diff --git a/src/com/android/settings/privacy/PermissionBarChartPreferenceController.java b/src/com/android/settings/privacy/PermissionBarChartPreferenceController.java
index 3fac672..e43140f 100644
--- a/src/com/android/settings/privacy/PermissionBarChartPreferenceController.java
+++ b/src/com/android/settings/privacy/PermissionBarChartPreferenceController.java
@@ -76,6 +76,7 @@
.setEmptyText(R.string.permission_bar_chart_empty_text)
.setDetailsOnClickListener((View v) -> {
final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE);
+ intent.putExtra(Intent.EXTRA_DURATION_MILLIS, DAYS.toMillis(1));
mContext.startActivity(intent);
})
.build();
@@ -102,7 +103,7 @@
private void retrievePermissionUsageData() {
mContext.getSystemService(PermissionControllerManager.class).getPermissionUsages(
- false /* countSystem */, (int) DAYS.toSeconds(1),
+ false /* countSystem */, (int) DAYS.toMillis(1),
mContext.getMainExecutor() /* executor */, this /* callback */);
}
@@ -111,6 +112,9 @@
return null;
}
+ // STOPSHIP: Ignore the STORAGE group since it's going away.
+ usageInfos.removeIf(usage -> usage.getName().equals("android.permission-group.STORAGE"));
+
final BarViewInfo[] barViewInfos = new BarViewInfo[
Math.min(BarChartPreference.MAXIMUM_BAR_VIEWS, usageInfos.size())];
diff --git a/src/com/android/settings/slices/SliceBackgroundWorker.java b/src/com/android/settings/slices/SliceBackgroundWorker.java
index 284fd23..6df45ba 100644
--- a/src/com/android/settings/slices/SliceBackgroundWorker.java
+++ b/src/com/android/settings/slices/SliceBackgroundWorker.java
@@ -59,10 +59,6 @@
mUri = uri;
}
- protected Uri getUri() {
- return mUri;
- }
-
/**
* Returns the singleton instance of the {@link SliceBackgroundWorker} for specified {@link Uri}
* if exists
@@ -148,7 +144,14 @@
if (needNotify) {
mCachedResults = results;
- mContext.getContentResolver().notifyChange(mUri, null);
+ notifySliceChange();
}
}
-}
+
+ /**
+ * Notify that data was updated and attempt to sync changes to the Slice.
+ */
+ protected void notifySliceChange() {
+ mContext.getContentResolver().notifyChange(mUri, null);
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/slices/SlicePreferenceController.java b/src/com/android/settings/slices/SlicePreferenceController.java
index d7fcc18..89294c7 100644
--- a/src/com/android/settings/slices/SlicePreferenceController.java
+++ b/src/com/android/settings/slices/SlicePreferenceController.java
@@ -82,6 +82,8 @@
@Override
public void onChanged(Slice slice) {
- mSlicePreference.onSliceUpdated(slice);
+ if (slice != null) {
+ mSlicePreference.onSliceUpdated(slice);
+ }
}
}
diff --git a/src/com/android/settings/vpn2/AppDialogFragment.java b/src/com/android/settings/vpn2/AppDialogFragment.java
index 0d0022f..2f9cd7a 100644
--- a/src/com/android/settings/vpn2/AppDialogFragment.java
+++ b/src/com/android/settings/vpn2/AppDialogFragment.java
@@ -164,7 +164,8 @@
final int userId = getUserId();
try {
if (mPackageInfo.packageName.equals(VpnUtils.getConnectedPackage(mService, userId))) {
- mService.setAlwaysOnVpnPackage(userId, null, /* lockdownEnabled */ false);
+ mService.setAlwaysOnVpnPackage(userId, null, /* lockdownEnabled */ false,
+ /* lockdownWhitelist */ null);
mService.prepareVpn(mPackageInfo.packageName, VpnConfig.LEGACY_VPN, userId);
}
} catch (RemoteException e) {
diff --git a/src/com/android/settings/vpn2/AppManagementFragment.java b/src/com/android/settings/vpn2/AppManagementFragment.java
index 1571216..5f46446 100644
--- a/src/com/android/settings/vpn2/AppManagementFragment.java
+++ b/src/com/android/settings/vpn2/AppManagementFragment.java
@@ -225,7 +225,7 @@
private boolean setAlwaysOnVpn(boolean isEnabled, boolean isLockdown) {
return mConnectivityManager.setAlwaysOnVpnPackageForUser(mUserId,
- isEnabled ? mPackageName : null, isLockdown);
+ isEnabled ? mPackageName : null, isLockdown, /* lockdownWhitelist */ null);
}
private void updateUI() {
diff --git a/src/com/android/settings/vpn2/ConfigDialogFragment.java b/src/com/android/settings/vpn2/ConfigDialogFragment.java
index ec927ae..01b20f0 100644
--- a/src/com/android/settings/vpn2/ConfigDialogFragment.java
+++ b/src/com/android/settings/vpn2/ConfigDialogFragment.java
@@ -199,7 +199,7 @@
final ConnectivityManager conn = ConnectivityManager.from(mContext);
conn.setAlwaysOnVpnPackageForUser(UserHandle.myUserId(), null,
- /* lockdownEnabled */ false);
+ /* lockdownEnabled */ false, /* lockdownWhitelist */ null);
VpnUtils.setLockdownVpn(mContext, profile.key);
} else {
// update only if lockdown vpn has been changed
diff --git a/src/com/android/settings/wifi/slice/WifiSlice.java b/src/com/android/settings/wifi/slice/WifiSlice.java
index 1c79c1d..88fa8b2 100644
--- a/src/com/android/settings/wifi/slice/WifiSlice.java
+++ b/src/com/android/settings/wifi/slice/WifiSlice.java
@@ -292,11 +292,12 @@
@Override
public void onWifiStateChanged(int state) {
- mContext.getContentResolver().notifyChange(getUri(), null);
+ notifySliceChange();
}
@Override
public void onConnectedChanged() {
+ notifySliceChange();
}
@Override
diff --git a/tests/robotests/src/com/android/settings/accounts/CrossProfileCalendarPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accounts/CrossProfileCalendarPreferenceControllerTest.java
index bf4dec6..c6a48a8 100644
--- a/tests/robotests/src/com/android/settings/accounts/CrossProfileCalendarPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/accounts/CrossProfileCalendarPreferenceControllerTest.java
@@ -32,6 +32,7 @@
import android.content.Context;
import android.os.UserHandle;
import android.provider.Settings;
+import android.util.ArraySet;
import com.android.settingslib.RestrictedSwitchPreference;
@@ -45,6 +46,9 @@
import org.robolectric.Shadows;
import org.robolectric.shadows.ShadowDevicePolicyManager;
+import java.util.Arrays;
+import java.util.Collections;
+
@RunWith(RobolectricTestRunner.class)
public class CrossProfileCalendarPreferenceControllerTest {
@@ -123,7 +127,17 @@
@Test
public void updateState_somePackagesAllowed_preferenceShouldNotBeDisabled() throws Exception {
dpm.setProfileOwner(TEST_COMPONENT_NAME);
- dpm.addCrossProfileCalendarPackage(TEST_COMPONENT_NAME, TEST_PACKAGE_NAME);
+ dpm.setCrossProfileCalendarPackages(TEST_COMPONENT_NAME,
+ Collections.singleton(TEST_PACKAGE_NAME));
+
+ mController.updateState(mPreference);
+ verify(mPreference).setDisabledByAdmin(null);
+ }
+
+ @Test
+ public void updateState_allPackagesAllowed_preferenceShouldNotBeDisabled() throws Exception {
+ dpm.setProfileOwner(TEST_COMPONENT_NAME);
+ dpm.setCrossProfileCalendarPackages(TEST_COMPONENT_NAME, null);
mController.updateState(mPreference);
verify(mPreference).setDisabledByAdmin(null);
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbConnectionBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbConnectionBroadcastReceiverTest.java
index 1da97f5..b6bbe8a 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbConnectionBroadcastReceiverTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbConnectionBroadcastReceiverTest.java
@@ -15,6 +15,8 @@
*/
package com.android.settings.connecteddevice.usb;
+import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED;
+import static android.hardware.usb.UsbPortStatus.CONTAMINANT_PROTECTION_NONE;
import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE;
import static android.hardware.usb.UsbPortStatus.DATA_ROLE_NONE;
import static android.hardware.usb.UsbPortStatus.POWER_ROLE_NONE;
@@ -101,7 +103,8 @@
final Intent intent = new Intent();
intent.setAction(UsbManager.ACTION_USB_PORT_CHANGED);
final UsbPortStatus status = new UsbPortStatus(0, POWER_ROLE_SINK,
- DATA_ROLE_DEVICE, 0);
+ DATA_ROLE_DEVICE, 0, CONTAMINANT_PROTECTION_NONE,
+ CONTAMINANT_DETECTION_NOT_SUPPORTED);
intent.putExtra(UsbManager.EXTRA_PORT_STATUS, status);
mReceiver.onReceive(mContext, intent);
diff --git a/tests/robotests/src/com/android/settings/location/RecentLocationAccessPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/RecentLocationAccessPreferenceControllerTest.java
index 3b33558..8cba1de 100644
--- a/tests/robotests/src/com/android/settings/location/RecentLocationAccessPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/location/RecentLocationAccessPreferenceControllerTest.java
@@ -36,6 +36,7 @@
import com.android.settingslib.widget.LayoutPreference;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -77,6 +78,7 @@
/** Verifies the title text, details text are correct, and the click listener is set. */
@Test
+ @Ignore
public void updateState_whenAppListIsEmpty_shouldDisplayTitleTextAndDetailsText() {
doReturn(new ArrayList<>()).when(mRecentLocationApps).getAppListSorted();
mController.displayPreference(mScreen);
diff --git a/tests/robotests/src/com/android/settings/password/ChooseLockGenericControllerTest.java b/tests/robotests/src/com/android/settings/password/ChooseLockGenericControllerTest.java
index cbc5765..2b7bdeb 100644
--- a/tests/robotests/src/com/android/settings/password/ChooseLockGenericControllerTest.java
+++ b/tests/robotests/src/com/android/settings/password/ChooseLockGenericControllerTest.java
@@ -16,15 +16,22 @@
package com.android.settings.password;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
import static org.robolectric.RuntimeEnvironment.application;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManager.PasswordComplexity;
import android.content.ComponentName;
import com.android.settings.R;
@@ -58,11 +65,7 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
- mController = new ChooseLockGenericController(
- application,
- 0 /* userId */,
- mDevicePolicyManager,
- mManagedLockPasswordProvider);
+ mController = createController(PASSWORD_COMPLEXITY_NONE);
SettingsShadowResources.overrideResource(R.bool.config_hide_none_security_option, false);
SettingsShadowResources.overrideResource(R.bool.config_hide_swipe_security_option, false);
}
@@ -225,4 +228,44 @@
assertThat(upgradedQuality).named("upgradedQuality")
.isEqualTo(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC);
}
+
+ @Test
+ public void upgradeQuality_complexityHigh_minQualityNumericComplex() {
+ when(mDevicePolicyManager.getPasswordQuality(nullable(ComponentName.class), anyInt()))
+ .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
+ ChooseLockGenericController controller = createController(PASSWORD_COMPLEXITY_HIGH);
+
+ assertThat(controller.upgradeQuality(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED))
+ .isEqualTo(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX);
+ }
+
+ @Test
+ public void upgradeQuality_complexityMedium_minQualityNumericComplex() {
+ when(mDevicePolicyManager.getPasswordQuality(nullable(ComponentName.class), anyInt()))
+ .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
+ ChooseLockGenericController controller = createController(PASSWORD_COMPLEXITY_MEDIUM);
+
+ assertThat(controller.upgradeQuality(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED))
+ .isEqualTo(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX);
+ }
+
+ @Test
+ public void upgradeQuality_complexityLow_minQualitySomething() {
+ when(mDevicePolicyManager.getPasswordQuality(nullable(ComponentName.class), anyInt()))
+ .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
+ ChooseLockGenericController controller = createController(PASSWORD_COMPLEXITY_LOW);
+
+ assertThat(controller.upgradeQuality(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED))
+ .isEqualTo(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
+ }
+
+ private ChooseLockGenericController createController(
+ @PasswordComplexity int minPasswordComplexity) {
+ return new ChooseLockGenericController(
+ application,
+ 0 /* userId */,
+ minPasswordComplexity,
+ mDevicePolicyManager,
+ mManagedLockPasswordProvider);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java b/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java
index e324214..a1db12c 100644
--- a/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java
+++ b/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java
@@ -16,6 +16,15 @@
package com.android.settings.password;
+import static android.app.admin.DevicePolicyManager.ACTION_SET_NEW_PASSWORD;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+
+import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CALLER_APP_NAME;
+import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY;
+
import static com.google.common.truth.Truth.assertThat;
import static org.robolectric.RuntimeEnvironment.application;
@@ -27,8 +36,10 @@
import android.provider.Settings.Global;
import androidx.annotation.Nullable;
+import androidx.preference.Preference;
import com.android.internal.widget.LockPatternUtils;
+import com.android.settings.R;
import com.android.settings.biometrics.BiometricEnrollBase;
import com.android.settings.password.ChooseLockGeneric.ChooseLockGenericFragment;
import com.android.settings.search.SearchFeatureProvider;
@@ -36,6 +47,7 @@
import com.android.settings.testutils.shadow.ShadowStorageManager;
import com.android.settings.testutils.shadow.ShadowUserManager;
import com.android.settings.testutils.shadow.ShadowUtils;
+import com.android.settingslib.widget.FooterPreference;
import org.junit.After;
import org.junit.Before;
@@ -113,6 +125,70 @@
}
@Test
+ public void updatePreferencesOrFinish_footerPreferenceAddedHighComplexityText() {
+ ShadowStorageManager.setIsFileEncryptedNativeOrEmulated(false);
+ Intent intent = new Intent()
+ .putExtra(EXTRA_KEY_CALLER_APP_NAME, "app name")
+ .putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_HIGH);
+ initActivity(intent);
+ CharSequence expectedTitle =
+ mActivity.getString(R.string.unlock_footer_high_complexity_requested, "app name");
+
+ mFragment.updatePreferencesOrFinish(false /* isRecreatingActivity */);
+ FooterPreference footer = mFragment.findPreference(FooterPreference.KEY_FOOTER);
+
+ assertThat(footer.getTitle()).isEqualTo(expectedTitle);
+ }
+
+ @Test
+ public void updatePreferencesOrFinish_footerPreferenceAddedMediumComplexityText() {
+ ShadowStorageManager.setIsFileEncryptedNativeOrEmulated(false);
+ Intent intent = new Intent()
+ .putExtra(EXTRA_KEY_CALLER_APP_NAME, "app name")
+ .putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_MEDIUM);
+ initActivity(intent);
+ CharSequence expectedTitle =
+ mActivity.getString(R.string.unlock_footer_medium_complexity_requested, "app name");
+
+ mFragment.updatePreferencesOrFinish(false /* isRecreatingActivity */);
+ FooterPreference footer = mFragment.findPreference(FooterPreference.KEY_FOOTER);
+
+ assertThat(footer.getTitle()).isEqualTo(expectedTitle);
+ }
+
+ @Test
+ public void updatePreferencesOrFinish_footerPreferenceAddedLowComplexityText() {
+ ShadowStorageManager.setIsFileEncryptedNativeOrEmulated(false);
+ Intent intent = new Intent()
+ .putExtra(EXTRA_KEY_CALLER_APP_NAME, "app name")
+ .putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_LOW);
+ initActivity(intent);
+ CharSequence expectedTitle =
+ mActivity.getString(R.string.unlock_footer_low_complexity_requested, "app name");
+
+ mFragment.updatePreferencesOrFinish(false /* isRecreatingActivity */);
+ FooterPreference footer = mFragment.findPreference(FooterPreference.KEY_FOOTER);
+
+ assertThat(footer.getTitle()).isEqualTo(expectedTitle);
+ }
+
+ @Test
+ public void updatePreferencesOrFinish_footerPreferenceAddedNoneComplexityText() {
+ ShadowStorageManager.setIsFileEncryptedNativeOrEmulated(false);
+ Intent intent = new Intent()
+ .putExtra(EXTRA_KEY_CALLER_APP_NAME, "app name")
+ .putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE);
+ initActivity(intent);
+ CharSequence expectedTitle =
+ mActivity.getString(R.string.unlock_footer_none_complexity_requested, "app name");
+
+ mFragment.updatePreferencesOrFinish(false /* isRecreatingActivity */);
+ FooterPreference footer = mFragment.findPreference(FooterPreference.KEY_FOOTER);
+
+ assertThat(footer.getTitle()).isEqualTo(expectedTitle);
+ }
+
+ @Test
public void onActivityResult_requestcode0_shouldNotFinish() {
initActivity(null);
@@ -165,6 +241,48 @@
assertThat(mActivity.isFinishing()).isTrue();
}
+ @Test
+ public void onPreferenceTreeClick_fingerprintPassesMinComplexityInfoOntoNextActivity() {
+ Intent intent = new Intent(ACTION_SET_NEW_PASSWORD)
+ .putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_HIGH)
+ .putExtra(EXTRA_KEY_CALLER_APP_NAME, "app name");
+ initActivity(intent);
+
+ Preference fingerprintPref = new Preference(application);
+ fingerprintPref.setKey("unlock_skip_fingerprint");
+ boolean result = mFragment.onPreferenceTreeClick(fingerprintPref);
+
+ assertThat(result).isTrue();
+ Intent actualIntent = shadowOf(mActivity).getNextStartedActivityForResult().intent;
+ assertThat(actualIntent.hasExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY)).isTrue();
+ assertThat(actualIntent.getIntExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE))
+ .isEqualTo(PASSWORD_COMPLEXITY_HIGH);
+ assertThat(actualIntent.hasExtra(EXTRA_KEY_CALLER_APP_NAME)).isTrue();
+ assertThat(actualIntent.getStringExtra(EXTRA_KEY_CALLER_APP_NAME))
+ .isEqualTo("app name");
+ }
+
+ @Test
+ public void onPreferenceTreeClick_facePassesMinComplexityInfoOntoNextActivity() {
+ Intent intent = new Intent(ACTION_SET_NEW_PASSWORD)
+ .putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_HIGH)
+ .putExtra(EXTRA_KEY_CALLER_APP_NAME, "app name");
+ initActivity(intent);
+
+ Preference facePref = new Preference(application);
+ facePref.setKey("unlock_skip_face");
+ boolean result = mFragment.onPreferenceTreeClick(facePref);
+
+ assertThat(result).isTrue();
+ Intent actualIntent = shadowOf(mActivity).getNextStartedActivityForResult().intent;
+ assertThat(actualIntent.hasExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY)).isTrue();
+ assertThat(actualIntent.getIntExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE))
+ .isEqualTo(PASSWORD_COMPLEXITY_HIGH);
+ assertThat(actualIntent.hasExtra(EXTRA_KEY_CALLER_APP_NAME)).isTrue();
+ assertThat(actualIntent.getStringExtra(EXTRA_KEY_CALLER_APP_NAME))
+ .isEqualTo("app name");
+ }
+
private void initActivity(@Nullable Intent intent) {
if (intent == null) {
intent = new Intent();
diff --git a/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java b/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java
index 367cb4c..404d205 100644
--- a/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java
+++ b/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java
@@ -16,19 +16,37 @@
package com.android.settings.password;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
+import static com.android.internal.widget.LockPatternUtils.PASSWORD_TYPE_KEY;
+import static com.android.settings.password.ChooseLockGeneric.CONFIRM_CREDENTIALS;
+import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY;
+
import static com.google.common.truth.Truth.assertThat;
import static org.robolectric.RuntimeEnvironment.application;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManager.PasswordComplexity;
import android.content.Intent;
import android.os.UserHandle;
-import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settings.password.ChooseLockPassword.ChooseLockPasswordFragment;
import com.android.settings.password.ChooseLockPassword.IntentBuilder;
import com.android.settings.testutils.shadow.SettingsShadowResources;
+import com.android.settings.testutils.shadow.ShadowDevicePolicyManager;
+import com.android.settings.testutils.shadow.ShadowLockPatternUtils;
import com.android.settings.testutils.shadow.ShadowUtils;
import com.google.android.setupdesign.GlifLayout;
@@ -44,13 +62,21 @@
import org.robolectric.shadows.ShadowDrawable;
@RunWith(RobolectricTestRunner.class)
-@Config(shadows = {SettingsShadowResources.class, ShadowUtils.class})
+@Config(shadows = {
+ SettingsShadowResources.class,
+ ShadowUtils.class,
+ ShadowDevicePolicyManager.class,
+})
public class ChooseLockPasswordTest {
+ private ShadowDevicePolicyManager mShadowDpm;
+
@Before
public void setUp() {
SettingsShadowResources.overrideResource(
com.android.internal.R.string.config_headlineFontFamily, "");
+ mShadowDpm = ShadowDevicePolicyManager.getShadow();
+ mShadowDpm.setPasswordMaximumLength(16);
}
@After
@@ -72,7 +98,7 @@
assertThat(intent.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD))
.named("EXTRA_KEY_PASSWORD")
.isEqualTo("password");
- assertThat(intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, 0))
+ assertThat(intent.getIntExtra(PASSWORD_TYPE_KEY, 0))
.named("PASSWORD_TYPE_KEY")
.isEqualTo(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC);
assertThat(intent.getIntExtra(Intent.EXTRA_USER_ID, 0))
@@ -84,7 +110,7 @@
public void intentBuilder_setChallenge_shouldAddExtras() {
Intent intent = new IntentBuilder(application)
.setChallenge(12345L)
- .setPasswordQuality(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC)
+ .setPasswordQuality(PASSWORD_QUALITY_ALPHANUMERIC)
.setUserId(123)
.build();
@@ -94,15 +120,214 @@
assertThat(intent.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0L))
.named("EXTRA_KEY_CHALLENGE")
.isEqualTo(12345L);
- assertThat(intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, 0))
+ assertThat(intent.getIntExtra(PASSWORD_TYPE_KEY, 0))
.named("PASSWORD_TYPE_KEY")
- .isEqualTo(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC);
+ .isEqualTo(PASSWORD_QUALITY_ALPHANUMERIC);
assertThat(intent.getIntExtra(Intent.EXTRA_USER_ID, 0))
.named("EXTRA_USER_ID")
.isEqualTo(123);
}
@Test
+ public void intentBuilder_setMinComplexityMedium_hasMinComplexityExtraMedium() {
+ Intent intent = new IntentBuilder(application)
+ .setRequestedMinComplexity(PASSWORD_COMPLEXITY_MEDIUM)
+ .build();
+
+ assertThat(intent.hasExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY)).isTrue();
+ assertThat(intent.getIntExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE))
+ .isEqualTo(PASSWORD_COMPLEXITY_MEDIUM);
+ }
+
+ @Test
+ public void intentBuilder_setMinComplexityNotCalled() {
+ Intent intent = new IntentBuilder(application).build();
+
+ assertThat(intent.hasExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY)).isFalse();
+ }
+
+ @Test
+ public void processAndValidatePasswordRequirements_noMinPasswordComplexity() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_ALPHABETIC);
+ mShadowDpm.setPasswordMinimumLength(10);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_NONE,
+ /* passwordType= */ PASSWORD_QUALITY_ALPHABETIC,
+ /* userEnteredPassword= */ "",
+ "Must contain at least 1 letter",
+ "Must be at least 10 characters");
+ }
+
+ @Test
+ public void processAndValidatePasswordRequirements_minPasswordComplexityStricter_pin() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_SOMETHING);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_HIGH,
+ /* passwordType= */ PASSWORD_QUALITY_NUMERIC,
+ /* userEnteredPassword= */ "",
+ "PIN must be at least 8 digits");
+ }
+
+ @Test
+ public void processAndValidatePasswordRequirements_minPasswordComplexityStricter_password() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_SOMETHING);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_MEDIUM,
+ /* passwordType= */ PASSWORD_QUALITY_ALPHABETIC,
+ /* userEnteredPassword= */ "",
+ "Must contain at least 1 letter",
+ "Must be at least 4 characters");
+ }
+
+ @Test
+ public void processAndValidatePasswordRequirements_dpmRestrictionsStricter_password() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_ALPHANUMERIC);
+ mShadowDpm.setPasswordMinimumLength(9);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_LOW,
+ /* passwordType= */ PASSWORD_QUALITY_ALPHABETIC,
+ /* userEnteredPassword= */ "",
+ "Must contain at least 1 letter",
+ "Must contain at least 1 numerical digit",
+ "Must be at least 9 characters");
+ }
+
+ @Test
+ public void processAndValidatePasswordRequirements_dpmLengthLonger_pin() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_NUMERIC);
+ mShadowDpm.setPasswordMinimumLength(11);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_MEDIUM,
+ /* passwordType= */ PASSWORD_QUALITY_NUMERIC,
+ /* userEnteredPassword= */ "",
+ "PIN must be at least 11 digits");
+ }
+
+ @Test
+ public void processAndValidatePasswordRequirements_dpmQualityComplex() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_COMPLEX);
+ mShadowDpm.setPasswordMinimumSymbols(2);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_HIGH,
+ /* passwordType= */ PASSWORD_QUALITY_ALPHABETIC,
+ /* userEnteredPassword= */ "",
+ "Must contain at least 2 special symbols",
+ "Must be at least 6 characters");
+ }
+
+ @Test
+ @Config(shadows = ShadowLockPatternUtils.class)
+ public void processAndValidatePasswordRequirements_numericComplexNoMinComplexity_pinRequested() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_NUMERIC_COMPLEX);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_NONE,
+ /* passwordType= */ PASSWORD_QUALITY_NUMERIC,
+ /* userEnteredPassword= */ "12345678",
+ "Ascending, descending, or repeated sequence of digits isn't allowed");
+ }
+
+ @Test
+ @Config(shadows = ShadowLockPatternUtils.class)
+ public void processAndValidatePasswordRequirements_numericComplexNoMinComplexity_passwordRequested() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_NUMERIC_COMPLEX);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_NONE,
+ /* passwordType= */ PASSWORD_QUALITY_ALPHABETIC,
+ /* userEnteredPassword= */ "12345678",
+ "Ascending, descending, or repeated sequence of digits isn't allowed");
+ }
+
+ @Test
+ @Config(shadows = ShadowLockPatternUtils.class)
+ public void processAndValidatePasswordRequirements_numericComplexHighComplexity_pinRequested() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_NUMERIC_COMPLEX);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_HIGH,
+ /* passwordType= */ PASSWORD_QUALITY_NUMERIC,
+ /* userEnteredPassword= */ "12345678",
+ "Ascending, descending, or repeated sequence of digits isn't allowed");
+ }
+
+ @Test
+ @Config(shadows = ShadowLockPatternUtils.class)
+ public void processAndValidatePasswordRequirements_numericHighComplexity_pinRequested() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_NUMERIC);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_HIGH,
+ /* passwordType= */ PASSWORD_QUALITY_NUMERIC,
+ /* userEnteredPassword= */ "12345678",
+ "Ascending, descending, or repeated sequence of digits isn't allowed");
+ }
+
+ @Test
+ @Config(shadows = ShadowLockPatternUtils.class)
+ public void processAndValidatePasswordRequirements_numericComplexLowComplexity_passwordRequested() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_NUMERIC_COMPLEX);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_LOW,
+ /* passwordType= */ PASSWORD_QUALITY_ALPHABETIC,
+ /* userEnteredPassword= */ "12345678",
+ "Must contain at least 1 letter");
+ }
+
+ @Test
+ public void processAndValidatePasswordRequirements_requirementsUpdateAccordingToMinComplexityAndUserInput_empty() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_UNSPECIFIED);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_HIGH,
+ /* passwordType= */ PASSWORD_QUALITY_ALPHABETIC,
+ /* userEnteredPassword= */ "",
+ "Must contain at least 1 letter",
+ "Must be at least 6 characters");
+ }
+
+ @Test
+ public void processAndValidatePasswordRequirements_requirementsUpdateAccordingToMinComplexityAndUserInput_numeric() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_UNSPECIFIED);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_HIGH,
+ /* passwordType= */ PASSWORD_QUALITY_ALPHABETIC,
+ /* userEnteredPassword= */ "1",
+ "Must contain at least 1 letter",
+ "Must be at least 6 characters");
+ }
+
+ @Test
+ public void processAndValidatePasswordRequirements_requirementsUpdateAccordingToMinComplexityAndUserInput_alphabetic() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_UNSPECIFIED);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_HIGH,
+ /* passwordType= */ PASSWORD_QUALITY_ALPHABETIC,
+ /* userEnteredPassword= */ "b",
+ "Must be at least 6 characters");
+ }
+
+ @Test
+ public void processAndValidatePasswordRequirements_requirementsUpdateAccordingToMinComplexityAndUserInput_alphanumeric() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_UNSPECIFIED);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_HIGH,
+ /* passwordType= */ PASSWORD_QUALITY_ALPHABETIC,
+ /* userEnteredPassword= */ "b1",
+ "Must be at least 6 characters");
+ }
+
+ @Test
public void assertThat_chooseLockIconChanged_WhenFingerprintExtraSet() {
ShadowDrawable drawable = setActivityAndGetIconDrawable(true);
assertThat(drawable.getCreatedFromResId()).isEqualTo(R.drawable.ic_fingerprint_header);
@@ -132,4 +357,18 @@
ChooseLockPasswordFragment fragment = getChooseLockPasswordFragment(passwordActivity);
return Shadows.shadowOf(((GlifLayout) fragment.getView()).getIcon());
}
+
+ private void assertPasswordValidationResult(@PasswordComplexity int minComplexity,
+ int passwordType, String userEnteredPassword, String... expectedValidationResult) {
+ Intent intent = new Intent();
+ intent.putExtra(CONFIRM_CREDENTIALS, false);
+ intent.putExtra(PASSWORD_TYPE_KEY, passwordType);
+ intent.putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, minComplexity);
+ ChooseLockPassword activity = buildChooseLockPasswordActivity(intent);
+ ChooseLockPasswordFragment fragment = getChooseLockPasswordFragment(activity);
+ int validateResult = fragment.validatePassword(userEnteredPassword);
+ String[] messages = fragment.convertErrorCodeToMessages(validateResult);
+
+ assertThat(messages).asList().containsExactly((Object[]) expectedValidationResult);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/password/PasswordUtilsTest.java b/tests/robotests/src/com/android/settings/password/PasswordUtilsTest.java
new file mode 100644
index 0000000..845d346
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/password/PasswordUtilsTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2019 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.password;
+
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import static com.android.settings.password.PasswordUtils.getCallingAppLabel;
+import static com.android.settings.password.PasswordUtils.isCallingAppPermitted;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.app.IActivityManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.settings.testutils.shadow.ShadowActivityManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowActivityManager.class})
+public class PasswordUtilsTest {
+
+ private static final String PACKAGE_NAME = "com.android.app";
+ private static final String PERMISSION = "com.testing.permission";
+ private static final int UID = 1234;
+
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private ApplicationInfo mApplicationInfo;
+ @Mock
+ private IActivityManager mActivityService;
+ @Mock
+ private IBinder mActivityToken;
+
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = spy(RuntimeEnvironment.application);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ ShadowActivityManager.setService(mActivityService);
+ }
+
+ @Test
+ public void getCallingAppLabel_activityServiceThrowsRemoteException_returnsNull()
+ throws Exception {
+ when(mActivityService.getLaunchedFromPackage(mActivityToken))
+ .thenThrow(new RemoteException());
+
+ assertThat(getCallingAppLabel(mContext, mActivityToken)).isNull();
+ }
+
+ @Test
+ public void getCallingAppLabel_activityServiceReturnsSettingsApp_returnsNull()
+ throws Exception {
+ when(mActivityService.getLaunchedFromPackage(mActivityToken))
+ .thenReturn("com.android.settings");
+
+ assertThat(getCallingAppLabel(mContext, mActivityToken)).isNull();
+ }
+
+ @Test
+ public void getCallingAppLabel_packageManagerThrowsNameNotFound_returnsNull() throws Exception {
+ when(mActivityService.getLaunchedFromPackage(mActivityToken))
+ .thenReturn(PACKAGE_NAME);
+ when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+ .thenThrow(new NameNotFoundException());
+
+ assertThat(getCallingAppLabel(mContext, mActivityToken)).isNull();
+ }
+
+ @Test
+ public void getCallingAppLabel_returnsLabel() throws Exception {
+ when(mActivityService.getLaunchedFromPackage(mActivityToken))
+ .thenReturn(PACKAGE_NAME);
+ when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(mApplicationInfo);
+ when(mApplicationInfo.loadLabel(mPackageManager)).thenReturn("label");
+
+ assertThat(getCallingAppLabel(mContext, mActivityToken)).isEqualTo("label");
+ }
+
+ @Test
+ public void isCallingAppPermitted_permissionGranted_returnsTrue() throws Exception {
+ when(mActivityService.getLaunchedFromUid(mActivityToken)).thenReturn(UID);
+ when(mContext.checkPermission(PERMISSION, -1, UID)).thenReturn(PERMISSION_GRANTED);
+
+ assertThat(isCallingAppPermitted(mContext, mActivityToken, PERMISSION)).isTrue();
+ }
+
+ @Test
+ public void isCallingAppPermitted_permissionDenied_returnsFalse() throws Exception {
+ when(mActivityService.getLaunchedFromUid(mActivityToken)).thenReturn(UID);
+ when(mContext.checkPermission(PERMISSION, -1, UID)).thenReturn(PERMISSION_DENIED);
+
+ assertThat(isCallingAppPermitted(mContext, mActivityToken, PERMISSION)).isFalse();
+ }
+
+ @Test
+ public void isCallingAppPermitted_throwsRemoteException_returnsFalse() throws Exception {
+ when(mActivityService.getLaunchedFromUid(mActivityToken)).thenThrow(new RemoteException());
+
+ assertThat(isCallingAppPermitted(mContext, mActivityToken, PERMISSION)).isFalse();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/password/SetNewPasswordActivityTest.java b/tests/robotests/src/com/android/settings/password/SetNewPasswordActivityTest.java
index 99738e7..d1b2b74 100644
--- a/tests/robotests/src/com/android/settings/password/SetNewPasswordActivityTest.java
+++ b/tests/robotests/src/com/android/settings/password/SetNewPasswordActivityTest.java
@@ -16,6 +16,16 @@
package com.android.settings.password;
+import static android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY;
+import static android.app.admin.DevicePolicyManager.ACTION_SET_NEW_PARENT_PROFILE_PASSWORD;
+import static android.app.admin.DevicePolicyManager.ACTION_SET_NEW_PASSWORD;
+import static android.app.admin.DevicePolicyManager.EXTRA_PASSWORD_COMPLEXITY;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+
+import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CALLER_APP_NAME;
+import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY;
+
import static com.google.common.truth.Truth.assertThat;
import android.content.ComponentName;
@@ -23,6 +33,8 @@
import android.os.Bundle;
import android.provider.Settings;
+import com.android.settings.testutils.shadow.ShadowPasswordUtils;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -31,11 +43,14 @@
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowActivity;
@RunWith(RobolectricTestRunner.class)
public class SetNewPasswordActivityTest {
+ private static final String APP_LABEL = "label";
+
private int mProvisioned;
@Before
@@ -48,6 +63,7 @@
public void tearDown() {
Settings.Global.putInt(RuntimeEnvironment.application.getContentResolver(),
Settings.Global.DEVICE_PROVISIONED, mProvisioned);
+ ShadowPasswordUtils.reset();
}
@Test
@@ -77,4 +93,106 @@
assertThat(intent.getComponent())
.isEqualTo(new ComponentName(activity, SetupChooseLockGeneric.class));
}
+
+ @Test
+ @Config(shadows = {ShadowPasswordUtils.class})
+ public void testLaunchChooseLock_setNewPasswordExtraWithoutPermission() {
+ ShadowPasswordUtils.setCallingAppLabel(APP_LABEL);
+ Settings.Global.putInt(RuntimeEnvironment.application.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 1);
+
+ Intent intent = new Intent(ACTION_SET_NEW_PASSWORD);
+ intent.putExtra(EXTRA_PASSWORD_COMPLEXITY, PASSWORD_COMPLEXITY_HIGH);
+ SetNewPasswordActivity activity =
+ Robolectric.buildActivity(SetNewPasswordActivity.class, intent).create().get();
+
+ ShadowActivity shadowActivity = Shadows.shadowOf(activity);
+ assertThat(shadowActivity.getNextStartedActivityForResult()).isNull();
+ }
+
+ @Test
+ @Config(shadows = {ShadowPasswordUtils.class})
+ public void testLaunchChooseLock_setNewPasswordExtraWithPermission() {
+ ShadowPasswordUtils.setCallingAppLabel(APP_LABEL);
+ ShadowPasswordUtils.addGrantedPermission(GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY);
+ Settings.Global.putInt(RuntimeEnvironment.application.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 1);
+
+ Intent intent = new Intent(ACTION_SET_NEW_PASSWORD);
+ intent.putExtra(EXTRA_PASSWORD_COMPLEXITY, PASSWORD_COMPLEXITY_HIGH);
+ SetNewPasswordActivity activity =
+ Robolectric.buildActivity(SetNewPasswordActivity.class, intent).create().get();
+
+ ShadowActivity shadowActivity = Shadows.shadowOf(activity);
+ Intent actualIntent = shadowActivity.getNextStartedActivityForResult().intent;
+ assertThat(actualIntent.getAction()).isEqualTo(ACTION_SET_NEW_PASSWORD);
+ assertThat(actualIntent.hasExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY)).isTrue();
+ assertThat(actualIntent.getIntExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE))
+ .isEqualTo(PASSWORD_COMPLEXITY_HIGH);
+ assertThat(actualIntent.hasExtra(EXTRA_KEY_CALLER_APP_NAME)).isTrue();
+ assertThat(actualIntent.getStringExtra(EXTRA_KEY_CALLER_APP_NAME)).isEqualTo(APP_LABEL);
+ }
+
+ @Test
+ @Config(shadows = {ShadowPasswordUtils.class})
+ public void testLaunchChooseLock_setNewPasswordExtraInvalidValue() {
+ ShadowPasswordUtils.setCallingAppLabel(APP_LABEL);
+ ShadowPasswordUtils.addGrantedPermission(GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY);
+ Settings.Global.putInt(RuntimeEnvironment.application.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 1);
+
+ Intent intent = new Intent(ACTION_SET_NEW_PASSWORD);
+ intent.putExtra(EXTRA_PASSWORD_COMPLEXITY, -1);
+ SetNewPasswordActivity activity =
+ Robolectric.buildActivity(SetNewPasswordActivity.class, intent).create().get();
+
+ ShadowActivity shadowActivity = Shadows.shadowOf(activity);
+ Intent actualIntent = shadowActivity.getNextStartedActivityForResult().intent;
+ assertThat(actualIntent.getAction()).isEqualTo(ACTION_SET_NEW_PASSWORD);
+ assertThat(actualIntent.hasExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY)).isFalse();
+ assertThat(actualIntent.hasExtra(EXTRA_KEY_CALLER_APP_NAME)).isTrue();
+ assertThat(actualIntent.getStringExtra(EXTRA_KEY_CALLER_APP_NAME)).isEqualTo(APP_LABEL);
+ }
+
+ @Test
+ @Config(shadows = {ShadowPasswordUtils.class})
+ public void testLaunchChooseLock_setNewPasswordExtraNoneComplexity() {
+ ShadowPasswordUtils.setCallingAppLabel(APP_LABEL);
+ ShadowPasswordUtils.addGrantedPermission(GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY);
+ Settings.Global.putInt(RuntimeEnvironment.application.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 1);
+
+ Intent intent = new Intent(ACTION_SET_NEW_PASSWORD);
+ intent.putExtra(EXTRA_PASSWORD_COMPLEXITY, PASSWORD_COMPLEXITY_NONE);
+ SetNewPasswordActivity activity =
+ Robolectric.buildActivity(SetNewPasswordActivity.class, intent).create().get();
+
+ ShadowActivity shadowActivity = Shadows.shadowOf(activity);
+ Intent actualIntent = shadowActivity.getNextStartedActivityForResult().intent;
+ assertThat(actualIntent.getAction()).isEqualTo(ACTION_SET_NEW_PASSWORD);
+ assertThat(actualIntent.hasExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY)).isFalse();
+ assertThat(actualIntent.hasExtra(EXTRA_KEY_CALLER_APP_NAME)).isTrue();
+ assertThat(actualIntent.getStringExtra(EXTRA_KEY_CALLER_APP_NAME)).isEqualTo(APP_LABEL);
+ }
+
+ @Test
+ @Config(shadows = {ShadowPasswordUtils.class})
+ public void testLaunchChooseLock_setNewParentProfilePasswordExtraWithPermission() {
+ ShadowPasswordUtils.setCallingAppLabel(APP_LABEL);
+ ShadowPasswordUtils.addGrantedPermission(GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY);
+ Settings.Global.putInt(RuntimeEnvironment.application.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 1);
+
+ Intent intent = new Intent(ACTION_SET_NEW_PARENT_PROFILE_PASSWORD);
+ intent.putExtra(EXTRA_PASSWORD_COMPLEXITY, PASSWORD_COMPLEXITY_HIGH);
+ SetNewPasswordActivity activity =
+ Robolectric.buildActivity(SetNewPasswordActivity.class, intent).create().get();
+
+ ShadowActivity shadowActivity = Shadows.shadowOf(activity);
+ Intent actualIntent = shadowActivity.getNextStartedActivityForResult().intent;
+ assertThat(actualIntent.getAction()).isEqualTo(ACTION_SET_NEW_PARENT_PROFILE_PASSWORD);
+ assertThat(actualIntent.hasExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY)).isFalse();
+ assertThat(actualIntent.hasExtra(EXTRA_KEY_CALLER_APP_NAME)).isTrue();
+ assertThat(actualIntent.getStringExtra(EXTRA_KEY_CALLER_APP_NAME)).isEqualTo(APP_LABEL);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/password/SetupChooseLockGenericTest.java b/tests/robotests/src/com/android/settings/password/SetupChooseLockGenericTest.java
new file mode 100644
index 0000000..63bdc38
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/password/SetupChooseLockGenericTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2019 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.password;
+
+import static android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
+
+import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Intent;
+
+import com.android.settings.testutils.shadow.ShadowLockPatternUtils;
+import com.android.settings.testutils.shadow.ShadowPasswordUtils;
+import com.android.settings.testutils.shadow.ShadowUserManager;
+import com.android.settings.testutils.shadow.ShadowUtils;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowActivity;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {
+ ShadowUserManager.class,
+ ShadowUtils.class,
+ ShadowLockPatternUtils.class,
+})
+public class SetupChooseLockGenericTest {
+
+ @After
+ public void tearDown() {
+ ShadowPasswordUtils.reset();
+ }
+
+ @Test
+ public void setupChooseLockGenericPasswordComplexityExtraWithoutPermission() {
+ Intent intent = new Intent("com.android.settings.SETUP_LOCK_SCREEN");
+ intent.putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_HIGH);
+ SetupChooseLockGeneric activity =
+ Robolectric.buildActivity(SetupChooseLockGeneric.class, intent).create().get();
+
+ ShadowActivity shadowActivity = Shadows.shadowOf(activity);
+ assertThat(shadowActivity.isFinishing()).isTrue();
+ }
+
+ @Test
+ @Config(shadows = {ShadowPasswordUtils.class})
+ public void setupChooseLockGenericPasswordComplexityExtraWithPermission() {
+ ShadowPasswordUtils.addGrantedPermission(GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY);
+
+ Intent intent = new Intent("com.android.settings.SETUP_LOCK_SCREEN");
+ intent.putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_HIGH);
+ SetupChooseLockGeneric activity =
+ Robolectric.buildActivity(SetupChooseLockGeneric.class, intent).create().get();
+
+ ShadowActivity shadowActivity = Shadows.shadowOf(activity);
+ assertThat(shadowActivity.isFinishing()).isFalse();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowActivityManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowActivityManager.java
index 38d658c..76bdaef 100644
--- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowActivityManager.java
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowActivityManager.java
@@ -17,6 +17,7 @@
package com.android.settings.testutils.shadow;
import android.app.ActivityManager;
+import android.app.IActivityManager;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
@@ -24,6 +25,7 @@
@Implements(ActivityManager.class)
public class ShadowActivityManager {
private static int sCurrentUserId = 0;
+ private static IActivityManager sService = null;
@Implementation
protected static int getCurrentUser() {
@@ -33,4 +35,13 @@
public static void setCurrentUser(int userId) {
sCurrentUserId = userId;
}
+
+ @Implementation
+ public static IActivityManager getService() {
+ return sService;
+ }
+
+ public static void setService(IActivityManager service) {
+ sService = service;
+ }
}
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowDevicePolicyManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowDevicePolicyManager.java
index 6d2dbef..ca75916 100644
--- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowDevicePolicyManager.java
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowDevicePolicyManager.java
@@ -1,5 +1,7 @@
package com.android.settings.testutils.shadow;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -23,6 +25,10 @@
private boolean mIsAdminActiveAsUser = false;
private ComponentName mDeviceOwnerComponentName;
private int mDeviceOwnerUserId = -1;
+ private int mPasswordMinQuality = PASSWORD_QUALITY_UNSPECIFIED;
+ private int mPasswordMaxLength = 16;
+ private int mPasswordMinLength = 0;
+ private int mPasswordMinSymbols = 0;
public void setShortSupportMessageForUser(ComponentName admin, int userHandle, String message) {
mSupportMessagesMap.put(Objects.hash(admin, userHandle), message);
@@ -70,6 +76,42 @@
mDeviceOwnerComponentName = admin;
}
+ @Implementation
+ public int getPasswordQuality(ComponentName admin, int userHandle) {
+ return mPasswordMinQuality;
+ }
+
+ public void setPasswordQuality(int quality) {
+ mPasswordMinQuality = quality;
+ }
+
+ @Implementation
+ public int getPasswordMinimumLength(ComponentName admin, int userHandle) {
+ return mPasswordMinLength;
+ }
+
+ public void setPasswordMinimumLength(int length) {
+ mPasswordMinLength = length;
+ }
+
+ @Implementation
+ public int getPasswordMinimumSymbols(ComponentName admin, int userHandle) {
+ return mPasswordMinSymbols;
+ }
+
+ public void setPasswordMinimumSymbols(int numOfSymbols) {
+ mPasswordMinSymbols = numOfSymbols;
+ }
+
+ @Implementation
+ public int getPasswordMaximumLength(int quality) {
+ return mPasswordMaxLength;
+ }
+
+ public void setPasswordMaximumLength(int length) {
+ mPasswordMaxLength = length;
+ }
+
public static ShadowDevicePolicyManager getShadow() {
return (ShadowDevicePolicyManager) Shadow.extract(
RuntimeEnvironment.application.getSystemService(DevicePolicyManager.class));
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowLockPatternUtils.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowLockPatternUtils.java
index 663ab91..7ce098d 100644
--- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowLockPatternUtils.java
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowLockPatternUtils.java
@@ -59,4 +59,14 @@
public static void setDeviceEncryptionEnabled(boolean deviceEncryptionEnabled) {
sDeviceEncryptionEnabled = deviceEncryptionEnabled;
}
+
+ @Implementation
+ protected byte[] getPasswordHistoryHashFactor(String currentPassword, int userId) {
+ return null;
+ }
+
+ @Implementation
+ protected boolean checkPasswordHistory(String passwordToCheck, byte[] hashFactor, int userId) {
+ return false;
+ }
}
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowPasswordUtils.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowPasswordUtils.java
new file mode 100644
index 0000000..6a5c4ae
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowPasswordUtils.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2019 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.testutils.shadow;
+
+import android.content.Context;
+import android.os.IBinder;
+
+import com.android.settings.password.PasswordUtils;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+@Implements(PasswordUtils.class)
+public class ShadowPasswordUtils {
+
+ private static String sCallingAppLabel;
+ private static Set<String> sGrantedPermissions;
+
+ public static void reset() {
+ sCallingAppLabel = null;
+ sGrantedPermissions = null;
+ }
+
+ @Implementation
+ protected static boolean isCallingAppPermitted(Context context, IBinder activityToken,
+ String permission) {
+ if (sGrantedPermissions == null) {
+ return false;
+ }
+ return sGrantedPermissions.contains(permission);
+ }
+
+ public static void addGrantedPermission(String... permissions) {
+ if (sGrantedPermissions == null) {
+ sGrantedPermissions = new HashSet<>();
+ }
+ sGrantedPermissions.addAll(Arrays.asList(permissions));
+ }
+
+ @Implementation
+ protected static String getCallingAppLabel(Context context, IBinder activityToken) {
+ return sCallingAppLabel;
+ }
+
+ public static void setCallingAppLabel(String label) {
+ sCallingAppLabel = label;
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/wifi/slice/WifiSliceTest.java b/tests/robotests/src/com/android/settings/wifi/slice/WifiSliceTest.java
index ae352cc..dd99e55 100644
--- a/tests/robotests/src/com/android/settings/wifi/slice/WifiSliceTest.java
+++ b/tests/robotests/src/com/android/settings/wifi/slice/WifiSliceTest.java
@@ -139,4 +139,11 @@
verify(mResolver).notifyChange(WIFI_SLICE_URI, null);
}
+
+ @Test
+ public void onConnectedChanged_shouldNotifyChange() {
+ mWifiScanWorker.onConnectedChanged();
+
+ verify(mResolver).notifyChange(WIFI_SLICE_URI, null);
+ }
}