More multi-user stuff.

- New public APIs to find out when a user goes to the foreground,
  background, and is first initializing.
- New activity manager callback to be involved in the user switch
  process, allowing other services to let it know when it is safe
  to stop freezing the screen.
- Wallpaper service now implements this to handle its user switch,
  telling the activity manager when it is done.  (Currently this is
  only handling the old wallpaper going away, we need a little more
  work to correctly wait for the new wallpaper to get added.)
- Lock screen now implements the callback to do its user switch.  It
  also now locks itself when this happens, instead of relying on
  some other entity making sure it is locked.
- Pre-boot broadcasts now go to all users.
- WallpaperManager now has an API to find out if a named wallpaper is
  in use by any users.

Change-Id: I27877aef1d82126c0a1428c3d1861619ee5f8653
diff --git a/services/java/com/android/server/WallpaperManagerService.java b/services/java/com/android/server/WallpaperManagerService.java
index a807f4c..b027c1f 100644
--- a/services/java/com/android/server/WallpaperManagerService.java
+++ b/services/java/com/android/server/WallpaperManagerService.java
@@ -16,10 +16,11 @@
 
 package com.android.server;
 
-import static android.os.FileObserver.*;
 import static android.os.ParcelFileDescriptor.*;
 
+import android.app.ActivityManagerNative;
 import android.app.AppGlobals;
+import android.app.IUserSwitchObserver;
 import android.app.IWallpaperManager;
 import android.app.IWallpaperManagerCallback;
 import android.app.PendingIntent;
@@ -43,6 +44,7 @@
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
 import android.os.RemoteException;
 import android.os.FileObserver;
 import android.os.ParcelFileDescriptor;
@@ -79,7 +81,6 @@
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.JournaledFile;
-import com.android.server.am.ActivityManagerService;
 
 class WallpaperManagerService extends IWallpaperManager.Stub {
     static final String TAG = "WallpaperService";
@@ -136,7 +137,7 @@
                             mWallpaper.imageWallpaperPending = false;
                         }
                         bindWallpaperComponentLocked(mWallpaper.imageWallpaperComponent, true,
-                                false, mWallpaper);
+                                false, mWallpaper, null);
                         saveSettingsLocked(mWallpaper);
                     }
                 }
@@ -214,12 +215,14 @@
         IWallpaperService mService;
         IWallpaperEngine mEngine;
         WallpaperData mWallpaper;
+        IRemoteCallback mReply;
 
         public WallpaperConnection(WallpaperInfo info, WallpaperData wallpaper) {
             mInfo = info;
             mWallpaper = wallpaper;
         }
-        
+
+        @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
             synchronized (mLock) {
                 if (mWallpaper.connection == this) {
@@ -235,6 +238,7 @@
             }
         }
 
+        @Override
         public void onServiceDisconnected(ComponentName name) {
             synchronized (mLock) {
                 mService = null;
@@ -246,16 +250,35 @@
                                 > SystemClock.uptimeMillis()
                             && mWallpaper.userId == mCurrentUserId) {
                         Slog.w(TAG, "Reverting to built-in wallpaper!");
-                        clearWallpaperLocked(true, mWallpaper.userId);
+                        clearWallpaperLocked(true, mWallpaper.userId, null);
                     }
                 }
             }
         }
 
+        @Override
         public void attachEngine(IWallpaperEngine engine) {
-            mEngine = engine;
+            synchronized (mLock) {
+                mEngine = engine;
+            }
         }
 
+        @Override
+        public void engineShown(IWallpaperEngine engine) {
+            synchronized (mLock) {
+                if (mReply != null) {
+                    long ident = Binder.clearCallingIdentity();
+                    try {
+                        mReply.sendResult(null);
+                    } catch (RemoteException e) {
+                        Binder.restoreCallingIdentity(ident);
+                    }
+                    mReply = null;
+                }
+            }
+        }
+
+        @Override
         public ParcelFileDescriptor setWallpaper(String name) {
             synchronized (mLock) {
                 if (mWallpaper.connection == this) {
@@ -279,9 +302,10 @@
                         clearWallpaperComponentLocked(wallpaper);
                         // Do this only for the current user's wallpaper
                         if (wallpaper.userId == mCurrentUserId
-                                && !bindWallpaperComponentLocked(comp, false, false, wallpaper)) {
+                                && !bindWallpaperComponentLocked(comp, false, false,
+                                        wallpaper, null)) {
                             Slog.w(TAG, "Wallpaper no longer available; reverting to default");
-                            clearWallpaperLocked(false, wallpaper.userId);
+                            clearWallpaperLocked(false, wallpaper.userId, null);
                         }
                     }
                 }
@@ -349,7 +373,7 @@
                     if (doit) {
                         Slog.w(TAG, "Wallpaper uninstalled, removing: "
                                 + wallpaper.wallpaperComponent);
-                        clearWallpaperLocked(false, wallpaper.userId);
+                        clearWallpaperLocked(false, wallpaper.userId, null);
                     }
                 }
             }
