diff options
-rw-r--r-- | Android.mk | 1 | ||||
-rw-r--r-- | cmds/am/src/com/android/commands/am/Am.java | 39 | ||||
-rw-r--r-- | core/java/android/app/ActivityManager.java | 9 | ||||
-rw-r--r-- | core/java/android/app/ActivityManagerNative.java | 27 | ||||
-rw-r--r-- | core/java/android/app/IActivityManager.java | 2 | ||||
-rw-r--r-- | core/java/android/app/IStopUserCallback.aidl | 27 | ||||
-rw-r--r-- | core/java/android/content/Intent.java | 10 | ||||
-rw-r--r-- | core/java/com/android/internal/content/PackageMonitor.java | 8 | ||||
-rw-r--r-- | core/res/AndroidManifest.xml | 1 | ||||
-rw-r--r-- | services/java/com/android/server/AlarmManagerService.java | 32 | ||||
-rwxr-xr-x | services/java/com/android/server/NotificationManagerService.java | 29 | ||||
-rw-r--r-- | services/java/com/android/server/am/ActiveServices.java | 13 | ||||
-rw-r--r-- | services/java/com/android/server/am/ActivityManagerService.java | 329 | ||||
-rwxr-xr-x | services/java/com/android/server/am/ActivityStack.java | 33 | ||||
-rw-r--r-- | services/java/com/android/server/am/UserStartedState.java | 43 | ||||
-rw-r--r-- | services/java/com/android/server/pm/UserManagerService.java | 37 |
16 files changed, 536 insertions, 104 deletions
@@ -74,6 +74,7 @@ LOCAL_SRC_FILES += \ core/java/android/app/ISearchManager.aidl \ core/java/android/app/ISearchManagerCallback.aidl \ core/java/android/app/IServiceConnection.aidl \ + core/java/android/app/IStopUserCallback.aidl \ core/java/android/app/IThumbnailReceiver.aidl \ core/java/android/app/IThumbnailRetriever.aidl \ core/java/android/app/ITransientNotification.aidl \ diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index 47d6a02..7f3dbe5 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -31,7 +31,6 @@ import android.content.Intent; import android.content.pm.IPackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; -import android.os.Binder; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.RemoteException; @@ -141,6 +140,8 @@ public class Am { runToUri(true); } else if (op.equals("switch-user")) { runSwitchUser(); + } else if (op.equals("stop-user")) { + runStopUser(); } else { throw new IllegalArgumentException("Unknown command: " + op); } @@ -323,7 +324,6 @@ public class Am { mUserId = Integer.parseInt(nextArgRequired()); } else { System.err.println("Error: Unknown option: " + opt); - showUsage(); return null; } } @@ -594,7 +594,6 @@ public class Am { no_window_animation = true; } else { System.err.println("Error: Unknown option: " + opt); - showUsage(); return; } } @@ -738,7 +737,6 @@ public class Am { persistent = true; } else { System.err.println("Error: Unknown option: " + opt); - showUsage(); return; } } @@ -752,13 +750,27 @@ public class Am { } private void runSwitchUser() throws Exception { - if (android.os.Process.myUid() != 0) { - throw new RuntimeException("switchuser can only be run as root"); - } String user = nextArgRequired(); mAm.switchUser(Integer.parseInt(user)); } + private void runStopUser() throws Exception { + String user = nextArgRequired(); + int res = mAm.stopUser(Integer.parseInt(user), null); + if (res != ActivityManager.USER_OP_SUCCESS) { + String txt = ""; + switch (res) { + case ActivityManager.USER_OP_IS_CURRENT: + txt = " (Can't stop current user)"; + break; + case ActivityManager.USER_OP_UNKNOWN_USER: + txt = " (Unknown user " + user + ")"; + break; + } + System.err.println("Switch failed: " + res + txt); + } + } + class MyActivityController extends IActivityController.Stub { final String mGdbPort; @@ -1047,7 +1059,6 @@ public class Am { gdbPort = nextArgRequired(); } else { System.err.println("Error: Unknown option: " + opt); - showUsage(); return; } } @@ -1065,7 +1076,6 @@ public class Am { enabled = false; } else { System.err.println("Error: enabled mode must be 'on' or 'off' at " + mode); - showUsage(); return; } @@ -1090,7 +1100,6 @@ public class Am { int div = size.indexOf('x'); if (div <= 0 || div >= (size.length()-1)) { System.err.println("Error: bad size " + size); - showUsage(); return; } String mstr = size.substring(0, div); @@ -1100,7 +1109,6 @@ public class Am { n = Integer.parseInt(nstr); } catch (NumberFormatException e) { System.err.println("Error: bad number " + e); - showUsage(); return; } } @@ -1139,12 +1147,10 @@ public class Am { density = Integer.parseInt(densityStr); } catch (NumberFormatException e) { System.err.println("Error: bad number " + e); - showUsage(); return; } if (density < 72) { System.err.println("Error: density must be >= 72"); - showUsage(); return; } } @@ -1345,6 +1351,7 @@ public class Am { " am to-uri [INTENT]\n" + " am to-intent-uri [INTENT]\n" + " am switch-user <USER_ID>\n" + + " am stop-user <USER_ID>\n" + "\n" + "am start: start an Activity. Options are:\n" + " -D: enable debugging\n" + @@ -1403,6 +1410,12 @@ public class Am { "\n" + "am to-intent-uri: print the given Intent specification as an intent: URI.\n" + "\n" + + "am switch-user: switch to put USER_ID in the foreground, starting" + + " execution of that user if it is currently stopped.\n" + + "\n" + + "am stop-user: stop execution of USER_ID, not allowing it to run any" + + " code until a later explicit switch to it.\n" + + "\n" + "<INTENT> specifications include these flags and arguments:\n" + " [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]\n" + " [-c <CATEGORY> [-c <CATEGORY>] ...]\n" + diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index e644db4..3d88b46 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -210,6 +210,15 @@ public class ActivityManager { */ public static final int INTENT_SENDER_SERVICE = 4; + /** @hide User operation call: success! */ + public static final int USER_OP_SUCCESS = 0; + + /** @hide User operation call: given user id is not known. */ + public static final int USER_OP_UNKNOWN_USER = -1; + + /** @hide User operation call: given user id is the current user, can't be stopped. */ + public static final int USER_OP_IS_CURRENT = -2; + /*package*/ ActivityManager(Context context, Handler handler) { mContext = context; mHandler = handler; diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index adc9434..05c009f 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -1570,6 +1570,17 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case STOP_USER_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int userid = data.readInt(); + IStopUserCallback callback = IStopUserCallback.Stub.asInterface( + data.readStrongBinder()); + int result = stopUser(userid, callback); + reply.writeNoException(); + reply.writeInt(result); + return true; + } + case GET_CURRENT_USER_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); UserInfo userInfo = getCurrentUser(); @@ -3756,11 +3767,25 @@ class ActivityManagerProxy implements IActivityManager return result; } + public int stopUser(int userid, IStopUserCallback callback) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(userid); + data.writeStrongInterface(callback); + mRemote.transact(STOP_USER_TRANSACTION, data, reply, 0); + reply.readException(); + int result = reply.readInt(); + reply.recycle(); + data.recycle(); + return result; + } + public UserInfo getCurrentUser() throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); - mRemote.transact(SWITCH_USER_TRANSACTION, data, reply, 0); + mRemote.transact(GET_CURRENT_USER_TRANSACTION, data, reply, 0); reply.readException(); UserInfo userInfo = UserInfo.CREATOR.createFromParcel(reply); reply.recycle(); diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index c3e911e..70d8445 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -331,6 +331,7 @@ public interface IActivityManager extends IInterface { // Multi-user APIs public boolean switchUser(int userid) throws RemoteException; + public int stopUser(int userid, IStopUserCallback callback) throws RemoteException; public UserInfo getCurrentUser() throws RemoteException; public boolean removeSubTask(int taskId, int subTaskIndex) throws RemoteException; @@ -611,4 +612,5 @@ public interface IActivityManager extends IInterface { int UNSTABLE_PROVIDER_DIED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+150; int IS_INTENT_SENDER_AN_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+151; int START_ACTIVITY_AS_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+152; + int STOP_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+153; } diff --git a/core/java/android/app/IStopUserCallback.aidl b/core/java/android/app/IStopUserCallback.aidl new file mode 100644 index 0000000..19ac1d5 --- /dev/null +++ b/core/java/android/app/IStopUserCallback.aidl @@ -0,0 +1,27 @@ +/* +** Copyright 2012, 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 android.app; + +/** + * Callback to find out when we have finished stopping a user. + * {@hide} + */ +interface IStopUserCallback +{ + void userStopped(int userId); + void userStopAborted(int userId); +} diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 06edf32..53e0a75 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -27,7 +27,6 @@ import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Rect; -import android.media.RemoteControlClient; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; @@ -2287,6 +2286,15 @@ public class Intent implements Parcelable, Cloneable { "android.intent.action.USER_ADDED"; /** + * Broadcast sent to the system when a user is stopped. Carries an extra EXTRA_USER_HANDLE that has + * the userHandle of the user. This is similar to {@link #ACTION_PACKAGE_RESTARTED}, + * but for an entire user instead of a specific package. + * @hide + */ + public static final String ACTION_USER_STOPPED = + "android.intent.action.USER_STOPPED"; + + /** * Broadcast sent to the system when a user is removed. Carries an extra EXTRA_USER_HANDLE that has * the userHandle of the user. * @hide diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java index 650681a..3477a90 100644 --- a/core/java/com/android/internal/content/PackageMonitor.java +++ b/core/java/com/android/internal/content/PackageMonitor.java @@ -49,6 +49,7 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { sPackageFilt.addAction(Intent.ACTION_UID_REMOVED); sPackageFilt.addDataScheme("package"); sNonDataFilt.addAction(Intent.ACTION_UID_REMOVED); + sNonDataFilt.addAction(Intent.ACTION_USER_STOPPED); sExternalFilt.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); sExternalFilt.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); } @@ -136,6 +137,9 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) { return false; } + + public void onHandleUserStop(Intent intent, int userHandle) { + } public void onUidRemoved(int uid) { } @@ -307,6 +311,10 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { intent.getIntExtra(Intent.EXTRA_UID, 0), true); } else if (Intent.ACTION_UID_REMOVED.equals(action)) { onUidRemoved(intent.getIntExtra(Intent.EXTRA_UID, 0)); + } else if (Intent.ACTION_USER_STOPPED.equals(action)) { + if (intent.hasExtra(Intent.EXTRA_USER_HANDLE)) { + onHandleUserStop(intent, intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); + } } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { String[] pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); mAppearingPackages = pkgList; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 5829787..96eb54a 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -62,6 +62,7 @@ <protected-broadcast android:name="android.intent.action.MASTER_CLEAR_NOTIFICATION" /> <protected-broadcast android:name="android.intent.action.USER_ADDED" /> <protected-broadcast android:name="android.intent.action.USER_REMOVED" /> + <protected-broadcast android:name="android.intent.action.USER_STOPPED" /> <protected-broadcast android:name="android.intent.action.USER_SWITCHED" /> <protected-broadcast android:name="android.app.action.ENTER_CAR_MODE" /> diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java index 32ac8e1..9b7be02 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<Alarm> alarmList, int userHandle) { + if (alarmList.size() <= 0) { + return; + } + + // iterator over the list removing any it where the intent match + Iterator<Alarm> 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 d6fed39..9b61ec4 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 ca7faa2..b0dfa80 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<ServiceRecord> services = new ArrayList<ServiceRecord>(); 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 6e4759d..6467b7a 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; @@ -428,6 +429,11 @@ public final class ActivityManagerService extends ActivityManagerNative long mPreviousProcessVisibleTime; /** + * Which uses have been started, so are allowed to run code. + */ + final SparseArray<UserStartedState> mStartedUsers = new SparseArray<UserStartedState>(); + + /** * 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<ProcessRecord> procs = new ArrayList<ProcessRecord>(); // 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<ProcessRecord> apps : mProcessNames.getMap().values()) { final int NA = apps.size(); for (int ia=0; ia<NA; ia++) { @@ -3684,6 +3707,18 @@ public final class ActivityManagerService extends ActivityManagerNative if (doit) { procs.add(app); } + // If no package is specified, we call all processes under the + // give user id. + } else if (packageName == null) { + if (app.userId == userId) { + if (app.setAdj >= 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<SparseArray<Long>> badApps = mProcessCrashTimes.getMap().values().iterator(); while (badApps.hasNext()) { SparseArray<Long> 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<mMainStack.mHistory.size(); i++) { ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); - final boolean samePackage = r.packageName.equals(name); + final boolean samePackage = r.packageName.equals(name) + || (name == null && r.userId == userId); if (r.userId == userId && (samePackage || r.task == lastTask) && (r.app == null || evenPersistent || !r.app.persistent)) { @@ -3776,7 +3832,7 @@ public final class ActivityManagerService extends ActivityManagerNative ArrayList<ContentProviderRecord> providers = new ArrayList<ContentProviderRecord>(); 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<UserInfo> 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<mStartedUsers.size(); i++) { + UserStartedState uss = mStartedUsers.valueAt(i); + if (uss.mState == UserStartedState.STATE_BOOTING) { + uss.mState = UserStartedState.STATE_RUNNING; + 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, + mStartedUsers.keyAt(i)); + } } } } @@ -6296,6 +6347,16 @@ public final class ActivityManagerService extends ActivityManagerNative "Attempt to launch content provider before system ready"); } + // Make sure that the user who owns this provider is started. If not, + // we don't want to allow it to run. + if (mStartedUsers.get(userId) == null) { + Slog.w(TAG, "Unable to launch app " + + cpi.applicationInfo.packageName + "/" + + cpi.applicationInfo.uid + " for provider " + + name + ": user " + userId + " is stopped"); + return null; + } + ComponentName comp = new ComponentName(cpi.packageName, cpi.name); cpr = mProviderMap.getProviderByClass(comp, userId); final boolean firstClass = cpr == null; @@ -9047,6 +9108,13 @@ public final class ActivityManagerService extends ActivityManagerNative } pw.println(); + pw.println(" mStartedUsers:"); + for (int i=0; i<mStartedUsers.size(); i++) { + UserStartedState uss = mStartedUsers.valueAt(i); + pw.print(" User #"); pw.print(uss.mHandle.getIdentifier()); + pw.println(":"); + uss.dump(" ", pw); + } pw.println(" mHomeProcess: " + mHomeProcess); pw.println(" mPreviousProcess: " + mPreviousProcess); if (dumpAll) { @@ -11106,6 +11174,14 @@ public final class ActivityManagerService extends ActivityManagerNative } } + // Make sure that the user who is receiving this broadcast is started + // If not, we will just skip it. + if (mStartedUsers.get(userId) == null) { + Slog.w(TAG, "Skipping broadcast of " + intent + + ": user " + userId + " is stopped"); + return ActivityManager.BROADCAST_SUCCESS; + } + // Handle special intents: if this broadcast is from the package // manager about a package being removed, we need to remove all of // its activities from the history stack. @@ -11648,7 +11724,7 @@ public final class ActivityManagerService extends ActivityManagerNative app.instrumentationProfileFile = null; app.instrumentationArguments = null; - forceStopPackageLocked(app.processName, -1, false, false, true, true, app.userId); + forceStopPackageLocked(app.info.packageName, -1, false, false, true, true, app.userId); } public void finishInstrumentation(IApplicationThread target, @@ -13490,75 +13566,174 @@ public final class ActivityManagerService extends ActivityManagerNative // Multi-user methods + @Override public boolean switchUser(int userId) { - final int callingUid = Binder.getCallingUid(); - if (callingUid != 0 && callingUid != Process.myUid()) { - Slog.e(TAG, "Trying to switch user from unauthorized app"); - return false; + 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 (mCurrentUserId == userId) - return true; - synchronized (this) { - // Check if user is already logged in, otherwise check if user exists first before - // adding to the list of logged in users. - if (mLoggedInUsers.indexOfKey(userId) < 0) { - if (!userExists(userId)) { - return false; - } - mLoggedInUsers.append(userId, userId); + if (mCurrentUserId == userId) { + return true; + } + + // If the user we are switching to is not currently started, then + // we need to start it now. + if (mStartedUsers.get(userId) == null) { + mStartedUsers.put(userId, new UserStartedState(new UserHandle(userId), false)); } mCurrentUserId = userId; boolean haveActivities = mMainStack.switchUser(userId); if (!haveActivities) { - startHomeActivityLocked(userId); + startHomeActivityLocked(userId, mStartedUsers.get(userId)); } - } - // Inform of user switch - Intent addedIntent = new Intent(Intent.ACTION_USER_SWITCHED); - addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId); - mContext.sendBroadcast(addedIntent, android.Manifest.permission.MANAGE_USERS); + long ident = Binder.clearCallingIdentity(); + try { + // Inform of user switch + Intent addedIntent = new Intent(Intent.ACTION_USER_SWITCHED); + addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId); + mContext.sendBroadcast(addedIntent, android.Manifest.permission.MANAGE_USERS); + } finally { + Binder.restoreCallingIdentity(ident); + } return true; } - @Override - public UserInfo getCurrentUser() throws RemoteException { - final int callingUid = Binder.getCallingUid(); - if (callingUid != 0 && callingUid != Process.myUid()) { - Slog.e(TAG, "Trying to get user from unauthorized app"); - return null; + void finishUserSwitch(UserStartedState uss) { + synchronized (this) { + if (uss.mState == UserStartedState.STATE_BOOTING + && mStartedUsers.get(uss.mHandle.getIdentifier()) == uss) { + uss.mState = UserStartedState.STATE_RUNNING; + 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, uss.mHandle.getIdentifier()); + } } - return getUserManager().getUserInfo(mCurrentUserId); } - private void onUserRemoved(Intent intent) { - int extraUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); - if (extraUserId < 1) return; - - // Kill all the processes for the user - ArrayList<Pair<String, Integer>> pkgAndUids = new ArrayList<Pair<String,Integer>>(); + @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<String,SparseArray<ProcessRecord>> map = mProcessNames.getMap(); - for (Entry<String, SparseArray<ProcessRecord>> uidMap : map.entrySet()) { - SparseArray<ProcessRecord> uids = uidMap.getValue(); - for (int i = 0; i < uids.size(); i++) { - if (UserHandle.getUserId(uids.keyAt(i)) == extraUserId) { - pkgAndUids.add(new Pair<String,Integer>(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<IStopUserCallback> callbacks; + synchronized (this) { + callbacks = new ArrayList<IStopUserCallback>(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<String,Integer> pkgAndUid : pkgAndUids) { - forceStopPackageLocked(pkgAndUid.first, pkgAndUid.second, - false, false, true, true, extraUserId); + for (int i=0; i<callbacks.size(); i++) { + try { + if (stopped) callbacks.get(i).userStopped(userId); + else callbacks.get(i).userStopAborted(userId); + } catch (RemoteException e) { } } } + @Override + public UserInfo getCurrentUser() { + if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + != PackageManager.PERMISSION_GRANTED) { + String msg = "Permission Denial: getCurrentUser() from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + synchronized (this) { + return getUserManager().getUserInfo(mCurrentUserId); + } + } + private boolean userExists(int userId) { UserInfo user = getUserManager().getUserInfo(userId); return user != null; diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index ccea41a..a389edc 100755 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -211,7 +211,10 @@ final class ActivityStack { */ final ArrayList<IActivityManager.WaitResult> mWaitingActivityVisible = new ArrayList<IActivityManager.WaitResult>(); - + + final ArrayList<UserStartedState> mStartingUsers + = new ArrayList<UserStartedState>(); + /** * 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<ActivityRecord> stops = null; ArrayList<ActivityRecord> finishes = null; ArrayList<ActivityRecord> thumbnails = null; + ArrayList<UserStartedState> 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<UserStartedState>(mStartingUsers); + mStartingUsers.clear(); + } } int i; @@ -3539,6 +3556,10 @@ final class ActivityStack { if (booting) { mService.finishBooting(); + } else if (startingUsers != null) { + for (i=0; i<startingUsers.size(); i++) { + mService.finishUserSwitch(startingUsers.get(i)); + } } mService.trimApplications(); @@ -3556,6 +3577,10 @@ final class ActivityStack { return res; } + final void addStartingUserLocked(UserStartedState uss) { + mStartingUsers.add(uss); + } + /** * @return Returns true if the activity is being finished, false if for * some reason it is being left as-is. diff --git a/services/java/com/android/server/am/UserStartedState.java b/services/java/com/android/server/am/UserStartedState.java new file mode 100644 index 0000000..3f3ed85 --- /dev/null +++ b/services/java/com/android/server/am/UserStartedState.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2012 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.server.am; + +import java.io.PrintWriter; +import java.util.ArrayList; + +import android.app.IStopUserCallback; +import android.os.UserHandle; + +public class UserStartedState { + public final static int STATE_BOOTING = 0; + public final static int STATE_RUNNING = 1; + public final static int STATE_STOPPING = 2; + + public final UserHandle mHandle; + public final ArrayList<IStopUserCallback> mStopCallbacks + = new ArrayList<IStopUserCallback>(); + + 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 750aa72..fb04d0f 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 |