/* * Copyright (C) 2018 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.phone; import static com.android.internal.telephony.d2d.Communicator.MESSAGE_CALL_AUDIO_CODEC; import static com.android.internal.telephony.d2d.Communicator.MESSAGE_CALL_RADIO_ACCESS_TYPE; import static com.android.internal.telephony.d2d.Communicator.MESSAGE_DEVICE_BATTERY_STATE; import static com.android.internal.telephony.d2d.Communicator.MESSAGE_DEVICE_NETWORK_COVERAGE; import android.Manifest; import android.content.Context; import android.net.Uri; import android.os.Binder; import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.provider.BlockedNumberContract; import android.telephony.BarringInfo; import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.TelephonyRegistryManager; import android.telephony.emergency.EmergencyNumber; import android.telephony.ims.ImsException; import android.telephony.ims.RcsContactUceCapability; import android.telephony.ims.feature.ImsFeature; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import com.android.ims.rcs.uce.util.FeatureTags; import com.android.internal.telephony.ITelephony; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.d2d.Communicator; import com.android.internal.telephony.emergency.EmergencyNumberTracker; import com.android.internal.telephony.util.TelephonyUtils; import com.android.modules.utils.BasicShellCommandHandler; import com.android.phone.callcomposer.CallComposerPictureManager; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.UUID; import java.util.concurrent.CompletableFuture; /** * Takes actions based on the adb commands given by "adb shell cmd phone ...". Be careful, no * permission checks have been done before onCommand was called. Make sure any commands processed * here also contain the appropriate permissions checks. */ public class TelephonyShellCommand extends BasicShellCommandHandler { private static final String LOG_TAG = "TelephonyShellCommand"; // Don't commit with this true. private static final boolean VDBG = true; private static final int DEFAULT_PHONE_ID = 0; private static final String CALL_COMPOSER_SUBCOMMAND = "callcomposer"; private static final String IMS_SUBCOMMAND = "ims"; private static final String NUMBER_VERIFICATION_SUBCOMMAND = "numverify"; private static final String EMERGENCY_CALLBACK_MODE = "emergency-callback-mode"; private static final String EMERGENCY_NUMBER_TEST_MODE = "emergency-number-test-mode"; private static final String END_BLOCK_SUPPRESSION = "end-block-suppression"; private static final String RESTART_MODEM = "restart-modem"; private static final String UNATTENDED_REBOOT = "unattended-reboot"; private static final String CARRIER_CONFIG_SUBCOMMAND = "cc"; private static final String DATA_TEST_MODE = "data"; private static final String ENABLE = "enable"; private static final String DISABLE = "disable"; private static final String QUERY = "query"; private static final String CALL_COMPOSER_TEST_MODE = "test-mode"; private static final String CALL_COMPOSER_SIMULATE_CALL = "simulate-outgoing-call"; private static final String CALL_COMPOSER_USER_SETTING = "user-setting"; private static final String IMS_SET_IMS_SERVICE = "set-ims-service"; private static final String IMS_GET_IMS_SERVICE = "get-ims-service"; private static final String IMS_CLEAR_SERVICE_OVERRIDE = "clear-ims-service-override"; // Used to disable or enable processing of conference event package data from the network. // This is handy for testing scenarios where CEP data does not exist on a network which does // support CEP data. private static final String IMS_CEP = "conference-event-package"; private static final String NUMBER_VERIFICATION_OVERRIDE_PACKAGE = "override-package"; private static final String NUMBER_VERIFICATION_FAKE_CALL = "fake-call"; private static final String CC_GET_VALUE = "get-value"; private static final String CC_SET_VALUE = "set-value"; private static final String CC_CLEAR_VALUES = "clear-values"; private static final String GBA_SUBCOMMAND = "gba"; private static final String GBA_SET_SERVICE = "set-service"; private static final String GBA_GET_SERVICE = "get-service"; private static final String GBA_SET_RELEASE_TIME = "set-release"; private static final String GBA_GET_RELEASE_TIME = "get-release"; private static final String SINGLE_REGISTATION_CONFIG = "src"; private static final String SRC_SET_DEVICE_ENABLED = "set-device-enabled"; private static final String SRC_GET_DEVICE_ENABLED = "get-device-enabled"; private static final String SRC_SET_CARRIER_ENABLED = "set-carrier-enabled"; private static final String SRC_GET_CARRIER_ENABLED = "get-carrier-enabled"; private static final String SRC_SET_TEST_ENABLED = "set-test-enabled"; private static final String SRC_GET_TEST_ENABLED = "get-test-enabled"; private static final String SRC_SET_FEATURE_ENABLED = "set-feature-validation"; private static final String SRC_GET_FEATURE_ENABLED = "get-feature-validation"; private static final String D2D_SUBCOMMAND = "d2d"; private static final String D2D_SEND = "send"; private static final String D2D_TRANSPORT = "transport"; private static final String D2D_SET_DEVICE_SUPPORT = "set-device-support"; private static final String BARRING_SUBCOMMAND = "barring"; private static final String BARRING_SEND_INFO = "send"; private static final String RCS_UCE_COMMAND = "uce"; private static final String UCE_GET_EAB_CONTACT = "get-eab-contact"; private static final String UCE_GET_EAB_CAPABILITY = "get-eab-capability"; private static final String UCE_REMOVE_EAB_CONTACT = "remove-eab-contact"; private static final String UCE_GET_DEVICE_ENABLED = "get-device-enabled"; private static final String UCE_SET_DEVICE_ENABLED = "set-device-enabled"; private static final String UCE_OVERRIDE_PUBLISH_CAPS = "override-published-caps"; private static final String UCE_GET_LAST_PIDF_XML = "get-last-publish-pidf"; private static final String UCE_REMOVE_REQUEST_DISALLOWED_STATUS = "remove-request-disallowed-status"; private static final String UCE_SET_CAPABILITY_REQUEST_TIMEOUT = "set-capabilities-request-timeout"; // Check if a package has carrier privileges on any SIM, regardless of subId/phoneId. private static final String HAS_CARRIER_PRIVILEGES_COMMAND = "has-carrier-privileges"; private static final String DISABLE_PHYSICAL_SUBSCRIPTION = "disable-physical-subscription"; private static final String ENABLE_PHYSICAL_SUBSCRIPTION = "enable-physical-subscription"; private static final String THERMAL_MITIGATION_COMMAND = "thermal-mitigation"; private static final String ALLOW_THERMAL_MITIGATION_PACKAGE_SUBCOMMAND = "allow-package"; private static final String DISALLOW_THERMAL_MITIGATION_PACKAGE_SUBCOMMAND = "disallow-package"; private static final String GET_ALLOWED_NETWORK_TYPES_FOR_USER = "get-allowed-network-types-for-users"; private static final String SET_ALLOWED_NETWORK_TYPES_FOR_USER = "set-allowed-network-types-for-users"; // Take advantage of existing methods that already contain permissions checks when possible. private final ITelephony mInterface; private SubscriptionManager mSubscriptionManager; private CarrierConfigManager mCarrierConfigManager; private TelephonyRegistryManager mTelephonyRegistryManager; private Context mContext; private enum CcType { BOOLEAN, DOUBLE, DOUBLE_ARRAY, INT, INT_ARRAY, LONG, LONG_ARRAY, STRING, STRING_ARRAY, UNKNOWN } private class CcOptionParseResult { public int mSubId; public boolean mPersistent; } // Maps carrier config keys to type. It is possible to infer the type for most carrier config // keys by looking at the end of the string which usually tells the type. // For instance: "xxxx_string", "xxxx_string_array", etc. // The carrier config keys in this map does not follow this convention. It is therefore not // possible to infer the type for these keys by looking at the string. private static final Map CC_TYPE_MAP = new HashMap() {{ put(CarrierConfigManager.Gps.KEY_A_GLONASS_POS_PROTOCOL_SELECT_STRING, CcType.STRING); put(CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, CcType.STRING); put(CarrierConfigManager.Gps.KEY_GPS_LOCK_STRING, CcType.STRING); put(CarrierConfigManager.Gps.KEY_LPP_PROFILE_STRING, CcType.STRING); put(CarrierConfigManager.Gps.KEY_NFW_PROXY_APPS_STRING, CcType.STRING); put(CarrierConfigManager.Gps.KEY_SUPL_ES_STRING, CcType.STRING); put(CarrierConfigManager.Gps.KEY_SUPL_HOST_STRING, CcType.STRING); put(CarrierConfigManager.Gps.KEY_SUPL_MODE_STRING, CcType.STRING); put(CarrierConfigManager.Gps.KEY_SUPL_PORT_STRING, CcType.STRING); put(CarrierConfigManager.Gps.KEY_SUPL_VER_STRING, CcType.STRING); put(CarrierConfigManager.Gps.KEY_USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL_STRING, CcType.STRING); put(CarrierConfigManager.KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY, CcType.STRING_ARRAY); put(CarrierConfigManager.KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY, CcType.STRING_ARRAY); put(CarrierConfigManager.KEY_CARRIER_CALL_SCREENING_APP_STRING, CcType.STRING); put(CarrierConfigManager.KEY_MMS_EMAIL_GATEWAY_NUMBER_STRING, CcType.STRING); put(CarrierConfigManager.KEY_MMS_HTTP_PARAMS_STRING, CcType.STRING); put(CarrierConfigManager.KEY_MMS_NAI_SUFFIX_STRING, CcType.STRING); put(CarrierConfigManager.KEY_MMS_UA_PROF_TAG_NAME_STRING, CcType.STRING); put(CarrierConfigManager.KEY_MMS_UA_PROF_URL_STRING, CcType.STRING); put(CarrierConfigManager.KEY_MMS_USER_AGENT_STRING, CcType.STRING); put(CarrierConfigManager.KEY_RATCHET_RAT_FAMILIES, CcType.STRING_ARRAY); } }; /** * Map from a shorthand string to the feature tags required in registration required in order * for the RCS feature to be considered "capable". */ private static final Map> TEST_FEATURE_TAG_MAP; static { ArrayMap> map = new ArrayMap<>(18); map.put("chat_v1", Collections.singleton(FeatureTags.FEATURE_TAG_CHAT_IM)); map.put("chat_v2", Collections.singleton(FeatureTags.FEATURE_TAG_CHAT_SESSION)); map.put("ft", Collections.singleton(FeatureTags.FEATURE_TAG_FILE_TRANSFER)); map.put("ft_sms", Collections.singleton(FeatureTags.FEATURE_TAG_FILE_TRANSFER_VIA_SMS)); map.put("mmtel", Collections.singleton(FeatureTags.FEATURE_TAG_MMTEL)); map.put("mmtel_vt", new ArraySet<>(Arrays.asList(FeatureTags.FEATURE_TAG_MMTEL, FeatureTags.FEATURE_TAG_VIDEO))); map.put("geo_push", Collections.singleton(FeatureTags.FEATURE_TAG_GEO_PUSH)); map.put("geo_push_sms", Collections.singleton(FeatureTags.FEATURE_TAG_GEO_PUSH_VIA_SMS)); map.put("call_comp", Collections.singleton(FeatureTags.FEATURE_TAG_CALL_COMPOSER_ENRICHED_CALLING)); map.put("call_comp_mmtel", Collections.singleton(FeatureTags.FEATURE_TAG_CALL_COMPOSER_VIA_TELEPHONY)); map.put("call_post", Collections.singleton(FeatureTags.FEATURE_TAG_POST_CALL)); map.put("map", Collections.singleton(FeatureTags.FEATURE_TAG_SHARED_MAP)); map.put("sketch", Collections.singleton(FeatureTags.FEATURE_TAG_SHARED_SKETCH)); // Feature tags defined twice for chatbot session because we want v1 and v2 based on bot // version map.put("chatbot", new ArraySet<>(Arrays.asList( FeatureTags.FEATURE_TAG_CHATBOT_COMMUNICATION_USING_SESSION, FeatureTags.FEATURE_TAG_CHATBOT_VERSION_SUPPORTED))); map.put("chatbot_v2", new ArraySet<>(Arrays.asList( FeatureTags.FEATURE_TAG_CHATBOT_COMMUNICATION_USING_SESSION, FeatureTags.FEATURE_TAG_CHATBOT_VERSION_SUPPORTED))); map.put("chatbot_sa", new ArraySet<>(Arrays.asList( FeatureTags.FEATURE_TAG_CHATBOT_COMMUNICATION_USING_STANDALONE_MSG, FeatureTags.FEATURE_TAG_CHATBOT_VERSION_SUPPORTED))); map.put("chatbot_sa_v2", new ArraySet<>(Arrays.asList( FeatureTags.FEATURE_TAG_CHATBOT_COMMUNICATION_USING_STANDALONE_MSG, FeatureTags.FEATURE_TAG_CHATBOT_VERSION_SUPPORTED))); map.put("chatbot_role", Collections.singleton(FeatureTags.FEATURE_TAG_CHATBOT_ROLE)); TEST_FEATURE_TAG_MAP = Collections.unmodifiableMap(map); } public TelephonyShellCommand(ITelephony binder, Context context) { mInterface = binder; mCarrierConfigManager = (CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE); mSubscriptionManager = (SubscriptionManager) context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); mTelephonyRegistryManager = (TelephonyRegistryManager) context.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE); mContext = context; } @Override public int onCommand(String cmd) { if (cmd == null) { return handleDefaultCommands(null); } switch (cmd) { case IMS_SUBCOMMAND: { return handleImsCommand(); } case RCS_UCE_COMMAND: return handleRcsUceCommand(); case NUMBER_VERIFICATION_SUBCOMMAND: return handleNumberVerificationCommand(); case EMERGENCY_CALLBACK_MODE: return handleEmergencyCallbackModeCommand(); case EMERGENCY_NUMBER_TEST_MODE: return handleEmergencyNumberTestModeCommand(); case CARRIER_CONFIG_SUBCOMMAND: { return handleCcCommand(); } case DATA_TEST_MODE: return handleDataTestModeCommand(); case END_BLOCK_SUPPRESSION: return handleEndBlockSuppressionCommand(); case GBA_SUBCOMMAND: return handleGbaCommand(); case D2D_SUBCOMMAND: return handleD2dCommand(); case BARRING_SUBCOMMAND: return handleBarringCommand(); case SINGLE_REGISTATION_CONFIG: return handleSingleRegistrationConfigCommand(); case RESTART_MODEM: return handleRestartModemCommand(); case CALL_COMPOSER_SUBCOMMAND: return handleCallComposerCommand(); case UNATTENDED_REBOOT: return handleUnattendedReboot(); case HAS_CARRIER_PRIVILEGES_COMMAND: return handleHasCarrierPrivilegesCommand(); case THERMAL_MITIGATION_COMMAND: return handleThermalMitigationCommand(); case DISABLE_PHYSICAL_SUBSCRIPTION: return handleEnablePhysicalSubscription(false); case ENABLE_PHYSICAL_SUBSCRIPTION: return handleEnablePhysicalSubscription(true); case GET_ALLOWED_NETWORK_TYPES_FOR_USER: case SET_ALLOWED_NETWORK_TYPES_FOR_USER: return handleAllowedNetworkTypesCommand(cmd); default: { return handleDefaultCommands(cmd); } } } @Override public void onHelp() { PrintWriter pw = getOutPrintWriter(); pw.println("Telephony Commands:"); pw.println(" help"); pw.println(" Print this help text."); pw.println(" ims"); pw.println(" IMS Commands."); pw.println(" uce"); pw.println(" RCS User Capability Exchange Commands."); pw.println(" emergency-number-test-mode"); pw.println(" Emergency Number Test Mode Commands."); pw.println(" end-block-suppression"); pw.println(" End Block Suppression command."); pw.println(" data"); pw.println(" Data Test Mode Commands."); pw.println(" cc"); pw.println(" Carrier Config Commands."); pw.println(" gba"); pw.println(" GBA Commands."); pw.println(" src"); pw.println(" RCS VoLTE Single Registration Config Commands."); pw.println(" restart-modem"); pw.println(" Restart modem command."); pw.println(" unattended-reboot"); pw.println(" Prepare for unattended reboot."); pw.println(" has-carrier-privileges [package]"); pw.println(" Query carrier privilege status for a package. Prints true or false."); pw.println(" get-allowed-network-types-for-users"); pw.println(" Get the Allowed Network Types."); pw.println(" set-allowed-network-types-for-users"); pw.println(" Set the Allowed Network Types."); onHelpIms(); onHelpUce(); onHelpEmergencyNumber(); onHelpEndBlockSupperssion(); onHelpDataTestMode(); onHelpCc(); onHelpGba(); onHelpSrc(); onHelpD2D(); onHelpDisableOrEnablePhysicalSubscription(); onHelpAllowedNetworkTypes(); } private void onHelpD2D() { PrintWriter pw = getOutPrintWriter(); pw.println("D2D Comms Commands:"); pw.println(" d2d send TYPE VALUE"); pw.println(" Sends a D2D message of specified type and value."); pw.println(" Type: " + MESSAGE_CALL_RADIO_ACCESS_TYPE + " - " + Communicator.messageToString(MESSAGE_CALL_RADIO_ACCESS_TYPE)); pw.println(" Type: " + MESSAGE_CALL_AUDIO_CODEC + " - " + Communicator.messageToString( MESSAGE_CALL_AUDIO_CODEC)); pw.println(" Type: " + MESSAGE_DEVICE_BATTERY_STATE + " - " + Communicator.messageToString( MESSAGE_DEVICE_BATTERY_STATE)); pw.println(" Type: " + MESSAGE_DEVICE_NETWORK_COVERAGE + " - " + Communicator.messageToString(MESSAGE_DEVICE_NETWORK_COVERAGE)); pw.println(" d2d transport TYPE"); pw.println(" Forces the specified D2D transport TYPE to be active. Use the"); pw.println(" short class name of the transport; i.e. DtmfTransport or RtpTransport."); pw.println(" d2d set-device-support true/default"); pw.println(" true - forces device support to be enabled for D2D."); pw.println(" default - clear any previously set force-enable of D2D, reverting to "); pw.println(" the current device's configuration."); } private void onHelpBarring() { PrintWriter pw = getOutPrintWriter(); pw.println("Barring Commands:"); pw.println(" barring send -s SLOT_ID -b BARRING_TYPE -c IS_CONDITIONALLY_BARRED" + " -t CONDITIONAL_BARRING_TIME_SECS"); pw.println(" Notifies of a barring info change for the specified slot id."); pw.println(" BARRING_TYPE: 0 for BARRING_TYPE_NONE"); pw.println(" BARRING_TYPE: 1 for BARRING_TYPE_UNCONDITIONAL"); pw.println(" BARRING_TYPE: 2 for BARRING_TYPE_CONDITIONAL"); pw.println(" BARRING_TYPE: -1 for BARRING_TYPE_UNKNOWN"); } private void onHelpIms() { PrintWriter pw = getOutPrintWriter(); pw.println("IMS Commands:"); pw.println(" ims set-ims-service [-s SLOT_ID] (-c | -d | -f) PACKAGE_NAME"); pw.println(" Sets the ImsService defined in PACKAGE_NAME to to be the bound"); pw.println(" ImsService. Options are:"); pw.println(" -s: the slot ID that the ImsService should be bound for. If no option"); pw.println(" is specified, it will choose the default voice SIM slot."); pw.println(" -c: Override the ImsService defined in the carrier configuration."); pw.println(" -d: Override the ImsService defined in the device overlay."); pw.println(" -f: Set the feature that this override if for, if no option is"); pw.println(" specified, the new package name will be used for all features."); pw.println(" ims get-ims-service [-s SLOT_ID] [-c | -d]"); pw.println(" Gets the package name of the currently defined ImsService."); pw.println(" Options are:"); pw.println(" -s: The SIM slot ID for the registered ImsService. If no option"); pw.println(" is specified, it will choose the default voice SIM slot."); pw.println(" -c: The ImsService defined as the carrier configured ImsService."); pw.println(" -d: The ImsService defined as the device default ImsService."); pw.println(" -f: The feature type that the query will be requested for. If none is"); pw.println(" specified, the returned package name will correspond to MMTEL."); pw.println(" ims clear-ims-service-override [-s SLOT_ID]"); pw.println(" Clear all carrier ImsService overrides. This does not work for device "); pw.println(" configuration overrides. Options are:"); pw.println(" -s: The SIM slot ID for the registered ImsService. If no option"); pw.println(" is specified, it will choose the default voice SIM slot."); pw.println(" ims enable [-s SLOT_ID]"); pw.println(" enables IMS for the SIM slot specified, or for the default voice SIM slot"); pw.println(" if none is specified."); pw.println(" ims disable [-s SLOT_ID]"); pw.println(" disables IMS for the SIM slot specified, or for the default voice SIM"); pw.println(" slot if none is specified."); pw.println(" ims conference-event-package [enable/disable]"); pw.println(" enables or disables handling or network conference event package data."); } private void onHelpUce() { PrintWriter pw = getOutPrintWriter(); pw.println("User Capability Exchange Commands:"); pw.println(" uce get-eab-contact [PHONE_NUMBER]"); pw.println(" Get the EAB contacts from the EAB database."); pw.println(" Options are:"); pw.println(" PHONE_NUMBER: The phone numbers to be removed from the EAB databases"); pw.println(" Expected output format :"); pw.println(" [PHONE_NUMBER],[RAW_CONTACT_ID],[CONTACT_ID],[DATA_ID]"); pw.println(" uce remove-eab-contact [-s SLOT_ID] [PHONE_NUMBER]"); pw.println(" Remove the EAB contacts from the EAB database."); pw.println(" Options are:"); pw.println(" -s: The SIM slot ID to read carrier config value for. If no option"); pw.println(" is specified, it will choose the default voice SIM slot."); pw.println(" PHONE_NUMBER: The phone numbers to be removed from the EAB databases"); pw.println(" uce get-device-enabled"); pw.println(" Get the config to check whether the device supports RCS UCE or not."); pw.println(" uce set-device-enabled true|false"); pw.println(" Set the device config for RCS User Capability Exchange to the value."); pw.println(" The value could be true, false."); pw.println(" uce override-published-caps [-s SLOT_ID] add|remove|clear [CAPABILITIES]"); pw.println(" Override the existing SIP PUBLISH with different capabilities."); pw.println(" Options are:"); pw.println(" -s: The SIM slot ID to read carrier config value for. If no option"); pw.println(" is specified, it will choose the default voice SIM slot."); pw.println(" add [CAPABILITY]: add a new capability"); pw.println(" remove [CAPABILITY]: remove a capability"); pw.println(" clear: clear all capability overrides"); pw.println(" CAPABILITY: \":\" separated list of capabilities."); pw.println(" Valid options are: [mmtel(_vt), chat_v1, chat_v2, ft, ft_sms,"); pw.println(" geo_push, geo_push_sms, call_comp, call_post, map, sketch, chatbot,"); pw.println(" chatbot_sa, chatbot_role] as well as full length"); pw.println(" featureTag=\"featureValue\" feature tags that are not defined here."); pw.println(" uce get-last-publish-pidf [-s SLOT_ID]"); pw.println(" Get the PIDF XML included in the last SIP PUBLISH, or \"none\" if no "); pw.println(" PUBLISH is active"); pw.println(" uce remove-request-disallowed-status [-s SLOT_ID]"); pw.println(" Remove the UCE is disallowed to execute UCE requests status"); pw.println(" uce set-capabilities-request-timeout [-s SLOT_ID] [REQUEST_TIMEOUT_MS]"); pw.println(" Set the timeout for contact capabilities request."); } private void onHelpNumberVerification() { PrintWriter pw = getOutPrintWriter(); pw.println("Number verification commands"); pw.println(" numverify override-package PACKAGE_NAME;"); pw.println(" Set the authorized package for number verification."); pw.println(" Leave the package name blank to reset."); pw.println(" numverify fake-call NUMBER;"); pw.println(" Fake an incoming call from NUMBER. This is for testing. Output will be"); pw.println(" 1 if the call would have been intercepted, 0 otherwise."); } private void onHelpThermalMitigation() { PrintWriter pw = getOutPrintWriter(); pw.println("Thermal mitigation commands"); pw.println(" thermal-mitigation allow-package PACKAGE_NAME"); pw.println(" Set the package as one of authorized packages for thermal mitigation."); pw.println(" thermal-mitigation disallow-package PACKAGE_NAME"); pw.println(" Remove the package from one of the authorized packages for thermal " + "mitigation."); } private void onHelpDisableOrEnablePhysicalSubscription() { PrintWriter pw = getOutPrintWriter(); pw.println("Disable or enable a physical subscription"); pw.println(" disable-physical-subscription SUB_ID"); pw.println(" Disable the physical subscription with the provided subId, if allowed."); pw.println(" enable-physical-subscription SUB_ID"); pw.println(" Enable the physical subscription with the provided subId, if allowed."); } private void onHelpDataTestMode() { PrintWriter pw = getOutPrintWriter(); pw.println("Mobile Data Test Mode Commands:"); pw.println(" data enable: enable mobile data connectivity"); pw.println(" data disable: disable mobile data connectivity"); } private void onHelpEmergencyNumber() { PrintWriter pw = getOutPrintWriter(); pw.println("Emergency Number Test Mode Commands:"); pw.println(" emergency-number-test-mode "); pw.println(" Add(-a), Clear(-c), Print (-p) or Remove(-r) the emergency number list in" + " the test mode"); pw.println(" -a : add an emergency number address for the" + " test mode, only allows '0'-'9', '*', '#' or '+'."); pw.println(" -c: clear the emergency number list in the test mode."); pw.println(" -r : remove an existing emergency number" + " address added by the test mode, only allows '0'-'9', '*', '#' or '+'."); pw.println(" -p: get the full emergency number list in the test mode."); } private void onHelpEndBlockSupperssion() { PrintWriter pw = getOutPrintWriter(); pw.println("End Block Suppression command:"); pw.println(" end-block-suppression: disable suppressing blocking by contact"); pw.println(" with emergency services."); } private void onHelpCc() { PrintWriter pw = getOutPrintWriter(); pw.println("Carrier Config Commands:"); pw.println(" cc get-value [-s SLOT_ID] [KEY]"); pw.println(" Print carrier config values."); pw.println(" Options are:"); pw.println(" -s: The SIM slot ID to read carrier config value for. If no option"); pw.println(" is specified, it will choose the default voice SIM slot."); pw.println(" KEY: The key to the carrier config value to print. All values are printed"); pw.println(" if KEY is not specified."); pw.println(" cc set-value [-s SLOT_ID] [-p] KEY [NEW_VALUE]"); pw.println(" Set carrier config KEY to NEW_VALUE."); pw.println(" Options are:"); pw.println(" -s: The SIM slot ID to set carrier config value for. If no option"); pw.println(" is specified, it will choose the default voice SIM slot."); pw.println(" -p: Value will be stored persistent"); pw.println(" NEW_VALUE specifies the new value for carrier config KEY. Null will be"); pw.println(" used if NEW_VALUE is not set. Strings should be encapsulated with"); pw.println(" quotation marks. Spaces needs to be escaped. Example: \"Hello\\ World\""); pw.println(" Separate items in arrays with space . Example: \"item1\" \"item2\""); pw.println(" cc clear-values [-s SLOT_ID]"); pw.println(" Clear all carrier override values that has previously been set"); pw.println(" with set-value"); pw.println(" Options are:"); pw.println(" -s: The SIM slot ID to clear carrier config values for. If no option"); pw.println(" is specified, it will choose the default voice SIM slot."); } private void onHelpGba() { PrintWriter pw = getOutPrintWriter(); pw.println("Gba Commands:"); pw.println(" gba set-service [-s SLOT_ID] PACKAGE_NAME"); pw.println(" Sets the GbaService defined in PACKAGE_NAME to to be the bound."); pw.println(" Options are:"); pw.println(" -s: The SIM slot ID to read carrier config value for. If no option"); pw.println(" is specified, it will choose the default voice SIM slot."); pw.println(" gba get-service [-s SLOT_ID]"); pw.println(" Gets the package name of the currently defined GbaService."); pw.println(" Options are:"); pw.println(" -s: The SIM slot ID to read carrier config value for. If no option"); pw.println(" is specified, it will choose the default voice SIM slot."); pw.println(" gba set-release [-s SLOT_ID] n"); pw.println(" Sets the time to release/unbind GbaService in n milli-second."); pw.println(" Do not release/unbind if n is -1."); pw.println(" Options are:"); pw.println(" -s: The SIM slot ID to read carrier config value for. If no option"); pw.println(" is specified, it will choose the default voice SIM slot."); pw.println(" gba get-release [-s SLOT_ID]"); pw.println(" Gets the time to release/unbind GbaService in n milli-sencond."); pw.println(" Options are:"); pw.println(" -s: The SIM slot ID to read carrier config value for. If no option"); pw.println(" is specified, it will choose the default voice SIM slot."); } private void onHelpSrc() { PrintWriter pw = getOutPrintWriter(); pw.println("RCS VoLTE Single Registration Config Commands:"); pw.println(" src set-test-enabled true|false"); pw.println(" Sets the test mode enabled for RCS VoLTE single registration."); pw.println(" The value could be true, false, or null(undefined)."); pw.println(" src get-test-enabled"); pw.println(" Gets the test mode for RCS VoLTE single registration."); pw.println(" src set-device-enabled true|false|null"); pw.println(" Sets the device config for RCS VoLTE single registration to the value."); pw.println(" The value could be true, false, or null(undefined)."); pw.println(" src get-device-enabled"); pw.println(" Gets the device config for RCS VoLTE single registration."); pw.println(" src set-carrier-enabled [-s SLOT_ID] true|false|null"); pw.println(" Sets the carrier config for RCS VoLTE single registration to the value."); pw.println(" The value could be true, false, or null(undefined)."); pw.println(" Options are:"); pw.println(" -s: The SIM slot ID to set the config value for. If no option"); pw.println(" is specified, it will choose the default voice SIM slot."); pw.println(" src get-carrier-enabled [-s SLOT_ID]"); pw.println(" Gets the carrier config for RCS VoLTE single registration."); pw.println(" Options are:"); pw.println(" -s: The SIM slot ID to read the config value for. If no option"); pw.println(" is specified, it will choose the default voice SIM slot."); pw.println(" src set-feature-validation [-s SLOT_ID] true|false|null"); pw.println(" Sets ims feature validation result."); pw.println(" The value could be true, false, or null(undefined)."); pw.println(" Options are:"); pw.println(" -s: The SIM slot ID to set the config value for. If no option"); pw.println(" is specified, it will choose the default voice SIM slot."); pw.println(" src get-feature-validation [-s SLOT_ID]"); pw.println(" Gets ims feature validation override value."); pw.println(" Options are:"); pw.println(" -s: The SIM slot ID to read the config value for. If no option"); pw.println(" is specified, it will choose the default voice SIM slot."); } private void onHelpAllowedNetworkTypes() { PrintWriter pw = getOutPrintWriter(); pw.println("Allowed Network Types Commands:"); pw.println(" get-allowed-network-types-for-users [-s SLOT_ID]"); pw.println(" Print allowed network types value."); pw.println(" Options are:"); pw.println(" -s: The SIM slot ID to read allowed network types value for. If no"); pw.println(" option is specified, it will choose the default voice SIM slot."); pw.println(" set-allowed-network-types-for-users [-s SLOT_ID] [NETWORK_TYPES_BITMASK]"); pw.println(" Sets allowed network types to NETWORK_TYPES_BITMASK."); pw.println(" Options are:"); pw.println(" -s: The SIM slot ID to set allowed network types value for. If no"); pw.println(" option is specified, it will choose the default voice SIM slot."); pw.println(" NETWORK_TYPES_BITMASK specifies the new network types value and this type"); pw.println(" is bitmask in binary format. Reference the NetworkTypeBitMask"); pw.println(" at TelephonyManager.java"); pw.println(" For example:"); pw.println(" NR only : 10000000000000000000"); pw.println(" NR|LTE : 11000001000000000000"); pw.println(" NR|LTE|CDMA|EVDO|GSM|WCDMA : 11001111101111111111"); pw.println(" LTE|CDMA|EVDO|GSM|WCDMA : 01001111101111111111"); pw.println(" LTE only : 01000001000000000000"); } private int handleImsCommand() { String arg = getNextArg(); if (arg == null) { onHelpIms(); return 0; } switch (arg) { case IMS_SET_IMS_SERVICE: { return handleImsSetServiceCommand(); } case IMS_GET_IMS_SERVICE: { return handleImsGetServiceCommand(); } case IMS_CLEAR_SERVICE_OVERRIDE: { return handleImsClearCarrierServiceCommand(); } case ENABLE: { return handleEnableIms(); } case DISABLE: { return handleDisableIms(); } case IMS_CEP: { return handleCepChange(); } } return -1; } private int handleDataTestModeCommand() { PrintWriter errPw = getErrPrintWriter(); String arg = getNextArgRequired(); if (arg == null) { onHelpDataTestMode(); return 0; } switch (arg) { case ENABLE: { try { mInterface.enableDataConnectivity(); } catch (RemoteException ex) { Log.w(LOG_TAG, "data enable, error " + ex.getMessage()); errPw.println("Exception: " + ex.getMessage()); return -1; } break; } case DISABLE: { try { mInterface.disableDataConnectivity(); } catch (RemoteException ex) { Log.w(LOG_TAG, "data disable, error " + ex.getMessage()); errPw.println("Exception: " + ex.getMessage()); return -1; } break; } default: onHelpDataTestMode(); break; } return 0; } private int handleEmergencyCallbackModeCommand() { PrintWriter errPw = getErrPrintWriter(); try { mInterface.startEmergencyCallbackMode(); Log.d(LOG_TAG, "handleEmergencyCallbackModeCommand: triggered"); } catch (RemoteException ex) { Log.w(LOG_TAG, "emergency-callback-mode error: " + ex.getMessage()); errPw.println("Exception: " + ex.getMessage()); return -1; } return 0; } private int handleEmergencyNumberTestModeCommand() { PrintWriter errPw = getErrPrintWriter(); String opt = getNextOption(); if (opt == null) { onHelpEmergencyNumber(); return 0; } switch (opt) { case "-a": { String emergencyNumberCmd = getNextArgRequired(); if (emergencyNumberCmd == null || !EmergencyNumber.validateEmergencyNumberAddress(emergencyNumberCmd)) { errPw.println("An emergency number (only allow '0'-'9', '*', '#' or '+') needs" + " to be specified after -a in the command "); return -1; } try { mInterface.updateEmergencyNumberListTestMode( EmergencyNumberTracker.ADD_EMERGENCY_NUMBER_TEST_MODE, new EmergencyNumber(emergencyNumberCmd, "", "", EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED, new ArrayList(), EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST, EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN)); } catch (RemoteException ex) { Log.w(LOG_TAG, "emergency-number-test-mode -a " + emergencyNumberCmd + ", error " + ex.getMessage()); errPw.println("Exception: " + ex.getMessage()); return -1; } break; } case "-c": { try { mInterface.updateEmergencyNumberListTestMode( EmergencyNumberTracker.RESET_EMERGENCY_NUMBER_TEST_MODE, null); } catch (RemoteException ex) { Log.w(LOG_TAG, "emergency-number-test-mode -c " + "error " + ex.getMessage()); errPw.println("Exception: " + ex.getMessage()); return -1; } break; } case "-r": { String emergencyNumberCmd = getNextArgRequired(); if (emergencyNumberCmd == null || !EmergencyNumber.validateEmergencyNumberAddress(emergencyNumberCmd)) { errPw.println("An emergency number (only allow '0'-'9', '*', '#' or '+') needs" + " to be specified after -r in the command "); return -1; } try { mInterface.updateEmergencyNumberListTestMode( EmergencyNumberTracker.REMOVE_EMERGENCY_NUMBER_TEST_MODE, new EmergencyNumber(emergencyNumberCmd, "", "", EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED, new ArrayList(), EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST, EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN)); } catch (RemoteException ex) { Log.w(LOG_TAG, "emergency-number-test-mode -r " + emergencyNumberCmd + ", error " + ex.getMessage()); errPw.println("Exception: " + ex.getMessage()); return -1; } break; } case "-p": { try { getOutPrintWriter().println(mInterface.getEmergencyNumberListTestMode()); } catch (RemoteException ex) { Log.w(LOG_TAG, "emergency-number-test-mode -p " + "error " + ex.getMessage()); errPw.println("Exception: " + ex.getMessage()); return -1; } break; } default: onHelpEmergencyNumber(); break; } return 0; } private int handleNumberVerificationCommand() { String arg = getNextArg(); if (arg == null) { onHelpNumberVerification(); return 0; } if (!checkShellUid()) { return -1; } switch (arg) { case NUMBER_VERIFICATION_OVERRIDE_PACKAGE: { NumberVerificationManager.overrideAuthorizedPackage(getNextArg()); return 0; } case NUMBER_VERIFICATION_FAKE_CALL: { boolean val = NumberVerificationManager.getInstance() .checkIncomingCall(getNextArg()); getOutPrintWriter().println(val ? "1" : "0"); return 0; } } return -1; } private boolean subIsEsim(int subId) { SubscriptionInfo info = mSubscriptionManager.getActiveSubscriptionInfo(subId); if (info != null) { return info.isEmbedded(); } return false; } private int handleEnablePhysicalSubscription(boolean enable) { PrintWriter errPw = getErrPrintWriter(); int subId = 0; try { subId = Integer.parseInt(getNextArgRequired()); } catch (NumberFormatException e) { errPw.println((enable ? "enable" : "disable") + "-physical-subscription requires an integer as a subId."); return -1; } // Verify that the user is allowed to run the command. Only allowed in rooted device in a // non user build. if (Binder.getCallingUid() != Process.ROOT_UID || TelephonyUtils.IS_USER) { errPw.println("cc: Permission denied."); return -1; } // Verify that the subId represents a physical sub if (subIsEsim(subId)) { errPw.println("SubId " + subId + " is not for a physical subscription"); return -1; } Log.d(LOG_TAG, (enable ? "Enabling" : "Disabling") + " physical subscription with subId=" + subId); mSubscriptionManager.setUiccApplicationsEnabled(subId, enable); return 0; } private int handleThermalMitigationCommand() { String arg = getNextArg(); String packageName = getNextArg(); if (arg == null || packageName == null) { onHelpThermalMitigation(); return 0; } if (!checkShellUid()) { return -1; } switch (arg) { case ALLOW_THERMAL_MITIGATION_PACKAGE_SUBCOMMAND: { PhoneInterfaceManager.addPackageToThermalMitigationAllowlist(packageName, mContext); return 0; } case DISALLOW_THERMAL_MITIGATION_PACKAGE_SUBCOMMAND: { PhoneInterfaceManager.removePackageFromThermalMitigationAllowlist(packageName, mContext); return 0; } default: onHelpThermalMitigation(); } return -1; } private int handleD2dCommand() { String arg = getNextArg(); if (arg == null) { onHelpD2D(); return 0; } switch (arg) { case D2D_SEND: { return handleD2dSendCommand(); } case D2D_TRANSPORT: { return handleD2dTransportCommand(); } case D2D_SET_DEVICE_SUPPORT: { return handleD2dDeviceSupportedCommand(); } } return -1; } private int handleD2dSendCommand() { PrintWriter errPw = getErrPrintWriter(); int messageType = -1; int messageValue = -1; String arg = getNextArg(); if (arg == null) { onHelpD2D(); return 0; } try { messageType = Integer.parseInt(arg); } catch (NumberFormatException e) { errPw.println("message type must be a valid integer"); return -1; } arg = getNextArg(); if (arg == null) { onHelpD2D(); return 0; } try { messageValue = Integer.parseInt(arg); } catch (NumberFormatException e) { errPw.println("message value must be a valid integer"); return -1; } try { mInterface.sendDeviceToDeviceMessage(messageType, messageValue); } catch (RemoteException e) { Log.w(LOG_TAG, "d2d send error: " + e.getMessage()); errPw.println("Exception: " + e.getMessage()); return -1; } return 0; } private int handleD2dTransportCommand() { PrintWriter errPw = getErrPrintWriter(); String arg = getNextArg(); if (arg == null) { onHelpD2D(); return 0; } try { mInterface.setActiveDeviceToDeviceTransport(arg); } catch (RemoteException e) { Log.w(LOG_TAG, "d2d transport error: " + e.getMessage()); errPw.println("Exception: " + e.getMessage()); return -1; } return 0; } private int handleBarringCommand() { String arg = getNextArg(); if (arg == null) { onHelpBarring(); return 0; } switch (arg) { case BARRING_SEND_INFO: { return handleBarringSendCommand(); } } return -1; } private int handleBarringSendCommand() { PrintWriter errPw = getErrPrintWriter(); int slotId = getDefaultSlot(); int subId = SubscriptionManager.getSubId(slotId)[0]; @BarringInfo.BarringServiceInfo.BarringType int barringType = BarringInfo.BarringServiceInfo.BARRING_TYPE_UNCONDITIONAL; boolean isConditionallyBarred = false; int conditionalBarringTimeSeconds = 0; String opt; while ((opt = getNextOption()) != null) { switch (opt) { case "-s": { try { slotId = Integer.parseInt(getNextArgRequired()); subId = SubscriptionManager.getSubId(slotId)[0]; } catch (NumberFormatException e) { errPw.println("barring send requires an integer as a SLOT_ID."); return -1; } break; } case "-b": { try { barringType = Integer.parseInt(getNextArgRequired()); if (barringType < -1 || barringType > 2) { throw new NumberFormatException(); } } catch (NumberFormatException e) { errPw.println("barring send requires an integer in range [-1,2] as " + "a BARRING_TYPE."); return -1; } break; } case "-c": { try { isConditionallyBarred = Boolean.parseBoolean(getNextArgRequired()); } catch (Exception e) { errPw.println("barring send requires a boolean after -c indicating" + " conditional barring"); return -1; } break; } case "-t": { try { conditionalBarringTimeSeconds = Integer.parseInt(getNextArgRequired()); } catch (NumberFormatException e) { errPw.println("barring send requires an integer for time of barring" + " in seconds after -t for conditional barring"); return -1; } break; } } } SparseArray barringServiceInfos = new SparseArray<>(); BarringInfo.BarringServiceInfo bsi = new BarringInfo.BarringServiceInfo( barringType, isConditionallyBarred, 0, conditionalBarringTimeSeconds); barringServiceInfos.append(0, bsi); BarringInfo barringInfo = new BarringInfo(null, barringServiceInfos); try { mTelephonyRegistryManager.notifyBarringInfoChanged(slotId, subId, barringInfo); } catch (Exception e) { Log.w(LOG_TAG, "barring send error: " + e.getMessage()); errPw.println("Exception: " + e.getMessage()); return -1; } return 0; } private int handleD2dDeviceSupportedCommand() { PrintWriter errPw = getErrPrintWriter(); String arg = getNextArg(); if (arg == null) { onHelpD2D(); return 0; } boolean isEnabled = "true".equals(arg.toLowerCase()); try { mInterface.setDeviceToDeviceForceEnabled(isEnabled); } catch (RemoteException e) { Log.w(LOG_TAG, "Error forcing D2D enabled: " + e.getMessage()); errPw.println("Exception: " + e.getMessage()); return -1; } return 0; } // ims set-ims-service private int handleImsSetServiceCommand() { PrintWriter errPw = getErrPrintWriter(); int slotId = getDefaultSlot(); Boolean isCarrierService = null; List featuresList = new ArrayList<>(); String opt; while ((opt = getNextOption()) != null) { switch (opt) { case "-s": { try { slotId = Integer.parseInt(getNextArgRequired()); } catch (NumberFormatException e) { errPw.println("ims set-ims-service requires an integer as a SLOT_ID."); return -1; } break; } case "-c": { isCarrierService = true; break; } case "-d": { isCarrierService = false; break; } case "-f": { String featureString = getNextArgRequired(); String[] features = featureString.split(","); for (int i = 0; i < features.length; i++) { try { Integer result = Integer.parseInt(features[i]); if (result < ImsFeature.FEATURE_EMERGENCY_MMTEL || result >= ImsFeature.FEATURE_MAX) { errPw.println("ims set-ims-service -f " + result + " is an invalid feature."); return -1; } featuresList.add(result); } catch (NumberFormatException e) { errPw.println("ims set-ims-service -f tried to parse " + features[i] + " as an integer."); return -1; } } } } } // Mandatory param, either -c or -d if (isCarrierService == null) { errPw.println("ims set-ims-service requires either \"-c\" or \"-d\" to be set."); return -1; } String packageName = getNextArg(); try { if (packageName == null) { packageName = ""; } int[] featureArray = new int[featuresList.size()]; for (int i = 0; i < featuresList.size(); i++) { featureArray[i] = featuresList.get(i); } boolean result = mInterface.setBoundImsServiceOverride(slotId, isCarrierService, featureArray, packageName); if (VDBG) { Log.v(LOG_TAG, "ims set-ims-service -s " + slotId + " " + (isCarrierService ? "-c " : "-d ") + "-f " + featuresList + " " + packageName + ", result=" + result); } getOutPrintWriter().println(result); } catch (RemoteException e) { Log.w(LOG_TAG, "ims set-ims-service -s " + slotId + " " + (isCarrierService ? "-c " : "-d ") + "-f " + featuresList + " " + packageName + ", error" + e.getMessage()); errPw.println("Exception: " + e.getMessage()); return -1; } return 0; } // ims clear-ims-service-override private int handleImsClearCarrierServiceCommand() { PrintWriter errPw = getErrPrintWriter(); int slotId = getDefaultSlot(); String opt; while ((opt = getNextOption()) != null) { switch (opt) { case "-s": { try { slotId = Integer.parseInt(getNextArgRequired()); } catch (NumberFormatException e) { errPw.println("ims set-ims-service requires an integer as a SLOT_ID."); return -1; } break; } } } try { boolean result = mInterface.clearCarrierImsServiceOverride(slotId); if (VDBG) { Log.v(LOG_TAG, "ims clear-ims-service-override -s " + slotId + ", result=" + result); } getOutPrintWriter().println(result); } catch (RemoteException e) { Log.w(LOG_TAG, "ims clear-ims-service-override -s " + slotId + ", error" + e.getMessage()); errPw.println("Exception: " + e.getMessage()); return -1; } return 0; } // ims get-ims-service private int handleImsGetServiceCommand() { PrintWriter errPw = getErrPrintWriter(); int slotId = getDefaultSlot(); Boolean isCarrierService = null; Integer featureType = ImsFeature.FEATURE_MMTEL; String opt; while ((opt = getNextOption()) != null) { switch (opt) { case "-s": { try { slotId = Integer.parseInt(getNextArgRequired()); } catch (NumberFormatException e) { errPw.println("ims set-ims-service requires an integer as a SLOT_ID."); return -1; } break; } case "-c": { isCarrierService = true; break; } case "-d": { isCarrierService = false; break; } case "-f": { try { featureType = Integer.parseInt(getNextArg()); } catch (NumberFormatException e) { errPw.println("ims get-ims-service -f requires valid integer as feature."); return -1; } if (featureType < ImsFeature.FEATURE_EMERGENCY_MMTEL || featureType >= ImsFeature.FEATURE_MAX) { errPw.println("ims get-ims-service -f invalid feature."); return -1; } } } } // Mandatory param, either -c or -d if (isCarrierService == null) { errPw.println("ims get-ims-service requires either \"-c\" or \"-d\" to be set."); return -1; } String result; try { result = mInterface.getBoundImsServicePackage(slotId, isCarrierService, featureType); } catch (RemoteException e) { return -1; } if (VDBG) { Log.v(LOG_TAG, "ims get-ims-service -s " + slotId + " " + (isCarrierService ? "-c " : "-d ") + (featureType != null ? ("-f " + featureType) : "") + " , returned: " + result); } getOutPrintWriter().println(result); return 0; } private int handleEnableIms() { int slotId = getDefaultSlot(); String opt; while ((opt = getNextOption()) != null) { switch (opt) { case "-s": { try { slotId = Integer.parseInt(getNextArgRequired()); } catch (NumberFormatException e) { getErrPrintWriter().println("ims enable requires an integer as a SLOT_ID."); return -1; } break; } } } try { mInterface.enableIms(slotId); } catch (RemoteException e) { return -1; } if (VDBG) { Log.v(LOG_TAG, "ims enable -s " + slotId); } return 0; } private int handleDisableIms() { int slotId = getDefaultSlot(); String opt; while ((opt = getNextOption()) != null) { switch (opt) { case "-s": { try { slotId = Integer.parseInt(getNextArgRequired()); } catch (NumberFormatException e) { getErrPrintWriter().println( "ims disable requires an integer as a SLOT_ID."); return -1; } break; } } } try { mInterface.disableIms(slotId); } catch (RemoteException e) { return -1; } if (VDBG) { Log.v(LOG_TAG, "ims disable -s " + slotId); } return 0; } private int handleCepChange() { Log.i(LOG_TAG, "handleCepChange"); String opt = getNextArg(); if (opt == null) { return -1; } boolean isCepEnabled = opt.equals("enable"); try { mInterface.setCepEnabled(isCepEnabled); } catch (RemoteException e) { return -1; } return 0; } private int getDefaultSlot() { int slotId = SubscriptionManager.getDefaultVoicePhoneId(); if (slotId <= SubscriptionManager.INVALID_SIM_SLOT_INDEX || slotId == SubscriptionManager.DEFAULT_PHONE_INDEX) { // If there is no default, default to slot 0. slotId = DEFAULT_PHONE_ID; } return slotId; } // Parse options related to Carrier Config Commands. private CcOptionParseResult parseCcOptions(String tag, boolean allowOptionPersistent) { PrintWriter errPw = getErrPrintWriter(); CcOptionParseResult result = new CcOptionParseResult(); result.mSubId = SubscriptionManager.getDefaultSubscriptionId(); result.mPersistent = false; String opt; while ((opt = getNextOption()) != null) { switch (opt) { case "-s": { try { result.mSubId = slotStringToSubId(tag, getNextArgRequired()); if (!SubscriptionManager.isValidSubscriptionId(result.mSubId)) { errPw.println(tag + "No valid subscription found."); return null; } } catch (IllegalArgumentException e) { // Missing slot id errPw.println(tag + "SLOT_ID expected after -s."); return null; } break; } case "-p": { if (allowOptionPersistent) { result.mPersistent = true; } else { errPw.println(tag + "Unexpected option " + opt); return null; } break; } default: { errPw.println(tag + "Unknown option " + opt); return null; } } } return result; } private int slotStringToSubId(String tag, String slotString) { int slotId = -1; try { slotId = Integer.parseInt(slotString); } catch (NumberFormatException e) { getErrPrintWriter().println(tag + slotString + " is not a valid number for SLOT_ID."); return SubscriptionManager.INVALID_SUBSCRIPTION_ID; } if (!SubscriptionManager.isValidPhoneId(slotId)) { getErrPrintWriter().println(tag + slotString + " is not a valid SLOT_ID."); return SubscriptionManager.INVALID_SUBSCRIPTION_ID; } Phone phone = PhoneFactory.getPhone(slotId); if (phone == null) { getErrPrintWriter().println(tag + "No subscription found in slot " + slotId + "."); return SubscriptionManager.INVALID_SUBSCRIPTION_ID; } return phone.getSubId(); } private boolean checkShellUid() { // adb can run as root or as shell, depending on whether the device is rooted. return Binder.getCallingUid() == Process.SHELL_UID || Binder.getCallingUid() == Process.ROOT_UID; } private int handleCcCommand() { // Verify that the user is allowed to run the command. Only allowed in rooted device in a // non user build. if (Binder.getCallingUid() != Process.ROOT_UID || TelephonyUtils.IS_USER) { getErrPrintWriter().println("cc: Permission denied."); return -1; } String arg = getNextArg(); if (arg == null) { onHelpCc(); return 0; } switch (arg) { case CC_GET_VALUE: { return handleCcGetValue(); } case CC_SET_VALUE: { return handleCcSetValue(); } case CC_CLEAR_VALUES: { return handleCcClearValues(); } default: { getErrPrintWriter().println("cc: Unknown argument: " + arg); } } return -1; } // cc get-value private int handleCcGetValue() { PrintWriter errPw = getErrPrintWriter(); String tag = CARRIER_CONFIG_SUBCOMMAND + " " + CC_GET_VALUE + ": "; String key = null; // Parse all options CcOptionParseResult options = parseCcOptions(tag, false); if (options == null) { return -1; } // Get bundle containing all carrier configuration values. PersistableBundle bundle = mCarrierConfigManager.getConfigForSubId(options.mSubId); if (bundle == null) { errPw.println(tag + "No carrier config values found for subId " + options.mSubId + "."); return -1; } // Get the key. key = getNextArg(); if (key != null) { // A key was provided. Verify if it is a valid key if (!bundle.containsKey(key)) { errPw.println(tag + key + " is not a valid key."); return -1; } // Print the carrier config value for key. getOutPrintWriter().println(ccValueToString(key, getType(tag, key, bundle), bundle)); } else { // No key provided. Show all values. // Iterate over a sorted list of all carrier config keys and print them. TreeSet sortedSet = new TreeSet(bundle.keySet()); for (String k : sortedSet) { getOutPrintWriter().println(ccValueToString(k, getType(tag, k, bundle), bundle)); } } return 0; } // cc set-value private int handleCcSetValue() { PrintWriter errPw = getErrPrintWriter(); String tag = CARRIER_CONFIG_SUBCOMMAND + " " + CC_SET_VALUE + ": "; // Parse all options CcOptionParseResult options = parseCcOptions(tag, true); if (options == null) { return -1; } // Get bundle containing all current carrier configuration values. PersistableBundle originalValues = mCarrierConfigManager.getConfigForSubId(options.mSubId); if (originalValues == null) { errPw.println(tag + "No carrier config values found for subId " + options.mSubId + "."); return -1; } // Get the key. String key = getNextArg(); if (key == null || key.equals("")) { errPw.println(tag + "KEY is missing"); return -1; } // Verify if the key is valid if (!originalValues.containsKey(key)) { errPw.println(tag + key + " is not a valid key."); return -1; } // Remaining arguments is a list of new values. Add them all into an ArrayList. ArrayList valueList = new ArrayList(); while (peekNextArg() != null) { valueList.add(getNextArg()); } // Find the type of the carrier config value CcType type = getType(tag, key, originalValues); if (type == CcType.UNKNOWN) { errPw.println(tag + "ERROR: Not possible to override key with unknown type."); return -1; } // Create an override bundle containing the key and value that should be overriden. PersistableBundle overrideBundle = getOverrideBundle(tag, type, key, valueList); if (overrideBundle == null) { return -1; } // Override the value mCarrierConfigManager.overrideConfig(options.mSubId, overrideBundle, options.mPersistent); // Find bundle containing all new carrier configuration values after the override. PersistableBundle newValues = mCarrierConfigManager.getConfigForSubId(options.mSubId); if (newValues == null) { errPw.println(tag + "No carrier config values found for subId " + options.mSubId + "."); return -1; } // Print the original and new value. String originalValueString = ccValueToString(key, type, originalValues); String newValueString = ccValueToString(key, type, newValues); getOutPrintWriter().println("Previous value: \n" + originalValueString); getOutPrintWriter().println("New value: \n" + newValueString); return 0; } // cc clear-values private int handleCcClearValues() { PrintWriter errPw = getErrPrintWriter(); String tag = CARRIER_CONFIG_SUBCOMMAND + " " + CC_CLEAR_VALUES + ": "; // Parse all options CcOptionParseResult options = parseCcOptions(tag, false); if (options == null) { return -1; } // Clear all values that has previously been set. mCarrierConfigManager.overrideConfig(options.mSubId, null, true); getOutPrintWriter() .println("All previously set carrier config override values has been cleared"); return 0; } private CcType getType(String tag, String key, PersistableBundle bundle) { // Find the type by checking the type of the current value stored in the bundle. Object value = bundle.get(key); if (CC_TYPE_MAP.containsKey(key)) { return CC_TYPE_MAP.get(key); } else if (value != null) { if (value instanceof Boolean) { return CcType.BOOLEAN; } else if (value instanceof Double) { return CcType.DOUBLE; } else if (value instanceof double[]) { return CcType.DOUBLE_ARRAY; } else if (value instanceof Integer) { return CcType.INT; } else if (value instanceof int[]) { return CcType.INT_ARRAY; } else if (value instanceof Long) { return CcType.LONG; } else if (value instanceof long[]) { return CcType.LONG_ARRAY; } else if (value instanceof String) { return CcType.STRING; } else if (value instanceof String[]) { return CcType.STRING_ARRAY; } } else { // Current value was null and can therefore not be used in order to find the type. // Check the name of the key to infer the type. This check is not needed for primitive // data types (boolean, double, int and long), since they can not be null. if (key.endsWith("double_array")) { return CcType.DOUBLE_ARRAY; } if (key.endsWith("int_array")) { return CcType.INT_ARRAY; } if (key.endsWith("long_array")) { return CcType.LONG_ARRAY; } if (key.endsWith("string")) { return CcType.STRING; } if (key.endsWith("string_array") || key.endsWith("strings")) { return CcType.STRING_ARRAY; } } // Not possible to infer the type by looking at the current value or the key. PrintWriter errPw = getErrPrintWriter(); errPw.println(tag + "ERROR: " + key + " has unknown type."); return CcType.UNKNOWN; } private String ccValueToString(String key, CcType type, PersistableBundle bundle) { String result; StringBuilder valueString = new StringBuilder(); String typeString = type.toString(); Object value = bundle.get(key); if (value == null) { valueString.append("null"); } else { switch (type) { case DOUBLE_ARRAY: { // Format the string representation of the int array as value1 value2...... double[] valueArray = (double[]) value; for (int i = 0; i < valueArray.length; i++) { if (i != 0) { valueString.append(" "); } valueString.append(valueArray[i]); } break; } case INT_ARRAY: { // Format the string representation of the int array as value1 value2...... int[] valueArray = (int[]) value; for (int i = 0; i < valueArray.length; i++) { if (i != 0) { valueString.append(" "); } valueString.append(valueArray[i]); } break; } case LONG_ARRAY: { // Format the string representation of the int array as value1 value2...... long[] valueArray = (long[]) value; for (int i = 0; i < valueArray.length; i++) { if (i != 0) { valueString.append(" "); } valueString.append(valueArray[i]); } break; } case STRING: { valueString.append("\"" + value.toString() + "\""); break; } case STRING_ARRAY: { // Format the string representation of the string array as "value1" "value2".... String[] valueArray = (String[]) value; for (int i = 0; i < valueArray.length; i++) { if (i != 0) { valueString.append(" "); } if (valueArray[i] != null) { valueString.append("\"" + valueArray[i] + "\""); } else { valueString.append("null"); } } break; } default: { valueString.append(value.toString()); } } } return String.format("%-70s %-15s %s", key, typeString, valueString); } private PersistableBundle getOverrideBundle(String tag, CcType type, String key, ArrayList valueList) { PrintWriter errPw = getErrPrintWriter(); PersistableBundle bundle = new PersistableBundle(); // First verify that a valid number of values has been provided for the type. switch (type) { case BOOLEAN: case DOUBLE: case INT: case LONG: { if (valueList.size() != 1) { errPw.println(tag + "Expected 1 value for type " + type + ". Found: " + valueList.size()); return null; } break; } case STRING: { if (valueList.size() > 1) { errPw.println(tag + "Expected 0 or 1 values for type " + type + ". Found: " + valueList.size()); return null; } break; } } // Parse the value according to type and add it to the Bundle. switch (type) { case BOOLEAN: { if ("true".equalsIgnoreCase(valueList.get(0))) { bundle.putBoolean(key, true); } else if ("false".equalsIgnoreCase(valueList.get(0))) { bundle.putBoolean(key, false); } else { errPw.println(tag + "Unable to parse " + valueList.get(0) + " as a " + type); return null; } break; } case DOUBLE: { try { bundle.putDouble(key, Double.parseDouble(valueList.get(0))); } catch (NumberFormatException nfe) { // Not a valid double errPw.println(tag + "Unable to parse " + valueList.get(0) + " as a " + type); return null; } break; } case DOUBLE_ARRAY: { double[] valueDoubleArray = null; if (valueList.size() > 0) { valueDoubleArray = new double[valueList.size()]; for (int i = 0; i < valueList.size(); i++) { try { valueDoubleArray[i] = Double.parseDouble(valueList.get(i)); } catch (NumberFormatException nfe) { // Not a valid double errPw.println( tag + "Unable to parse " + valueList.get(i) + " as a double."); return null; } } } bundle.putDoubleArray(key, valueDoubleArray); break; } case INT: { try { bundle.putInt(key, Integer.parseInt(valueList.get(0))); } catch (NumberFormatException nfe) { // Not a valid integer errPw.println(tag + "Unable to parse " + valueList.get(0) + " as an " + type); return null; } break; } case INT_ARRAY: { int[] valueIntArray = null; if (valueList.size() > 0) { valueIntArray = new int[valueList.size()]; for (int i = 0; i < valueList.size(); i++) { try { valueIntArray[i] = Integer.parseInt(valueList.get(i)); } catch (NumberFormatException nfe) { // Not a valid integer errPw.println(tag + "Unable to parse " + valueList.get(i) + " as an integer."); return null; } } } bundle.putIntArray(key, valueIntArray); break; } case LONG: { try { bundle.putLong(key, Long.parseLong(valueList.get(0))); } catch (NumberFormatException nfe) { // Not a valid long errPw.println(tag + "Unable to parse " + valueList.get(0) + " as a " + type); return null; } break; } case LONG_ARRAY: { long[] valueLongArray = null; if (valueList.size() > 0) { valueLongArray = new long[valueList.size()]; for (int i = 0; i < valueList.size(); i++) { try { valueLongArray[i] = Long.parseLong(valueList.get(i)); } catch (NumberFormatException nfe) { // Not a valid long errPw.println( tag + "Unable to parse " + valueList.get(i) + " as a long"); return null; } } } bundle.putLongArray(key, valueLongArray); break; } case STRING: { String value = null; if (valueList.size() > 0) { value = valueList.get(0); } bundle.putString(key, value); break; } case STRING_ARRAY: { String[] valueStringArray = null; if (valueList.size() > 0) { valueStringArray = new String[valueList.size()]; valueList.toArray(valueStringArray); } bundle.putStringArray(key, valueStringArray); break; } } return bundle; } private int handleEndBlockSuppressionCommand() { if (!checkShellUid()) { return -1; } if (BlockedNumberContract.SystemContract.getBlockSuppressionStatus(mContext).isSuppressed) { BlockedNumberContract.SystemContract.endBlockSuppression(mContext); } return 0; } private int handleRestartModemCommand() { // Verify that the user is allowed to run the command. Only allowed in rooted device in a // non user build. if (Binder.getCallingUid() != Process.ROOT_UID || TelephonyUtils.IS_USER) { getErrPrintWriter().println("RestartModem: Permission denied."); return -1; } boolean result = TelephonyManager.getDefault().rebootRadio(); getOutPrintWriter().println(result); return result ? 0 : -1; } private int handleUnattendedReboot() { // Verify that the user is allowed to run the command. Only allowed in rooted device in a // non user build. if (Binder.getCallingUid() != Process.ROOT_UID || TelephonyUtils.IS_USER) { getErrPrintWriter().println("UnattendedReboot: Permission denied."); return -1; } int result = TelephonyManager.getDefault().prepareForUnattendedReboot(); getOutPrintWriter().println("result: " + result); return result != TelephonyManager.PREPARE_UNATTENDED_REBOOT_ERROR ? 0 : -1; } private int handleGbaCommand() { String arg = getNextArg(); if (arg == null) { onHelpGba(); return 0; } switch (arg) { case GBA_SET_SERVICE: { return handleGbaSetServiceCommand(); } case GBA_GET_SERVICE: { return handleGbaGetServiceCommand(); } case GBA_SET_RELEASE_TIME: { return handleGbaSetReleaseCommand(); } case GBA_GET_RELEASE_TIME: { return handleGbaGetReleaseCommand(); } } return -1; } private int getSubId(String cmd) { int slotId = getDefaultSlot(); String opt = getNextOption(); if (opt != null && opt.equals("-s")) { try { slotId = Integer.parseInt(getNextArgRequired()); } catch (NumberFormatException e) { getErrPrintWriter().println(cmd + " requires an integer as a SLOT_ID."); return SubscriptionManager.INVALID_SUBSCRIPTION_ID; } } int[] subIds = SubscriptionManager.getSubId(slotId); return subIds[0]; } private int handleGbaSetServiceCommand() { int subId = getSubId("gba set-service"); if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { return -1; } String packageName = getNextArg(); try { if (packageName == null) { packageName = ""; } boolean result = mInterface.setBoundGbaServiceOverride(subId, packageName); if (VDBG) { Log.v(LOG_TAG, "gba set-service -s " + subId + " " + packageName + ", result=" + result); } getOutPrintWriter().println(result); } catch (RemoteException e) { Log.w(LOG_TAG, "gba set-service " + subId + " " + packageName + ", error" + e.getMessage()); getErrPrintWriter().println("Exception: " + e.getMessage()); return -1; } return 0; } private int handleGbaGetServiceCommand() { String result; int subId = getSubId("gba get-service"); if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { return -1; } try { result = mInterface.getBoundGbaService(subId); } catch (RemoteException e) { return -1; } if (VDBG) { Log.v(LOG_TAG, "gba get-service -s " + subId + ", returned: " + result); } getOutPrintWriter().println(result); return 0; } private int handleGbaSetReleaseCommand() { //the release time value could be -1 int subId = getRemainingArgsCount() > 1 ? getSubId("gba set-release") : SubscriptionManager.getDefaultSubscriptionId(); if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { return -1; } String intervalStr = getNextArg(); if (intervalStr == null) { return -1; } try { int interval = Integer.parseInt(intervalStr); boolean result = mInterface.setGbaReleaseTimeOverride(subId, interval); if (VDBG) { Log.v(LOG_TAG, "gba set-release -s " + subId + " " + intervalStr + ", result=" + result); } getOutPrintWriter().println(result); } catch (NumberFormatException | RemoteException e) { Log.w(LOG_TAG, "gba set-release -s " + subId + " " + intervalStr + ", error" + e.getMessage()); getErrPrintWriter().println("Exception: " + e.getMessage()); return -1; } return 0; } private int handleGbaGetReleaseCommand() { int subId = getSubId("gba get-release"); if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { return -1; } int result = 0; try { result = mInterface.getGbaReleaseTime(subId); } catch (RemoteException e) { return -1; } if (VDBG) { Log.v(LOG_TAG, "gba get-release -s " + subId + ", returned: " + result); } getOutPrintWriter().println(result); return 0; } private int handleSingleRegistrationConfigCommand() { String arg = getNextArg(); if (arg == null) { onHelpSrc(); return 0; } switch (arg) { case SRC_SET_TEST_ENABLED: { return handleSrcSetTestEnabledCommand(); } case SRC_GET_TEST_ENABLED: { return handleSrcGetTestEnabledCommand(); } case SRC_SET_DEVICE_ENABLED: { return handleSrcSetDeviceEnabledCommand(); } case SRC_GET_DEVICE_ENABLED: { return handleSrcGetDeviceEnabledCommand(); } case SRC_SET_CARRIER_ENABLED: { return handleSrcSetCarrierEnabledCommand(); } case SRC_GET_CARRIER_ENABLED: { return handleSrcGetCarrierEnabledCommand(); } case SRC_SET_FEATURE_ENABLED: { return handleSrcSetFeatureValidationCommand(); } case SRC_GET_FEATURE_ENABLED: { return handleSrcGetFeatureValidationCommand(); } } return -1; } private int handleRcsUceCommand() { String arg = getNextArg(); if (arg == null) { onHelpUce(); return 0; } switch (arg) { case UCE_REMOVE_EAB_CONTACT: return handleRemovingEabContactCommand(); case UCE_GET_EAB_CONTACT: return handleGettingEabContactCommand(); case UCE_GET_EAB_CAPABILITY: return handleGettingEabCapabilityCommand(); case UCE_GET_DEVICE_ENABLED: return handleUceGetDeviceEnabledCommand(); case UCE_SET_DEVICE_ENABLED: return handleUceSetDeviceEnabledCommand(); case UCE_OVERRIDE_PUBLISH_CAPS: return handleUceOverridePublishCaps(); case UCE_GET_LAST_PIDF_XML: return handleUceGetPidfXml(); case UCE_REMOVE_REQUEST_DISALLOWED_STATUS: return handleUceRemoveRequestDisallowedStatus(); case UCE_SET_CAPABILITY_REQUEST_TIMEOUT: return handleUceSetCapRequestTimeout(); } return -1; } private int handleRemovingEabContactCommand() { int subId = getSubId("uce remove-eab-contact"); if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { return -1; } String phoneNumber = getNextArgRequired(); if (TextUtils.isEmpty(phoneNumber)) { return -1; } int result = 0; try { result = mInterface.removeContactFromEab(subId, phoneNumber); } catch (RemoteException e) { Log.w(LOG_TAG, "uce remove-eab-contact -s " + subId + ", error " + e.getMessage()); getErrPrintWriter().println("Exception: " + e.getMessage()); return -1; } if (VDBG) { Log.v(LOG_TAG, "uce remove-eab-contact -s " + subId + ", result: " + result); } return 0; } private int handleGettingEabContactCommand() { String phoneNumber = getNextArgRequired(); if (TextUtils.isEmpty(phoneNumber)) { return -1; } String result = ""; try { result = mInterface.getContactFromEab(phoneNumber); } catch (RemoteException e) { Log.w(LOG_TAG, "uce get-eab-contact, error " + e.getMessage()); getErrPrintWriter().println("Exception: " + e.getMessage()); return -1; } if (VDBG) { Log.v(LOG_TAG, "uce get-eab-contact, result: " + result); } getOutPrintWriter().println(result); return 0; } private int handleGettingEabCapabilityCommand() { String phoneNumber = getNextArgRequired(); if (TextUtils.isEmpty(phoneNumber)) { return -1; } String result = ""; try { result = mInterface.getCapabilityFromEab(phoneNumber); } catch (RemoteException e) { Log.w(LOG_TAG, "uce get-eab-capability, error " + e.getMessage()); getErrPrintWriter().println("Exception: " + e.getMessage()); return -1; } if (VDBG) { Log.v(LOG_TAG, "uce get-eab-capability, result: " + result); } getOutPrintWriter().println(result); return 0; } private int handleUceGetDeviceEnabledCommand() { boolean result = false; try { result = mInterface.getDeviceUceEnabled(); } catch (RemoteException e) { Log.w(LOG_TAG, "uce get-device-enabled, error " + e.getMessage()); return -1; } if (VDBG) { Log.v(LOG_TAG, "uce get-device-enabled, returned: " + result); } getOutPrintWriter().println(result); return 0; } private int handleUceSetDeviceEnabledCommand() { String enabledStr = getNextArg(); if (TextUtils.isEmpty(enabledStr)) { return -1; } try { boolean isEnabled = Boolean.parseBoolean(enabledStr); mInterface.setDeviceUceEnabled(isEnabled); if (VDBG) { Log.v(LOG_TAG, "uce set-device-enabled " + enabledStr + ", done"); } } catch (NumberFormatException | RemoteException e) { Log.w(LOG_TAG, "uce set-device-enabled " + enabledStr + ", error " + e.getMessage()); getErrPrintWriter().println("Exception: " + e.getMessage()); return -1; } return 0; } private int handleUceRemoveRequestDisallowedStatus() { int subId = getSubId("uce remove-request-disallowed-status"); if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { Log.w(LOG_TAG, "uce remove-request-disallowed-status, Invalid subscription ID"); return -1; } boolean result; try { result = mInterface.removeUceRequestDisallowedStatus(subId); } catch (RemoteException e) { Log.w(LOG_TAG, "uce remove-request-disallowed-status, error " + e.getMessage()); return -1; } if (VDBG) { Log.v(LOG_TAG, "uce remove-request-disallowed-status, returned: " + result); } getOutPrintWriter().println(result); return 0; } private int handleUceSetCapRequestTimeout() { int subId = getSubId("uce set-capabilities-request-timeout"); if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { Log.w(LOG_TAG, "uce set-capabilities-request-timeout, Invalid subscription ID"); return -1; } long timeoutAfterMs = Long.valueOf(getNextArg()); boolean result; try { result = mInterface.setCapabilitiesRequestTimeout(subId, timeoutAfterMs); } catch (RemoteException e) { Log.w(LOG_TAG, "uce set-capabilities-request-timeout, error " + e.getMessage()); return -1; } if (VDBG) { Log.v(LOG_TAG, "uce set-capabilities-request-timeout, returned: " + result); } getOutPrintWriter().println(result); return 0; } private int handleSrcSetTestEnabledCommand() { String enabledStr = getNextArg(); if (enabledStr == null) { return -1; } try { mInterface.setRcsSingleRegistrationTestModeEnabled(Boolean.parseBoolean(enabledStr)); if (VDBG) { Log.v(LOG_TAG, "src set-test-enabled " + enabledStr + ", done"); } getOutPrintWriter().println("Done"); } catch (NumberFormatException | RemoteException e) { Log.w(LOG_TAG, "src set-test-enabled " + enabledStr + ", error" + e.getMessage()); getErrPrintWriter().println("Exception: " + e.getMessage()); return -1; } return 0; } private int handleSrcGetTestEnabledCommand() { boolean result = false; try { result = mInterface.getRcsSingleRegistrationTestModeEnabled(); } catch (RemoteException e) { return -1; } if (VDBG) { Log.v(LOG_TAG, "src get-test-enabled, returned: " + result); } getOutPrintWriter().println(result); return 0; } private int handleUceOverridePublishCaps() { int subId = getSubId("uce override-published-caps"); if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { return -1; } //uce override-published-caps [-s SLOT_ID] add|remove|clear|list [CAPABILITIES] String operation = getNextArgRequired(); String caps = getNextArg(); if (!"add".equals(operation) && !"remove".equals(operation) && !"clear".equals(operation) && !"list".equals(operation)) { getErrPrintWriter().println("Invalid operation: " + operation); return -1; } // add/remove requires capabilities to be specified. if ((!"clear".equals(operation) && !"list".equals(operation)) && TextUtils.isEmpty(caps)) { getErrPrintWriter().println("\"" + operation + "\" requires capabilities to be " + "specified"); return -1; } ArraySet capSet = new ArraySet<>(); if (!TextUtils.isEmpty(caps)) { String[] capArray = caps.split(":"); for (String cap : capArray) { // Allow unknown tags to be passed in as well. capSet.addAll(TEST_FEATURE_TAG_MAP.getOrDefault(cap, Collections.singleton(cap))); } } RcsContactUceCapability result = null; try { switch (operation) { case "add": result = mInterface.addUceRegistrationOverrideShell(subId, new ArrayList<>(capSet)); break; case "remove": result = mInterface.removeUceRegistrationOverrideShell(subId, new ArrayList<>(capSet)); break; case "clear": result = mInterface.clearUceRegistrationOverrideShell(subId); break; case "list": result = mInterface.getLatestRcsContactUceCapabilityShell(subId); break; } } catch (RemoteException e) { Log.w(LOG_TAG, "uce override-published-caps, error " + e.getMessage()); getErrPrintWriter().println("Exception: " + e.getMessage()); return -1; } catch (ServiceSpecificException sse) { // Reconstruct ImsException ImsException imsException = new ImsException(sse.getMessage(), sse.errorCode); Log.w(LOG_TAG, "uce override-published-caps, error " + imsException); getErrPrintWriter().println("Exception: " + imsException); return -1; } if (result == null) { getErrPrintWriter().println("Service not available"); return -1; } getOutPrintWriter().println(result); return 0; } private int handleUceGetPidfXml() { int subId = getSubId("uce get-last-publish-pidf"); if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { return -1; } String result; try { result = mInterface.getLastUcePidfXmlShell(subId); } catch (RemoteException e) { Log.w(LOG_TAG, "uce get-last-publish-pidf, error " + e.getMessage()); getErrPrintWriter().println("Exception: " + e.getMessage()); return -1; } catch (ServiceSpecificException sse) { // Reconstruct ImsException ImsException imsException = new ImsException(sse.getMessage(), sse.errorCode); Log.w(LOG_TAG, "uce get-last-publish-pidf error " + imsException); getErrPrintWriter().println("Exception: " + imsException); return -1; } if (result == null) { getErrPrintWriter().println("Service not available"); return -1; } getOutPrintWriter().println(result); return 0; } private int handleSrcSetDeviceEnabledCommand() { String enabledStr = getNextArg(); if (enabledStr == null) { return -1; } try { mInterface.setDeviceSingleRegistrationEnabledOverride(enabledStr); if (VDBG) { Log.v(LOG_TAG, "src set-device-enabled " + enabledStr + ", done"); } getOutPrintWriter().println("Done"); } catch (NumberFormatException | RemoteException e) { Log.w(LOG_TAG, "src set-device-enabled " + enabledStr + ", error" + e.getMessage()); getErrPrintWriter().println("Exception: " + e.getMessage()); return -1; } return 0; } private int handleSrcGetDeviceEnabledCommand() { boolean result = false; try { result = mInterface.getDeviceSingleRegistrationEnabled(); } catch (RemoteException e) { return -1; } if (VDBG) { Log.v(LOG_TAG, "src get-device-enabled, returned: " + result); } getOutPrintWriter().println(result); return 0; } private int handleSrcSetCarrierEnabledCommand() { //the release time value could be -1 int subId = getRemainingArgsCount() > 1 ? getSubId("src set-carrier-enabled") : SubscriptionManager.getDefaultSubscriptionId(); if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { return -1; } String enabledStr = getNextArg(); if (enabledStr == null) { return -1; } try { boolean result = mInterface.setCarrierSingleRegistrationEnabledOverride(subId, enabledStr); if (VDBG) { Log.v(LOG_TAG, "src set-carrier-enabled -s " + subId + " " + enabledStr + ", result=" + result); } getOutPrintWriter().println(result); } catch (NumberFormatException | RemoteException e) { Log.w(LOG_TAG, "src set-carrier-enabled -s " + subId + " " + enabledStr + ", error" + e.getMessage()); getErrPrintWriter().println("Exception: " + e.getMessage()); return -1; } return 0; } private int handleSrcGetCarrierEnabledCommand() { int subId = getSubId("src get-carrier-enabled"); if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { return -1; } boolean result = false; try { result = mInterface.getCarrierSingleRegistrationEnabled(subId); } catch (RemoteException e) { return -1; } if (VDBG) { Log.v(LOG_TAG, "src get-carrier-enabled -s " + subId + ", returned: " + result); } getOutPrintWriter().println(result); return 0; } private int handleSrcSetFeatureValidationCommand() { //the release time value could be -1 int subId = getRemainingArgsCount() > 1 ? getSubId("src set-feature-validation") : SubscriptionManager.getDefaultSubscriptionId(); if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { return -1; } String enabledStr = getNextArg(); if (enabledStr == null) { return -1; } try { boolean result = mInterface.setImsFeatureValidationOverride(subId, enabledStr); if (VDBG) { Log.v(LOG_TAG, "src set-feature-validation -s " + subId + " " + enabledStr + ", result=" + result); } getOutPrintWriter().println(result); } catch (NumberFormatException | RemoteException e) { Log.w(LOG_TAG, "src set-feature-validation -s " + subId + " " + enabledStr + ", error" + e.getMessage()); getErrPrintWriter().println("Exception: " + e.getMessage()); return -1; } return 0; } private int handleSrcGetFeatureValidationCommand() { int subId = getSubId("src get-feature-validation"); if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { return -1; } Boolean result = false; try { result = mInterface.getImsFeatureValidationOverride(subId); } catch (RemoteException e) { return -1; } if (VDBG) { Log.v(LOG_TAG, "src get-feature-validation -s " + subId + ", returned: " + result); } getOutPrintWriter().println(result); return 0; } private void onHelpCallComposer() { PrintWriter pw = getOutPrintWriter(); pw.println("Call composer commands"); pw.println(" callcomposer test-mode enable|disable|query"); pw.println(" Enables or disables test mode for call composer. In test mode, picture"); pw.println(" upload/download from carrier servers is disabled, and operations are"); pw.println(" performed using emulated local files instead."); pw.println(" callcomposer simulate-outgoing-call [subId] [UUID]"); pw.println(" Simulates an outgoing call being placed with the picture ID as"); pw.println(" the provided UUID. This triggers storage to the call log."); pw.println(" callcomposer user-setting [subId] enable|disable|query"); pw.println(" Enables or disables the user setting for call composer, as set by"); pw.println(" TelephonyManager#setCallComposerStatus."); } private int handleCallComposerCommand() { String arg = getNextArg(); if (arg == null) { onHelpCallComposer(); return 0; } mContext.enforceCallingPermission(Manifest.permission.MODIFY_PHONE_STATE, "MODIFY_PHONE_STATE required for call composer shell cmds"); switch (arg) { case CALL_COMPOSER_TEST_MODE: { String enabledStr = getNextArg(); if (ENABLE.equals(enabledStr)) { CallComposerPictureManager.sTestMode = true; } else if (DISABLE.equals(enabledStr)) { CallComposerPictureManager.sTestMode = false; } else if (QUERY.equals(enabledStr)) { getOutPrintWriter().println(CallComposerPictureManager.sTestMode); } else { onHelpCallComposer(); return 1; } break; } case CALL_COMPOSER_SIMULATE_CALL: { int subscriptionId = Integer.valueOf(getNextArg()); String uuidString = getNextArg(); UUID uuid = UUID.fromString(uuidString); CompletableFuture storageUriFuture = new CompletableFuture<>(); Binder.withCleanCallingIdentity(() -> { CallComposerPictureManager.getInstance(mContext, subscriptionId) .storeUploadedPictureToCallLog(uuid, storageUriFuture::complete); }); try { Uri uri = storageUriFuture.get(); getOutPrintWriter().println(String.valueOf(uri)); } catch (Exception e) { throw new RuntimeException(e); } break; } case CALL_COMPOSER_USER_SETTING: { try { int subscriptionId = Integer.valueOf(getNextArg()); String enabledStr = getNextArg(); if (ENABLE.equals(enabledStr)) { mInterface.setCallComposerStatus(subscriptionId, TelephonyManager.CALL_COMPOSER_STATUS_ON); } else if (DISABLE.equals(enabledStr)) { mInterface.setCallComposerStatus(subscriptionId, TelephonyManager.CALL_COMPOSER_STATUS_OFF); } else if (QUERY.equals(enabledStr)) { getOutPrintWriter().println(mInterface.getCallComposerStatus(subscriptionId) == TelephonyManager.CALL_COMPOSER_STATUS_ON); } else { onHelpCallComposer(); return 1; } } catch (RemoteException e) { e.printStackTrace(getOutPrintWriter()); return 1; } break; } } return 0; } private int handleHasCarrierPrivilegesCommand() { String packageName = getNextArgRequired(); boolean hasCarrierPrivileges; final long token = Binder.clearCallingIdentity(); try { hasCarrierPrivileges = mInterface.checkCarrierPrivilegesForPackageAnyPhone(packageName) == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; } catch (RemoteException e) { Log.w(LOG_TAG, HAS_CARRIER_PRIVILEGES_COMMAND + " exception", e); getErrPrintWriter().println("Exception: " + e.getMessage()); return -1; } finally { Binder.restoreCallingIdentity(token); } getOutPrintWriter().println(hasCarrierPrivileges); return 0; } private int handleAllowedNetworkTypesCommand(String command) { if (!checkShellUid()) { return -1; } PrintWriter errPw = getErrPrintWriter(); String tag = command + ": "; String opt; int subId = -1; Log.v(LOG_TAG, command + " start"); while ((opt = getNextOption()) != null) { if (opt.equals("-s")) { try { subId = slotStringToSubId(tag, getNextArgRequired()); if (!SubscriptionManager.isValidSubscriptionId(subId)) { errPw.println(tag + "No valid subscription found."); return -1; } } catch (IllegalArgumentException e) { // Missing slot id errPw.println(tag + "SLOT_ID expected after -s."); return -1; } } else { errPw.println(tag + "Unknown option " + opt); return -1; } } if (GET_ALLOWED_NETWORK_TYPES_FOR_USER.equals(command)) { return handleGetAllowedNetworkTypesCommand(subId); } if (SET_ALLOWED_NETWORK_TYPES_FOR_USER.equals(command)) { return handleSetAllowedNetworkTypesCommand(subId); } return -1; } private int handleGetAllowedNetworkTypesCommand(int subId) { PrintWriter errPw = getErrPrintWriter(); long result = -1; try { if (mInterface != null) { result = mInterface.getAllowedNetworkTypesForReason(subId, TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER); } else { throw new IllegalStateException("telephony service is null."); } } catch (RemoteException e) { Log.e(TAG, "getAllowedNetworkTypesForReason RemoteException" + e); errPw.println(GET_ALLOWED_NETWORK_TYPES_FOR_USER + "RemoteException " + e); return -1; } getOutPrintWriter().println(TelephonyManager.convertNetworkTypeBitmaskToString(result)); return 0; } private int handleSetAllowedNetworkTypesCommand(int subId) { PrintWriter errPw = getErrPrintWriter(); String bitmaskString = getNextArg(); if (TextUtils.isEmpty(bitmaskString)) { errPw.println(SET_ALLOWED_NETWORK_TYPES_FOR_USER + " No NETWORK_TYPES_BITMASK"); return -1; } long allowedNetworkTypes = convertNetworkTypeBitmaskFromStringToLong(bitmaskString); if (allowedNetworkTypes < 0) { errPw.println(SET_ALLOWED_NETWORK_TYPES_FOR_USER + " No valid NETWORK_TYPES_BITMASK"); return -1; } boolean result = false; try { if (mInterface != null) { result = mInterface.setAllowedNetworkTypesForReason(subId, TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER, allowedNetworkTypes); } else { throw new IllegalStateException("telephony service is null."); } } catch (RemoteException e) { Log.e(TAG, "setAllowedNetworkTypesForReason RemoteException" + e); errPw.println(SET_ALLOWED_NETWORK_TYPES_FOR_USER + " RemoteException " + e); return -1; } String resultMessage = SET_ALLOWED_NETWORK_TYPES_FOR_USER + " failed"; if (result) { resultMessage = SET_ALLOWED_NETWORK_TYPES_FOR_USER + " completed"; } getOutPrintWriter().println(resultMessage); return 0; } private long convertNetworkTypeBitmaskFromStringToLong(String bitmaskString) { if (TextUtils.isEmpty(bitmaskString)) { return -1; } if (VDBG) { Log.v(LOG_TAG, "AllowedNetworkTypes:" + bitmaskString + ", length: " + bitmaskString.length()); } try { return Long.parseLong(bitmaskString, 2); } catch (NumberFormatException e) { Log.e(LOG_TAG, "AllowedNetworkTypes: " + e); return -1; } } }