Home | History | Annotate | Download | only in policy
      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 
     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")
    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 
    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 
    223     public boolean isSimpleUserSwitcher() {
    224         return mSimpleUserSwitcher;
    225     }
    226 
    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 
    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 
    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 
    272     private void showAddUserDialog() {
    273         if (mAddUserDialog != null && mAddUserDialog.isShowing()) {
    274             mAddUserDialog.cancel();
    275         }
    276         mAddUserDialog = new AddUserDialog(mContext);
    277         mAddUserDialog.show();
    278     }
    279 
    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 
    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 
    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 
    366         protected BaseUserAdapter(UserSwitcherController controller) {
    367             mController = controller;
    368             controller.mAdapters.add(new WeakReference<>(this));
    369         }
    370 
    371         @Override
    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
    392         public UserRecord getItem(int position) {
    393             return mController.mUsers.get(position);
    394         }
    395 
    396         @Override
    397         public long getItemId(int position) {
    398             return position;
    399         }
    400 
    401         public void switchTo(UserRecord record) {
    402             mController.switchTo(record);
    403         }
    404 
    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 
    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 
    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 
    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 
    452         public UserRecord copyWithIsCurrent(boolean _isCurrent) {
    453             return new UserRecord(info, picture, isGuest, _isCurrent, isAddUser, isRestricted);
    454         }
    455 
    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 
    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
    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 
    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
    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 
    587     public static boolean isUserSwitcherAvailable(UserManager um) {
    588         return UserManager.supportsMultipleUsers() && um.isUserSwitcherEnabled();
    589     }
    590 
    591 }
    592