blob: 827a59eddf56df7b56effe918c93f556047426c3 [file] [log] [blame]
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -07001/*
2 * Copyright (C) 2018 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
Brad Stenning8d1a51c2018-11-20 17:34:16 -080014 * limitations under the License.
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -070015 */
16
17package com.android.systemui.statusbar.car;
18
Aarthi Balachandera7096002018-05-21 18:12:25 -070019import static android.content.DialogInterface.BUTTON_NEGATIVE;
Aarthi Balachander0e0e9212018-04-19 19:25:33 -070020import static android.content.DialogInterface.BUTTON_POSITIVE;
21
22import android.app.AlertDialog;
23import android.app.AlertDialog.Builder;
24import android.app.Dialog;
Ying Zhengad2773f2018-09-14 10:31:07 -070025import android.car.userlib.CarUserManagerHelper;
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -070026import android.content.Context;
Aarthi Balachander0e0e9212018-04-19 19:25:33 -070027import android.content.DialogInterface;
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -070028import android.content.pm.UserInfo;
29import android.content.res.Resources;
jovanake4ce3cc2018-04-19 12:17:12 -070030import android.graphics.Bitmap;
Heemin Seog0d5e0182019-03-13 13:49:24 -070031import android.graphics.Rect;
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -070032import android.os.AsyncTask;
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -070033import android.util.AttributeSet;
34import android.view.LayoutInflater;
35import android.view.View;
36import android.view.ViewGroup;
37import android.widget.ImageView;
38import android.widget.TextView;
39
Aarthi Balachandere3110e42018-04-30 18:16:26 -070040import androidx.core.graphics.drawable.RoundedBitmapDrawable;
41import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
Heemin Seog0d5e0182019-03-13 13:49:24 -070042import androidx.recyclerview.widget.GridLayoutManager;
Aarthi Balachandera7096002018-05-21 18:12:25 -070043import androidx.recyclerview.widget.RecyclerView;
44
jovanake4ce3cc2018-04-19 12:17:12 -070045import com.android.internal.util.UserIcons;
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -070046import com.android.systemui.R;
Aarthi Balachander0e0e9212018-04-19 19:25:33 -070047import com.android.systemui.statusbar.phone.SystemUIDialog;
Gus Prevasab336792018-11-14 13:52:20 -050048
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -070049import java.util.ArrayList;
50import java.util.List;
51
52/**
53 * Displays a GridLayout with icons for the users in the system to allow switching between users.
54 * One of the uses of this is for the lock screen in auto.
55 */
Heemin Seog0d5e0182019-03-13 13:49:24 -070056public class UserGridRecyclerView extends RecyclerView implements
jovanak78cacc42018-08-06 18:38:03 -070057 CarUserManagerHelper.OnUsersUpdateListener {
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -070058 private UserSelectionListener mUserSelectionListener;
59 private UserAdapter mAdapter;
jovanak78cacc42018-08-06 18:38:03 -070060 private CarUserManagerHelper mCarUserManagerHelper;
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -070061 private Context mContext;
62
63 public UserGridRecyclerView(Context context, AttributeSet attrs) {
64 super(context, attrs);
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -070065 mContext = context;
jovanak78cacc42018-08-06 18:38:03 -070066 mCarUserManagerHelper = new CarUserManagerHelper(mContext);
Heemin Seog0d5e0182019-03-13 13:49:24 -070067
68 addItemDecoration(new ItemSpacingDecoration(context.getResources().getDimensionPixelSize(
69 R.dimen.car_user_switcher_vertical_spacing_between_users)));
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -070070 }
71
72 /**
73 * Register listener for any update to the users
74 */
75 @Override
76 public void onFinishInflate() {
Aarthi Balachander608b6e32018-04-11 18:41:52 -070077 super.onFinishInflate();
jovanak78cacc42018-08-06 18:38:03 -070078 mCarUserManagerHelper.registerOnUsersUpdateListener(this);
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -070079 }
80
81 /**
82 * Unregisters listener checking for any change to the users
83 */
84 @Override
85 public void onDetachedFromWindow() {
Aarthi Balachander608b6e32018-04-11 18:41:52 -070086 super.onDetachedFromWindow();
jovanak78cacc42018-08-06 18:38:03 -070087 mCarUserManagerHelper.unregisterOnUsersUpdateListener(this);
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -070088 }
89
90 /**
91 * Initializes the adapter that populates the grid layout
92 *
93 * @return the adapter
94 */
95 public void buildAdapter() {
jovanak78cacc42018-08-06 18:38:03 -070096 List<UserRecord> userRecords = createUserRecords(mCarUserManagerHelper
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -070097 .getAllUsers());
98 mAdapter = new UserAdapter(mContext, userRecords);
99 super.setAdapter(mAdapter);
100 }
101
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700102 private List<UserRecord> createUserRecords(List<UserInfo> userInfoList) {
103 List<UserRecord> userRecords = new ArrayList<>();
jovanak2baf8d62018-08-09 12:59:50 -0700104
105 // If the foreground user CANNOT switch to other users, only display the foreground user.
106 if (!mCarUserManagerHelper.canForegroundUserSwitchUsers()) {
107 userRecords.add(createForegroundUserRecord());
108 return userRecords;
109 }
110
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700111 for (UserInfo userInfo : userInfoList) {
jovanak80b48592018-04-11 17:09:53 -0700112 if (userInfo.isGuest()) {
113 // Don't display guests in the switcher.
114 continue;
115 }
jovanak78cacc42018-08-06 18:38:03 -0700116
117 boolean isForeground =
118 mCarUserManagerHelper.getCurrentForegroundUserId() == userInfo.id;
jovanak80b48592018-04-11 17:09:53 -0700119 UserRecord record = new UserRecord(userInfo, false /* isStartGuestSession */,
jovanak0535abc2018-04-10 15:14:50 -0700120 false /* isAddUser */, isForeground);
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700121 userRecords.add(record);
122 }
123
jovanak2d26ae32018-07-31 10:44:41 -0700124 // Add button for starting guest session.
125 userRecords.add(createStartGuestUserRecord());
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700126
jovanak0535abc2018-04-10 15:14:50 -0700127 // Add add user record if the foreground user can add users
jovanak78cacc42018-08-06 18:38:03 -0700128 if (mCarUserManagerHelper.canForegroundUserAddUsers()) {
jovanak2d26ae32018-07-31 10:44:41 -0700129 userRecords.add(createAddUserRecord());
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700130 }
131
132 return userRecords;
133 }
134
jovanak2baf8d62018-08-09 12:59:50 -0700135 private UserRecord createForegroundUserRecord() {
136 return new UserRecord(mCarUserManagerHelper.getCurrentForegroundUserInfo(),
137 false /* isStartGuestSession */, false /* isAddUser */, true /* isForeground */);
138 }
139
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700140 /**
141 * Create guest user record
142 */
jovanak2d26ae32018-07-31 10:44:41 -0700143 private UserRecord createStartGuestUserRecord() {
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700144 UserInfo userInfo = new UserInfo();
jovanak2d26ae32018-07-31 10:44:41 -0700145 userInfo.name = mContext.getString(R.string.start_guest_session);
146 return new UserRecord(userInfo, true /* isStartGuestSession */, false /* isAddUser */,
147 false /* isForeground */);
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700148 }
149
150 /**
151 * Create add user record
152 */
jovanak2d26ae32018-07-31 10:44:41 -0700153 private UserRecord createAddUserRecord() {
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700154 UserInfo userInfo = new UserInfo();
155 userInfo.name = mContext.getString(R.string.car_add_user);
jovanak80b48592018-04-11 17:09:53 -0700156 return new UserRecord(userInfo, false /* isStartGuestSession */,
jovanak0535abc2018-04-10 15:14:50 -0700157 true /* isAddUser */, false /* isForeground */);
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700158 }
159
160 public void setUserSelectionListener(UserSelectionListener userSelectionListener) {
161 mUserSelectionListener = userSelectionListener;
162 }
163
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700164 @Override
165 public void onUsersUpdate() {
166 mAdapter.clearUsers();
jovanak78cacc42018-08-06 18:38:03 -0700167 mAdapter.updateUsers(createUserRecords(mCarUserManagerHelper.getAllUsers()));
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700168 mAdapter.notifyDataSetChanged();
169 }
170
171 /**
172 * Adapter to populate the grid layout with the available user profiles
173 */
Aarthi Balachander0e0e9212018-04-19 19:25:33 -0700174 public final class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserAdapterViewHolder>
Aarthi Balachanderc6d13662018-08-13 14:49:41 -0700175 implements Dialog.OnClickListener, Dialog.OnCancelListener {
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700176
177 private final Context mContext;
178 private List<UserRecord> mUsers;
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700179 private final Resources mRes;
180 private final String mGuestName;
181 private final String mNewUserName;
Aarthi Balachander0e0e9212018-04-19 19:25:33 -0700182 // View that holds the add user button. Used to enable/disable the view
183 private View mAddUserView;
Aarthi Balachandera7096002018-05-21 18:12:25 -0700184 // User record for the add user. Need to call notifyUserSelected only if the user
185 // confirms adding a user
186 private UserRecord mAddUserRecord;
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700187
188 public UserAdapter(Context context, List<UserRecord> users) {
189 mRes = context.getResources();
190 mContext = context;
191 updateUsers(users);
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700192 mGuestName = mRes.getString(R.string.car_guest);
193 mNewUserName = mRes.getString(R.string.car_new_user);
194 }
195
196 public void clearUsers() {
197 mUsers.clear();
198 }
199
200 public void updateUsers(List<UserRecord> users) {
201 mUsers = users;
202 }
203
204 @Override
205 public UserAdapterViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
206 View view = LayoutInflater.from(mContext)
207 .inflate(R.layout.car_fullscreen_user_pod, parent, false);
208 view.setAlpha(1f);
209 view.bringToFront();
210 return new UserAdapterViewHolder(view);
211 }
212
213 @Override
214 public void onBindViewHolder(UserAdapterViewHolder holder, int position) {
215 UserRecord userRecord = mUsers.get(position);
Aarthi Balachandere3110e42018-04-30 18:16:26 -0700216 RoundedBitmapDrawable circleIcon = RoundedBitmapDrawableFactory.create(mRes,
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800217 getUserRecordIcon(userRecord));
Aarthi Balachandere3110e42018-04-30 18:16:26 -0700218 circleIcon.setCircular(true);
219 holder.mUserAvatarImageView.setImageDrawable(circleIcon);
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700220 holder.mUserNameTextView.setText(userRecord.mInfo.name);
Aarthi Balachandera7096002018-05-21 18:12:25 -0700221
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700222 holder.mView.setOnClickListener(v -> {
223 if (userRecord == null) {
224 return;
225 }
226
jovanak80b48592018-04-11 17:09:53 -0700227 if (userRecord.mIsStartGuestSession) {
Aarthi Balachandera7096002018-05-21 18:12:25 -0700228 notifyUserSelected(userRecord);
jovanak2469ca72018-09-19 16:30:04 -0700229 mCarUserManagerHelper.startGuestSession(mGuestName);
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700230 return;
231 }
232
Aarthi Balachander0e0e9212018-04-19 19:25:33 -0700233 // If the user wants to add a user, show dialog to confirm adding a user
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700234 if (userRecord.mIsAddUser) {
Aarthi Balachander0e0e9212018-04-19 19:25:33 -0700235 // Disable button so it cannot be clicked multiple times
236 mAddUserView = holder.mView;
237 mAddUserView.setEnabled(false);
Aarthi Balachandera7096002018-05-21 18:12:25 -0700238 mAddUserRecord = userRecord;
jovanaka17e1682018-08-09 15:02:13 -0700239
240 handleAddUserClicked();
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700241 return;
242 }
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700243 // If the user doesn't want to be a guest or add a user, switch to the user selected
Aarthi Balachandera7096002018-05-21 18:12:25 -0700244 notifyUserSelected(userRecord);
jovanak78cacc42018-08-06 18:38:03 -0700245 mCarUserManagerHelper.switchToUser(userRecord.mInfo);
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700246 });
247
248 }
249
jovanaka17e1682018-08-09 15:02:13 -0700250 private void handleAddUserClicked() {
251 if (mCarUserManagerHelper.isUserLimitReached()) {
252 mAddUserView.setEnabled(true);
253 showMaxUserLimitReachedDialog();
254 } else {
255 showConfirmAddUserDialog();
256 }
257 }
258
259 private void showMaxUserLimitReachedDialog() {
Heemin Seog50b43b72019-03-17 23:54:23 -0700260 AlertDialog maxUsersDialog = new Builder(mContext,
261 com.android.internal.R.style.Theme_DeviceDefault_Dialog_Alert)
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800262 .setTitle(R.string.user_limit_reached_title)
263 .setMessage(getResources().getQuantityString(
264 R.plurals.user_limit_reached_message,
265 mCarUserManagerHelper.getMaxSupportedRealUsers(),
266 mCarUserManagerHelper.getMaxSupportedRealUsers()))
267 .setPositiveButton(android.R.string.ok, null)
268 .create();
jovanaka17e1682018-08-09 15:02:13 -0700269 // Sets window flags for the SysUI dialog
270 SystemUIDialog.applyFlags(maxUsersDialog);
271 maxUsersDialog.show();
272 }
273
274 private void showConfirmAddUserDialog() {
275 String message = mRes.getString(R.string.user_add_user_message_setup)
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800276 .concat(System.getProperty("line.separator"))
277 .concat(System.getProperty("line.separator"))
278 .concat(mRes.getString(R.string.user_add_user_message_update));
jovanaka17e1682018-08-09 15:02:13 -0700279
Heemin Seog50b43b72019-03-17 23:54:23 -0700280 AlertDialog addUserDialog = new Builder(mContext,
281 com.android.internal.R.style.Theme_DeviceDefault_Dialog_Alert)
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800282 .setTitle(R.string.user_add_user_title)
283 .setMessage(message)
284 .setNegativeButton(android.R.string.cancel, this)
285 .setPositiveButton(android.R.string.ok, this)
286 .setOnCancelListener(this)
287 .create();
jovanaka17e1682018-08-09 15:02:13 -0700288 // Sets window flags for the SysUI dialog
289 SystemUIDialog.applyFlags(addUserDialog);
290 addUserDialog.show();
291 }
292
Aarthi Balachandera7096002018-05-21 18:12:25 -0700293 private void notifyUserSelected(UserRecord userRecord) {
294 // Notify the listener which user was selected
295 if (mUserSelectionListener != null) {
296 mUserSelectionListener.onUserSelected(userRecord);
297 }
298 }
299
jovanake4ce3cc2018-04-19 12:17:12 -0700300 private Bitmap getUserRecordIcon(UserRecord userRecord) {
301 if (userRecord.mIsStartGuestSession) {
jovanak78cacc42018-08-06 18:38:03 -0700302 return mCarUserManagerHelper.getGuestDefaultIcon();
jovanake4ce3cc2018-04-19 12:17:12 -0700303 }
304
305 if (userRecord.mIsAddUser) {
306 return UserIcons.convertToBitmap(mContext
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800307 .getDrawable(R.drawable.car_add_circle_round));
jovanake4ce3cc2018-04-19 12:17:12 -0700308 }
309
jovanak78cacc42018-08-06 18:38:03 -0700310 return mCarUserManagerHelper.getUserIcon(userRecord.mInfo);
jovanake4ce3cc2018-04-19 12:17:12 -0700311 }
312
Aarthi Balachander0e0e9212018-04-19 19:25:33 -0700313 @Override
314 public void onClick(DialogInterface dialog, int which) {
Aarthi Balachander0e0e9212018-04-19 19:25:33 -0700315 if (which == BUTTON_POSITIVE) {
Aarthi Balachandera7096002018-05-21 18:12:25 -0700316 notifyUserSelected(mAddUserRecord);
Aarthi Balachander0e0e9212018-04-19 19:25:33 -0700317 new AddNewUserTask().execute(mNewUserName);
Aarthi Balachandera7096002018-05-21 18:12:25 -0700318 } else if (which == BUTTON_NEGATIVE) {
319 // Enable the add button only if cancel
320 if (mAddUserView != null) {
321 mAddUserView.setEnabled(true);
322 }
Aarthi Balachander0e0e9212018-04-19 19:25:33 -0700323 }
324 }
325
Aarthi Balachanderc6d13662018-08-13 14:49:41 -0700326 @Override
327 public void onCancel(DialogInterface dialog) {
328 // Enable the add button again if user cancels dialog by clicking outside the dialog
329 if (mAddUserView != null) {
330 mAddUserView.setEnabled(true);
331 }
332 }
333
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700334 private class AddNewUserTask extends AsyncTask<String, Void, UserInfo> {
335
336 @Override
337 protected UserInfo doInBackground(String... userNames) {
jovanak78cacc42018-08-06 18:38:03 -0700338 return mCarUserManagerHelper.createNewNonAdminUser(userNames[0]);
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700339 }
340
341 @Override
342 protected void onPreExecute() {
343 }
344
345 @Override
346 protected void onPostExecute(UserInfo user) {
347 if (user != null) {
jovanak78cacc42018-08-06 18:38:03 -0700348 mCarUserManagerHelper.switchToUser(user);
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700349 }
350 }
351 }
352
353 @Override
354 public int getItemCount() {
355 return mUsers.size();
356 }
357
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700358 public class UserAdapterViewHolder extends RecyclerView.ViewHolder {
359
360 public ImageView mUserAvatarImageView;
361 public TextView mUserNameTextView;
362 public View mView;
363
364 public UserAdapterViewHolder(View view) {
365 super(view);
366 mView = view;
367 mUserAvatarImageView = (ImageView) view.findViewById(R.id.user_avatar);
368 mUserNameTextView = (TextView) view.findViewById(R.id.user_name);
369 }
370 }
371 }
372
373 /**
374 * Object wrapper class for the userInfo. Use it to distinguish if a profile is a
jovanak0535abc2018-04-10 15:14:50 -0700375 * guest profile, add user profile, or the foreground user.
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700376 */
377 public static final class UserRecord {
378
379 public final UserInfo mInfo;
jovanak80b48592018-04-11 17:09:53 -0700380 public final boolean mIsStartGuestSession;
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700381 public final boolean mIsAddUser;
jovanak0535abc2018-04-10 15:14:50 -0700382 public final boolean mIsForeground;
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700383
jovanak80b48592018-04-11 17:09:53 -0700384 public UserRecord(UserInfo userInfo, boolean isStartGuestSession, boolean isAddUser,
jovanak0535abc2018-04-10 15:14:50 -0700385 boolean isForeground) {
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700386 mInfo = userInfo;
jovanak80b48592018-04-11 17:09:53 -0700387 mIsStartGuestSession = isStartGuestSession;
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700388 mIsAddUser = isAddUser;
jovanak0535abc2018-04-10 15:14:50 -0700389 mIsForeground = isForeground;
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700390 }
391 }
392
393 /**
394 * Listener used to notify when a user has been selected
395 */
396 interface UserSelectionListener {
397
398 void onUserSelected(UserRecord record);
399 }
Heemin Seog0d5e0182019-03-13 13:49:24 -0700400
401 /**
402 * A {@link RecyclerView.ItemDecoration} that will add spacing between each item in the
403 * RecyclerView that it is added to.
404 */
405 private static class ItemSpacingDecoration extends RecyclerView.ItemDecoration {
406 private int mItemSpacing;
407
408 private ItemSpacingDecoration(int itemSpacing) {
409 mItemSpacing = itemSpacing;
410 }
411
412 @Override
413 public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
414 RecyclerView.State state) {
415 super.getItemOffsets(outRect, view, parent, state);
416 int position = parent.getChildAdapterPosition(view);
417
418 // Skip offset for last item except for GridLayoutManager.
419 if (position == state.getItemCount() - 1
420 && !(parent.getLayoutManager() instanceof GridLayoutManager)) {
421 return;
422 }
423
424 outRect.bottom = mItemSpacing;
425 }
426 }
Aarthi Balachanderd8bf2492018-03-30 11:15:59 -0700427}