blob: f0cc245030a8ecb8fefca52f161be047e42f5f10 [file]
/*
* Copyright (C) 2024 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.server.telecom;
import android.content.ComponentName;
import android.content.Context;
import android.net.Uri;
import android.os.Binder;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.sysprop.TelephonyProperties;
import android.telecom.Log;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import com.android.internal.telecom.ITelecomService;
import com.android.modules.utils.BasicShellCommandHandler;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* Implements shell commands sent to telecom using the "adb shell cmd telecom..." command from shell
* or CTS.
*/
public class TelecomShellCommand extends BasicShellCommandHandler {
private static final String CALLING_PACKAGE = TelecomShellCommand.class.getPackageName();
private static final String COMMAND_SET_PHONE_ACCOUNT_ENABLED = "set-phone-account-enabled";
private static final String COMMAND_SET_PHONE_ACCOUNT_DISABLED = "set-phone-account-disabled";
private static final String COMMAND_REGISTER_PHONE_ACCOUNT = "register-phone-account";
private static final String COMMAND_SET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT =
"set-user-selected-outgoing-phone-account";
private static final String COMMAND_REGISTER_SIM_PHONE_ACCOUNT = "register-sim-phone-account";
private static final String COMMAND_SET_TEST_CALL_REDIRECTION_APP =
"set-test-call-redirection-app";
private static final String COMMAND_SET_TEST_CALL_SCREENING_APP = "set-test-call-screening-app";
private static final String COMMAND_ADD_OR_REMOVE_CALL_COMPANION_APP =
"add-or-remove-call-companion-app";
private static final String COMMAND_SET_PHONE_ACCOUNT_SUGGESTION_COMPONENT =
"set-phone-acct-suggestion-component";
private static final String COMMAND_UNREGISTER_PHONE_ACCOUNT = "unregister-phone-account";
private static final String COMMAND_SET_CALL_DIAGNOSTIC_SERVICE = "set-call-diagnostic-service";
private static final String COMMAND_SET_DEFAULT_DIALER = "set-default-dialer";
private static final String COMMAND_GET_DEFAULT_DIALER = "get-default-dialer";
private static final String COMMAND_STOP_BLOCK_SUPPRESSION = "stop-block-suppression";
private static final String COMMAND_CLEANUP_STUCK_CALLS = "cleanup-stuck-calls";
private static final String COMMAND_CLEANUP_ORPHAN_PHONE_ACCOUNTS =
"cleanup-orphan-phone-accounts";
private static final String COMMAND_RESET_CAR_MODE = "reset-car-mode";
private static final String COMMAND_IS_NON_IN_CALL_SERVICE_BOUND =
"is-non-ui-in-call-service-bound";
private static final String COMMAND_WAIT_FOR_AUDIO_OPS_COMPLETION =
"wait-for-audio-ops-complete";
private static final String COMMAND_WAIT_FOR_AUDIO_ACTIVE_COMPLETION =
"wait-for-audio-active";
/**
* Change the system dialer package name if a package name was specified,
* Example: adb shell telecom set-system-dialer <PACKAGE>
*
* Restore it to the default if if argument is "default" or no argument is passed.
* Example: adb shell telecom set-system-dialer default
*/
private static final String COMMAND_SET_SYSTEM_DIALER = "set-system-dialer";
private static final String COMMAND_GET_SYSTEM_DIALER = "get-system-dialer";
private static final String COMMAND_WAIT_ON_HANDLERS = "wait-on-handlers";
private static final String COMMAND_SET_SIM_COUNT = "set-sim-count";
private static final String COMMAND_GET_SIM_CONFIG = "get-sim-config";
private static final String COMMAND_GET_MAX_PHONES = "get-max-phones";
private static final String COMMAND_SET_TEST_EMERGENCY_PHONE_ACCOUNT_PACKAGE_FILTER =
"set-test-emergency-phone-account-package-filter";
private static final String COMMAND_SET_METRICS_TEST_ENABLED = "set-metrics-test-enabled";
private static final String COMMAND_SET_METRICS_TEST_DISABLED = "set-metrics-test-disabled";
/**
* Command used to emit a distinct "mark" in the logs.
*/
private static final String COMMAND_LOG_MARK = "log-mark";
private static final String COMMAND_SET_LOCAL_VOICEMAIL_SERVICE = "set-local-voicemail-service";
private static final String COMMAND_SET_LOCAL_VOICEMAIL_TIMEOUT = "set-local-voicemail-timeout";
private final Context mContext;
private final ITelecomService mTelecomService;
private TelephonyManager mTelephonyManager;
private UserManager mUserManager;
public TelecomShellCommand(ITelecomService binder, Context context) {
mTelecomService = binder;
mContext = context;
}
@Override
public int onCommand(String command) {
if (command == null || command.isEmpty()) {
onHelp();
return 0;
}
try {
switch (command) {
case COMMAND_SET_PHONE_ACCOUNT_ENABLED:
runSetPhoneAccountEnabled(true);
break;
case COMMAND_SET_PHONE_ACCOUNT_DISABLED:
runSetPhoneAccountEnabled(false);
break;
case COMMAND_REGISTER_PHONE_ACCOUNT:
runRegisterPhoneAccount();
break;
case COMMAND_SET_TEST_CALL_REDIRECTION_APP:
runSetTestCallRedirectionApp();
break;
case COMMAND_SET_TEST_CALL_SCREENING_APP:
runSetTestCallScreeningApp();
break;
case COMMAND_ADD_OR_REMOVE_CALL_COMPANION_APP:
runAddOrRemoveCallCompanionApp();
break;
case COMMAND_SET_PHONE_ACCOUNT_SUGGESTION_COMPONENT:
runSetTestPhoneAcctSuggestionComponent();
break;
case COMMAND_SET_CALL_DIAGNOSTIC_SERVICE:
runSetCallDiagnosticService();
break;
case COMMAND_REGISTER_SIM_PHONE_ACCOUNT:
runRegisterSimPhoneAccount();
break;
case COMMAND_SET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT:
runSetUserSelectedOutgoingPhoneAccount();
break;
case COMMAND_UNREGISTER_PHONE_ACCOUNT:
runUnregisterPhoneAccount();
break;
case COMMAND_STOP_BLOCK_SUPPRESSION:
runStopBlockSuppression();
break;
case COMMAND_CLEANUP_STUCK_CALLS:
runCleanupStuckCalls();
break;
case COMMAND_CLEANUP_ORPHAN_PHONE_ACCOUNTS:
runCleanupOrphanPhoneAccounts();
break;
case COMMAND_RESET_CAR_MODE:
runResetCarMode();
break;
case COMMAND_SET_DEFAULT_DIALER:
runSetDefaultDialer();
break;
case COMMAND_GET_DEFAULT_DIALER:
runGetDefaultDialer();
break;
case COMMAND_SET_SYSTEM_DIALER:
runSetSystemDialer();
break;
case COMMAND_GET_SYSTEM_DIALER:
runGetSystemDialer();
break;
case COMMAND_WAIT_ON_HANDLERS:
runWaitOnHandler();
break;
case COMMAND_SET_SIM_COUNT:
runSetSimCount();
break;
case COMMAND_GET_SIM_CONFIG:
runGetSimConfig();
break;
case COMMAND_GET_MAX_PHONES:
runGetMaxPhones();
break;
case COMMAND_IS_NON_IN_CALL_SERVICE_BOUND:
runIsNonUiInCallServiceBound();
break;
case COMMAND_SET_TEST_EMERGENCY_PHONE_ACCOUNT_PACKAGE_FILTER:
runSetEmergencyPhoneAccountPackageFilter();
break;
case COMMAND_LOG_MARK:
runLogMark();
break;
case COMMAND_SET_METRICS_TEST_ENABLED:
mTelecomService.setMetricsTestMode(true);
break;
case COMMAND_SET_METRICS_TEST_DISABLED:
mTelecomService.setMetricsTestMode(false);
break;
case COMMAND_WAIT_FOR_AUDIO_OPS_COMPLETION:
mTelecomService.waitForAudioToUpdate(false);
break;
case COMMAND_WAIT_FOR_AUDIO_ACTIVE_COMPLETION:
mTelecomService.waitForAudioToUpdate(true);
break;
case COMMAND_SET_LOCAL_VOICEMAIL_SERVICE:
runSetLocalVoicemailService();
break;
case COMMAND_SET_LOCAL_VOICEMAIL_TIMEOUT:
runSetLocalVoicemailTimeout();
break;
default:
return handleDefaultCommands(command);
}
} catch (Exception e) {
getErrPrintWriter().println("Command["+ command + "]: Error: " + e);
return -1;
}
return 0;
}
@Override
public void onHelp() {
getOutPrintWriter().println("usage: telecom [subcommand] [options]\n"
+ "usage: telecom set-phone-account-enabled <COMPONENT> <ID> <USER_SN>\n"
+ "usage: telecom set-phone-account-disabled <COMPONENT> <ID> <USER_SN>\n"
+ "usage: telecom register-phone-account <COMPONENT> <ID> <USER_SN> <LABEL>\n"
+ "usage: telecom register-sim-phone-account [-e] <COMPONENT> <ID> <USER_SN>"
+ " <LABEL>: registers a PhoneAccount with CAPABILITY_SIM_SUBSCRIPTION"
+ " and optionally CAPABILITY_PLACE_EMERGENCY_CALLS if \"-e\" is provided\n"
+ "usage: telecom set-user-selected-outgoing-phone-account [-e] <COMPONENT> <ID> "
+ "<USER_SN>\n"
+ "usage: telecom set-test-call-redirection-app <PACKAGE>\n"
+ "usage: telecom set-test-call-screening-app <PACKAGE>\n"
+ "usage: telecom set-phone-acct-suggestion-component <COMPONENT>\n"
+ "usage: telecom add-or-remove-call-companion-app <PACKAGE> <1/0>\n"
+ "usage: telecom register-sim-phone-account <COMPONENT> <ID> <USER_SN>"
+ " <LABEL> <ADDRESS>\n"
+ "usage: telecom unregister-phone-account <COMPONENT> <ID> <USER_SN>\n"
+ "usage: telecom set-call-diagnostic-service <PACKAGE>\n"
+ "usage: telecom set-default-dialer <PACKAGE>\n"
+ "usage: telecom get-default-dialer\n"
+ "usage: telecom get-system-dialer\n"
+ "usage: telecom wait-on-handlers\n"
+ "usage: telecom set-sim-count <COUNT>\n"
+ "usage: telecom get-sim-config\n"
+ "usage: telecom get-max-phones\n"
+ "usage: telecom stop-block-suppression: Stop suppressing the blocked number"
+ " provider after a call to emergency services.\n"
+ "usage: telecom cleanup-stuck-calls: Clear any disconnected calls that have"
+ " gotten wedged in Telecom.\n"
+ "usage: telecom cleanup-orphan-phone-accounts: remove any phone accounts that"
+ " no longer have a valid UserHandle or accounts that no longer belongs to an"
+ " installed package.\n"
+ "usage: telecom set-emer-phone-account-filter <PACKAGE>\n"
+ "\n"
+ "telecom set-phone-account-enabled: Enables the given phone account, if it has"
+ " already been registered with Telecom.\n"
+ "\n"
+ "telecom set-phone-account-disabled: Disables the given phone account, if it"
+ " has already been registered with telecom.\n"
+ "\n"
+ "telecom set-call-diagnostic-service: overrides call diagnostic service.\n"
+ "telecom set-default-dialer: Sets the override default dialer to the given"
+ " component; this will override whatever the dialer role is set to.\n"
+ "\n"
+ "telecom get-default-dialer: Displays the current default dialer.\n"
+ "\n"
+ "telecom get-system-dialer: Displays the current system dialer.\n"
+ "telecom set-system-dialer: Set the override system dialer to the given"
+ " component. To remove the override, send \"default\"\n"
+ "\n"
+ "telecom wait-on-handlers: Wait until all handlers finish their work.\n"
+ "\n"
+ "telecom set-sim-count: Set num SIMs (2 for DSDS, 1 for single SIM."
+ " This may restart the device.\n"
+ "\n"
+ "telecom get-sim-config: Get the mSIM config string. \"DSDS\" for DSDS mode,"
+ " or \"\" for single SIM\n"
+ "\n"
+ "telecom get-max-phones: Get the max supported phones from the modem.\n"
+ "telecom set-test-emergency-phone-account-package-filter <PACKAGE>: sets a"
+ " package name that will be used for test emergency calls. To clear,"
+ " send an empty package name. Real emergency calls will still be placed"
+ " over Telephony.\n"
+ "telecom log-mark <MESSAGE>: emits a message into the telecom logs. Useful for "
+ "testers to indicate where in the logs various test steps take place.\n"
+ "telecom is-non-ui-in-call-service-bound <PACKAGE>: queries a particular "
+ "non-ui-InCallService in InCallController to determine if it is bound \n"
+ "telecom set-metrics-test-enabled: Enable the metrics test mode.\n"
+ "telecom set-metrics-test-disabled: Disable the metrics test mode.\n"
+ "telecom set-local-voicemail-service: Override the local voicemail service to the"
+ " specified package. To remove the override, send \"default\"\n"
);
}
private void runSetPhoneAccountEnabled(boolean enabled) throws RemoteException {
final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
final boolean success = mTelecomService.enablePhoneAccount(handle, enabled);
if (success) {
getOutPrintWriter().println("Success - " + handle
+ (enabled ? " enabled." : " disabled."));
} else {
getOutPrintWriter().println("Error - is " + handle + " a valid PhoneAccount?");
}
}
private void runRegisterPhoneAccount() throws RemoteException {
final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
final String label = getNextArgRequired();
PhoneAccount account = PhoneAccount.builder(handle, label)
.setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER).build();
mTelecomService.registerPhoneAccount(account, CALLING_PACKAGE);
getOutPrintWriter().println("Success - " + handle + " registered.");
}
private void runRegisterSimPhoneAccount() throws RemoteException {
boolean isEmergencyAccount = false;
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "-e": {
isEmergencyAccount = true;
break;
}
}
}
final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
final String label = getNextArgRequired();
final String address = getNextArgRequired();
int capabilities = PhoneAccount.CAPABILITY_CALL_PROVIDER
| PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
| (isEmergencyAccount ? PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS : 0);
PhoneAccount account = PhoneAccount.builder(
handle, label)
.setAddress(Uri.parse(address))
.setSubscriptionAddress(Uri.parse(address))
.setCapabilities(capabilities)
.setShortDescription(label)
.addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
.addSupportedUriScheme(PhoneAccount.SCHEME_VOICEMAIL)
.build();
mTelecomService.registerPhoneAccount(account, CALLING_PACKAGE);
getOutPrintWriter().println("Success - " + handle + " registered.");
}
private void runSetTestCallRedirectionApp() throws RemoteException {
final String packageName = getNextArg();
mTelecomService.setTestDefaultCallRedirectionApp(packageName);
}
private void runSetTestCallScreeningApp() throws RemoteException {
final String packageName = getNextArg();
mTelecomService.setTestDefaultCallScreeningApp(packageName);
}
private void runAddOrRemoveCallCompanionApp() throws RemoteException {
final String packageName = getNextArgRequired();
String isAdded = getNextArgRequired();
boolean isAddedBool = "1".equals(isAdded);
mTelecomService.addOrRemoveTestCallCompanionApp(packageName, isAddedBool);
}
private void runSetCallDiagnosticService() throws RemoteException {
String packageName = getNextArg();
if ("default".equals(packageName)) packageName = null;
mTelecomService.setTestCallDiagnosticService(packageName);
getOutPrintWriter().println("Success - " + packageName
+ " set as call diagnostic service.");
}
private void runSetTestPhoneAcctSuggestionComponent() throws RemoteException {
final String componentName = getNextArg();
final UserHandle userHandle = getUserHandleFromArgs();
mTelecomService.setTestPhoneAcctSuggestionComponent(componentName, userHandle);
}
private void runSetUserSelectedOutgoingPhoneAccount() throws RemoteException {
Log.i(this, "runSetUserSelectedOutgoingPhoneAccount");
final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
mTelecomService.setUserSelectedOutgoingPhoneAccount(handle);
getOutPrintWriter().println("Success - " + handle + " set as default outgoing account.");
}
private void runUnregisterPhoneAccount() throws RemoteException {
final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
mTelecomService.unregisterPhoneAccount(handle, CALLING_PACKAGE);
getOutPrintWriter().println("Success - " + handle + " unregistered.");
}
private void runStopBlockSuppression() throws RemoteException {
mTelecomService.stopBlockSuppression();
}
private void runCleanupStuckCalls() throws RemoteException {
mTelecomService.cleanupStuckCalls();
}
private void runCleanupOrphanPhoneAccounts() throws RemoteException {
getOutPrintWriter().println("Success - cleaned up "
+ mTelecomService.cleanupOrphanPhoneAccounts()
+ " phone accounts.");
}
private void runResetCarMode() throws RemoteException {
mTelecomService.resetCarMode();
}
private void runSetDefaultDialer() throws RemoteException {
String packageName = getNextArg();
if ("default".equals(packageName)) packageName = null;
mTelecomService.setTestDefaultDialer(packageName);
getOutPrintWriter().println("Success - " + packageName
+ " set as override default dialer.");
}
private void runSetSystemDialer() throws RemoteException {
final String flatComponentName = getNextArg();
final ComponentName componentName = (flatComponentName.equals("default")
? null : parseComponentName(flatComponentName));
mTelecomService.setSystemDialer(componentName);
getOutPrintWriter().println("Success - " + componentName + " set as override system dialer.");
}
private void runGetDefaultDialer() throws RemoteException {
getOutPrintWriter().println(mTelecomService.getDefaultDialerPackage(CALLING_PACKAGE));
}
private void runGetSystemDialer() throws RemoteException {
getOutPrintWriter().println(mTelecomService.getSystemDialerPackage(CALLING_PACKAGE));
}
private void runWaitOnHandler() throws RemoteException {
}
private void runSetSimCount() throws RemoteException {
if (!callerIsRoot()) {
getOutPrintWriter().println("set-sim-count requires adb root");
return;
}
int numSims = Integer.parseInt(getNextArgRequired());
getOutPrintWriter().println("Setting sim count to " + numSims + ". Device may reboot");
getTelephonyManager().switchMultiSimConfig(numSims);
}
/**
* prints out whether a particular non-ui InCallServices is bound in a call
*/
public void runIsNonUiInCallServiceBound() throws RemoteException {
if (TextUtils.isEmpty(peekNextArg())) {
getOutPrintWriter().println("No Argument passed. Please pass a <PACKAGE_NAME> to "
+ "lookup.");
} else {
getOutPrintWriter().println(
String.valueOf(mTelecomService.isNonUiInCallServiceBound(getNextArg())));
}
}
/**
* Prints the mSIM config to the console.
* "DSDS" for a phone in DSDS mode
* "" (empty string) for a phone in SS mode
*/
private void runGetSimConfig() throws RemoteException {
getOutPrintWriter().println(TelephonyProperties.multi_sim_config().orElse(""));
}
private void runGetMaxPhones() throws RemoteException {
// how many logical modems can be potentially active simultaneously
getOutPrintWriter().println(getTelephonyManager().getSupportedModemCount());
}
private void runSetEmergencyPhoneAccountPackageFilter() throws RemoteException {
String packageName = getNextArg();
if (TextUtils.isEmpty(packageName)) {
mTelecomService.setTestEmergencyPhoneAccountPackageNameFilter(null);
getOutPrintWriter().println("Success - filter cleared");
} else {
mTelecomService.setTestEmergencyPhoneAccountPackageNameFilter(packageName);
getOutPrintWriter().println("Success = filter set to " + packageName);
}
}
private void runSetLocalVoicemailService() throws RemoteException {
String packageName = getNextArg();
if ("default".equals(packageName)) packageName = null;
mTelecomService.setTestLocalVoicemailService(packageName);
getOutPrintWriter().println("Success - changed local vm service to " + packageName);
}
private void runSetLocalVoicemailTimeout() throws RemoteException {
String timeout = getNextArg();
Log.i(this, "runSetLocalVoicemailTimeout %s", timeout);
Duration timeoutSeconds;
if ("disabled".equals(timeout)) {
timeoutSeconds = null;
} else {
try {
timeoutSeconds = Duration.ofSeconds(Integer.parseInt(timeout));
} catch (NumberFormatException nfe) {
timeoutSeconds = null;
}
}
try {
TelecomManager mgr = mContext.getSystemService(TelecomManager.class);
List<PhoneAccountHandle> handles = mgr.getCallCapablePhoneAccounts();
for (PhoneAccountHandle h : handles) {
if (timeoutSeconds == null) {
mgr.disableLocalVoicemail(h);
} else {
mgr.enableLocalVoicemail(h, timeoutSeconds);
}
}
getOutPrintWriter().println("Success - changed local vm timeout to " + timeoutSeconds);
} catch (Exception e) {
Log.i(this, "runSetLocalVoicemailTimeout failed.");
}
}
private void runLogMark() throws RemoteException {
String message = Arrays.stream(peekRemainingArgs()).collect(Collectors.joining(" "));
mTelecomService.requestLogMark(message);
}
private UserHandle getUserHandleFromArgs() throws RemoteException {
if (TextUtils.isEmpty(peekNextArg())) {
return null;
}
final String userSnInStr = getNextArgRequired();
return parseUserHandle(userSnInStr);
}
private PhoneAccountHandle getPhoneAccountHandleFromArgs() throws RemoteException {
if (TextUtils.isEmpty(peekNextArg())) {
return null;
}
final ComponentName component = parseComponentName(getNextArgRequired());
final String accountId = getNextArgRequired();
final String userSnInStr = getNextArgRequired();
return new PhoneAccountHandle(component, accountId, parseUserHandle(userSnInStr));
}
private UserHandle parseUserHandle(String userSnInStr) {
try {
final int userSn = Integer.parseInt(userSnInStr);
return getUserManager().getUserForSerialNumber((long) userSn);
} catch (NumberFormatException ex) {
Log.w(this, "parseUserHandle - invalid user %s", userSnInStr);
throw new IllegalArgumentException ("Invalid user serial number " + userSnInStr);
}
}
private boolean callerIsRoot() {
return Process.ROOT_UID == Process.myUid();
}
private ComponentName parseComponentName(String component) {
ComponentName cn = ComponentName.unflattenFromString(component);
if (cn == null) {
throw new IllegalArgumentException ("Invalid component " + component);
}
return cn;
}
private TelephonyManager getTelephonyManager() throws IllegalStateException {
if (mTelephonyManager == null) {
mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
}
if (mTelephonyManager == null) {
Log.w(this, "getTelephonyManager: Can't access telephony service.");
throw new IllegalStateException("Could not access the Telephony Service. Is the system "
+ "running?");
}
return mTelephonyManager;
}
private UserManager getUserManager() throws IllegalStateException {
if (mUserManager == null) {
mUserManager = mContext.getSystemService(UserManager.class);
}
if (mUserManager == null) {
Log.w(this, "getUserManager: Can't access UserManager service.");
throw new IllegalStateException("Could not access the UserManager Service. Is the "
+ "system running?");
}
return mUserManager;
}
}