2 * Copyright (C) 2014 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
17 package com.android.systemui.statusbar.policy;
19 import android.app.ActivityManager;
20 import android.app.ActivityManagerNative;
21 import android.app.Dialog;
22 import android.content.BroadcastReceiver;
23 import android.content.Context;
24 import android.content.DialogInterface;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.content.pm.UserInfo;
28 import android.database.ContentObserver;
29 import android.graphics.Bitmap;
30 import android.graphics.drawable.Drawable;
31 import android.os.AsyncTask;
32 import android.os.Handler;
33 import android.os.RemoteException;
34 import android.os.UserHandle;
35 import android.os.UserManager;
36 import android.provider.Settings;
37 import android.util.Log;
38 import android.util.SparseArray;
39 import android.view.View;
40 import android.view.ViewGroup;
41 import android.widget.BaseAdapter;
43 import com.android.internal.util.UserIcons;
44 import com.android.systemui.BitmapHelper;
45 import com.android.systemui.GuestResumeSessionReceiver;
46 import com.android.systemui.R;
47 import com.android.systemui.qs.QSTile;
48 import com.android.systemui.qs.tiles.UserDetailView;
49 import com.android.systemui.statusbar.phone.SystemUIDialog;
51 import java.io.FileDescriptor;
52 import java.io.PrintWriter;
53 import java.lang.ref.WeakReference;
54 import java.util.ArrayList;
55 import java.util.List;
58 * Keeps a list of all users on the device for user switching.
60 public class UserSwitcherController {
62 private static final String TAG = "UserSwitcherController";
63 private static final boolean DEBUG = false;
64 private static final String SIMPLE_USER_SWITCHER_GLOBAL_SETTING =
65 "lockscreenSimpleUserSwitcher";
67 private final Context mContext;
68 private final UserManager mUserManager;
69 private final ArrayList<WeakReference<BaseUserAdapter>> mAdapters = new ArrayList<>();
70 private final GuestResumeSessionReceiver mGuestResumeSessionReceiver
71 = new GuestResumeSessionReceiver();
72 private final KeyguardMonitor mKeyguardMonitor;
74 private ArrayList<UserRecord> mUsers = new ArrayList<>();
75 private Dialog mExitGuestDialog;
76 private Dialog mAddUserDialog;
77 private int mLastNonGuestUser = UserHandle.USER_OWNER;
78 private boolean mSimpleUserSwitcher;
79 private boolean mAddUsersWhenLocked;
81 public UserSwitcherController(Context context, KeyguardMonitor keyguardMonitor) {
83 mGuestResumeSessionReceiver.register(context);
84 mKeyguardMonitor = keyguardMonitor;
85 mUserManager = UserManager.get(context);
86 IntentFilter filter = new IntentFilter();
87 filter.addAction(Intent.ACTION_USER_ADDED);
88 filter.addAction(Intent.ACTION_USER_REMOVED);
89 filter.addAction(Intent.ACTION_USER_INFO_CHANGED);
90 filter.addAction(Intent.ACTION_USER_SWITCHED);
91 filter.addAction(Intent.ACTION_USER_STOPPING);
92 mContext.registerReceiverAsUser(mReceiver, UserHandle.OWNER, filter,
93 null /* permission */, null /* scheduler */);
96 mContext.getContentResolver().registerContentObserver(
97 Settings.Global.getUriFor(SIMPLE_USER_SWITCHER_GLOBAL_SETTING), true,
99 mContext.getContentResolver().registerContentObserver(
100 Settings.Global.getUriFor(Settings.Global.ADD_USERS_WHEN_LOCKED), true,
102 // Fetch initial values.
103 mSettingsObserver.onChange(false);
105 keyguardMonitor.addCallback(mCallback);
107 refreshUsers(UserHandle.USER_NULL);
111 * Refreshes users from UserManager.
113 * The pictures are only loaded if they have not been loaded yet.
115 * @param forcePictureLoadForId forces the picture of the given user to be reloaded.
117 @SuppressWarnings("unchecked")
118 private void refreshUsers(int forcePictureLoadForId) {
120 SparseArray<Bitmap> bitmaps = new SparseArray<>(mUsers.size());
121 final int N = mUsers.size();
122 for (int i = 0; i < N; i++) {
123 UserRecord r = mUsers.get(i);
124 if (r == null || r.info == null
125 || r.info.id == forcePictureLoadForId || r.picture == null) {
128 bitmaps.put(r.info.id, r.picture);
131 final boolean addUsersWhenLocked = mAddUsersWhenLocked;
132 new AsyncTask<SparseArray<Bitmap>, Void, ArrayList<UserRecord>>() {
133 @SuppressWarnings("unchecked")
135 protected ArrayList<UserRecord> doInBackground(SparseArray<Bitmap>... params) {
136 final SparseArray<Bitmap> bitmaps = params[0];
137 List<UserInfo> infos = mUserManager.getUsers(true);
141 ArrayList<UserRecord> records = new ArrayList<>(infos.size());
142 int currentId = ActivityManager.getCurrentUser();
143 UserRecord guestRecord = null;
144 int avatarSize = mContext.getResources()
145 .getDimensionPixelSize(R.dimen.max_avatar_size);
147 for (UserInfo info : infos) {
148 boolean isCurrent = currentId == info.id;
149 if (info.isGuest()) {
150 guestRecord = new UserRecord(info, null /* picture */,
151 true /* isGuest */, isCurrent, false /* isAddUser */,
152 false /* isRestricted */);
153 } else if (info.supportsSwitchTo()) {
154 Bitmap picture = bitmaps.get(info.id);
155 if (picture == null) {
156 picture = mUserManager.getUserIcon(info.id);
158 if (picture != null) {
159 picture = BitmapHelper.createCircularClip(
160 picture, avatarSize, avatarSize);
162 int index = isCurrent ? 0 : records.size();
163 records.add(index, new UserRecord(info, picture, false /* isGuest */,
164 isCurrent, false /* isAddUser */, false /* isRestricted */));
168 boolean ownerCanCreateUsers = !mUserManager.hasUserRestriction(
169 UserManager.DISALLOW_ADD_USER, UserHandle.OWNER);
170 boolean currentUserCanCreateUsers =
171 (currentId == UserHandle.USER_OWNER) && ownerCanCreateUsers;
172 boolean anyoneCanCreateUsers = ownerCanCreateUsers && addUsersWhenLocked;
173 boolean canCreateGuest = (currentUserCanCreateUsers || anyoneCanCreateUsers)
174 && guestRecord == null;
175 boolean canCreateUser = (currentUserCanCreateUsers || anyoneCanCreateUsers)
176 && mUserManager.canAddMoreUsers();
177 boolean createIsRestricted = !addUsersWhenLocked;
179 if (!mSimpleUserSwitcher) {
180 if (guestRecord == null) {
181 if (canCreateGuest) {
182 records.add(new UserRecord(null /* info */, null /* picture */,
183 true /* isGuest */, false /* isCurrent */,
184 false /* isAddUser */, createIsRestricted));
187 int index = guestRecord.isCurrent ? 0 : records.size();
188 records.add(index, guestRecord);
192 if (!mSimpleUserSwitcher && canCreateUser) {
193 records.add(new UserRecord(null /* info */, null /* picture */,
194 false /* isGuest */, false /* isCurrent */, true /* isAddUser */,
195 createIsRestricted));
202 protected void onPostExecute(ArrayList<UserRecord> userRecords) {
203 if (userRecords != null) {
204 mUsers = userRecords;
208 }.execute((SparseArray) bitmaps);
211 private void notifyAdapters() {
212 for (int i = mAdapters.size() - 1; i >= 0; i--) {
213 BaseUserAdapter adapter = mAdapters.get(i).get();
214 if (adapter != null) {
215 adapter.notifyDataSetChanged();
222 public boolean isSimpleUserSwitcher() {
223 return mSimpleUserSwitcher;
226 public void switchTo(UserRecord record) {
228 if (record.isGuest && record.info == null) {
229 // No guest user. Create one.
230 UserInfo guest = mUserManager.createGuest(
231 mContext, mContext.getString(R.string.guest_nickname));
233 // Couldn't create guest, most likely because there already exists one, we just
234 // haven't reloaded the user list yet.
238 } else if (record.isAddUser) {
245 if (ActivityManager.getCurrentUser() == id) {
246 if (record.isGuest) {
247 showExitGuestDialog(id);
255 private void switchToUserId(int id) {
257 ActivityManagerNative.getDefault().switchUser(id);
258 } catch (RemoteException e) {
259 Log.e(TAG, "Couldn't switch user.", e);
263 private void showExitGuestDialog(int id) {
264 if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
265 mExitGuestDialog.cancel();
267 mExitGuestDialog = new ExitGuestDialog(mContext, id);
268 mExitGuestDialog.show();
271 private void showAddUserDialog() {
272 if (mAddUserDialog != null && mAddUserDialog.isShowing()) {
273 mAddUserDialog.cancel();
275 mAddUserDialog = new AddUserDialog(mContext);
276 mAddUserDialog.show();
279 private void exitGuest(int id) {
280 int newId = UserHandle.USER_OWNER;
281 if (mLastNonGuestUser != UserHandle.USER_OWNER) {
282 UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
283 if (info != null && info.isEnabled() && info.supportsSwitchTo()) {
287 switchToUserId(newId);
288 mUserManager.removeUser(id);
291 private BroadcastReceiver mReceiver = new BroadcastReceiver() {
293 public void onReceive(Context context, Intent intent) {
295 Log.v(TAG, "Broadcast: a=" + intent.getAction()
296 + " user=" + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1));
298 if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
299 if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
300 mExitGuestDialog.cancel();
301 mExitGuestDialog = null;
304 final int currentId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
305 final int N = mUsers.size();
306 for (int i = 0; i < N; i++) {
307 UserRecord record = mUsers.get(i);
308 if (record.info == null) continue;
309 boolean shouldBeCurrent = record.info.id == currentId;
310 if (record.isCurrent != shouldBeCurrent) {
311 mUsers.set(i, record.copyWithIsCurrent(shouldBeCurrent));
313 if (shouldBeCurrent && !record.isGuest) {
314 mLastNonGuestUser = record.info.id;
316 if (currentId != UserHandle.USER_OWNER && record.isRestricted) {
317 // Immediately remove restricted records in case the AsyncTask is too slow.
324 int forcePictureLoadForId = UserHandle.USER_NULL;
325 if (Intent.ACTION_USER_INFO_CHANGED.equals(intent.getAction())) {
326 forcePictureLoadForId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
327 UserHandle.USER_NULL);
329 refreshUsers(forcePictureLoadForId);
333 private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) {
334 public void onChange(boolean selfChange) {
335 mSimpleUserSwitcher = Settings.Global.getInt(mContext.getContentResolver(),
336 SIMPLE_USER_SWITCHER_GLOBAL_SETTING, 0) != 0;
337 mAddUsersWhenLocked = Settings.Global.getInt(mContext.getContentResolver(),
338 Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0;
339 refreshUsers(UserHandle.USER_NULL);
343 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
344 pw.println("UserSwitcherController state:");
345 pw.println(" mLastNonGuestUser=" + mLastNonGuestUser);
346 pw.print(" mUsers.size="); pw.println(mUsers.size());
347 for (int i = 0; i < mUsers.size(); i++) {
348 final UserRecord u = mUsers.get(i);
349 pw.print(" "); pw.println(u.toString());
353 public String getCurrentUserName(Context context) {
354 if (mUsers.isEmpty()) return null;
355 UserRecord item = mUsers.get(0);
356 if (item == null || item.info == null) return null;
357 if (item.isGuest) return context.getString(R.string.guest_nickname);
358 return item.info.name;
361 public static abstract class BaseUserAdapter extends BaseAdapter {
363 final UserSwitcherController mController;
365 protected BaseUserAdapter(UserSwitcherController controller) {
366 mController = controller;
367 controller.mAdapters.add(new WeakReference<>(this));
371 public int getCount() {
372 boolean secureKeyguardShowing = mController.mKeyguardMonitor.isShowing()
373 && mController.mKeyguardMonitor.isSecure();
374 if (!secureKeyguardShowing) {
375 return mController.mUsers.size();
377 // The lock screen is secure and showing. Filter out restricted records.
378 final int N = mController.mUsers.size();
380 for (int i = 0; i < N; i++) {
381 if (mController.mUsers.get(i).isRestricted) {
391 public UserRecord getItem(int position) {
392 return mController.mUsers.get(position);
396 public long getItemId(int position) {
400 public void switchTo(UserRecord record) {
401 mController.switchTo(record);
404 public String getName(Context context, UserRecord item) {
406 if (item.isCurrent) {
407 return context.getString(R.string.guest_exit_guest);
409 return context.getString(
410 item.info == null ? R.string.guest_new_guest : R.string.guest_nickname);
412 } else if (item.isAddUser) {
413 return context.getString(R.string.user_add_user);
415 return item.info.name;
419 public int getSwitchableUsers() {
421 ArrayList<UserRecord> users = mController.mUsers;
422 int N = users.size();
423 for (int i = 0; i < N; i++) {
424 if (users.get(i).info != null) {
431 public Drawable getDrawable(Context context, UserRecord item) {
432 if (item.isAddUser) {
433 return context.getDrawable(R.drawable.ic_add_circle_qs);
435 return UserIcons.getDefaultUserIcon(item.isGuest ? UserHandle.USER_NULL : item.info.id,
440 public static final class UserRecord {
441 public final UserInfo info;
442 public final Bitmap picture;
443 public final boolean isGuest;
444 public final boolean isCurrent;
445 public final boolean isAddUser;
446 /** If true, the record is only visible to the owner and only when unlocked. */
447 public final boolean isRestricted;
449 public UserRecord(UserInfo info, Bitmap picture, boolean isGuest, boolean isCurrent,
450 boolean isAddUser, boolean isRestricted) {
452 this.picture = picture;
453 this.isGuest = isGuest;
454 this.isCurrent = isCurrent;
455 this.isAddUser = isAddUser;
456 this.isRestricted = isRestricted;
459 public UserRecord copyWithIsCurrent(boolean _isCurrent) {
460 return new UserRecord(info, picture, isGuest, _isCurrent, isAddUser, isRestricted);
463 public String toString() {
464 StringBuilder sb = new StringBuilder();
465 sb.append("UserRecord(");
467 sb.append("name=\"" + info.name + "\" id=" + info.id);
470 sb.append("<add guest placeholder>");
471 } else if (isAddUser) {
472 sb.append("<add user placeholder>");
475 if (isGuest) sb.append(" <isGuest>");
476 if (isAddUser) sb.append(" <isAddUser>");
477 if (isCurrent) sb.append(" <isCurrent>");
478 if (picture != null) sb.append(" <hasPicture>");
479 if (isRestricted) sb.append(" <isRestricted>");
481 return sb.toString();
485 public final QSTile.DetailAdapter userDetailAdapter = new QSTile.DetailAdapter() {
486 private final Intent USER_SETTINGS_INTENT = new Intent("android.settings.USER_SETTINGS");
489 public int getTitle() {
490 return R.string.quick_settings_user_title;
494 public View createDetailView(Context context, View convertView, ViewGroup parent) {
496 if (!(convertView instanceof UserDetailView)) {
497 v = UserDetailView.inflate(context, parent, false);
498 v.createAndSetAdapter(UserSwitcherController.this);
500 v = (UserDetailView) convertView;
506 public Intent getSettingsIntent() {
507 return USER_SETTINGS_INTENT;
511 public Boolean getToggleState() {
516 public void setToggleState(boolean state) {
520 private final KeyguardMonitor.Callback mCallback = new KeyguardMonitor.Callback() {
522 public void onKeyguardChanged() {
527 private final class ExitGuestDialog extends SystemUIDialog implements
528 DialogInterface.OnClickListener {
530 private final int mGuestId;
532 public ExitGuestDialog(Context context, int guestId) {
534 setTitle(R.string.guest_exit_guest_dialog_title);
535 setMessage(context.getString(R.string.guest_exit_guest_dialog_message));
536 setButton(DialogInterface.BUTTON_NEGATIVE,
537 context.getString(android.R.string.cancel), this);
538 setButton(DialogInterface.BUTTON_POSITIVE,
539 context.getString(R.string.guest_exit_guest_dialog_remove), this);
540 setCanceledOnTouchOutside(false);
545 public void onClick(DialogInterface dialog, int which) {
546 if (which == BUTTON_NEGATIVE) {
555 private final class AddUserDialog extends SystemUIDialog implements
556 DialogInterface.OnClickListener {
558 public AddUserDialog(Context context) {
560 setTitle(R.string.user_add_user_title);
561 setMessage(context.getString(R.string.user_add_user_message_short));
562 setButton(DialogInterface.BUTTON_NEGATIVE,
563 context.getString(android.R.string.cancel), this);
564 setButton(DialogInterface.BUTTON_POSITIVE,
565 context.getString(android.R.string.ok), this);
569 public void onClick(DialogInterface dialog, int which) {
570 if (which == BUTTON_NEGATIVE) {
574 UserInfo user = mUserManager.createSecondaryUser(
575 mContext.getString(R.string.user_new_user_name), 0 /* flags */);
577 // Couldn't create user, most likely because there are too many, but we haven't
578 // been able to reload the list yet.
582 Bitmap icon = UserIcons.convertToBitmap(UserIcons.getDefaultUserIcon(
583 id, /* light= */ false));
584 mUserManager.setUserIcon(id, icon);