From 219ec91e1ab58621d354637c8de9228d7f8e2329 Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Tue, 19 Dec 2017 14:57:39 -0700 Subject: [PATCH] Unlock all users before moving or migrating. When moving apps or shared storage between storage media on FBE devices, we need all users to be unlocked to successfully move the data. This change asks the user to enter the credentials for any locked users as part of the moving/migration wizard flows. To do this we relax Utils.enforceSameOwner() to let us prompt for the credentials of unrelated users, but we carefully only extend this capability to callers interacting with the "internal" activities, which require the MANAGE_USERS permission. Test: builds, boots, users are unlocked before moving Bug: 29923055, 25861755 Change-Id: Ifaeb2557c4f8c4354e1d380eaa0e413768ee239f --- res/values/strings.xml | 3 + src/com/android/settings/Utils.java | 28 +++++++-- .../StorageWizardMigrateConfirm.java | 45 ++++++++++++-- .../deviceinfo/StorageWizardMoveConfirm.java | 58 ++++++++++++++++--- .../password/ChooseLockSettingsHelper.java | 44 ++++++++++++-- .../ConfirmDeviceCredentialBaseActivity.java | 7 ++- .../ConfirmDeviceCredentialBaseFragment.java | 10 +++- 7 files changed, 169 insertions(+), 26 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 4a8e7e3ea0a..08f4fa1fedf 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -3053,6 +3053,9 @@ \n\nDon\u2019t remove the ^2 during the move. + + To move data you need to unlock user ^1. + Moving ^1\u2026 diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java index cab31396810..6b0a5b8b765 100644 --- a/src/com/android/settings/Utils.java +++ b/src/com/android/settings/Utils.java @@ -106,6 +106,7 @@ import android.widget.TabWidget; import com.android.internal.app.UnlaunchableAppActivity; import com.android.internal.util.ArrayUtils; import com.android.internal.widget.LockPatternUtils; +import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settings.wrapper.DevicePolicyManagerWrapper; import com.android.settings.wrapper.FingerprintManagerWrapper; @@ -995,20 +996,37 @@ public final class Utils extends com.android.settingslib.Utils { } /** - * Returns the user id present in the bundle with {@link Intent#EXTRA_USER_ID} if it - * belongs to the current user. + * Returns the user id present in the bundle with + * {@link Intent#EXTRA_USER_ID} if it belongs to the current user. * - * @throws SecurityException if the given userId does not belong to the current user group. + * @throws SecurityException if the given userId does not belong to the + * current user group. */ public static int getUserIdFromBundle(Context context, Bundle bundle) { + return getUserIdFromBundle(context, bundle, false); + } + + /** + * Returns the user id present in the bundle with + * {@link Intent#EXTRA_USER_ID} if it belongs to the current user. + * + * @param isInternal indicating if the caller is "internal" to the system, + * meaning we're willing to trust extras like + * {@link ChooseLockSettingsHelper#EXTRA_ALLOW_ANY_USER}. + * @throws SecurityException if the given userId does not belong to the + * current user group. + */ + public static int getUserIdFromBundle(Context context, Bundle bundle, boolean isInternal) { if (bundle == null) { return getCredentialOwnerUserId(context); } + final boolean allowAnyUser = isInternal + && bundle.getBoolean(ChooseLockSettingsHelper.EXTRA_ALLOW_ANY_USER, false); int userId = bundle.getInt(Intent.EXTRA_USER_ID, UserHandle.myUserId()); if (userId == LockPatternUtils.USER_FRP) { - return enforceSystemUser(context, userId); + return allowAnyUser ? userId : enforceSystemUser(context, userId); } else { - return enforceSameOwner(context, userId); + return allowAnyUser ? userId : enforceSameOwner(context, userId); } } diff --git a/src/com/android/settings/deviceinfo/StorageWizardMigrateConfirm.java b/src/com/android/settings/deviceinfo/StorageWizardMigrateConfirm.java index d9e42dff770..8c8b90e9be5 100644 --- a/src/com/android/settings/deviceinfo/StorageWizardMigrateConfirm.java +++ b/src/com/android/settings/deviceinfo/StorageWizardMigrateConfirm.java @@ -16,22 +16,28 @@ package com.android.settings.deviceinfo; +import static com.android.settings.deviceinfo.StorageSettings.TAG; + import android.content.Intent; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.os.Bundle; +import android.os.UserManager; import android.os.storage.DiskInfo; import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; +import android.text.TextUtils; import android.util.Log; - import android.widget.Toast; + import com.android.settings.R; +import com.android.settings.password.ChooseLockSettingsHelper; import java.util.Objects; -import static com.android.settings.deviceinfo.StorageSettings.TAG; - public class StorageWizardMigrateConfirm extends StorageWizardBase { + private static final int REQUEST_CREDENTIAL = 100; + private MigrateEstimateTask mEstimate; @Override @@ -75,9 +81,22 @@ public class StorageWizardMigrateConfirm extends StorageWizardBase { @Override public void onNavigateNext() { - int moveId; + // Ensure that all users are unlocked so that we can move their data + if (StorageManager.isFileEncryptedNativeOrEmulated()) { + for (UserInfo user : getSystemService(UserManager.class).getUsers()) { + if (!StorageManager.isUserKeyUnlocked(user.id)) { + Log.d(TAG, "User " + user.id + " is currently locked; requesting unlock"); + final CharSequence description = TextUtils.expandTemplate( + getText(R.string.storage_wizard_move_unlock), user.name); + new ChooseLockSettingsHelper(this).launchConfirmationActivityForAnyUser( + REQUEST_CREDENTIAL, null, null, description, user.id); + return; + } + } + } // We only expect exceptions from StorageManagerService#setPrimaryStorageUuid + int moveId; try { moveId = getPackageManager().movePrimaryStorage(mVolume); } catch (IllegalArgumentException e) { @@ -108,4 +127,22 @@ public class StorageWizardMigrateConfirm extends StorageWizardBase { startActivity(intent); finishAffinity(); } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == REQUEST_CREDENTIAL) { + if (resultCode == RESULT_OK) { + // Credentials confirmed, so storage should be unlocked; let's + // go look for the next locked user. + onNavigateNext(); + } else { + // User wasn't able to confirm credentials, so we're okay + // landing back at the wizard page again, where they read + // instructions again and tap "Next" to try again. + Log.w(TAG, "Failed to confirm credentials"); + } + } else { + super.onActivityResult(requestCode, resultCode, data); + } + } } diff --git a/src/com/android/settings/deviceinfo/StorageWizardMoveConfirm.java b/src/com/android/settings/deviceinfo/StorageWizardMoveConfirm.java index 65b3d1f04a5..e82612c4f98 100644 --- a/src/com/android/settings/deviceinfo/StorageWizardMoveConfirm.java +++ b/src/com/android/settings/deviceinfo/StorageWizardMoveConfirm.java @@ -16,20 +16,30 @@ package com.android.settings.deviceinfo; -import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.os.Bundle; - -import com.android.internal.util.Preconditions; -import com.android.settings.R; - import static android.content.Intent.EXTRA_PACKAGE_NAME; import static android.content.Intent.EXTRA_TITLE; import static android.content.pm.PackageManager.EXTRA_MOVE_ID; import static android.os.storage.VolumeInfo.EXTRA_VOLUME_ID; +import static com.android.settings.deviceinfo.StorageSettings.TAG; + +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.UserInfo; +import android.os.Bundle; +import android.os.UserManager; +import android.os.storage.StorageManager; +import android.text.TextUtils; +import android.util.Log; + +import com.android.internal.util.Preconditions; +import com.android.settings.R; +import com.android.settings.password.ChooseLockSettingsHelper; + public class StorageWizardMoveConfirm extends StorageWizardBase { + private static final int REQUEST_CREDENTIAL = 100; + private String mPackageName; private ApplicationInfo mApp; @@ -66,6 +76,20 @@ public class StorageWizardMoveConfirm extends StorageWizardBase { @Override public void onNavigateNext() { + // Ensure that all users are unlocked so that we can move their data + if (StorageManager.isFileEncryptedNativeOrEmulated()) { + for (UserInfo user : getSystemService(UserManager.class).getUsers()) { + if (!StorageManager.isUserKeyUnlocked(user.id)) { + Log.d(TAG, "User " + user.id + " is currently locked; requesting unlock"); + final CharSequence description = TextUtils.expandTemplate( + getText(R.string.storage_wizard_move_unlock), user.name); + new ChooseLockSettingsHelper(this).launchConfirmationActivityForAnyUser( + REQUEST_CREDENTIAL, null, null, description, user.id); + return; + } + } + } + // Kick off move before we transition final String appName = getPackageManager().getApplicationLabel(mApp).toString(); final int moveId = getPackageManager().movePackage(mPackageName, mVolume); @@ -77,4 +101,22 @@ public class StorageWizardMoveConfirm extends StorageWizardBase { startActivity(intent); finishAffinity(); } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == REQUEST_CREDENTIAL) { + if (resultCode == RESULT_OK) { + // Credentials confirmed, so storage should be unlocked; let's + // go look for the next locked user. + onNavigateNext(); + } else { + // User wasn't able to confirm credentials, so we're okay + // landing back at the wizard page again, where they read + // instructions again and tap "Next" to try again. + Log.w(TAG, "Failed to confirm credentials"); + } + } else { + super.onActivityResult(requestCode, resultCode, data); + } + } } diff --git a/src/com/android/settings/password/ChooseLockSettingsHelper.java b/src/com/android/settings/password/ChooseLockSettingsHelper.java index 806ee67ff3b..11d40a3419d 100644 --- a/src/com/android/settings/password/ChooseLockSettingsHelper.java +++ b/src/com/android/settings/password/ChooseLockSettingsHelper.java @@ -23,6 +23,7 @@ import android.app.KeyguardManager; import android.app.admin.DevicePolicyManager; import android.content.Intent; import android.content.IntentSender; +import android.os.Bundle; import android.os.UserManager; import com.android.internal.annotations.VisibleForTesting; @@ -42,6 +43,12 @@ public final class ChooseLockSettingsHelper { public static final String EXTRA_KEY_FOR_FINGERPRINT = "for_fingerprint"; public static final String EXTRA_KEY_FOR_CHANGE_CRED_REQUIRED_FOR_BOOT = "for_cred_req_boot"; + /** + * When invoked via {@link ConfirmLockPassword.InternalActivity}, this flag + * controls if we relax the enforcement of + * {@link Utils#enforceSameOwner(android.content.Context, int)}. + */ + public static final String EXTRA_ALLOW_ANY_USER = "allow_any_user"; @VisibleForTesting LockPatternUtils mLockPatternUtils; private Activity mActivity; @@ -200,25 +207,47 @@ public final class ChooseLockSettingsHelper { external, true, challenge, Utils.enforceSameOwner(mActivity, userId)); } + /** + * Variant that allows you to prompt for credentials of any user, including + * those which aren't associated with the current user. As an example, this + * is useful when unlocking the storage for secondary users. + */ + public boolean launchConfirmationActivityForAnyUser(int request, + @Nullable CharSequence title, @Nullable CharSequence header, + @Nullable CharSequence description, int userId) { + final Bundle extras = new Bundle(); + extras.putBoolean(EXTRA_ALLOW_ANY_USER, true); + return launchConfirmationActivity(request, title, header, description, false, + false, true, 0, userId, extras); + } + private boolean launchConfirmationActivity(int request, @Nullable CharSequence title, @Nullable CharSequence header, @Nullable CharSequence description, boolean returnCredentials, boolean external, boolean hasChallenge, long challenge, int userId) { return launchConfirmationActivity(request, title, header, description, returnCredentials, - external, hasChallenge, challenge, userId, null /* alternateButton */); + external, hasChallenge, challenge, userId, null /* alternateButton */, null); + } + + private boolean launchConfirmationActivity(int request, @Nullable CharSequence title, + @Nullable CharSequence header, @Nullable CharSequence description, + boolean returnCredentials, boolean external, boolean hasChallenge, + long challenge, int userId, Bundle extras) { + return launchConfirmationActivity(request, title, header, description, returnCredentials, + external, hasChallenge, challenge, userId, null /* alternateButton */, extras); } public boolean launchFrpConfirmationActivity(int request, @Nullable CharSequence header, @Nullable CharSequence description, @Nullable CharSequence alternateButton) { return launchConfirmationActivity(request, null /* title */, header, description, false /* returnCredentials */, true /* external */, false /* hasChallenge */, - 0 /* challenge */, LockPatternUtils.USER_FRP, alternateButton); + 0 /* challenge */, LockPatternUtils.USER_FRP, alternateButton, null); } private boolean launchConfirmationActivity(int request, @Nullable CharSequence title, @Nullable CharSequence header, @Nullable CharSequence description, boolean returnCredentials, boolean external, boolean hasChallenge, - long challenge, int userId, @Nullable CharSequence alternateButton) { + long challenge, int userId, @Nullable CharSequence alternateButton, Bundle extras) { final int effectiveUserId = UserManager.get(mActivity).getCredentialOwnerProfile(userId); boolean launched = false; @@ -228,7 +257,7 @@ public final class ChooseLockSettingsHelper { returnCredentials || hasChallenge ? ConfirmLockPattern.InternalActivity.class : ConfirmLockPattern.class, returnCredentials, external, - hasChallenge, challenge, userId, alternateButton); + hasChallenge, challenge, userId, alternateButton, extras); break; case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX: @@ -240,7 +269,7 @@ public final class ChooseLockSettingsHelper { returnCredentials || hasChallenge ? ConfirmLockPassword.InternalActivity.class : ConfirmLockPassword.class, returnCredentials, external, - hasChallenge, challenge, userId, alternateButton); + hasChallenge, challenge, userId, alternateButton, extras); break; } return launched; @@ -249,7 +278,7 @@ public final class ChooseLockSettingsHelper { private boolean launchConfirmationActivity(int request, CharSequence title, CharSequence header, CharSequence message, Class activityClass, boolean returnCredentials, boolean external, boolean hasChallenge, long challenge, - int userId, @Nullable CharSequence alternateButton) { + int userId, @Nullable CharSequence alternateButton, Bundle extras) { final boolean frp = (userId == LockPatternUtils.USER_FRP); final Intent intent = new Intent(); intent.putExtra(ConfirmDeviceCredentialBaseFragment.TITLE_TEXT, title); @@ -266,6 +295,9 @@ public final class ChooseLockSettingsHelper { intent.putExtra(SettingsActivity.EXTRA_HIDE_DRAWER, true); intent.putExtra(Intent.EXTRA_USER_ID, userId); intent.putExtra(KeyguardManager.EXTRA_ALTERNATE_BUTTON_LABEL, alternateButton); + if (extras != null) { + intent.putExtras(extras); + } intent.setClassName(ConfirmDeviceCredentialBaseFragment.PACKAGE, activityClass.getName()); if (external) { intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialBaseActivity.java b/src/com/android/settings/password/ConfirmDeviceCredentialBaseActivity.java index d2bd934b7c1..ab8c3dc4312 100644 --- a/src/com/android/settings/password/ConfirmDeviceCredentialBaseActivity.java +++ b/src/com/android/settings/password/ConfirmDeviceCredentialBaseActivity.java @@ -45,10 +45,15 @@ public abstract class ConfirmDeviceCredentialBaseActivity extends SettingsActivi private boolean mIsKeyguardLocked = false; private ConfirmCredentialTheme mConfirmCredentialTheme; + private boolean isInternalActivity() { + return (this instanceof ConfirmLockPassword.InternalActivity) + || (this instanceof ConfirmLockPattern.InternalActivity); + } + @Override protected void onCreate(Bundle savedState) { int credentialOwnerUserId = Utils.getCredentialOwnerUserId(this, - Utils.getUserIdFromBundle(this, getIntent().getExtras())); + Utils.getUserIdFromBundle(this, getIntent().getExtras(), isInternalActivity())); if (UserManager.get(this).isManagedProfile(credentialOwnerUserId)) { setTheme(R.style.Theme_ConfirmDeviceCredentialsWork); mConfirmCredentialTheme = ConfirmCredentialTheme.WORK; diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java b/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java index 350fc76cef7..5b18925f1c7 100644 --- a/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java +++ b/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java @@ -94,6 +94,11 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedPr protected boolean mFrp; private CharSequence mFrpAlternateButtonText; + private boolean isInternalActivity() { + return (getActivity() instanceof ConfirmLockPassword.InternalActivity) + || (getActivity() instanceof ConfirmLockPattern.InternalActivity); + } + @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -103,7 +108,8 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedPr ChooseLockSettingsHelper.EXTRA_KEY_RETURN_CREDENTIALS, false); // Only take this argument into account if it belongs to the current profile. Intent intent = getActivity().getIntent(); - mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras()); + mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras(), + isInternalActivity()); mFrp = (mUserId == LockPatternUtils.USER_FRP); mUserManager = UserManager.get(getActivity()); mEffectiveUserId = mUserManager.getCredentialOwnerProfile(mUserId); @@ -141,7 +147,7 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedPr getActivity(), Utils.getUserIdFromBundle( getActivity(), - getActivity().getIntent().getExtras())); + getActivity().getIntent().getExtras(), isInternalActivity())); if (mUserManager.isManagedProfile(credentialOwnerUserId)) { setWorkChallengeBackground(view, credentialOwnerUserId); }