UserSwitcherController.java revision 4d75c079f35d85b687d8349e5e2940447d01198e
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.R;
21import com.android.systemui.qs.QSTile;
22import com.android.systemui.qs.tiles.UserDetailView;
23
24import android.app.ActivityManager;
25import android.app.ActivityManagerNative;
26import android.content.BroadcastReceiver;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.pm.UserInfo;
31import android.graphics.Bitmap;
32import android.os.AsyncTask;
33import android.os.RemoteException;
34import android.os.UserHandle;
35import android.os.UserManager;
36import android.util.Log;
37import android.util.SparseArray;
38import android.view.View;
39import android.view.ViewGroup;
40import android.view.WindowManagerGlobal;
41import android.widget.BaseAdapter;
42
43import java.io.FileDescriptor;
44import java.io.PrintWriter;
45import java.lang.ref.WeakReference;
46import java.util.ArrayList;
47import java.util.List;
48
49/**
50 * Keeps a list of all users on the device for user switching.
51 */
52public class UserSwitcherController {
53
54    private static final String TAG = "UserSwitcherController";
55
56    private final Context mContext;
57    private final UserManager mUserManager;
58    private final ArrayList<WeakReference<BaseUserAdapter>> mAdapters = new ArrayList<>();
59
60    private ArrayList<UserRecord> mUsers = new ArrayList<>();
61
62    public UserSwitcherController(Context context) {
63        mContext = context;
64        mUserManager = UserManager.get(context);
65        IntentFilter filter = new IntentFilter();
66        filter.addAction(Intent.ACTION_USER_ADDED);
67        filter.addAction(Intent.ACTION_USER_REMOVED);
68        filter.addAction(Intent.ACTION_USER_INFO_CHANGED);
69        filter.addAction(Intent.ACTION_USER_SWITCHED);
70        filter.addAction(Intent.ACTION_USER_STOPPING);
71        mContext.registerReceiverAsUser(mReceiver, UserHandle.OWNER, filter,
72                null /* permission */, null /* scheduler */);
73        refreshUsers(UserHandle.USER_NULL);
74    }
75
76    /**
77     * Refreshes users from UserManager.
78     *
79     * The pictures are only loaded if they have not been loaded yet.
80     *
81     * @param forcePictureLoadForId forces the picture of the given user to be reloaded.
82     */
83    private void refreshUsers(int forcePictureLoadForId) {
84
85        SparseArray<Bitmap> bitmaps = new SparseArray<>(mUsers.size());
86        final int N = mUsers.size();
87        for (int i = 0; i < N; i++) {
88            UserRecord r = mUsers.get(i);
89            if (r == null || r.info == null
90                    || r.info.id == forcePictureLoadForId || r.picture == null) {
91                continue;
92            }
93            bitmaps.put(r.info.id, r.picture);
94        }
95
96        new AsyncTask<SparseArray<Bitmap>, Void, ArrayList<UserRecord>>() {
97            @SuppressWarnings("unchecked")
98            @Override
99            protected ArrayList<UserRecord> doInBackground(SparseArray<Bitmap>... params) {
100                final SparseArray<Bitmap> bitmaps = params[0];
101                List<UserInfo> infos = mUserManager.getUsers(true);
102                if (infos == null) {
103                    return null;
104                }
105                ArrayList<UserRecord> records = new ArrayList<>(infos.size());
106                int currentId = ActivityManager.getCurrentUser();
107                UserRecord guestRecord = null;
108                int avatarSize = mContext.getResources()
109                        .getDimensionPixelSize(R.dimen.max_avatar_size);
110
111                for (UserInfo info : infos) {
112                    boolean isCurrent = currentId == info.id;
113                    if (info.isGuest()) {
114                        guestRecord = new UserRecord(info, null /* picture */,
115                                true /* isGuest */, isCurrent);
116                    } else if (!info.isManagedProfile()) {
117                        Bitmap picture = bitmaps.get(info.id);
118                        if (picture == null) {
119                            picture = mUserManager.getUserIcon(info.id);
120                        }
121                        if (picture != null) {
122                            picture = BitmapHelper.createCircularClip(
123                                    picture, avatarSize, avatarSize);
124                        }
125                        records.add(new UserRecord(info, picture, false /* isGuest */, isCurrent));
126                    }
127                }
128
129                if (guestRecord == null) {
130                    records.add(new UserRecord(null /* info */, null /* picture */,
131                            true /* isGuest */, false /* isCurrent */));
132                } else {
133                    records.add(guestRecord);
134                }
135
136                return records;
137            }
138
139            @Override
140            protected void onPostExecute(ArrayList<UserRecord> userRecords) {
141                if (userRecords != null) {
142                    mUsers = userRecords;
143                    notifyAdapters();
144                }
145            }
146        }.execute((SparseArray)bitmaps);
147    }
148
149    private void notifyAdapters() {
150        for (int i = mAdapters.size() - 1; i >= 0; i--) {
151            BaseUserAdapter adapter = mAdapters.get(i).get();
152            if (adapter != null) {
153                adapter.notifyDataSetChanged();
154            } else {
155                mAdapters.remove(i);
156            }
157        }
158    }
159
160    public void switchTo(UserRecord record) {
161        int id;
162        if (record.isGuest && record.info == null) {
163            // No guest user. Create one.
164            id = mUserManager.createGuest(mContext,
165                    mContext.getResources().getString(R.string.guest_nickname)).id;
166        } else {
167            id = record.info.id;
168        }
169
170        if (ActivityManager.getCurrentUser() == id) {
171            if (record.isGuest) {
172                exitGuest(id);
173            }
174            return;
175        }
176
177        switchToUserId(id);
178    }
179
180    private void switchToUserId(int id) {
181        try {
182            WindowManagerGlobal.getWindowManagerService().lockNow(null);
183            ActivityManagerNative.getDefault().switchUser(id);
184        } catch (RemoteException e) {
185            Log.e(TAG, "Couldn't switch user.", e);
186        }
187    }
188
189    private void exitGuest(int id) {
190        // TODO: show confirmation dialog
191        switchToUserId(UserHandle.USER_OWNER);
192        mUserManager.removeUser(id);
193    }
194
195    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
196        @Override
197        public void onReceive(Context context, Intent intent) {
198            if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
199                final int currentId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
200                final int N = mUsers.size();
201                for (int i = 0; i < N; i++) {
202                    UserRecord record = mUsers.get(i);
203                    if (record.info == null) continue;
204                    boolean shouldBeCurrent = record.info.id == currentId;
205                    if (record.isCurrent != shouldBeCurrent) {
206                        mUsers.set(i, record.copyWithIsCurrent(shouldBeCurrent));
207                    }
208                }
209                notifyAdapters();
210            }
211            int forcePictureLoadForId = UserHandle.USER_NULL;
212            if (Intent.ACTION_USER_INFO_CHANGED.equals(intent.getAction())) {
213                forcePictureLoadForId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
214                        UserHandle.USER_NULL);
215            }
216            refreshUsers(forcePictureLoadForId);
217        }
218    };
219
220    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
221        pw.println("UserSwitcherController state:");
222        pw.print("  mUsers.size="); pw.println(mUsers.size());
223        for (int i = 0; i < mUsers.size(); i++) {
224            final UserRecord u = mUsers.get(i);
225            pw.print("    "); pw.println(u.toString());
226        }
227    }
228
229    public static abstract class BaseUserAdapter extends BaseAdapter {
230
231        final UserSwitcherController mController;
232
233        protected BaseUserAdapter(UserSwitcherController controller) {
234            mController = controller;
235            controller.mAdapters.add(new WeakReference<>(this));
236        }
237
238        @Override
239        public int getCount() {
240            return mController.mUsers.size();
241        }
242
243        @Override
244        public UserRecord getItem(int position) {
245            return mController.mUsers.get(position);
246        }
247
248        @Override
249        public long getItemId(int position) {
250            return position;
251        }
252
253        public void switchTo(UserRecord record) {
254            mController.switchTo(record);
255        }
256
257        public String getName(Context context, UserRecord item) {
258            if (item.isGuest) {
259                if (item.isCurrent) {
260                    return context.getString(R.string.guest_exit_guest);
261                } else {
262                    return context.getString(
263                            item.info == null ? R.string.guest_new_guest : R.string.guest_nickname);
264                }
265            } else {
266                return item.info.name;
267            }
268        }
269    }
270
271    public static final class UserRecord {
272        public final UserInfo info;
273        public final Bitmap picture;
274        public final boolean isGuest;
275        public final boolean isCurrent;
276
277        public UserRecord(UserInfo info, Bitmap picture, boolean isGuest, boolean isCurrent) {
278            this.info = info;
279            this.picture = picture;
280            this.isGuest = isGuest;
281            this.isCurrent = isCurrent;
282        }
283
284        public UserRecord copyWithIsCurrent(boolean _isCurrent) {
285            return new UserRecord(info, picture, isGuest, _isCurrent);
286        }
287
288        public String toString() {
289            StringBuilder sb = new StringBuilder();
290            sb.append("UserRecord(");
291            if (info != null) {
292                sb.append("name=\"" + info.name + "\" id=" + info.id);
293            } else {
294                sb.append("<add guest placeholder>");
295            }
296            if (isGuest) {
297                sb.append(" <isGuest>");
298            }
299            if (isCurrent) {
300                sb.append(" <isCurrent>");
301            }
302            if (picture != null) {
303                sb.append(" <hasPicture>");
304            }
305            sb.append(')');
306            return sb.toString();
307        }
308    }
309
310    public final QSTile.DetailAdapter userDetailAdapter = new QSTile.DetailAdapter() {
311        private final Intent USER_SETTINGS_INTENT = new Intent("android.settings.USER_SETTINGS");
312
313        @Override
314        public int getTitle() {
315            return R.string.quick_settings_user_title;
316        }
317
318        @Override
319        public View createDetailView(Context context, View convertView, ViewGroup parent) {
320            if (!(convertView instanceof UserDetailView)) {
321                convertView = UserDetailView.inflate(context, parent, false);
322            }
323            UserDetailView v = (UserDetailView) convertView;
324            if (v.getAdapter() == null) {
325                v.createAndSetAdapter(UserSwitcherController.this);
326            }
327            return v;
328        }
329
330        @Override
331        public Intent getSettingsIntent() {
332            return USER_SETTINGS_INTENT;
333        }
334
335        @Override
336        public Boolean getToggleState() {
337            return null;
338        }
339
340        @Override
341        public void setToggleState(boolean state) {
342        }
343    };
344}
345