@@ -369,7 +393,7 @@
                 } catch (NameNotFoundException e) {
                     Slog.w(TAG, "Wallpaper component gone, removing: "
                             + wallpaper.wallpaperComponent);
-                    clearWallpaperLocked(false, wallpaper.userId);
+                    clearWallpaperLocked(false, wallpaper.userId, null);
                 }
             }
             if (wallpaper.nextWallpaperComponent != null
@@ -413,28 +437,43 @@
     public void systemReady() {
         if (DEBUG) Slog.v(TAG, "systemReady");
         WallpaperData wallpaper = mWallpaperMap.get(UserHandle.USER_OWNER);
-        switchWallpaper(wallpaper);
+        switchWallpaper(wallpaper, null);
         wallpaper.wallpaperObserver = new WallpaperObserver(wallpaper);
         wallpaper.wallpaperObserver.startWatching();
 
         IntentFilter userFilter = new IntentFilter();
-        userFilter.addAction(Intent.ACTION_USER_SWITCHED);
         userFilter.addAction(Intent.ACTION_USER_REMOVED);
         mContext.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
                 String action = intent.getAction();
-                if (Intent.ACTION_USER_SWITCHED.equals(action)) {
-                    switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
-                } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
+                if (Intent.ACTION_USER_REMOVED.equals(action)) {
                     removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
                 }
             }
         }, userFilter);
+        try {
+            ActivityManagerNative.getDefault().registerUserSwitchObserver(
+                    new IUserSwitchObserver.Stub() {
+                        @Override
+                        public void onUserSwitching(int newUserId, IRemoteCallback reply) {
+                            switchUser(newUserId, reply);
+                        }
+
+                        @Override
+                        public void onUserSwitchComplete(int newUserId) throws RemoteException {
+                        }
+                    });
+        } catch (RemoteException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
     }
 
     String getName() {
-        return mWallpaperMap.get(0).name;
+        synchronized (mLock) {
+            return mWallpaperMap.get(0).name;
+        }
     }
 
     void removeUser(int userId) {
@@ -451,7 +490,7 @@
         }
     }
 
-    void switchUser(int userId) {
+    void switchUser(int userId, IRemoteCallback reply) {
         synchronized (mLock) {
             mCurrentUserId = userId;
             WallpaperData wallpaper = mWallpaperMap.get(userId);
@@ -462,35 +501,35 @@
                 wallpaper.wallpaperObserver = new WallpaperObserver(wallpaper);
                 wallpaper.wallpaperObserver.startWatching();
             }
-            switchWallpaper(wallpaper);
+            switchWallpaper(wallpaper, reply);
         }
     }
 
