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.stopUserWithCallback(userToUpdate, /* 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