OSDN Git Service

More multi-user stuff.
authorDianne Hackborn <hackbod@google.com>
Sun, 16 Sep 2012 02:33:48 +0000 (19:33 -0700)
committerDianne Hackborn <hackbod@google.com>
Sun, 16 Sep 2012 06:58:55 +0000 (23:58 -0700)
- 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

20 files changed:
Android.mk
api/current.txt
core/java/android/app/ActivityManagerNative.java
core/java/android/app/IActivityManager.java
core/java/android/app/IUserSwitchObserver.aidl [new file with mode: 0644]
core/java/android/app/IWallpaperManager.aidl
core/java/android/app/WallpaperManager.java
core/java/android/content/Intent.java
core/java/android/content/pm/UserInfo.java
core/java/android/service/wallpaper/IWallpaperConnection.aidl
core/java/android/service/wallpaper/WallpaperService.java
core/res/AndroidManifest.xml
policy/src/com/android/internal/policy/impl/GlobalActions.java
policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java
policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java
services/java/com/android/server/WallpaperManagerService.java
services/java/com/android/server/am/ActivityManagerService.java
services/java/com/android/server/am/ActivityStack.java
services/java/com/android/server/pm/UserManagerService.java
services/java/com/android/server/wm/WindowManagerService.java

index cb1b90b..be98487 100644 (file)
@@ -79,6 +79,7 @@ LOCAL_SRC_FILES += \
        core/java/android/app/IThumbnailRetriever.aidl \
        core/java/android/app/ITransientNotification.aidl \
        core/java/android/app/IUiModeManager.aidl \
+    core/java/android/app/IUserSwitchObserver.aidl \
        core/java/android/app/IWallpaperManager.aidl \
        core/java/android/app/IWallpaperManagerCallback.aidl \
        core/java/android/app/admin/IDevicePolicyManager.aidl \
index cda6be1..9734532 100644 (file)
@@ -4169,6 +4169,7 @@ package android.app {
     method public android.graphics.drawable.Drawable getFastDrawable();
     method public static android.app.WallpaperManager getInstance(android.content.Context);
     method public android.app.WallpaperInfo getWallpaperInfo();
+    method public boolean hasResourceWallpaper(int);
     method public android.graphics.drawable.Drawable peekDrawable();
     method public android.graphics.drawable.Drawable peekFastDrawable();
     method public void sendWallpaperCommand(android.os.IBinder, java.lang.String, int, int, int, android.os.Bundle);
@@ -5843,6 +5844,9 @@ package android.content {
     field public static final deprecated java.lang.String ACTION_UMS_CONNECTED = "android.intent.action.UMS_CONNECTED";
     field public static final deprecated java.lang.String ACTION_UMS_DISCONNECTED = "android.intent.action.UMS_DISCONNECTED";
     field public static final java.lang.String ACTION_UNINSTALL_PACKAGE = "android.intent.action.UNINSTALL_PACKAGE";
+    field public static final java.lang.String ACTION_USER_BACKGROUND = "android.intent.action.USER_BACKGROUND";
+    field public static final java.lang.String ACTION_USER_FOREGROUND = "android.intent.action.USER_FOREGROUND";
+    field public static final java.lang.String ACTION_USER_INITIALIZE = "android.intent.action.USER_INITIALIZE";
     field public static final java.lang.String ACTION_USER_PRESENT = "android.intent.action.USER_PRESENT";
     field public static final java.lang.String ACTION_VIEW = "android.intent.action.VIEW";
     field public static final java.lang.String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
index bf77f6e..eae3b1f 100644 (file)
@@ -1734,6 +1734,22 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
             return true;
         }
 
+        case REGISTER_USER_SWITCH_OBSERVER_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IUserSwitchObserver observer = IUserSwitchObserver.Stub.asInterface(
+                    data.readStrongBinder());
+            registerUserSwitchObserver(observer);
+            return true;
+        }
+
+        case UNREGISTER_USER_SWITCH_OBSERVER_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IUserSwitchObserver observer = IUserSwitchObserver.Stub.asInterface(
+                    data.readStrongBinder());
+            unregisterUserSwitchObserver(observer);
+            return true;
+        }
+
         }
 
         return super.onTransact(code, data, reply, flags);
@@ -3955,5 +3971,27 @@ class ActivityManagerProxy implements IActivityManager
         return result;
     }
 
