UserSwitcherController.java revision 092be7de024b8b1a050ad247a016febc795546af
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 17package com.android.systemui.statusbar.policy; 18 19import com.android.systemui.BitmapHelper; 20import com.android.systemui.GuestResumeSessionReceiver; 21import com.android.systemui.R; 22import com.android.systemui.qs.QSTile; 23import com.android.systemui.qs.tiles.UserDetailView; 24import com.android.systemui.statusbar.phone.SystemUIDialog; 25 26import android.app.ActivityManager; 27import android.app.ActivityManagerNative; 28import android.app.Dialog; 29import android.content.BroadcastReceiver; 30import android.content.Context; 31import android.content.DialogInterface; 32import android.content.Intent; 33import android.content.IntentFilter; 34import android.content.pm.UserInfo; 35import android.database.ContentObserver; 36import android.graphics.Bitmap; 37import android.graphics.drawable.Drawable; 38import android.os.AsyncTask; 39import android.os.Handler; 40import android.os.RemoteException; 41import android.os.UserHandle; 42import android.os.UserManager; 43import android.provider.Settings; 44import android.util.Log; 45import android.util.SparseArray; 46import android.view.View; 47import android.view.ViewGroup; 48import android.view.WindowManagerGlobal; 49import android.widget.BaseAdapter; 50 51import java.io.FileDescriptor; 52import java.io.PrintWriter; 53import java.lang.ref.WeakReference; 54import java.util.ArrayList; 55import java.util.List; 56 57/** 58 * Keeps a list of all users on the device for user switching. 59 */ 60public 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 int mLastNonGuestUser = UserHandle.USER_OWNER; 77 private boolean mSimpleUserSwitcher; 78 private boolean mAddUsersWhenLocked; 79 80 public UserSwitcherController(Context context, KeyguardMonitor keyguardMonitor) { 81 mContext = context; 82 mGuestResumeSessionReceiver.register(context); 83 mKeyguardMonitor = keyguardMonitor; 84 mUserManager = UserManager.get(context); 85 IntentFilter filter = new IntentFilter(); 86 filter.addAction(Intent.ACTION_USER_ADDED); 87 filter.addAction(Intent.ACTION_USER_REMOVED); 88 filter.addAction(Intent.ACTION_USER_INFO_CHANGED); 89 filter.addAction(Intent.ACTION_USER_SWITCHED); 90 filter.addAction(Intent.ACTION_USER_STOPPING); 91 mContext.registerReceiverAsUser(mReceiver, UserHandle.OWNER, filter, 92 null /* permission */, null /* scheduler */); 93 94 95 mContext.getContentResolver().registerContentObserver( 96 Settings.Global.getUriFor(SIMPLE_USER_SWITCHER_GLOBAL_SETTING), true, 97 mSettingsObserver); 98 mContext.getContentResolver().registerContentObserver( 99 Settings.Global.getUriFor(Settings.Global.ADD_USERS_WHEN_LOCKED), true, 100 mSettingsObserver); 101 // Fetch initial values. 102 mSettingsObserver.onChange(false); 103 104 keyguardMonitor.addCallback(mCallback); 105 106 refreshUsers(UserHandle.USER_NULL); 107 } 108 109 /** 110 * Refreshes users from UserManager. 111 * 112 * The pictures are only loaded if they have not been loaded yet. 113 * 114 * @param forcePictureLoadForId forces the picture of the given user to be reloaded. 115 */ 116 @SuppressWarnings("unchecked") 117 private void refreshUsers(int forcePictureLoadForId) { 118 119 SparseArray<Bitmap> bitmaps = new SparseArray<>(mUsers.size()); 120 final int N = mUsers.size(); 121 for (int i = 0; i < N; i++) { 122 UserRecord r = mUsers.get(i); 123 if (r == null || r.info == null 124 || r.info.id == forcePictureLoadForId || r.picture == null) { 125 continue; 126 } 127 bitmaps.put(r.info.id, r.picture); 128 } 129 130 final boolean addUsersWhenLocked = mAddUsersWhenLocked; 131 new AsyncTask<SparseArray<Bitmap>, Void, ArrayList<UserRecord>>() { 132 @SuppressWarnings("unchecked") 133 @Override 134 protected ArrayList<UserRecord> doInBackground(SparseArray<Bitmap>... params) { 135 final SparseArray<Bitmap> bitmaps = params[0]; 136 List<UserInfo> infos = mUserManager.getUsers(true); 137 if (infos == null) { 138 return null; 139 } 140 ArrayList<UserRecord> records = new ArrayList<>(infos.size()); 141 int currentId = ActivityManager.getCurrentUser(); 142 UserRecord guestRecord = null; 143 int avatarSize = mContext.getResources() 144 .getDimensionPixelSize(R.dimen.max_avatar_size); 145 146 for (UserInfo info : infos) { 147 boolean isCurrent = currentId == info.id; 148 if (info.isGuest()) { 149 guestRecord = new UserRecord(info, null /* picture */, 150 true /* isGuest */, isCurrent, false /* isAddUser */, 151 false /* isRestricted */); 152 } else if (info.supportsSwitchTo()) { 153 Bitmap picture = bitmaps.get(info.id); 154 if (picture == null) { 155 picture = mUserManager.getUserIcon(info.id); 156 } 157 if (picture != null) { 158 picture = BitmapHelper.createCircularClip( 159 picture, avatarSize, avatarSize); 160 } 161 int index = isCurrent ? 0 : records.size(); 162 records.add(index, new UserRecord(info, picture, false /* isGuest */, 163 isCurrent, false /* isAddUser */, false /* isRestricted */)); 164 } 165 } 166 167 boolean ownerCanCreateUsers = !mUserManager.hasUserRestriction( 168 UserManager.DISALLOW_ADD_USER, UserHandle.OWNER); 169 boolean currentUserCanCreateUsers = 170 (currentId == UserHandle.USER_OWNER) && ownerCanCreateUsers; 171 boolean anyoneCanCreateUsers = ownerCanCreateUsers && addUsersWhenLocked; 172 boolean canCreateGuest = (currentUserCanCreateUsers || anyoneCanCreateUsers) 173 && guestRecord == null; 174 boolean canCreateUser = (currentUserCanCreateUsers || anyoneCanCreateUsers) 175 && mUserManager.canAddMoreUsers(); 176 boolean createIsRestricted = !addUsersWhenLocked; 177 178 if (!mSimpleUserSwitcher) { 179 if (guestRecord == null) { 180 if (canCreateGuest) { 181 records.add(new UserRecord(null /* info */, null /* picture */, 182 true /* isGuest */, false /* isCurrent */, 183 false /* isAddUser */, createIsRestricted)); 184 } 185 } else { 186 int index = guestRecord.isCurrent ? 0 : records.size(); 187 records.add(index, guestRecord); 188 } 189 } 190 191 if (!mSimpleUserSwitcher && canCreateUser) { 192 records.add(new UserRecord(null /* info */, null /* picture */, 193 false /* isGuest */, false /* isCurrent */, true /* isAddUser */, 194 createIsRestricted)); 195 } 196 197 return records; 198 } 199 200 @Override 201 protected void onPostExecute(ArrayList<UserRecord> userRecords) { 202 if (userRecords != null) { 203 mUsers = userRecords; 204 notifyAdapters(); 205 } 206 } 207 }.execute((SparseArray) bitmaps); 208 } 209 210 private void notifyAdapters() { 211 for (int i = mAdapters.size() - 1; i >= 0; i--) { 212 BaseUserAdapter adapter = mAdapters.get(i).get(); 213 if (adapter != null) { 214 adapter.notifyDataSetChanged(); 215 } else { 216 mAdapters.remove(i); 217 } 218 } 219 } 220 221 public boolean isSimpleUserSwitcher() { 222 return mSimpleUserSwitcher; 223 } 224 225 public void switchTo(UserRecord record) { 226 int id; 227 if (record.isGuest && record.info == null) { 228 // No guest user. Create one. 229 id = mUserManager.createGuest(mContext, mContext.getString(R.string.guest_nickname)).id; 230 } else if (record.isAddUser) { 231 id = mUserManager.createUser( 232 mContext.getString(R.string.user_new_user_name), 0 /* flags */).id; 233 } else { 234 id = record.info.id; 235 } 236 237 if (ActivityManager.getCurrentUser() == id) { 238 if (record.isGuest) { 239 showExitGuestDialog(id); 240 } 241 return; 242 } 243 244 switchToUserId(id); 245 } 246 247 private void switchToUserId(int id) { 248 try { 249 ActivityManagerNative.getDefault().switchUser(id); 250 } catch (RemoteException e) { 251 Log.e(TAG, "Couldn't switch user.", e); 252 } 253 } 254 255 private void showExitGuestDialog(int id) { 256 if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) { 257 mExitGuestDialog.cancel(); 258 } 259 mExitGuestDialog = new ExitGuestDialog(mContext, id); 260 mExitGuestDialog.show(); 261 } 262 263 private void exitGuest(int id) { 264 int newId = UserHandle.USER_OWNER; 265 if (mLastNonGuestUser != UserHandle.USER_OWNER) { 266 UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser); 267 if (info != null && info.isEnabled() && info.supportsSwitchTo()) { 268 newId = info.id; 269 } 270 } 271 switchToUserId(newId); 272 mUserManager.removeUser(id); 273 } 274 275 private BroadcastReceiver mReceiver = new BroadcastReceiver() { 276 @Override 277 public void onReceive(Context context, Intent intent) { 278 if (DEBUG) { 279 Log.v(TAG, "Broadcast: a=" + intent.getAction() 280 + " user=" + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)); 281 } 282 if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) { 283 if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) { 284 mExitGuestDialog.cancel(); 285 mExitGuestDialog = null; 286 } 287 288 final int currentId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); 289 final int N = mUsers.size(); 290 for (int i = 0; i < N; i++) { 291 UserRecord record = mUsers.get(i); 292 if (record.info == null) continue; 293 boolean shouldBeCurrent = record.info.id == currentId; 294 if (record.isCurrent != shouldBeCurrent) { 295 mUsers.set(i, record.copyWithIsCurrent(shouldBeCurrent)); 296 } 297 if (shouldBeCurrent && !record.isGuest) { 298 mLastNonGuestUser = record.info.id; 299 } 300 if (currentId != UserHandle.USER_OWNER && record.isRestricted) { 301 // Immediately remove restricted records in case the AsyncTask is too slow. 302 mUsers.remove(i); 303 i--; 304 } 305 } 306 notifyAdapters(); 307 } 308 int forcePictureLoadForId = UserHandle.USER_NULL; 309 if (Intent.ACTION_USER_INFO_CHANGED.equals(intent.getAction())) { 310 forcePictureLoadForId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 311 UserHandle.USER_NULL); 312 } 313 refreshUsers(forcePictureLoadForId); 314 } 315 }; 316 317 private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) { 318 public void onChange(boolean selfChange) { 319 mSimpleUserSwitcher = Settings.Global.getInt(mContext.getContentResolver(), 320 SIMPLE_USER_SWITCHER_GLOBAL_SETTING, 0) != 0; 321 mAddUsersWhenLocked = Settings.Global.getInt(mContext.getContentResolver(), 322 Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0; 323 refreshUsers(UserHandle.USER_NULL); 324 }; 325 }; 326 327 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 328 pw.println("UserSwitcherController state:"); 329 pw.println(" mLastNonGuestUser=" + mLastNonGuestUser); 330 pw.print(" mUsers.size="); pw.println(mUsers.size()); 331 for (int i = 0; i < mUsers.size(); i++) { 332 final UserRecord u = mUsers.get(i); 333 pw.print(" "); pw.println(u.toString()); 334 } 335 } 336 337 public static abstract class BaseUserAdapter extends BaseAdapter { 338 339 final UserSwitcherController mController; 340 341 protected BaseUserAdapter(UserSwitcherController controller) { 342 mController = controller; 343 controller.mAdapters.add(new WeakReference<>(this)); 344 } 345 346 @Override 347 public int getCount() { 348 boolean secureKeyguardShowing = mController.mKeyguardMonitor.isShowing() 349 && mController.mKeyguardMonitor.isSecure(); 350 if (!secureKeyguardShowing) { 351 return mController.mUsers.size(); 352 } 353 // The lock screen is secure and showing. Filter out restricted records. 354 final int N = mController.mUsers.size(); 355 int count = 0; 356 for (int i = 0; i < N; i++) { 357 if (mController.mUsers.get(i).isRestricted) { 358 break; 359 } else { 360 count++; 361 } 362 } 363 return count; 364 } 365 366 @Override 367 public UserRecord getItem(int position) { 368 return mController.mUsers.get(position); 369 } 370 371 @Override 372 public long getItemId(int position) { 373 return position; 374 } 375 376 public void switchTo(UserRecord record) { 377 mController.switchTo(record); 378 } 379 380 public String getName(Context context, UserRecord item) { 381 if (item.isGuest) { 382 if (item.isCurrent) { 383 return context.getString(R.string.guest_exit_guest); 384 } else { 385 return context.getString( 386 item.info == null ? R.string.guest_new_guest : R.string.guest_nickname); 387 } 388 } else if (item.isAddUser) { 389 return context.getString(R.string.user_add_user); 390 } else { 391 return item.info.name; 392 } 393 } 394 395 public int getSwitchableUsers() { 396 int result = 0; 397 ArrayList<UserRecord> users = mController.mUsers; 398 int N = users.size(); 399 for (int i = 0; i < N; i++) { 400 if (users.get(i).info != null) { 401 result++; 402 } 403 } 404 return result; 405 } 406 407 public Drawable getDrawable(Context context, UserRecord item) { 408 if (item.isAddUser) { 409 return context.getDrawable(R.drawable.ic_add_circle_qs); 410 } 411 return context.getDrawable(R.drawable.ic_account_circle_qs); 412 } 413 } 414 415 public static final class UserRecord { 416 public final UserInfo info; 417 public final Bitmap picture; 418 public final boolean isGuest; 419 public final boolean isCurrent; 420 public final boolean isAddUser; 421 /** If true, the record is only visible to the owner and only when unlocked. */ 422 public final boolean isRestricted; 423 424 public UserRecord(UserInfo info, Bitmap picture, boolean isGuest, boolean isCurrent, 425 boolean isAddUser, boolean isRestricted) { 426 this.info = info; 427 this.picture = picture; 428 this.isGuest = isGuest; 429 this.isCurrent = isCurrent; 430 this.isAddUser = isAddUser; 431 this.isRestricted = isRestricted; 432 } 433 434 public UserRecord copyWithIsCurrent(boolean _isCurrent) { 435 return new UserRecord(info, picture, isGuest, _isCurrent, isAddUser, isRestricted); 436 } 437 438 public String toString() { 439 StringBuilder sb = new StringBuilder(); 440 sb.append("UserRecord("); 441 if (info != null) { 442 sb.append("name=\"" + info.name + "\" id=" + info.id); 443 } else { 444 if (isGuest) { 445 sb.append("<add guest placeholder>"); 446 } else if (isAddUser) { 447 sb.append("<add user placeholder>"); 448 } 449 } 450 if (isGuest) sb.append(" <isGuest>"); 451 if (isAddUser) sb.append(" <isAddUser>"); 452 if (isCurrent) sb.append(" <isCurrent>"); 453 if (picture != null) sb.append(" <hasPicture>"); 454 if (isRestricted) sb.append(" <isRestricted>"); 455 sb.append(')'); 456 return sb.toString(); 457 } 458 } 459 460 public final QSTile.DetailAdapter userDetailAdapter = new QSTile.DetailAdapter() { 461 private final Intent USER_SETTINGS_INTENT = new Intent("android.settings.USER_SETTINGS"); 462 463 @Override 464 public int getTitle() { 465 return R.string.quick_settings_user_title; 466 } 467 468 @Override 469 public View createDetailView(Context context, View convertView, ViewGroup parent) { 470 UserDetailView v; 471 if (!(convertView instanceof UserDetailView)) { 472 v = UserDetailView.inflate(context, parent, false); 473 v.createAndSetAdapter(UserSwitcherController.this); 474 } else { 475 v = (UserDetailView) convertView; 476 } 477 return v; 478 } 479 480 @Override 481 public Intent getSettingsIntent() { 482 return USER_SETTINGS_INTENT; 483 } 484 485 @Override 486 public Boolean getToggleState() { 487 return null; 488 } 489 490 @Override 491 public void setToggleState(boolean state) { 492 } 493 }; 494 495 private final KeyguardMonitor.Callback mCallback = new KeyguardMonitor.Callback() { 496 @Override 497 public void onKeyguardChanged() { 498 notifyAdapters(); 499 } 500 }; 501 502 private final class ExitGuestDialog extends SystemUIDialog implements 503 DialogInterface.OnClickListener { 504 505 private final int mGuestId; 506 507 public ExitGuestDialog(Context context, int guestId) { 508 super(context); 509 setTitle(R.string.guest_exit_guest_dialog_title); 510 setMessage(context.getString(R.string.guest_exit_guest_dialog_message)); 511 setButton(DialogInterface.BUTTON_NEGATIVE, 512 context.getString(android.R.string.cancel), this); 513 setButton(DialogInterface.BUTTON_POSITIVE, 514 context.getString(R.string.guest_exit_guest_dialog_remove), this); 515 setCanceledOnTouchOutside(false); 516 mGuestId = guestId; 517 } 518 519 @Override 520 public void onClick(DialogInterface dialog, int which) { 521 if (which == BUTTON_NEGATIVE) { 522 cancel(); 523 } else { 524 dismiss(); 525 exitGuest(mGuestId); 526 } 527 } 528 } 529} 530