From 80a4af2bbc6af42ae605e454bf89558e564f5244 Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Mon, 27 Aug 2012 19:18:31 -0700 Subject: Start implementing concept of "running" users. The activity manager now keeps track of which users are running. Initially, only user 0 is running. When you switch to another user, that user is started so it is running. It is only at this point that BOOT_COMPLETED is sent for that user and it is allowed to execute anything. You can stop any user except user 0, which brings it back to the same state as when you first boot the device. This is also used to be able to more cleaning delete a user, by first stopping it before removing its data. There is a new broadcast ACTION_USER_STOPPED sent when a user is stopped; system services need to handle this like they currently handle ACTION_PACKAGE_RESTARTED when individual packages are restarted. Change-Id: I89adbd7cbaf4a0bb72ea201385f93477f40a4119 --- .../com/android/server/AlarmManagerService.java | 32 +- .../android/server/NotificationManagerService.java | 29 ++ .../java/com/android/server/am/ActiveServices.java | 13 +- .../android/server/am/ActivityManagerService.java | 329 ++++++++++++++++----- .../java/com/android/server/am/ActivityStack.java | 33 ++- .../com/android/server/am/UserStartedState.java | 43 +++ .../com/android/server/pm/UserManagerService.java | 37 ++- 7 files changed, 427 insertions(+), 89 deletions(-) create mode 100644 services/java/com/android/server/am/UserStartedState.java (limited to 'services/java') diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java index 32ac8e1506cd..9b7be028cfe1 100644 --- a/services/java/com/android/server/AlarmManagerService.java +++ b/services/java/com/android/server/AlarmManagerService.java @@ -34,6 +34,7 @@ import android.os.Message; import android.os.PowerManager; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.UserHandle; import android.os.WorkSource; import android.text.TextUtils; import android.text.format.Time; @@ -303,7 +304,7 @@ class AlarmManagerService extends IAlarmManager.Stub { } } } - + public void removeLocked(String packageName) { removeLocked(mRtcWakeupAlarms, packageName); removeLocked(mRtcAlarms, packageName); @@ -327,6 +328,29 @@ class AlarmManagerService extends IAlarmManager.Stub { } } } + + public void removeUserLocked(int userHandle) { + removeUserLocked(mRtcWakeupAlarms, userHandle); + removeUserLocked(mRtcAlarms, userHandle); + removeUserLocked(mElapsedRealtimeWakeupAlarms, userHandle); + removeUserLocked(mElapsedRealtimeAlarms, userHandle); + } + + private void removeUserLocked(ArrayList alarmList, int userHandle) { + if (alarmList.size() <= 0) { + return; + } + + // iterator over the list removing any it where the intent match + Iterator it = alarmList.iterator(); + + while (it.hasNext()) { + Alarm alarm = it.next(); + if (UserHandle.getUserId(alarm.operation.getTargetUid()) == userHandle) { + it.remove(); + } + } + } public boolean lookForPackageLocked(String packageName) { return lookForPackageLocked(mRtcWakeupAlarms, packageName) @@ -822,6 +846,7 @@ class AlarmManagerService extends IAlarmManager.Stub { // Register for events related to sdcard installation. IntentFilter sdFilter = new IntentFilter(); sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); + sdFilter.addAction(Intent.ACTION_USER_STOPPED); mContext.registerReceiver(this, sdFilter); } @@ -841,6 +866,11 @@ class AlarmManagerService extends IAlarmManager.Stub { return; } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + } else if (Intent.ACTION_USER_STOPPED.equals(action)) { + int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (userHandle >= 0) { + removeUserLocked(userHandle); + } } else { if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index d6fed39afad3..9b61ec480bd7 100755 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -544,6 +544,11 @@ public class NotificationManagerService extends INotificationManager.Stub mInCall = (intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals( TelephonyManager.EXTRA_STATE_OFFHOOK)); updateNotificationPulse(); + } else if (action.equals(Intent.ACTION_USER_STOPPED)) { + int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (userHandle >= 0) { + cancelAllNotificationsUser(userHandle); + } } else if (action.equals(Intent.ACTION_USER_PRESENT)) { // turn off LED when user passes through lock screen mNotificationLight.turnOff(); @@ -619,6 +624,7 @@ public class NotificationManagerService extends INotificationManager.Stub filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); filter.addAction(Intent.ACTION_USER_PRESENT); + filter.addAction(Intent.ACTION_USER_STOPPED); mContext.registerReceiver(mIntentReceiver, filter); IntentFilter pkgFilter = new IntentFilter(); pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); @@ -1249,6 +1255,29 @@ public class NotificationManagerService extends INotificationManager.Stub } } + /** + * Cancels all notifications from a given user. + */ + boolean cancelAllNotificationsUser(int userHandle) { + synchronized (mNotificationList) { + final int N = mNotificationList.size(); + boolean canceledSomething = false; + for (int i = N-1; i >= 0; --i) { + NotificationRecord r = mNotificationList.get(i); + if (UserHandle.getUserId(r.uid) != userHandle) { + continue; + } + canceledSomething = true; + mNotificationList.remove(i); + cancelNotificationLocked(r, false); + } + if (canceledSomething) { + updateLightsLocked(); + } + return canceledSomething; + } + } + @Deprecated public void cancelNotification(String pkg, int id) { cancelNotificationWithTag(pkg, null /* tag */, id); diff --git a/services/java/com/android/server/am/ActiveServices.java b/services/java/com/android/server/am/ActiveServices.java index ca7faa2de622..b0dfa800a45e 100644 --- a/services/java/com/android/server/am/ActiveServices.java +++ b/services/java/com/android/server/am/ActiveServices.java @@ -989,6 +989,17 @@ public class ActiveServices { // restarting state. mRestartingServices.remove(r); + // Make sure that the user who owns this service is started. If not, + // we don't want to allow it to run. + if (mAm.mStartedUsers.get(r.userId) == null) { + Slog.w(TAG, "Unable to launch app " + + r.appInfo.packageName + "/" + + r.appInfo.uid + " for service " + + r.intent.getIntent() + ": user " + r.userId + " is stopped"); + bringDownServiceLocked(r, true); + return false; + } + // Service is now being launched, its package can't be stopped. try { AppGlobals.getPackageManager().setPackageStoppedState( @@ -1509,7 +1520,7 @@ public class ActiveServices { boolean didSomething = false; ArrayList services = new ArrayList(); for (ServiceRecord service : mServiceMap.getAllServices(userId)) { - if (service.packageName.equals(name) + if ((name == null || service.packageName.equals(name)) && (service.app == null || evenPersistent || !service.app.persistent)) { if (!doit) { return true; diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 6e4759d31ab7..6467b7ac8e98 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -46,6 +46,7 @@ import android.app.IInstrumentationWatcher; import android.app.INotificationManager; import android.app.IProcessObserver; import android.app.IServiceConnection; +import android.app.IStopUserCallback; import android.app.IThumbnailReceiver; import android.app.Instrumentation; import android.app.Notification; @@ -427,6 +428,11 @@ public final class ActivityManagerService extends ActivityManagerNative */ long mPreviousProcessVisibleTime; + /** + * Which uses have been started, so are allowed to run code. + */ + final SparseArray mStartedUsers = new SparseArray(); + /** * Packages that the user has asked to have run in screen size * compatibility mode instead of filling the screen. @@ -791,7 +797,6 @@ public final class ActivityManagerService extends ActivityManagerNative static ActivityThread mSystemThread; private int mCurrentUserId; - private SparseIntArray mLoggedInUsers = new SparseIntArray(5); private UserManager mUserManager; private final class AppDeathRecipient implements IBinder.DeathRecipient { @@ -1506,6 +1511,9 @@ public final class ActivityManagerService extends ActivityManagerNative systemDir, "usagestats").toString()); mHeadless = "1".equals(SystemProperties.get("ro.config.headless", "0")); + // User 0 is the first and only user that runs at boot. + mStartedUsers.put(0, new UserStartedState(new UserHandle(0), true)); + GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version", ConfigurationInfo.GL_ES_VERSION_UNDEFINED); @@ -2095,7 +2103,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } - boolean startHomeActivityLocked(int userId) { + boolean startHomeActivityLocked(int userId, UserStartedState startingUser) { if (mHeadless) { // Added because none of the other calls to ensureBootCompleted seem to fire // when running headless. @@ -2135,6 +2143,9 @@ public final class ActivityManagerService extends ActivityManagerNative null, null, 0, 0, 0, 0, null, false, null); } } + if (startingUser != null) { + mMainStack.addStartingUserLocked(startingUser); + } return true; } @@ -3454,7 +3465,7 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.w(TAG, "Invalid packageName: " + packageName); return; } - killPackageProcessesLocked(packageName, pkgUid, + killPackageProcessesLocked(packageName, pkgUid, -1, ProcessList.SERVICE_ADJ, false, true, true, false, "kill background"); } } finally { @@ -3650,7 +3661,8 @@ public final class ActivityManagerService extends ActivityManagerNative } private void forceStopPackageLocked(final String packageName, int uid) { - forceStopPackageLocked(packageName, uid, false, false, true, false, UserHandle.getUserId(uid)); + forceStopPackageLocked(packageName, uid, false, false, true, false, + UserHandle.getUserId(uid)); Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED, Uri.fromParts("package", packageName, null)); if (!mProcessesReady) { @@ -3662,16 +3674,27 @@ public final class ActivityManagerService extends ActivityManagerNative false, false, MY_PID, Process.SYSTEM_UID, UserHandle.getUserId(uid)); } - + + private void forceStopUserLocked(int userId) { + forceStopPackageLocked(null, -1, false, false, true, false, userId); + Intent intent = new Intent(Intent.ACTION_USER_STOPPED); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + intent.putExtra(Intent.EXTRA_USER_HANDLE, userId); + broadcastIntentLocked(null, null, intent, + null, null, 0, null, null, null, + false, false, + MY_PID, Process.SYSTEM_UID, userId); + } + private final boolean killPackageProcessesLocked(String packageName, int uid, - int minOomAdj, boolean callerWillRestart, boolean allowRestart, boolean doit, - boolean evenPersistent, String reason) { + int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart, + boolean doit, boolean evenPersistent, String reason) { ArrayList procs = new ArrayList(); // Remove all processes this package may have touched: all with the // same UID (except for the system or root user), and all whose name // matches the package name. - final String procNamePrefix = packageName + ":"; + final String procNamePrefix = packageName != null ? (packageName + ":") : null; for (SparseArray apps : mProcessNames.getMap().values()) { final int NA = apps.size(); for (int ia=0; ia= minOomAdj) { + if (!doit) { + return true; + } + app.removed = true; + procs.add(app); + } + } // If uid is specified and the uid and process name match // Or, the uid is not specified and the process name matches } else if (((uid > 0 && uid != Process.SYSTEM_UID && app.info.uid == uid) @@ -3714,7 +3749,7 @@ public final class ActivityManagerService extends ActivityManagerNative int i; int N; - if (uid < 0) { + if (uid < 0 && name != null) { try { uid = AppGlobals.getPackageManager().getPackageUid(name, userId); } catch (RemoteException e) { @@ -3722,24 +3757,45 @@ public final class ActivityManagerService extends ActivityManagerNative } if (doit) { - Slog.i(TAG, "Force stopping package " + name + " uid=" + uid); + if (name != null) { + Slog.i(TAG, "Force stopping package " + name + " uid=" + uid); + } else { + Slog.i(TAG, "Force stopping user " + userId); + } Iterator> badApps = mProcessCrashTimes.getMap().values().iterator(); while (badApps.hasNext()) { SparseArray ba = badApps.next(); - if (ba.get(uid) != null) { + for (i=ba.size()-1; i>=0; i--) { + boolean remove = false; + final int entUid = ba.keyAt(i); + if (name != null) { + if (entUid == uid) { + remove = true; + } + } else if (UserHandle.getUserId(entUid) == userId) { + remove = true; + } + if (remove) { + ba.removeAt(i); + } + } + if (ba.size() == 0) { badApps.remove(); } } } - - boolean didSomething = killPackageProcessesLocked(name, uid, -100, - callerWillRestart, false, doit, evenPersistent, "force stop"); + + boolean didSomething = killPackageProcessesLocked(name, uid, + name == null ? userId : -1 , -100, callerWillRestart, false, + doit, evenPersistent, + name == null ? ("force stop user " + userId) : ("force stop " + name)); TaskRecord lastTask = null; for (i=0; i providers = new ArrayList(); for (ContentProviderRecord provider : mProviderMap.getProvidersByClass(userId).values()) { - if (provider.info.packageName.equals(name) + if ((name == null || provider.info.packageName.equals(name)) && (provider.proc == null || evenPersistent || !provider.proc.persistent)) { if (!doit) { return true; @@ -3792,7 +3848,7 @@ public final class ActivityManagerService extends ActivityManagerNative } if (doit) { - if (purgeCache) { + if (purgeCache && name != null) { AttributeCache ac = AttributeCache.instance(); if (ac != null) { ac.removePackage(name); @@ -4197,15 +4253,6 @@ public final class ActivityManagerService extends ActivityManagerNative } }, pkgFilter); - IntentFilter userFilter = new IntentFilter(); - userFilter.addAction(Intent.ACTION_USER_REMOVED); - mContext.registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - onUserRemoved(intent); - } - }, userFilter); - synchronized (this) { // Ensure that any processes we had put on hold are now started // up. @@ -4227,13 +4274,17 @@ public final class ActivityManagerService extends ActivityManagerNative // Tell anyone interested that we are done booting! SystemProperties.set("sys.boot_completed", "1"); SystemProperties.set("dev.bootcomplete", "1"); - List users = getUserManager().getUsers(); - for (UserInfo user : users) { - broadcastIntentLocked(null, null, - new Intent(Intent.ACTION_BOOT_COMPLETED, null), - null, null, 0, null, null, - android.Manifest.permission.RECEIVE_BOOT_COMPLETED, - false, false, MY_PID, Process.SYSTEM_UID, user.id); + for (int i=0; i> pkgAndUids = new ArrayList>(); + @Override + public int stopUser(final int userId, final IStopUserCallback callback) { + if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + != PackageManager.PERMISSION_GRANTED) { + String msg = "Permission Denial: switchUser() from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + if (userId <= 0) { + throw new IllegalArgumentException("Can't stop primary user " + userId); + } synchronized (this) { - HashMap> map = mProcessNames.getMap(); - for (Entry> uidMap : map.entrySet()) { - SparseArray uids = uidMap.getValue(); - for (int i = 0; i < uids.size(); i++) { - if (UserHandle.getUserId(uids.keyAt(i)) == extraUserId) { - pkgAndUids.add(new Pair(uidMap.getKey(), uids.keyAt(i))); - } + if (mCurrentUserId == userId) { + return ActivityManager.USER_OP_IS_CURRENT; + } + + final UserStartedState uss = mStartedUsers.get(userId); + if (uss == null) { + // User is not started, nothing to do... but we do need to + // callback if requested. + if (callback != null) { + mHandler.post(new Runnable() { + @Override + public void run() { + try { + callback.userStopped(userId); + } catch (RemoteException e) { + } + } + }); + } + return ActivityManager.USER_OP_SUCCESS; + } + + if (callback != null) { + uss.mStopCallbacks.add(callback); + } + + if (uss.mState != UserStartedState.STATE_STOPPING) { + uss.mState = UserStartedState.STATE_STOPPING; + + long ident = Binder.clearCallingIdentity(); + try { + // Inform of user switch + Intent intent = new Intent(Intent.ACTION_SHUTDOWN); + final IIntentReceiver resultReceiver = new IIntentReceiver.Stub() { + @Override + public void performReceive(Intent intent, int resultCode, String data, + Bundle extras, boolean ordered, boolean sticky) { + finishUserStop(uss); + } + }; + broadcastIntentLocked(null, null, intent, + null, resultReceiver, 0, null, null, null, + true, false, MY_PID, Process.SYSTEM_UID, userId); + } finally { + Binder.restoreCallingIdentity(ident); } } + } + + return ActivityManager.USER_OP_SUCCESS; + } + + void finishUserStop(UserStartedState uss) { + final int userId = uss.mHandle.getIdentifier(); + boolean stopped; + ArrayList callbacks; + synchronized (this) { + callbacks = new ArrayList(uss.mStopCallbacks); + if (uss.mState != UserStartedState.STATE_STOPPING + || mStartedUsers.get(userId) != uss) { + stopped = false; + } else { + stopped = true; + // User can no longer run. + mStartedUsers.remove(userId); + + // Clean up all state and processes associated with the user. + // Kill all the processes for the user. + forceStopUserLocked(userId); + } + } - for (Pair pkgAndUid : pkgAndUids) { - forceStopPackageLocked(pkgAndUid.first, pkgAndUid.second, - false, false, true, true, extraUserId); + for (int i=0; i mWaitingActivityVisible = new ArrayList(); - + + final ArrayList mStartingUsers + = new ArrayList(); + /** * Set when the system is going to sleep, until we have * successfully paused the current activity and released our wake lock. @@ -1397,7 +1400,7 @@ final class ActivityStack { // Launcher... if (mMainStack) { ActivityOptions.abort(options); - return mService.startHomeActivityLocked(0); + return mService.startHomeActivityLocked(0, null); } } @@ -1427,7 +1430,16 @@ final class ActivityStack { ActivityOptions.abort(options); return false; } - + + // Make sure that the user who owns this activity is started. If not, + // we will just leave it as is because someone should be bringing + // another user's activities to the top of the stack. + if (mService.mStartedUsers.get(next.userId) == null) { + Slog.w(TAG, "Skipping resume of top activity " + next + + ": user " + next.userId + " is stopped"); + return false; + } + // The activity may be waiting for stop, but that is no longer // appropriate for it. mStoppingActivities.remove(next); @@ -1494,7 +1506,7 @@ final class ActivityStack { Slog.d(TAG, "no-history finish of " + last + " on new resume"); } requestFinishActivityLocked(last.appToken, Activity.RESULT_CANCELED, null, - "no-history"); + "no-history"); } } @@ -3414,6 +3426,7 @@ final class ActivityStack { ArrayList stops = null; ArrayList finishes = null; ArrayList thumbnails = null; + ArrayList startingUsers = null; int NS = 0; int NF = 0; int NT = 0; @@ -3495,6 +3508,10 @@ final class ActivityStack { booting = mService.mBooting; mService.mBooting = false; } + if (mStartingUsers.size() > 0) { + startingUsers = new ArrayList(mStartingUsers); + mStartingUsers.clear(); + } } int i; @@ -3539,6 +3556,10 @@ final class ActivityStack { if (booting) { mService.finishBooting(); + } else if (startingUsers != null) { + for (i=0; i mStopCallbacks + = new ArrayList(); + + public int mState = STATE_BOOTING; + + public UserStartedState(UserHandle handle, boolean initial) { + mHandle = handle; + } + + void dump(String prefix, PrintWriter pw) { + pw.print(prefix); pw.print("mState="); pw.println(mState); + } +} diff --git a/services/java/com/android/server/pm/UserManagerService.java b/services/java/com/android/server/pm/UserManagerService.java index 750aa720e953..fb04d0f1c941 100644 --- a/services/java/com/android/server/pm/UserManagerService.java +++ b/services/java/com/android/server/pm/UserManagerService.java @@ -23,6 +23,8 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastXmlSerializer; import android.app.ActivityManager; +import android.app.ActivityManagerNative; +import android.app.IStopUserCallback; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -33,6 +35,7 @@ import android.os.FileUtils; import android.os.IUserManager; import android.os.ParcelFileDescriptor; import android.os.Process; +import android.os.RemoteException; import android.os.UserHandle; import android.util.AtomicFile; import android.util.Slog; @@ -549,13 +552,36 @@ public class UserManagerService extends IUserManager.Stub { */ public boolean removeUser(int userHandle) { checkManageUsersPermission("Only the system can remove users"); + final UserInfo user; + synchronized (mPackagesLock) { + user = mUsers.get(userHandle); + if (userHandle == 0 || user == null) { + return false; + } + } + + int res; + try { + res = ActivityManagerNative.getDefault().stopUser(userHandle, + new IStopUserCallback.Stub() { + @Override + public void userStopped(int userId) { + finishRemoveUser(userId); + } + @Override + public void userStopAborted(int userId) { + } + }); + } catch (RemoteException e) { + return false; + } + + return res == ActivityManager.USER_OP_SUCCESS; + } + + void finishRemoveUser(int userHandle) { synchronized (mInstallLock) { synchronized (mPackagesLock) { - final UserInfo user = mUsers.get(userHandle); - if (userHandle == 0 || user == null) { - return false; - } - // Cleanup package manager settings mPm.cleanUpUserLILPw(userHandle); @@ -574,7 +600,6 @@ public class UserManagerService extends IUserManager.Stub { Intent addedIntent = new Intent(Intent.ACTION_USER_REMOVED); addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userHandle); mContext.sendBroadcast(addedIntent, android.Manifest.permission.MANAGE_USERS); - return true; } @Override -- cgit v1.2.3-59-g8ed1b