UserSwitcherController.java revision 092be7de024b8b1a050ad247a016febc795546af
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 com.android.systemui.BitmapHelper;
20import com.android.systemui.GuestResumeSessionReceiver;
21import com.android.systemui.R;
22import com.android.systemui.qs.QSTile;
23import com.android.systemui.qs.tiles.UserDetailView;
24import com.android.systemui.statusbar.phone.SystemUIDialog;
25
26import android.app.ActivityManager;
27import android.app.ActivityManagerNative;
28import android.app.Dialog;
29import android.content.BroadcastReceiver;
30import android.content.Context;
31import android.content.DialogInterface;
32import android.content.Intent;
33import android.content.IntentFilter;
34import android.content.pm.UserInfo;
35import android.database.ContentObserver;
36import android.graphics.Bitmap;
37import android.graphics.drawable.Drawable;
38import android.os.AsyncTask;
39import android.os.Handler;
40import android.os.RemoteException;
41import android.os.UserHandle;
42import android.os.UserManager;
43import android.provider.Settings;
44import android.util.Log;
45import android.util.SparseArray;
46import android.view.View;
47import android.view.ViewGroup;
48import android.view.WindowManagerGlobal;
49import android.widget.BaseAdapter;
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 int mLastNonGuestUser = UserHandle.USER_OWNER;
77    private boolean mSimpleUserSwitcher;
78    private boolean mAddUsersWhenLocked;
79
80    public UserSwitcherController(Context context, KeyguardMonitor keyguardMonitor) {
81        mContext = context;
82        mGuestResumeSessionReceiver.register(context);
83        mKeyguardMonitor = keyguardMonitor;
84        mUserManager = UserManager.get(context);
85        IntentFilter filter = new IntentFilter();
86        filter.addAction(Intent.ACTION_USER_ADDED);
87        filter.addAction(Intent.ACTION_USER_REMOVED);
88        filter.addAction(Intent.ACTION_USER_INFO_CHANGED);
89        filter.addAction(Intent.ACTION_USER_SWITCHED);
90        filter.addAction(Intent.ACTION_USER_STOPPING);
91        mContext.registerReceiverAsUser(mReceiver, UserHandle.OWNER, filter,
92                null /* permission */, null /* scheduler */);
93
94
95        mContext.getContentResolver().registerContentObserver(
96                Settings.Global.getUriFor(SIMPLE_USER_SWITCHER_GLOBAL_SETTING), true,
97                mSettingsObserver);
98        mContext.getContentResolver().registerContentObserver(
99                Settings.Global.getUriFor(Settings.Global.ADD_USERS_WHEN_LOCKED), true,
100                mSettingsObserver);
101        // Fetch initial values.
102        mSettingsObserver.onChange(false);
103
104        keyguardMonitor.addCallback(mCallback);
105
106        refreshUsers(UserHandle.USER_NULL);
107    }
108
109    /**
110     * Refreshes users from UserManager.
111     *
112     * The pictures are only loaded if they have not been loaded yet.
113     *
114     * @param forcePictureLoadForId forces the picture of the given user to be reloaded.
115     */
116    @SuppressWarnings("unchecked")
117    private void refreshUsers(int forcePictureLoadForId) {
118
119        SparseArray<Bitmap> bitmaps = new SparseArray<>(mUsers.size());
120        final int N = mUsers.size();
121        for (int i = 0; i < N; i++) {
122            UserRecord r = mUsers.get(i);
123            if (r == null || r.info == null
124                    || r.info.id == forcePictureLoadForId || r.picture == null) {
125                continue;
126            }
127            bitmaps.put(r.info.id, r.picture);
128        }
129
130        final boolean addUsersWhenLocked = mAddUsersWhenLocked;
131        new AsyncTask<SparseArray<Bitmap>, Void, ArrayList<UserRecord>>() {
132            @SuppressWarnings("unchecked")
133            @Override
134            protected ArrayList<UserRecord> doInBackground(SparseArray<Bitmap>... params) {
135                final SparseArray<Bitmap> bitmaps = params[0];
136                List<UserInfo> infos = mUserManager.getUsers(true);
137                if (infos == null) {
138                    return null;
139                }
140                ArrayList<UserRecord> records = new ArrayList<>(infos.size());
141                int currentId = ActivityManager.getCurrentUser();
142                UserRecord guestRecord = null;
143                int avatarSize = mContext.getResources()
144                        .getDimensionPixelSize(R.dimen.max_avatar_size);
145
146                for (UserInfo info : infos) {
147                    boolean isCurrent = currentId == info.id;
148                    if (info.isGuest()) {
149                        guestRecord = new UserRecord(info, null /* picture */,
150                                true /* isGuest */, isCurrent, false /* isAddUser */,
151                                false /* isRestricted */);
152                    } else if (info.supportsSwitchTo()) {
153                        Bitmap picture = bitmaps.get(info.id);
154                        if (picture == null) {
155                            picture = mUserManager.getUserIcon(info.id);
156                        }
157                        if (picture != null) {
158                            picture = BitmapHelper.createCircularClip(
159                                    picture, avatarSize, avatarSize);
160                        }
161                        int index = isCurrent ? 0 : records.size();
162                        records.add(index, new UserRecord(info, picture, false /* isGuest */,
163                                isCurrent, false /* isAddUser */, false /* isRestricted */));
164                    }
165                }
166
167                boolean ownerCanCreateUsers = !mUserManager.hasUserRestriction(
168                        UserManager.DISALLOW_ADD_USER, UserHandle.OWNER);
169                boolean currentUserCanCreateUsers =
170                        (currentId == UserHandle.USER_OWNER) && ownerCanCreateUsers;
171                boolean anyoneCanCreateUsers = ownerCanCreateUsers && addUsersWhenLocked;
172                boolean canCreateGuest = (currentUserCanCreateUsers || anyoneCanCreateUsers)
173                        && guestRecord == null;
174                boolean canCreateUser = (currentUserCanCreateUsers || anyoneCanCreateUsers)
175                        && mUserManager.canAddMoreUsers();
176                boolean createIsRestricted = !addUsersWhenLocked;
177
178                if (!mSimpleUserSwitcher) {
179                    if (guestRecord == null) {
180                        if (canCreateGuest) {
181                            records.add(new UserRecord(null /* info */, null /* picture */,
182                                    true /* isGuest */, false /* isCurrent */,
183                                    false /* isAddUser */, createIsRestricted));
184                        }
185                    } else {
186                        int index = guestRecord.isCurrent ? 0 : records.size();
187                        records.add(index, guestRecord);
188                    }
189                }
190
191                if (!mSimpleUserSwitcher && canCreateUser) {
192                    records.add(new UserRecord(null /* info */, null /* picture */,
193                            false /* isGuest */, false /* isCurrent */, true /* isAddUser */,
194                            createIsRestricted));
195                }
196
197                return records;
198            }
199
200            @Override
201            protected void onPostExecute(ArrayList<UserRecord> userRecords) {
202                if (userRecords != null) {
203                    mUsers = userRecords;
204                    notifyAdapters();
205                }
206            }
207        }.execute((SparseArray) bitmaps);
208    }
209
210    private void notifyAdapters() {
211        for (int i = mAdapters.size() - 1; i >= 0; i--) {
212            BaseUserAdapter adapter = mAdapters.get(i).get();
213            if (adapter != null) {
214                adapter.notifyDataSetChanged();
215            } else {
216                mAdapters.remove(i);
217            }
218        }
219    }
220
221    public boolean isSimpleUserSwitcher() {
222        return mSimpleUserSwitcher;
223    }
224
225    public void switchTo(UserRecord record) {
226        int id;
227        if (record.isGuest && record.info == null) {
228            // No guest user. Create one.
229            id = mUserManager.createGuest(mContext, mContext.getString(R.string.guest_nickname)).id;
230        } else if (record.isAddUser) {
231            id = mUserManager.createUser(
232                    mContext.getString(R.string.user_new_user_name), 0 /* flags */).id;
233        } else {
234            id = record.info.id;
235        }
236
237        if (ActivityManager.getCurrentUser() == id) {
238            if (record.isGuest) {
239                showExitGuestDialog(id);
240            }
241            return;
242        }
243
244        switchToUserId(id);
245    }
246
247    private void switchToUserId(int id) {
248        try {
249            ActivityManagerNative.getDefault().switchUser(id);
250        } catch (RemoteException e) {
251            Log.e(TAG, "Couldn't switch user.", e);
252        }
253    }
254
255    private void showExitGuestDialog(int id) {
256        if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
257            mExitGuestDialog.cancel();
258        }
259        mExitGuestDialog = new ExitGuestDialog(mContext, id);
260        mExitGuestDialog.show();
261    }
262
263    private void exitGuest(int id) {
264        int newId = UserHandle.USER_OWNER;
265        if (mLastNonGuestUser != UserHandle.USER_OWNER) {
266            UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
267            if (info != null && info.isEnabled() && info.supportsSwitchTo()) {
268                newId = info.id;
269            }
270        }
271        switchToUserId(newId);
272        mUserManager.removeUser(id);
273    }
274
275    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
276        @Override
277        public void onReceive(Context context, Intent intent) {
278            if (DEBUG) {
279                Log.v(TAG, "Broadcast: a=" + intent.getAction()
280                       + " user=" + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1));
281            }
282            if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
283                if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
284                    mExitGuestDialog.cancel();
285                    mExitGuestDialog = null;
286                }
287
288                final int currentId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
289                final int N = mUsers.size();
290                for (int i = 0; i < N; i++) {
291                    UserRecord record = mUsers.get(i);
292                    if (record.info == null) continue;
293                    boolean shouldBeCurrent = record.info.id == currentId;
294                    if (record.isCurrent != shouldBeCurrent) {
295                        mUsers.set(i, record.copyWithIsCurrent(shouldBeCurrent));
296                    }
297                    if (shouldBeCurrent && !record.isGuest) {
298                        mLastNonGuestUser = record.info.id;
299                    }
300                    if (currentId != UserHandle.USER_OWNER && record.isRestricted) {
301                        // Immediately remove restricted records in case the AsyncTask is too slow.
302                        mUsers.remove(i);
303                        i--;
304                    }
305                }
306                notifyAdapters();
307            }
308            int forcePictureLoadForId = UserHandle.USER_NULL;
309            if (Intent.ACTION_USER_INFO_CHANGED.equals(intent.getAction())) {
310                forcePictureLoadForId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
311                        UserHandle.USER_NULL);
312            }
313            refreshUsers(forcePictureLoadForId);
314        }
315    };
316
317    private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) {
318        public void onChange(boolean selfChange) {
319            mSimpleUserSwitcher = Settings.Global.getInt(mContext.getContentResolver(),
320                    SIMPLE_USER_SWITCHER_GLOBAL_SETTING, 0) != 0;
321            mAddUsersWhenLocked = Settings.Global.getInt(mContext.getContentResolver(),
322                    Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0;
323            refreshUsers(UserHandle.USER_NULL);
324        };
325    };
326
327    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
328        pw.println("UserSwitcherController state:");
329        pw.println("  mLastNonGuestUser=" + mLastNonGuestUser);
330        pw.print("  mUsers.size="); pw.println(mUsers.size());
331        for (int i = 0; i < mUsers.size(); i++) {
332            final UserRecord u = mUsers.get(i);
333            pw.print("    "); pw.println(u.toString());
334        }
335    }
336
337    public static abstract class BaseUserAdapter extends BaseAdapter {
338
339        final UserSwitcherController mController;
340
341        protected BaseUserAdapter(UserSwitcherController controller) {
342            mController = controller;
343            controller.mAdapters.add(new WeakReference<>(this));
344        }
345
346        @Override
347        public int getCount() {
348            boolean secureKeyguardShowing = mController.mKeyguardMonitor.isShowing()
349                    && mController.mKeyguardMonitor.isSecure();
350            if (!secureKeyguardShowing) {
351                return mController.mUsers.size();
352            }
353            // The lock screen is secure and showing. Filter out restricted records.
354            final int N = mController.mUsers.size();
355            int count = 0;
356            for (int i = 0; i < N; i++) {
357                if (mController.mUsers.get(i).isRestricted) {
358                    break;
359                } else {
360                    count++;
361                }
362            }
363            return count;
364        }
365
366        @Override
367        public UserRecord getItem(int position) {
368            return mController.mUsers.get(position);
369        }
370
371        @Override
372        public long getItemId(int position) {
373            return position;
374        }
375
376        public void switchTo(UserRecord record) {
377            mController.switchTo(record);
378        }
379
380        public String getName(Context context, UserRecord item) {
381            if (item.isGuest) {
382                if (item.isCurrent) {
383                    return context.getString(R.string.guest_exit_guest);
384                } else {
385                    return context.getString(
386                            item.info == null ? R.string.guest_new_guest : R.string.guest_nickname);
387                }
388            } else if (item.isAddUser) {
389                return context.getString(R.string.user_add_user);
390            } else {
391                return item.info.name;
392            }
393        }
394
395        public int getSwitchableUsers() {
396            int result = 0;
397            ArrayList<UserRecord> users = mController.mUsers;
398            int N = users.size();
399            for (int i = 0; i < N; i++) {
400                if (users.get(i).info != null) {
401                    result++;
402                }
403            }
404            return result;
405        }
406
407        public Drawable getDrawable(Context context, UserRecord item) {
408            if (item.isAddUser) {
409                return context.getDrawable(R.drawable.ic_add_circle_qs);
410            }
411            return context.getDrawable(R.drawable.ic_account_circle_qs);
412        }
413    }
414
415    public static final class UserRecord {
416        public final UserInfo info;
417        public final Bitmap picture;
418        public final boolean isGuest;
419        public final boolean isCurrent;
420        public final boolean isAddUser;
421        /** If true, the record is only visible to the owner and only when unlocked. */
422        public final boolean isRestricted;
423
424        public UserRecord(UserInfo info, Bitmap picture, boolean isGuest, boolean isCurrent,
425                boolean isAddUser, boolean isRestricted) {
426            this.info = info;
427            this.picture = picture;
428            this.isGuest = isGuest;
429            this.isCurrent = isCurrent;
430            this.isAddUser = isAddUser;
431            this.isRestricted = isRestricted;
432        }
433
434        public UserRecord copyWithIsCurrent(boolean _isCurrent) {
435            return new UserRecord(info, picture, isGuest, _isCurrent, isAddUser, isRestricted);
436        }
437
438        public String toString() {
439            StringBuilder sb = new StringBuilder();
440            sb.append("UserRecord(");
441            if (info != null) {
442                sb.append("name=\"" + info.name + "\" id=" + info.id);
443            } else {
444                if (isGuest) {
445                    sb.append("<add guest placeholder>");
446                } else if (isAddUser) {
447                    sb.append("<add user placeholder>");
448                }
449            }
450            if (isGuest) sb.append(" <isGuest>");
451            if (isAddUser) sb.append(" <isAddUser>");
452            if (isCurrent) sb.append(" <isCurrent>");
453            if (picture != null) sb.append(" <hasPicture>");
454            if (isRestricted) sb.append(" <isRestricted>");
455            sb.append(')');
456            return sb.toString();
457        }
458    }
459
460    public final QSTile.DetailAdapter userDetailAdapter = new QSTile.DetailAdapter() {
461        private final Intent USER_SETTINGS_INTENT = new Intent("android.settings.USER_SETTINGS");
462
463        @Override
464        public int getTitle() {
465            return R.string.quick_settings_user_title;
466        }
467
468        @Override
469        public View createDetailView(Context context, View convertView, ViewGroup parent) {
470            UserDetailView v;
471            if (!(convertView instanceof UserDetailView)) {
472                v = UserDetailView.inflate(context, parent, false);
473                v.createAndSetAdapter(UserSwitcherController.this);
474            } else {
475                v = (UserDetailView) convertView;
476            }
477            return v;
478        }
479
480        @Override
481        public Intent getSettingsIntent() {
482            return USER_SETTINGS_INTENT;
483        }
484
485        @Override
486        public Boolean getToggleState() {
487            return null;
488        }
489
490        @Override
491        public void setToggleState(boolean state) {
492        }
493    };
494
495    private final KeyguardMonitor.Callback mCallback = new KeyguardMonitor.Callback() {
496        @Override
497        public void onKeyguardChanged() {
498            notifyAdapters();
499        }
500    };
501
502    private final class ExitGuestDialog extends SystemUIDialog implements
503            DialogInterface.OnClickListener {
504
505        private final int mGuestId;
506
507        public ExitGuestDialog(Context context, int guestId) {
508            super(context);
509            setTitle(R.string.guest_exit_guest_dialog_title);
510            setMessage(context.getString(R.string.guest_exit_guest_dialog_message));
511            setButton(DialogInterface.BUTTON_NEGATIVE,
512                    context.getString(android.R.string.cancel), this);
513            setButton(DialogInterface.BUTTON_POSITIVE,
514                    context.getString(R.string.guest_exit_guest_dialog_remove), this);
515            setCanceledOnTouchOutside(false);
516            mGuestId = guestId;
517        }
518
519        @Override
520        public void onClick(DialogInterface dialog, int which) {
521            if (which == BUTTON_NEGATIVE) {
522                cancel();
523            } else {
524                dismiss();
525                exitGuest(mGuestId);
526            }
527        }
528    }
529}
530