1 /*
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
14  * limitations under the License
15  */
16 
17 package com.android.systemui.statusbar.car;
18 
19 import static android.content.DialogInterface.BUTTON_NEGATIVE;
20 import static android.content.DialogInterface.BUTTON_POSITIVE;
21 
22 import android.app.AlertDialog;
23 import android.app.AlertDialog.Builder;
24 import android.app.Dialog;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.content.pm.UserInfo;
28 import android.content.res.Resources;
29 import android.graphics.Bitmap;
30 import android.os.AsyncTask;
31 import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
32 import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
33 import android.support.v7.widget.RecyclerView;
34 import android.util.AttributeSet;
35 import android.view.LayoutInflater;
36 import android.view.View;
37 import android.view.ViewGroup;
38 import android.widget.ImageView;
39 import android.widget.TextView;
40 
41 import androidx.car.widget.PagedListView;
42 
43 import com.android.internal.util.UserIcons;
44 import com.android.settingslib.users.UserManagerHelper;
45 import com.android.systemui.R;
46 
47 import com.android.systemui.statusbar.phone.SystemUIDialog;
48 import java.util.ArrayList;
49 import java.util.List;
50 
51 /**
52  * Displays a GridLayout with icons for the users in the system to allow switching between users.
53  * One of the uses of this is for the lock screen in auto.
54  */
55 public class UserGridRecyclerView extends PagedListView implements
56         UserManagerHelper.OnUsersUpdateListener {
57     private UserSelectionListener mUserSelectionListener;
58     private UserAdapter mAdapter;
59     private UserManagerHelper mUserManagerHelper;
60     private Context mContext;
61 
UserGridRecyclerView(Context context, AttributeSet attrs)62     public UserGridRecyclerView(Context context, AttributeSet attrs) {
63         super(context, attrs);
64         mContext = context;
65         mUserManagerHelper = new UserManagerHelper(mContext);
66     }
67 
68     /**
69      * Register listener for any update to the users
70      */
71     @Override
onFinishInflate()72     public void onFinishInflate() {
73         super.onFinishInflate();
74         mUserManagerHelper.registerOnUsersUpdateListener(this);
75     }
76 
77     /**
78      * Unregisters listener checking for any change to the users
79      */
80     @Override
onDetachedFromWindow()81     public void onDetachedFromWindow() {
82         super.onDetachedFromWindow();
83         mUserManagerHelper.unregisterOnUsersUpdateListener();
84     }
85 
86     /**
87      * Initializes the adapter that populates the grid layout
88      *
89      * @return the adapter
90      */
buildAdapter()91     public void buildAdapter() {
92         List<UserRecord> userRecords = createUserRecords(mUserManagerHelper
93                 .getAllUsers());
94         mAdapter = new UserAdapter(mContext, userRecords);
95         super.setAdapter(mAdapter);
96     }
97 
createUserRecords(List<UserInfo> userInfoList)98     private List<UserRecord> createUserRecords(List<UserInfo> userInfoList) {
99         List<UserRecord> userRecords = new ArrayList<>();
100         for (UserInfo userInfo : userInfoList) {
101             if (userInfo.isGuest()) {
102                 // Don't display guests in the switcher.
103                 continue;
104             }
105             boolean isForeground =
106                 mUserManagerHelper.getForegroundUserId() == userInfo.id;
107             UserRecord record = new UserRecord(userInfo, false /* isStartGuestSession */,
108                     false /* isAddUser */, isForeground);
109             userRecords.add(record);
110         }
111 
112         // Add guest user record if the foreground user is not a guest
113         if (!mUserManagerHelper.foregroundUserIsGuestUser()) {
114             userRecords.add(addGuestUserRecord());
115         }
116 
117         // Add add user record if the foreground user can add users
118         if (mUserManagerHelper.foregroundUserCanAddUsers()) {
119             userRecords.add(addUserRecord());
120         }
121 
122         return userRecords;
123     }
124 
125     /**
126      * Create guest user record
127      */
addGuestUserRecord()128     private UserRecord addGuestUserRecord() {
129         UserInfo userInfo = new UserInfo();
130         userInfo.name = mContext.getString(R.string.car_guest);
131         return new UserRecord(userInfo, true /* isStartGuestSession */,
132                 false /* isAddUser */, false /* isForeground */);
133     }
134 
135     /**
136      * Create add user record
137      */
addUserRecord()138     private UserRecord addUserRecord() {
139         UserInfo userInfo = new UserInfo();
140         userInfo.name = mContext.getString(R.string.car_add_user);
141         return new UserRecord(userInfo, false /* isStartGuestSession */,
142                 true /* isAddUser */, false /* isForeground */);
143     }
144 
setUserSelectionListener(UserSelectionListener userSelectionListener)145     public void setUserSelectionListener(UserSelectionListener userSelectionListener) {
146         mUserSelectionListener = userSelectionListener;
147     }
148 
149     @Override
onUsersUpdate()150     public void onUsersUpdate() {
151         mAdapter.clearUsers();
152         mAdapter.updateUsers(createUserRecords(mUserManagerHelper.getAllUsers()));
153         mAdapter.notifyDataSetChanged();
154     }
155 
156     /**
157      * Adapter to populate the grid layout with the available user profiles
158      */
159     public final class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserAdapterViewHolder>
160             implements Dialog.OnClickListener {
161 
162         private final Context mContext;
163         private List<UserRecord> mUsers;
164         private final Resources mRes;
165         private final String mGuestName;
166         private final String mNewUserName;
167         private AlertDialog mDialog;
168         // View that holds the add user button.  Used to enable/disable the view
169         private View mAddUserView;
170         // User record for the add user.  Need to call notifyUserSelected only if the user
171         // confirms adding a user
172         private UserRecord mAddUserRecord;
173 
UserAdapter(Context context, List<UserRecord> users)174         public UserAdapter(Context context, List<UserRecord> users) {
175             mRes = context.getResources();
176             mContext = context;
177             updateUsers(users);
178             mGuestName = mRes.getString(R.string.car_guest);
179             mNewUserName = mRes.getString(R.string.car_new_user);
180         }
181 
clearUsers()182         public void clearUsers() {
183             mUsers.clear();
184         }
185 
updateUsers(List<UserRecord> users)186         public void updateUsers(List<UserRecord> users) {
187             mUsers = users;
188         }
189 
190         @Override
onCreateViewHolder(ViewGroup parent, int viewType)191         public UserAdapterViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
192             View view = LayoutInflater.from(mContext)
193                     .inflate(R.layout.car_fullscreen_user_pod, parent, false);
194             view.setAlpha(1f);
195             view.bringToFront();
196             return new UserAdapterViewHolder(view);
197         }
198 
199         @Override
onBindViewHolder(UserAdapterViewHolder holder, int position)200         public void onBindViewHolder(UserAdapterViewHolder holder, int position) {
201             UserRecord userRecord = mUsers.get(position);
202             RoundedBitmapDrawable circleIcon = RoundedBitmapDrawableFactory.create(mRes,
203                 getUserRecordIcon(userRecord));
204             circleIcon.setCircular(true);
205             holder.mUserAvatarImageView.setImageDrawable(circleIcon);
206             holder.mUserNameTextView.setText(userRecord.mInfo.name);
207 
208             holder.mView.setOnClickListener(v -> {
209                 if (userRecord == null) {
210                     return;
211                 }
212 
213 
214                 // If the user selects Guest, start the guest session.
215                 if (userRecord.mIsStartGuestSession) {
216                     notifyUserSelected(userRecord);
217                     mUserManagerHelper.startNewGuestSession(mGuestName);
218                     return;
219                 }
220 
221                 // If the user wants to add a user, show dialog to confirm adding a user
222                 if (userRecord.mIsAddUser) {
223                     // Disable button so it cannot be clicked multiple times
224                     mAddUserView = holder.mView;
225                     mAddUserView.setEnabled(false);
226 
227                     String message = mRes.getString(R.string.user_add_user_message_setup)
228                         .concat(System.getProperty("line.separator"))
229                         .concat(System.getProperty("line.separator"))
230                         .concat(mRes.getString(R.string.user_add_user_message_update));
231 
232                     mAddUserRecord = userRecord;
233                     mDialog = new Builder(mContext, R.style.Theme_Car_Dark_Dialog_Alert)
234                         .setTitle(R.string.user_add_user_title)
235                         .setMessage(message)
236                         .setNegativeButton(android.R.string.cancel, this)
237                         .setPositiveButton(android.R.string.ok, this)
238                         .create();
239                     // Sets window flags for the SysUI dialog
240                     SystemUIDialog.applyFlags(mDialog);
241                     mDialog.show();
242                     return;
243                 }
244                 // If the user doesn't want to be a guest or add a user, switch to the user selected
245                 notifyUserSelected(userRecord);
246                 mUserManagerHelper.switchToUser(userRecord.mInfo);
247             });
248 
249         }
250 
notifyUserSelected(UserRecord userRecord)251         private void notifyUserSelected(UserRecord userRecord) {
252             // Notify the listener which user was selected
253             if (mUserSelectionListener != null) {
254                 mUserSelectionListener.onUserSelected(userRecord);
255             }
256         }
257 
getUserRecordIcon(UserRecord userRecord)258         private Bitmap getUserRecordIcon(UserRecord userRecord) {
259             if (userRecord.mIsStartGuestSession) {
260                 return mUserManagerHelper.getGuestDefaultIcon();
261             }
262 
263             if (userRecord.mIsAddUser) {
264                 return UserIcons.convertToBitmap(mContext
265                     .getDrawable(R.drawable.car_add_circle_round));
266             }
267 
268             return mUserManagerHelper.getUserIcon(userRecord.mInfo);
269         }
270 
271         @Override
onClick(DialogInterface dialog, int which)272         public void onClick(DialogInterface dialog, int which) {
273             if (which == BUTTON_POSITIVE) {
274                 notifyUserSelected(mAddUserRecord);
275                 new AddNewUserTask().execute(mNewUserName);
276             } else if (which == BUTTON_NEGATIVE) {
277                 // Enable the add button only if cancel
278                 if (mAddUserView != null) {
279                     mAddUserView.setEnabled(true);
280                 }
281             }
282         }
283 
284         private class AddNewUserTask extends AsyncTask<String, Void, UserInfo> {
285 
286             @Override
doInBackground(String... userNames)287             protected UserInfo doInBackground(String... userNames) {
288                 return mUserManagerHelper.createNewUser(userNames[0]);
289             }
290 
291             @Override
onPreExecute()292             protected void onPreExecute() {
293             }
294 
295             @Override
onPostExecute(UserInfo user)296             protected void onPostExecute(UserInfo user) {
297                 if (user != null) {
298                     mUserManagerHelper.switchToUser(user);
299                 }
300             }
301         }
302 
303         @Override
getItemCount()304         public int getItemCount() {
305             return mUsers.size();
306         }
307 
308         public class UserAdapterViewHolder extends RecyclerView.ViewHolder {
309 
310             public ImageView mUserAvatarImageView;
311             public TextView mUserNameTextView;
312             public View mView;
313 
UserAdapterViewHolder(View view)314             public UserAdapterViewHolder(View view) {
315                 super(view);
316                 mView = view;
317                 mUserAvatarImageView = (ImageView) view.findViewById(R.id.user_avatar);
318                 mUserNameTextView = (TextView) view.findViewById(R.id.user_name);
319             }
320         }
321     }
322 
323     /**
324      * Object wrapper class for the userInfo.  Use it to distinguish if a profile is a
325      * guest profile, add user profile, or the foreground user.
326      */
327     public static final class UserRecord {
328 
329         public final UserInfo mInfo;
330         public final boolean mIsStartGuestSession;
331         public final boolean mIsAddUser;
332         public final boolean mIsForeground;
333 
UserRecord(UserInfo userInfo, boolean isStartGuestSession, boolean isAddUser, boolean isForeground)334         public UserRecord(UserInfo userInfo, boolean isStartGuestSession, boolean isAddUser,
335                 boolean isForeground) {
336             mInfo = userInfo;
337             mIsStartGuestSession = isStartGuestSession;
338             mIsAddUser = isAddUser;
339             mIsForeground = isForeground;
340         }
341     }
342 
343     /**
344      * Listener used to notify when a user has been selected
345      */
346     interface UserSelectionListener {
347 
onUserSelected(UserRecord record)348         void onUserSelected(UserRecord record);
349     }
350 }
351