Framework side of issue #7302511: GCM client needs to use new framework API...

...to fix background->shutdown delivery race

Add ACTION_STOPPING and ACTION_STARTING to allow such apps to keep track of
which users are started/stopped, and be involved in the process of stopping
a user.

Also get rid of the scale part of the wallpaper transitions, since it seems
like I have still failed at getting the user switch to wait until the new
wallpaper is displayed.

Change-Id: If7e8fdae3544a9d7987a1b9274dc8b49022f6f62
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index c14a703..97d299a 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2327,7 +2327,9 @@
      * 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.
+     * {@link #ACTION_BOOT_COMPLETED} is sent.  This is sent as a foreground
+     * broadcast, since it is part of a visible user interaction; be as quick
+     * as possible when handling it.
      */
     public static final String ACTION_USER_INITIALIZE =
             "android.intent.action.USER_INITIALIZE";
@@ -2337,7 +2339,9 @@
      * 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.
+     * foreground.  This is sent as a foreground
+     * broadcast, since it is part of a visible user interaction; be as quick
+     * as possible when handling it.
      */
     public static final String ACTION_USER_FOREGROUND =
             "android.intent.action.USER_FOREGROUND";
@@ -2347,14 +2351,17 @@
      * 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.
+     * background.  This is sent as a foreground
+     * broadcast, since it is part of a visible user interaction; be as quick
+     * as possible when handling it.
      */
     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.  It is sent to all running users.  You must hold
+     * Broadcast sent to the system when a user is added. Carries an extra
+     * EXTRA_USER_HANDLE that has the 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
      */
@@ -2362,22 +2369,59 @@
             "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
+     * Broadcast sent by 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.
+     * that has been started.  This is sent as a foreground
+     * broadcast, since it is part of a visible user interaction; be as quick
+     * as possible when handling it.
      * @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.  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).
