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 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") 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 int index = isCurrent ? 0 : records.size(); 163 records.add(index, new UserRecord(info, picture, false /* isGuest */, 164 isCurrent, false /* isAddUser */, false /* isRestricted */)); 165 } 166 } 167 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; 178 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)); 185 } 186 } else { 187 int index = guestRecord.isCurrent ? 0 : records.size(); 188 records.add(index, guestRecord); 189 } 190 } 191 192 if (!mSimpleUserSwitcher && canCreateUser) { 193 records.add(new UserRecord(null /* info */, null /* picture */, 194 false /* isGuest */, false /* isCurrent */, true /* isAddUser */, 195 createIsRestricted)); 196 } 197 198 return records; 199 } 200 201 @Override 202 protected void onPostExecute(ArrayList<UserRecord> userRecords) { 203 if (userRecords != null) { 204 mUsers = userRecords; 205 notifyAdapters(); 206 } 207 } 208 }.execute((SparseArray) bitmaps); 209 } 210 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(); 216 } else { 217 mAdapters.remove(i); 218 } 219 } 220 } 221 222 public boolean isSimpleUserSwitcher() { 223 return mSimpleUserSwitcher; 224 } 225 226 public void switchTo(UserRecord record) { 227 int id; 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)); 232 if (guest == null) { 233 // Couldn't create guest, most likely because there already exists one, we just 234 // haven't reloaded the user list yet. 235 return; 236 } 237 id = guest.id; 238 } else if (record.isAddUser) { 239 showAddUserDialog(); 240 return; 241 } else { 242 id = record.info.id; 243 } 244 245 if (ActivityManager.getCurrentUser() == id) { 246 if (record.isGuest) { 247 showExitGuestDialog(id); 248 } 249 return; 250 } 251 252 switchToUserId(id); 253 } 254 255 private void switchToUserId(int id) { 256 try { 257 ActivityManagerNative.getDefault().switchUser(id); 258 } catch (RemoteException e) { 259 Log.e(TAG, "Couldn't switch user.", e); 260 } 261 } 262 263 private void showExitGuestDialog(int id) { 264 if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) { 265 mExitGuestDialog.cancel(); 266 } 267 mExitGuestDialog = new ExitGuestDialog(mContext, id); 268 mExitGuestDialog.show(); 269 } 270 271 private void showAddUserDialog() { 272 if (mAddUserDialog != null && mAddUserDialog.isShowing()) { 273 mAddUserDialog.cancel(); 274 } 275 mAddUserDialog = new AddUserDialog(mContext); 276 mAddUserDialog.show(); 277 } 278 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()) { 284 newId = info.id; 285 } 286 } 287 switchToUserId(newId); 288 mUserManager.removeUser(id); 289 } 290 291 private BroadcastReceiver mReceiver = new BroadcastReceiver() { 292 @Override 293 public void onReceive(Context context, Intent intent) { 294 if (DEBUG) { 295 Log.v(TAG, "Broadcast: a=" + intent.getAction() 296 + " user=" + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)); 297 } 298 if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) { 299 if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) { 300 mExitGuestDialog.cancel(); 301 mExitGuestDialog = null; 302 } 303 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)); 312 } 313 if (shouldBeCurrent && !record.isGuest) { 314 mLastNonGuestUser = record.info.id; 315 } 316 if (currentId != UserHandle.USER_OWNER && record.isRestricted) { 317 // Immediately remove restricted records in case the AsyncTask is too slow. 318 mUsers.remove(i); 319 i--; 320 } 321 } 322 notifyAdapters(); 323 } 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); 328 } 329 refreshUsers(forcePictureLoadForId); 330 } 331 }; 332 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); 340 }; 341 }; 342 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()); 350 } 351 } 352 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; 359 } 360 361 public static abstract class BaseUserAdapter extends BaseAdapter { 362 363 final UserSwitcherController mController; 364 365 protected BaseUserAdapter(UserSwitcherController controller) { 366 mController = controller; 367 controller.mAdapters.add(new WeakReference<>(this)); 368 } 369 370 @Override 371 public int getCount() { 372 boolean secureKeyguardShowing = mController.mKeyguardMonitor.isShowing() 373 && mController.mKeyguardMonitor.isSecure(); 374 if (!secureKeyguardShowing) { 375 return mController.mUsers.size(); 376 } 377 // The lock screen is secure and showing. Filter out restricted records. 378 final int N = mController.mUsers.size(); 379 int count = 0; 380 for (int i = 0; i < N; i++) { 381 if (mController.mUsers.get(i).isRestricted) { 382 break; 383 } else { 384 count++; 385 } 386 } 387 return count; 388 } 389 390 @Override 391 public UserRecord getItem(int position) { 392 return mController.mUsers.get(position); 393 } 394 395 @Override 396 public long getItemId(int position) { 397 return position; 398 } 399 400 public void switchTo(UserRecord record) { 401 mController.switchTo(record); 402 } 403 404 public String getName(Context context, UserRecord item) { 405 if (item.isGuest) { 406 if (item.isCurrent) { 407 return context.getString(R.string.guest_exit_guest); 408 } else { 409 return context.getString( 410 item.info == null ? R.string.guest_new_guest : R.string.guest_nickname); 411 } 412 } else if (item.isAddUser) { 413 return context.getString(R.string.user_add_user); 414 } else { 415 return item.info.name; 416 } 417 } 418 419 public int getSwitchableUsers() { 420 int result = 0; 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) { 425 result++; 426 } 427 } 428 return result; 429 } 430 431 public Drawable getDrawable(Context context, UserRecord item) { 432 if (item.isAddUser) { 433 return context.getDrawable(R.drawable.ic_add_circle_qs); 434 } 435 return UserIcons.getDefaultUserIcon(item.isGuest ? UserHandle.USER_NULL : item.info.id, 436 /* light= */ true); 437 } 438 } 439 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; 448 449 public UserRecord(UserInfo info, Bitmap picture, boolean isGuest, boolean isCurrent, 450 boolean isAddUser, boolean isRestricted) { 451 this.info = info; 452 this.picture = picture; 453 this.isGuest = isGuest; 454 this.isCurrent = isCurrent; 455 this.isAddUser = isAddUser; 456 this.isRestricted = isRestricted; 457 } 458 459 public UserRecord copyWithIsCurrent(boolean _isCurrent) { 460 return new UserRecord(info, picture, isGuest, _isCurrent, isAddUser, isRestricted); 461 } 462 463 public String toString() { 464 StringBuilder sb = new StringBuilder(); 465 sb.append("UserRecord("); 466 if (info != null) { 467 sb.append("name=\"" + info.name + "\" id=" + info.id); 468 } else { 469 if (isGuest) { 470 sb.append("<add guest placeholder>"); 471 } else if (isAddUser) { 472 sb.append("<add user placeholder>"); 473 } 474 } 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>"); 480 sb.append(')'); 481 return sb.toString(); 482 } 483 } 484 485 public final QSTile.DetailAdapter userDetailAdapter = new QSTile.DetailAdapter() { 486 private final Intent USER_SETTINGS_INTENT = new Intent("android.settings.USER_SETTINGS"); 487 488 @Override 489 public int getTitle() { 490 return R.string.quick_settings_user_title; 491 } 492 493 @Override 494 public View createDetailView(Context context, View convertView, ViewGroup parent) { 495 UserDetailView v; 496 if (!(convertView instanceof UserDetailView)) { 497 v = UserDetailView.inflate(context, parent, false); 498 v.createAndSetAdapter(UserSwitcherController.this); 499 } else { 500 v = (UserDetailView) convertView; 501 } 502 return v; 503 } 504 505 @Override 506 public Intent getSettingsIntent() { 507 return USER_SETTINGS_INTENT; 508 } 509 510 @Override 511 public Boolean getToggleState() { 512 return null; 513 } 514 515 @Override 516 public void setToggleState(boolean state) { 517 } 518 }; 519 520 private final KeyguardMonitor.Callback mCallback = new KeyguardMonitor.Callback() { 521 @Override 522 public void onKeyguardChanged() { 523 notifyAdapters(); 524 } 525 }; 526 527 private final class ExitGuestDialog extends SystemUIDialog implements 528 DialogInterface.OnClickListener { 529 530 private final int mGuestId; 531 532 public ExitGuestDialog(Context context, int guestId) { 533 super(context); 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); 541 mGuestId = guestId; 542 } 543 544 @Override 545 public void onClick(DialogInterface dialog, int which) { 546 if (which == BUTTON_NEGATIVE) { 547 cancel(); 548 } else { 549 dismiss(); 550 exitGuest(mGuestId); 551 } 552 } 553 } 554 555 private final class AddUserDialog extends SystemUIDialog implements 556 DialogInterface.OnClickListener { 557 558 public AddUserDialog(Context context) { 559 super(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); 566 } 567 568 @Override 569 public void onClick(DialogInterface dialog, int which) { 570 if (which == BUTTON_NEGATIVE) { 571 cancel(); 572 } else { 573 dismiss(); 574 if (ActivityManager.isUserAMonkey()) { 575 return; 576 } 577 UserInfo user = mUserManager.createSecondaryUser( 578 mContext.getString(R.string.user_new_user_name), 0 /* flags */); 579 if (user == null) { 580 // Couldn't create user, most likely because there are too many, but we haven't 581 // been able to reload the list yet. 582 return; 583 } 584 int id = user.id; 585 Bitmap icon = UserIcons.convertToBitmap(UserIcons.getDefaultUserIcon( 586 id, /* light= */ false)); 587 mUserManager.setUserIcon(id, icon); 588 switchToUserId(id); 589 } 590 } 591 } 592 } 593