blob: b010761aecc7a725c2ed68eed16c5efeba380cb0 [file] [log] [blame]
Adrian Roos00a0b1f2014-07-16 16:44:49 +02001/*
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
Adrian Roos00a0b1f2014-07-16 16:44:49 +020019import android.app.ActivityManager;
20import android.app.ActivityManagerNative;
Adrian Roos50052442014-07-17 23:35:18 +020021import android.app.Dialog;
Fyodor Kupolovf4d6ad22015-04-13 11:52:18 -070022import android.app.Notification;
23import android.app.NotificationManager;
24import android.app.PendingIntent;
Adrian Roos00a0b1f2014-07-16 16:44:49 +020025import android.content.BroadcastReceiver;
26import android.content.Context;
Adrian Roos50052442014-07-17 23:35:18 +020027import android.content.DialogInterface;
Adrian Roos00a0b1f2014-07-16 16:44:49 +020028import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.pm.UserInfo;
Jason Monk2daf62c2014-07-31 14:35:06 -040031import android.database.ContentObserver;
Adrian Roos00a0b1f2014-07-16 16:44:49 +020032import android.graphics.Bitmap;
Adrian Roosccdff622014-08-06 00:07:18 +020033import android.graphics.drawable.Drawable;
Adrian Roos00a0b1f2014-07-16 16:44:49 +020034import android.os.AsyncTask;
Xiyuan Xiacc3a74f62015-07-22 14:16:34 -070035import android.os.Build;
Jason Monk2daf62c2014-07-31 14:35:06 -040036import android.os.Handler;
Adrian Roos00a0b1f2014-07-16 16:44:49 +020037import android.os.RemoteException;
Adrian Roose9c7d432014-07-17 18:27:38 +020038import android.os.UserHandle;
Adrian Roos00a0b1f2014-07-16 16:44:49 +020039import android.os.UserManager;
Jason Monk2daf62c2014-07-31 14:35:06 -040040import android.provider.Settings;
Adrian Roos00a0b1f2014-07-16 16:44:49 +020041import android.util.Log;
Adrian Roose9c7d432014-07-17 18:27:38 +020042import android.util.SparseArray;
Adrian Roos88b11932015-07-22 14:59:48 -070043import android.util.SparseBooleanArray;
44import android.util.SparseIntArray;
Adrian Roos00a0b1f2014-07-16 16:44:49 +020045import android.view.View;
46import android.view.ViewGroup;
Adrian Roos00a0b1f2014-07-16 16:44:49 +020047import android.widget.BaseAdapter;
48
Chris Wren457a21c2015-05-06 17:50:34 -040049import com.android.internal.logging.MetricsLogger;
Alexandra Gherghina64d4dca2014-08-28 18:26:56 +010050import com.android.internal.util.UserIcons;
51import com.android.systemui.BitmapHelper;
52import com.android.systemui.GuestResumeSessionReceiver;
53import com.android.systemui.R;
54import com.android.systemui.qs.QSTile;
55import com.android.systemui.qs.tiles.UserDetailView;
56import com.android.systemui.statusbar.phone.SystemUIDialog;
57
Adrian Roos00a0b1f2014-07-16 16:44:49 +020058import java.io.FileDescriptor;
59import java.io.PrintWriter;
60import java.lang.ref.WeakReference;
61import java.util.ArrayList;
62import java.util.List;
63
64/**
65 * Keeps a list of all users on the device for user switching.
66 */
67public class UserSwitcherController {
68
69 private static final String TAG = "UserSwitcherController";
Adrian Roos50052442014-07-17 23:35:18 +020070 private static final boolean DEBUG = false;
Jason Monk2daf62c2014-07-31 14:35:06 -040071 private static final String SIMPLE_USER_SWITCHER_GLOBAL_SETTING =
72 "lockscreenSimpleUserSwitcher";
Fyodor Kupolovf4d6ad22015-04-13 11:52:18 -070073 private static final String ACTION_REMOVE_GUEST = "com.android.systemui.REMOVE_GUEST";
Xiaohui Chen860397f2015-07-22 15:03:48 -070074 private static final String ACTION_LOGOUT_USER = "com.android.systemui.LOGOUT_USER";
Adrian Roos88b11932015-07-22 14:59:48 -070075 private static final int PAUSE_REFRESH_USERS_TIMEOUT_MS = 3000;
Adrian Roos00a0b1f2014-07-16 16:44:49 +020076
Amith Yamasanid81f8272015-07-23 17:15:45 -070077 private static final int ID_REMOVE_GUEST = 1010;
Xiaohui Chen860397f2015-07-22 15:03:48 -070078 private static final int ID_LOGOUT_USER = 1011;
Amith Yamasanid81f8272015-07-23 17:15:45 -070079 private static final String TAG_REMOVE_GUEST = "remove_guest";
Xiaohui Chen860397f2015-07-22 15:03:48 -070080 private static final String TAG_LOGOUT_USER = "logout_user";
81
Amith Yamasanid81f8272015-07-23 17:15:45 -070082 private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
83
Adrian Roos00a0b1f2014-07-16 16:44:49 +020084 private final Context mContext;
85 private final UserManager mUserManager;
86 private final ArrayList<WeakReference<BaseUserAdapter>> mAdapters = new ArrayList<>();
Adrian Roos50052442014-07-17 23:35:18 +020087 private final GuestResumeSessionReceiver mGuestResumeSessionReceiver
88 = new GuestResumeSessionReceiver();
Adrian Roosccdff622014-08-06 00:07:18 +020089 private final KeyguardMonitor mKeyguardMonitor;
Adrian Roos88b11932015-07-22 14:59:48 -070090 private final Handler mHandler;
Adrian Roos00a0b1f2014-07-16 16:44:49 +020091
92 private ArrayList<UserRecord> mUsers = new ArrayList<>();
Adrian Roos50052442014-07-17 23:35:18 +020093 private Dialog mExitGuestDialog;
Adrian Roos0c6763a2014-09-08 19:11:00 +020094 private Dialog mAddUserDialog;
Xiaohui Chen7cb69df2015-07-13 16:01:01 -070095 private int mLastNonGuestUser = UserHandle.USER_SYSTEM;
Adrian Roosccdff622014-08-06 00:07:18 +020096 private boolean mSimpleUserSwitcher;
97 private boolean mAddUsersWhenLocked;
Adrian Roos88b11932015-07-22 14:59:48 -070098 private boolean mPauseRefreshUsers;
99 private SparseBooleanArray mForcePictureLoadForUserId = new SparseBooleanArray(2);
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200100
Adrian Roos88b11932015-07-22 14:59:48 -0700101 public UserSwitcherController(Context context, KeyguardMonitor keyguardMonitor,
102 Handler handler) {
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200103 mContext = context;
Adrian Roos50052442014-07-17 23:35:18 +0200104 mGuestResumeSessionReceiver.register(context);
Adrian Roosccdff622014-08-06 00:07:18 +0200105 mKeyguardMonitor = keyguardMonitor;
Adrian Roos88b11932015-07-22 14:59:48 -0700106 mHandler = handler;
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200107 mUserManager = UserManager.get(context);
108 IntentFilter filter = new IntentFilter();
109 filter.addAction(Intent.ACTION_USER_ADDED);
110 filter.addAction(Intent.ACTION_USER_REMOVED);
111 filter.addAction(Intent.ACTION_USER_INFO_CHANGED);
112 filter.addAction(Intent.ACTION_USER_SWITCHED);
Adrian Roose9c7d432014-07-17 18:27:38 +0200113 filter.addAction(Intent.ACTION_USER_STOPPING);
Xiaohui Chen7cb69df2015-07-13 16:01:01 -0700114 mContext.registerReceiverAsUser(mReceiver, UserHandle.SYSTEM, filter,
Adrian Roose9c7d432014-07-17 18:27:38 +0200115 null /* permission */, null /* scheduler */);
Jason Monk2daf62c2014-07-31 14:35:06 -0400116
Amith Yamasanid81f8272015-07-23 17:15:45 -0700117 filter = new IntentFilter();
118 filter.addAction(ACTION_REMOVE_GUEST);
Xiaohui Chen860397f2015-07-22 15:03:48 -0700119 filter.addAction(ACTION_LOGOUT_USER);
Amith Yamasani5891a342015-07-24 13:50:58 -0700120 mContext.registerReceiverAsUser(mReceiver, UserHandle.SYSTEM, filter,
Amith Yamasanid81f8272015-07-23 17:15:45 -0700121 PERMISSION_SELF, null /* scheduler */);
Adrian Roosccdff622014-08-06 00:07:18 +0200122
Jason Monk2daf62c2014-07-31 14:35:06 -0400123 mContext.getContentResolver().registerContentObserver(
124 Settings.Global.getUriFor(SIMPLE_USER_SWITCHER_GLOBAL_SETTING), true,
Adrian Roosccdff622014-08-06 00:07:18 +0200125 mSettingsObserver);
126 mContext.getContentResolver().registerContentObserver(
127 Settings.Global.getUriFor(Settings.Global.ADD_USERS_WHEN_LOCKED), true,
128 mSettingsObserver);
129 // Fetch initial values.
130 mSettingsObserver.onChange(false);
131
132 keyguardMonitor.addCallback(mCallback);
Jason Monk2daf62c2014-07-31 14:35:06 -0400133
Adrian Roose9c7d432014-07-17 18:27:38 +0200134 refreshUsers(UserHandle.USER_NULL);
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200135 }
136
Adrian Roose9c7d432014-07-17 18:27:38 +0200137 /**
138 * Refreshes users from UserManager.
139 *
140 * The pictures are only loaded if they have not been loaded yet.
141 *
142 * @param forcePictureLoadForId forces the picture of the given user to be reloaded.
143 */
Amith Yamasani95ab7842014-08-11 17:09:26 -0700144 @SuppressWarnings("unchecked")
Adrian Roose9c7d432014-07-17 18:27:38 +0200145 private void refreshUsers(int forcePictureLoadForId) {
Adrian Roos88b11932015-07-22 14:59:48 -0700146 if (DEBUG) Log.d(TAG, "refreshUsers(forcePictureLoadForId=" + forcePictureLoadForId+")");
147 if (forcePictureLoadForId != UserHandle.USER_NULL) {
148 mForcePictureLoadForUserId.put(forcePictureLoadForId, true);
149 }
150
151 if (mPauseRefreshUsers) {
152 return;
153 }
Adrian Roosc5db3902014-11-21 13:54:04 +0000154
155 SparseArray<Bitmap> bitmaps = new SparseArray<>(mUsers.size());
156 final int N = mUsers.size();
157 for (int i = 0; i < N; i++) {
158 UserRecord r = mUsers.get(i);
Adrian Roos88b11932015-07-22 14:59:48 -0700159 if (r == null || r.picture == null ||
160 r.info == null || mForcePictureLoadForUserId.get(r.info.id)) {
Adrian Roosc5db3902014-11-21 13:54:04 +0000161 continue;
Adrian Roose9c7d432014-07-17 18:27:38 +0200162 }
Adrian Roosc5db3902014-11-21 13:54:04 +0000163 bitmaps.put(r.info.id, r.picture);
Adrian Roose9c7d432014-07-17 18:27:38 +0200164 }
Adrian Roos88b11932015-07-22 14:59:48 -0700165 mForcePictureLoadForUserId.clear();
Adrian Roose9c7d432014-07-17 18:27:38 +0200166
Adrian Roosccdff622014-08-06 00:07:18 +0200167 final boolean addUsersWhenLocked = mAddUsersWhenLocked;
Adrian Roose9c7d432014-07-17 18:27:38 +0200168 new AsyncTask<SparseArray<Bitmap>, Void, ArrayList<UserRecord>>() {
169 @SuppressWarnings("unchecked")
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200170 @Override
Adrian Roose9c7d432014-07-17 18:27:38 +0200171 protected ArrayList<UserRecord> doInBackground(SparseArray<Bitmap>... params) {
172 final SparseArray<Bitmap> bitmaps = params[0];
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200173 List<UserInfo> infos = mUserManager.getUsers(true);
174 if (infos == null) {
175 return null;
176 }
177 ArrayList<UserRecord> records = new ArrayList<>(infos.size());
178 int currentId = ActivityManager.getCurrentUser();
Xiaohui Chen7cb69df2015-07-13 16:01:01 -0700179 UserInfo currentUserInfo = null;
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200180 UserRecord guestRecord = null;
Dan Sandler4d75c072014-07-17 16:01:28 -0400181 int avatarSize = mContext.getResources()
182 .getDimensionPixelSize(R.dimen.max_avatar_size);
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200183
184 for (UserInfo info : infos) {
185 boolean isCurrent = currentId == info.id;
Xiaohui Chen7cb69df2015-07-13 16:01:01 -0700186 if (isCurrent) {
187 currentUserInfo = info;
188 }
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200189 if (info.isGuest()) {
190 guestRecord = new UserRecord(info, null /* picture */,
Adrian Roosccdff622014-08-06 00:07:18 +0200191 true /* isGuest */, isCurrent, false /* isAddUser */,
192 false /* isRestricted */);
Xiyuan Xiacc3a74f62015-07-22 14:16:34 -0700193 } else if (info.isEnabled() && info.supportsSwitchToByUser()) {
Adrian Roosc5db3902014-11-21 13:54:04 +0000194 Bitmap picture = bitmaps.get(info.id);
195 if (picture == null) {
196 picture = mUserManager.getUserIcon(info.id);
Adrian Rooscba0faa2014-11-17 17:41:28 +0100197
Adrian Roosc5db3902014-11-21 13:54:04 +0000198 if (picture != null) {
Adrian Rooscba0faa2014-11-17 17:41:28 +0100199 picture = BitmapHelper.createCircularClip(
Adrian Roosc5db3902014-11-21 13:54:04 +0000200 picture, avatarSize, avatarSize);
Adrian Rooscba0faa2014-11-17 17:41:28 +0100201 }
Dan Sandler4d75c072014-07-17 16:01:28 -0400202 }
Adrian Roosbed6e3b2014-08-21 20:31:41 +0200203 int index = isCurrent ? 0 : records.size();
204 records.add(index, new UserRecord(info, picture, false /* isGuest */,
205 isCurrent, false /* isAddUser */, false /* isRestricted */));
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200206 }
207 }
208
Xiaohui Chen7cb69df2015-07-13 16:01:01 -0700209 boolean systemCanCreateUsers = !mUserManager.hasUserRestriction(
210 UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM);
211 boolean currentUserCanCreateUsers = currentUserInfo != null
212 && (currentUserInfo.isAdmin()
213 || currentUserInfo.id == UserHandle.USER_SYSTEM)
214 && systemCanCreateUsers;
215 boolean anyoneCanCreateUsers = systemCanCreateUsers && addUsersWhenLocked;
Adrian Roosccdff622014-08-06 00:07:18 +0200216 boolean canCreateGuest = (currentUserCanCreateUsers || anyoneCanCreateUsers)
217 && guestRecord == null;
218 boolean canCreateUser = (currentUserCanCreateUsers || anyoneCanCreateUsers)
Amith Yamasani95ab7842014-08-11 17:09:26 -0700219 && mUserManager.canAddMoreUsers();
Adrian Roosccdff622014-08-06 00:07:18 +0200220 boolean createIsRestricted = !addUsersWhenLocked;
221
Jason Monk2daf62c2014-07-31 14:35:06 -0400222 if (!mSimpleUserSwitcher) {
223 if (guestRecord == null) {
Adrian Roosccdff622014-08-06 00:07:18 +0200224 if (canCreateGuest) {
225 records.add(new UserRecord(null /* info */, null /* picture */,
226 true /* isGuest */, false /* isCurrent */,
227 false /* isAddUser */, createIsRestricted));
228 }
Jason Monk2daf62c2014-07-31 14:35:06 -0400229 } else {
Adrian Roosbed6e3b2014-08-21 20:31:41 +0200230 int index = guestRecord.isCurrent ? 0 : records.size();
231 records.add(index, guestRecord);
Jason Monk2daf62c2014-07-31 14:35:06 -0400232 }
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200233 }
234
Jason Monk092be7d2014-09-02 15:26:13 -0400235 if (!mSimpleUserSwitcher && canCreateUser) {
Adrian Roosccdff622014-08-06 00:07:18 +0200236 records.add(new UserRecord(null /* info */, null /* picture */,
237 false /* isGuest */, false /* isCurrent */, true /* isAddUser */,
238 createIsRestricted));
239 }
240
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200241 return records;
242 }
243
244 @Override
245 protected void onPostExecute(ArrayList<UserRecord> userRecords) {
246 if (userRecords != null) {
247 mUsers = userRecords;
248 notifyAdapters();
249 }
250 }
Adrian Roosccdff622014-08-06 00:07:18 +0200251 }.execute((SparseArray) bitmaps);
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200252 }
253
Adrian Roos88b11932015-07-22 14:59:48 -0700254 private void pauseRefreshUsers() {
255 if (!mPauseRefreshUsers) {
256 mHandler.postDelayed(mUnpauseRefreshUsers, PAUSE_REFRESH_USERS_TIMEOUT_MS);
257 mPauseRefreshUsers = true;
258 }
259 }
260
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200261 private void notifyAdapters() {
262 for (int i = mAdapters.size() - 1; i >= 0; i--) {
263 BaseUserAdapter adapter = mAdapters.get(i).get();
264 if (adapter != null) {
265 adapter.notifyDataSetChanged();
266 } else {
267 mAdapters.remove(i);
268 }
269 }
270 }
271
Jason Monk2daf62c2014-07-31 14:35:06 -0400272 public boolean isSimpleUserSwitcher() {
273 return mSimpleUserSwitcher;
274 }
275
Xiyuan Xiacc3a74f62015-07-22 14:16:34 -0700276 public boolean useFullscreenUserSwitcher() {
277 // Use adb to override:
278 // adb shell settings put system enable_fullscreen_user_switcher 0 # Turn it off.
279 // adb shell settings put system enable_fullscreen_user_switcher 1 # Turn it on.
280 // Restart SystemUI or adb reboot.
281 final int DEFAULT = -1;
282 final int overrideUseFullscreenUserSwitcher =
283 Settings.System.getInt(mContext.getContentResolver(),
284 "enable_fullscreen_user_switcher", DEFAULT);
285 if (overrideUseFullscreenUserSwitcher != DEFAULT) {
286 return overrideUseFullscreenUserSwitcher != 0;
287 }
288 // Otherwise default to the build setting.
Xiyuan Xia40f9dab2015-08-17 13:19:30 -0700289 return mContext.getResources().getBoolean(R.bool.config_enableFullscreenUserSwitcher);
Xiyuan Xiacc3a74f62015-07-22 14:16:34 -0700290 }
291
292 public void removeUserId(int userId) {
293 if (userId == UserHandle.USER_SYSTEM) {
294 Log.w(TAG, "User " + userId + " could not removed.");
295 return;
296 }
297 if (ActivityManager.getCurrentUser() == userId) {
298 switchToUserId(UserHandle.USER_SYSTEM);
299 }
300 if (mUserManager.removeUser(userId)) {
301 refreshUsers(UserHandle.USER_NULL);
302 }
303 }
304
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200305 public void switchTo(UserRecord record) {
306 int id;
307 if (record.isGuest && record.info == null) {
308 // No guest user. Create one.
Adrian Roosf99727c2014-09-17 16:15:07 +0200309 UserInfo guest = mUserManager.createGuest(
310 mContext, mContext.getString(R.string.guest_nickname));
311 if (guest == null) {
312 // Couldn't create guest, most likely because there already exists one, we just
313 // haven't reloaded the user list yet.
314 return;
315 }
316 id = guest.id;
Adrian Roosccdff622014-08-06 00:07:18 +0200317 } else if (record.isAddUser) {
Adrian Roos0c6763a2014-09-08 19:11:00 +0200318 showAddUserDialog();
319 return;
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200320 } else {
321 id = record.info.id;
322 }
323
324 if (ActivityManager.getCurrentUser() == id) {
Adrian Roose9c7d432014-07-17 18:27:38 +0200325 if (record.isGuest) {
Adrian Roos50052442014-07-17 23:35:18 +0200326 showExitGuestDialog(id);
Adrian Roose9c7d432014-07-17 18:27:38 +0200327 }
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200328 return;
329 }
330
Adrian Roose9c7d432014-07-17 18:27:38 +0200331 switchToUserId(id);
332 }
333
334 private void switchToUserId(int id) {
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200335 try {
Adrian Roos88b11932015-07-22 14:59:48 -0700336 pauseRefreshUsers();
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200337 ActivityManagerNative.getDefault().switchUser(id);
338 } catch (RemoteException e) {
339 Log.e(TAG, "Couldn't switch user.", e);
340 }
341 }
342
Xiaohui Chen860397f2015-07-22 15:03:48 -0700343 private void stopUserId(int id) {
344 try {
Fyodor Kupolov9cbfc9e2015-10-07 15:52:33 -0700345 ActivityManagerNative.getDefault().stopUser(id, /* force= */ false, null);
Xiaohui Chen860397f2015-07-22 15:03:48 -0700346 } catch (RemoteException e) {
347 Log.e(TAG, "Couldn't stop user.", e);
348 }
349 }
350
Adrian Roos50052442014-07-17 23:35:18 +0200351 private void showExitGuestDialog(int id) {
352 if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
353 mExitGuestDialog.cancel();
354 }
355 mExitGuestDialog = new ExitGuestDialog(mContext, id);
356 mExitGuestDialog.show();
357 }
358
Adrian Roos0c6763a2014-09-08 19:11:00 +0200359 private void showAddUserDialog() {
360 if (mAddUserDialog != null && mAddUserDialog.isShowing()) {
361 mAddUserDialog.cancel();
362 }
363 mAddUserDialog = new AddUserDialog(mContext);
364 mAddUserDialog.show();
365 }
366
Adrian Roose9c7d432014-07-17 18:27:38 +0200367 private void exitGuest(int id) {
Xiaohui Chen7cb69df2015-07-13 16:01:01 -0700368 int newId = UserHandle.USER_SYSTEM;
369 if (mLastNonGuestUser != UserHandle.USER_SYSTEM) {
Adrian Roos70441462014-07-22 16:03:12 +0200370 UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
Xiaohui Chen7cb69df2015-07-13 16:01:01 -0700371 if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) {
Adrian Roos70441462014-07-22 16:03:12 +0200372 newId = info.id;
373 }
374 }
375 switchToUserId(newId);
Adrian Roose9c7d432014-07-17 18:27:38 +0200376 mUserManager.removeUser(id);
377 }
378
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200379 private BroadcastReceiver mReceiver = new BroadcastReceiver() {
380 @Override
381 public void onReceive(Context context, Intent intent) {
Adrian Roos50052442014-07-17 23:35:18 +0200382 if (DEBUG) {
383 Log.v(TAG, "Broadcast: a=" + intent.getAction()
384 + " user=" + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1));
385 }
Adrian Roos88b11932015-07-22 14:59:48 -0700386
387 boolean unpauseRefreshUsers = false;
388 int forcePictureLoadForId = UserHandle.USER_NULL;
389
Fyodor Kupolovf4d6ad22015-04-13 11:52:18 -0700390 if (ACTION_REMOVE_GUEST.equals(intent.getAction())) {
391 int currentUser = ActivityManager.getCurrentUser();
392 UserInfo userInfo = mUserManager.getUserInfo(currentUser);
393 if (userInfo != null && userInfo.isGuest()) {
394 showExitGuestDialog(currentUser);
395 }
396 return;
Adrian Roos80996bb2015-07-24 16:38:58 -0700397 } else if (ACTION_LOGOUT_USER.equals(intent.getAction())) {
Xiaohui Chen860397f2015-07-22 15:03:48 -0700398 int currentUser = ActivityManager.getCurrentUser();
399 if (currentUser != UserHandle.USER_SYSTEM) {
400 switchToUserId(UserHandle.USER_SYSTEM);
401 stopUserId(currentUser);
402 }
Adrian Roos88b11932015-07-22 14:59:48 -0700403 } else if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
Adrian Roos50052442014-07-17 23:35:18 +0200404 if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
405 mExitGuestDialog.cancel();
406 mExitGuestDialog = null;
407 }
408
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200409 final int currentId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
Xiaohui Chen7cb69df2015-07-13 16:01:01 -0700410 final UserInfo userInfo = mUserManager.getUserInfo(currentId);
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200411 final int N = mUsers.size();
412 for (int i = 0; i < N; i++) {
413 UserRecord record = mUsers.get(i);
Adrian Roose9c7d432014-07-17 18:27:38 +0200414 if (record.info == null) continue;
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200415 boolean shouldBeCurrent = record.info.id == currentId;
416 if (record.isCurrent != shouldBeCurrent) {
417 mUsers.set(i, record.copyWithIsCurrent(shouldBeCurrent));
418 }
Adrian Roos70441462014-07-22 16:03:12 +0200419 if (shouldBeCurrent && !record.isGuest) {
420 mLastNonGuestUser = record.info.id;
421 }
Xiaohui Chen7cb69df2015-07-13 16:01:01 -0700422 if ((userInfo == null || !userInfo.isAdmin()) && record.isRestricted) {
Adrian Roosccdff622014-08-06 00:07:18 +0200423 // Immediately remove restricted records in case the AsyncTask is too slow.
424 mUsers.remove(i);
425 i--;
426 }
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200427 }
428 notifyAdapters();
Xiaohui Chen860397f2015-07-22 15:03:48 -0700429
430 if (UserManager.isSplitSystemUser() && userInfo != null && !userInfo.isGuest()
431 && userInfo.id != UserHandle.USER_SYSTEM) {
432 showLogoutNotification(currentId);
433 }
Fyodor Kupolovce161862015-08-18 12:41:52 -0700434 if (userInfo != null && userInfo.isGuest()) {
435 showGuestNotification(currentId);
436 }
Adrian Roos88b11932015-07-22 14:59:48 -0700437 unpauseRefreshUsers = true;
438 } else if (Intent.ACTION_USER_INFO_CHANGED.equals(intent.getAction())) {
Adrian Roose9c7d432014-07-17 18:27:38 +0200439 forcePictureLoadForId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
440 UserHandle.USER_NULL);
441 }
442 refreshUsers(forcePictureLoadForId);
Adrian Roos88b11932015-07-22 14:59:48 -0700443 if (unpauseRefreshUsers) {
444 mUnpauseRefreshUsers.run();
445 }
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200446 }
Fyodor Kupolovf4d6ad22015-04-13 11:52:18 -0700447
448 private void showGuestNotification(int guestUserId) {
449 PendingIntent removeGuestPI = PendingIntent.getBroadcastAsUser(mContext,
Xiaohui Chen7cb69df2015-07-13 16:01:01 -0700450 0, new Intent(ACTION_REMOVE_GUEST), 0, UserHandle.SYSTEM);
Fyodor Kupolovf4d6ad22015-04-13 11:52:18 -0700451 Notification notification = new Notification.Builder(mContext)
452 .setVisibility(Notification.VISIBILITY_SECRET)
453 .setPriority(Notification.PRIORITY_MIN)
454 .setSmallIcon(R.drawable.ic_person)
455 .setContentTitle(mContext.getString(R.string.guest_notification_title))
456 .setContentText(mContext.getString(R.string.guest_notification_text))
Fyodor Kupolov52408f42015-08-17 16:34:07 -0700457 .setContentIntent(removeGuestPI)
Fyodor Kupolovf4d6ad22015-04-13 11:52:18 -0700458 .setShowWhen(false)
459 .addAction(R.drawable.ic_delete,
460 mContext.getString(R.string.guest_notification_remove_action),
461 removeGuestPI)
462 .build();
Amith Yamasanid81f8272015-07-23 17:15:45 -0700463 NotificationManager.from(mContext).notifyAsUser(TAG_REMOVE_GUEST, ID_REMOVE_GUEST,
464 notification, new UserHandle(guestUserId));
Fyodor Kupolovf4d6ad22015-04-13 11:52:18 -0700465 }
Xiaohui Chen860397f2015-07-22 15:03:48 -0700466
467 private void showLogoutNotification(int userId) {
468 PendingIntent logoutPI = PendingIntent.getBroadcastAsUser(mContext,
469 0, new Intent(ACTION_LOGOUT_USER), 0, UserHandle.SYSTEM);
470 Notification notification = new Notification.Builder(mContext)
471 .setVisibility(Notification.VISIBILITY_SECRET)
472 .setPriority(Notification.PRIORITY_MIN)
473 .setSmallIcon(R.drawable.ic_person)
474 .setContentTitle(mContext.getString(R.string.user_logout_notification_title))
475 .setContentText(mContext.getString(R.string.user_logout_notification_text))
Fyodor Kupolov52408f42015-08-17 16:34:07 -0700476 .setContentIntent(logoutPI)
Fyodor Kupolovce161862015-08-18 12:41:52 -0700477 .setOngoing(true)
Xiaohui Chen860397f2015-07-22 15:03:48 -0700478 .setShowWhen(false)
479 .addAction(R.drawable.ic_delete,
480 mContext.getString(R.string.user_logout_notification_action),
481 logoutPI)
482 .build();
483 NotificationManager.from(mContext).notifyAsUser(TAG_LOGOUT_USER, ID_LOGOUT_USER,
484 notification, new UserHandle(userId));
485 }
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200486 };
487
Adrian Roos88b11932015-07-22 14:59:48 -0700488 private final Runnable mUnpauseRefreshUsers = new Runnable() {
489 @Override
490 public void run() {
491 mHandler.removeCallbacks(this);
492 mPauseRefreshUsers = false;
493 refreshUsers(UserHandle.USER_NULL);
494 }
495 };
496
Adrian Roosccdff622014-08-06 00:07:18 +0200497 private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) {
Jason Monk2daf62c2014-07-31 14:35:06 -0400498 public void onChange(boolean selfChange) {
499 mSimpleUserSwitcher = Settings.Global.getInt(mContext.getContentResolver(),
500 SIMPLE_USER_SWITCHER_GLOBAL_SETTING, 0) != 0;
Adrian Roosccdff622014-08-06 00:07:18 +0200501 mAddUsersWhenLocked = Settings.Global.getInt(mContext.getContentResolver(),
502 Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0;
Jason Monk2daf62c2014-07-31 14:35:06 -0400503 refreshUsers(UserHandle.USER_NULL);
504 };
505 };
506
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200507 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
508 pw.println("UserSwitcherController state:");
Adrian Roos70441462014-07-22 16:03:12 +0200509 pw.println(" mLastNonGuestUser=" + mLastNonGuestUser);
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200510 pw.print(" mUsers.size="); pw.println(mUsers.size());
511 for (int i = 0; i < mUsers.size(); i++) {
512 final UserRecord u = mUsers.get(i);
513 pw.print(" "); pw.println(u.toString());
514 }
515 }
516
Adrian Roos57cf5702014-09-03 15:56:30 +0200517 public String getCurrentUserName(Context context) {
518 if (mUsers.isEmpty()) return null;
519 UserRecord item = mUsers.get(0);
520 if (item == null || item.info == null) return null;
521 if (item.isGuest) return context.getString(R.string.guest_nickname);
522 return item.info.name;
523 }
524
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200525 public static abstract class BaseUserAdapter extends BaseAdapter {
526
527 final UserSwitcherController mController;
528
529 protected BaseUserAdapter(UserSwitcherController controller) {
530 mController = controller;
531 controller.mAdapters.add(new WeakReference<>(this));
532 }
533
534 @Override
535 public int getCount() {
Adrian Roosccdff622014-08-06 00:07:18 +0200536 boolean secureKeyguardShowing = mController.mKeyguardMonitor.isShowing()
Jason Monk8a3a9642015-06-05 11:01:30 -0400537 && mController.mKeyguardMonitor.isSecure()
Selim Cineke8bae622015-07-15 13:24:06 -0700538 && !mController.mKeyguardMonitor.canSkipBouncer();
Adrian Roosccdff622014-08-06 00:07:18 +0200539 if (!secureKeyguardShowing) {
540 return mController.mUsers.size();
541 }
542 // The lock screen is secure and showing. Filter out restricted records.
543 final int N = mController.mUsers.size();
544 int count = 0;
545 for (int i = 0; i < N; i++) {
546 if (mController.mUsers.get(i).isRestricted) {
547 break;
548 } else {
549 count++;
550 }
551 }
552 return count;
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200553 }
554
555 @Override
556 public UserRecord getItem(int position) {
557 return mController.mUsers.get(position);
558 }
559
560 @Override
561 public long getItemId(int position) {
Adrian Roose9c7d432014-07-17 18:27:38 +0200562 return position;
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200563 }
564
565 public void switchTo(UserRecord record) {
566 mController.switchTo(record);
567 }
Adrian Roose9c7d432014-07-17 18:27:38 +0200568
569 public String getName(Context context, UserRecord item) {
570 if (item.isGuest) {
571 if (item.isCurrent) {
572 return context.getString(R.string.guest_exit_guest);
573 } else {
574 return context.getString(
575 item.info == null ? R.string.guest_new_guest : R.string.guest_nickname);
576 }
Adrian Roosccdff622014-08-06 00:07:18 +0200577 } else if (item.isAddUser) {
578 return context.getString(R.string.user_add_user);
Adrian Roose9c7d432014-07-17 18:27:38 +0200579 } else {
580 return item.info.name;
581 }
582 }
Adrian Roos723632e2014-07-23 21:13:21 +0200583
Adrian Roosccdff622014-08-06 00:07:18 +0200584 public Drawable getDrawable(Context context, UserRecord item) {
585 if (item.isAddUser) {
586 return context.getDrawable(R.drawable.ic_add_circle_qs);
587 }
Alexandra Gherghina64d4dca2014-08-28 18:26:56 +0100588 return UserIcons.getDefaultUserIcon(item.isGuest ? UserHandle.USER_NULL : item.info.id,
589 /* light= */ true);
Adrian Roosccdff622014-08-06 00:07:18 +0200590 }
Adrian Roos844c92b2014-12-01 14:19:05 +0100591
592 public void refresh() {
593 mController.refreshUsers(UserHandle.USER_NULL);
594 }
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200595 }
596
597 public static final class UserRecord {
598 public final UserInfo info;
599 public final Bitmap picture;
600 public final boolean isGuest;
601 public final boolean isCurrent;
Adrian Roosccdff622014-08-06 00:07:18 +0200602 public final boolean isAddUser;
603 /** If true, the record is only visible to the owner and only when unlocked. */
604 public final boolean isRestricted;
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200605
Adrian Roosccdff622014-08-06 00:07:18 +0200606 public UserRecord(UserInfo info, Bitmap picture, boolean isGuest, boolean isCurrent,
607 boolean isAddUser, boolean isRestricted) {
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200608 this.info = info;
609 this.picture = picture;
610 this.isGuest = isGuest;
611 this.isCurrent = isCurrent;
Adrian Roosccdff622014-08-06 00:07:18 +0200612 this.isAddUser = isAddUser;
613 this.isRestricted = isRestricted;
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200614 }
615
616 public UserRecord copyWithIsCurrent(boolean _isCurrent) {
Adrian Roosccdff622014-08-06 00:07:18 +0200617 return new UserRecord(info, picture, isGuest, _isCurrent, isAddUser, isRestricted);
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200618 }
619
620 public String toString() {
621 StringBuilder sb = new StringBuilder();
622 sb.append("UserRecord(");
623 if (info != null) {
624 sb.append("name=\"" + info.name + "\" id=" + info.id);
625 } else {
Adrian Roosccdff622014-08-06 00:07:18 +0200626 if (isGuest) {
627 sb.append("<add guest placeholder>");
628 } else if (isAddUser) {
629 sb.append("<add user placeholder>");
630 }
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200631 }
Adrian Roosccdff622014-08-06 00:07:18 +0200632 if (isGuest) sb.append(" <isGuest>");
633 if (isAddUser) sb.append(" <isAddUser>");
634 if (isCurrent) sb.append(" <isCurrent>");
635 if (picture != null) sb.append(" <hasPicture>");
636 if (isRestricted) sb.append(" <isRestricted>");
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200637 sb.append(')');
638 return sb.toString();
639 }
640 }
641
642 public final QSTile.DetailAdapter userDetailAdapter = new QSTile.DetailAdapter() {
643 private final Intent USER_SETTINGS_INTENT = new Intent("android.settings.USER_SETTINGS");
644
645 @Override
646 public int getTitle() {
647 return R.string.quick_settings_user_title;
648 }
649
650 @Override
651 public View createDetailView(Context context, View convertView, ViewGroup parent) {
Adrian Roos19408922014-08-07 20:54:12 +0200652 UserDetailView v;
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200653 if (!(convertView instanceof UserDetailView)) {
Adrian Roos19408922014-08-07 20:54:12 +0200654 v = UserDetailView.inflate(context, parent, false);
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200655 v.createAndSetAdapter(UserSwitcherController.this);
Adrian Roos19408922014-08-07 20:54:12 +0200656 } else {
657 v = (UserDetailView) convertView;
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200658 }
Adrian Roos844c92b2014-12-01 14:19:05 +0100659 v.refreshAdapter();
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200660 return v;
661 }
662
663 @Override
664 public Intent getSettingsIntent() {
665 return USER_SETTINGS_INTENT;
666 }
667
668 @Override
669 public Boolean getToggleState() {
670 return null;
671 }
672
673 @Override
674 public void setToggleState(boolean state) {
675 }
Chris Wren457a21c2015-05-06 17:50:34 -0400676
677 @Override
678 public int getMetricsCategory() {
679 return MetricsLogger.QS_USERDETAIL;
680 }
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200681 };
Adrian Roos50052442014-07-17 23:35:18 +0200682
Adrian Roosccdff622014-08-06 00:07:18 +0200683 private final KeyguardMonitor.Callback mCallback = new KeyguardMonitor.Callback() {
684 @Override
685 public void onKeyguardChanged() {
686 notifyAdapters();
687 }
688 };
689
Adrian Roos50052442014-07-17 23:35:18 +0200690 private final class ExitGuestDialog extends SystemUIDialog implements
691 DialogInterface.OnClickListener {
692
693 private final int mGuestId;
694
695 public ExitGuestDialog(Context context, int guestId) {
696 super(context);
697 setTitle(R.string.guest_exit_guest_dialog_title);
698 setMessage(context.getString(R.string.guest_exit_guest_dialog_message));
699 setButton(DialogInterface.BUTTON_NEGATIVE,
Amith Yamasanie5b274a2014-08-18 07:57:56 -0700700 context.getString(android.R.string.cancel), this);
Adrian Roos50052442014-07-17 23:35:18 +0200701 setButton(DialogInterface.BUTTON_POSITIVE,
Amith Yamasanie5b274a2014-08-18 07:57:56 -0700702 context.getString(R.string.guest_exit_guest_dialog_remove), this);
Adrian Roos50052442014-07-17 23:35:18 +0200703 setCanceledOnTouchOutside(false);
704 mGuestId = guestId;
705 }
706
707 @Override
708 public void onClick(DialogInterface dialog, int which) {
709 if (which == BUTTON_NEGATIVE) {
710 cancel();
711 } else {
712 dismiss();
713 exitGuest(mGuestId);
714 }
715 }
716 }
Adrian Roos0c6763a2014-09-08 19:11:00 +0200717
718 private final class AddUserDialog extends SystemUIDialog implements
719 DialogInterface.OnClickListener {
720
721 public AddUserDialog(Context context) {
722 super(context);
723 setTitle(R.string.user_add_user_title);
724 setMessage(context.getString(R.string.user_add_user_message_short));
725 setButton(DialogInterface.BUTTON_NEGATIVE,
726 context.getString(android.R.string.cancel), this);
727 setButton(DialogInterface.BUTTON_POSITIVE,
728 context.getString(android.R.string.ok), this);
729 }
730
731 @Override
732 public void onClick(DialogInterface dialog, int which) {
733 if (which == BUTTON_NEGATIVE) {
734 cancel();
735 } else {
736 dismiss();
Guang Zhuccbeb612014-10-21 14:33:50 -0700737 if (ActivityManager.isUserAMonkey()) {
738 return;
739 }
Xiaohui Chencfe64c82015-07-16 14:30:50 -0700740 UserInfo user = mUserManager.createUser(
Adrian Roosf99727c2014-09-17 16:15:07 +0200741 mContext.getString(R.string.user_new_user_name), 0 /* flags */);
742 if (user == null) {
743 // Couldn't create user, most likely because there are too many, but we haven't
744 // been able to reload the list yet.
745 return;
746 }
747 int id = user.id;
Alexandra Gherghina64d4dca2014-08-28 18:26:56 +0100748 Bitmap icon = UserIcons.convertToBitmap(UserIcons.getDefaultUserIcon(
749 id, /* light= */ false));
750 mUserManager.setUserIcon(id, icon);
Adrian Roos0c6763a2014-09-08 19:11:00 +0200751 switchToUserId(id);
752 }
753 }
754 }
Adrian Roos00a0b1f2014-07-16 16:44:49 +0200755}