1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 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 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 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 15 */ 16 17 package com.android.systemui.statusbar.policy; 18 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; 42 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; 50 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; 56 57 /** 58 * Keeps a list of all users on the device for user switching. 59 */ 60 public class UserSwitcherController { 61 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"; 66 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; 73 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; 80 UserSwitcherController(Context context, KeyguardMonitor keyguardMonitor)81 public UserSwitcherController(Context context, KeyguardMonitor keyguardMonitor) { 82 mContext = context; 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 */); 94 95 96 mContext.getContentResolver().registerContentObserver( 97 Settings.Global.getUriFor(SIMPLE_USER_SWITCHER_GLOBAL_SETTING), true, 98 mSettingsObserver); 99 mContext.getContentResolver().registerContentObserver( 100 Settings.Global.getUriFor(Settings.Global.ADD_USERS_WHEN_LOCKED), true, 101 mSettingsObserver); 102 // Fetch initial values. 103 mSettingsObserver.onChange(false); 104 105 keyguardMonitor.addCallback(mCallback); 106 107 refreshUsers(UserHandle.USER_NULL); 108 } 109 110 /** 111 * Refreshes users from UserManager. 112 * 113 * The pictures are only loaded if they have not been loaded yet. 114 * 115 * @param forcePictureLoadForId forces the picture of the given user to be reloaded. 116 */ 117 @SuppressWarnings("unchecked") refreshUsers(int forcePictureLoadForId)118 private void refreshUsers(int forcePictureLoadForId) { 119 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) { 126 continue; 127 } 128 bitmaps.put(r.info.id, r.picture); 129 } 130 131 final boolean addUsersWhenLocked = mAddUsersWhenLocked; 132 new AsyncTask<SparseArray<Bitmap>, Void, ArrayList<UserRecord>>() { 133 @SuppressWarnings("unchecked") 134 @Override 135 protected ArrayList<UserRecord> doInBackground(SparseArray<Bitmap>... params) { 136 final SparseArray<Bitmap> bitmaps = params[0]; 137 List<UserInfo> infos = mUserManager.getUsers(true); 138 if (infos == null) { 139 return null; 140 } 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); 146 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); 157 158 if (picture != null) { 159 picture = BitmapHelper.createCircularClip( 160 picture, avatarSize, avatarSize); 161 } 162 } 163 int index = isCurrent ? 0 : records.size(); 164 records.add(index, new UserRecord(info, picture, false /* isGuest */, 165 isCurrent, false /* isAddUser */, false /* isRestricted */)); 166 } 167 } 168 169 boolean ownerCanCreateUsers = !mUserManager.hasUserRestriction( 170 UserManager.DISALLOW_ADD_USER, UserHandle.OWNER); 171 boolean currentUserCanCreateUsers = 172 (currentId == UserHandle.USER_OWNER) && ownerCanCreateUsers; 173 boolean anyoneCanCreateUsers = ownerCanCreateUsers && addUsersWhenLocked; 174 boolean canCreateGuest = (currentUserCanCreateUsers || anyoneCanCreateUsers) 175 && guestRecord == null; 176 boolean canCreateUser = (currentUserCanCreateUsers || anyoneCanCreateUsers) 177 && mUserManager.canAddMoreUsers(); 178 boolean createIsRestricted = !addUsersWhenLocked; 179 180 if (!mSimpleUserSwitcher) { 181 if (guestRecord == null) { 182 if (canCreateGuest) { 183 records.add(new UserRecord(null /* info */, null /* picture */, 184 true /* isGuest */, false /* isCurrent */, 185 false /* isAddUser */, createIsRestricted)); 186 } 187 } else { 188 int index = guestRecord.isCurrent ? 0 : records.size(); 189 records.add(index, guestRecord); 190 } 191 } 192 193 if (!mSimpleUserSwitcher && canCreateUser) { 194 records.add(new UserRecord(null /* info */, null /* picture */, 195 false /* isGuest */, false /* isCurrent */, true /* isAddUser */, 196 createIsRestricted)); 197 } 198 199 return records; 200 } 201 202 @Override 203 protected void onPostExecute(ArrayList<UserRecord> userRecords) { 204 if (userRecords != null) { 205 mUsers = userRecords; 206 notifyAdapters(); 207 } 208 } 209 }.execute((SparseArray) bitmaps); 210 } 211 notifyAdapters()212 private void notifyAdapters() { 213 for (int i = mAdapters.size() - 1; i >= 0; i--) { 214 BaseUserAdapter adapter = mAdapters.get(i).get(); 215 if (adapter != null) { 216 adapter.notifyDataSetChanged(); 217 } else { 218 mAdapters.remove(i); 219 } 220 } 221 } 222 isSimpleUserSwitcher()223 public boolean isSimpleUserSwitcher() { 224 return mSimpleUserSwitcher; 225 } 226 switchTo(UserRecord record)227 public void switchTo(UserRecord record) { 228 int id; 229 if (record.isGuest && record.info == null) { 230 // No guest user. Create one. 231 UserInfo guest = mUserManager.createGuest( 232 mContext, mContext.getString(R.string.guest_nickname)); 233 if (guest == null) { 234 // Couldn't create guest, most likely because there already exists one, we just 235 // haven't reloaded the user list yet. 236 return; 237 } 238 id = guest.id; 239 } else if (record.isAddUser) { 240 showAddUserDialog(); 241 return; 242 } else { 243 id = record.info.id; 244 } 245 246 if (ActivityManager.getCurrentUser() == id) { 247 if (record.isGuest) { 248 showExitGuestDialog(id); 249 } 250 return; 251 } 252 253 switchToUserId(id); 254 } 255 switchToUserId(int id)256 private void switchToUserId(int id) { 257 try { 258 ActivityManagerNative.getDefault().switchUser(id); 259 } catch (RemoteException e) { 260 Log.e(TAG, "Couldn't switch user.", e); 261 } 262 } 263 showExitGuestDialog(int id)264 private void showExitGuestDialog(int id) { 265 if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) { 266 mExitGuestDialog.cancel(); 267 } 268 mExitGuestDialog = new ExitGuestDialog(mContext, id); 269 mExitGuestDialog.show(); 270 } 271 showAddUserDialog()272 private void showAddUserDialog() { 273 if (mAddUserDialog != null && mAddUserDialog.isShowing()) { 274 mAddUserDialog.cancel(); 275 } 276 mAddUserDialog = new AddUserDialog(mContext); 277 mAddUserDialog.show(); 278 } 279 exitGuest(int id)280 private void exitGuest(int id) { 281 int newId = UserHandle.USER_OWNER; 282 if (mLastNonGuestUser != UserHandle.USER_OWNER) { 283 UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser); 284 if (info != null && info.isEnabled() && info.supportsSwitchTo()) { 285 newId = info.id; 286 } 287 } 288 switchToUserId(newId); 289 mUserManager.removeUser(id); 290 } 291 292 private BroadcastReceiver mReceiver = new BroadcastReceiver() { 293 @Override 294 public void onReceive(Context context, Intent intent) { 295 if (DEBUG) { 296 Log.v(TAG, "Broadcast: a=" + intent.getAction() 297 + " user=" + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)); 298 } 299 if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) { 300 if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) { 301 mExitGuestDialog.cancel(); 302 mExitGuestDialog = null; 303 } 304 305 final int currentId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); 306 final int N = mUsers.size(); 307 for (int i = 0; i < N; i++) { 308 UserRecord record = mUsers.get(i); 309 if (record.info == null) continue; 310 boolean shouldBeCurrent = record.info.id == currentId; 311 if (record.isCurrent != shouldBeCurrent) { 312 mUsers.set(i, record.copyWithIsCurrent(shouldBeCurrent)); 313 } 314 if (shouldBeCurrent && !record.isGuest) { 315 mLastNonGuestUser = record.info.id; 316 } 317 if (currentId != UserHandle.USER_OWNER && record.isRestricted) { 318 // Immediately remove restricted records in case the AsyncTask is too slow. 319 mUsers.remove(i); 320 i--; 321 } 322 } 323 notifyAdapters(); 324 } 325 int forcePictureLoadForId = UserHandle.USER_NULL; 326 if (Intent.ACTION_USER_INFO_CHANGED.equals(intent.getAction())) { 327 forcePictureLoadForId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 328 UserHandle.USER_NULL); 329 } 330 refreshUsers(forcePictureLoadForId); 331 } 332 }; 333 334 private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) { 335 public void onChange(boolean selfChange) { 336 mSimpleUserSwitcher = Settings.Global.getInt(mContext.getContentResolver(), 337 SIMPLE_USER_SWITCHER_GLOBAL_SETTING, 0) != 0; 338 mAddUsersWhenLocked = Settings.Global.getInt(mContext.getContentResolver(), 339 Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0; 340 refreshUsers(UserHandle.USER_NULL); 341 }; 342 }; 343 dump(FileDescriptor fd, PrintWriter pw, String[] args)344 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 345 pw.println("UserSwitcherController state:"); 346 pw.println(" mLastNonGuestUser=" + mLastNonGuestUser); 347 pw.print(" mUsers.size="); pw.println(mUsers.size()); 348 for (int i = 0; i < mUsers.size(); i++) { 349 final UserRecord u = mUsers.get(i); 350 pw.print(" "); pw.println(u.toString()); 351 } 352 } 353 getCurrentUserName(Context context)354 public String getCurrentUserName(Context context) { 355 if (mUsers.isEmpty()) return null; 356 UserRecord item = mUsers.get(0); 357 if (item == null || item.info == null) return null; 358 if (item.isGuest) return context.getString(R.string.guest_nickname); 359 return item.info.name; 360 } 361 362 public static abstract class BaseUserAdapter extends BaseAdapter { 363 364 final UserSwitcherController mController; 365 BaseUserAdapter(UserSwitcherController controller)366 protected BaseUserAdapter(UserSwitcherController controller) { 367 mController = controller; 368 controller.mAdapters.add(new WeakReference<>(this)); 369 } 370 371 @Override getCount()372 public int getCount() { 373 boolean secureKeyguardShowing = mController.mKeyguardMonitor.isShowing() 374 && mController.mKeyguardMonitor.isSecure(); 375 if (!secureKeyguardShowing) { 376 return mController.mUsers.size(); 377 } 378 // The lock screen is secure and showing. Filter out restricted records. 379 final int N = mController.mUsers.size(); 380 int count = 0; 381 for (int i = 0; i < N; i++) { 382 if (mController.mUsers.get(i).isRestricted) { 383 break; 384 } else { 385 count++; 386 } 387 } 388 return count; 389 } 390 391 @Override getItem(int position)392 public UserRecord getItem(int position) { 393 return mController.mUsers.get(position); 394 } 395 396 @Override getItemId(int position)397 public long getItemId(int position) { 398 return position; 399 } 400 switchTo(UserRecord record)401 public void switchTo(UserRecord record) { 402 mController.switchTo(record); 403 } 404 getName(Context context, UserRecord item)405 public String getName(Context context, UserRecord item) { 406 if (item.isGuest) { 407 if (item.isCurrent) { 408 return context.getString(R.string.guest_exit_guest); 409 } else { 410 return context.getString( 411 item.info == null ? R.string.guest_new_guest : R.string.guest_nickname); 412 } 413 } else if (item.isAddUser) { 414 return context.getString(R.string.user_add_user); 415 } else { 416 return item.info.name; 417 } 418 } 419 getDrawable(Context context, UserRecord item)420 public Drawable getDrawable(Context context, UserRecord item) { 421 if (item.isAddUser) { 422 return context.getDrawable(R.drawable.ic_add_circle_qs); 423 } 424 return UserIcons.getDefaultUserIcon(item.isGuest ? UserHandle.USER_NULL : item.info.id, 425 /* light= */ true); 426 } 427 refresh()428 public void refresh() { 429 mController.refreshUsers(UserHandle.USER_NULL); 430 } 431 } 432 433 public static final class UserRecord { 434 public final UserInfo info; 435 public final Bitmap picture; 436 public final boolean isGuest; 437 public final boolean isCurrent; 438 public final boolean isAddUser; 439 /** If true, the record is only visible to the owner and only when unlocked. */ 440 public final boolean isRestricted; 441 UserRecord(UserInfo info, Bitmap picture, boolean isGuest, boolean isCurrent, boolean isAddUser, boolean isRestricted)442 public UserRecord(UserInfo info, Bitmap picture, boolean isGuest, boolean isCurrent, 443 boolean isAddUser, boolean isRestricted) { 444 this.info = info; 445 this.picture = picture; 446 this.isGuest = isGuest; 447 this.isCurrent = isCurrent; 448 this.isAddUser = isAddUser; 449 this.isRestricted = isRestricted; 450 } 451 copyWithIsCurrent(boolean _isCurrent)452 public UserRecord copyWithIsCurrent(boolean _isCurrent) { 453 return new UserRecord(info, picture, isGuest, _isCurrent, isAddUser, isRestricted); 454 } 455 toString()456 public String toString() { 457 StringBuilder sb = new StringBuilder(); 458 sb.append("UserRecord("); 459 if (info != null) { 460 sb.append("name=\"" + info.name + "\" id=" + info.id); 461 } else { 462 if (isGuest) { 463 sb.append("<add guest placeholder>"); 464 } else if (isAddUser) { 465 sb.append("<add user placeholder>"); 466 } 467 } 468 if (isGuest) sb.append(" <isGuest>"); 469 if (isAddUser) sb.append(" <isAddUser>"); 470 if (isCurrent) sb.append(" <isCurrent>"); 471 if (picture != null) sb.append(" <hasPicture>"); 472 if (isRestricted) sb.append(" <isRestricted>"); 473 sb.append(')'); 474 return sb.toString(); 475 } 476 } 477 478 public final QSTile.DetailAdapter userDetailAdapter = new QSTile.DetailAdapter() { 479 private final Intent USER_SETTINGS_INTENT = new Intent("android.settings.USER_SETTINGS"); 480 481 @Override 482 public int getTitle() { 483 return R.string.quick_settings_user_title; 484 } 485 486 @Override 487 public View createDetailView(Context context, View convertView, ViewGroup parent) { 488 UserDetailView v; 489 if (!(convertView instanceof UserDetailView)) { 490 v = UserDetailView.inflate(context, parent, false); 491 v.createAndSetAdapter(UserSwitcherController.this); 492 } else { 493 v = (UserDetailView) convertView; 494 } 495 v.refreshAdapter(); 496 return v; 497 } 498 499 @Override 500 public Intent getSettingsIntent() { 501 return USER_SETTINGS_INTENT; 502 } 503 504 @Override 505 public Boolean getToggleState() { 506 return null; 507 } 508 509 @Override 510 public void setToggleState(boolean state) { 511 } 512 }; 513 514 private final KeyguardMonitor.Callback mCallback = new KeyguardMonitor.Callback() { 515 @Override 516 public void onKeyguardChanged() { 517 notifyAdapters(); 518 } 519 }; 520 521 private final class ExitGuestDialog extends SystemUIDialog implements 522 DialogInterface.OnClickListener { 523 524 private final int mGuestId; 525 ExitGuestDialog(Context context, int guestId)526 public ExitGuestDialog(Context context, int guestId) { 527 super(context); 528 setTitle(R.string.guest_exit_guest_dialog_title); 529 setMessage(context.getString(R.string.guest_exit_guest_dialog_message)); 530 setButton(DialogInterface.BUTTON_NEGATIVE, 531 context.getString(android.R.string.cancel), this); 532 setButton(DialogInterface.BUTTON_POSITIVE, 533 context.getString(R.string.guest_exit_guest_dialog_remove), this); 534 setCanceledOnTouchOutside(false); 535 mGuestId = guestId; 536 } 537 538 @Override onClick(DialogInterface dialog, int which)539 public void onClick(DialogInterface dialog, int which) { 540 if (which == BUTTON_NEGATIVE) { 541 cancel(); 542 } else { 543 dismiss(); 544 exitGuest(mGuestId); 545 } 546 } 547 } 548 549 private final class AddUserDialog extends SystemUIDialog implements 550 DialogInterface.OnClickListener { 551 AddUserDialog(Context context)552 public AddUserDialog(Context context) { 553 super(context); 554 setTitle(R.string.user_add_user_title); 555 setMessage(context.getString(R.string.user_add_user_message_short)); 556 setButton(DialogInterface.BUTTON_NEGATIVE, 557 context.getString(android.R.string.cancel), this); 558 setButton(DialogInterface.BUTTON_POSITIVE, 559 context.getString(android.R.string.ok), this); 560 } 561 562 @Override onClick(DialogInterface dialog, int which)563 public void onClick(DialogInterface dialog, int which) { 564 if (which == BUTTON_NEGATIVE) { 565 cancel(); 566 } else { 567 dismiss(); 568 if (ActivityManager.isUserAMonkey()) { 569 return; 570 } 571 UserInfo user = mUserManager.createSecondaryUser( 572 mContext.getString(R.string.user_new_user_name), 0 /* flags */); 573 if (user == null) { 574 // Couldn't create user, most likely because there are too many, but we haven't 575 // been able to reload the list yet. 576 return; 577 } 578 int id = user.id; 579 Bitmap icon = UserIcons.convertToBitmap(UserIcons.getDefaultUserIcon( 580 id, /* light= */ false)); 581 mUserManager.setUserIcon(id, icon); 582 switchToUserId(id); 583 } 584 } 585 } 586 isUserSwitcherAvailable(UserManager um)587 public static boolean isUserSwitcherAvailable(UserManager um) { 588 return UserManager.supportsMultipleUsers() && um.isUserSwitcherEnabled(); 589 } 590 591 } 592