Flesh out user locked/unlocked lifecycle.
When a user is first started, we assume that they're "locked" meaning
that credential-encrypted data is unavailable. Once credentials have
been supplied, we can transition the user to a fully running state.
To facilitate this lifecycle, UserState now has two separate
RUNNING_LOCKED and RUNNING states. To ensure consistent events are
sent on all devices, we always step through RUNNING_LOCKED before
arriving at RUNNING. This consistency means that apps processing
data based on the new ACTION_LOCKED_BOOT_COMPLETED broadcast and
system services using the new onUnlockUser() event will be less
bug-prone over time.
If the user storage is unlocked (which is the case on the majority
of legacy devices), we immediately transition from the RUNNING_LOCKED
into the RUNNING state.
Add logging for all state transitions.
When we "recover" a user in the process of being shut down, return
to the last known state.
Bug: 25943941
Change-Id: I5fec980f10b0d0fb2c272a662d193dc15136f9b9
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 3e0ae17..195465c 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -34,6 +34,7 @@
import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_MSG;
import static com.android.server.am.ActivityManagerService.SYSTEM_USER_CURRENT_MSG;
import static com.android.server.am.ActivityManagerService.SYSTEM_USER_START_MSG;
+import static com.android.server.am.ActivityManagerService.SYSTEM_USER_UNLOCK_MSG;
import static com.android.server.am.ActivityManagerService.USER_SWITCH_TIMEOUT_MSG;
import android.annotation.NonNull;
@@ -147,7 +148,6 @@
// User 0 is the first and only user that runs at boot.
final UserState uss = new UserState(UserHandle.SYSTEM);
mStartedUsers.put(UserHandle.USER_SYSTEM, uss);
- updateUserUnlockedState(uss);
mUserLru.add(UserHandle.USER_SYSTEM);
updateStartedUserArrayLocked();
}
@@ -173,8 +173,8 @@
num--;
continue;
}
- if (oldUss.mState == UserState.STATE_STOPPING
- || oldUss.mState == UserState.STATE_SHUTDOWN) {
+ if (oldUss.state == UserState.STATE_STOPPING
+ || oldUss.state == UserState.STATE_SHUTDOWN) {
// This user is already stopping, doesn't count.
num--;
i++;
@@ -199,17 +199,65 @@
}
void finishUserBoot(UserState uss) {
+ finishUserBoot(uss, null);
+ }
+
+ void finishUserBoot(UserState uss, IIntentReceiver resultTo) {
+ final int userId = uss.mHandle.getIdentifier();
synchronized (mService) {
- if (uss.mState == UserState.STATE_BOOTING
- && mStartedUsers.get(uss.mHandle.getIdentifier()) == uss) {
- uss.mState = UserState.STATE_RUNNING;
- final int userId = uss.mHandle.getIdentifier();
- Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
+ // Bail if we ended up with a stale user
+ if (mStartedUsers.get(userId) != uss) return;
+
+ // We always walk through all the user lifecycle states to send
+ // consistent developer events. We step into RUNNING_LOCKED here,
+ // but we might immediately step into RUNNING below if the user
+ // storage is already unlocked.
+ if (uss.state == UserState.STATE_BOOTING) {
+ uss.setState(UserState.STATE_RUNNING_LOCKED);
+
+ Intent intent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED, null);
intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
- mService.broadcastIntentLocked(null, null, intent,
- null, null, 0, null, null,
- new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
+ mService.broadcastIntentLocked(null, null, intent, null, resultTo, 0, null, null,
+ new String[] { android.Manifest.permission.RECEIVE_BOOT_COMPLETED },
+ AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
+ }
+
+ maybeFinishUserUnlock(uss);
+ }
+ }
+
+ /**
+ * Consider stepping from {@link UserState#STATE_RUNNING_LOCKED} into
+ * {@link UserState#STATE_RUNNING}, which only occurs if the user storage is
+ * actually unlocked.
+ */
+ void maybeFinishUserUnlock(UserState uss) {
+ final int userId = uss.mHandle.getIdentifier();
+ synchronized (mService) {
+ // Bail if we ended up with a stale user
+ if (mStartedUsers.get(uss.mHandle.getIdentifier()) != uss) return;
+
+ // Only keep marching forward if user is actually unlocked
+ if (!isUserKeyUnlocked(userId)) return;
+
+ if (uss.state == UserState.STATE_RUNNING_LOCKED) {
+ uss.setState(UserState.STATE_RUNNING);
+
+ mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_UNLOCK_MSG, userId, 0));
+
+ final Intent unlockedIntent = new Intent(Intent.ACTION_USER_UNLOCKED);
+ unlockedIntent.addFlags(
+ Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+ mService.broadcastIntentLocked(null, null, unlockedIntent, null, null, 0, null,
+ null, null, AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
+ userId);
+
+ final Intent bootIntent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
+ bootIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+ bootIntent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
+ mService.broadcastIntentLocked(null, null, bootIntent, null, null, 0, null, null,
+ new String[] { android.Manifest.permission.RECEIVE_BOOT_COMPLETED },
AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
}
}
@@ -291,9 +339,9 @@
uss.mStopCallbacks.add(callback);
}
- if (uss.mState != UserState.STATE_STOPPING
- && uss.mState != UserState.STATE_SHUTDOWN) {
- uss.mState = UserState.STATE_STOPPING;
+ if (uss.state != UserState.STATE_STOPPING
+ && uss.state != UserState.STATE_SHUTDOWN) {
+ uss.setState(UserState.STATE_STOPPING);
updateStartedUserArrayLocked();
long ident = Binder.clearCallingIdentity();
@@ -321,11 +369,11 @@
Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
// On to the next.
synchronized (mService) {
- if (uss.mState != UserState.STATE_STOPPING) {
+ if (uss.state != UserState.STATE_STOPPING) {
// Whoops, we are being started back up. Abort, abort!
return;
}
- uss.mState = UserState.STATE_SHUTDOWN;
+ uss.setState(UserState.STATE_SHUTDOWN);
}
mService.mBatteryStatsService.noteEvent(
BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH,
@@ -355,7 +403,7 @@
callbacks = new ArrayList<>(uss.mStopCallbacks);
if (mStartedUsers.get(userId) != uss) {
stopped = false;
- } else if (uss.mState != UserState.STATE_SHUTDOWN) {
+ } else if (uss.state != UserState.STATE_SHUTDOWN) {
stopped = false;
} else {
stopped = true;
@@ -438,8 +486,8 @@
Integer oldUserId = mUserLru.get(i);
UserState oldUss = mStartedUsers.get(oldUserId);
if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId
- || oldUss.mState == UserState.STATE_STOPPING
- || oldUss.mState == UserState.STATE_SHUTDOWN) {
+ || oldUss.state == UserState.STATE_STOPPING
+ || oldUss.state == UserState.STATE_SHUTDOWN) {
continue;
}
UserInfo userInfo = getUserInfo(oldUserId);
@@ -482,18 +530,18 @@
return userManager;
}
- private void updateUserUnlockedState(UserState uss) {
+ private boolean isUserKeyUnlocked(int userId) {
final IMountService mountService = IMountService.Stub
.asInterface(ServiceManager.getService("mount"));
if (mountService != null) {
try {
- uss.unlocked = mountService.isUserKeyUnlocked(uss.mHandle.getIdentifier());
+ return mountService.isUserKeyUnlocked(userId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
} else {
- // System isn't fully booted yet, so guess based on property
- uss.unlocked = !StorageManager.isFileBasedEncryptionEnabled();
+ Slog.w(TAG, "Mount service not published; guessing locked state based on property");
+ return !StorageManager.isFileBasedEncryptionEnabled();
}
}
@@ -547,8 +595,6 @@
}
final UserState uss = mStartedUsers.get(userId);
- updateUserUnlockedState(uss);
-
final Integer userIdInt = userId;
mUserLru.remove(userIdInt);
mUserLru.add(userIdInt);
@@ -572,22 +618,22 @@
// Make sure user is in the started state. If it is currently
// stopping, we need to knock that off.
- if (uss.mState == UserState.STATE_STOPPING) {
+ if (uss.state == UserState.STATE_STOPPING) {
// If we are stopping, we haven't sent ACTION_SHUTDOWN,
// so we can just fairly silently bring the user back from
// the almost-dead.
- uss.mState = UserState.STATE_RUNNING;
+ uss.setState(uss.lastState);
updateStartedUserArrayLocked();
needStart = true;
- } else if (uss.mState == UserState.STATE_SHUTDOWN) {
+ } else if (uss.state == UserState.STATE_SHUTDOWN) {
// This means ACTION_SHUTDOWN has been sent, so we will
// need to treat this as a new boot of the user.
- uss.mState = UserState.STATE_BOOTING;
+ uss.setState(UserState.STATE_BOOTING);
updateStartedUserArrayLocked();
needStart = true;
}
- if (uss.mState == UserState.STATE_BOOTING) {
+ if (uss.state == UserState.STATE_BOOTING) {
// Let user manager propagate user restrictions to other services.
getUserManager().onBeforeStartUser(userId);
@@ -699,11 +745,9 @@
boolean unlockUserCleared(final int userId, byte[] token) {
synchronized (mService) {
+ // Bail if already running unlocked
final UserState uss = mStartedUsers.get(userId);
- if (uss.unlocked) {
- // Bail early when already unlocked
- return true;
- }
+ if (uss.state == UserState.STATE_RUNNING) return true;
}
final UserInfo userInfo = getUserInfo(userId);
@@ -718,14 +762,9 @@
synchronized (mService) {
final UserState uss = mStartedUsers.get(userId);
- updateUserUnlockedState(uss);
+ maybeFinishUserUnlock(uss);
}
- final Intent intent = new Intent(Intent.ACTION_USER_UNLOCKED);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
- mService.broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
- AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID, userId);
-
return true;
}
@@ -1045,8 +1084,8 @@
for (int i = 0; i < mStartedUsers.size(); i++) {
UserState uss = mStartedUsers.valueAt(i);
// This list does not include stopping users.
- if (uss.mState != UserState.STATE_STOPPING
- && uss.mState != UserState.STATE_SHUTDOWN) {
+ if (uss.state != UserState.STATE_STOPPING
+ && uss.state != UserState.STATE_SHUTDOWN) {
num++;
}
}
@@ -1054,8 +1093,8 @@
num = 0;
for (int i = 0; i < mStartedUsers.size(); i++) {
UserState uss = mStartedUsers.valueAt(i);
- if (uss.mState != UserState.STATE_STOPPING
- && uss.mState != UserState.STATE_SHUTDOWN) {
+ if (uss.state != UserState.STATE_STOPPING
+ && uss.state != UserState.STATE_SHUTDOWN) {
mStartedUserArray[num] = mStartedUsers.keyAt(i);
num++;
}
@@ -1065,17 +1104,7 @@
void sendBootCompletedLocked(IIntentReceiver resultTo) {
for (int i = 0; i < mStartedUsers.size(); i++) {
UserState uss = mStartedUsers.valueAt(i);
- if (uss.mState == UserState.STATE_BOOTING) {
- uss.mState = UserState.STATE_RUNNING;
- final int userId = mStartedUsers.keyAt(i);
- Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
- intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
- mService.broadcastIntentLocked(null, null, intent, null,
- resultTo, 0, null, null,
- new String[] {android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
- AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
- }
+ finishUserBoot(uss, resultTo);
}
}
@@ -1117,14 +1146,33 @@
if ((flags & ActivityManager.FLAG_OR_STOPPED) != 0) {
return true;
}
- if ((flags & ActivityManager.FLAG_AND_LOCKED) != 0 && state.unlocked) {
- return false;
+
+ final boolean unlocked;
+ switch (state.state) {
+ case UserState.STATE_STOPPING:
+ case UserState.STATE_SHUTDOWN:
+ default:
+ return false;
+
+ case UserState.STATE_BOOTING:
+ case UserState.STATE_RUNNING_LOCKED:
+ unlocked = false;
+ break;
+
+ case UserState.STATE_RUNNING:
+ unlocked = true;
+ break;
}
- if ((flags & ActivityManager.FLAG_AND_UNLOCKED) != 0 && !state.unlocked) {
- return false;
+
+ if ((flags & ActivityManager.FLAG_AND_LOCKED) != 0) {
+ return !unlocked;
}
- return state.mState != UserState.STATE_STOPPING
- && state.mState != UserState.STATE_SHUTDOWN;
+ if ((flags & ActivityManager.FLAG_AND_UNLOCKED) != 0) {
+ return unlocked;
+ }
+
+ // One way or another, we're running!
+ return true;
}
UserInfo getCurrentUser() {