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