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 static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; 20 21 import android.R.attr; 22 import android.app.ActivityManager; 23 import android.app.Dialog; 24 import android.app.Notification; 25 import android.app.NotificationManager; 26 import android.app.PendingIntent; 27 import android.content.BroadcastReceiver; 28 import android.content.Context; 29 import android.content.DialogInterface; 30 import android.content.Intent; 31 import android.content.IntentFilter; 32 import android.content.pm.UserInfo; 33 import android.database.ContentObserver; 34 import android.graphics.Bitmap; 35 import android.graphics.PorterDuff.Mode; 36 import android.graphics.drawable.Drawable; 37 import android.os.AsyncTask; 38 import android.os.Handler; 39 import android.os.RemoteException; 40 import android.os.UserHandle; 41 import android.os.UserManager; 42 import android.provider.Settings; 43 import android.telephony.PhoneStateListener; 44 import android.telephony.TelephonyManager; 45 import android.util.Log; 46 import android.util.SparseArray; 47 import android.util.SparseBooleanArray; 48 import android.view.View; 49 import android.view.ViewGroup; 50 import android.widget.BaseAdapter; 51 52 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 53 import com.android.internal.annotations.VisibleForTesting; 54 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; 55 import com.android.internal.util.UserIcons; 56 import com.android.settingslib.RestrictedLockUtils; 57 import com.android.settingslib.Utils; 58 import com.android.systemui.Dependency; 59 import com.android.systemui.GuestResumeSessionReceiver; 60 import com.android.systemui.R; 61 import com.android.systemui.SystemUI; 62 import com.android.systemui.SystemUISecondaryUserService; 63 import com.android.systemui.plugins.qs.DetailAdapter; 64 import com.android.systemui.qs.tiles.UserDetailView; 65 import com.android.systemui.plugins.ActivityStarter; 66 import com.android.systemui.statusbar.phone.SystemUIDialog; 67 import com.android.systemui.util.NotificationChannels; 68 69 import java.io.FileDescriptor; 70 import java.io.PrintWriter; 71 import java.lang.ref.WeakReference; 72 import java.util.ArrayList; 73 import java.util.List; 74 75 /** 76 * Keeps a list of all users on the device for user switching. 77 */ 78 public class UserSwitcherController { 79 80 private static final String TAG = "UserSwitcherController"; 81 private static final boolean DEBUG = false; 82 private static final String SIMPLE_USER_SWITCHER_GLOBAL_SETTING = 83 "lockscreenSimpleUserSwitcher"; 84 private static final String ACTION_REMOVE_GUEST = "com.android.systemui.REMOVE_GUEST"; 85 private static final String ACTION_LOGOUT_USER = "com.android.systemui.LOGOUT_USER"; 86 private static final int PAUSE_REFRESH_USERS_TIMEOUT_MS = 3000; 87 88 private static final String TAG_REMOVE_GUEST = "remove_guest"; 89 private static final String TAG_LOGOUT_USER = "logout_user"; 90 91 private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF"; 92 93 protected final Context mContext; 94 protected final UserManager mUserManager; 95 private final ArrayList<WeakReference<BaseUserAdapter>> mAdapters = new ArrayList<>(); 96 private final GuestResumeSessionReceiver mGuestResumeSessionReceiver 97 = new GuestResumeSessionReceiver(); 98 private final KeyguardMonitor mKeyguardMonitor; 99 protected final Handler mHandler; 100 private final ActivityStarter mActivityStarter; 101 102 private ArrayList<UserRecord> mUsers = new ArrayList<>(); 103 private Dialog mExitGuestDialog; 104 private Dialog mAddUserDialog; 105 private int mLastNonGuestUser = UserHandle.USER_SYSTEM; 106 private boolean mResumeUserOnGuestLogout = true; 107 private boolean mSimpleUserSwitcher; 108 private boolean mAddUsersWhenLocked; 109 private boolean mPauseRefreshUsers; 110 private int mSecondaryUser = UserHandle.USER_NULL; 111 private Intent mSecondaryUserServiceIntent; 112 private SparseBooleanArray mForcePictureLoadForUserId = new SparseBooleanArray(2); 113 UserSwitcherController(Context context, KeyguardMonitor keyguardMonitor, Handler handler, ActivityStarter activityStarter)114 public UserSwitcherController(Context context, KeyguardMonitor keyguardMonitor, 115 Handler handler, ActivityStarter activityStarter) { 116 mContext = context; 117 mGuestResumeSessionReceiver.register(context); 118 mKeyguardMonitor = keyguardMonitor; 119 mHandler = handler; 120 mActivityStarter = activityStarter; 121 mUserManager = UserManager.get(context); 122 IntentFilter filter = new IntentFilter(); 123 filter.addAction(Intent.ACTION_USER_ADDED); 124 filter.addAction(Intent.ACTION_USER_REMOVED); 125 filter.addAction(Intent.ACTION_USER_INFO_CHANGED); 126 filter.addAction(Intent.ACTION_USER_SWITCHED); 127 filter.addAction(Intent.ACTION_USER_STOPPED); 128 filter.addAction(Intent.ACTION_USER_UNLOCKED); 129 mContext.registerReceiverAsUser(mReceiver, UserHandle.SYSTEM, filter, 130 null /* permission */, null /* scheduler */); 131 132 mSecondaryUserServiceIntent = new Intent(context, SystemUISecondaryUserService.class); 133 134 filter = new IntentFilter(); 135 filter.addAction(ACTION_REMOVE_GUEST); 136 filter.addAction(ACTION_LOGOUT_USER); 137 mContext.registerReceiverAsUser(mReceiver, UserHandle.SYSTEM, filter, 138 PERMISSION_SELF, null /* scheduler */); 139 140 mContext.getContentResolver().registerContentObserver( 141 Settings.Global.getUriFor(SIMPLE_USER_SWITCHER_GLOBAL_SETTING), true, 142 mSettingsObserver); 143 mContext.getContentResolver().registerContentObserver( 144 Settings.Global.getUriFor(Settings.Global.ADD_USERS_WHEN_LOCKED), true, 145 mSettingsObserver); 146 mContext.getContentResolver().registerContentObserver( 147 Settings.Global.getUriFor( 148 Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED), 149 true, mSettingsObserver); 150 // Fetch initial values. 151 mSettingsObserver.onChange(false); 152 153 keyguardMonitor.addCallback(mCallback); 154 listenForCallState(); 155 156 refreshUsers(UserHandle.USER_NULL); 157 } 158 159 /** 160 * Refreshes users from UserManager. 161 * 162 * The pictures are only loaded if they have not been loaded yet. 163 * 164 * @param forcePictureLoadForId forces the picture of the given user to be reloaded. 165 */ 166 @SuppressWarnings("unchecked") refreshUsers(int forcePictureLoadForId)167 private void refreshUsers(int forcePictureLoadForId) { 168 if (DEBUG) Log.d(TAG, "refreshUsers(forcePictureLoadForId=" + forcePictureLoadForId+")"); 169 if (forcePictureLoadForId != UserHandle.USER_NULL) { 170 mForcePictureLoadForUserId.put(forcePictureLoadForId, true); 171 } 172 173 if (mPauseRefreshUsers) { 174 return; 175 } 176 177 boolean forceAllUsers = mForcePictureLoadForUserId.get(UserHandle.USER_ALL); 178 SparseArray<Bitmap> bitmaps = new SparseArray<>(mUsers.size()); 179 final int N = mUsers.size(); 180 for (int i = 0; i < N; i++) { 181 UserRecord r = mUsers.get(i); 182 if (r == null || r.picture == null || r.info == null || forceAllUsers 183 || mForcePictureLoadForUserId.get(r.info.id)) { 184 continue; 185 } 186 bitmaps.put(r.info.id, r.picture); 187 } 188 mForcePictureLoadForUserId.clear(); 189 190 final boolean addUsersWhenLocked = mAddUsersWhenLocked; 191 new AsyncTask<SparseArray<Bitmap>, Void, ArrayList<UserRecord>>() { 192 @SuppressWarnings("unchecked") 193 @Override 194 protected ArrayList<UserRecord> doInBackground(SparseArray<Bitmap>... params) { 195 final SparseArray<Bitmap> bitmaps = params[0]; 196 List<UserInfo> infos = mUserManager.getUsers(true); 197 if (infos == null) { 198 return null; 199 } 200 ArrayList<UserRecord> records = new ArrayList<>(infos.size()); 201 int currentId = ActivityManager.getCurrentUser(); 202 boolean canSwitchUsers = mUserManager.canSwitchUsers(); 203 UserInfo currentUserInfo = null; 204 UserRecord guestRecord = null; 205 206 for (UserInfo info : infos) { 207 boolean isCurrent = currentId == info.id; 208 if (isCurrent) { 209 currentUserInfo = info; 210 } 211 boolean switchToEnabled = canSwitchUsers || isCurrent; 212 if (info.isEnabled()) { 213 if (info.isGuest()) { 214 // Tapping guest icon triggers remove and a user switch therefore 215 // the icon shouldn't be enabled even if the user is current 216 guestRecord = new UserRecord(info, null /* picture */, 217 true /* isGuest */, isCurrent, false /* isAddUser */, 218 false /* isRestricted */, canSwitchUsers); 219 } else if (info.supportsSwitchToByUser()) { 220 Bitmap picture = bitmaps.get(info.id); 221 if (picture == null) { 222 picture = mUserManager.getUserIcon(info.id); 223 224 if (picture != null) { 225 int avatarSize = mContext.getResources() 226 .getDimensionPixelSize(R.dimen.max_avatar_size); 227 picture = Bitmap.createScaledBitmap( 228 picture, avatarSize, avatarSize, true); 229 } 230 } 231 int index = isCurrent ? 0 : records.size(); 232 records.add(index, new UserRecord(info, picture, false /* isGuest */, 233 isCurrent, false /* isAddUser */, false /* isRestricted */, 234 switchToEnabled)); 235 } 236 } 237 } 238 239 boolean systemCanCreateUsers = !mUserManager.hasBaseUserRestriction( 240 UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM); 241 boolean currentUserCanCreateUsers = currentUserInfo != null 242 && (currentUserInfo.isAdmin() 243 || currentUserInfo.id == UserHandle.USER_SYSTEM) 244 && systemCanCreateUsers; 245 boolean anyoneCanCreateUsers = systemCanCreateUsers && addUsersWhenLocked; 246 boolean canCreateGuest = (currentUserCanCreateUsers || anyoneCanCreateUsers) 247 && guestRecord == null; 248 boolean canCreateUser = (currentUserCanCreateUsers || anyoneCanCreateUsers) 249 && mUserManager.canAddMoreUsers(); 250 boolean createIsRestricted = !addUsersWhenLocked; 251 252 if (!mSimpleUserSwitcher) { 253 if (guestRecord == null) { 254 if (canCreateGuest) { 255 guestRecord = new UserRecord(null /* info */, null /* picture */, 256 true /* isGuest */, false /* isCurrent */, 257 false /* isAddUser */, createIsRestricted, canSwitchUsers); 258 checkIfAddUserDisallowedByAdminOnly(guestRecord); 259 records.add(guestRecord); 260 } 261 } else { 262 int index = guestRecord.isCurrent ? 0 : records.size(); 263 records.add(index, guestRecord); 264 } 265 } 266 267 if (!mSimpleUserSwitcher && canCreateUser) { 268 UserRecord addUserRecord = new UserRecord(null /* info */, null /* picture */, 269 false /* isGuest */, false /* isCurrent */, true /* isAddUser */, 270 createIsRestricted, canSwitchUsers); 271 checkIfAddUserDisallowedByAdminOnly(addUserRecord); 272 records.add(addUserRecord); 273 } 274 275 return records; 276 } 277 278 @Override 279 protected void onPostExecute(ArrayList<UserRecord> userRecords) { 280 if (userRecords != null) { 281 mUsers = userRecords; 282 notifyAdapters(); 283 } 284 } 285 }.execute((SparseArray) bitmaps); 286 } 287 pauseRefreshUsers()288 private void pauseRefreshUsers() { 289 if (!mPauseRefreshUsers) { 290 mHandler.postDelayed(mUnpauseRefreshUsers, PAUSE_REFRESH_USERS_TIMEOUT_MS); 291 mPauseRefreshUsers = true; 292 } 293 } 294 notifyAdapters()295 private void notifyAdapters() { 296 for (int i = mAdapters.size() - 1; i >= 0; i--) { 297 BaseUserAdapter adapter = mAdapters.get(i).get(); 298 if (adapter != null) { 299 adapter.notifyDataSetChanged(); 300 } else { 301 mAdapters.remove(i); 302 } 303 } 304 } 305 isSimpleUserSwitcher()306 public boolean isSimpleUserSwitcher() { 307 return mSimpleUserSwitcher; 308 } 309 useFullscreenUserSwitcher()310 public boolean useFullscreenUserSwitcher() { 311 // Use adb to override: 312 // adb shell settings put system enable_fullscreen_user_switcher 0 # Turn it off. 313 // adb shell settings put system enable_fullscreen_user_switcher 1 # Turn it on. 314 // Restart SystemUI or adb reboot. 315 final int DEFAULT = -1; 316 final int overrideUseFullscreenUserSwitcher = 317 Settings.System.getInt(mContext.getContentResolver(), 318 "enable_fullscreen_user_switcher", DEFAULT); 319 if (overrideUseFullscreenUserSwitcher != DEFAULT) { 320 return overrideUseFullscreenUserSwitcher != 0; 321 } 322 // Otherwise default to the build setting. 323 return mContext.getResources().getBoolean(R.bool.config_enableFullscreenUserSwitcher); 324 } 325 setResumeUserOnGuestLogout(boolean resume)326 public void setResumeUserOnGuestLogout(boolean resume) { 327 mResumeUserOnGuestLogout = resume; 328 } 329 logoutCurrentUser()330 public void logoutCurrentUser() { 331 int currentUser = ActivityManager.getCurrentUser(); 332 if (currentUser != UserHandle.USER_SYSTEM) { 333 pauseRefreshUsers(); 334 ActivityManager.logoutCurrentUser(); 335 } 336 } 337 removeUserId(int userId)338 public void removeUserId(int userId) { 339 if (userId == UserHandle.USER_SYSTEM) { 340 Log.w(TAG, "User " + userId + " could not removed."); 341 return; 342 } 343 if (ActivityManager.getCurrentUser() == userId) { 344 switchToUserId(UserHandle.USER_SYSTEM); 345 } 346 if (mUserManager.removeUser(userId)) { 347 refreshUsers(UserHandle.USER_NULL); 348 } 349 } 350 switchTo(UserRecord record)351 public void switchTo(UserRecord record) { 352 int id; 353 if (record.isGuest && record.info == null) { 354 // No guest user. Create one. 355 UserInfo guest = mUserManager.createGuest( 356 mContext, mContext.getString(R.string.guest_nickname)); 357 if (guest == null) { 358 // Couldn't create guest, most likely because there already exists one, we just 359 // haven't reloaded the user list yet. 360 return; 361 } 362 id = guest.id; 363 } else if (record.isAddUser) { 364 showAddUserDialog(); 365 return; 366 } else { 367 id = record.info.id; 368 } 369 370 int currUserId = ActivityManager.getCurrentUser(); 371 if (currUserId == id) { 372 if (record.isGuest) { 373 showExitGuestDialog(id); 374 } 375 return; 376 } 377 378 if (UserManager.isGuestUserEphemeral()) { 379 // If switching from guest, we want to bring up the guest exit dialog instead of switching 380 UserInfo currUserInfo = mUserManager.getUserInfo(currUserId); 381 if (currUserInfo != null && currUserInfo.isGuest()) { 382 showExitGuestDialog(currUserId, record.resolveId()); 383 return; 384 } 385 } 386 387 switchToUserId(id); 388 } 389 switchTo(int userId)390 public void switchTo(int userId) { 391 final int count = mUsers.size(); 392 for (int i = 0; i < count; ++i) { 393 UserRecord record = mUsers.get(i); 394 if (record.info != null && record.info.id == userId) { 395 switchTo(record); 396 return; 397 } 398 } 399 400 Log.e(TAG, "Couldn't switch to user, id=" + userId); 401 } 402 getSwitchableUserCount()403 public int getSwitchableUserCount() { 404 int count = 0; 405 final int N = mUsers.size(); 406 for (int i = 0; i < N; ++i) { 407 UserRecord record = mUsers.get(i); 408 if (record.info != null && record.info.supportsSwitchTo()) { 409 count++; 410 } 411 } 412 return count; 413 } 414 switchToUserId(int id)415 protected void switchToUserId(int id) { 416 try { 417 pauseRefreshUsers(); 418 ActivityManager.getService().switchUser(id); 419 } catch (RemoteException e) { 420 Log.e(TAG, "Couldn't switch user.", e); 421 } 422 } 423 showExitGuestDialog(int id)424 private void showExitGuestDialog(int id) { 425 int newId = UserHandle.USER_SYSTEM; 426 if (mResumeUserOnGuestLogout && mLastNonGuestUser != UserHandle.USER_SYSTEM) { 427 UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser); 428 if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) { 429 newId = info.id; 430 } 431 } 432 showExitGuestDialog(id, newId); 433 } 434 showExitGuestDialog(int id, int targetId)435 protected void showExitGuestDialog(int id, int targetId) { 436 if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) { 437 mExitGuestDialog.cancel(); 438 } 439 mExitGuestDialog = new ExitGuestDialog(mContext, id, targetId); 440 mExitGuestDialog.show(); 441 } 442 showAddUserDialog()443 public void showAddUserDialog() { 444 if (mAddUserDialog != null && mAddUserDialog.isShowing()) { 445 mAddUserDialog.cancel(); 446 } 447 mAddUserDialog = new AddUserDialog(mContext); 448 mAddUserDialog.show(); 449 } 450 exitGuest(int id, int targetId)451 protected void exitGuest(int id, int targetId) { 452 switchToUserId(targetId); 453 mUserManager.removeUser(id); 454 } 455 listenForCallState()456 private void listenForCallState() { 457 TelephonyManager.from(mContext).listen(mPhoneStateListener, 458 PhoneStateListener.LISTEN_CALL_STATE); 459 } 460 461 private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() { 462 private int mCallState; 463 464 @Override 465 public void onCallStateChanged(int state, String incomingNumber) { 466 if (mCallState == state) return; 467 if (DEBUG) Log.v(TAG, "Call state changed: " + state); 468 mCallState = state; 469 int currentUserId = ActivityManager.getCurrentUser(); 470 UserInfo userInfo = mUserManager.getUserInfo(currentUserId); 471 if (userInfo != null && userInfo.isGuest()) { 472 showGuestNotification(currentUserId); 473 } 474 refreshUsers(UserHandle.USER_NULL); 475 } 476 }; 477 478 private BroadcastReceiver mReceiver = new BroadcastReceiver() { 479 @Override 480 public void onReceive(Context context, Intent intent) { 481 if (DEBUG) { 482 Log.v(TAG, "Broadcast: a=" + intent.getAction() 483 + " user=" + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)); 484 } 485 486 boolean unpauseRefreshUsers = false; 487 int forcePictureLoadForId = UserHandle.USER_NULL; 488 489 if (ACTION_REMOVE_GUEST.equals(intent.getAction())) { 490 int currentUser = ActivityManager.getCurrentUser(); 491 UserInfo userInfo = mUserManager.getUserInfo(currentUser); 492 if (userInfo != null && userInfo.isGuest()) { 493 showExitGuestDialog(currentUser); 494 } 495 return; 496 } else if (ACTION_LOGOUT_USER.equals(intent.getAction())) { 497 logoutCurrentUser(); 498 } else if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) { 499 if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) { 500 mExitGuestDialog.cancel(); 501 mExitGuestDialog = null; 502 } 503 504 final int currentId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); 505 final UserInfo userInfo = mUserManager.getUserInfo(currentId); 506 final int N = mUsers.size(); 507 for (int i = 0; i < N; i++) { 508 UserRecord record = mUsers.get(i); 509 if (record.info == null) continue; 510 boolean shouldBeCurrent = record.info.id == currentId; 511 if (record.isCurrent != shouldBeCurrent) { 512 mUsers.set(i, record.copyWithIsCurrent(shouldBeCurrent)); 513 } 514 if (shouldBeCurrent && !record.isGuest) { 515 mLastNonGuestUser = record.info.id; 516 } 517 if ((userInfo == null || !userInfo.isAdmin()) && record.isRestricted) { 518 // Immediately remove restricted records in case the AsyncTask is too slow. 519 mUsers.remove(i); 520 i--; 521 } 522 } 523 notifyAdapters(); 524 525 // Disconnect from the old secondary user's service 526 if (mSecondaryUser != UserHandle.USER_NULL) { 527 context.stopServiceAsUser(mSecondaryUserServiceIntent, 528 UserHandle.of(mSecondaryUser)); 529 mSecondaryUser = UserHandle.USER_NULL; 530 } 531 // Connect to the new secondary user's service (purely to ensure that a persistent 532 // SystemUI application is created for that user) 533 if (userInfo != null && userInfo.id != UserHandle.USER_SYSTEM) { 534 context.startServiceAsUser(mSecondaryUserServiceIntent, 535 UserHandle.of(userInfo.id)); 536 mSecondaryUser = userInfo.id; 537 } 538 539 if (UserManager.isSplitSystemUser() && userInfo != null && !userInfo.isGuest() 540 && userInfo.id != UserHandle.USER_SYSTEM) { 541 showLogoutNotification(currentId); 542 } 543 if (userInfo != null && userInfo.isGuest()) { 544 showGuestNotification(currentId); 545 } 546 unpauseRefreshUsers = true; 547 } else if (Intent.ACTION_USER_INFO_CHANGED.equals(intent.getAction())) { 548 forcePictureLoadForId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 549 UserHandle.USER_NULL); 550 } else if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { 551 // Unlocking the system user may require a refresh 552 int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); 553 if (userId != UserHandle.USER_SYSTEM) { 554 return; 555 } 556 } 557 refreshUsers(forcePictureLoadForId); 558 if (unpauseRefreshUsers) { 559 mUnpauseRefreshUsers.run(); 560 } 561 } 562 563 private void showLogoutNotification(int userId) { 564 PendingIntent logoutPI = PendingIntent.getBroadcastAsUser(mContext, 565 0, new Intent(ACTION_LOGOUT_USER), 0, UserHandle.SYSTEM); 566 Notification.Builder builder = 567 new Notification.Builder(mContext, NotificationChannels.GENERAL) 568 .setVisibility(Notification.VISIBILITY_SECRET) 569 .setSmallIcon(R.drawable.ic_person) 570 .setContentTitle(mContext.getString( 571 R.string.user_logout_notification_title)) 572 .setContentText(mContext.getString( 573 R.string.user_logout_notification_text)) 574 .setContentIntent(logoutPI) 575 .setOngoing(true) 576 .setShowWhen(false) 577 .addAction(R.drawable.ic_delete, 578 mContext.getString(R.string.user_logout_notification_action), 579 logoutPI); 580 SystemUI.overrideNotificationAppName(mContext, builder); 581 NotificationManager.from(mContext).notifyAsUser(TAG_LOGOUT_USER, 582 SystemMessage.NOTE_LOGOUT_USER, builder.build(), new UserHandle(userId)); 583 } 584 }; 585 showGuestNotification(int guestUserId)586 private void showGuestNotification(int guestUserId) { 587 boolean canSwitchUsers = mUserManager.canSwitchUsers(); 588 // Disable 'Remove guest' action if cannot switch users right now 589 PendingIntent removeGuestPI = canSwitchUsers ? PendingIntent.getBroadcastAsUser(mContext, 590 0, new Intent(ACTION_REMOVE_GUEST), 0, UserHandle.SYSTEM) : null; 591 592 Notification.Builder builder = 593 new Notification.Builder(mContext, NotificationChannels.GENERAL) 594 .setVisibility(Notification.VISIBILITY_SECRET) 595 .setSmallIcon(R.drawable.ic_person) 596 .setContentTitle(mContext.getString(R.string.guest_notification_title)) 597 .setContentText(mContext.getString(R.string.guest_notification_text)) 598 .setContentIntent(removeGuestPI) 599 .setShowWhen(false) 600 .addAction(R.drawable.ic_delete, 601 mContext.getString(R.string.guest_notification_remove_action), 602 removeGuestPI); 603 SystemUI.overrideNotificationAppName(mContext, builder); 604 NotificationManager.from(mContext).notifyAsUser(TAG_REMOVE_GUEST, 605 SystemMessage.NOTE_REMOVE_GUEST, builder.build(), new UserHandle(guestUserId)); 606 } 607 608 private final Runnable mUnpauseRefreshUsers = new Runnable() { 609 @Override 610 public void run() { 611 mHandler.removeCallbacks(this); 612 mPauseRefreshUsers = false; 613 refreshUsers(UserHandle.USER_NULL); 614 } 615 }; 616 617 private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) { 618 public void onChange(boolean selfChange) { 619 mSimpleUserSwitcher = Settings.Global.getInt(mContext.getContentResolver(), 620 SIMPLE_USER_SWITCHER_GLOBAL_SETTING, 0) != 0; 621 mAddUsersWhenLocked = Settings.Global.getInt(mContext.getContentResolver(), 622 Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0; 623 refreshUsers(UserHandle.USER_NULL); 624 }; 625 }; 626 dump(FileDescriptor fd, PrintWriter pw, String[] args)627 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 628 pw.println("UserSwitcherController state:"); 629 pw.println(" mLastNonGuestUser=" + mLastNonGuestUser); 630 pw.print(" mUsers.size="); pw.println(mUsers.size()); 631 for (int i = 0; i < mUsers.size(); i++) { 632 final UserRecord u = mUsers.get(i); 633 pw.print(" "); pw.println(u.toString()); 634 } 635 } 636 getCurrentUserName(Context context)637 public String getCurrentUserName(Context context) { 638 if (mUsers.isEmpty()) return null; 639 UserRecord item = mUsers.get(0); 640 if (item == null || item.info == null) return null; 641 if (item.isGuest) return context.getString(R.string.guest_nickname); 642 return item.info.name; 643 } 644 onDensityOrFontScaleChanged()645 public void onDensityOrFontScaleChanged() { 646 refreshUsers(UserHandle.USER_ALL); 647 } 648 649 @VisibleForTesting addAdapter(WeakReference<BaseUserAdapter> adapter)650 public void addAdapter(WeakReference<BaseUserAdapter> adapter) { 651 mAdapters.add(adapter); 652 } 653 654 @VisibleForTesting getUsers()655 public ArrayList<UserRecord> getUsers() { 656 return mUsers; 657 } 658 659 public static abstract class BaseUserAdapter extends BaseAdapter { 660 661 final UserSwitcherController mController; 662 private final KeyguardMonitor mKeyguardMonitor; 663 BaseUserAdapter(UserSwitcherController controller)664 protected BaseUserAdapter(UserSwitcherController controller) { 665 mController = controller; 666 mKeyguardMonitor = Dependency.get(KeyguardMonitor.class); 667 controller.addAdapter(new WeakReference<>(this)); 668 } 669 getUserCount()670 public int getUserCount() { 671 boolean secureKeyguardShowing = mKeyguardMonitor.isShowing() 672 && mKeyguardMonitor.isSecure() 673 && !mKeyguardMonitor.canSkipBouncer(); 674 if (!secureKeyguardShowing) { 675 return mController.getUsers().size(); 676 } 677 // The lock screen is secure and showing. Filter out restricted records. 678 final int N = mController.getUsers().size(); 679 int count = 0; 680 for (int i = 0; i < N; i++) { 681 if (mController.getUsers().get(i).isGuest) continue; 682 if (mController.getUsers().get(i).isRestricted) { 683 break; 684 } else { 685 count++; 686 } 687 } 688 return count; 689 } 690 691 @Override getCount()692 public int getCount() { 693 boolean secureKeyguardShowing = mKeyguardMonitor.isShowing() 694 && mKeyguardMonitor.isSecure() 695 && !mKeyguardMonitor.canSkipBouncer(); 696 if (!secureKeyguardShowing) { 697 return mController.getUsers().size(); 698 } 699 // The lock screen is secure and showing. Filter out restricted records. 700 final int N = mController.getUsers().size(); 701 int count = 0; 702 for (int i = 0; i < N; i++) { 703 if (mController.getUsers().get(i).isRestricted) { 704 break; 705 } else { 706 count++; 707 } 708 } 709 return count; 710 } 711 712 @Override getItem(int position)713 public UserRecord getItem(int position) { 714 return mController.getUsers().get(position); 715 } 716 717 @Override getItemId(int position)718 public long getItemId(int position) { 719 return position; 720 } 721 switchTo(UserRecord record)722 public void switchTo(UserRecord record) { 723 mController.switchTo(record); 724 } 725 getName(Context context, UserRecord item)726 public String getName(Context context, UserRecord item) { 727 if (item.isGuest) { 728 if (item.isCurrent) { 729 return context.getString(R.string.guest_exit_guest); 730 } else { 731 return context.getString( 732 item.info == null ? R.string.guest_new_guest : R.string.guest_nickname); 733 } 734 } else if (item.isAddUser) { 735 return context.getString(R.string.user_add_user); 736 } else { 737 return item.info.name; 738 } 739 } 740 getDrawable(Context context, UserRecord item)741 public Drawable getDrawable(Context context, UserRecord item) { 742 if (item.isAddUser) { 743 return context.getDrawable(R.drawable.ic_add_circle_qs); 744 } 745 Drawable icon = UserIcons.getDefaultUserIcon(item.resolveId(), /* light= */ false); 746 if (item.isGuest) { 747 icon.setColorFilter(Utils.getColorAttr(context, android.R.attr.colorForeground), 748 Mode.SRC_IN); 749 } 750 return icon; 751 } 752 refresh()753 public void refresh() { 754 mController.refreshUsers(UserHandle.USER_NULL); 755 } 756 } 757 checkIfAddUserDisallowedByAdminOnly(UserRecord record)758 private void checkIfAddUserDisallowedByAdminOnly(UserRecord record) { 759 EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(mContext, 760 UserManager.DISALLOW_ADD_USER, ActivityManager.getCurrentUser()); 761 if (admin != null && !RestrictedLockUtils.hasBaseUserRestriction(mContext, 762 UserManager.DISALLOW_ADD_USER, ActivityManager.getCurrentUser())) { 763 record.isDisabledByAdmin = true; 764 record.enforcedAdmin = admin; 765 } else { 766 record.isDisabledByAdmin = false; 767 record.enforcedAdmin = null; 768 } 769 } 770 startActivity(Intent intent)771 public void startActivity(Intent intent) { 772 mActivityStarter.startActivity(intent, true); 773 } 774 775 public static final class UserRecord { 776 public final UserInfo info; 777 public final Bitmap picture; 778 public final boolean isGuest; 779 public final boolean isCurrent; 780 public final boolean isAddUser; 781 /** If true, the record is only visible to the owner and only when unlocked. */ 782 public final boolean isRestricted; 783 public boolean isDisabledByAdmin; 784 public EnforcedAdmin enforcedAdmin; 785 public boolean isSwitchToEnabled; 786 UserRecord(UserInfo info, Bitmap picture, boolean isGuest, boolean isCurrent, boolean isAddUser, boolean isRestricted, boolean isSwitchToEnabled)787 public UserRecord(UserInfo info, Bitmap picture, boolean isGuest, boolean isCurrent, 788 boolean isAddUser, boolean isRestricted, boolean isSwitchToEnabled) { 789 this.info = info; 790 this.picture = picture; 791 this.isGuest = isGuest; 792 this.isCurrent = isCurrent; 793 this.isAddUser = isAddUser; 794 this.isRestricted = isRestricted; 795 this.isSwitchToEnabled = isSwitchToEnabled; 796 } 797 copyWithIsCurrent(boolean _isCurrent)798 public UserRecord copyWithIsCurrent(boolean _isCurrent) { 799 return new UserRecord(info, picture, isGuest, _isCurrent, isAddUser, isRestricted, 800 isSwitchToEnabled); 801 } 802 resolveId()803 public int resolveId() { 804 if (isGuest || info == null) { 805 return UserHandle.USER_NULL; 806 } 807 return info.id; 808 } 809 toString()810 public String toString() { 811 StringBuilder sb = new StringBuilder(); 812 sb.append("UserRecord("); 813 if (info != null) { 814 sb.append("name=\"").append(info.name).append("\" id=").append(info.id); 815 } else { 816 if (isGuest) { 817 sb.append("<add guest placeholder>"); 818 } else if (isAddUser) { 819 sb.append("<add user placeholder>"); 820 } 821 } 822 if (isGuest) sb.append(" <isGuest>"); 823 if (isAddUser) sb.append(" <isAddUser>"); 824 if (isCurrent) sb.append(" <isCurrent>"); 825 if (picture != null) sb.append(" <hasPicture>"); 826 if (isRestricted) sb.append(" <isRestricted>"); 827 if (isDisabledByAdmin) { 828 sb.append(" <isDisabledByAdmin>"); 829 sb.append(" enforcedAdmin=").append(enforcedAdmin); 830 } 831 if (isSwitchToEnabled) { 832 sb.append(" <isSwitchToEnabled>"); 833 } 834 sb.append(')'); 835 return sb.toString(); 836 } 837 } 838 839 public final DetailAdapter userDetailAdapter = new DetailAdapter() { 840 private final Intent USER_SETTINGS_INTENT = new Intent(Settings.ACTION_USER_SETTINGS); 841 842 @Override 843 public CharSequence getTitle() { 844 return mContext.getString(R.string.quick_settings_user_title); 845 } 846 847 @Override 848 public View createDetailView(Context context, View convertView, ViewGroup parent) { 849 UserDetailView v; 850 if (!(convertView instanceof UserDetailView)) { 851 v = UserDetailView.inflate(context, parent, false); 852 v.createAndSetAdapter(UserSwitcherController.this); 853 } else { 854 v = (UserDetailView) convertView; 855 } 856 v.refreshAdapter(); 857 return v; 858 } 859 860 @Override 861 public Intent getSettingsIntent() { 862 return USER_SETTINGS_INTENT; 863 } 864 865 @Override 866 public Boolean getToggleState() { 867 return null; 868 } 869 870 @Override 871 public void setToggleState(boolean state) { 872 } 873 874 @Override 875 public int getMetricsCategory() { 876 return MetricsEvent.QS_USERDETAIL; 877 } 878 }; 879 880 private final KeyguardMonitor.Callback mCallback = new KeyguardMonitor.Callback() { 881 @Override 882 public void onKeyguardShowingChanged() { 883 884 // When Keyguard is going away, we don't need to update our items immediately which 885 // helps making the transition faster. 886 if (!mKeyguardMonitor.isShowing()) { 887 mHandler.post(UserSwitcherController.this::notifyAdapters); 888 } else { 889 notifyAdapters(); 890 } 891 } 892 }; 893 894 private final class ExitGuestDialog extends SystemUIDialog implements 895 DialogInterface.OnClickListener { 896 897 private final int mGuestId; 898 private final int mTargetId; 899 ExitGuestDialog(Context context, int guestId, int targetId)900 public ExitGuestDialog(Context context, int guestId, int targetId) { 901 super(context); 902 setTitle(R.string.guest_exit_guest_dialog_title); 903 setMessage(context.getString(R.string.guest_exit_guest_dialog_message)); 904 setButton(DialogInterface.BUTTON_NEGATIVE, 905 context.getString(android.R.string.cancel), this); 906 setButton(DialogInterface.BUTTON_POSITIVE, 907 context.getString(R.string.guest_exit_guest_dialog_remove), this); 908 setCanceledOnTouchOutside(false); 909 mGuestId = guestId; 910 mTargetId = targetId; 911 } 912 913 @Override onClick(DialogInterface dialog, int which)914 public void onClick(DialogInterface dialog, int which) { 915 if (which == BUTTON_NEGATIVE) { 916 cancel(); 917 } else { 918 dismiss(); 919 exitGuest(mGuestId, mTargetId); 920 } 921 } 922 } 923 924 private final class AddUserDialog extends SystemUIDialog implements 925 DialogInterface.OnClickListener { 926 AddUserDialog(Context context)927 public AddUserDialog(Context context) { 928 super(context); 929 setTitle(R.string.user_add_user_title); 930 setMessage(context.getString(R.string.user_add_user_message_short)); 931 setButton(DialogInterface.BUTTON_NEGATIVE, 932 context.getString(android.R.string.cancel), this); 933 setButton(DialogInterface.BUTTON_POSITIVE, 934 context.getString(android.R.string.ok), this); 935 } 936 937 @Override onClick(DialogInterface dialog, int which)938 public void onClick(DialogInterface dialog, int which) { 939 if (which == BUTTON_NEGATIVE) { 940 cancel(); 941 } else { 942 dismiss(); 943 if (ActivityManager.isUserAMonkey()) { 944 return; 945 } 946 UserInfo user = mUserManager.createUser( 947 mContext.getString(R.string.user_new_user_name), 0 /* flags */); 948 if (user == null) { 949 // Couldn't create user, most likely because there are too many, but we haven't 950 // been able to reload the list yet. 951 return; 952 } 953 int id = user.id; 954 Bitmap icon = UserIcons.convertToBitmap(UserIcons.getDefaultUserIcon( 955 id, /* light= */ false)); 956 mUserManager.setUserIcon(id, icon); 957 switchToUserId(id); 958 } 959 } 960 } 961 } 962