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/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