+    public void registerUserSwitchObserver(IUserSwitchObserver observer) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(observer != null ? observer.asBinder() : null);
+        mRemote.transact(REGISTER_USER_SWITCH_OBSERVER_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
+    public void unregisterUserSwitchObserver(IUserSwitchObserver observer) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(observer != null ? observer.asBinder() : null);
+        mRemote.transact(UNREGISTER_USER_SWITCH_OBSERVER_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
     private IBinder mRemote;
 }
index 4c0e2a7..9ef375a 100644 (file)
@@ -360,6 +360,9 @@ public interface IActivityManager extends IInterface {
     // manage your activity to make sure it is always the uid you expect.
     public int getLaunchedFromUid(IBinder activityToken) throws RemoteException;
 
+    public void registerUserSwitchObserver(IUserSwitchObserver observer) throws RemoteException;
+    public void unregisterUserSwitchObserver(IUserSwitchObserver observer) throws RemoteException;
+
     /*
      * Private non-Binder interfaces
      */
@@ -609,4 +612,6 @@ public interface IActivityManager extends IInterface {
     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;
+    int REGISTER_USER_SWITCH_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+154;
+    int UNREGISTER_USER_SWITCH_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+155;
 }
diff --git a/core/java/android/app/IUserSwitchObserver.aidl b/core/java/android/app/IUserSwitchObserver.aidl
new file mode 100644 (file)
index 0000000..845897b
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * 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 android.app;
+
+import android.os.IRemoteCallback;
+
+/** {@hide} */
+oneway interface IUserSwitchObserver {
+    void onUserSwitching(int newUserId, IRemoteCallback reply);
+    void onUserSwitchComplete(int newUserId);
+}
index 69f64a1..3efd3c0 100644 (file)
@@ -52,6 +52,11 @@ interface IWallpaperManager {
     void clearWallpaper();
 
     /**
+     * Return whether there is a wallpaper set with the given name.
+     */
+    boolean hasNamedWallpaper(String name);
+
+    /**
      * Sets the dimension hint for the wallpaper. These hints indicate the desired
      * minimum width and height for the wallpaper.
      */
index 1ad2e6d..9c0064e 100644 (file)
@@ -590,6 +590,25 @@ public class WallpaperManager {
     }
 
     /**
+     * Return whether any users are currently set to use the wallpaper
+     * with the given resource ID.  That is, their wallpaper has been
+     * set through {@link #setResource(int)} with the same resource id.
+     */
+    public boolean hasResourceWallpaper(int resid) {
+        if (sGlobals.mService == null) {
+            Log.w(TAG, "WallpaperService not running");
+            return false;
+        }
+        try {
+            Resources resources = mContext.getResources();
+            String name = "res:" + resources.getResourceName(resid);
+            return sGlobals.mService.hasNamedWallpaper(name);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
      * Returns the desired minimum width for the wallpaper. Callers of
      * {@link #setBitmap(android.graphics.Bitmap)} or
      * {@link #setStream(java.io.InputStream)} should check this value
index bca5ade..15dec3e 100644 (file)
@@ -2287,17 +2287,62 @@ public class Intent implements Parcelable, Cloneable {
             "android.intent.action.PRE_BOOT_COMPLETED";
 
     /**
+     * Sent the first time a user is starting, to allow system apps to
+     * perform one time initialization.  (This will not be seen by third
+     * party applications because a newly initialized user does not have any
+     * third party applications installed for it.)  This is sent early in
+     * starting the user, around the time the home app is started, before
+     * {@link #ACTION_BOOT_COMPLETED} is sent.
+     */
+    public static final String ACTION_USER_INITIALIZE =
+            "android.intent.action.USER_INITIALIZE";
+
+    /**
+     * Sent when a user switch is happening, causing the process's user to be
+     * brought to the foreground.  This is only sent to receivers registered
+     * through {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
+     * Context.registerReceiver}.  It is sent to the user that is going to the
+     * foreground.
+     */
+    public static final String ACTION_USER_FOREGROUND =
+            "android.intent.action.USER_FOREGROUND";
+
+    /**
+     * Sent when a user switch is happening, causing the process's user to be
+     * sent to the background.  This is only sent to receivers registered
+     * through {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
+     * Context.registerReceiver}.  It is sent to the user that is going to the
+     * background.
+     */
+    public static final String ACTION_USER_BACKGROUND =
+            "android.intent.action.USER_BACKGROUND";
+
+    /**
      * Broadcast sent to the system when a user is added. Carries an extra EXTRA_USER_HANDLE that has the
-     * userHandle of the new user.
+     * userHandle of the new user.  It is sent to all running users.  You must hold
+     * {@link android.Manifest.permission#MANAGE_USERS} to receive this broadcast.
      * @hide
      */
     public static final String ACTION_USER_ADDED =
             "android.intent.action.USER_ADDED";
 
     /**
+     * Broadcast sent to the system when a user is started. Carries an extra EXTRA_USER_HANDLE that has
+     * the userHandle of the user.  This is only sent to
+     * registered receivers, not manifest receivers.  It is sent to the user
+     * that has been started.
+     * @hide
+     */
+    public static final String ACTION_USER_STARTED =
+            "android.intent.action.USER_STARTED";
+
+    /**
      * 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.
+     * but for an entire user instead of a specific package.  This is only sent to
+     * registered receivers, not manifest receivers.  It is sent to all running
+     * users <em>except</em> the one that has just been stopped (which is no
+     * longer running).
      * @hide
      */
     public static final String ACTION_USER_STOPPED =
@@ -2305,7 +2350,9 @@ public class Intent implements Parcelable, Cloneable {
 
     /**
      * Broadcast sent to the system when a user is removed. Carries an extra EXTRA_USER_HANDLE that has
-     * the userHandle of the user.
+     * the userHandle of the user.  It is sent to all running users except the
+     * one that has been removed.  You must hold
+     * {@link android.Manifest.permission#MANAGE_USERS} to receive this broadcast.
      * @hide
      */
     public static final String ACTION_USER_REMOVED =
@@ -2313,7 +2360,10 @@ public class Intent implements Parcelable, Cloneable {
 
     /**
      * Broadcast sent to the system when the user switches. Carries an extra EXTRA_USER_HANDLE that has
-     * the userHandle of the user to become the current one.
+     * the userHandle of the user to become the current one. This is only sent to
+     * registered receivers, not manifest receivers.  It is sent to all running users.
+     * You must hold
+     * {@link android.Manifest.permission#MANAGE_USERS} to receive this broadcast.
      * @hide
      */
     public static final String ACTION_USER_SWITCHED =
index 6bc9a1f..a06aba9 100644 (file)
@@ -30,6 +30,12 @@ public class UserInfo implements Parcelable {
     public static final int FLAG_MASK_USER_TYPE = 0x0000003F;
 
     /**
+     * *************************** NOTE ***************************
+     * These flag values CAN NOT CHANGE because they are written
+     * directly to storage.
+     */
+
+    /**
      * Primary user. Only one user can have this flag set. Meaning of this
      * flag TBD.
      */
@@ -52,6 +58,11 @@ public class UserInfo implements Parcelable {
      */
     public static final int FLAG_RESTRICTED = 0x00000008;
 
+    /**
+     * Indicates that this user has gone through its first-time initialization.
+     */
+    public static final int FLAG_INITIALIZED = 0x00000010;
+
     public int id;
     public int serialNumber;
     public String name;
index b09ccab..f9c5aaa 100644 (file)
@@ -24,5 +24,6 @@ import android.service.wallpaper.IWallpaperEngine;
  */
 interface IWallpaperConnection {
        void attachEngine(IWallpaperEngine engine);
+       void engineShown(IWallpaperEngine engine);
     ParcelFileDescriptor setWallpaper(String name);
 }
index efa8911..86bbc55 100644 (file)
@@ -1020,6 +1020,12 @@ public abstract class WallpaperService extends Service {
                     mEngine = engine;
                     mActiveEngines.add(engine);
                     engine.attach(this);
+                    try {
+                        mConnection.engineShown(this);
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "Wallpaper host disappeared", e);
+                        return;
+                    }
                     return;
                 }
                 case DO_DETACH: {
index 7305d8b..0a409ad 100644 (file)
@@ -64,6 +64,8 @@
     <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_BACKGROUND" />
+    <protected-broadcast android:name="android.intent.action.USER_FOREGROUND" />
     <protected-broadcast android:name="android.intent.action.USER_SWITCHED" />
 
     <protected-broadcast android:name="android.app.action.ENTER_CAR_MODE" />
index 753b864..d8e361f 100644 (file)
@@ -300,7 +300,6 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac
                     public void onPress() {
                         try {
                             ActivityManagerNative.getDefault().switchUser(user.id);
-                            WindowManagerGlobal.getWindowManagerService().lockNow();
                         } catch (RemoteException re) {
                             Log.e(TAG, "Couldn't switch user " + re);
                         }
index c48e2d7..4524c94 100644 (file)
@@ -16,6 +16,8 @@
 
 package com.android.internal.policy.impl.keyguard;
 
+import android.app.ActivityManagerNative;
+import android.app.IUserSwitchObserver;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -32,7 +34,9 @@ import static android.os.BatteryManager.EXTRA_HEALTH;
 import android.media.AudioManager;
 import android.os.BatteryManager;
 import android.os.Handler;
+import android.os.IRemoteCallback;
 import android.os.Message;
+import android.os.RemoteException;
 import android.provider.Settings;
 
 import com.android.internal.telephony.IccCardConstants;
@@ -136,7 +140,7 @@ public class KeyguardUpdateMonitor {
                     handleDevicePolicyManagerStateChanged();
                     break;
                 case MSG_USER_SWITCHED:
-                    handleUserSwitched(msg.arg1);
+                    handleUserSwitched(msg.arg1, (IRemoteCallback)msg.obj);
                     break;
                 case MSG_USER_REMOVED:
                     handleUserRemoved(msg.arg1);
@@ -183,9 +187,6 @@ public class KeyguardUpdateMonitor {
             } else if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED
                     .equals(action)) {
                 mHandler.sendMessage(mHandler.obtainMessage(MSG_DPM_STATE_CHANGED));
-            } else if (Intent.ACTION_USER_SWITCHED.equals(action)) {
-                mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_SWITCHED,
-                       intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0), 0));
             } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
                 mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_REMOVED,
                        intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0), 0));
@@ -325,9 +326,25 @@ public class KeyguardUpdateMonitor {
         filter.addAction(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION);
         filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
         filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
-        filter.addAction(Intent.ACTION_USER_SWITCHED);
         filter.addAction(Intent.ACTION_USER_REMOVED);
         context.registerReceiver(mBroadcastReceiver, filter);
+
+        try {
+            ActivityManagerNative.getDefault().registerUserSwitchObserver(
+                    new IUserSwitchObserver.Stub() {
+                        @Override
+                        public void onUserSwitching(int newUserId, IRemoteCallback reply) {
+                            mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_SWITCHED,
+                                    newUserId, 0, reply));
+                        }
+                        @Override
+                        public void onUserSwitchComplete(int newUserId) throws RemoteException {
+                        }
+                    });
+        } catch (RemoteException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
     }
 
     private void watchForDeviceProvisioning() {
@@ -375,13 +392,17 @@ public class KeyguardUpdateMonitor {
     /**
      * Handle {@link #MSG_USER_SWITCHED}
      */
-    protected void handleUserSwitched(int userId) {
+    protected void handleUserSwitched(int userId, IRemoteCallback reply) {
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
                 cb.onUserSwitched(userId);
             }
         }
+        try {
+            reply.sendResult(null);
+        } catch (RemoteException e) {
+        }
     }
 
     /**
index 1f0f5ef..372b0fc 100644 (file)
@@ -48,6 +48,7 @@ import android.util.EventLog;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
 import android.view.WindowManagerPolicy;
 
 
@@ -300,6 +301,12 @@ public class KeyguardViewMediator {
             synchronized (KeyguardViewMediator.this) {
                 resetStateLocked();
             }
+            // We should always go back to the locked state when a user
+            // switch happens.  Is there a more direct way to do this?
+            try {
+                WindowManagerGlobal.getWindowManagerService().lockNow();
+            } catch (RemoteException e) {
+            }
         }
 
         @Override
index a807f4c..b027c1f 100644 (file)
 
 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.Bundle;
 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 org.xmlpull.v1.XmlSerializer;
 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 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
                             mWallpaper.imageWallpaperPending = false;
                         }
                         bindWallpaperComponentLocked(mWallpaper.imageWallpaperComponent, true,
-                                false, mWallpaper);
+                                false, mWallpaper, null);
                         saveSettingsLocked(mWallpaper);
                     }
                 }
@@ -214,12 +215,14 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
         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 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
             }
         }
 
+        @Override
         public void onServiceDisconnected(ComponentName name) {
             synchronized (mLock) {
                 mService = null;
@@ -246,16 +250,35 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
                                 > 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 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
                         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 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
                     if (doit) {
                         Slog.w(TAG, "Wallpaper uninstalled, removing: "
                                 + wallpaper.wallpaperComponent);
-                        clearWallpaperLocked(false, wallpaper.userId);
+                        clearWallpaperLocked(false, wallpaper.userId, null);
                     }
                 }
             }
@@ -369,7 +393,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
                 } 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 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
     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 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
         }
     }
 
-    void switchUser(int userId) {
+    void switchUser(int userId, IRemoteCallback reply) {
         synchronized (mLock) {
             mCurrentUserId = userId;
             WallpaperData wallpaper = mWallpaperMap.get(userId);
@@ -462,35 +501,35 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
                 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 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
             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 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
         // wallpaper.
         Slog.e(TAG, "Default wallpaper component not found!", e);
         clearWallpaperComponentLocked(wallpaper);
+        if (reply != null) {
+            try {
+                reply.sendResult(null);
+            } catch (RemoteException e1) {
+            }
+        }
     }
 
-    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");
+    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);
         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 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
     }
 
     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 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
     }
 
     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 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
     }
     
     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 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
             wallpaper.wallpaperComponent = componentName;
             wallpaper.connection = newConn;
             wallpaper.lastDiedTime = SystemClock.uptimeMillis();
+            newConn.mReply = reply;
             try {
                 if (wallpaper.userId == mCurrentUserId) {
                     if (DEBUG)
@@ -817,6 +874,13 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
 
     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 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
         } 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 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
             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 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
                 if (DEBUG) Slog.v(TAG, "settingsRestored: success=" + success);
                 if (success) {
                     bindWallpaperComponentLocked(wallpaper.nextWallpaperComponent, false, false,
-                            wallpaper);
+                            wallpaper, null);
                 }
             }
         }
index 1c5a8a5..b8072b3 100644 (file)
@@ -49,6 +49,7 @@ import android.app.IProcessObserver;
 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.FileUtils;
 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 @@ public final class ActivityManagerService extends ActivityManagerNative
     // 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 @@ public final class ActivityManagerService extends ActivityManagerNative
     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 @@ public final class ActivityManagerService extends ActivityManagerNative
     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 @@ public final class ActivityManagerService extends ActivityManagerNative
                 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 @@ public final class ActivityManagerService extends ActivityManagerNative
         }
     }
 
-    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 @@ public final class ActivityManagerService extends ActivityManagerNative
                         null, null, 0, 0, 0, 0, null, false, null);
             }
         }
-        if (startingUser != null) {
-            mMainStack.addStartingUserLocked(startingUser);
-        }
 
         return true;
     }
@@ -3731,7 +3760,7 @@ public final class ActivityManagerService extends ActivityManagerNative
         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 @@ public final class ActivityManagerService extends ActivityManagerNative
                         }
                     }
 
+                    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 @@ public final class ActivityManagerService extends ActivityManagerNative
             } 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 @@ 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 (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,44 +13931,177 @@ public final class ActivityManagerService extends ActivityManagerNative
             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);
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                final int oldUserId = mCurrentUserId;
+                if (oldUserId == 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));
-            }
+                final UserInfo userInfo = getUserManagerLocked().getUserInfo(userId);
+                if (userInfo == null) {
+                    Slog.w(TAG, "No user info for user #" + userId);
+                    return 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));
+                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);
         }
 
+        return true;
+    }
+
+    void sendUserSwitchBroadcastsLocked(int oldUserId, int newUserId) {
         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);
+            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);
         }
+    }
 
-        return true;
+    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) {
@@ -13937,7 +14117,6 @@ public final class ActivityManagerService extends ActivityManagerNative
                         android.Manifest.permission.RECEIVE_BOOT_COMPLETED,
                         false, false, MY_PID, Process.SYSTEM_UID, userId);
             }
-            mWindowManager.stopFreezingScreen();
         }
     }
 
@@ -14074,6 +14253,26 @@ public final class ActivityManagerService extends ActivityManagerNative
         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;
index bc835b6..f72d318 100755 (executable)
@@ -575,37 +575,36 @@ final class ActivityStack {
      * 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 @@ final class ActivityStack {
             // Launcher...
             if (mMainStack) {
                 ActivityOptions.abort(options);
-                return mService.startHomeActivityLocked(mCurrentUser, null);
+                return mService.startHomeActivityLocked(mCurrentUser);
             }
         }
 
@@ -3577,10 +3576,6 @@ 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.
index fc01f60..a58c4ea 100644 (file)
@@ -83,6 +83,8 @@ public class UserManagerService extends IUserManager.Stub {
 
     private SparseArray<UserInfo> mUsers = new SparseArray<UserInfo>();
 
+    private final int mUserLimit;
+
     private int[] mUserIds;
     private boolean mGuestEnabled;
     private int mNextSerialNumber;
@@ -125,6 +127,8 @@ public class UserManagerService extends IUserManager.Stub {
             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 @@ public class UserManagerService extends IUserManager.Stub {
         // 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 class UserManagerService extends IUserManager.Stub {
     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 @@ public class UserManagerService extends IUserManager.Stub {
         }
 
         // 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 @@ public class UserManagerService extends IUserManager.Stub {
      * 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) {
index 1a101ad..556613e 100755 (executable)
@@ -294,7 +294,6 @@ public class WindowManagerService extends IWindowManager.Stub
                     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;
             }
         }