Snap for 9891612 from 2eb8ed2488 to udc-release

Change-Id: I3689b0e7bda335e3f2abcdd3afce8e7bbb551956
This commit is contained in:
Android Build Coastguard Worker
2023-04-06 01:38:25 +00:00
14 changed files with 521 additions and 122 deletions

View File

@@ -2785,6 +2785,7 @@
</intent-filter>
<intent-filter>
<action android:name="com.android.settings.action.SETTINGS" />
<action android:name="com.android.intent.action.SHOW_CONTRAST_DIALOG" />
</intent-filter>
<meta-data android:name="com.android.settings.order" android:value="-40"/>
<meta-data android:name="com.android.settings.category"

View File

@@ -19,13 +19,11 @@
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?android:attr/textColorPrimary">
<group>
<clip-path
android:pathData="M4,2h16v20h-16z"/>
<path
android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" />
<path
android:fillColor="#000000"
android:pathData="M12,4.14l6,2.25v4.71c0,2.12-0.62,4-1.88,5.74c-1.13,1.55-2.48,2.56-4.12,3.09c-1.64-0.52-2.99-1.54-4.12-3.09 C6.62,15.1,6,13.22,6,11.1V6.39L12,4.14 M12,2L4,5v6.1c0,2.53,0.75,4.84,2.26,6.91C7.77,20.09,9.68,21.42,12,22 c2.32-0.58,4.23-1.91,5.74-3.99C19.25,15.94,20,13.63,20,11.1V5L12,2L12,2z" />
<path
android:fillColor="#000000"
android:pathData="M 10.95 15.55 L 7.42 12.02 L 8.85 10.6 L 10.95 12.7 L 15.17 8.47 L 16.6 9.9 Z" />
</vector>
android:pathData="M18.92,4.4L12.56,2.1C12.38,2.03 12.19,2 12,2C11.81,2 11.62,2.03 11.44,2.1L5.08,4.4C4.43,4.63 4,5.25 4,5.94V10.32C4.02,11.07 4.07,11.79 4.17,12.54C4.64,15.72 6.44,19.33 11.37,21.85C11.57,21.95 11.78,22 12,22C12.22,22 12.43,21.95 12.63,21.85C13.08,21.62 13.5,21.37 13.9,21.12C14.04,21.05 14.18,20.96 14.32,20.86C17.98,18.43 19.41,15.32 19.82,12.54C19.92,11.8 19.98,11.07 19.99,10.32V5.94C19.99,5.25 19.56,4.64 18.91,4.4H18.92ZM12.25,19.78C12.1,19.87 11.9,19.87 11.74,19.78C8.5,17.97 6.62,15.43 6.15,12.27C6.06,11.59 6.01,10.94 6,10.32V6.55C6,6.34 6.13,6.15 6.33,6.08L8.26,5.38C8.11,5.89 8.03,6.44 8.03,7.04C8.04,8.91 9.03,10.68 10.7,11.8C11.15,12.08 12.39,12.89 12.78,13.19C13.27,13.57 13.95,14.21 14.26,14.74C15.29,16.52 14.26,18.46 13.1,19.27C12.83,19.45 12.55,19.62 12.25,19.79V19.78ZM17.85,12.24C17.66,13.49 17.26,14.63 16.65,15.68C16.57,15.04 16.37,14.38 15.99,13.74C15.42,12.75 14.33,11.86 14.01,11.61C13.47,11.19 11.94,10.22 11.79,10.12C10.69,9.39 10.04,8.23 10.03,6.99C10.03,5.01 11.25,4.34 11.98,4.12C12.08,4.09 12.19,4.1 12.29,4.13L17.68,6.08C17.88,6.15 18.01,6.34 18.01,6.55V10.28C18,10.94 17.95,11.59 17.86,12.24H17.85Z"
android:fillColor="#000000"/>
</group>
</vector>

View File