-    void switchWallpaper(WallpaperData wallpaper) {
+    void switchWallpaper(WallpaperData wallpaper, IRemoteCallback reply) {
         synchronized (mLock) {
             RuntimeException e = null;
             try {
                 ComponentName cname = wallpaper.wallpaperComponent != null ?
                         wallpaper.wallpaperComponent : wallpaper.nextWallpaperComponent;
-                if (bindWallpaperComponentLocked(cname, true, false, wallpaper)) {
+                if (bindWallpaperComponentLocked(cname, true, false, wallpaper, reply)) {
                     return;
                 }
             } catch (RuntimeException e1) {
                 e = e1;
             }
             Slog.w(TAG, "Failure starting previous wallpaper", e);
-            clearWallpaperLocked(false, wallpaper.userId);
+            clearWallpaperLocked(false, wallpaper.userId, reply);
         }
     }
 
     public void clearWallpaper() {
         if (DEBUG) Slog.v(TAG, "clearWallpaper");
         synchronized (mLock) {
-            clearWallpaperLocked(false, UserHandle.getCallingUserId());
+            clearWallpaperLocked(false, UserHandle.getCallingUserId(), null);
         }
     }
 
-    void clearWallpaperLocked(boolean defaultFailed, int userId) {
+    void clearWallpaperLocked(boolean defaultFailed, int userId, IRemoteCallback reply) {
         WallpaperData wallpaper = mWallpaperMap.get(userId);
         File f = new File(getWallpaperDir(userId), WALLPAPER);
         if (f.exists()) {
@@ -503,7 +542,7 @@
             if (userId != mCurrentUserId) return;
             if (bindWallpaperComponentLocked(defaultFailed
                     ? wallpaper.imageWallpaperComponent
-                    : null, true, false, wallpaper)) {
+                    : null, true, false, wallpaper, reply)) {
                 return;
             }
         } catch (IllegalArgumentException e1) {
@@ -518,21 +557,38 @@
         // wallpaper.
         Slog.e(TAG, "Default wallpaper component not found!", e);
         clearWallpaperComponentLocked(wallpaper);
+        if (reply != null) {
+            try {
+                reply.sendResult(null);
+            } catch (RemoteException e1) {
+            }
+        }
+    }
+
+    public boolean hasNamedWallpaper(String name) {
+        synchronized (mLock) {
+            for (int i=0; i<mWallpaperMap.size(); i++) {
+                WallpaperData wd = mWallpaperMap.valueAt(i);
+                if (name.equals(wd.name)) {
+                    return true;
+                }
+            }
+        }
+        return false;
     }
 
     public void setDimensionHints(int width, int height) throws RemoteException {
         checkPermission(android.Manifest.permission.SET_WALLPAPER_HINTS);
-
-        int userId = UserHandle.getCallingUserId();
-        WallpaperData wallpaper = mWallpaperMap.get(userId);
-        if (wallpaper == null) {
-            throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);
-        }
-        if (width <= 0 || height <= 0) {
-            throw new IllegalArgumentException("width and height must be > 0");
-        }
-
         synchronized (mLock) {
+            int userId = UserHandle.getCallingUserId();
+            WallpaperData wallpaper = mWallpaperMap.get(userId);
+            if (wallpaper == null) {
+                throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);
+            }
+            if (width <= 0 || height <= 0) {
+                throw new IllegalArgumentException("width and height must be > 0");
+            }
+
             if (width != wallpaper.width || height != wallpaper.height) {
                 wallpaper.width = width;
                 wallpaper.height = height;
@@ -610,14 +666,14 @@
     }
 
     public ParcelFileDescriptor setWallpaper(String name) {
-        if (DEBUG) Slog.v(TAG, "setWallpaper");
-        int userId = UserHandle.getCallingUserId();
-        WallpaperData wallpaper = mWallpaperMap.get(userId);
-        if (wallpaper == null) {
-            throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);
-        }
         checkPermission(android.Manifest.permission.SET_WALLPAPER);
         synchronized (mLock) {
+            if (DEBUG) Slog.v(TAG, "setWallpaper");
+            int userId = UserHandle.getCallingUserId();
+            WallpaperData wallpaper = mWallpaperMap.get(userId);
+            if (wallpaper == null) {
+                throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);
+            }
             final long ident = Binder.clearCallingIdentity();
             try {
                 ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(name, wallpaper);
@@ -657,18 +713,18 @@
     }
 
     public void setWallpaperComponent(ComponentName name) {
-        if (DEBUG) Slog.v(TAG, "setWallpaperComponent name=" + name);
-        int userId = UserHandle.getCallingUserId();
-        WallpaperData wallpaper = mWallpaperMap.get(userId);
-        if (wallpaper == null) {
-            throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);
-        }
         checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
         synchronized (mLock) {
+            if (DEBUG) Slog.v(TAG, "setWallpaperComponent name=" + name);
+            int userId = UserHandle.getCallingUserId();
+            WallpaperData wallpaper = mWallpaperMap.get(userId);
+            if (wallpaper == null) {
+                throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);
+            }
             final long ident = Binder.clearCallingIdentity();
             try {
                 wallpaper.imageWallpaperPending = false;
-                bindWallpaperComponentLocked(name, false, true, wallpaper);
+                bindWallpaperComponentLocked(name, false, true, wallpaper, null);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -676,7 +732,7 @@
     }
     
     boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
-            boolean fromUser, WallpaperData wallpaper) {
+            boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
         if (DEBUG) Slog.v(TAG, "bindWallpaperComponentLocked: componentName=" + componentName);
         // Has the component changed?
         if (!force) {
@@ -794,6 +850,7 @@
             wallpaper.wallpaperComponent = componentName;
             wallpaper.connection = newConn;
             wallpaper.lastDiedTime = SystemClock.uptimeMillis();
+            newConn.mReply = reply;
             try {
                 if (wallpaper.userId == mCurrentUserId) {
                     if (DEBUG)
@@ -817,6 +874,13 @@
 
     void detachWallpaperLocked(WallpaperData wallpaper) {
         if (wallpaper.connection != null) {
+            if (wallpaper.connection.mReply != null) {
+                try {
+                    wallpaper.connection.mReply.sendResult(null);
+                } catch (RemoteException e) {
+                }
+                wallpaper.connection.mReply = null;
+            }
             if (wallpaper.connection.mEngine != null) {
                 try {
                     wallpaper.connection.mEngine.destroy();
@@ -849,7 +913,7 @@
         } catch (RemoteException e) {
             Slog.w(TAG, "Failed attaching wallpaper; clearing", e);
             if (!wallpaper.wallpaperUpdating) {
-                bindWallpaperComponentLocked(null, false, false, wallpaper);
+                bindWallpaperComponentLocked(null, false, false, wallpaper, null);
             }
         }
     }
@@ -1032,11 +1096,11 @@
             if (wallpaper.nextWallpaperComponent != null
                     && !wallpaper.nextWallpaperComponent.equals(wallpaper.imageWallpaperComponent)) {
                 if (!bindWallpaperComponentLocked(wallpaper.nextWallpaperComponent, false, false,
-                        wallpaper)) {
+                        wallpaper, null)) {
                     // No such live wallpaper or other failure; fall back to the default
                     // live wallpaper (since the profile being restored indicated that the
                     // user had selected a live rather than static one).
-                    bindWallpaperComponentLocked(null, false, false, wallpaper);
+                    bindWallpaperComponentLocked(null, false, false, wallpaper, null);
                 }
                 success = true;
             } else {
@@ -1052,7 +1116,7 @@
                 if (DEBUG) Slog.v(TAG, "settingsRestored: success=" + success);
                 if (success) {
                     bindWallpaperComponentLocked(wallpaper.nextWallpaperComponent, false, false,
-                            wallpaper);
+                            wallpaper, null);
                 }
             }
         }
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 1c5a8a5..b8072b3 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -49,6 +49,7 @@
 import android.app.IServiceConnection;
 import android.app.IStopUserCallback;
 import android.app.IThumbnailReceiver;
+import android.app.IUserSwitchObserver;
 import android.app.Instrumentation;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -100,6 +101,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IPermissionController;
+import android.os.IRemoteCallback;
 import android.os.IUserManager;
 import android.os.Looper;
 import android.os.Message;
@@ -247,6 +249,10 @@
     // How long we wait until we timeout on key dispatching during instrumentation.
     static final int INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT = 60*1000;
 
+    // Amount of time we wait for observers to handle a user switch before
+    // giving up on them and unfreezing the screen.
+    static final int USER_SWITCH_TIMEOUT = 2*1000;
+
     static final int MY_PID = Process.myPid();
     
     static final String[] EMPTY_STRING_ARRAY = new String[0];
@@ -438,6 +444,17 @@
     final ArrayList<Integer> mUserLru = new ArrayList<Integer>();
 
     /**
+     * Registered observers of the user switching mechanics.
+     */
+    final RemoteCallbackList<IUserSwitchObserver> mUserSwitchObservers
+            = new RemoteCallbackList<IUserSwitchObserver>();
+
+    /**
+     * Currently active user switch.
+     */
+    Object mCurUserSwitchCallback;
+
+    /**
      * Packages that the user has asked to have run in screen size
      * compatibility mode instead of filling the screen.
      */
@@ -863,6 +880,9 @@
     static final int DISPATCH_PROCESSES_CHANGED = 31;
     static final int DISPATCH_PROCESS_DIED = 32;
     static final int REPORT_MEM_USAGE = 33;
+    static final int REPORT_USER_SWITCH_MSG = 34;
+    static final int CONTINUE_USER_SWITCH_MSG = 35;
+    static final int USER_SWITCH_TIMEOUT_MSG = 36;
 
     static final int FIRST_ACTIVITY_STACK_MSG = 100;
     static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1293,6 +1313,18 @@
                 thread.start();
                 break;
             }
+            case REPORT_USER_SWITCH_MSG: {
+                dispatchUserSwitch((UserStartedState)msg.obj, msg.arg1, msg.arg2);
+                break;
+            }
+            case CONTINUE_USER_SWITCH_MSG: {
+                continueUserSwitch((UserStartedState)msg.obj, msg.arg1, msg.arg2);
+                break;
+            }
+            case USER_SWITCH_TIMEOUT_MSG: {
+                timeoutUserSwitch((UserStartedState)msg.obj, msg.arg1, msg.arg2);
+                break;
+            }
             }
         }
     };
@@ -2142,7 +2174,7 @@
         }
     }
 
