/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.car; import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationSetValue.ASSOCIATE_CURRENT_USER; import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationSetValue.DISASSOCIATE_ALL_USERS; import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationSetValue.DISASSOCIATE_CURRENT_USER; import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType.CUSTOM_1; import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType.CUSTOM_2; import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType.CUSTOM_3; import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType.CUSTOM_4; import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType.KEY_FOB; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.UiModeManager; import android.car.Car; import android.car.input.CarInputManager; import android.car.input.RotaryEvent; import android.car.user.CarUserManager; import android.car.user.UserCreationResult; import android.car.user.UserIdentificationAssociationResponse; import android.car.user.UserRemovalResult; import android.car.user.UserSwitchResult; import android.car.userlib.HalCallback; import android.car.userlib.UserHalHelper; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.hardware.automotive.vehicle.V2_0.CreateUserRequest; import android.hardware.automotive.vehicle.V2_0.CreateUserStatus; import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse; import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction; import android.hardware.automotive.vehicle.V2_0.RemoveUserRequest; import android.hardware.automotive.vehicle.V2_0.SwitchUserMessageType; import android.hardware.automotive.vehicle.V2_0.SwitchUserRequest; import android.hardware.automotive.vehicle.V2_0.SwitchUserStatus; import android.hardware.automotive.vehicle.V2_0.UserFlags; import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociation; import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationSetValue; import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType; import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationValue; import android.hardware.automotive.vehicle.V2_0.UserIdentificationGetRequest; import android.hardware.automotive.vehicle.V2_0.UserIdentificationResponse; import android.hardware.automotive.vehicle.V2_0.UserIdentificationSetAssociation; import android.hardware.automotive.vehicle.V2_0.UserIdentificationSetRequest; import android.hardware.automotive.vehicle.V2_0.UserInfo; import android.hardware.automotive.vehicle.V2_0.UsersInfo; import android.hardware.automotive.vehicle.V2_0.VehicleArea; import android.os.Binder; import android.os.Build; import android.os.Process; import android.os.ShellCommand; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; import android.view.KeyEvent; import com.android.car.am.FixedActivityService; import com.android.car.audio.CarAudioService; import com.android.car.garagemode.GarageModeService; import com.android.car.hal.InputHalService; import com.android.car.hal.UserHalService; import com.android.car.hal.VehicleHal; import com.android.car.pm.CarPackageManagerService; import com.android.car.systeminterface.SystemInterface; import com.android.car.trust.CarTrustedDeviceService; import com.android.car.user.CarUserService; import com.android.internal.infra.AndroidFuture; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; final class CarShellCommand extends ShellCommand { private static final String NO_INITIAL_USER = "N/A"; private static final String TAG = CarShellCommand.class.getSimpleName(); private static final boolean VERBOSE = false; private static final String COMMAND_HELP = "-h"; private static final String COMMAND_DAY_NIGHT_MODE = "day-night-mode"; private static final String COMMAND_INJECT_VHAL_EVENT = "inject-vhal-event"; private static final String COMMAND_INJECT_ERROR_EVENT = "inject-error-event"; private static final String COMMAND_ENABLE_UXR = "enable-uxr"; private static final String COMMAND_GARAGE_MODE = "garage-mode"; private static final String COMMAND_GET_DO_ACTIVITIES = "get-do-activities"; private static final String COMMAND_GET_CARPROPERTYCONFIG = "get-carpropertyconfig"; private static final String COMMAND_GET_PROPERTY_VALUE = "get-property-value"; private static final String COMMAND_PROJECTION_AP_TETHERING = "projection-tethering"; private static final String COMMAND_PROJECTION_UI_MODE = "projection-ui-mode"; private static final String COMMAND_RESUME = "resume"; private static final String COMMAND_SUSPEND = "suspend"; private static final String COMMAND_ENABLE_TRUSTED_DEVICE = "enable-trusted-device"; private static final String COMMAND_REMOVE_TRUSTED_DEVICES = "remove-trusted-devices"; private static final String COMMAND_SET_UID_TO_ZONE = "set-audio-zone-for-uid"; private static final String COMMAND_START_FIXED_ACTIVITY_MODE = "start-fixed-activity-mode"; private static final String COMMAND_STOP_FIXED_ACTIVITY_MODE = "stop-fixed-activity-mode"; private static final String COMMAND_ENABLE_FEATURE = "enable-feature"; private static final String COMMAND_DISABLE_FEATURE = "disable-feature"; private static final String COMMAND_INJECT_KEY = "inject-key"; private static final String COMMAND_INJECT_ROTARY = "inject-rotary"; private static final String COMMAND_GET_INITIAL_USER_INFO = "get-initial-user-info"; private static final String COMMAND_SWITCH_USER = "switch-user"; private static final String COMMAND_REMOVE_USER = "remove-user"; private static final String COMMAND_CREATE_USER = "create-user"; private static final String COMMAND_GET_INITIAL_USER = "get-initial-user"; private static final String COMMAND_SET_USER_ID_TO_OCCUPANT_ZONE = "set-occupant-zone-for-user"; private static final String COMMAND_RESET_USER_ID_IN_OCCUPANT_ZONE = "reset-user-in-occupant-zone"; private static final String COMMAND_GET_USER_AUTH_ASSOCIATION = "get-user-auth-association"; private static final String COMMAND_SET_USER_AUTH_ASSOCIATION = "set-user-auth-association"; // Whitelist of commands allowed in user build. All these command should be protected with // a permission. K: command, V: required permission. // Only commands with permission already granted to shell user should be allowed. // Commands that can affect safety should be never allowed in user build. private static final ArrayMap USER_BUILD_COMMAND_TO_PERMISSION_MAP; static { USER_BUILD_COMMAND_TO_PERMISSION_MAP = new ArrayMap<>(); USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_GARAGE_MODE, android.Manifest.permission.DEVICE_POWER); USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_RESUME, android.Manifest.permission.DEVICE_POWER); USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_SUSPEND, android.Manifest.permission.DEVICE_POWER); USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_GET_INITIAL_USER, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_GET_INITIAL_USER_INFO, android.Manifest.permission.MANAGE_USERS); USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_SWITCH_USER, android.Manifest.permission.MANAGE_USERS); USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_REMOVE_USER, android.Manifest.permission.MANAGE_USERS); USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_CREATE_USER, android.Manifest.permission.MANAGE_USERS); USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_GET_USER_AUTH_ASSOCIATION, android.Manifest.permission.MANAGE_USERS); USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_SET_USER_AUTH_ASSOCIATION, android.Manifest.permission.MANAGE_USERS); } private static final String DEVICE_POWER_PERMISSION = "android.permission.DEVICE_POWER"; private static final String PARAM_DAY_MODE = "day"; private static final String PARAM_NIGHT_MODE = "night"; private static final String PARAM_SENSOR_MODE = "sensor"; private static final String PARAM_VEHICLE_PROPERTY_AREA_GLOBAL = "0"; private static final String PARAM_ON_MODE = "on"; private static final String PARAM_OFF_MODE = "off"; private static final String PARAM_QUERY_MODE = "query"; private static final String PARAM_REBOOT = "reboot"; private static final int RESULT_OK = 0; private static final int RESULT_ERROR = -1; // Arbitrary value, any non-0 is fine private static final int DEFAULT_HAL_TIMEOUT_MS = 1_000; private static final int INVALID_USER_AUTH_TYPE_OR_VALUE = -1; private static final SparseArray VALID_USER_AUTH_TYPES; private static final String VALID_USER_AUTH_TYPES_HELP; private static final SparseArray VALID_USER_AUTH_SET_VALUES; private static final String VALID_USER_AUTH_SET_VALUES_HELP; static { VALID_USER_AUTH_TYPES = new SparseArray(5); VALID_USER_AUTH_TYPES.put(KEY_FOB, UserIdentificationAssociationType.toString(KEY_FOB)); VALID_USER_AUTH_TYPES.put(CUSTOM_1, UserIdentificationAssociationType.toString(CUSTOM_1)); VALID_USER_AUTH_TYPES.put(CUSTOM_2, UserIdentificationAssociationType.toString(CUSTOM_2)); VALID_USER_AUTH_TYPES.put(CUSTOM_3, UserIdentificationAssociationType.toString(CUSTOM_3)); VALID_USER_AUTH_TYPES.put(CUSTOM_4, UserIdentificationAssociationType.toString(CUSTOM_4)); VALID_USER_AUTH_TYPES_HELP = getHelpString("types", VALID_USER_AUTH_TYPES); VALID_USER_AUTH_SET_VALUES = new SparseArray(3); VALID_USER_AUTH_SET_VALUES.put(ASSOCIATE_CURRENT_USER, UserIdentificationAssociationSetValue.toString(ASSOCIATE_CURRENT_USER)); VALID_USER_AUTH_SET_VALUES.put(DISASSOCIATE_CURRENT_USER, UserIdentificationAssociationSetValue.toString(DISASSOCIATE_CURRENT_USER)); VALID_USER_AUTH_SET_VALUES.put(DISASSOCIATE_ALL_USERS, UserIdentificationAssociationSetValue.toString(DISASSOCIATE_ALL_USERS)); VALID_USER_AUTH_SET_VALUES_HELP = getHelpString("values", VALID_USER_AUTH_SET_VALUES); } @NonNull private static String getHelpString(@NonNull String name, @NonNull SparseArray values) { StringBuilder help = new StringBuilder("Valid ").append(name).append(" are: "); int size = values.size(); for (int i = 0; i < size; i++) { help.append(values.valueAt(i)); if (i != size - 1) { help.append(", "); } } return help.append('.').toString(); } private final Context mContext; private final VehicleHal mHal; private final CarAudioService mCarAudioService; private final CarPackageManagerService mCarPackageManagerService; private final CarProjectionService mCarProjectionService; private final CarPowerManagementService mCarPowerManagementService; private final CarTrustedDeviceService mCarTrustedDeviceService; private final FixedActivityService mFixedActivityService; private final CarFeatureController mFeatureController; private final CarInputService mCarInputService; private final CarNightService mCarNightService; private final SystemInterface mSystemInterface; private final GarageModeService mGarageModeService; private final CarUserService mCarUserService; private final CarOccupantZoneService mCarOccupantZoneService; CarShellCommand(Context context, VehicleHal hal, CarAudioService carAudioService, CarPackageManagerService carPackageManagerService, CarProjectionService carProjectionService, CarPowerManagementService carPowerManagementService, CarTrustedDeviceService carTrustedDeviceService, FixedActivityService fixedActivityService, CarFeatureController featureController, CarInputService carInputService, CarNightService carNightService, SystemInterface systemInterface, GarageModeService garageModeService, CarUserService carUserService, CarOccupantZoneService carOccupantZoneService) { mContext = context; mHal = hal; mCarAudioService = carAudioService; mCarPackageManagerService = carPackageManagerService; mCarProjectionService = carProjectionService; mCarPowerManagementService = carPowerManagementService; mCarTrustedDeviceService = carTrustedDeviceService; mFixedActivityService = fixedActivityService; mFeatureController = featureController; mCarInputService = carInputService; mCarNightService = carNightService; mSystemInterface = systemInterface; mGarageModeService = garageModeService; mCarUserService = carUserService; mCarOccupantZoneService = carOccupantZoneService; } @Override public int onCommand(String cmd) { if (cmd == null) { onHelp(); return RESULT_ERROR; } ArrayList argsList = new ArrayList<>(); argsList.add(cmd); String arg = null; do { arg = getNextArg(); if (arg != null) { argsList.add(arg); } } while (arg != null); String[] args = new String[argsList.size()]; argsList.toArray(args); return exec(args, getOutPrintWriter()); } @Override public void onHelp() { showHelp(getOutPrintWriter()); } private static void showHelp(PrintWriter pw) { pw.println("Car service commands:"); pw.println("\t-h"); pw.println("\t Print this help text."); pw.println("\tday-night-mode [day|night|sensor]"); pw.println("\t Force into day/night mode or restore to auto."); pw.println("\tinject-vhal-event property [zone] data(can be comma separated list) " + "[-t delay_time_seconds]"); pw.println("\t Inject a vehicle property for testing."); pw.println("\t delay_time_seconds: the event timestamp is increased by certain second."); pw.println("\t If not specified, it will be 0."); pw.println("\tinject-error-event property zone errorCode"); pw.println("\t Inject an error event from VHAL for testing."); pw.println("\tenable-uxr true|false"); pw.println("\t Enable/Disable UX restrictions and App blocking."); pw.println("\tgarage-mode [on|off|query|reboot]"); pw.println("\t Force into or out of garage mode, or check status."); pw.println("\t With 'reboot', enter garage mode, then reboot when it completes."); pw.println("\tget-do-activities pkgname"); pw.println("\t Get Distraction Optimized activities in given package."); pw.println("\tget-carpropertyconfig [propertyId]"); pw.println("\t Get a CarPropertyConfig by Id in Hex or list all CarPropertyConfigs"); pw.println("\tget-property-value [propertyId] [areaId]"); pw.println("\t Get a vehicle property value by property id in Hex and areaId"); pw.println("\t or list all property values for all areaId"); pw.println("\tsuspend"); pw.println("\t Suspend the system to Deep Sleep."); pw.println("\tresume"); pw.println("\t Wake the system up after a 'suspend.'"); pw.println("\tenable-trusted-device true|false"); pw.println("\t Enable/Disable Trusted device feature."); pw.println("\tremove-trusted-devices"); pw.println("\t Remove all trusted devices for the current foreground user."); pw.println("\tprojection-tethering [true|false]"); pw.println("\t Whether tethering should be used when creating access point for" + " wireless projection"); pw.println("\t--metrics"); pw.println("\t When used with dumpsys, only metrics will be in the dumpsys output."); pw.printf("\t%s [zoneid] [uid]\n", COMMAND_SET_UID_TO_ZONE); pw.println("\t Maps the audio zoneid to uid."); pw.println("\tstart-fixed-activity displayId packageName activityName"); pw.println("\t Start an Activity the specified display as fixed mode"); pw.println("\tstop-fixed-mode displayId"); pw.println("\t Stop fixed Activity mode for the given display. " + "The Activity will not be restarted upon crash."); pw.println("\tenable-feature featureName"); pw.println("\t Enable the requested feature. Change will happen after reboot."); pw.println("\t This requires root/su."); pw.println("\tdisable-feature featureName"); pw.println("\t Disable the requested feature. Change will happen after reboot"); pw.println("\t This requires root/su."); pw.println("\tinject-key [-d display] [-t down_delay_ms] key_code"); pw.println("\t inject key down / up event to car service"); pw.println("\t display: 0 for main, 1 for cluster. If not specified, it will be 0."); pw.println("\t down_delay_ms: delay from down to up key event. If not specified,"); pw.println("\t it will be 0"); pw.println("\t key_code: int key code defined in android KeyEvent"); pw.println("\tinject-rotary [-d display] [-i input_type] [-c clockwise]"); pw.println("\t [-dt delta_times_ms]"); pw.println("\t inject rotary input event to car service."); pw.println("\t display: 0 for main, 1 for cluster. If not specified, it will be 0."); pw.println("\t input_type: 10 for navigation controller input, 11 for volume"); pw.println("\t controller input. If not specified, it will be 10."); pw.println("\t clockwise: true if the event is clockwise, false if the event is"); pw.println("\t counter-clockwise. If not specified, it will be false."); pw.println("\t delta_times_ms: a list of delta time (current time minus event time)"); pw.println("\t in descending order. If not specified, it will be 0."); pw.printf("\t%s [--hal-only] [--timeout TIMEOUT_MS]\n", COMMAND_GET_INITIAL_USER_INFO); pw.println("\t Calls the Vehicle HAL to get the initial boot info, passing the given"); pw.println("\t REQ_TYPE (which could be either FIRST_BOOT, FIRST_BOOT_AFTER_OTA, "); pw.println("\t COLD_BOOT, RESUME, or any numeric value that would be passed 'as-is')"); pw.println("\t and an optional TIMEOUT_MS to wait for the HAL response (if not set,"); pw.println("\t it will use a default value)."); pw.println("\t The --hal-only option only calls HAL, without using CarUserService."); pw.printf("\t%s [--hal-only] [--timeout TIMEOUT_MS]\n", COMMAND_SWITCH_USER); pw.println("\t Switches to user USER_ID using the HAL integration."); pw.println("\t The --hal-only option only calls HAL, without switching the user,"); pw.println("\t while the --timeout defines how long to wait for the HAL response."); pw.printf("\t%s [--hal-only]\n", COMMAND_REMOVE_USER); pw.println("\t Removes user with USER_ID using the HAL integration."); pw.println("\t The --hal-only option only calls HAL, without removing the user,"); pw.printf("\t%s [--hal-only] [--timeout TIMEOUT_MS] [--type TYPE] [--flags FLAGS] [NAME]\n", COMMAND_CREATE_USER); pw.println("\t Creates a new user using the HAL integration."); pw.println("\t The --hal-only uses UserManager to create the user,"); pw.println("\t while the --timeout defines how long to wait for the HAL response."); pw.printf("\t%s\n", COMMAND_GET_INITIAL_USER); pw.printf("\t Gets the id of the initial user (or %s when it's not available)\n", NO_INITIAL_USER); pw.printf("\t%s [occupantZoneId] [userId]\n", COMMAND_SET_USER_ID_TO_OCCUPANT_ZONE); pw.println("\t Maps the occupant zone id to user id."); pw.printf("\t%s [occupantZoneId]\n", COMMAND_RESET_USER_ID_IN_OCCUPANT_ZONE); pw.println("\t Unmaps the user assigned to occupant zone id."); pw.printf("\t%s [--hal-only] [--user USER_ID] TYPE1 [..TYPE_N]\n", COMMAND_GET_USER_AUTH_ASSOCIATION); pw.println("\t Gets the N user authentication values for the N types for the given user"); pw.println("\t (or current user when not specified)."); pw.println("\t By defautt it calls CarUserManager, but using --hal-only will call just " + "UserHalService."); pw.printf("\t%s [--hal-only] [--user USER_ID] TYPE1 VALUE1 [..TYPE_N VALUE_N]\n", COMMAND_SET_USER_AUTH_ASSOCIATION); pw.println("\t Sets the N user authentication types with the N values for the given user"); pw.println("\t (or current user when not specified)."); pw.println("\t By defautt it calls CarUserManager, but using --hal-only will call just " + "UserHalService."); pw.printf("\t %s\n", VALID_USER_AUTH_TYPES_HELP); pw.printf("\t %s\n", VALID_USER_AUTH_SET_VALUES_HELP); } private static int showInvalidArguments(PrintWriter pw) { pw.println("Incorrect number of arguments."); showHelp(pw); return RESULT_ERROR; } private void runSetZoneIdForUid(String zoneString, String uidString) { int uid = Integer.parseInt(uidString); int zoneId = Integer.parseInt(zoneString); mCarAudioService.setZoneIdForUid(zoneId, uid); } private void runSetOccupantZoneIdForUserId(String occupantZoneIdString, String userIdString) { int userId = Integer.parseInt(userIdString); int occupantZoneId = Integer.parseInt(occupantZoneIdString); if (!mCarOccupantZoneService.assignProfileUserToOccupantZone(occupantZoneId, userId)) { throw new IllegalStateException("Failed to set userId " + userId + " to occupantZoneId " + occupantZoneIdString); } } private void runResetOccupantZoneId(String occupantZoneIdString) { int occupantZoneId = Integer.parseInt(occupantZoneIdString); if (!mCarOccupantZoneService .assignProfileUserToOccupantZone(occupantZoneId, UserHandle.USER_NULL)) { throw new IllegalStateException("Failed to reset occupantZoneId " + occupantZoneIdString); } } int exec(String[] args, PrintWriter writer) { String cmd = args[0]; String requiredPermission = USER_BUILD_COMMAND_TO_PERMISSION_MAP.get(cmd); if (VERBOSE) { Log.v(TAG, "cmd: " + cmd + ", requiredPermission: " + requiredPermission); } if (Build.IS_USER && requiredPermission == null) { throw new SecurityException("The command " + cmd + "requires non-user build"); } if (requiredPermission != null) { if (!ICarImpl.hasPermission(mContext, requiredPermission)) { throw new SecurityException("The command " + cmd + "requires permission:" + requiredPermission); } } switch (cmd) { case COMMAND_HELP: showHelp(writer); break; case COMMAND_DAY_NIGHT_MODE: { String value = args.length < 2 ? "" : args[1]; forceDayNightMode(value, writer); break; } case COMMAND_GARAGE_MODE: { String value = args.length < 2 ? "" : args[1]; forceGarageMode(value, writer); break; } case COMMAND_INJECT_VHAL_EVENT: String zone = PARAM_VEHICLE_PROPERTY_AREA_GLOBAL; String data; int argNum = args.length; if (argNum < 3 || argNum > 6) { return showInvalidArguments(writer); } String delayTime = args[argNum - 2].equals("-t") ? args[argNum - 1] : "0"; if (argNum == 4 || argNum == 6) { // Zoned zone = args[2]; data = args[3]; } else { // Global data = args[2]; } injectVhalEvent(args[1], zone, data, false, delayTime, writer); break; case COMMAND_INJECT_ERROR_EVENT: if (args.length != 4) { return showInvalidArguments(writer); } String errorAreaId = args[2]; String errorCode = args[3]; injectVhalEvent(args[1], errorAreaId, errorCode, true, "0", writer); break; case COMMAND_ENABLE_UXR: if (args.length != 2) { return showInvalidArguments(writer); } boolean enableBlocking = Boolean.valueOf(args[1]); if (mCarPackageManagerService != null) { mCarPackageManagerService.setEnableActivityBlocking(enableBlocking); } break; case COMMAND_GET_DO_ACTIVITIES: if (args.length != 2) { return showInvalidArguments(writer); } String pkgName = args[1].toLowerCase(); if (mCarPackageManagerService != null) { String[] doActivities = mCarPackageManagerService.getDistractionOptimizedActivities( pkgName); if (doActivities != null) { writer.println("DO Activities for " + pkgName); for (String a : doActivities) { writer.println(a); } } else { writer.println("No DO Activities for " + pkgName); } } break; case COMMAND_GET_CARPROPERTYCONFIG: String propertyId = args.length < 2 ? "" : args[1]; mHal.dumpPropertyConfigs(writer, propertyId); break; case COMMAND_GET_PROPERTY_VALUE: String propId = args.length < 2 ? "" : args[1]; String areaId = args.length < 3 ? "" : args[2]; mHal.dumpPropertyValueByCommend(writer, propId, areaId); break; case COMMAND_PROJECTION_UI_MODE: if (args.length != 2) { return showInvalidArguments(writer); } mCarProjectionService.setUiMode(Integer.valueOf(args[1])); break; case COMMAND_PROJECTION_AP_TETHERING: if (args.length != 2) { return showInvalidArguments(writer); } mCarProjectionService.setAccessPointTethering(Boolean.valueOf(args[1])); break; case COMMAND_RESUME: mCarPowerManagementService.forceSimulatedResume(); writer.println("Resume: Simulating resuming from Deep Sleep"); break; case COMMAND_SUSPEND: mCarPowerManagementService.forceSuspendAndMaybeReboot(false); writer.println("Resume: Simulating powering down to Deep Sleep"); break; case COMMAND_ENABLE_TRUSTED_DEVICE: if (args.length != 2) { return showInvalidArguments(writer); } mCarTrustedDeviceService.getCarTrustAgentEnrollmentService() .setTrustedDeviceEnrollmentEnabled(Boolean.valueOf(args[1])); mCarTrustedDeviceService.getCarTrustAgentUnlockService() .setTrustedDeviceUnlockEnabled(Boolean.valueOf(args[1])); break; case COMMAND_REMOVE_TRUSTED_DEVICES: mCarTrustedDeviceService.getCarTrustAgentEnrollmentService() .removeAllTrustedDevices(ActivityManager.getCurrentUser()); break; case COMMAND_SET_UID_TO_ZONE: if (args.length != 3) { return showInvalidArguments(writer); } runSetZoneIdForUid(args[1], args[2]); break; case COMMAND_SET_USER_ID_TO_OCCUPANT_ZONE: if (args.length != 3) { return showInvalidArguments(writer); } runSetOccupantZoneIdForUserId(args[1], args[2]); break; case COMMAND_RESET_USER_ID_IN_OCCUPANT_ZONE: if (args.length != 2) { return showInvalidArguments(writer); } runResetOccupantZoneId(args[1]); break; case COMMAND_START_FIXED_ACTIVITY_MODE: startFixedActivity(args, writer); break; case COMMAND_STOP_FIXED_ACTIVITY_MODE: stopFixedMode(args, writer); break; case COMMAND_ENABLE_FEATURE: if (args.length != 2) { return showInvalidArguments(writer); } enableDisableFeature(args, writer, /* enable= */ true); break; case COMMAND_DISABLE_FEATURE: if (args.length != 2) { return showInvalidArguments(writer); } enableDisableFeature(args, writer, /* enable= */ false); break; case COMMAND_INJECT_KEY: if (args.length < 2) { return showInvalidArguments(writer); } injectKey(args, writer); break; case COMMAND_INJECT_ROTARY: if (args.length < 1) { return showInvalidArguments(writer); } injectRotary(args, writer); break; case COMMAND_GET_INITIAL_USER_INFO: getInitialUserInfo(args, writer); break; case COMMAND_SWITCH_USER: switchUser(args, writer); break; case COMMAND_REMOVE_USER: removeUser(args, writer); break; case COMMAND_CREATE_USER: createUser(args, writer); break; case COMMAND_GET_INITIAL_USER: getInitialUser(writer); break; case COMMAND_GET_USER_AUTH_ASSOCIATION: getUserAuthAssociation(args, writer); break; case COMMAND_SET_USER_AUTH_ASSOCIATION: setUserAuthAssociation(args, writer); break; default: writer.println("Unknown command: \"" + cmd + "\""); showHelp(writer); return RESULT_ERROR; } return RESULT_OK; } private void startFixedActivity(String[] args, PrintWriter writer) { if (args.length != 4) { writer.println("Incorrect number of arguments"); showHelp(writer); return; } int displayId; try { displayId = Integer.parseInt(args[1]); } catch (NumberFormatException e) { writer.println("Wrong display id:" + args[1]); return; } String packageName = args[2]; String activityName = args[3]; Intent intent = new Intent(); intent.setComponent(new ComponentName(packageName, activityName)); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); ActivityOptions options = ActivityOptions.makeBasic(); options.setLaunchDisplayId(displayId); if (!mFixedActivityService.startFixedActivityModeForDisplayAndUser(intent, options, displayId, ActivityManager.getCurrentUser())) { writer.println("Failed to start"); return; } writer.println("Succeeded"); } private void stopFixedMode(String[] args, PrintWriter writer) { if (args.length != 2) { writer.println("Incorrect number of arguments"); showHelp(writer); return; } int displayId; try { displayId = Integer.parseInt(args[1]); } catch (NumberFormatException e) { writer.println("Wrong display id:" + args[1]); return; } mFixedActivityService.stopFixedActivityMode(displayId); } private void enableDisableFeature(String[] args, PrintWriter writer, boolean enable) { if (Binder.getCallingUid() != Process.ROOT_UID) { writer.println("Only allowed to root/su"); return; } String featureName = args[1]; long id = Binder.clearCallingIdentity(); // no permission check here int r; if (enable) { r = mFeatureController.enableFeature(featureName); } else { r = mFeatureController.disableFeature(featureName); } switch (r) { case Car.FEATURE_REQUEST_SUCCESS: if (enable) { writer.println("Enabled feature:" + featureName); } else { writer.println("Disabled feature:" + featureName); } break; case Car.FEATURE_REQUEST_ALREADY_IN_THE_STATE: if (enable) { writer.println("Already enabled:" + featureName); } else { writer.println("Already disabled:" + featureName); } break; case Car.FEATURE_REQUEST_MANDATORY: writer.println("Cannot change mandatory feature:" + featureName); break; case Car.FEATURE_REQUEST_NOT_EXISTING: writer.println("Non-existing feature:" + featureName); break; default: writer.println("Unknown error:" + r); break; } Binder.restoreCallingIdentity(id); } private void injectKey(String[] args, PrintWriter writer) { int i = 1; // 0 is command itself int display = InputHalService.DISPLAY_MAIN; int delayMs = 0; int keyCode = KeyEvent.KEYCODE_UNKNOWN; try { while (i < args.length) { switch (args[i]) { case "-d": i++; display = Integer.parseInt(args[i]); break; case "-t": i++; delayMs = Integer.parseInt(args[i]); break; default: if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { throw new IllegalArgumentException("key_code already set:" + keyCode); } keyCode = Integer.parseInt(args[i]); } i++; } } catch (Exception e) { writer.println("Invalid args:" + e); showHelp(writer); return; } if (keyCode == KeyEvent.KEYCODE_UNKNOWN) { writer.println("Missing key code or invalid keycode"); showHelp(writer); return; } if (display != InputHalService.DISPLAY_MAIN && display != InputHalService.DISPLAY_INSTRUMENT_CLUSTER) { writer.println("Invalid display:" + display); showHelp(writer); return; } if (delayMs < 0) { writer.println("Invalid delay:" + delayMs); showHelp(writer); return; } KeyEvent keyDown = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode); mCarInputService.onKeyEvent(keyDown, display); SystemClock.sleep(delayMs); KeyEvent keyUp = new KeyEvent(KeyEvent.ACTION_UP, keyCode); mCarInputService.onKeyEvent(keyUp, display); writer.println("Succeeded"); } private void injectRotary(String[] args, PrintWriter writer) { int i = 1; // 0 is command itself int display = InputHalService.DISPLAY_MAIN; int inputType = CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION; boolean clockwise = false; List deltaTimeMs = new ArrayList<>(); try { while (i < args.length) { switch (args[i]) { case "-d": i++; display = Integer.parseInt(args[i]); break; case "-i": i++; inputType = Integer.parseInt(args[i]); break; case "-c": i++; clockwise = Boolean.parseBoolean(args[i]); break; case "-dt": i++; while (i < args.length) { deltaTimeMs.add(Long.parseLong(args[i])); i++; } break; default: writer.println("Invalid option at index " + i + ": " + args[i]); return; } i++; } } catch (Exception e) { writer.println("Invalid args:" + e); showHelp(writer); return; } if (deltaTimeMs.isEmpty()) { deltaTimeMs.add(0L); } for (int j = 0; j < deltaTimeMs.size(); j++) { if (deltaTimeMs.get(j) < 0) { writer.println("Delta time shouldn't be negative: " + deltaTimeMs.get(j)); showHelp(writer); return; } if (j > 0 && deltaTimeMs.get(j) > deltaTimeMs.get(j - 1)) { writer.println("Delta times should be in descending order"); showHelp(writer); return; } } long[] uptimeMs = new long[deltaTimeMs.size()]; long currentUptime = SystemClock.uptimeMillis(); for (int j = 0; j < deltaTimeMs.size(); j++) { uptimeMs[j] = currentUptime - deltaTimeMs.get(j); } RotaryEvent rotaryEvent = new RotaryEvent(inputType, clockwise, uptimeMs); mCarInputService.onRotaryEvent(rotaryEvent, display); writer.println("Succeeded in injecting: " + rotaryEvent); } private void getInitialUserInfo(String[] args, PrintWriter writer) { if (args.length < 2) { writer.println("Insufficient number of args"); return; } // Gets the request type String typeArg = args[1]; int requestType = UserHalHelper.parseInitialUserInfoRequestType(typeArg); boolean halOnly = false; int timeout = DEFAULT_HAL_TIMEOUT_MS; for (int i = 2; i < args.length; i++) { String arg = args[i]; switch (arg) { case "--timeout": timeout = Integer.parseInt(args[++i]); break; case "--hal-only": halOnly = true; break; default: writer.println("Invalid option at index " + i + ": " + arg); return; } } Log.d(TAG, "handleGetInitialUserInfo(): type=" + requestType + " (" + typeArg + "), timeout=" + timeout); CountDownLatch latch = new CountDownLatch(1); HalCallback callback = (status, resp) -> { try { Log.d(TAG, "GetUserInfoResponse: status=" + status + ", resp=" + resp); writer.printf("Call status: %s\n", UserHalHelper.halCallbackStatusToString(status)); if (status != HalCallback.STATUS_OK) { return; } writer.printf("Request id: %d\n", resp.requestId); writer.printf("Action: %s\n", InitialUserInfoResponseAction.toString(resp.action)); if (!TextUtils.isEmpty(resp.userNameToCreate)) { writer.printf("User name: %s\n", resp.userNameToCreate); } if (resp.userToSwitchOrCreate.userId != UserHandle.USER_NULL) { writer.printf("User id: %d\n", resp.userToSwitchOrCreate.userId); } if (resp.userToSwitchOrCreate.flags != UserFlags.NONE) { writer.printf("User flags: %s\n", UserHalHelper.userFlagsToString(resp.userToSwitchOrCreate.flags)); } if (!TextUtils.isEmpty(resp.userLocales)) { writer.printf("User locales: %s\n", resp.userLocales); } } finally { latch.countDown(); } }; if (halOnly) { UsersInfo usersInfo = generateUsersInfo(); mHal.getUserHal().getInitialUserInfo(requestType, timeout, usersInfo, callback); } else { mCarUserService.getInitialUserInfo(requestType, callback); } waitForHal(writer, latch, timeout); } private UsersInfo generateUsersInfo() { return UserHalHelper.newUsersInfo(UserManager.get(mContext)); } private int getUserHalFlags(@UserIdInt int userId) { return UserHalHelper.getFlags(UserManager.get(mContext), userId); } private static void waitForHal(PrintWriter writer, CountDownLatch latch, int timeoutMs) { try { if (!latch.await(timeoutMs, TimeUnit.MILLISECONDS)) { writer.printf("HAL didn't respond in %dms\n", timeoutMs); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); writer.println("Interrupted waiting for HAL"); } return; } private void switchUser(String[] args, PrintWriter writer) { if (args.length < 2) { writer.println("Insufficient number of args"); return; } int targetUserId = Integer.parseInt(args[1]); int timeout = DEFAULT_HAL_TIMEOUT_MS; boolean halOnly = false; for (int i = 2; i < args.length; i++) { String arg = args[i]; switch (arg) { case "--timeout": timeout = Integer.parseInt(args[++i]); break; case "--hal-only": halOnly = true; break; default: writer.println("Invalid option at index " + i + ": " + arg); return; } } Log.d(TAG, "switchUser(): target=" + targetUserId + ", halOnly=" + halOnly + ", timeout=" + timeout); if (halOnly) { CountDownLatch latch = new CountDownLatch(1); UserHalService userHal = mHal.getUserHal(); UserInfo targetUserInfo = new UserInfo(); targetUserInfo.userId = targetUserId; targetUserInfo.flags = getUserHalFlags(targetUserId); SwitchUserRequest request = new SwitchUserRequest(); request.targetUser = targetUserInfo; request.usersInfo = generateUsersInfo(); userHal.switchUser(request, timeout, (status, resp) -> { try { Log.d(TAG, "SwitchUserResponse: status=" + status + ", resp=" + resp); writer.printf("Call Status: %s\n", UserHalHelper.halCallbackStatusToString(status)); if (status != HalCallback.STATUS_OK) { return; } writer.printf("Request id: %d\n", resp.requestId); writer.printf("Message type: %s\n", SwitchUserMessageType.toString(resp.messageType)); writer.printf("Switch Status: %s\n", SwitchUserStatus.toString(resp.status)); String errorMessage = resp.errorMessage; if (!TextUtils.isEmpty(errorMessage)) { writer.printf("Error message: %s", errorMessage); } // If HAL returned OK, make a "post-switch" call to the HAL indicating an // Android error. This is to "rollback" the HAL switch. if (status == HalCallback.STATUS_OK && resp.status == SwitchUserStatus.SUCCESS) { userHal.postSwitchResponse(request); } } finally { latch.countDown(); } }); waitForHal(writer, latch, timeout); return; } CarUserManager carUserManager = getCarUserManager(mContext); AndroidFuture future = carUserManager.switchUser(targetUserId); UserSwitchResult result = waitForFuture(writer, future, timeout); if (result == null) return; writer.printf("UserSwitchResult: status=%s", UserSwitchResult.statusToString(result.getStatus())); String msg = result.getErrorMessage(); if (!TextUtils.isEmpty(msg)) { writer.printf(", errorMessage=%s", msg); } writer.println(); } private void createUser(String[] args, PrintWriter writer) { int timeout = DEFAULT_HAL_TIMEOUT_MS; int flags = 0; boolean halOnly = false; String name = null; String userType = null; for (int i = 1; i < args.length; i++) { String arg = args[i]; switch (arg) { case "--timeout": timeout = Integer.parseInt(args[++i]); break; case "--hal-only": halOnly = true; break; case "--flags": flags = Integer.parseInt(args[++i]); break; case "--type": userType = args[++i]; break; default: if (name != null) { writer.println("Invalid option at index " + i + ": " + arg); return; } name = arg; } } if (userType == null) { userType = android.content.pm.UserInfo.getDefaultUserType(flags); } Log.d(TAG, "createUser(): name=" + name + ", userType=" + userType + ", flags=" + UserHalHelper.userFlagsToString(flags) + ", halOnly=" + halOnly + ", timeout=" + timeout); if (!halOnly) { CarUserManager carUserManager = getCarUserManager(mContext); AndroidFuture future = carUserManager .createUser(name, userType, flags); UserCreationResult result = waitForFuture(writer, future, timeout); if (result == null) return; android.content.pm.UserInfo user = result.getUser(); writer.printf("UserCreationResult: status=%s, user=%s", UserCreationResult.statusToString(result.getStatus()), user == null ? "N/A" : user.toFullString()); String msg = result.getErrorMessage(); if (!TextUtils.isEmpty(msg)) { writer.printf(", errorMessage=%s", msg); } writer.println(); return; } CountDownLatch latch = new CountDownLatch(1); UserHalService userHal = mHal.getUserHal(); CreateUserRequest request = new CreateUserRequest(); UserManager um = UserManager.get(mContext); android.content.pm.UserInfo newUser = um.createUser(name, userType, flags); if (newUser == null) { writer.printf("Failed to create user"); return; } writer.printf("New user: %s\n", newUser.toFullString()); Log.i(TAG, "Created new user: " + newUser.toFullString()); request.newUserInfo.userId = newUser.id; request.newUserInfo.flags = UserHalHelper.convertFlags(newUser); request.usersInfo = generateUsersInfo(); AtomicBoolean halOk = new AtomicBoolean(false); try { userHal.createUser(request, timeout, (status, resp) -> { Log.d(TAG, "CreateUserResponse: status=" + status + ", resp=" + resp); writer.printf("Call Status: %s\n", UserHalHelper.halCallbackStatusToString(status)); if (status == HalCallback.STATUS_OK) { halOk.set(resp.status == CreateUserStatus.SUCCESS); writer.printf("Request id: %d\n", resp.requestId); writer.printf("Create Status: %s\n", CreateUserStatus.toString(resp.status)); String errorMessage = resp.errorMessage; if (!TextUtils.isEmpty(errorMessage)) { writer.printf("Error message: %s", errorMessage); } } latch.countDown(); }); waitForHal(writer, latch, timeout); } catch (Exception e) { writer.printf("HAL failed: %s\n", e); } finally { if (!halOk.get()) { writer.printf("Removing user %d due to HAL failure\n", newUser.id); boolean removed = um.removeUser(newUser.id); writer.printf("User removed: %b\n", removed); } } } private void removeUser(String[] args, PrintWriter writer) { if (args.length < 2) { writer.println("Insufficient number of args"); return; } int userId = Integer.parseInt(args[1]); boolean halOnly = false; for (int i = 2; i < args.length; i++) { String arg = args[i]; switch (arg) { case "--hal-only": halOnly = true; break; default: writer.println("Invalid option at index " + i + ": " + arg); return; } } Log.d(TAG, "handleRemoveUser(): User to remove=" + userId + ", halOnly=" + halOnly); if (halOnly) { UserHalService userHal = mHal.getUserHal(); UsersInfo usersInfo = generateUsersInfo(); UserInfo userInfo = new UserInfo(); userInfo.userId = userId; userInfo.flags = getUserHalFlags(userId); RemoveUserRequest request = new RemoveUserRequest(); request.removedUserInfo = userInfo; request.usersInfo = usersInfo; userHal.removeUser(request); writer.printf("User removal sent for HAL only.\n"); return; } CarUserManager carUserManager = getCarUserManager(mContext); UserRemovalResult result = carUserManager.removeUser(userId); if (result == null) return; writer.printf("UserRemovalResult: status = %s\n", UserRemovalResult.statusToString(result.getStatus())); } private static T waitForFuture(@NonNull PrintWriter writer, @NonNull AndroidFuture future, int timeoutMs) { T result = null; try { result = future.get(timeoutMs, TimeUnit.MILLISECONDS); if (result == null) { writer.printf("Service didn't respond in %d ms", timeoutMs); } } catch (Exception e) { writer.printf("Exception getting future: %s", e); } return result; } private void getInitialUser(PrintWriter writer) { android.content.pm.UserInfo user = mCarUserService.getInitialUser(); writer.println(user == null ? NO_INITIAL_USER : user.id); } private void getUserAuthAssociation(String[] args, PrintWriter writer) { if (args.length < 2) { writer.println("invalid usage, must pass at least 1 argument"); return; } boolean halOnly = false; int userId = UserHandle.USER_CURRENT; UserIdentificationGetRequest request = new UserIdentificationGetRequest(); for (int i = 1; i < args.length; i++) { String arg = args[i]; switch (arg) { case "--user": try { userId = Integer.parseInt(args[++i]); } catch (Exception e) { writer.printf("Invalid user id at index %d (from %s): %s\n", i + 1, Arrays.toString(args), arg); } break; case "--hal-only": halOnly = true; break; default: int type = parseAuthArg(VALID_USER_AUTH_TYPES, arg); if (type == INVALID_USER_AUTH_TYPE_OR_VALUE) { writer.printf("Invalid type at index %d (from %s): %s. %s\n", i + 1, Arrays.toString(args), arg, VALID_USER_AUTH_TYPES_HELP); return; } request.associationTypes.add(type); } } if (userId == UserHandle.USER_CURRENT) { userId = ActivityManager.getCurrentUser(); } int requestSize = request.associationTypes.size(); if (halOnly) { request.numberAssociationTypes = requestSize; request.userInfo.userId = userId; request.userInfo.flags = getUserHalFlags(userId); Log.d(TAG, "getUserAuthAssociation(): user=" + userId + ", halOnly=" + halOnly + ", request=" + request); UserIdentificationResponse response = mHal.getUserHal().getUserAssociation(request); Log.d(TAG, "getUserAuthAssociation(): response=" + response); showResponse(writer, response); return; } CarUserManager carUserManager = getCarUserManager(writer, userId); int[] types = new int[requestSize]; for (int i = 0; i < requestSize; i++) { types[i] = request.associationTypes.get(i); } UserIdentificationAssociationResponse response = carUserManager .getUserIdentificationAssociation(types); showResponse(writer, response); } private CarUserManager getCarUserManager(@NonNull PrintWriter writer, @UserIdInt int userId) { Context context; if (userId == mContext.getUserId()) { context = mContext; } else { context = mContext.createContextAsUser(UserHandle.of(userId), /* flags= */ 0); } int actualUserId = Binder.getCallingUid(); if (actualUserId != userId) { writer.printf("Emulating call for user id %d, but caller's user id is %d, so that's " + "what CarUserService will use when calling HAL.\n", userId, actualUserId); } return getCarUserManager(context); } private CarUserManager getCarUserManager(@NonNull Context context) { Car car = Car.createCar(context); CarUserManager carUserManager = (CarUserManager) car.getCarManager(Car.CAR_USER_SERVICE); return carUserManager; } private void showResponse(@NonNull PrintWriter writer, @NonNull UserIdentificationResponse response) { if (response == null) { writer.println("null response"); return; } if (!TextUtils.isEmpty(response.errorMessage)) { writer.printf("Error message: %s\n", response.errorMessage); } int numberAssociations = response.associations.size(); writer.printf("%d associations:\n", numberAssociations); for (int i = 0; i < numberAssociations; i++) { UserIdentificationAssociation association = response.associations.get(i); writer.printf(" %s\n", association); } } private void showResponse(@NonNull PrintWriter writer, @NonNull UserIdentificationAssociationResponse response) { if (response == null) { writer.println("null response"); return; } if (!response.isSuccess()) { writer.printf("failed response: %s\n", response); return; } String errorMessage = response.getErrorMessage(); if (!TextUtils.isEmpty(errorMessage)) { writer.printf("Error message: %s\n", errorMessage); } int[] values = response.getValues(); if (values == null) { writer.printf("no associations on %s\n", response); return; } writer.printf("%d associations:\n", values.length); for (int i = 0; i < values.length; i++) { writer.printf(" %s\n", UserIdentificationAssociationValue.toString(values[i])); } } private void setUserAuthAssociation(String[] args, PrintWriter writer) { if (args.length < 3) { writer.println("invalid usage, must pass at least 4 arguments"); return; } boolean halOnly = false; int timeout = DEFAULT_HAL_TIMEOUT_MS; int userId = UserHandle.USER_CURRENT; UserIdentificationSetRequest request = new UserIdentificationSetRequest(); for (int i = 1; i < args.length; i++) { String arg = args[i]; switch (arg) { case "--user": try { userId = Integer.parseInt(args[++i]); } catch (Exception e) { writer.printf("Invalid user id at index %d (from %s): %s\n", i + 1, Arrays.toString(args), arg); } break; case "--hal-only": halOnly = true; break; case "--timeout": timeout = Integer.parseInt(args[++i]); break; default: UserIdentificationSetAssociation association = new UserIdentificationSetAssociation(); association.type = parseAuthArg(VALID_USER_AUTH_TYPES, arg); if (association.type == INVALID_USER_AUTH_TYPE_OR_VALUE) { writer.printf("Invalid type at index %d (from %s): %s. %s\n", i + 1, Arrays.toString(args), arg, VALID_USER_AUTH_TYPES_HELP); return; } association.value = parseAuthArg(VALID_USER_AUTH_SET_VALUES, args[++i]); if (association.value == INVALID_USER_AUTH_TYPE_OR_VALUE) { writer.printf("Invalid value at index %d (from %s): %s. %s\n", i + 1, Arrays.toString(args), arg, VALID_USER_AUTH_SET_VALUES_HELP); return; } request.associations.add(association); } } if (userId == UserHandle.USER_CURRENT) { userId = ActivityManager.getCurrentUser(); } int requestSize = request.associations.size(); if (halOnly) { request.numberAssociations = requestSize; request.userInfo.userId = userId; request.userInfo.flags = getUserHalFlags(userId); Log.d(TAG, "setUserAuthAssociation(): user=" + userId + ", halOnly=" + halOnly + ", request=" + request); CountDownLatch latch = new CountDownLatch(1); mHal.getUserHal().setUserAssociation(timeout, request, (status, response) -> { Log.d(TAG, "setUserAuthAssociation(): response=" + response); try { showResponse(writer, response); } finally { latch.countDown(); } }); waitForHal(writer, latch, timeout); return; } CarUserManager carUserManager = getCarUserManager(writer, userId); int[] types = new int[requestSize]; int[] values = new int[requestSize]; for (int i = 0; i < requestSize; i++) { UserIdentificationSetAssociation association = request.associations.get(i); types[i] = association.type; values[i] = association.value; } AndroidFuture future = carUserManager .setUserIdentificationAssociation(types, values); UserIdentificationAssociationResponse response = waitForFuture(writer, future, timeout); if (response != null) { showResponse(writer, response); } } private static int parseAuthArg(@NonNull SparseArray types, @NonNull String type) { for (int i = 0; i < types.size(); i++) { if (types.valueAt(i).equals(type)) { return types.keyAt(i); } } return INVALID_USER_AUTH_TYPE_OR_VALUE; } private void forceDayNightMode(String arg, PrintWriter writer) { int mode; switch (arg) { case PARAM_DAY_MODE: mode = CarNightService.FORCED_DAY_MODE; break; case PARAM_NIGHT_MODE: mode = CarNightService.FORCED_NIGHT_MODE; break; case PARAM_SENSOR_MODE: mode = CarNightService.FORCED_SENSOR_MODE; break; default: writer.println("Unknown value. Valid argument: " + PARAM_DAY_MODE + "|" + PARAM_NIGHT_MODE + "|" + PARAM_SENSOR_MODE); return; } int current = mCarNightService.forceDayNightMode(mode); String currentMode = null; switch (current) { case UiModeManager.MODE_NIGHT_AUTO: currentMode = PARAM_SENSOR_MODE; break; case UiModeManager.MODE_NIGHT_YES: currentMode = PARAM_NIGHT_MODE; break; case UiModeManager.MODE_NIGHT_NO: currentMode = PARAM_DAY_MODE; break; } writer.println("DayNightMode changed to: " + currentMode); } private void forceGarageMode(String arg, PrintWriter writer) { switch (arg) { case PARAM_ON_MODE: mSystemInterface.setDisplayState(false); mGarageModeService.forceStartGarageMode(); writer.println("Garage mode: " + mGarageModeService.isGarageModeActive()); break; case PARAM_OFF_MODE: mSystemInterface.setDisplayState(true); mGarageModeService.stopAndResetGarageMode(); writer.println("Garage mode: " + mGarageModeService.isGarageModeActive()); break; case PARAM_QUERY_MODE: mGarageModeService.dump(writer); break; case PARAM_REBOOT: mCarPowerManagementService.forceSuspendAndMaybeReboot(true); writer.println("Entering Garage Mode. Will reboot when it completes."); break; default: writer.println("Unknown value. Valid argument: " + PARAM_ON_MODE + "|" + PARAM_OFF_MODE + "|" + PARAM_QUERY_MODE + "|" + PARAM_REBOOT); } } /** * Inject a fake VHAL event * * @param property the Vehicle property Id as defined in the HAL * @param zone Zone that this event services * @param isErrorEvent indicates the type of event * @param value Data value of the event * @param delayTime the event timestamp is increased by delayTime * @param writer PrintWriter */ private void injectVhalEvent(String property, String zone, String value, boolean isErrorEvent, String delayTime, PrintWriter writer) { if (zone != null && (zone.equalsIgnoreCase(PARAM_VEHICLE_PROPERTY_AREA_GLOBAL))) { if (!isPropertyAreaTypeGlobal(property)) { writer.println("Property area type inconsistent with given zone"); return; } } try { if (isErrorEvent) { mHal.injectOnPropertySetError(property, zone, value); } else { mHal.injectVhalEvent(property, zone, value, delayTime); } } catch (NumberFormatException e) { writer.println("Invalid property Id zone Id or value" + e); showHelp(writer); } } // Check if the given property is global private static boolean isPropertyAreaTypeGlobal(@Nullable String property) { if (property == null) { return false; } return (Integer.decode(property) & VehicleArea.MASK) == VehicleArea.GLOBAL; } }