@@ -4946,6 +4946,8 @@
<string name="keywords_color_correction">adjust color </string>
<!-- List of synonyms used in the settings search bar to find the “Color inversion”. [CHAR LIMIT=NONE] -->
<string name="keywords_color_inversion">turn screen dark, turn screen light</string>
<!-- List of synonyms used in the settings search bar to find the “Contrast”. [CHAR LIMIT=NONE] -->
<string name="keywords_contrast">color contrast</string>
<!-- List of synonyms used in the settings search bar to find the “Accessibility Menu”. [CHAR LIMIT=NONE] -->
<string name="keywords_accessibility_menu"></string>
<!-- List of synonyms used in the settings search bar to find the “Switch Access”. [CHAR LIMIT=NONE] -->
@@ -12021,6 +12023,15 @@
<!-- Button to close the dialog without saving in screen flash color selection dialog. [CHAR LIMIT=20] -->
<string name="color_selector_dialog_cancel">Cancel</string>
<!-- Title for the contrast preference fragment [CHAR LIMIT=35] -->
<string name="contrast_title">Contrast</string>
<!-- 'Standard' contrast option [CHAR LIMIT=20] -->
<string name="contrast_standard">Standard</string>
<!-- 'Medium' contrast option [CHAR LIMIT=20] -->
<string name="contrast_medium">Medium</string>
<!-- 'High' contrast option [CHAR LIMIT=20] -->
<string name="contrast_high">High</string>
<!-- Warning message when we try to dock an app not supporting multiple instances split into multiple sides [CHAR LIMIT=NONE] -->
<string name="dock_multi_instances_not_supported_text">"This app can only be opened in 1 window"</string>
</resources>

View File

@@ -515,6 +515,12 @@
android:title="@string/transparent_navigation_bar"
android:summary="@string/transparent_navigation_bar_summary" />
<Preference
android:key="contrast_preference"
android:title="@string/contrast_title"
android:persistent="false"
settings:keywords="@string/keywords_contrast" />
</PreferenceCategory>
<PreferenceCategory

View File

@@ -19,6 +19,7 @@ package com.android.settings.development;
import static android.service.quicksettings.TileService.ACTION_QS_TILE_PREFERENCES;
import android.app.Activity;
import android.app.UiModeManager;
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
@@ -63,6 +64,7 @@ import com.android.settings.development.storage.SharedDataPreferenceController;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.actionbar.SearchMenuController;
import com.android.settings.theme.ContrastPreferenceController;
import com.android.settings.widget.SettingsMainSwitchBar;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
@@ -685,6 +687,8 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra
controllers.add(new IngressRateLimitPreferenceController((context)));
controllers.add(new BackAnimationPreferenceController(context, fragment));
controllers.add(new PhantomProcessPreferenceController(context));
controllers.add(new ContrastPreferenceController(
context, context.getSystemService(UiModeManager.class)));
return controllers;
}

View File

