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                         int index = isCurrent ? 0 : records.size();
    163                         records.add(index, new UserRecord(info, picture, false /* isGuest */,
    164                                 isCurrent, false /* isAddUser */, false /* isRestricted */));
    165                     }
    166                 }
    167 
    168                 boolean ownerCanCreateUsers = !mUserManager.hasUserRestriction(
    169                         UserManager.DISALLOW_ADD_USER, UserHandle.OWNER);
    170                 boolean currentUserCanCreateUsers =
    171                         (currentId == UserHandle.USER_OWNER) && ownerCanCreateUsers;
    172                 boolean anyoneCanCreateUsers = ownerCanCreateUsers && addUsersWhenLocked;
    173                 boolean canCreateGuest = (currentUserCanCreateUsers || anyoneCanCreateUsers)
    174                         && guestRecord == null;
    175                 boolean canCreateUser = (currentUserCanCreateUsers || anyoneCanCreateUsers)
    176                         && mUserManager.canAddMoreUsers();
    177                 boolean createIsRestricted = !addUsersWhenLocked;
    178 
    179                 if (!mSimpleUserSwitcher) {
    180                     if (guestRecord == null) {
    181                         if (canCreateGuest) {
    182                             records.add(new UserRecord(null /* info */, null /* picture */,
    183                                     true /* isGuest */, false /* isCurrent */,
    184                                     false /* isAddUser */, createIsRestricted));
    185                         }
    186                     } else {
    187                         int index = guestRecord.isCurrent ? 0 : records.size();
    188                         records.add(index, guestRecord);
    189                     }
    190                 }
    191 
    192                 if (!mSimpleUserSwitcher && canCreateUser) {
    193                     records.add(new UserRecord(null /* info */, null /* picture */,
    194                             false /* isGuest */, false /* isCurrent */, true /* isAddUser */,
    195                             createIsRestricted));
    196                 }
    197 
    198                 return records;
    199             }
    200 
    201             @Override
    202             protected void onPostExecute(ArrayList<UserRecord> userRecords) {
    203                 if (userRecords != null) {
    204                     mUsers = userRecords;
    205                     notifyAdapters();
    206                 }
    207             }
    208         }.execute((SparseArray) bitmaps);
    209     }
    210 
    211     private void notifyAdapters() {
    212         for (int i = mAdapters.size() - 1; i >= 0; i--) {
    213             BaseUserAdapter adapter = mAdapters.get(i).get();
    214             if (adapter != null) {
    215                 adapter.notifyDataSetChanged();
    216             } else {
    217                 mAdapters.remove(i);
    218             }
    219         }
    220     }
    221 
    222     public boolean isSimpleUserSwitcher() {
    223         return mSimpleUserSwitcher;
    224     }
    225 
    226     public void switchTo(UserRecord record) {
    227         int id;
    228         if (record.isGuest && record.info == null) {
    229             // No guest user. Create one.
    230             UserInfo guest = mUserManager.createGuest(
    231                     mContext, mContext.getString(R.string.guest_nickname));
    232             if (guest == null) {
    233                 // Couldn't create guest, most likely because there already exists one, we just
    234                 // haven't reloaded the user list yet.
    235                 return;
    236             }
    237             id = guest.id;
    238         } else if (record.isAddUser) {
    239             showAddUserDialog();
    240             return;
    241         } else {
    242             id = record.info.id;
    243         }
    244 
    245         if (ActivityManager.getCurrentUser() == id) {
    246             if (record.isGuest) {
    247                 showExitGuestDialog(id);
    248             }
    249             return;
    250         }
    251 
    252         switchToUserId(id);
    253     }
    254 
    255     private void switchToUserId(int id) {
    256         try {
    257             ActivityManagerNative.getDefault().switchUser(id);
    258         } catch (RemoteException e) {
    259             Log.e(TAG, "Couldn't switch user.", e);
    260         }
    261     }
    262 
    263     private void showExitGuestDialog(int id) {
    264         if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
    265             mExitGuestDialog.cancel();
    266         }
    267         mExitGuestDialog = new ExitGuestDialog(mContext, id);
    268         mExitGuestDialog.show();
    269     }
    270 
    271     private void showAddUserDialog() {
    272         if (mAddUserDialog != null && mAddUserDialog.isShowing()) {
    273             mAddUserDialog.cancel();
    274         }
    275         mAddUserDialog = new AddUserDialog(mContext);
    276         mAddUserDialog.show();
    277     }
    278 
    279     private void exitGuest(int id) {
    280         int newId = UserHandle.USER_OWNER;
    281         if (mLastNonGuestUser != UserHandle.USER_OWNER) {
    282             UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
    283             if (info != null && info.isEnabled() && info.supportsSwitchTo()) {
    284                 newId = info.id;
    285             }
    286         }
    287         switchToUserId(newId);
    288         mUserManager.removeUser(id);
    289     }
    290 
    291     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
    292         @Override
    293         public void onReceive(Context context, Intent intent) {
    294             if (DEBUG) {
    295                 Log.v(TAG, "Broadcast: a=" + intent.getAction()
    296                        + " user=" + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1));
    297             }
    298             if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
    299                 if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
    300                     mExitGuestDialog.cancel();
    301                     mExitGuestDialog = null;
    302                 }
    303 
    304                 final int currentId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
    305                 final int N = mUsers.size();
    306                 for (int i = 0; i < N; i++) {
    307                     UserRecord record = mUsers.get(i);
    308                     if (record.info == null) continue;
    309                     boolean shouldBeCurrent = record.info.id == currentId;
    310                     if (record.isCurrent != shouldBeCurrent) {
    311                         mUsers.set(i, record.copyWithIsCurrent(shouldBeCurrent));
    312                     }
    313                     if (shouldBeCurrent && !record.isGuest) {
    314                         mLastNonGuestUser = record.info.id;
    315                     }
    316                     if (currentId != UserHandle.USER_OWNER && record.isRestricted) {
    317                         // Immediately remove restricted records in case the AsyncTask is too slow.
    318                         mUsers.remove(i);
    319                         i--;
    320                     }
    321                 }
    322                 notifyAdapters();
    323             }
    324             int forcePictureLoadForId = UserHandle.USER_NULL;
    325             if (Intent.ACTION_USER_INFO_CHANGED.equals(intent.getAction())) {
    326                 forcePictureLoadForId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
    327                         UserHandle.USER_NULL);
    328             }
    329             refreshUsers(forcePictureLoadForId);
    330         }
    331     };
    332 
    333     private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) {
    334         public void onChange(boolean selfChange) {
    335             mSimpleUserSwitcher = Settings.Global.getInt(mContext.getContentResolver(),
    336                     SIMPLE_USER_SWITCHER_GLOBAL_SETTING, 0) != 0;
    337             mAddUsersWhenLocked = Settings.Global.getInt(mContext.getContentResolver(),
    338                     Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0;
    339             refreshUsers(UserHandle.USER_NULL);
    340         };
    341     };
    342 
    343     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    344         pw.println("UserSwitcherController state:");
    345         pw.println("  mLastNonGuestUser=" + mLastNonGuestUser);
    346         pw.print("  mUsers.size="); pw.println(mUsers.size());
    347         for (int i = 0; i < mUsers.size(); i++) {
    348             final UserRecord u = mUsers.get(i);
    349             pw.print("    "); pw.println(u.toString());
    350         }
    351     }
    352 
    353     public String getCurrentUserName(Context context) {
    354         if (mUsers.isEmpty()) return null;
    355         UserRecord item = mUsers.get(0);
    356         if (item == null || item.info == null) return null;
    357         if (item.isGuest) return context.getString(R.string.guest_nickname);
    358         return item.info.name;
    359     }
    360 
    361     public static abstract class BaseUserAdapter extends BaseAdapter {
    362 
    363         final UserSwitcherController mController;
    364 
    365         protected BaseUserAdapter(UserSwitcherController controller) {
    366             mController = controller;
    367             controller.mAdapters.add(new WeakReference<>(this));
    368         }
    369 
    370         @Override
    371         public int getCount() {
    372             boolean secureKeyguardShowing = mController.mKeyguardMonitor.isShowing()
    373                     && mController.mKeyguardMonitor.isSecure();
    374             if (!secureKeyguardShowing) {
    375                 return mController.mUsers.size();
    376             }
    377             // The lock screen is secure and showing. Filter out restricted records.
    378             final int N = mController.mUsers.size();
    379             int count = 0;
    380             for (int i = 0; i < N; i++) {
    381                 if (mController.mUsers.get(i).isRestricted) {
    382                     break;
    383                 } else {
    384                     count++;
    385                 }
    386             }
    387             return count;
    388         }
    389 
    390         @Override
    391         public UserRecord getItem(int position) {
    392             return mController.mUsers.get(position);
    393         }
    394 
    395         @Override
    396         public long getItemId(int position) {
    397             return position;
    398         }
    399 
    400         public void switchTo(UserRecord record) {
    401             mController.switchTo(record);
    402         }
    403 
    404         public String getName(Context context, UserRecord item) {
    405             if (item.isGuest) {
    406                 if (item.isCurrent) {
    407                     return context.getString(R.string.guest_exit_guest);
    408                 } else {
    409                     return context.getString(
    410                             item.info == null ? R.string.guest_new_guest : R.string.guest_nickname);
    411                 }
    412             } else if (item.isAddUser) {
    413                 return context.getString(R.string.user_add_user);
    414             } else {
    415                 return item.info.name;
    416             }
    417         }
    418 
    419         public int getSwitchableUsers() {
    420             int result = 0;
    421             ArrayList<UserRecord> users = mController.mUsers;
    422             int N = users.size();
    423             for (int i = 0; i < N; i++) {
    424                 if (users.get(i).info != null) {
    425                     result++;
    426                 }
    427             }
    428             return result;
    429         }
    430 
    431         public Drawable getDrawable(Context context, UserRecord item) {
    432             if (item.isAddUser) {
    433                 return context.getDrawable(R.drawable.ic_add_circle_qs);
    434             }
    435             return UserIcons.getDefaultUserIcon(item.isGuest ? UserHandle.USER_NULL : item.info.id,
    436                     /* light= */ true);
    437         }
    438     }
    439 
    440     public static final class UserRecord {
    441         public final UserInfo info;
    442         public final Bitmap picture;
    443         public final boolean isGuest;
    444         public final boolean isCurrent;
    445         public final boolean isAddUser;
    446         /** If true, the record is only visible to the owner and only when unlocked. */
    447         public final boolean isRestricted;
    448 
    449         public UserRecord(UserInfo info, Bitmap picture, boolean isGuest, boolean isCurrent,
    450                 boolean isAddUser, boolean isRestricted) {
    451             this.info = info;
    452             this.picture = picture;
    453             this.isGuest = isGuest;
    454             this.isCurrent = isCurrent;
    455             this.isAddUser = isAddUser;
    456             this.isRestricted = isRestricted;
    457         }
    458 
    459         public UserRecord copyWithIsCurrent(boolean _isCurrent) {
    460             return new UserRecord(info, picture, isGuest, _isCurrent, isAddUser, isRestricted);
    461         }
    462 
    463         public String toString() {
    464             StringBuilder sb = new StringBuilder();
    465             sb.append("UserRecord(");
    466             if (info != null) {
    467                 sb.append("name=\"" + info.name + "\" id=" + info.id);
    468             } else {
    469                 if (isGuest) {
    470                     sb.append("<add guest placeholder>");
    471                 } else if (isAddUser) {
    472                     sb.append("<add user placeholder>");
    473                 }
    474             }
    475             if (isGuest) sb.append(" <isGuest>");
    476             if (isAddUser) sb.append(" <isAddUser>");
    477             if (isCurrent) sb.append(" <isCurrent>");
    478             if (picture != null) sb.append(" <hasPicture>");
    479             if (isRestricted) sb.append(" <isRestricted>");
    480             sb.append(')');
    481             return sb.toString();
    482         }
    483     }
    484 
    485     public final QSTile.DetailAdapter userDetailAdapter = new QSTile.DetailAdapter() {
    486         private final Intent USER_SETTINGS_INTENT = new Intent("android.settings.USER_SETTINGS");
    487 
    488         @Override
    489         public int getTitle() {
    490             return R.string.quick_settings_user_title;
    491         }
    492 
    493         @Override
    494         public View createDetailView(Context context, View convertView, ViewGroup parent) {
    495             UserDetailView v;
    496             if (!(convertView instanceof UserDetailView)) {
    497                 v = UserDetailView.inflate(context, parent, false);
    498                 v.createAndSetAdapter(UserSwitcherController.this);
    499             } else {
    500                 v = (UserDetailView) convertView;
    501             }
    502             return v;
    503         }
    504 
    505         @Override
    506         public Intent getSettingsIntent() {
    507             return USER_SETTINGS_INTENT;
    508         }
    509 
    510         @Override
    511         public Boolean getToggleState() {
    512             return null;
    513         }
    514 
    515         @Override
    516         public void setToggleState(boolean state) {
    517         }
    518     };
    519 
    520     private final KeyguardMonitor.Callback mCallback = new KeyguardMonitor.Callback() {
    521         @Override
    522         public void onKeyguardChanged() {
    523             notifyAdapters();
    524         }
    525     };
    526 
    527     private final class ExitGuestDialog extends SystemUIDialog implements
    528             DialogInterface.OnClickListener {
    529 
    530         private final int mGuestId;
    531 
    532         public ExitGuestDialog(Context context, int guestId) {
    533             super(context);
    534             setTitle(R.string.guest_exit_guest_dialog_title);
    535             setMessage(context.getString(R.string.guest_exit_guest_dialog_message));
    536             setButton(DialogInterface.BUTTON_NEGATIVE,
    537                     context.getString(android.R.string.cancel), this);
    538             setButton(DialogInterface.BUTTON_POSITIVE,
    539                     context.getString(R.string.guest_exit_guest_dialog_remove), this);
    540             setCanceledOnTouchOutside(false);
    541             mGuestId = guestId;
    542         }
    543 
    544         @Override
    545         public void onClick(DialogInterface dialog, int which) {
    546             if (which == BUTTON_NEGATIVE) {
    547                 cancel();
    548             } else {
    549                 dismiss();
    550                 exitGuest(mGuestId);
    551             }
    552         }
    553     }
    554 
    555     private final class AddUserDialog extends SystemUIDialog implements
    556             DialogInterface.OnClickListener {
    557 
    558         public AddUserDialog(Context context) {
    559             super(context);
    560             setTitle(R.string.user_add_user_title);
    561             setMessage(context.getString(R.string.user_add_user_message_short));
    562             setButton(DialogInterface.BUTTON_NEGATIVE,
    563                     context.getString(android.R.string.cancel), this);
    564             setButton(DialogInterface.BUTTON_POSITIVE,
    565                     context.getString(android.R.string.ok), this);
    566         }
    567 
    568         @Override
    569         public void onClick(DialogInterface dialog, int which) {
    570             if (which == BUTTON_NEGATIVE) {
    571                 cancel();
    572             } else {
    573                 dismiss();
    574                 if (ActivityManager.isUserAMonkey()) {
    575                     return;
    576                 }
    577                 UserInfo user = mUserManager.createSecondaryUser(
    578                         mContext.getString(R.string.user_new_user_name), 0 /* flags */);
    579                 if (user == null) {
    580                     // Couldn't create user, most likely because there are too many, but we haven't
    581                     // been able to reload the list yet.
    582                     return;
    583                 }
    584                 int id = user.id;
    585                 Bitmap icon = UserIcons.convertToBitmap(UserIcons.getDefaultUserIcon(
    586                         id, /* light= */ false));
    587                 mUserManager.setUserIcon(id, icon);
    588                 switchToUserId(id);
    589             }
    590         }
    591     }
    592 }
    593