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