@@ -17,6 +17,7 @@
// TODO (b/35202196): move this class out of the root of the package.
package com.android.settings.password;
import static android.app.Activity.RESULT_FIRST_USER;
import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCK_ATTEMPTS_FAILED;
import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
@@ -24,7 +25,6 @@ import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
import android.annotation.Nullable;
import android.app.Dialog;
import android.app.KeyguardManager;
import android.app.RemoteLockscreenValidationResult;
import android.app.RemoteLockscreenValidationSession;
import android.app.admin.DevicePolicyManager;
import android.app.admin.ManagedSubscriptionsPolicy;
@@ -38,7 +38,6 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
import android.service.remotelockscreenvalidation.IRemoteLockscreenValidationCallback;
import android.service.remotelockscreenvalidation.RemoteLockscreenValidationClient;
import android.telecom.TelecomManager;
import android.text.TextUtils;
@@ -55,14 +54,11 @@ import androidx.fragment.app.FragmentManager;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
import com.android.security.SecureBox;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.core.InstrumentedFragment;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import com.google.android.setupdesign.GlifLayout;
/**
* Base fragment to be shared for PIN/Pattern/Password confirmation fragments.
@@ -89,9 +85,13 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
/** Time we wait before clearing a wrong input attempt (e.g. pattern) and the error message. */
protected static final long CLEAR_WRONG_ATTEMPT_TIMEOUT_MS = 3000;
protected static final String FRAGMENT_TAG_REMOTE_LOCKSCREEN_VALIDATION =
"remote_lockscreen_validation";
protected boolean mReturnCredentials = false;
protected boolean mReturnGatekeeperPassword = false;
protected boolean mForceVerifyPath = false;
protected GlifLayout mGlifLayout;
protected CheckBox mCheckBox;
protected Button mCancelButton;
/** Button allowing managed profile password reset, null when is not shown. */
@@ -109,8 +109,8 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
protected BiometricManager mBiometricManager;
@Nullable protected RemoteLockscreenValidationSession mRemoteLockscreenValidationSession;
/** Credential saved so the credential can be set for device if remote validation passes */
@Nullable protected LockscreenCredential mDeviceCredentialGuess;
@Nullable protected RemoteLockscreenValidationClient mRemoteLockscreenValidationClient;
protected RemoteLockscreenValidationFragment mRemoteLockscreenValidationFragment;
private boolean isInternalActivity() {
return (getActivity() instanceof ConfirmLockPassword.InternalActivity)
@@ -136,8 +136,8 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
FeatureFlagUtils.SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION)) {
mRemoteValidation = true;
} else {
Log.e(TAG, "Remote device credential validation not enabled.");
getActivity().finish();
onRemoteLockscreenValidationFailure(
"Remote lockscreen validation not enabled.");
}
}
if (mRemoteValidation) {
@@ -146,23 +146,31 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
RemoteLockscreenValidationSession.class);
if (mRemoteLockscreenValidationSession == null
|| mRemoteLockscreenValidationSession.getRemainingAttempts() == 0) {
Log.e(TAG, "RemoteLockscreenValidationSession is null or "
onRemoteLockscreenValidationFailure("RemoteLockscreenValidationSession is null or "
+ "no more attempts for remote lockscreen validation.");
getActivity().finish();
}
ComponentName remoteLockscreenValidationServiceComponent =
intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName.class);
if (remoteLockscreenValidationServiceComponent == null) {
Log.e(TAG, "RemoteLockscreenValidationService ComponentName is null");
getActivity().finish();
onRemoteLockscreenValidationFailure(
"RemoteLockscreenValidationService ComponentName is null");
}
mRemoteLockscreenValidationClient = RemoteLockscreenValidationClient
.create(getContext(), remoteLockscreenValidationServiceComponent);
if (!mRemoteLockscreenValidationClient.isServiceAvailable()) {
Log.e(TAG, String.format("RemoteLockscreenValidationService at %s is not available",
onRemoteLockscreenValidationFailure(String.format(
"RemoteLockscreenValidationService at %s is not available",
remoteLockscreenValidationServiceComponent.getClassName()));
getActivity().finish();
}
mRemoteLockscreenValidationFragment =
(RemoteLockscreenValidationFragment) getFragmentManager()
.findFragmentByTag(FRAGMENT_TAG_REMOTE_LOCKSCREEN_VALIDATION);
if (mRemoteLockscreenValidationFragment == null) {
mRemoteLockscreenValidationFragment = new RemoteLockscreenValidationFragment();
getFragmentManager().beginTransaction().add(mRemoteLockscreenValidationFragment,
FRAGMENT_TAG_REMOTE_LOCKSCREEN_VALIDATION).commit();
}
}
@@ -194,8 +202,10 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
mCancelButton.setOnClickListener(v -> {
if (hasAlternateButton) {
getActivity().setResult(KeyguardManager.RESULT_ALTERNATE);
getActivity().finish();
} else if (mRemoteValidation) {
onRemoteLockscreenValidationFailure("Forgot lockscreen credential button pressed.");
}
getActivity().finish();
});
setupForgotButtonIfManagedProfile(view);
@@ -299,17 +309,11 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
if (mRemoteLockscreenValidationClient != null) {
mRemoteLockscreenValidationClient.disconnect();
}
if (mDeviceCredentialGuess != null) {
mDeviceCredentialGuess.zeroize();
}
super.onDestroy();
}
protected abstract void authenticationSucceeded();
protected abstract void onRemoteDeviceCredentialValidationResult(
RemoteLockscreenValidationResult result);
public void prepareEnterAnimation() {
}
@@ -411,43 +415,33 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
}
protected void validateGuess(LockscreenCredential credentialGuess) {
if (mCheckBox.isChecked()) {
// Keep credential in memory since user wants to set guess as screen lock.
mDeviceCredentialGuess = credentialGuess;
} else if (mDeviceCredentialGuess != null) {
mDeviceCredentialGuess.zeroize();
}
mRemoteLockscreenValidationClient.validateLockscreenGuess(
encryptDeviceCredentialGuess(credentialGuess.getCredential()),
new IRemoteLockscreenValidationCallback.Stub() {
@Override
public void onSuccess(RemoteLockscreenValidationResult result) {
mHandler.post(()->onRemoteDeviceCredentialValidationResult(result));
}
@Override
public void onFailure(String message) {
Log.e(TAG, "A failure occurred while trying "
+ "to validate lockscreen guess: " + message);
mHandler.post(()->getActivity().finish());
}
});
mRemoteLockscreenValidationFragment.validateLockscreenGuess(
mRemoteLockscreenValidationClient, credentialGuess,
mRemoteLockscreenValidationSession.getSourcePublicKey(), mCheckBox.isChecked());
}
private byte[] encryptDeviceCredentialGuess(byte[] guess) {
try {
byte[] encodedPublicKey = mRemoteLockscreenValidationSession.getSourcePublicKey();
PublicKey publicKey = SecureBox.decodePublicKey(encodedPublicKey);
return SecureBox.encrypt(
publicKey,
/* sharedSecret= */ null,
LockPatternUtils.ENCRYPTED_REMOTE_CREDENTIALS_HEADER,
guess);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
Log.w(TAG, "Error encrypting device credential guess. Returning empty byte[].", e);
return new byte[0];
protected void updateRemoteLockscreenValidationViews() {
if (!mRemoteValidation || mRemoteLockscreenValidationFragment == null) {
return;
}
boolean enable = mRemoteLockscreenValidationFragment.isRemoteValidationInProgress();
mGlifLayout.setProgressBarShown(enable);
mCheckBox.setEnabled(!enable);
mCancelButton.setEnabled(!enable);
}
/**
* Finishes the activity with result code {@link android.app.Activity#RESULT_FIRST_USER}
* after logging the error message.
* @param message Optional message to log.
*/
public void onRemoteLockscreenValidationFailure(String message) {
if (!TextUtils.isEmpty(message)) {
Log.w(TAG, message);
}
getActivity().setResult(RESULT_FIRST_USER);
getActivity().finish();
}
protected abstract void onShowError();

View File

@@ -71,8 +71,6 @@ import com.android.settings.R;
import com.android.settingslib.animation.AppearAnimationUtils;
import com.android.settingslib.animation.DisappearAnimationUtils;
import com.google.android.setupdesign.GlifLayout;
import java.util.ArrayList;
public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
@@ -127,7 +125,8 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
public static class ConfirmLockPasswordFragment extends ConfirmDeviceCredentialBaseFragment
implements OnClickListener, OnEditorActionListener,
CredentialCheckResultTracker.Listener, SaveChosenLockWorkerBase.Listener {
CredentialCheckResultTracker.Listener, SaveChosenLockWorkerBase.Listener,
RemoteLockscreenValidationFragment.Listener {
private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result";
private ImeAwareEditText mPasswordEntry;
private TextViewInputDisabler mPasswordEntryInputDisabler;
@@ -140,7 +139,6 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
private AppearAnimationUtils mAppearAnimationUtils;
private DisappearAnimationUtils mDisappearAnimationUtils;
private boolean mIsManagedProfile;
private GlifLayout mGlifLayout;
private CharSequence mCheckBoxLabel;
// required constructor for fragments
@@ -255,6 +253,7 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
? R.string.lockpassword_forgot_password
: R.string.lockpassword_forgot_pin);
}
updateRemoteLockscreenValidationViews();
}
if (mForgotButton != null) {
@@ -405,6 +404,9 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
mCountdownTimer = null;
}
mCredentialCheckResultTracker.setListener(null);
if (mRemoteLockscreenValidationFragment != null) {
mRemoteLockscreenValidationFragment.setListener(null, /* handler= */ null);
}
}
@Override
@@ -426,6 +428,9 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId));
}
mCredentialCheckResultTracker.setListener(this);
if (mRemoteLockscreenValidationFragment != null) {
mRemoteLockscreenValidationFragment.setListener(this, mHandler);
}
}
@Override
@@ -436,13 +441,17 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
private void updatePasswordEntry() {
final boolean isLockedOut =
mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId) != 0;
mPasswordEntry.setEnabled(!isLockedOut);
mPasswordEntryInputDisabler.setInputEnabled(!isLockedOut);
if (isLockedOut) {
mImm.hideSoftInputFromWindow(mPasswordEntry.getWindowToken(), 0 /*flags*/);
} else {
final boolean isRemoteLockscreenValidationInProgress =
mRemoteLockscreenValidationFragment != null
&& mRemoteLockscreenValidationFragment.isRemoteValidationInProgress();
boolean shouldEnableInput = !isLockedOut && !isRemoteLockscreenValidationInProgress;
mPasswordEntry.setEnabled(shouldEnableInput);
mPasswordEntryInputDisabler.setInputEnabled(shouldEnableInput);
if (shouldEnableInput) {
mPasswordEntry.scheduleShowSoftInput();
mPasswordEntry.requestFocus();
} else {
mImm.hideSoftInputFromWindow(mPasswordEntry.getWindowToken(), /* flags= */0);
}
}
@@ -472,7 +481,8 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
if (mRemoteValidation) {
validateGuess(credential);
mGlifLayout.setProgressBarShown(true);
updateRemoteLockscreenValidationViews();
updatePasswordEntry();
return;
}
@@ -604,14 +614,15 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
}
@Override
protected void onRemoteDeviceCredentialValidationResult(
public void onRemoteLockscreenValidationResult(
RemoteLockscreenValidationResult result) {
switch (result.getResultCode()) {
case RemoteLockscreenValidationResult.RESULT_GUESS_VALID:
if (mCheckBox.isChecked()) {
if (mCheckBox.isChecked() && mRemoteLockscreenValidationFragment
.getLockscreenCredential() != null) {
Log.i(TAG, "Setting device screen lock to the other device's screen lock.");
ChooseLockPassword.SaveAndFinishWorker saveAndFinishWorker =
new ChooseLockPassword.SaveAndFinishWorker();
Log.i(TAG, "Setting device screen lock to the other device's screen lock.");
getFragmentManager().beginTransaction().add(saveAndFinishWorker, null)
.commit();
getFragmentManager().executePendingTransactions();
@@ -619,14 +630,14 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
saveAndFinishWorker.start(
mLockPatternUtils,
/* requestGatekeeperPassword= */ true,
mDeviceCredentialGuess,
mRemoteLockscreenValidationFragment.getLockscreenCredential(),
/* currentCredential= */ null,
mEffectiveUserId);
return;
} else {
mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(),
/* timeoutMs= */ 0, mEffectiveUserId);
}
mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(),
/* timeoutMs= */ 0, mEffectiveUserId);
break;
return;
case RemoteLockscreenValidationResult.RESULT_GUESS_INVALID:
mCredentialCheckResultTracker.setResult(/* matched= */ false, new Intent(),
/* timeoutMs= */ 0, mEffectiveUserId);
@@ -636,12 +647,15 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
(int) result.getTimeoutMillis(), mEffectiveUserId);
break;
case RemoteLockscreenValidationResult.RESULT_NO_REMAINING_ATTEMPTS:
getActivity().finish();
break;
case RemoteLockscreenValidationResult.RESULT_SESSION_EXPIRED:
getActivity().finish();
onRemoteLockscreenValidationFailure(String.format(
"Cannot continue remote lockscreen validation. ResultCode=%d",
result.getResultCode()));
break;
}
mGlifLayout.setProgressBarShown(false);
updateRemoteLockscreenValidationViews();
updatePasswordEntry();
mRemoteLockscreenValidationFragment.clearLockscreenCredential();
}
@Override
@@ -701,21 +715,18 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
}
/**
* Callback for when the device credential guess used for remote validation was set as the
* current device's device credential.
* Callback for when the current device's lockscreen was set to the guess used for
* remote lockscreen validation.
*/
@Override
public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
if (mDeviceCredentialGuess != null) {
mDeviceCredentialGuess.zeroize();
}
Log.i(TAG, "Device lockscreen has been set to remote device's lockscreen.");
mRemoteLockscreenValidationFragment.clearLockscreenCredential();
Intent result = new Intent();
if (mRemoteValidation && containsGatekeeperPasswordHandle(resultData)) {
result.putExtra(EXTRA_KEY_GK_PW_HANDLE, getGatekeeperPasswordHandle(resultData));
}
mGlifLayout.setProgressBarShown(false);
mCredentialCheckResultTracker.setResult(/* matched= */ true, result,
/* timeoutMs= */ 0, mEffectiveUserId);
}

