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();