Skip to content

Commit db6a14c

Browse files
author
Amith Yamasani
committed
Fix crashes when quickly adding and removing users
Make USER_REMOVED an ordered broadcast and send it before the user's state is completely removed from the system. This gives services the opportunity to clean up their state, while still having access to the user's directory and UserInfo object (such as serial number). Tell SyncManager to skip over dying/partially created users. Improve UserManager tests, waiting for users to be removed fully. Bug: 7382252 Change-Id: I93cfb39c9efe6f15087bf83c569a2d154ef27168
1 parent ba0372d commit db6a14c

File tree

6 files changed

+94
-24
lines changed

6 files changed

+94
-24
lines changed

core/java/android/content/Intent.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2430,7 +2430,8 @@ public static Intent createChooser(Intent target, CharSequence title) {
24302430
/**
24312431
* Broadcast sent to the system when a user is removed. Carries an extra EXTRA_USER_HANDLE that has
24322432
* the userHandle of the user. It is sent to all running users except the
2433-
* one that has been removed. You must hold
2433+
* one that has been removed. The user will not be completely removed until all receivers have
2434+
* handled the broadcast. You must hold
24342435
* {@link android.Manifest.permission#MANAGE_USERS} to receive this broadcast.
24352436
* @hide
24362437
*/

core/java/android/content/SyncManager.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,9 @@ public void updateRunningAccounts() {
265265
}
266266

267267
private void doDatabaseCleanup() {
268-
for (UserInfo user : mUserManager.getUsers()) {
268+
for (UserInfo user : mUserManager.getUsers(true)) {
269+
// Skip any partially created/removed users
270+
if (user.partial) continue;
269271
Account[] accountsForUser = AccountManagerService.getSingleton().getAccounts(user.id);
270272
mSyncStorageEngine.doDatabaseCleanup(accountsForUser, user.id);
271273
}

services/java/com/android/server/pm/PackageManagerService.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2575,7 +2575,7 @@ ResolveInfo findPreferredActivity(Intent intent, String resolvedType,
25752575
@Override
25762576
public List<ResolveInfo> queryIntentActivities(Intent intent,
25772577
String resolvedType, int flags, int userId) {
2578-
if (!sUserManager.exists(userId)) return null;
2578+
if (!sUserManager.exists(userId)) return Collections.emptyList();
25792579
enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "query intent activities");
25802580
ComponentName comp = intent.getComponent();
25812581
if (comp == null) {
@@ -2615,7 +2615,7 @@ public List<ResolveInfo> queryIntentActivities(Intent intent,
26152615
public List<ResolveInfo> queryIntentActivityOptions(ComponentName caller,
26162616
Intent[] specifics, String[] specificTypes, Intent intent,
26172617
String resolvedType, int flags, int userId) {
2618-
if (!sUserManager.exists(userId)) return null;
2618+
if (!sUserManager.exists(userId)) return Collections.emptyList();
26192619
enforceCrossUserPermission(Binder.getCallingUid(), userId, false,
26202620
"query intent activity options");
26212621
final String resultsAction = intent.getAction();
@@ -2787,7 +2787,7 @@ public List<ResolveInfo> queryIntentActivityOptions(ComponentName caller,
27872787
@Override
27882788
public List<ResolveInfo> queryIntentReceivers(Intent intent, String resolvedType, int flags,
27892789
int userId) {
2790-
if (!sUserManager.exists(userId)) return null;
2790+
if (!sUserManager.exists(userId)) return Collections.emptyList();
27912791
ComponentName comp = intent.getComponent();
27922792
if (comp == null) {
27932793
if (intent.getSelector() != null) {
@@ -2838,7 +2838,7 @@ public ResolveInfo resolveService(Intent intent, String resolvedType, int flags,
28382838
@Override
28392839
public List<ResolveInfo> queryIntentServices(Intent intent, String resolvedType, int flags,
28402840
int userId) {
2841-
if (!sUserManager.exists(userId)) return null;
2841+
if (!sUserManager.exists(userId)) return Collections.emptyList();
28422842
ComponentName comp = intent.getComponent();
28432843
if (comp == null) {
28442844
if (intent.getSelector() != null) {

services/java/com/android/server/pm/UserManagerService.java

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919
import com.android.internal.util.ArrayUtils;
2020
import com.android.internal.util.FastXmlSerializer;
2121

22+
import android.app.Activity;
2223
import android.app.ActivityManager;
2324
import android.app.ActivityManagerNative;
2425
import android.app.IStopUserCallback;
26+
import android.content.BroadcastReceiver;
2527
import android.content.Context;
2628
import android.content.Intent;
2729
import android.content.pm.PackageManager;
@@ -736,21 +738,38 @@ public void userStopAborted(int userId) {
736738
return res == ActivityManager.USER_OP_SUCCESS;
737739
}
738740

739-
void finishRemoveUser(int userHandle) {
741+
void finishRemoveUser(final int userHandle) {
740742
if (DBG) Slog.i(LOG_TAG, "finishRemoveUser " + userHandle);
741-
synchronized (mInstallLock) {
742-
synchronized (mPackagesLock) {
743-
removeUserStateLocked(userHandle);
744-
}
745-
}
746-
if (DBG) Slog.i(LOG_TAG, "Removed user " + userHandle + ", sending broadcast");
747-
// Let other services shutdown any activity
743+
// Let other services shutdown any activity and clean up their state before completely
744+
// wiping the user's system directory and removing from the user list
748745
long ident = Binder.clearCallingIdentity();
749746
try {
750747
Intent addedIntent = new Intent(Intent.ACTION_USER_REMOVED);
751748
addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userHandle);
752-
mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
753-
android.Manifest.permission.MANAGE_USERS);
749+
mContext.sendOrderedBroadcastAsUser(addedIntent, UserHandle.ALL,
750+
android.Manifest.permission.MANAGE_USERS,
751+
752+
new BroadcastReceiver() {
753+
@Override
754+
public void onReceive(Context context, Intent intent) {
755+
if (DBG) {
756+
Slog.i(LOG_TAG,
757+
"USER_REMOVED broadcast sent, cleaning up user data "
758+
+ userHandle);
759+
}
760+
new Thread() {
761+
public void run() {
762+
synchronized (mInstallLock) {
763+
synchronized (mPackagesLock) {
764+
removeUserStateLocked(userHandle);
765+
}
766+
}
767+
}
768+
}.start();
769+
}
770+
},
771+
772+
null, Activity.RESULT_OK, null, null);
754773
} finally {
755774
Binder.restoreCallingIdentity(ident);
756775
}

services/tests/servicestests/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
<uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
3535
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
3636
<uses-permission android:name="android.permission.MANAGE_USERS" />
37-
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
37+
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
3838

3939
<application>
4040
<uses-library android:name="android.test.runner" />

services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,37 @@
1616

1717
package com.android.server.pm;
1818

19+
import android.content.BroadcastReceiver;
1920
import android.content.Context;
21+
import android.content.Intent;
22+
import android.content.IntentFilter;
2023
import android.content.pm.UserInfo;
2124
import android.os.Debug;
2225
import android.os.Environment;
2326
import android.os.UserManager;
2427
import android.test.AndroidTestCase;
2528

29+
import java.util.ArrayList;
2630
import java.util.List;
2731

2832
/** Test {@link UserManager} functionality. */
2933
public class UserManagerTest extends AndroidTestCase {
3034

3135
UserManager mUserManager = null;
36+
Object mUserLock = new Object();
3237

3338
@Override
3439
public void setUp() throws Exception {
3540
mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
41+
IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);
42+
getContext().registerReceiver(new BroadcastReceiver() {
43+
@Override
44+
public void onReceive(Context context, Intent intent) {
45+
synchronized (mUserLock) {
46+
mUserLock.notifyAll();
47+
}
48+
}
49+
}, filter);
3650
}
3751

3852
public void testHasPrimary() throws Exception {
@@ -54,7 +68,7 @@ public void testAddUser() throws Exception {
5468
}
5569
}
5670
assertTrue(found);
57-
mUserManager.removeUser(userInfo.id);
71+
removeUser(userInfo.id);
5872
}
5973

6074
public void testAdd2Users() throws Exception {
@@ -67,14 +81,13 @@ public void testAdd2Users() throws Exception {
6781
assertTrue(findUser(0));
6882
assertTrue(findUser(user1.id));
6983
assertTrue(findUser(user2.id));
70-
mUserManager.removeUser(user1.id);
71-
mUserManager.removeUser(user2.id);
84+
removeUser(user1.id);
85+
removeUser(user2.id);
7286
}
7387

7488
public void testRemoveUser() throws Exception {
7589
UserInfo userInfo = mUserManager.createUser("Guest 1", UserInfo.FLAG_GUEST);
76-
77-
mUserManager.removeUser(userInfo.id);
90+
removeUser(userInfo.id);
7891

7992
assertFalse(findUser(userInfo.id));
8093
}
@@ -95,12 +108,47 @@ public void testSerialNumber() {
95108
int serialNumber1 = user1.serialNumber;
96109
assertEquals(serialNumber1, mUserManager.getUserSerialNumber(user1.id));
97110
assertEquals(user1.id, mUserManager.getUserHandle(serialNumber1));
98-
mUserManager.removeUser(user1.id);
111+
removeUser(user1.id);
99112
UserInfo user2 = mUserManager.createUser("User 2", UserInfo.FLAG_RESTRICTED);
100113
int serialNumber2 = user2.serialNumber;
101114
assertFalse(serialNumber1 == serialNumber2);
102115
assertEquals(serialNumber2, mUserManager.getUserSerialNumber(user2.id));
103116
assertEquals(user2.id, mUserManager.getUserHandle(serialNumber2));
104-
mUserManager.removeUser(user2.id);
117+
removeUser(user2.id);
118+
}
119+
120+
public void testMaxUsers() {
121+
int N = UserManager.getMaxSupportedUsers();
122+
int count = mUserManager.getUsers().size();
123+
List<UserInfo> created = new ArrayList<UserInfo>();
124+
// Create as many users as permitted and make sure creation passes
125+
while (count < N) {
126+
UserInfo ui = mUserManager.createUser("User " + count, 0);
127+
assertNotNull(ui);
128+
created.add(ui);
129+
count++;
130+
}
131+
// Try to create one more user and make sure it fails
132+
UserInfo extra = null;
133+
assertNull(extra = mUserManager.createUser("One more", 0));
134+
if (extra != null) {
135+
removeUser(extra.id);
136+
}
137+
while (!created.isEmpty()) {
138+
UserInfo user = created.remove(0);
139+
removeUser(user.id);
140+
}
141+
}
142+
143+
private void removeUser(int userId) {
144+
synchronized (mUserLock) {
145+
mUserManager.removeUser(userId);
146+
while (mUserManager.getUserInfo(userId) != null) {
147+
try {
148+
mUserLock.wait(1000);
149+
} catch (InterruptedException ie) {
150+
}
151+
}
152+
}
105153
}
106154
}

0 commit comments

Comments
 (0)