View File

@@ -59,8 +59,6 @@ import com.android.settingslib.animation.AppearAnimationCreator;
import com.android.settingslib.animation.AppearAnimationUtils;
import com.android.settingslib.animation.DisappearAnimationUtils;
import com.google.android.setupdesign.GlifLayout;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -97,7 +95,7 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
public static class ConfirmLockPatternFragment extends ConfirmDeviceCredentialBaseFragment
implements AppearAnimationCreator<Object>, CredentialCheckResultTracker.Listener,
SaveChosenLockWorkerBase.Listener {
SaveChosenLockWorkerBase.Listener, RemoteLockscreenValidationFragment.Listener {
private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result";
@@ -107,7 +105,6 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
private boolean mDisappearing = false;
private CountDownTimer mCountdownTimer;
private GlifLayout mGlifLayout;
private View mSudContent;
// caller-supplied text for various prompts
@@ -239,6 +236,7 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
if (mCancelButton != null && TextUtils.isEmpty(mAlternateButtonText)) {
mCancelButton.setText(R.string.lockpassword_forgot_pattern);
}
updateRemoteLockscreenValidationViews();
}
if (mForgotButton != null) {
@@ -259,6 +257,9 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
mCountdownTimer.cancel();
}
mCredentialCheckResultTracker.setListener(null);
if (mRemoteLockscreenValidationFragment != null) {
mRemoteLockscreenValidationFragment.setListener(null, /* handler= */ null);
}
}
@Override
@@ -281,6 +282,12 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
updateStage(Stage.NeedToUnlock);
}
mCredentialCheckResultTracker.setListener(this);
if (mRemoteLockscreenValidationFragment != null) {
mRemoteLockscreenValidationFragment.setListener(this, mHandler);
if (mRemoteLockscreenValidationFragment.isRemoteValidationInProgress()) {
mLockPatternView.setEnabled(false);
}
}
}
@Override
@@ -502,7 +509,7 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
if (mRemoteValidation) {
validateGuess(credential);
mGlifLayout.setProgressBarShown(true);
updateRemoteLockscreenValidationViews();
return;
}
@@ -617,11 +624,12 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
}
@Override
protected void onRemoteDeviceCredentialValidationResult(
public void onRemoteLockscreenValidationResult(
RemoteLockscreenValidationResult result) {
switch (result.getResultCode()) {
case RemoteLockscreenValidationResult.RESULT_GUESS_VALID:
if (mCheckBox.isChecked()) {
if (mCheckBox.isChecked() && mRemoteLockscreenValidationFragment
.getLockscreenCredential() != null) {
Log.i(TAG, "Setting device screen lock to the other device's screen lock.");
ChooseLockPattern.SaveAndFinishWorker saveAndFinishWorker =
new ChooseLockPattern.SaveAndFinishWorker();
@@ -632,14 +640,14 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
saveAndFinishWorker.start(
mLockPatternUtils,
/* requestGatekeeperPassword= */ true,
mDeviceCredentialGuess,
mRemoteLockscreenValidationFragment.getLockscreenCredential(),
/* currentCredential= */ null,
mEffectiveUserId);
return;
} else {
mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(),
/* timeoutMs= */ 0, mEffectiveUserId);
}
mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(),
/* timeoutMs= */ 0, mEffectiveUserId);
break;
return;
case RemoteLockscreenValidationResult.RESULT_GUESS_INVALID:
mCredentialCheckResultTracker.setResult(/* matched= */ false, new Intent(),
/* timeoutMs= */ 0, mEffectiveUserId);
@@ -649,12 +657,14 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
(int) result.getTimeoutMillis(), mEffectiveUserId);
break;
case RemoteLockscreenValidationResult.RESULT_NO_REMAINING_ATTEMPTS:
getActivity().finish();
break;
case RemoteLockscreenValidationResult.RESULT_SESSION_EXPIRED:
getActivity().finish();
onRemoteLockscreenValidationFailure(String.format(
"Cannot continue remote lockscreen validation. ResultCode=%d",
result.getResultCode()));
break;
}
mGlifLayout.setProgressBarShown(false);
updateRemoteLockscreenValidationViews();
mRemoteLockscreenValidationFragment.clearLockscreenCredential();
}
@Override
@@ -728,21 +738,18 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
}
/**
* Callback for when the device credential guess used for remote validation was set as the
* current device's device credential.
* Callback for when the current device's lockscreen to the guess used for
* remote lockscreen validation.
*/
@Override
public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
if (mDeviceCredentialGuess != null) {
mDeviceCredentialGuess.zeroize();
}
Log.i(TAG, "Device lockscreen has been set to remote device's lockscreen.");
mRemoteLockscreenValidationFragment.clearLockscreenCredential();
Intent result = new Intent();
if (mRemoteValidation && containsGatekeeperPasswordHandle(resultData)) {
result.putExtra(EXTRA_KEY_GK_PW_HANDLE, getGatekeeperPasswordHandle(resultData));
}
mGlifLayout.setProgressBarShown(false);
mCredentialCheckResultTracker.setResult(/* matched= */ true, result,
/* timeoutMs= */ 0, mEffectiveUserId);
}

