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.app.Notification;
23import android.app.NotificationManager;
24import android.app.PendingIntent;
25import android.content.BroadcastReceiver;
26import android.content.Context;
27import android.content.DialogInterface;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.pm.UserInfo;
31import android.database.ContentObserver;
32import android.graphics.Bitmap;
33import android.graphics.drawable.Drawable;
34import android.os.AsyncTask;
35import android.os.Handler;
36import android.os.RemoteException;
37import android.os.UserHandle;
38import android.os.UserManager;
39import android.provider.Settings;
40import android.telephony.PhoneStateListener;
41import android.telephony.TelephonyManager;
42import android.util.Log;
43import android.util.SparseArray;
44import android.util.SparseBooleanArray;
45import android.view.View;
46import android.view.ViewGroup;
47import android.widget.BaseAdapter;
48
49import com.android.internal.logging.MetricsProto.MetricsEvent;
50import com.android.internal.messages.SystemMessageProto.SystemMessage;
51import com.android.internal.util.UserIcons;
52import com.android.settingslib.RestrictedLockUtils;
53import com.android.systemui.GuestResumeSessionReceiver;
54import com.android.systemui.R;
55import com.android.systemui.SystemUI;
56import com.android.systemui.SystemUISecondaryUserService;
57import com.android.systemui.qs.QSTile;
58import com.android.systemui.qs.tiles.UserDetailView;
59import com.android.systemui.statusbar.phone.ActivityStarter;
60import com.android.systemui.statusbar.phone.SystemUIDialog;
61
62import java.io.FileDescriptor;
63import java.io.PrintWriter;
64import java.lang.ref.WeakReference;
65import java.util.ArrayList;
66import java.util.List;
67
68import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
69
70/**
71 * Keeps a list of all users on the device for user switching.
72 */
73public class UserSwitcherController {
74
75    private static final String TAG = "UserSwitcherController";
76    private static final boolean DEBUG = false;
77    private static final String SIMPLE_USER_SWITCHER_GLOBAL_SETTING =
78            "lockscreenSimpleUserSwitcher";
79    private static final String ACTION_REMOVE_GUEST = "com.android.systemui.REMOVE_GUEST";
80    private static final String ACTION_LOGOUT_USER = "com.android.systemui.LOGOUT_USER";
81    private static final int PAUSE_REFRESH_USERS_TIMEOUT_MS = 3000;
82
83    private static final String TAG_REMOVE_GUEST = "remove_guest";
84    private static final String TAG_LOGOUT_USER = "logout_user";
85
86    private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
87
88    private final Context mContext;
89    private final UserManager mUserManager;
90    private final ArrayList<WeakReference<BaseUserAdapter>> mAdapters = new ArrayList<>();
91    private final GuestResumeSessionReceiver mGuestResumeSessionReceiver
92            = new GuestResumeSessionReceiver();
93    private final KeyguardMonitor mKeyguardMonitor;
94    private final Handler mHandler;
95    private final ActivityStarter mActivityStarter;
96
97    private ArrayList<UserRecord> mUsers = new ArrayList<>();
98    private Dialog mExitGuestDialog;
99    private Dialog mAddUserDialog;
100    private int mLastNonGuestUser = UserHandle.USER_SYSTEM;
101    private boolean mSimpleUserSwitcher;
102    private boolean mAddUsersWhenLocked;
103    private boolean mPauseRefreshUsers;
104    private int mSecondaryUser = UserHandle.USER_NULL;
105    private Intent mSecondaryUserServiceIntent;
106    private SparseBooleanArray mForcePictureLoadForUserId = new SparseBooleanArray(2);
107
108    public UserSwitcherController(Context context, KeyguardMonitor keyguardMonitor,
109            Handler handler, ActivityStarter activityStarter) {
110        mContext = context;
111        mGuestResumeSessionReceiver.register(context);
112        mKeyguardMonitor = keyguardMonitor;
113        mHandler = handler;
114        mActivityStarter = activityStarter;
115        mUserManager = UserManager.get(context);
116        IntentFilter filter = new IntentFilter();
117        filter.addAction(Intent.ACTION_USER_ADDED);
118        filter.addAction(Intent.ACTION_USER_REMOVED);
119        filter.addAction(Intent.ACTION_USER_INFO_CHANGED);
120        filter.addAction(Intent.ACTION_USER_SWITCHED);
121        filter.addAction(Intent.ACTION_USER_STOPPED);
122        filter.addAction(Intent.ACTION_USER_UNLOCKED);
123        mContext.registerReceiverAsUser(mReceiver, UserHandle.SYSTEM, filter,
124                null /* permission */, null /* scheduler */);
125
126        mSecondaryUserServiceIntent = new Intent(context, SystemUISecondaryUserService.class);
127
128        filter = new IntentFilter();
129        filter.addAction(ACTION_REMOVE_GUEST);
130        filter.addAction(ACTION_LOGOUT_USER);
131        mContext.registerReceiverAsUser(mReceiver, UserHandle.SYSTEM, filter,
132                PERMISSION_SELF, null /* scheduler */);
133
134        mContext.getContentResolver().registerContentObserver(
135                Settings.Global.getUriFor(SIMPLE_USER_SWITCHER_GLOBAL_SETTING), true,
136                mSettingsObserver);
137        mContext.getContentResolver().registerContentObserver(
138                Settings.Global.getUriFor(Settings.Global.ADD_USERS_WHEN_LOCKED), true,
139                mSettingsObserver);
140        mContext.getContentResolver().registerContentObserver(
141                Settings.Global.getUriFor(
142                        Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED),
143                true, mSettingsObserver);
144        // Fetch initial values.
145        mSettingsObserver.onChange(false);
146
147        keyguardMonitor.addCallback(mCallback);
148        listenForCallState();
149
150        refreshUsers(UserHandle.USER_NULL);
151    }
152
153    /**
154     * Refreshes users from UserManager.
155     *
156     * The pictures are only loaded if they have not been loaded yet.
157     *
158     * @param forcePictureLoadForId forces the picture of the given user to be reloaded.
159     */
160    @SuppressWarnings("unchecked")
161    private void refreshUsers(int forcePictureLoadForId) {
162        if (DEBUG) Log.d(TAG, "refreshUsers(forcePictureLoadForId=" + forcePictureLoadForId+")");
163        if (forcePictureLoadForId != UserHandle.USER_NULL) {
164            mForcePictureLoadForUserId.put(forcePictureLoadForId, true);
165        }
166
167        if (mPauseRefreshUsers) {
168            return;
169        }
170
171        boolean forceAllUsers = mForcePictureLoadForUserId.get(UserHandle.USER_ALL);
172        SparseArray<Bitmap> bitmaps = new SparseArray<>(mUsers.size());
173        final int N = mUsers.size();
174        for (int i = 0; i < N; i++) {
175            UserRecord r = mUsers.get(i);
176            if (r == null || r.picture == null || r.info == null || forceAllUsers
177                    || mForcePictureLoadForUserId.get(r.info.id)) {
178                continue;
179            }
180            bitmaps.put(r.info.id, r.picture);
181        }
182        mForcePictureLoadForUserId.clear();
183
184        final boolean addUsersWhenLocked = mAddUsersWhenLocked;
185        new AsyncTask<SparseArray<Bitmap>, Void, ArrayList<UserRecord>>() {
186            @SuppressWarnings("unchecked")
187            @Override
188            protected ArrayList<UserRecord> doInBackground(SparseArray<Bitmap>... params) {
189                final SparseArray<Bitmap> bitmaps = params[0];
190                List<UserInfo> infos = mUserManager.getUsers(true);
191                if (infos == null) {
192                    return null;
193                }
194                ArrayList<UserRecord> records = new ArrayList<>(infos.size());
195                int currentId = ActivityManager.getCurrentUser();
196                boolean canSwitchUsers = mUserManager.canSwitchUsers();
197                UserInfo currentUserInfo = null;
198                UserRecord guestRecord = null;
199
200                for (UserInfo info : infos) {
201                    boolean isCurrent = currentId == info.id;
202                    if (isCurrent) {
203                        currentUserInfo = info;
204                    }
205                    boolean switchToEnabled = canSwitchUsers || isCurrent;
206                    if (info.isEnabled()) {
207                        if (info.isGuest()) {
208                            // Tapping guest icon triggers remove and a user switch therefore
209                            // the icon shouldn't be enabled even if the user is current
210                            guestRecord = new UserRecord(info, null /* picture */,
211                                    true /* isGuest */, isCurrent, false /* isAddUser */,
212                                    false /* isRestricted */, canSwitchUsers);
213                        } else if (info.supportsSwitchToByUser()) {
214                            Bitmap picture = bitmaps.get(info.id);
215                            if (picture == null) {
216                                picture = mUserManager.getUserIcon(info.id);
217
218                                if (picture != null) {
219                                    int avatarSize = mContext.getResources()
220                                            .getDimensionPixelSize(R.dimen.max_avatar_size);
221                                    picture = Bitmap.createScaledBitmap(
222                                            picture, avatarSize, avatarSize, true);
223                                }
224                            }
225                            int index = isCurrent ? 0 : records.size();
226                            records.add(index, new UserRecord(info, picture, false /* isGuest */,
227                                    isCurrent, false /* isAddUser */, false /* isRestricted */,
228                                    switchToEnabled));
229                        }
230                    }
231                }
232
233                boolean systemCanCreateUsers = !mUserManager.hasBaseUserRestriction(
234                                UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM);
235                boolean currentUserCanCreateUsers = currentUserInfo != null
236                        && (currentUserInfo.isAdmin()
237                                || currentUserInfo.id == UserHandle.USER_SYSTEM)
238                        && systemCanCreateUsers;
239                boolean anyoneCanCreateUsers = systemCanCreateUsers && addUsersWhenLocked;
240                boolean canCreateGuest = (currentUserCanCreateUsers || anyoneCanCreateUsers)
241                        && guestRecord == null;
242                boolean canCreateUser = (currentUserCanCreateUsers || anyoneCanCreateUsers)
243                        && mUserManager.canAddMoreUsers();
244                boolean createIsRestricted = !addUsersWhenLocked;
245
246                if (!mSimpleUserSwitcher) {
247                    if (guestRecord == null) {
248                        if (canCreateGuest) {
249                            guestRecord = new UserRecord(null /* info */, null /* picture */,
250                                    true /* isGuest */, false /* isCurrent */,
251                                    false /* isAddUser */, createIsRestricted, canSwitchUsers);
252                            checkIfAddUserDisallowedByAdminOnly(guestRecord);
253                            records.add(guestRecord);
254                        }
255                    } else {
256                        int index = guestRecord.isCurrent ? 0 : records.size();
257                        records.add(index, guestRecord);
258                    }
259                }
260
261                if (!mSimpleUserSwitcher && canCreateUser) {
262                    UserRecord addUserRecord = new UserRecord(null /* info */, null /* picture */,
263                            false /* isGuest */, false /* isCurrent */, true /* isAddUser */,
264                            createIsRestricted, canSwitchUsers);
265                    checkIfAddUserDisallowedByAdminOnly(addUserRecord);
266                    records.add(addUserRecord);
267                }
268
269                return records;
270            }
271
272            @Override
273            protected void onPostExecute(ArrayList<UserRecord> userRecords) {
274                if (userRecords != null) {
275                    mUsers = userRecords;
276                    notifyAdapters();
277                }
278            }
279        }.execute((SparseArray) bitmaps);
280    }
281
282    private void pauseRefreshUsers() {
283        if (!mPauseRefreshUsers) {
284            mHandler.postDelayed(mUnpauseRefreshUsers, PAUSE_REFRESH_USERS_TIMEOUT_MS);
285            mPauseRefreshUsers = true;
286        }
287    }
288
289    private void notifyAdapters() {
290        for (int i = mAdapters.size() - 1; i >= 0; i--) {
291            BaseUserAdapter adapter = mAdapters.get(i).get();
292            if (adapter != null) {
293                adapter.notifyDataSetChanged();
294            } else {
295                mAdapters.remove(i);
296            }
297        }
298    }
299
300    public boolean isSimpleUserSwitcher() {
301        return mSimpleUserSwitcher;
302    }
303
304    public boolean useFullscreenUserSwitcher() {
305        // Use adb to override:
306        // adb shell settings put system enable_fullscreen_user_switcher 0  # Turn it off.
307        // adb shell settings put system enable_fullscreen_user_switcher 1  # Turn it on.
308        // Restart SystemUI or adb reboot.
309        final int DEFAULT = -1;
310        final int overrideUseFullscreenUserSwitcher =
311                Settings.System.getInt(mContext.getContentResolver(),
312                        "enable_fullscreen_user_switcher", DEFAULT);
313        if (overrideUseFullscreenUserSwitcher != DEFAULT) {
314            return overrideUseFullscreenUserSwitcher != 0;
315        }
316        // Otherwise default to the build setting.
317        return mContext.getResources().getBoolean(R.bool.config_enableFullscreenUserSwitcher);
318    }
319
320    public void logoutCurrentUser() {
321        int currentUser = ActivityManager.getCurrentUser();
322        if (currentUser != UserHandle.USER_SYSTEM) {
323            pauseRefreshUsers();
324            ActivityManager.logoutCurrentUser();
325        }
326    }
327
328    public void removeUserId(int userId) {
329        if (userId == UserHandle.USER_SYSTEM) {
330            Log.w(TAG, "User " + userId + " could not removed.");
331            return;
332        }
333        if (ActivityManager.getCurrentUser() == userId) {
334            switchToUserId(UserHandle.USER_SYSTEM);
335        }
336        if (mUserManager.removeUser(userId)) {
337            refreshUsers(UserHandle.USER_NULL);
338        }
339    }
340
341    public void switchTo(UserRecord record) {
342        int id;
343        if (record.isGuest && record.info == null) {
344            // No guest user. Create one.
345            UserInfo guest = mUserManager.createGuest(
346                    mContext, mContext.getString(R.string.guest_nickname));
347            if (guest == null) {
348                // Couldn't create guest, most likely because there already exists one, we just
349                // haven't reloaded the user list yet.
350                return;
351            }
352            id = guest.id;
353        } else if (record.isAddUser) {
354            showAddUserDialog();
355            return;
356        } else {
357            id = record.info.id;
358        }
359
360        if (ActivityManager.getCurrentUser() == id) {
361            if (record.isGuest) {
362                showExitGuestDialog(id);
363            }
364            return;
365        }
366
367        switchToUserId(id);
368    }
369
370    public void switchTo(int userId) {
371        final int count = mUsers.size();
372        for (int i = 0; i < count; ++i) {
373            UserRecord record = mUsers.get(i);
374            if (record.info != null && record.info.id == userId) {
375                switchTo(record);
376                return;
377            }
378        }
379
380        Log.e(TAG, "Couldn't switch to user, id=" + userId);
381    }
382
383    public int getSwitchableUserCount() {
384        int count = 0;
385        final int N = mUsers.size();
386        for (int i = 0; i < N; ++i) {
387            UserRecord record = mUsers.get(i);
388            if (record.info != null && record.info.supportsSwitchTo()) {
389                count++;
390            }
391        }
392        return count;
393    }
394
395    private void switchToUserId(int id) {
396        try {
397            pauseRefreshUsers();
398            ActivityManagerNative.getDefault().switchUser(id);
399        } catch (RemoteException e) {
400            Log.e(TAG, "Couldn't switch user.", e);
401        }
402    }
403
404    private void showExitGuestDialog(int id) {
405        if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
406            mExitGuestDialog.cancel();
407        }
408        mExitGuestDialog = new ExitGuestDialog(mContext, id);
409        mExitGuestDialog.show();
410    }
411
412    private void showAddUserDialog() {
413        if (mAddUserDialog != null && mAddUserDialog.isShowing()) {
414            mAddUserDialog.cancel();
415        }
416        mAddUserDialog = new AddUserDialog(mContext);
417        mAddUserDialog.show();
418    }
419
420    private void exitGuest(int id) {
421        int newId = UserHandle.USER_SYSTEM;
422        if (mLastNonGuestUser != UserHandle.USER_SYSTEM) {
423            UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
424            if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) {
425                newId = info.id;
426            }
427        }
428        switchToUserId(newId);
429        mUserManager.removeUser(id);
430    }
431
432    private void listenForCallState() {
433        TelephonyManager.from(mContext).listen(mPhoneStateListener,
434                PhoneStateListener.LISTEN_CALL_STATE);
435    }
436
437    private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
438        private int mCallState;
439
440        @Override
441        public void onCallStateChanged(int state, String incomingNumber) {
442            if (mCallState == state) return;
443            if (DEBUG) Log.v(TAG, "Call state changed: " + state);
444            mCallState = state;
445            int currentUserId = ActivityManager.getCurrentUser();
446            UserInfo userInfo = mUserManager.getUserInfo(currentUserId);
447            if (userInfo != null && userInfo.isGuest()) {
448                showGuestNotification(currentUserId);
449            }
450            refreshUsers(UserHandle.USER_NULL);
451        }
452    };
453
454    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
455        @Override
456        public void onReceive(Context context, Intent intent) {
457            if (DEBUG) {
458                Log.v(TAG, "Broadcast: a=" + intent.getAction()
459                       + " user=" + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1));
460            }
461
462            boolean unpauseRefreshUsers = false;
463            int forcePictureLoadForId = UserHandle.USER_NULL;
464
465            if (ACTION_REMOVE_GUEST.equals(intent.getAction())) {
466                int currentUser = ActivityManager.getCurrentUser();
467                UserInfo userInfo = mUserManager.getUserInfo(currentUser);
468                if (userInfo != null && userInfo.isGuest()) {
469                    showExitGuestDialog(currentUser);
470                }
471                return;
472            } else if (ACTION_LOGOUT_USER.equals(intent.getAction())) {
473                logoutCurrentUser();
474            } else if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
475                if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
476                    mExitGuestDialog.cancel();
477                    mExitGuestDialog = null;
478                }
479
480                final int currentId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
481                final UserInfo userInfo = mUserManager.getUserInfo(currentId);
482                final int N = mUsers.size();
483                for (int i = 0; i < N; i++) {
484                    UserRecord record = mUsers.get(i);
485                    if (record.info == null) continue;
486                    boolean shouldBeCurrent = record.info.id == currentId;
487                    if (record.isCurrent != shouldBeCurrent) {
488                        mUsers.set(i, record.copyWithIsCurrent(shouldBeCurrent));
489                    }
490                    if (shouldBeCurrent && !record.isGuest) {
491                        mLastNonGuestUser = record.info.id;
492                    }
493                    if ((userInfo == null || !userInfo.isAdmin()) && record.isRestricted) {
494                        // Immediately remove restricted records in case the AsyncTask is too slow.
495                        mUsers.remove(i);
496                        i--;
497                    }
498                }
499                notifyAdapters();
500
501                // Disconnect from the old secondary user's service
502                if (mSecondaryUser != UserHandle.USER_NULL) {
503                    context.stopServiceAsUser(mSecondaryUserServiceIntent,
504                            UserHandle.of(mSecondaryUser));
505                    mSecondaryUser = UserHandle.USER_NULL;
506                }
507                // Connect to the new secondary user's service (purely to ensure that a persistent
508                // SystemUI application is created for that user)
509                if (userInfo != null && !userInfo.isPrimary()) {
510                    context.startServiceAsUser(mSecondaryUserServiceIntent,
511                            UserHandle.of(userInfo.id));
512                    mSecondaryUser = userInfo.id;
513                }
514
515                if (UserManager.isSplitSystemUser() && userInfo != null && !userInfo.isGuest()
516                        && userInfo.id != UserHandle.USER_SYSTEM) {
517                    showLogoutNotification(currentId);
518                }
519                if (userInfo != null && userInfo.isGuest()) {
520                    showGuestNotification(currentId);
521                }
522                unpauseRefreshUsers = true;
523            } else if (Intent.ACTION_USER_INFO_CHANGED.equals(intent.getAction())) {
524                forcePictureLoadForId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
525                        UserHandle.USER_NULL);
526            } else if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
527                // Unlocking the system user may require a refresh
528                int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
529                if (userId != UserHandle.USER_SYSTEM) {
530                    return;
531                }
532            }
533            refreshUsers(forcePictureLoadForId);
534            if (unpauseRefreshUsers) {
535                mUnpauseRefreshUsers.run();
536            }
537        }
538
539        private void showLogoutNotification(int userId) {
540            PendingIntent logoutPI = PendingIntent.getBroadcastAsUser(mContext,
541                    0, new Intent(ACTION_LOGOUT_USER), 0, UserHandle.SYSTEM);
542            Notification.Builder builder = new Notification.Builder(mContext)
543                    .setVisibility(Notification.VISIBILITY_SECRET)
544                    .setPriority(Notification.PRIORITY_MIN)
545                    .setSmallIcon(R.drawable.ic_person)
546                    .setContentTitle(mContext.getString(R.string.user_logout_notification_title))
547                    .setContentText(mContext.getString(R.string.user_logout_notification_text))
548                    .setContentIntent(logoutPI)
549                    .setOngoing(true)
550                    .setShowWhen(false)
551                    .addAction(R.drawable.ic_delete,
552                            mContext.getString(R.string.user_logout_notification_action),
553                            logoutPI);
554            SystemUI.overrideNotificationAppName(mContext, builder);
555            NotificationManager.from(mContext).notifyAsUser(TAG_LOGOUT_USER,
556                    SystemMessage.NOTE_LOGOUT_USER, builder.build(), new UserHandle(userId));
557        }
558    };
559
560    private void showGuestNotification(int guestUserId) {
561        boolean canSwitchUsers = mUserManager.canSwitchUsers();
562        // Disable 'Remove guest' action if cannot switch users right now
563        PendingIntent removeGuestPI = canSwitchUsers ? PendingIntent.getBroadcastAsUser(mContext,
564                0, new Intent(ACTION_REMOVE_GUEST), 0, UserHandle.SYSTEM) : null;
565
566        Notification.Builder builder = new Notification.Builder(mContext)
567                .setVisibility(Notification.VISIBILITY_SECRET)
568                .setPriority(Notification.PRIORITY_MIN)
569                .setSmallIcon(R.drawable.ic_person)
570                .setContentTitle(mContext.getString(R.string.guest_notification_title))
571                .setContentText(mContext.getString(R.string.guest_notification_text))
572                .setContentIntent(removeGuestPI)
573                .setShowWhen(false)
574                .addAction(R.drawable.ic_delete,
575                        mContext.getString(R.string.guest_notification_remove_action),
576                        removeGuestPI);
577        SystemUI.overrideNotificationAppName(mContext, builder);
578        NotificationManager.from(mContext).notifyAsUser(TAG_REMOVE_GUEST,
579                SystemMessage.NOTE_REMOVE_GUEST, builder.build(), new UserHandle(guestUserId));
580    }
581
582    private final Runnable mUnpauseRefreshUsers = new Runnable() {
583        @Override
584        public void run() {
585            mHandler.removeCallbacks(this);
586            mPauseRefreshUsers = false;
587            refreshUsers(UserHandle.USER_NULL);
588        }
589    };
590
591    private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) {
592        public void onChange(boolean selfChange) {
593            mSimpleUserSwitcher = Settings.Global.getInt(mContext.getContentResolver(),
594                    SIMPLE_USER_SWITCHER_GLOBAL_SETTING, 0) != 0;
595            mAddUsersWhenLocked = Settings.Global.getInt(mContext.getContentResolver(),
596                    Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0;
597            refreshUsers(UserHandle.USER_NULL);
598        };
599    };
600
601    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
602        pw.println("UserSwitcherController state:");
603        pw.println("  mLastNonGuestUser=" + mLastNonGuestUser);
604        pw.print("  mUsers.size="); pw.println(mUsers.size());
605        for (int i = 0; i < mUsers.size(); i++) {
606            final UserRecord u = mUsers.get(i);
607            pw.print("    "); pw.println(u.toString());
608        }
609    }
610
611    public String getCurrentUserName(Context context) {
612        if (mUsers.isEmpty()) return null;
613        UserRecord item = mUsers.get(0);
614        if (item == null || item.info == null) return null;
615        if (item.isGuest) return context.getString(R.string.guest_nickname);
616        return item.info.name;
617    }
618
619    public void onDensityOrFontScaleChanged() {
620        refreshUsers(UserHandle.USER_ALL);
621    }
622
623    public static abstract class BaseUserAdapter extends BaseAdapter {
624
625        final UserSwitcherController mController;
626
627        protected BaseUserAdapter(UserSwitcherController controller) {
628            mController = controller;
629            controller.mAdapters.add(new WeakReference<>(this));
630        }
631
632        @Override
633        public int getCount() {
634            boolean secureKeyguardShowing = mController.mKeyguardMonitor.isShowing()
635                    && mController.mKeyguardMonitor.isSecure()
636                    && !mController.mKeyguardMonitor.canSkipBouncer();
637            if (!secureKeyguardShowing) {
638                return mController.mUsers.size();
639            }
640            // The lock screen is secure and showing. Filter out restricted records.
641            final int N = mController.mUsers.size();
642            int count = 0;
643            for (int i = 0; i < N; i++) {
644                if (mController.mUsers.get(i).isRestricted) {
645                    break;
646                } else {
647                    count++;
648                }
649            }
650            return count;
651        }
652
653        @Override
654        public UserRecord getItem(int position) {
655            return mController.mUsers.get(position);
656        }
657
658        @Override
659        public long getItemId(int position) {
660            return position;
661        }
662
663        public void switchTo(UserRecord record) {
664            mController.switchTo(record);
665        }
666
667        public String getName(Context context, UserRecord item) {
668            if (item.isGuest) {
669                if (item.isCurrent) {
670                    return context.getString(R.string.guest_exit_guest);
671                } else {
672                    return context.getString(
673                            item.info == null ? R.string.guest_new_guest : R.string.guest_nickname);
674                }
675            } else if (item.isAddUser) {
676                return context.getString(R.string.user_add_user);
677            } else {
678                return item.info.name;
679            }
680        }
681
682        public Drawable getDrawable(Context context, UserRecord item) {
683            if (item.isAddUser) {
684                return context.getDrawable(R.drawable.ic_add_circle_qs);
685            }
686            return UserIcons.getDefaultUserIcon(item.resolveId(), /* light= */ true);
687        }
688
689        public void refresh() {
690            mController.refreshUsers(UserHandle.USER_NULL);
691        }
692    }
693
694    private void checkIfAddUserDisallowedByAdminOnly(UserRecord record) {
695        EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(mContext,
696                UserManager.DISALLOW_ADD_USER, ActivityManager.getCurrentUser());
697        if (admin != null && !RestrictedLockUtils.hasBaseUserRestriction(mContext,
698                UserManager.DISALLOW_ADD_USER, ActivityManager.getCurrentUser())) {
699            record.isDisabledByAdmin = true;
700            record.enforcedAdmin = admin;
701        } else {
702            record.isDisabledByAdmin = false;
703            record.enforcedAdmin = null;
704        }
705    }
706
707    public void startActivity(Intent intent) {
708        mActivityStarter.startActivity(intent, true);
709    }
710
711    public static final class UserRecord {
712        public final UserInfo info;
713        public final Bitmap picture;
714        public final boolean isGuest;
715        public final boolean isCurrent;
716        public final boolean isAddUser;
717        /** If true, the record is only visible to the owner and only when unlocked. */
718        public final boolean isRestricted;
719        public boolean isDisabledByAdmin;
720        public EnforcedAdmin enforcedAdmin;
721        public boolean isSwitchToEnabled;
722
723        public UserRecord(UserInfo info, Bitmap picture, boolean isGuest, boolean isCurrent,
724                boolean isAddUser, boolean isRestricted, boolean isSwitchToEnabled) {
725            this.info = info;
726            this.picture = picture;
727            this.isGuest = isGuest;
728            this.isCurrent = isCurrent;
729            this.isAddUser = isAddUser;
730            this.isRestricted = isRestricted;
731            this.isSwitchToEnabled = isSwitchToEnabled;
732        }
733
734        public UserRecord copyWithIsCurrent(boolean _isCurrent) {
735            return new UserRecord(info, picture, isGuest, _isCurrent, isAddUser, isRestricted,
736                    isSwitchToEnabled);
737        }
738
739        public int resolveId() {
740            if (isGuest || info == null) {
741                return UserHandle.USER_NULL;
742            }
743            return info.id;
744        }
745
746        public String toString() {
747            StringBuilder sb = new StringBuilder();
748            sb.append("UserRecord(");
749            if (info != null) {
750                sb.append("name=\"").append(info.name).append("\" id=").append(info.id);
751            } else {
752                if (isGuest) {
753                    sb.append("<add guest placeholder>");
754                } else if (isAddUser) {
755                    sb.append("<add user placeholder>");
756                }
757            }
758            if (isGuest) sb.append(" <isGuest>");
759            if (isAddUser) sb.append(" <isAddUser>");
760            if (isCurrent) sb.append(" <isCurrent>");
761            if (picture != null) sb.append(" <hasPicture>");
762            if (isRestricted) sb.append(" <isRestricted>");
763            if (isDisabledByAdmin) {
764                sb.append(" <isDisabledByAdmin>");
765                sb.append(" enforcedAdmin=").append(enforcedAdmin);
766            }
767            if (isSwitchToEnabled) {
768                sb.append(" <isSwitchToEnabled>");
769            }
770            sb.append(')');
771            return sb.toString();
772        }
773    }
774
775    public final QSTile.DetailAdapter userDetailAdapter = new QSTile.DetailAdapter() {
776        private final Intent USER_SETTINGS_INTENT = new Intent(Settings.ACTION_USER_SETTINGS);
777
778        @Override
779        public CharSequence getTitle() {
780            return mContext.getString(R.string.quick_settings_user_title);
781        }
782
783        @Override
784        public View createDetailView(Context context, View convertView, ViewGroup parent) {
785            UserDetailView v;
786            if (!(convertView instanceof UserDetailView)) {
787                v = UserDetailView.inflate(context, parent, false);
788                v.createAndSetAdapter(UserSwitcherController.this);
789            } else {
790                v = (UserDetailView) convertView;
791            }
792            v.refreshAdapter();
793            return v;
794        }
795
796        @Override
797        public Intent getSettingsIntent() {
798            return USER_SETTINGS_INTENT;
799        }
800
801        @Override
802        public Boolean getToggleState() {
803            return null;
804        }
805
806        @Override
807        public void setToggleState(boolean state) {
808        }
809
810        @Override
811        public int getMetricsCategory() {
812            return MetricsEvent.QS_USERDETAIL;
813        }
814    };
815
816    private final KeyguardMonitor.Callback mCallback = new KeyguardMonitor.Callback() {
817        @Override
818        public void onKeyguardChanged() {
819            notifyAdapters();
820        }
821    };
822
823    private final class ExitGuestDialog extends SystemUIDialog implements
824            DialogInterface.OnClickListener {
825
826        private final int mGuestId;
827
828        public ExitGuestDialog(Context context, int guestId) {
829            super(context);
830            setTitle(R.string.guest_exit_guest_dialog_title);
831            setMessage(context.getString(R.string.guest_exit_guest_dialog_message));
832            setButton(DialogInterface.BUTTON_NEGATIVE,
833                    context.getString(android.R.string.cancel), this);
834            setButton(DialogInterface.BUTTON_POSITIVE,
835                    context.getString(R.string.guest_exit_guest_dialog_remove), this);
836            setCanceledOnTouchOutside(false);
837            mGuestId = guestId;
838        }
839
840        @Override
841        public void onClick(DialogInterface dialog, int which) {
842            if (which == BUTTON_NEGATIVE) {
843                cancel();
844            } else {
845                dismiss();
846                exitGuest(mGuestId);
847            }
848        }
849    }
850
851    private final class AddUserDialog extends SystemUIDialog implements
852            DialogInterface.OnClickListener {
853
854        public AddUserDialog(Context context) {
855            super(context);
856            setTitle(R.string.user_add_user_title);
857            setMessage(context.getString(R.string.user_add_user_message_short));
858            setButton(DialogInterface.BUTTON_NEGATIVE,
859                    context.getString(android.R.string.cancel), this);
860            setButton(DialogInterface.BUTTON_POSITIVE,
861                    context.getString(android.R.string.ok), this);
862        }
863
864        @Override
865        public void onClick(DialogInterface dialog, int which) {
866            if (which == BUTTON_NEGATIVE) {
867                cancel();
868            } else {
869                dismiss();
870                if (ActivityManager.isUserAMonkey()) {
871                    return;
872                }
873                UserInfo user = mUserManager.createUser(
874                        mContext.getString(R.string.user_new_user_name), 0 /* flags */);
875                if (user == null) {
876                    // Couldn't create user, most likely because there are too many, but we haven't
877                    // been able to reload the list yet.
878                    return;
879                }
880                int id = user.id;
881                Bitmap icon = UserIcons.convertToBitmap(UserIcons.getDefaultUserIcon(
882                        id, /* light= */ false));
883                mUserManager.setUserIcon(id, icon);
884                switchToUserId(id);
885            }
886        }
887    }
888}
889