1 /*
2  * Copyright (C) 2020 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 package com.google.android.car.kitchensink.users;
17 
18 import android.annotation.Nullable;
19 import android.app.ActivityManager;
20 import android.app.ActivityOptions;
21 import android.app.IActivityManager;
22 import android.car.Car;
23 import android.car.CarOccupantZoneManager;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.pm.UserInfo;
28 import android.graphics.Color;
29 import android.hardware.display.DisplayManager;
30 import android.os.Bundle;
31 import android.os.Process;
32 import android.os.RemoteException;
33 import android.os.UserHandle;
34 import android.os.UserManager;
35 import android.util.Log;
36 import android.view.Display;
37 import android.view.DisplayAddress;
38 import android.view.LayoutInflater;
39 import android.view.View;
40 import android.view.ViewGroup;
41 import android.widget.ArrayAdapter;
42 import android.widget.Button;
43 import android.widget.Spinner;
44 import android.widget.TextView;
45 
46 import androidx.fragment.app.Fragment;
47 
48 import com.google.android.car.kitchensink.KitchenSinkActivity;
49 import com.google.android.car.kitchensink.R;
50 
51 import java.util.ArrayList;
52 import java.util.List;
53 
54 public class ProfileUserFragment extends Fragment {
55 
56     private static final String TAG = ProfileUserFragment.class.getSimpleName();
57 
58     private static final int ERROR_MESSAGE = 0;
59     private static final int WARN_MESSAGE = 1;
60     private static final int INFO_MESSAGE = 2;
61 
62     private SpinnerWrapper mUsersSpinner;
63     private SpinnerWrapper mZonesSpinner;
64     private SpinnerWrapper mDisplaysSpinner;
65     private SpinnerWrapper mAppsSpinner;
66 
67     private Button mCreateRestrictedUserButton;
68     private Button mCreateManagedUserButton;
69     private Button mRemoveUserButton;
70     private Button mStartUserButton;
71     private Button mStopUserButton;
72     private Button mAssignUserToZoneButton;
73     private Button mLaunchAppForZoneButton;
74     private Button mLaunchAppForDisplayAndUserButton;
75 
76     private TextView mUserIdText;
77     private TextView mZoneInfoText;
78     private TextView mUserStateText;
79     private TextView mErrorMessageText;
80 
81     private UserManager mUserManager;
82     private DisplayManager mDisplayManager;
83     private CarOccupantZoneManager mZoneManager;
84 
85 
86     @Nullable
87     @Override
onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)88     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
89             @Nullable Bundle savedInstanceState) {
90         return inflater.inflate(R.layout.profile_user, container, false);
91     }
92 
onViewCreated(View view, Bundle savedInstanceState)93     public void onViewCreated(View view, Bundle savedInstanceState) {
94         mUserManager = getContext().getSystemService(UserManager.class);
95         mDisplayManager = getContext().getSystemService(DisplayManager.class);
96         Car car = ((KitchenSinkActivity) getHost()).getCar();
97         mZoneManager = (CarOccupantZoneManager) car.getCarManager(Car.CAR_OCCUPANT_ZONE_SERVICE);
98 
99         mUserIdText = view.findViewById(R.id.profile_textView_state);
100         mZoneInfoText = view.findViewById(R.id.profile_textView_zoneinfo);
101         mUserStateText = view.findViewById(R.id.profile_textView_userstate);
102         updateTextInfo();
103 
104         mUsersSpinner = SpinnerWrapper.create(getContext(),
105                 view.findViewById(R.id.profile_spinner_users), getUsers());
106         mZonesSpinner = SpinnerWrapper.create(getContext(),
107                 view.findViewById(R.id.profile_spinner_zones), getZones());
108         mDisplaysSpinner = SpinnerWrapper.create(getContext(),
109                 view.findViewById(R.id.profile_spinner_displays), getDisplays());
110         mAppsSpinner = SpinnerWrapper.create(getContext(),
111                 view.findViewById(R.id.profile_spinner_apps), getApps());
112 
113         mCreateRestrictedUserButton = view.findViewById(R.id.profile_button_create_restricted_user);
114         mCreateRestrictedUserButton.setOnClickListener(v -> {
115             createUser(/* restricted= */ true);
116         });
117         mCreateManagedUserButton = view.findViewById(R.id.profile_button_create_managed_user);
118         mCreateManagedUserButton.setOnClickListener(v -> {
119             createUser(/* restricted= */ false);
120         });
121         mRemoveUserButton = view.findViewById(R.id.profile_button_remove_user);
122         mRemoveUserButton.setOnClickListener(v -> {
123             removeUser();
124         });
125         mStartUserButton = view.findViewById(R.id.profile_button_start_user);
126         mStartUserButton.setOnClickListener(v -> {
127             startUser();
128         });
129         mStopUserButton = view.findViewById(R.id.profile_button_stop_user);
130         mStopUserButton.setOnClickListener(v -> {
131             stopUser();
132         });
133         mAssignUserToZoneButton = view.findViewById(R.id.profile_button_assign_user_to_zone);
134         mAssignUserToZoneButton.setOnClickListener(v -> {
135             assignUserToZone();
136         });
137         mLaunchAppForZoneButton = view.findViewById(R.id.profile_button_launch_app_for_zone);
138         mLaunchAppForZoneButton.setOnClickListener(v -> {
139             launchAppForZone();
140         });
141         mLaunchAppForDisplayAndUserButton = view.findViewById(
142                 R.id.profile_button_launch_app_for_display);
143         mLaunchAppForDisplayAndUserButton.setOnClickListener(v -> {
144             launchAppForDisplayAndUser();
145         });
146 
147         mErrorMessageText = view.findViewById(R.id.status_message_text_view);
148     }
149 
updateTextInfo()150     private void updateTextInfo() {
151         int currentUserId = ActivityManager.getCurrentUser();
152         int myUserId = UserHandle.myUserId();
153         mUserIdText.setText("Current userId:" + currentUserId + " myUserId:" + myUserId);
154         StringBuilder zoneStatebuilder = new StringBuilder();
155         zoneStatebuilder.append("Zone-User-Displays:");
156         List<CarOccupantZoneManager.OccupantZoneInfo> zonelist = mZoneManager.getAllOccupantZones();
157         for (CarOccupantZoneManager.OccupantZoneInfo zone : zonelist) {
158             zoneStatebuilder.append(zone.zoneId);
159             zoneStatebuilder.append("-");
160             zoneStatebuilder.append(mZoneManager.getUserForOccupant(zone));
161             zoneStatebuilder.append("-");
162             List<Display> displays = mZoneManager.getAllDisplaysForOccupant(zone);
163             for (Display display : displays) {
164                 zoneStatebuilder.append(display.getDisplayId());
165                 zoneStatebuilder.append(",");
166             }
167             zoneStatebuilder.append(":");
168         }
169         mZoneInfoText.setText(zoneStatebuilder.toString());
170         StringBuilder userStateBuilder = new StringBuilder();
171         userStateBuilder.append("UserId-state;");
172         int[] profileUsers = mUserManager.getEnabledProfileIds(currentUserId);
173         for (int profileUser : profileUsers) {
174             userStateBuilder.append(profileUser);
175             userStateBuilder.append("-");
176             if (mUserManager.isUserRunning(profileUser)) {
177                 userStateBuilder.append("R:");
178             } else {
179                 userStateBuilder.append("S:");
180             }
181         }
182         mUserStateText.setText(userStateBuilder.toString());
183     }
184 
createUser(boolean restricted)185     private void createUser(boolean restricted) {
186         try {
187             UserInfo user;
188             if (restricted) {
189                 user = mUserManager.createRestrictedProfile("RestrictedProfile");
190             } else {
191                 user = mUserManager.createProfileForUser("ManagedProfile",
192                         UserManager.USER_TYPE_PROFILE_MANAGED, /* flags= */ 0,
193                         ActivityManager.getCurrentUser());
194             }
195             setMessage(INFO_MESSAGE, "Created User " + user);
196             mUsersSpinner.updateEntries(getUsers());
197             updateTextInfo();
198         } catch (Exception e) {
199             setMessage(ERROR_MESSAGE, e);
200         }
201     }
202 
removeUser()203     private void removeUser() {
204         int userToRemove = getSelectedUser();
205         if (userToRemove == UserHandle.USER_NULL) {
206             setMessage(INFO_MESSAGE, "Cannot remove null user");
207             return;
208         }
209         int currentUser = ActivityManager.getCurrentUser();
210         if (userToRemove == currentUser) {
211             setMessage(WARN_MESSAGE, "Cannot remove current user");
212             return;
213         }
214         Log.i(TAG, "removing user:" + userToRemove);
215         try {
216             mUserManager.removeUser(userToRemove);
217             mUsersSpinner.updateEntries(getUsers());
218             updateTextInfo();
219             setMessage(INFO_MESSAGE, "Removed user " + userToRemove);
220         } catch (Exception e) {
221             setMessage(ERROR_MESSAGE, e);
222         }
223     }
224 
stopUser()225     private void stopUser() {
226         int userToUpdate = getSelectedUser();
227         if (!canChangeUser(userToUpdate)) {
228             return;
229         }
230 
231         if (!mUserManager.isUserRunning(userToUpdate)) {
232             setMessage(WARN_MESSAGE, "User " + userToUpdate + " is already stopped");
233             return;
234         }
235         IActivityManager am = ActivityManager.getService();
236         Log.i(TAG, "stop user:" + userToUpdate);
237         try {
238             am.stopUser(userToUpdate, /* force= */ false, /* callback= */ null);
239         } catch (RemoteException e) {
240             setMessage(WARN_MESSAGE, "Cannot stop user", e);
241             return;
242         }
243         setMessage(INFO_MESSAGE, "Stopped user " + userToUpdate);
244         updateTextInfo();
245     }
246 
startUser()247     private void startUser() {
248         int userToUpdate = getSelectedUser();
249         if (!canChangeUser(userToUpdate)) {
250             return;
251         }
252 
253         if (mUserManager.isUserRunning(userToUpdate)) {
254             setMessage(WARN_MESSAGE, "User " + userToUpdate + " is already running");
255             return;
256         }
257         IActivityManager am = ActivityManager.getService();
258         Log.i(TAG, "start user:" + userToUpdate);
259         try {
260             am.startUserInBackground(userToUpdate);
261         } catch (RemoteException e) {
262             setMessage(WARN_MESSAGE, "Cannot start user", e);
263             return;
264         }
265         setMessage(INFO_MESSAGE, "Started user " + userToUpdate);
266         updateTextInfo();
267     }
268 
canChangeUser(int userToUpdate)269     private boolean canChangeUser(int userToUpdate) {
270         if (userToUpdate == UserHandle.USER_NULL) {
271             return false;
272         }
273         int currentUser = ActivityManager.getCurrentUser();
274         if (userToUpdate == currentUser) {
275             setMessage(WARN_MESSAGE, "Can not change current user");
276             return false;
277         }
278         return true;
279     }
280 
assignUserToZone()281     private void assignUserToZone() {
282         int userId = getSelectedUser();
283         if (userId == UserHandle.USER_NULL) {
284             return;
285         }
286         Integer zoneId = getSelectedZone();
287         if (zoneId == null) {
288             return;
289         }
290         Log.i(TAG, "assigning user:" + userId + " to zone:" + zoneId);
291         boolean assignUserToZoneResults;
292         try {
293             assignUserToZoneResults =
294                     mZoneManager.assignProfileUserToOccupantZone(getZoneInfoForId(zoneId), userId);
295         } catch (IllegalArgumentException e) {
296             setMessage(ERROR_MESSAGE, e.getMessage());
297             return;
298         }
299         if (!assignUserToZoneResults) {
300             Log.e(TAG, "Assignment failed");
301             setMessage(ERROR_MESSAGE, "Failed to assign user " + userId + " to zone "
302                     + zoneId);
303             return;
304         }
305         setMessage(INFO_MESSAGE, "Assigned user " + userId + " to zone " + zoneId);
306         updateTextInfo();
307     }
308 
setMessage(int messageType, String title, Exception e)309     private void setMessage(int messageType, String title, Exception e) {
310         StringBuilder messageTextBuilder = new StringBuilder();
311         messageTextBuilder.append(title);
312         messageTextBuilder.append(": ");
313         messageTextBuilder.append(e.getMessage());
314         setMessage(messageType, messageTextBuilder.toString());
315     }
316 
setMessage(int messageType, Exception e)317     private void setMessage(int messageType, Exception e) {
318         setMessage(messageType, e.getMessage());
319     }
320 
setMessage(int messageType, String message)321     private void setMessage(int messageType, String message) {
322         int textColor;
323         switch (messageType) {
324             case ERROR_MESSAGE:
325                 Log.e(TAG, message);
326                 textColor = Color.RED;
327                 break;
328             case WARN_MESSAGE:
329                 Log.w(TAG, message);
330                 textColor = Color.GREEN;
331                 break;
332             case INFO_MESSAGE:
333             default:
334                 Log.i(TAG, message);
335                 textColor = Color.WHITE;
336         }
337         mErrorMessageText.setTextColor(textColor);
338         mErrorMessageText.setText(message);
339     }
340 
launchAppForZone()341     private void launchAppForZone() {
342         Intent intent = getSelectedApp();
343         if (intent == null) {
344             return;
345         }
346         Integer zoneId = getSelectedZone();
347         if (zoneId == null) {
348             return;
349         }
350         CarOccupantZoneManager.OccupantZoneInfo zoneInfo = getZoneInfoForId(zoneId);
351         if (zoneInfo == null) {
352             Log.e(TAG, "launchAppForZone, invalid zoneId:" + zoneId);
353             return;
354         }
355         int assignedUserId = mZoneManager.getUserForOccupant(zoneInfo);
356         if (assignedUserId == UserHandle.USER_NULL) {
357             Log.e(TAG, "launchAppForZone, invalid user for zone:" + zoneId);
358             return;
359         }
360         Log.i(TAG, "Launching Intent:" + intent + " for user:" + assignedUserId);
361         getContext().startActivityAsUser(intent, UserHandle.of(assignedUserId));
362     }
363 
launchAppForDisplayAndUser()364     private void launchAppForDisplayAndUser() {
365         Intent intent = getSelectedApp();
366         if (intent == null) {
367             return;
368         }
369         int displayId = getSelectedDisplay();
370         if (displayId == Display.INVALID_DISPLAY) {
371             return;
372         }
373         int userId = getSelectedUser();
374         if (userId == UserHandle.USER_NULL) {
375             return;
376         }
377         Log.i(TAG, "Launching Intent:" + intent + " for user:" + userId
378                 + " to display:" + displayId);
379         Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle();
380         getContext().startActivityAsUser(intent, bundle, UserHandle.of(userId));
381     }
382 
383     @Nullable
getZoneInfoForId(int zoneId)384     private CarOccupantZoneManager.OccupantZoneInfo getZoneInfoForId(int zoneId) {
385         List<CarOccupantZoneManager.OccupantZoneInfo> zonelist = mZoneManager.getAllOccupantZones();
386         for (CarOccupantZoneManager.OccupantZoneInfo zone : zonelist) {
387             if (zone.zoneId == zoneId) {
388                 return zone;
389             }
390         }
391         return null;
392     }
393 
394     @Nullable
getSelectedApp()395     private Intent getSelectedApp() {
396         String appStr = (String) mAppsSpinner.getSelectedEntry();
397         if (appStr == null) {
398             Log.w(TAG, "getSelectedApp, no app selected", new RuntimeException());
399             return null;
400         }
401         Intent intent = new Intent();
402         intent.setComponent(ComponentName.unflattenFromString(appStr));
403         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
404         return intent;
405     }
406 
407     @Nullable
getSelectedZone()408     private Integer getSelectedZone() {
409         String zoneStr = mZonesSpinner.getSelectedEntry();
410         if (zoneStr == null) {
411             Log.w(TAG, "getSelectedZone, no zone selected", new RuntimeException());
412             return null;
413         }
414         return Integer.valueOf(zoneStr.split(",")[0]);
415     }
416 
417     @Nullable
getSelectedDisplay()418     private int getSelectedDisplay() {
419         String displayStr = mDisplaysSpinner.getSelectedEntry();
420         if (displayStr == null) {
421             Log.w(TAG, "getSelectedDisplay, no display selected", new RuntimeException());
422             return Display.INVALID_DISPLAY;
423         }
424         return Integer.parseInt(displayStr.split(",")[0]);
425     }
426 
getSelectedUser()427     private int getSelectedUser() {
428         String userStr = mUsersSpinner.getSelectedEntry();
429         if (userStr == null) {
430             Log.w(TAG, "getSelectedUser, user not selected", new RuntimeException());
431             return UserHandle.USER_NULL;
432         }
433         return Integer.parseInt(userStr.split(",")[0]);
434     }
435 
436     // format: id,[CURRENT|PROFILE]
getUsers()437     private ArrayList<String> getUsers() {
438         ArrayList<String> users = new ArrayList<>();
439         int currentUser = ActivityManager.getCurrentUser();
440         users.add(Integer.toString(currentUser) + ",CURRENT");
441         int[] profileUsers = mUserManager.getEnabledProfileIds(currentUser);
442         for (int profileUser : profileUsers) {
443             if (profileUser == currentUser) {
444                 continue;
445             }
446             users.add(Integer.toString(profileUser) + ",PROFILE");
447         }
448         return users;
449     }
450 
451     // format: displayId,[P,]?,address]
getDisplays()452     private ArrayList<String> getDisplays() {
453         ArrayList<String> displays = new ArrayList<>();
454         Display[] disps = mDisplayManager.getDisplays();
455         int uidSelf = Process.myUid();
456         for (Display disp : disps) {
457             if (!disp.hasAccess(uidSelf)) {
458                 continue;
459             }
460             StringBuilder builder = new StringBuilder();
461             int displayId = disp.getDisplayId();
462             builder.append(displayId);
463             builder.append(",");
464             DisplayAddress address = disp.getAddress();
465             if (address instanceof  DisplayAddress.Physical) {
466                 builder.append("P,");
467             }
468             builder.append(address);
469             displays.add(builder.toString());
470         }
471         return displays;
472     }
473 
474     // format: zoneId,[D|F|R]
getZones()475     private ArrayList<String> getZones() {
476         ArrayList<String> zones = new ArrayList<>();
477         List<CarOccupantZoneManager.OccupantZoneInfo> zonelist = mZoneManager.getAllOccupantZones();
478         for (CarOccupantZoneManager.OccupantZoneInfo zone : zonelist) {
479             StringBuilder builder = new StringBuilder();
480             builder.append(zone.zoneId);
481             builder.append(",");
482             if (zone.occupantType == CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER) {
483                 builder.append("D");
484             } else if (zone.occupantType == CarOccupantZoneManager.OCCUPANT_TYPE_FRONT_PASSENGER) {
485                 builder.append("F");
486             } else {
487                 builder.append("R");
488             }
489             zones.add(builder.toString());
490         }
491         return zones;
492     }
493 
getApps()494     private ArrayList<String> getApps() {
495         ArrayList<String> apps = new ArrayList<>();
496         apps.add("com.google.android.car.kitchensink/.KitchenSinkActivity");
497         apps.add("com.android.car.multidisplay/.launcher.LauncherActivity");
498         apps.add("com.google.android.car.multidisplaytest/.MDTest");
499         return apps;
500     }
501 
502     private static class SpinnerWrapper {
503         private final Spinner mSpinner;
504         private final ArrayList<String> mEntries;
505         private final ArrayAdapter<String> mAdapter;
506 
create(Context context, Spinner spinner, ArrayList<String> entries)507         private static SpinnerWrapper create(Context context, Spinner spinner,
508                 ArrayList<String> entries) {
509             SpinnerWrapper wrapper = new SpinnerWrapper(context, spinner, entries);
510             wrapper.init();
511             return wrapper;
512         }
513 
SpinnerWrapper(Context context, Spinner spinner, ArrayList<String> entries)514         private SpinnerWrapper(Context context, Spinner spinner, ArrayList<String> entries) {
515             mSpinner = spinner;
516             mEntries = new ArrayList<>(entries);
517             mAdapter = new ArrayAdapter<String>(context, android.R.layout.simple_spinner_item,
518                     mEntries);
519         }
520 
init()521         private void init() {
522             mAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
523             mSpinner.setAdapter(mAdapter);
524         }
525 
updateEntries(ArrayList<String> entries)526         private void updateEntries(ArrayList<String> entries) {
527             mEntries.clear();
528             mEntries.addAll(entries);
529             mAdapter.notifyDataSetChanged();
530         }
531 
532         @Nullable
getSelectedEntry()533         private String getSelectedEntry() {
534             return (String) mSpinner.getSelectedItem();
535         }
536     }
537 }
538