UserSwitcherController.java revision c5db390f5b5c6c685267dd22cf3b2926f54fba35
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
17package com.android.systemui.statusbar.policy;
18
19import android.app.ActivityManager;
20import android.app.ActivityManagerNative;
21import android.app.Dialog;
22import android.content.BroadcastReceiver;
23import android.content.Context;
24import android.content.DialogInterface;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.content.pm.UserInfo;
28import android.database.ContentObserver;
29import android.graphics.Bitmap;
30import android.graphics.drawable.Drawable;
31import android.os.AsyncTask;
32import android.os.Handler;
33import android.os.RemoteException;
34import android.os.UserHandle;
35import android.os.UserManager;
36import android.provider.Settings;
37import android.util.Log;
38import android.util.SparseArray;
39import android.view.View;
40import android.view.ViewGroup;
41import android.widget.BaseAdapter;
42
43import com.android.internal.util.UserIcons;
44import com.android.systemui.BitmapHelper;
45import com.android.systemui.GuestResumeSessionReceiver;
46import com.android.systemui.R;
47import com.android.systemui.qs.QSTile;
48import com.android.systemui.qs.tiles.UserDetailView;
49import com.android.systemui.statusbar.phone.SystemUIDialog;
50
51import java.io.FileDescriptor;
52import java.io.PrintWriter;
53import java.lang.ref.WeakReference;
54import java.util.ArrayList;
55import java.util.List;
56
57/**
58 * Keeps a list of all users on the device for user switching.
59 */
60public 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 int getSwitchableUsers() {
421            int result = 0;
422            ArrayList<UserRecord> users = mController.mUsers;
423            int N = users.size();
424            for (int i = 0; i < N; i++) {
425                if (users.get(i).info != null) {
426                    result++;
427                }
428            }
429            return result;
430        }
431
432        public Drawable getDrawable(Context context, UserRecord item) {
433            if (item.isAddUser) {
434                return context.getDrawable(R.drawable.ic_add_circle_qs);
435            }
436            return UserIcons.getDefaultUserIcon(item.isGuest ? UserHandle.USER_NULL : item.info.id,
437                    /* light= */ true);
438        }
439    }
440
441    public static final class UserRecord {
442        public final UserInfo info;
443        public final Bitmap picture;
444        public final boolean isGuest;
445        public final boolean isCurrent;
446        public final boolean isAddUser;
447        /** If true, the record is only visible to the owner and only when unlocked. */
448        public final boolean isRestricted;
449
450        public UserRecord(UserInfo info, Bitmap picture, boolean isGuest, boolean isCurrent,
451                boolean isAddUser, boolean isRestricted) {
452            this.info = info;
453            this.picture = picture;
454            this.isGuest = isGuest;
455            this.isCurrent = isCurrent;
456            this.isAddUser = isAddUser;
457            this.isRestricted = isRestricted;
458        }
459
460        public UserRecord copyWithIsCurrent(boolean _isCurrent) {
461            return new UserRecord(info, picture, isGuest, _isCurrent, isAddUser, isRestricted);
462        }
463
464        public String toString() {
465            StringBuilder sb = new StringBuilder();
466            sb.append("UserRecord(");
467            if (info != null) {
468                sb.append("name=\"" + info.name + "\" id=" + info.id);
469            } else {
470                if (isGuest) {
471                    sb.append("<add guest placeholder>");
472                } else if (isAddUser) {
473                    sb.append("<add user placeholder>");
474                }
475            }
476            if (isGuest) sb.append(" <isGuest>");
477            if (isAddUser) sb.append(" <isAddUser>");
478            if (isCurrent) sb.append(" <isCurrent>");
479            if (picture != null) sb.append(" <hasPicture>");
480            if (isRestricted) sb.append(" <isRestricted>");
481            sb.append(')');
482            return sb.toString();
483        }
484    }
485
486    public final QSTile.DetailAdapter userDetailAdapter = new QSTile.DetailAdapter() {
487        private final Intent USER_SETTINGS_INTENT = new Intent("android.settings.USER_SETTINGS");
488
489        @Override
490        public int getTitle() {
491            return R.string.quick_settings_user_title;
492        }
493
494        @Override
495        public View createDetailView(Context context, View convertView, ViewGroup parent) {
496            UserDetailView v;
497            if (!(convertView instanceof UserDetailView)) {
498                v = UserDetailView.inflate(context, parent, false);
499                v.createAndSetAdapter(UserSwitcherController.this);
500            } else {
501                v = (UserDetailView) convertView;
502            }
503            return v;
504        }
505
506        @Override
507        public Intent getSettingsIntent() {
508            return USER_SETTINGS_INTENT;
509        }
510
511        @Override
512        public Boolean getToggleState() {
513            return null;
514        }
515
516        @Override
517        public void setToggleState(boolean state) {
518        }
519    };
520
521    private final KeyguardMonitor.Callback mCallback = new KeyguardMonitor.Callback() {
522        @Override
523        public void onKeyguardChanged() {
524            notifyAdapters();
525        }
526    };
527
528    private final class ExitGuestDialog extends SystemUIDialog implements
529            DialogInterface.OnClickListener {
530
531        private final int mGuestId;
532
533        public ExitGuestDialog(Context context, int guestId) {
534            super(context);
535            setTitle(R.string.guest_exit_guest_dialog_title);
536            setMessage(context.getString(R.string.guest_exit_guest_dialog_message));
537            setButton(DialogInterface.BUTTON_NEGATIVE,
538                    context.getString(android.R.string.cancel), this);
539            setButton(DialogInterface.BUTTON_POSITIVE,
540                    context.getString(R.string.guest_exit_guest_dialog_remove), this);
541            setCanceledOnTouchOutside(false);
542            mGuestId = guestId;
543        }
544
545        @Override
546        public void onClick(DialogInterface dialog, int which) {
547            if (which == BUTTON_NEGATIVE) {
548                cancel();
549            } else {
550                dismiss();
551                exitGuest(mGuestId);
552            }
553        }
554    }
555
556    private final class AddUserDialog extends SystemUIDialog implements
557            DialogInterface.OnClickListener {
558
559        public AddUserDialog(Context context) {
560            super(context);
561            setTitle(R.string.user_add_user_title);
562            setMessage(context.getString(R.string.user_add_user_message_short));
563            setButton(DialogInterface.BUTTON_NEGATIVE,
564                    context.getString(android.R.string.cancel), this);
565            setButton(DialogInterface.BUTTON_POSITIVE,
566                    context.getString(android.R.string.ok), this);
567        }
568
569        @Override
570        public void onClick(DialogInterface dialog, int which) {
571            if (which == BUTTON_NEGATIVE) {
572                cancel();
573            } else {
574                dismiss();
575                if (ActivityManager.isUserAMonkey()) {
576                    return;
577                }
578                UserInfo user = mUserManager.createSecondaryUser(
579                        mContext.getString(R.string.user_new_user_name), 0 /* flags */);
580                if (user == null) {
581                    // Couldn't create user, most likely because there are too many, but we haven't
582                    // been able to reload the list yet.
583                    return;
584                }
585                int id = user.id;
586                Bitmap icon = UserIcons.convertToBitmap(UserIcons.getDefaultUserIcon(
587                        id, /* light= */ false));
588                mUserManager.setUserIcon(id, icon);
589                switchToUserId(id);
590            }
591        }
592    }
593
594    public static boolean isUserSwitcherAvailable(UserManager um) {
595        return UserManager.supportsMultipleUsers() && um.isUserSwitcherEnabled();
596    }
597
598}
599