-    boolean startHomeActivityLocked(int userId, UserStartedState startingUser) {
+    boolean startHomeActivityLocked(int userId) {
         if (mHeadless) {
             // Added because none of the other calls to ensureBootCompleted seem to fire
             // when running headless.
@@ -2181,9 +2213,6 @@
                         null, null, 0, 0, 0, 0, null, false, null);
             }
         }
-        if (startingUser != null) {
-            mMainStack.addStartingUserLocked(startingUser);
-        }
 
         return true;
     }
@@ -3731,7 +3760,7 @@
         broadcastIntentLocked(null, null, intent,
                 null, null, 0, null, null, null,
                 false, false,
-                MY_PID, Process.SYSTEM_UID, userId);
+                MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
     }
 
     private final boolean killPackageProcessesLocked(String packageName, int appId,
@@ -7660,42 +7689,45 @@
                         }
                     }
 
+                    final int[] users = getUsersLocked();
                     for (int i=0; i<ris.size(); i++) {
                         ActivityInfo ai = ris.get(i).activityInfo;
                         ComponentName comp = new ComponentName(ai.packageName, ai.name);
                         doneReceivers.add(comp);
                         intent.setComponent(comp);
-                        IIntentReceiver finisher = null;
-                        if (i == ris.size()-1) {
-                            finisher = new IIntentReceiver.Stub() {
-                                public void performReceive(Intent intent, int resultCode,
-                                        String data, Bundle extras, boolean ordered,
-                                        boolean sticky, int sendingUser) {
-                                    // The raw IIntentReceiver interface is called
-                                    // with the AM lock held, so redispatch to
-                                    // execute our code without the lock.
-                                    mHandler.post(new Runnable() {
-                                        public void run() {
-                                            synchronized (ActivityManagerService.this) {
-                                                mDidUpdate = true;
+                        for (int j=0; j<users.length; j++) {
+                            IIntentReceiver finisher = null;
+                            if (i == ris.size()-1 && j == users.length-1) {
+                                finisher = new IIntentReceiver.Stub() {
+                                    public void performReceive(Intent intent, int resultCode,
+                                            String data, Bundle extras, boolean ordered,
+                                            boolean sticky, int sendingUser) {
+                                        // The raw IIntentReceiver interface is called
+                                        // with the AM lock held, so redispatch to
+                                        // execute our code without the lock.
+                                        mHandler.post(new Runnable() {
+                                            public void run() {
+                                                synchronized (ActivityManagerService.this) {
+                                                    mDidUpdate = true;
+                                                }
+                                                writeLastDonePreBootReceivers(doneReceivers);
+                                                showBootMessage(mContext.getText(
+                                                        R.string.android_upgrading_complete),
+                                                        false);
+                                                systemReady(goingCallback);
                                             }
-                                            writeLastDonePreBootReceivers(doneReceivers);
-                                            showBootMessage(mContext.getText(
-                                                    R.string.android_upgrading_complete),
-                                                    false);
-                                            systemReady(goingCallback);
-                                        }
-                                    });
-                                }
-                            };
-                        }
-                        Slog.i(TAG, "Sending system update to: " + intent.getComponent());
-                        // XXX also need to send this to stopped users(!!!)
-                        broadcastIntentLocked(null, null, intent, null, finisher,
-                                0, null, null, null, true, false, MY_PID, Process.SYSTEM_UID,
-                                UserHandle.USER_ALL);
-                        if (finisher != null) {
-                            mWaitingUpdate = true;
+                                        });
+                                    }
+                                };
+                            }
+                            Slog.i(TAG, "Sending system update to " + intent.getComponent()
+                                    + " for user " + users[j]);
+                            broadcastIntentLocked(null, null, intent, null, finisher,
+                                    0, null, null, null, true, false, MY_PID, Process.SYSTEM_UID,
+                                    users[j]);
+                            if (finisher != null) {
+                                mWaitingUpdate = true;
+                            }
                         }
                     }
                 }
@@ -7817,7 +7849,19 @@
             } catch (RemoteException e) {
             }
 
