blob: a34abc029c23b6c41f6e51090af3198ab6b71c82 [file] [log] [blame]
/*
* 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 android.os.Binder;
import android.os.Process;
import android.os.RemoteException;
import android.os.ShellCommand;
import android.os.UserHandle;
import android.telephony.SubscriptionManager;
import android.telephony.emergency.EmergencyNumber;
import android.util.Log;
import com.android.internal.telephony.ITelephony;
import com.android.internal.telephony.emergency.EmergencyNumberTracker;
import java.io.PrintWriter;
import java.util.ArrayList;
/**
* 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 ShellCommand {
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 IMS_SUBCOMMAND = "ims";
private static final String SMS_SUBCOMMAND = "sms";
private static final String NUMBER_VERIFICATION_SUBCOMMAND = "numverify";
private static final String EMERGENCY_NUMBER_TEST_MODE = "emergency-number-test-mode";
private static final String IMS_SET_CARRIER_SERVICE = "set-ims-service";
private static final String IMS_GET_CARRIER_SERVICE = "get-ims-service";
private static final String IMS_ENABLE = "enable";
private static final String IMS_DISABLE = "disable";
// 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 SMS_GET_APPS = "get-apps";
private static final String SMS_GET_DEFAULT_APP = "get-default-app";
private static final String SMS_SET_DEFAULT_APP = "set-default-app";
private static final String NUMBER_VERIFICATION_OVERRIDE_PACKAGE = "override-package";
private static final String NUMBER_VERIFICATION_FAKE_CALL = "fake-call";
// Take advantage of existing methods that already contain permissions checks when possible.
private final ITelephony mInterface;
public TelephonyShellCommand(ITelephony binder) {
mInterface = binder;
}
@Override
public int onCommand(String cmd) {
if (cmd == null) {
return handleDefaultCommands(null);
}
switch (cmd) {
case IMS_SUBCOMMAND: {
return handleImsCommand();
}
case SMS_SUBCOMMAND: {
return handleSmsCommand();
}
case NUMBER_VERIFICATION_SUBCOMMAND:
return handleNumberVerificationCommand();
case EMERGENCY_NUMBER_TEST_MODE:
return handleEmergencyNumberTestModeCommand();
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(" sms");
pw.println(" SMS Commands.");
pw.println(" emergency-number-test-mode");
pw.println(" Emergency Number Test Mode Commands.");
onHelpIms();
onHelpSms();
onHelpEmergencyNumber();
}
private void onHelpIms() {
PrintWriter pw = getOutPrintWriter();
pw.println("IMS Commands:");
pw.println(" ims set-ims-service [-s SLOT_ID] (-c | -d) 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(" 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(" -c: The ImsService defined as the device default ImsService.");
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 onHelpSms() {
PrintWriter pw = getOutPrintWriter();
pw.println("SMS Commands:");
pw.println(" sms get-apps [--user USER_ID]");
pw.println(" Print all SMS apps on a user.");
pw.println(" sms get-default-app [--user USER_ID]");
pw.println(" Get the default SMS app.");
pw.println(" sms set-default-app [--user USER_ID] PACKAGE_NAME");
pw.println(" Set PACKAGE_NAME as the default SMS app.");
}
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 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 <emergency number address>: 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 <emergency number address>: 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 int handleImsCommand() {
String arg = getNextArg();
if (arg == null) {
onHelpIms();
return 0;
}
switch (arg) {
case IMS_SET_CARRIER_SERVICE: {
return handleImsSetServiceCommand();
}
case IMS_GET_CARRIER_SERVICE: {
return handleImsGetServiceCommand();
}
case IMS_ENABLE: {
return handleEnableIms();
}
case IMS_DISABLE: {
return handleDisableIms();
}
case IMS_CEP: {
return handleCepChange();
}
}
return -1;
}
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<String>(),
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<String>(),
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;
}
// ims set-ims-service
private int handleImsSetServiceCommand() {
PrintWriter errPw = getErrPrintWriter();
int slotId = getDefaultSlot();
Boolean isCarrierService = null;
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;
}
}
}
// 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 = "";
}
boolean result = mInterface.setImsService(slotId, isCarrierService, packageName);
if (VDBG) {
Log.v(LOG_TAG, "ims set-ims-service -s " + slotId + " "
+ (isCarrierService ? "-c " : "-d ") + packageName + ", result=" + result);
}
getOutPrintWriter().println(result);
} catch (RemoteException e) {
Log.w(LOG_TAG, "ims set-ims-service -s " + slotId + " "
+ (isCarrierService ? "-c " : "-d ") + packageName + ", 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;
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;
}
}
}
// 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 result;
try {
result = mInterface.getImsService(slotId, isCarrierService);
} catch (RemoteException e) {
return -1;
}
if (VDBG) {
Log.v(LOG_TAG, "ims get-ims-service -s " + slotId + " "
+ (isCarrierService ? "-c " : "-d ") + ", 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;
}
private int handleSmsCommand() {
String arg = getNextArg();
if (arg == null) {
onHelpSms();
return 0;
}
try {
switch (arg) {
case SMS_GET_APPS: {
return handleSmsGetApps();
}
case SMS_GET_DEFAULT_APP: {
return handleSmsGetDefaultApp();
}
case SMS_SET_DEFAULT_APP: {
return handleSmsSetDefaultApp();
}
default:
getErrPrintWriter().println("Unknown command " + arg);
}
} catch (RemoteException e) {
getErrPrintWriter().println("RemoteException: " + e.getMessage());
}
return -1;
}
private int maybeParseUserIdArg() {
int userId = UserHandle.USER_SYSTEM;
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "--user": {
try {
userId = Integer.parseInt(getNextArgRequired());
} catch (NumberFormatException e) {
getErrPrintWriter().println("Invalid user ID for --user");
return -1;
}
break;
}
}
}
return userId;
}
private int handleSmsGetApps() throws RemoteException {
final int userId = maybeParseUserIdArg();
if (userId < 0) {
return -1;
}
for (String packageName : mInterface.getSmsApps(userId)) {
getOutPrintWriter().println(packageName);
}
return 0;
}
private int handleSmsGetDefaultApp() throws RemoteException {
final int userId = maybeParseUserIdArg();
if (userId < 0) {
return -1;
}
getOutPrintWriter().println(mInterface.getDefaultSmsApp(userId));
return 0;
}
private int handleSmsSetDefaultApp() throws RemoteException {
final int userId = maybeParseUserIdArg();
if (userId < 0) {
return -1;
}
String packageName = getNextArgRequired();
mInterface.setDefaultSmsApp(userId, packageName);
getOutPrintWriter().println("SMS app set to " + mInterface.getDefaultSmsApp(userId));
return 0;
}
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;
}
}