+     * Broadcast sent when a user is in the process of starting.  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 all
+     * users (including the one that is being started).  You must hold
+     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} to receive
+     * this broadcast.  This is sent as a background broadcast, since
+     * its result is not part of the primary UX flow; to safely keep track of
+     * started/stopped state of a user you can use this in conjunction with
+     * {@link #ACTION_USER_STOPPING}.  It is <b>not</b> generally safe to use with
+     * other user state broadcasts since those are foreground broadcasts so can
+     * execute in a different order.
+     * @hide
+     */
+    public static final String ACTION_USER_STARTING =
+            "android.intent.action.USER_STARTING";
+
+    /**
+     * Broadcast sent when a user is going to be stopped.  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 all
+     * users (including the one that is being stopped).  You must hold
+     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} to receive
+     * this broadcast.  The user will not stop until all receivers have
+     * handled the broadcast.  This is sent as a background broadcast, since
+     * its result is not part of the primary UX flow; to safely keep track of
+     * started/stopped state of a user you can use this in conjunction with
+     * {@link #ACTION_USER_STARTING}.  It is <b>not</b> generally safe to use with
+     * other user state broadcasts since those are foreground broadcasts so can
+     * execute in a different order.
+     * @hide
+     */
+    public static final String ACTION_USER_STOPPING =
+            "android.intent.action.USER_STOPPING";
+
+    /**
+     * 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.  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 =
diff --git a/core/res/res/anim/wallpaper_enter.xml b/core/res/res/anim/wallpaper_enter.xml
index 2993a2d..eb826b8 100644
--- a/core/res/res/anim/wallpaper_enter.xml
+++ b/core/res/res/anim/wallpaper_enter.xml
@@ -19,10 +19,12 @@
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
         android:interpolator="@interpolator/decelerate_quad">
+    <!-- Having trouble avoiding this when switching users, so simple fade for now
     <scale android:fromXScale="3.0" android:toXScale="1.0"
            android:fromYScale="3.0" android:toYScale="1.0"
            android:pivotX="50%" android:pivotY="50%"
            android:duration="@android:integer/config_longAnimTime" />
+    -->
     <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
             android:duration="@android:integer/config_longAnimTime" />
 </set>
\ No newline at end of file
diff --git a/core/res/res/anim/wallpaper_exit.xml b/core/res/res/anim/wallpaper_exit.xml
index 5d5b38a..d675afb 100644
--- a/core/res/res/anim/wallpaper_exit.xml
+++ b/core/res/res/anim/wallpaper_exit.xml
@@ -19,10 +19,12 @@
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
         android:interpolator="@interpolator/accelerate_quad">
+    <!-- Having trouble avoiding this when switching users, so simple fade for now
     <scale android:fromXScale="1.0" android:toXScale="3.0"
            android:fromYScale="1.0" android:toYScale="3.0"
            android:pivotX="50%" android:pivotY="50%"
            android:duration="@android:integer/config_longAnimTime" />
+    -->
     <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
             android:duration="@android:integer/config_longAnimTime"/>
 </set>
\ No newline at end of file
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index b1a2a2a..c2aa3a5 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -3739,8 +3739,7 @@
     private void forceStopUserLocked(int userId) {
         forceStopPackageLocked(null, -1, false, false, true, false, userId);
         Intent intent = new Intent(Intent.ACTION_USER_STOPPED);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
-                | Intent.FLAG_RECEIVER_FOREGROUND);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
         intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
         broadcastIntentLocked(null, null, intent,
                 null, null, 0, null, null, null,
@@ -14128,6 +14127,19 @@
 
                 final UserStartedState uss = mStartedUsers.get(userId);
 
+                // Make sure user is in the started state.  If it is currently
+                // stopping, we need to knock that off.
+                if (uss.mState == UserStartedState.STATE_STOPPING) {
+                    // If we are stopping, we haven't sent ACTION_SHUTDOWN,
+                    // so we can just fairly silently bring the user back from
+                    // the almost-dead.
+                    uss.mState = UserStartedState.STATE_RUNNING;
+                } else if (uss.mState == UserStartedState.STATE_SHUTDOWN) {
+                    // This means ACTION_SHUTDOWN has been sent, so we will
+                    // need to treat this as a new boot of the user.
+                    uss.mState = UserStartedState.STATE_BOOTING;
+                }
+
                 mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
                 mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
                 mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
@@ -14205,6 +14217,19 @@
                         null, null, 0, null, null,
                         android.Manifest.permission.MANAGE_USERS,
                         false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
+                intent = new Intent(Intent.ACTION_USER_STARTING);
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId);
+                broadcastIntentLocked(null, null, intent,
+                        null, new IIntentReceiver.Stub() {
+                            @Override
+                            public void performReceive(Intent intent, int resultCode, String data,
+                                    Bundle extras, boolean ordered, boolean sticky, int sendingUser)
+                                    throws RemoteException {
+                            }
+                        }, 0, null, null,
+                        android.Manifest.permission.INTERACT_ACROSS_USERS,
+                        false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -14293,7 +14318,8 @@
 
     void finishUserSwitch(UserStartedState uss) {
         synchronized (this) {
-            if (uss.mState == UserStartedState.STATE_BOOTING
+            if ((uss.mState == UserStartedState.STATE_BOOTING
+                    || uss.mState == UserStartedState.STATE_SHUTDOWN)
                     && mStartedUsers.get(uss.mHandle.getIdentifier()) == uss) {
                 uss.mState = UserStartedState.STATE_RUNNING;
                 final int userId = uss.mHandle.getIdentifier();
@@ -14315,7 +14341,8 @@
                     num--;
                     continue;
                 }
-                if (oldUss.mState == UserStartedState.STATE_STOPPING) {
+                if (oldUss.mState == UserStartedState.STATE_STOPPING
+                        || oldUss.mState == UserStartedState.STATE_SHUTDOWN) {
                     // This user is already stopping, doesn't count.
                     num--;
                     i++;
@@ -14380,23 +14407,50 @@
             uss.mStopCallbacks.add(callback);
         }
 
-        if (uss.mState != UserStartedState.STATE_STOPPING) {
+        if (uss.mState != UserStartedState.STATE_STOPPING
+                && uss.mState != UserStartedState.STATE_SHUTDOWN) {
             uss.mState = UserStartedState.STATE_STOPPING;
 
             long ident = Binder.clearCallingIdentity();
             try {
-                // Inform of user switch
-                Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
-                final IIntentReceiver resultReceiver = new IIntentReceiver.Stub() {
+                // We are going to broadcast ACTION_USER_STOPPING and then
+                // once that is down send a final ACTION_SHUTDOWN and then
+                // stop the user.
+                final Intent stoppingIntent = new Intent(Intent.ACTION_USER_STOPPING);
+                stoppingIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                stoppingIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+                final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN);
+                // This is the result receiver for the final shutdown broadcast.
+                final IIntentReceiver shutdownReceiver = new IIntentReceiver.Stub() {
                     @Override
                     public void performReceive(Intent intent, int resultCode, String data,
                             Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
                         finishUserStop(uss);
                     }
                 };
-                broadcastIntentLocked(null, null, intent,
-                        null, resultReceiver, 0, null, null, null,
-                        true, false, MY_PID, Process.SYSTEM_UID, userId);
+                // This is the result receiver for the initial stopping broadcast.
+                final IIntentReceiver stoppingReceiver = new IIntentReceiver.Stub() {
+                    @Override
+                    public void performReceive(Intent intent, int resultCode, String data,
+                            Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
+                        // On to the next.
+                        synchronized (ActivityManagerService.this) {
+                            if (uss.mState != UserStartedState.STATE_STOPPING) {
+                                // Whoops, we are being started back up.  Abort, abort!
+                                return;
+                            }
+                            uss.mState = UserStartedState.STATE_SHUTDOWN;
+                        }
+                        broadcastIntentLocked(null, null, shutdownIntent,
+                                null, shutdownReceiver, 0, null, null, null,
+                                true, false, MY_PID, Process.SYSTEM_UID, userId);
+                    }
+                };
+                // Kick things off.
+                broadcastIntentLocked(null, null, stoppingIntent,
+                        null, stoppingReceiver, 0, null, null,
+                        android.Manifest.permission.INTERACT_ACROSS_USERS,
+                        true, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -14411,8 +14465,9 @@
         ArrayList<IStopUserCallback> callbacks;
         synchronized (this) {
             callbacks = new ArrayList<IStopUserCallback>(uss.mStopCallbacks);
-            if (uss.mState != UserStartedState.STATE_STOPPING
-                    || mStartedUsers.get(userId) != uss) {
+            if (mStartedUsers.get(userId) != uss) {
+                stopped = false;
+            } else if (uss.mState != UserStartedState.STATE_SHUTDOWN) {
                 stopped = false;
             } else {
                 stopped = true;
@@ -14476,7 +14531,8 @@
 
     boolean isUserRunningLocked(int userId) {
         UserStartedState state = mStartedUsers.get(userId);
-        return state != null && state.mState != UserStartedState.STATE_STOPPING;
+        return state != null && state.mState != UserStartedState.STATE_STOPPING
+                && state.mState != UserStartedState.STATE_SHUTDOWN;
     }
 
     @Override
diff --git a/services/java/com/android/server/am/UserStartedState.java b/services/java/com/android/server/am/UserStartedState.java
index 50c8553..0e71f81 100644
--- a/services/java/com/android/server/am/UserStartedState.java
+++ b/services/java/com/android/server/am/UserStartedState.java
@@ -23,9 +23,14 @@
 import android.os.UserHandle;
 
 public class UserStartedState {
+    // User is first coming up.
     public final static int STATE_BOOTING = 0;
+    // User is in the normal running state.
     public final static int STATE_RUNNING = 1;
+    // User is in the initial process of being stopped.
     public final static int STATE_STOPPING = 2;
+    // User is in the final phase of stopping, sending Intent.ACTION_SHUTDOWN.
+    public final static int STATE_SHUTDOWN = 3;
 
     public final UserHandle mHandle;
     public final ArrayList<IStopUserCallback> mStopCallbacks
@@ -40,7 +45,14 @@
     }
 
     void dump(String prefix, PrintWriter pw) {
-        pw.print(prefix); pw.print("mState="); pw.print(mState);
+        pw.print(prefix); pw.print("mState=");
+        switch (mState) {
+            case STATE_BOOTING: pw.print("BOOTING"); break;
+            case STATE_RUNNING: pw.print("RUNNING"); break;
+            case STATE_STOPPING: pw.print("STOPPING"); break;
+            case STATE_SHUTDOWN: pw.print("SHUTDOWN"); break;
+            default: pw.print(mState); break; 
+        }
         if (switching) pw.print(" SWITCHING");
         if (initializing) pw.print(" INITIALIZING");
         pw.println();