+            long ident = Binder.clearCallingIdentity();
+            try {
+                Intent intent = new Intent(Intent.ACTION_USER_STARTED);
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                intent.putExtra(Intent.EXTRA_USER_HANDLE, mCurrentUserId);
+                broadcastIntentLocked(null, null, intent,
+                        null, null, 0, null, null, null,
+                        false, false, MY_PID, Process.SYSTEM_UID, mCurrentUserId);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
             mMainStack.resumeTopActivityLocked(null);
+            sendUserSwitchBroadcastsLocked(-1, mCurrentUserId);
         }
     }
 
@@ -11435,9 +11479,12 @@
         // Make sure that the user who is receiving this broadcast is started
         // If not, we will just skip it.
         if (userId != UserHandle.USER_ALL && mStartedUsers.get(userId) == null) {
-            Slog.w(TAG, "Skipping broadcast of " + intent
-                    + ": user " + userId + " is stopped");
-            return ActivityManager.BROADCAST_SUCCESS;
+            if (callingUid != Process.SYSTEM_UID || (intent.getFlags()
+                    & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0) {
+                Slog.w(TAG, "Skipping broadcast of " + intent
+                        + ": user " + userId + " is stopped");
+                return ActivityManager.BROADCAST_SUCCESS;
+            }
         }
 
         /*
@@ -13884,39 +13931,76 @@
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
-        synchronized (this) {
-            if (mCurrentUserId == userId) {
-                return true;
-            }
 
-            mWindowManager.startFreezingScreen(R.anim.screen_user_exit,
-                    R.anim.screen_user_enter);
-
-            // 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;
-            Integer userIdInt = Integer.valueOf(userId);
-            mUserLru.remove(userIdInt);
-            mUserLru.add(userIdInt);
-            boolean haveActivities = mMainStack.switchUser(userId);
-            if (!haveActivities) {
-                startHomeActivityLocked(userId, mStartedUsers.get(userId));
-            } else {
-                mMainStack.addStartingUserLocked(mStartedUsers.get(userId));
-            }
-        }
-
-        long ident = Binder.clearCallingIdentity();
+        final 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.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
-                    android.Manifest.permission.MANAGE_USERS);
+            synchronized (this) {
+                final int oldUserId = mCurrentUserId;
+                if (oldUserId == userId) {
+                    return true;
+                }
+
+                final UserInfo userInfo = getUserManagerLocked().getUserInfo(userId);
+                if (userInfo == null) {
+                    Slog.w(TAG, "No user info for user #" + userId);
+                    return false;
+                }
+
+                mWindowManager.startFreezingScreen(R.anim.screen_user_exit,
+                        R.anim.screen_user_enter);
+
+                // 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;
+                final Integer userIdInt = Integer.valueOf(userId);
+                mUserLru.remove(userIdInt);
+                mUserLru.add(userIdInt);
+
+                final UserStartedState uss = mStartedUsers.get(userId);
+
+                mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
+                mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
+                mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
+                        oldUserId, userId, uss));
+                mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
+                        oldUserId, userId, uss), USER_SWITCH_TIMEOUT);
+                Intent intent = new Intent(Intent.ACTION_USER_STARTED);
+                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);
+
+                if ((userInfo.flags&UserInfo.FLAG_INITIALIZED) == 0) {
+                    if (userId != 0) {
+                        intent = new Intent(Intent.ACTION_USER_INITIALIZE);
+                        broadcastIntentLocked(null, null, intent, null,
+                                new IIntentReceiver.Stub() {
+                                    public void performReceive(Intent intent, int resultCode,
+                                            String data, Bundle extras, boolean ordered,
+                                            boolean sticky, int sendingUser) {
+                                        synchronized (ActivityManagerService.this) {
+                                            getUserManagerLocked().makeInitialized(userInfo.id);
+                                        }
+                                    }
+                                }, 0, null, null, null, true, false, MY_PID, Process.SYSTEM_UID,
+                                userId);
+                    } else {
+                        getUserManagerLocked().makeInitialized(userInfo.id);
+                    }
+                }
+
+                boolean haveActivities = mMainStack.switchUserLocked(userId, uss);
+                if (!haveActivities) {
+                    startHomeActivityLocked(userId);
+                }
+            
+                sendUserSwitchBroadcastsLocked(oldUserId, userId);
+            }
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -13924,6 +14008,102 @@
         return true;
     }
 
+    void sendUserSwitchBroadcastsLocked(int oldUserId, int newUserId) {
+        long ident = Binder.clearCallingIdentity();
+        try {
+            Intent intent;
+            if (oldUserId >= 0) {
+                intent = new Intent(Intent.ACTION_USER_BACKGROUND);
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                intent.putExtra(Intent.EXTRA_USER_HANDLE, oldUserId);
+                broadcastIntentLocked(null, null, intent,
+                        null, null, 0, null, null, null,
+                        false, false, MY_PID, Process.SYSTEM_UID, oldUserId);
+            }
+            if (newUserId >= 0) {
+                intent = new Intent(Intent.ACTION_USER_FOREGROUND);
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId);
+                broadcastIntentLocked(null, null, intent,
+                        null, null, 0, null, null, null,
+                        false, false, MY_PID, Process.SYSTEM_UID, newUserId);
+                intent = new Intent(Intent.ACTION_USER_SWITCHED);
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId);
+                broadcastIntentLocked(null, null, intent,
+                        null, null, 0, null, null,
+                        android.Manifest.permission.MANAGE_USERS,
+                        false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    void dispatchUserSwitch(final UserStartedState uss, final int oldUserId,
+            final int newUserId) {
+        final int N = mUserSwitchObservers.beginBroadcast();
+        if (N > 0) {
+            final IRemoteCallback callback = new IRemoteCallback.Stub() {
+                int mCount = 0;
+                @Override
+                public void sendResult(Bundle data) throws RemoteException {
+                    synchronized (ActivityManagerService.this) {
+                        if (mCurUserSwitchCallback == this) {
+                            mCount++;
+                            if (mCount == N) {
+                                sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
+                            }
+                        }
+                    }
+                }
+            };
+            synchronized (this) {
+                mCurUserSwitchCallback = callback;
+            }
+            for (int i=0; i<N; i++) {
+                try {
+                    mUserSwitchObservers.getBroadcastItem(i).onUserSwitching(
+                            newUserId, callback);
+                } catch (RemoteException e) {
+                }
+            }
+        } else {
+            synchronized (this) {
+                sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
+            }
+        }
+        mUserSwitchObservers.finishBroadcast();
+    }
+
+    void timeoutUserSwitch(UserStartedState uss, int oldUserId, int newUserId) {
+        synchronized (this) {
+            Slog.w(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId);
+            sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
+        }
+    }
+
+    void sendContinueUserSwitchLocked(UserStartedState uss, int oldUserId, int newUserId) {
+        mCurUserSwitchCallback = null;
+        mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
+        mHandler.sendMessage(mHandler.obtainMessage(CONTINUE_USER_SWITCH_MSG,
+                oldUserId, newUserId, uss));
+    }
+
+    void continueUserSwitch(UserStartedState uss, int oldUserId, int newUserId) {
+        final int N = mUserSwitchObservers.beginBroadcast();
+        for (int i=0; i<N; i++) {
+            try {
+                mUserSwitchObservers.getBroadcastItem(i).onUserSwitchComplete(newUserId);
+            } catch (RemoteException e) {
+            }
+        }
+        mUserSwitchObservers.finishBroadcast();
+        synchronized (this) {
+            mWindowManager.stopFreezingScreen();
+        }
+    }
+
     void finishUserSwitch(UserStartedState uss) {
         synchronized (this) {
             if (uss.mState == UserStartedState.STATE_BOOTING
@@ -13937,7 +14117,6 @@
                         android.Manifest.permission.RECEIVE_BOOT_COMPLETED,
                         false, false, MY_PID, Process.SYSTEM_UID, userId);
             }
-            mWindowManager.stopFreezingScreen();
         }
     }
 
@@ -14074,6 +14253,26 @@
         return state != null && state.mState != UserStartedState.STATE_STOPPING;
     }
 
+    @Override
+    public void registerUserSwitchObserver(IUserSwitchObserver observer) {
+        if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+                != PackageManager.PERMISSION_GRANTED) {
+            String msg = "Permission Denial: registerUserSwitchObserver() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+
+        mUserSwitchObservers.register(observer);
+    }
+
+    @Override
+    public void unregisterUserSwitchObserver(IUserSwitchObserver observer) {
+        mUserSwitchObservers.unregister(observer);
+    }
+
     private boolean userExists(int userId) {
         if (userId == 0) {
             return true;
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index bc835b6..f72d318 100755
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -575,37 +575,36 @@
      * Move the activities around in the stack to bring a user to the foreground.
      * @return whether there are any activities for the specified user.
      */
-    final boolean switchUser(int userId) {
-        synchronized (mService) {
-            mCurrentUser = userId;
+    final boolean switchUserLocked(int userId, UserStartedState uss) {
+        mCurrentUser = userId;
+        mStartingUsers.add(uss);
 
-            // Only one activity? Nothing to do...
-            if (mHistory.size() < 2)
-                return false;
+        // Only one activity? Nothing to do...
+        if (mHistory.size() < 2)
+            return false;
 
-            boolean haveActivities = false;
-            // Check if the top activity is from the new user.
-            ActivityRecord top = mHistory.get(mHistory.size() - 1);
-            if (top.userId == userId) return true;
-            // Otherwise, move the user's activities to the top.
-            int N = mHistory.size();
-            int i = 0;
-            while (i < N) {
-                ActivityRecord r = mHistory.get(i);
-                if (r.userId == userId) {
-                    ActivityRecord moveToTop = mHistory.remove(i);
-                    mHistory.add(moveToTop);
-                    // No need to check the top one now
-                    N--;
-                    haveActivities = true;
-                } else {
-                    i++;
-                }
+        boolean haveActivities = false;
+        // Check if the top activity is from the new user.
+        ActivityRecord top = mHistory.get(mHistory.size() - 1);
+        if (top.userId == userId) return true;
+        // Otherwise, move the user's activities to the top.
+        int N = mHistory.size();
+        int i = 0;
+        while (i < N) {
+            ActivityRecord r = mHistory.get(i);
+            if (r.userId == userId) {
+                ActivityRecord moveToTop = mHistory.remove(i);
+                mHistory.add(moveToTop);
+                // No need to check the top one now
+                N--;
+                haveActivities = true;
+            } else {
+                i++;
             }
-            // Transition from the old top to the new top
-            resumeTopActivityLocked(top);
-            return haveActivities;
         }
+        // Transition from the old top to the new top
+        resumeTopActivityLocked(top);
+        return haveActivities;
     }
 
     final boolean realStartActivityLocked(ActivityRecord r,
@@ -1398,7 +1397,7 @@
             // Launcher...
             if (mMainStack) {
                 ActivityOptions.abort(options);
-                return mService.startHomeActivityLocked(mCurrentUser, null);
+                return mService.startHomeActivityLocked(mCurrentUser);
             }
         }
 
@@ -3577,10 +3576,6 @@
         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/pm/UserManagerService.java b/services/java/com/android/server/pm/UserManagerService.java
index fc01f60..a58c4ea 100644
--- a/services/java/com/android/server/pm/UserManagerService.java
+++ b/services/java/com/android/server/pm/UserManagerService.java
@@ -83,6 +83,8 @@
 
     private SparseArray<UserInfo> mUsers = new SparseArray<UserInfo>();
 
+    private final int mUserLimit;
+
     private int[] mUserIds;
     private boolean mGuestEnabled;
     private int mNextSerialNumber;
@@ -125,6 +127,8 @@
             mPm = pm;
             mInstallLock = installLock;
             mPackagesLock = packagesLock;
+            mUserLimit = mContext.getResources().getInteger(
+                    com.android.internal.R.integer.config_multiuserMaximumUsers);
             mUsersDir = new File(dataDir, USER_INFO_DIR);
             mUsersDir.mkdirs();
             // Make zeroth user directory, for services to migrate their files to that location
@@ -237,16 +241,23 @@
         // TODO:
     }
 
+    public void makeInitialized(int userId) {
+        checkManageUsersPermission("makeInitialized");
+        synchronized (mPackagesLock) {
+            UserInfo info = mUsers.get(userId);
+            if (info != null && (info.flags&UserInfo.FLAG_INITIALIZED) == 0) {
+                info.flags |= UserInfo.FLAG_INITIALIZED;
+                writeUserLocked(info);
+            }
+        }
+    }
+
     /**
      * Check if we've hit the limit of how many users can be created.
      */
-    private boolean isUserLimitReached() {
-        synchronized (mInstallLock) {
-            int nUsers = mUsers.size();
-            int userLimit = mContext.getResources().getInteger(
-                    com.android.internal.R.integer.config_multiuserMaximumUsers);
-            return nUsers >= userLimit;
-        }
+    private boolean isUserLimitReachedLocked() {
+        int nUsers = mUsers.size();
+        return nUsers >= mUserLimit;
     }
 
     /**
@@ -535,28 +546,31 @@
     public UserInfo createUser(String name, int flags) {
         checkManageUsersPermission("Only the system can create users");
 
-        if (isUserLimitReached()) return null;
-
-        int userId = getNextAvailableId();
-        UserInfo userInfo = new UserInfo(userId, name, null, flags);
-        File userPath = new File(mBaseUserPath, Integer.toString(userId));
-        synchronized (mInstallLock) {
-            synchronized (mPackagesLock) {
-                userInfo.serialNumber = mNextSerialNumber++;
-                mUsers.put(userId, userInfo);
-                writeUserListLocked();
-                writeUserLocked(userInfo);
-                updateUserIdsLocked();
-                mPm.createNewUserLILPw(userId, userPath);
+        final long ident = Binder.clearCallingIdentity();
+        final UserInfo userInfo;
+        try {
+            synchronized (mInstallLock) {
+                synchronized (mPackagesLock) {
+                    if (isUserLimitReachedLocked()) return null;
+                    int userId = getNextAvailableIdLocked();
+                    userInfo = new UserInfo(userId, name, null, flags);
+                    File userPath = new File(mBaseUserPath, Integer.toString(userId));
+                    userInfo.serialNumber = mNextSerialNumber++;
+                    mUsers.put(userId, userInfo);
+                    writeUserListLocked();
+                    writeUserLocked(userInfo);
+                    updateUserIdsLocked();
+                    mPm.createNewUserLILPw(userId, userPath);
+                }
             }
-        }
-        if (userInfo != null) {
-            Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
-            addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id);
-            mContext.sendBroadcastAsUser(new Intent(Intent.ACTION_BOOT_COMPLETED),
-                    new UserHandle(userInfo.id));
-            mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
-                    android.Manifest.permission.MANAGE_USERS);
+            if (userInfo != null) {
+                Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
+                addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id);
+                mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
+                        android.Manifest.permission.MANAGE_USERS);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
         return userInfo;
     }
@@ -614,9 +628,15 @@
         }
 
         // Let other services shutdown any activity
-        Intent addedIntent = new Intent(Intent.ACTION_USER_REMOVED);
-        addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userHandle);
-        mContext.sendBroadcast(addedIntent, android.Manifest.permission.MANAGE_USERS);
+        long ident = Binder.clearCallingIdentity();
+        try {
+            Intent addedIntent = new Intent(Intent.ACTION_USER_REMOVED);
+            addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userHandle);
+            mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
+                    android.Manifest.permission.MANAGE_USERS);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
     }
 
     private void removeDirectoryRecursive(File parent) {
@@ -666,7 +686,7 @@
      * for data and battery stats collection, or unexpected cross-talk.
      * @return
      */
-    private int getNextAvailableId() {
+    private int getNextAvailableIdLocked() {
         synchronized (mPackagesLock) {
             int i = 0;
             while (i < Integer.MAX_VALUE) {
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 1a101ad..556613e 100755
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -294,7 +294,6 @@
                     KeyguardDisableHandler.KEYGUARD_POLICY_CHANGED);
             } else if (Intent.ACTION_USER_SWITCHED.equals(action)) {
                 final int newUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
-                Slog.v(TAG, "Switching user from " + mCurrentUserId + " to " + newUserId);
                 mCurrentUserId = newUserId;
             }
         }