View File

@@ -0,0 +1,190 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.password;
import android.app.RemoteLockscreenValidationResult;
import android.os.Bundle;
import android.os.Handler;
import android.service.remotelockscreenvalidation.IRemoteLockscreenValidationCallback;
import android.service.remotelockscreenvalidation.RemoteLockscreenValidationClient;
import android.util.Log;
import androidx.fragment.app.Fragment;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
import com.android.security.SecureBox;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
/**
* A fragment used to hold state for remote lockscreen validation.
* If the original listener is ever re-created, the new listener must be set again using
* {@link #setListener} so that the validation result does not get handled by the old listener.
*/
public class RemoteLockscreenValidationFragment extends Fragment {
private static final String TAG = RemoteLockscreenValidationFragment.class.getSimpleName();
private Listener mListener;
private Handler mHandler;
private boolean mIsInProgress;
private RemoteLockscreenValidationResult mResult;
private String mErrorMessage;
private LockscreenCredential mLockscreenCredential;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
@Override
public void onDestroy() {
clearLockscreenCredential();
if (mResult != null && mErrorMessage != null) {
Log.w(TAG, "Unprocessed remote lockscreen validation result");
}
super.onDestroy();
}
/**
* @return {@code true} if remote lockscreen guess validation has started or
* the validation result has not yet been handled.
*/
public boolean isRemoteValidationInProgress() {
return mIsInProgress;
}
/**
* Sets the listener and handler that will handle the result of remote lockscreen validation.
* Unprocessed results or failures will be handled after the listener is set.
*/
public void setListener(Listener listener, Handler handler) {
if (mListener == listener) {
return;
}
mListener = listener;
mHandler = handler;
if (mResult != null) {
handleResult();
} else if (mErrorMessage != null) {
handleFailure();
}
}
/**
* @return {@link LockscreenCredential} if it was cached in {@link #validateLockscreenGuess}.
*/
public LockscreenCredential getLockscreenCredential() {
return mLockscreenCredential;
}
/**
* Clears the {@link LockscreenCredential} if it was cached in {@link #validateLockscreenGuess}.
*/
public void clearLockscreenCredential() {
if (mLockscreenCredential != null) {
mLockscreenCredential.zeroize();
mLockscreenCredential = null;
}
}
/**
* Validates the lockscreen guess on the remote device.
* @param remoteLockscreenValidationClient the client that should be used to send the guess to
* for validation
* @param guess the {@link LockscreenCredential} guess that the user entered
* @param encryptionKey the key that should be used to encrypt the guess before validation
* @param shouldCacheGuess whether to cache to guess so it can be used to set the current
* device's lockscreen after validation succeeds.
*/
public void validateLockscreenGuess(
RemoteLockscreenValidationClient remoteLockscreenValidationClient,
LockscreenCredential guess, byte[] encryptionKey, boolean shouldCacheGuess) {
if (shouldCacheGuess) {
mLockscreenCredential = guess;
}
remoteLockscreenValidationClient.validateLockscreenGuess(
encryptDeviceCredentialGuess(guess.getCredential(), encryptionKey),
new IRemoteLockscreenValidationCallback.Stub() {
@Override
public void onSuccess(RemoteLockscreenValidationResult result) {
mResult = result;
handleResult();
}
@Override
public void onFailure(String message) {
mErrorMessage = message;
handleFailure();
}
});
mIsInProgress = true;
}
private byte[] encryptDeviceCredentialGuess(byte[] guess, byte[] encryptionKey) {
try {
PublicKey publicKey = SecureBox.decodePublicKey(encryptionKey);
return SecureBox.encrypt(
publicKey,
/* sharedSecret= */ null,
LockPatternUtils.ENCRYPTED_REMOTE_CREDENTIALS_HEADER,
guess);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
Log.w(TAG, "Error encrypting device credential guess. Returning empty byte[].", e);
return new byte[0];
}
}
private void handleResult() {
if (mHandler != null) {
mHandler.post(()-> {
if (mListener == null || mResult == null) {
return;
}
mIsInProgress = false;
mListener.onRemoteLockscreenValidationResult(mResult);
mResult = null;
});
}
}
private void handleFailure() {
if (mHandler != null) {
mHandler.post(()-> {
if (mListener == null || mErrorMessage == null) {
return;
}
mIsInProgress = false;
mListener.onRemoteLockscreenValidationFailure(
String.format("Remote lockscreen validation failed: %s", mErrorMessage));
mErrorMessage = null;
});
}
}
interface Listener {
void onRemoteLockscreenValidationResult(RemoteLockscreenValidationResult result);
void onRemoteLockscreenValidationFailure(String message);
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.theme
import android.app.UiModeManager
import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_HIGH
import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_MEDIUM
import android.app.UiModeManager.ContrastUtils.toContrastLevel
import android.content.Context
import android.content.Intent
import android.os.UserHandle
import android.text.TextUtils
import androidx.preference.Preference
import com.android.internal.annotations.VisibleForTesting
import com.android.settings.R
import com.android.settings.core.BasePreferenceController
/**
* Controller that opens the contrast dialog and updates the text describing the current contrast
*/
class ContrastPreferenceController(
private val context: Context,
private val uiModeManager: UiModeManager) : BasePreferenceController(context, KEY) {
companion object {
@VisibleForTesting
const val KEY = "contrast_preference"
}
override fun getAvailabilityStatus(): Int {
return AVAILABLE
}
override fun handlePreferenceTreeClick(preference: Preference): Boolean {
if (TextUtils.equals(preference.key, preferenceKey)) {
val intent = Intent(Intent.ACTION_SHOW_CONTRAST_DIALOG)
context.startActivityAsUser(intent, UserHandle(UserHandle.USER_CURRENT))
return true
}
return false
}
override fun getSummary(): CharSequence = getSummary(toContrastLevel(uiModeManager.contrast))
@VisibleForTesting
fun getSummary(contrast: Int): String {
return when (contrast) {
CONTRAST_LEVEL_HIGH -> context.getString(R.string.contrast_high)
CONTRAST_LEVEL_MEDIUM -> context.getString(R.string.contrast_medium)
else -> context.getString(R.string.contrast_standard)
}
}
}

View File

@@ -394,7 +394,7 @@ public class StylusDevicesControllerTest {
assertThat(buttonsPref.isChecked()).isEqualTo(false);
assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
Secure.STYLUS_BUTTONS_ENABLED, -1)).isEqualTo(0);
Secure.STYLUS_BUTTONS_ENABLED, -1)).isEqualTo(1);
}
private void showScreen(StylusDevicesController controller) {

View File

@@ -197,7 +197,7 @@ public class ConfirmLockPasswordTest {
verify(mCredentialCheckResultTracker).setResult(
eq(true), any(), eq(0), eq(fragment.mEffectiveUserId));
assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isTrue();
assertThat(fragment.mDeviceCredentialGuess).isNotNull();
assertThat(fragment.mRemoteLockscreenValidationFragment.getLockscreenCredential()).isNull();
}
@Test
@@ -223,7 +223,7 @@ public class ConfirmLockPasswordTest {
verify(mCredentialCheckResultTracker).setResult(
eq(true), any(), eq(0), eq(fragment.mEffectiveUserId));
assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isFalse();
assertThat(fragment.mDeviceCredentialGuess).isNull();
assertThat(fragment.mRemoteLockscreenValidationFragment.getLockscreenCredential()).isNull();
}
@Test

View File

@@ -177,7 +177,7 @@ public class ConfirmLockPatternTest {
verify(mCredentialCheckResultTracker).setResult(
eq(true), any(), eq(0), eq(fragment.mEffectiveUserId));
assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isTrue();
assertThat(fragment.mDeviceCredentialGuess).isNotNull();
assertThat(fragment.mRemoteLockscreenValidationFragment.getLockscreenCredential()).isNull();
}
@Test
@@ -203,7 +203,7 @@ public class ConfirmLockPatternTest {
verify(mCredentialCheckResultTracker).setResult(
eq(true), any(), eq(0), eq(fragment.mEffectiveUserId));
assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isFalse();
assertThat(fragment.mDeviceCredentialGuess).isNull();
assertThat(fragment.mRemoteLockscreenValidationFragment.getLockscreenCredential()).isNull();
}
@Test

View File

@@ -0,0 +1,111 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.theme;
import static android.app.UiModeManager.ContrastUtils;
import static android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_HIGH;
import static android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_MEDIUM;
import static android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_STANDARD;
import static android.provider.Settings.Secure.CONTRAST_LEVEL;
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.UiModeManager;
import android.content.Context;
import android.provider.Settings;
import androidx.preference.Preference;
import androidx.test.core.app.ApplicationProvider;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import java.util.stream.Stream;
@RunWith(RobolectricTestRunner.class)
public class ContrastPreferenceControllerTest {
@Rule
public MockitoRule mocks = MockitoJUnit.rule();
private ContrastPreferenceController mController;
@Mock
private UiModeManager mMockUiModeManager;
private Context mContext;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mContext = ApplicationProvider.getApplicationContext();
mController = new ContrastPreferenceController(mContext, mMockUiModeManager);
}
@Test
public void controllerIsAvailable() {
assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
}
@Test
public void testHandlePreferenceTreeClick() {
Preference preference = new Preference(mContext);
preference.setKey(ContrastPreferenceController.KEY);
assertThat(mController.handlePreferenceTreeClick(preference)).isTrue();
Preference otherPreference = new Preference(mContext);
otherPreference.setKey("wrong key");
assertThat(mController.handlePreferenceTreeClick(otherPreference)).isFalse();
}
@Test
public void controllerSummary() {
float initialContrast = mContext.getSystemService(UiModeManager.class).getContrast();
try {
allContrastValues().forEach(contrastLevel -> {
float contrast = ContrastUtils.fromContrastLevel(contrastLevel);
clearInvocations(mMockUiModeManager);
when(mMockUiModeManager.getContrast()).thenReturn(contrast);
String summary = mController.getSummary().toString();
verify(mMockUiModeManager).getContrast();
assertThat(summary).isEqualTo(mController.getSummary(contrastLevel));
});
} finally {
putContrastInSettings(initialContrast);
}
}
private static Stream<Integer> allContrastValues() {
return Stream.of(CONTRAST_LEVEL_STANDARD, CONTRAST_LEVEL_MEDIUM, CONTRAST_LEVEL_HIGH);
}
private void putContrastInSettings(float contrast) {
Settings.Secure.putFloat(mContext.getContentResolver(), CONTRAST_LEVEL, contrast);
}
}