blob: c64c375724cbe1f7e0a0d2fbf216e6cf5d00463c [file] [log] [blame]
Brad Ebinger4dc095a2018-04-03 15:17:52 -07001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.phone;
18
Torbjorn Eklund1050cb02018-11-16 14:05:38 +010019import android.content.Context;
Hall Liud892bec2018-11-30 14:51:45 -080020import android.os.Binder;
Torbjorn Eklund1050cb02018-11-16 14:05:38 +010021import android.os.PersistableBundle;
Hall Liud892bec2018-11-30 14:51:45 -080022import android.os.Process;
Brad Ebinger4dc095a2018-04-03 15:17:52 -070023import android.os.RemoteException;
Shuo Qian489d9282020-07-09 11:30:03 -070024import android.provider.BlockedNumberContract;
Torbjorn Eklund1050cb02018-11-16 14:05:38 +010025import android.telephony.CarrierConfigManager;
Brad Ebinger4dc095a2018-04-03 15:17:52 -070026import android.telephony.SubscriptionManager;
Michele Berionne38c1afa2020-12-28 20:23:16 +000027import android.telephony.TelephonyManager;
sqian9d4df8b2019-01-15 18:32:07 -080028import android.telephony.emergency.EmergencyNumber;
Brad Ebinger24c29992019-12-05 13:03:21 -080029import android.telephony.ims.feature.ImsFeature;
Brad Ebinger4dc095a2018-04-03 15:17:52 -070030import android.util.Log;
31
32import com.android.internal.telephony.ITelephony;
Qiong Liuf25799b2020-09-10 10:13:46 +080033import com.android.internal.telephony.Phone;
34import com.android.internal.telephony.PhoneFactory;
sqian9d4df8b2019-01-15 18:32:07 -080035import com.android.internal.telephony.emergency.EmergencyNumberTracker;
Meng Wangc4f61042019-11-21 10:51:05 -080036import com.android.internal.telephony.util.TelephonyUtils;
Chiachang Wang99890092020-11-04 10:59:17 +080037import com.android.modules.utils.BasicShellCommandHandler;
Brad Ebinger4dc095a2018-04-03 15:17:52 -070038
39import java.io.PrintWriter;
sqian9d4df8b2019-01-15 18:32:07 -080040import java.util.ArrayList;
Torbjorn Eklund1050cb02018-11-16 14:05:38 +010041import java.util.HashMap;
Brad Ebinger24c29992019-12-05 13:03:21 -080042import java.util.List;
Torbjorn Eklund1050cb02018-11-16 14:05:38 +010043import java.util.Map;
44import java.util.TreeSet;
Brad Ebinger4dc095a2018-04-03 15:17:52 -070045
46/**
47 * Takes actions based on the adb commands given by "adb shell cmd phone ...". Be careful, no
48 * permission checks have been done before onCommand was called. Make sure any commands processed
49 * here also contain the appropriate permissions checks.
50 */
51
Hall Liua1548bd2019-12-24 14:14:12 -080052public class TelephonyShellCommand extends BasicShellCommandHandler {
Brad Ebinger4dc095a2018-04-03 15:17:52 -070053
54 private static final String LOG_TAG = "TelephonyShellCommand";
55 // Don't commit with this true.
56 private static final boolean VDBG = true;
Brad Ebinger0aa2f242018-04-12 09:49:23 -070057 private static final int DEFAULT_PHONE_ID = 0;
Brad Ebinger4dc095a2018-04-03 15:17:52 -070058
59 private static final String IMS_SUBCOMMAND = "ims";
Hall Liud892bec2018-11-30 14:51:45 -080060 private static final String NUMBER_VERIFICATION_SUBCOMMAND = "numverify";
sqian9d4df8b2019-01-15 18:32:07 -080061 private static final String EMERGENCY_NUMBER_TEST_MODE = "emergency-number-test-mode";
Shuo Qian489d9282020-07-09 11:30:03 -070062 private static final String END_BLOCK_SUPPRESSION = "end-block-suppression";
Michele Berionne38c1afa2020-12-28 20:23:16 +000063 private static final String RESTART_MODEM = "restart-modem";
Torbjorn Eklund1050cb02018-11-16 14:05:38 +010064 private static final String CARRIER_CONFIG_SUBCOMMAND = "cc";
Shuo Qianf5125122019-12-16 17:03:07 -080065 private static final String DATA_TEST_MODE = "data";
66 private static final String DATA_ENABLE = "enable";
67 private static final String DATA_DISABLE = "disable";
Hall Liud892bec2018-11-30 14:51:45 -080068
Brad Ebinger999d3302020-11-25 14:31:39 -080069 private static final String IMS_SET_IMS_SERVICE = "set-ims-service";
70 private static final String IMS_GET_IMS_SERVICE = "get-ims-service";
71 private static final String IMS_CLEAR_SERVICE_OVERRIDE = "clear-ims-service-override";
Brad Ebinger4dc095a2018-04-03 15:17:52 -070072 private static final String IMS_ENABLE = "enable";
73 private static final String IMS_DISABLE = "disable";
Tyler Gunn7bcdc742019-10-04 15:56:59 -070074 // Used to disable or enable processing of conference event package data from the network.
75 // This is handy for testing scenarios where CEP data does not exist on a network which does
76 // support CEP data.
77 private static final String IMS_CEP = "conference-event-package";
Brad Ebinger4dc095a2018-04-03 15:17:52 -070078
Hall Liud892bec2018-11-30 14:51:45 -080079 private static final String NUMBER_VERIFICATION_OVERRIDE_PACKAGE = "override-package";
Hall Liuca5af3a2018-12-04 16:58:23 -080080 private static final String NUMBER_VERIFICATION_FAKE_CALL = "fake-call";
Hall Liud892bec2018-11-30 14:51:45 -080081
Torbjorn Eklund1050cb02018-11-16 14:05:38 +010082 private static final String CC_GET_VALUE = "get-value";
83 private static final String CC_SET_VALUE = "set-value";
84 private static final String CC_CLEAR_VALUES = "clear-values";
85
Hui Wang0866fcc2020-10-12 12:14:23 -070086 private static final String GBA_SUBCOMMAND = "gba";
87 private static final String GBA_SET_SERVICE = "set-service";
88 private static final String GBA_GET_SERVICE = "get-service";
89 private static final String GBA_SET_RELEASE_TIME = "set-release";
90 private static final String GBA_GET_RELEASE_TIME = "get-release";
91
Hui Wang068ab862020-10-31 05:12:53 +000092 private static final String SINGLE_REGISTATION_CONFIG = "src";
93 private static final String SRC_SET_DEVICE_ENABLED = "set-device-enabled";
94 private static final String SRC_GET_DEVICE_ENABLED = "get-device-enabled";
95 private static final String SRC_SET_CARRIER_ENABLED = "set-carrier-enabled";
96 private static final String SRC_GET_CARRIER_ENABLED = "get-carrier-enabled";
97
Brad Ebinger4dc095a2018-04-03 15:17:52 -070098 // Take advantage of existing methods that already contain permissions checks when possible.
99 private final ITelephony mInterface;
100
Torbjorn Eklund1050cb02018-11-16 14:05:38 +0100101 private SubscriptionManager mSubscriptionManager;
102 private CarrierConfigManager mCarrierConfigManager;
Shuo Qian489d9282020-07-09 11:30:03 -0700103 private Context mContext;
Torbjorn Eklund1050cb02018-11-16 14:05:38 +0100104
105 private enum CcType {
106 BOOLEAN, DOUBLE, DOUBLE_ARRAY, INT, INT_ARRAY, LONG, LONG_ARRAY, STRING,
107 STRING_ARRAY, UNKNOWN
108 }
109
Torbjorn Eklund723527a2019-02-13 11:16:25 +0100110 private class CcOptionParseResult {
111 public int mSubId;
112 public boolean mPersistent;
113 }
114
Torbjorn Eklund1050cb02018-11-16 14:05:38 +0100115 // Maps carrier config keys to type. It is possible to infer the type for most carrier config
116 // keys by looking at the end of the string which usually tells the type.
117 // For instance: "xxxx_string", "xxxx_string_array", etc.
118 // The carrier config keys in this map does not follow this convention. It is therefore not
119 // possible to infer the type for these keys by looking at the string.
120 private static final Map<String, CcType> CC_TYPE_MAP = new HashMap<String, CcType>() {{
121 put(CarrierConfigManager.Gps.KEY_A_GLONASS_POS_PROTOCOL_SELECT_STRING, CcType.STRING);
122 put(CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, CcType.STRING);
123 put(CarrierConfigManager.Gps.KEY_GPS_LOCK_STRING, CcType.STRING);
124 put(CarrierConfigManager.Gps.KEY_LPP_PROFILE_STRING, CcType.STRING);
125 put(CarrierConfigManager.Gps.KEY_NFW_PROXY_APPS_STRING, CcType.STRING);
126 put(CarrierConfigManager.Gps.KEY_SUPL_ES_STRING, CcType.STRING);
127 put(CarrierConfigManager.Gps.KEY_SUPL_HOST_STRING, CcType.STRING);
128 put(CarrierConfigManager.Gps.KEY_SUPL_MODE_STRING, CcType.STRING);
129 put(CarrierConfigManager.Gps.KEY_SUPL_PORT_STRING, CcType.STRING);
130 put(CarrierConfigManager.Gps.KEY_SUPL_VER_STRING, CcType.STRING);
131 put(CarrierConfigManager.Gps.KEY_USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL_STRING,
132 CcType.STRING);
133 put(CarrierConfigManager.KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY,
134 CcType.STRING_ARRAY);
135 put(CarrierConfigManager.KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY,
136 CcType.STRING_ARRAY);
137 put(CarrierConfigManager.KEY_CARRIER_CALL_SCREENING_APP_STRING, CcType.STRING);
138 put(CarrierConfigManager.KEY_MMS_EMAIL_GATEWAY_NUMBER_STRING, CcType.STRING);
139 put(CarrierConfigManager.KEY_MMS_HTTP_PARAMS_STRING, CcType.STRING);
140 put(CarrierConfigManager.KEY_MMS_NAI_SUFFIX_STRING, CcType.STRING);
141 put(CarrierConfigManager.KEY_MMS_UA_PROF_TAG_NAME_STRING, CcType.STRING);
142 put(CarrierConfigManager.KEY_MMS_UA_PROF_URL_STRING, CcType.STRING);
143 put(CarrierConfigManager.KEY_MMS_USER_AGENT_STRING, CcType.STRING);
144 put(CarrierConfigManager.KEY_RATCHET_RAT_FAMILIES, CcType.STRING_ARRAY);
145 }
146 };
147
148 public TelephonyShellCommand(ITelephony binder, Context context) {
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700149 mInterface = binder;
Torbjorn Eklund1050cb02018-11-16 14:05:38 +0100150 mCarrierConfigManager =
151 (CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
152 mSubscriptionManager = (SubscriptionManager)
153 context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
Shuo Qian489d9282020-07-09 11:30:03 -0700154 mContext = context;
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700155 }
156
157 @Override
158 public int onCommand(String cmd) {
159 if (cmd == null) {
160 return handleDefaultCommands(null);
161 }
162
163 switch (cmd) {
164 case IMS_SUBCOMMAND: {
165 return handleImsCommand();
166 }
Hall Liud892bec2018-11-30 14:51:45 -0800167 case NUMBER_VERIFICATION_SUBCOMMAND:
168 return handleNumberVerificationCommand();
sqian9d4df8b2019-01-15 18:32:07 -0800169 case EMERGENCY_NUMBER_TEST_MODE:
170 return handleEmergencyNumberTestModeCommand();
Torbjorn Eklund1050cb02018-11-16 14:05:38 +0100171 case CARRIER_CONFIG_SUBCOMMAND: {
172 return handleCcCommand();
173 }
Shuo Qianf5125122019-12-16 17:03:07 -0800174 case DATA_TEST_MODE:
175 return handleDataTestModeCommand();
Shuo Qian489d9282020-07-09 11:30:03 -0700176 case END_BLOCK_SUPPRESSION:
177 return handleEndBlockSuppressionCommand();
Hui Wang0866fcc2020-10-12 12:14:23 -0700178 case GBA_SUBCOMMAND:
179 return handleGbaCommand();
Hui Wang068ab862020-10-31 05:12:53 +0000180 case SINGLE_REGISTATION_CONFIG:
181 return handleSingleRegistrationConfigCommand();
Michele Berionne38c1afa2020-12-28 20:23:16 +0000182 case RESTART_MODEM:
183 return handleRestartModemCommand();
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700184 default: {
185 return handleDefaultCommands(cmd);
186 }
187 }
188 }
189
190 @Override
191 public void onHelp() {
192 PrintWriter pw = getOutPrintWriter();
193 pw.println("Telephony Commands:");
194 pw.println(" help");
195 pw.println(" Print this help text.");
196 pw.println(" ims");
197 pw.println(" IMS Commands.");
sqian9d4df8b2019-01-15 18:32:07 -0800198 pw.println(" emergency-number-test-mode");
199 pw.println(" Emergency Number Test Mode Commands.");
Shuo Qian489d9282020-07-09 11:30:03 -0700200 pw.println(" end-block-suppression");
201 pw.println(" End Block Suppression command.");
Shuo Qianf5125122019-12-16 17:03:07 -0800202 pw.println(" data");
203 pw.println(" Data Test Mode Commands.");
Torbjorn Eklund1050cb02018-11-16 14:05:38 +0100204 pw.println(" cc");
205 pw.println(" Carrier Config Commands.");
Hui Wang0866fcc2020-10-12 12:14:23 -0700206 pw.println(" gba");
207 pw.println(" GBA Commands.");
Hui Wang068ab862020-10-31 05:12:53 +0000208 pw.println(" src");
209 pw.println(" RCS VoLTE Single Registration Config Commands.");
Michele Berionne38c1afa2020-12-28 20:23:16 +0000210 pw.println(" restart-modem");
211 pw.println(" Restart modem command.");
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700212 onHelpIms();
sqian9d4df8b2019-01-15 18:32:07 -0800213 onHelpEmergencyNumber();
Shuo Qian489d9282020-07-09 11:30:03 -0700214 onHelpEndBlockSupperssion();
Shuo Qianf5125122019-12-16 17:03:07 -0800215 onHelpDataTestMode();
Torbjorn Eklund1050cb02018-11-16 14:05:38 +0100216 onHelpCc();
Hui Wang0866fcc2020-10-12 12:14:23 -0700217 onHelpGba();
Hui Wang068ab862020-10-31 05:12:53 +0000218 onHelpSrc();
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700219 }
220
221 private void onHelpIms() {
222 PrintWriter pw = getOutPrintWriter();
223 pw.println("IMS Commands:");
Brad Ebinger24c29992019-12-05 13:03:21 -0800224 pw.println(" ims set-ims-service [-s SLOT_ID] (-c | -d | -f) PACKAGE_NAME");
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700225 pw.println(" Sets the ImsService defined in PACKAGE_NAME to to be the bound");
226 pw.println(" ImsService. Options are:");
227 pw.println(" -s: the slot ID that the ImsService should be bound for. If no option");
228 pw.println(" is specified, it will choose the default voice SIM slot.");
229 pw.println(" -c: Override the ImsService defined in the carrier configuration.");
230 pw.println(" -d: Override the ImsService defined in the device overlay.");
Brad Ebinger24c29992019-12-05 13:03:21 -0800231 pw.println(" -f: Set the feature that this override if for, if no option is");
232 pw.println(" specified, the new package name will be used for all features.");
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700233 pw.println(" ims get-ims-service [-s SLOT_ID] [-c | -d]");
234 pw.println(" Gets the package name of the currently defined ImsService.");
235 pw.println(" Options are:");
236 pw.println(" -s: The SIM slot ID for the registered ImsService. If no option");
237 pw.println(" is specified, it will choose the default voice SIM slot.");
238 pw.println(" -c: The ImsService defined as the carrier configured ImsService.");
Peter Kalauskas1defdc32020-09-03 19:20:26 +0000239 pw.println(" -d: The ImsService defined as the device default ImsService.");
Brad Ebinger24c29992019-12-05 13:03:21 -0800240 pw.println(" -f: The feature type that the query will be requested for. If none is");
241 pw.println(" specified, the returned package name will correspond to MMTEL.");
Brad Ebinger999d3302020-11-25 14:31:39 -0800242 pw.println(" ims clear-ims-service-override [-s SLOT_ID]");
243 pw.println(" Clear all carrier ImsService overrides. This does not work for device ");
244 pw.println(" configuration overrides. Options are:");
245 pw.println(" -s: The SIM slot ID for the registered ImsService. If no option");
246 pw.println(" is specified, it will choose the default voice SIM slot.");
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700247 pw.println(" ims enable [-s SLOT_ID]");
248 pw.println(" enables IMS for the SIM slot specified, or for the default voice SIM slot");
249 pw.println(" if none is specified.");
250 pw.println(" ims disable [-s SLOT_ID]");
251 pw.println(" disables IMS for the SIM slot specified, or for the default voice SIM");
252 pw.println(" slot if none is specified.");
Tyler Gunn7bcdc742019-10-04 15:56:59 -0700253 pw.println(" ims conference-event-package [enable/disable]");
254 pw.println(" enables or disables handling or network conference event package data.");
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700255 }
256
Hall Liud892bec2018-11-30 14:51:45 -0800257 private void onHelpNumberVerification() {
258 PrintWriter pw = getOutPrintWriter();
259 pw.println("Number verification commands");
260 pw.println(" numverify override-package PACKAGE_NAME;");
261 pw.println(" Set the authorized package for number verification.");
262 pw.println(" Leave the package name blank to reset.");
Hall Liuca5af3a2018-12-04 16:58:23 -0800263 pw.println(" numverify fake-call NUMBER;");
264 pw.println(" Fake an incoming call from NUMBER. This is for testing. Output will be");
265 pw.println(" 1 if the call would have been intercepted, 0 otherwise.");
Hall Liud892bec2018-11-30 14:51:45 -0800266 }
267
Shuo Qianf5125122019-12-16 17:03:07 -0800268 private void onHelpDataTestMode() {
269 PrintWriter pw = getOutPrintWriter();
270 pw.println("Mobile Data Test Mode Commands:");
271 pw.println(" data enable: enable mobile data connectivity");
272 pw.println(" data disable: disable mobile data connectivity");
273 }
274
sqian9d4df8b2019-01-15 18:32:07 -0800275 private void onHelpEmergencyNumber() {
276 PrintWriter pw = getOutPrintWriter();
277 pw.println("Emergency Number Test Mode Commands:");
278 pw.println(" emergency-number-test-mode ");
279 pw.println(" Add(-a), Clear(-c), Print (-p) or Remove(-r) the emergency number list in"
280 + " the test mode");
281 pw.println(" -a <emergency number address>: add an emergency number address for the"
sqian9121f982019-03-14 19:45:39 -0700282 + " test mode, only allows '0'-'9', '*', '#' or '+'.");
sqian9d4df8b2019-01-15 18:32:07 -0800283 pw.println(" -c: clear the emergency number list in the test mode.");
284 pw.println(" -r <emergency number address>: remove an existing emergency number"
sqian9121f982019-03-14 19:45:39 -0700285 + " address added by the test mode, only allows '0'-'9', '*', '#' or '+'.");
sqian9d4df8b2019-01-15 18:32:07 -0800286 pw.println(" -p: get the full emergency number list in the test mode.");
287 }
288
Shuo Qian489d9282020-07-09 11:30:03 -0700289 private void onHelpEndBlockSupperssion() {
290 PrintWriter pw = getOutPrintWriter();
291 pw.println("End Block Suppression command:");
292 pw.println(" end-block-suppression: disable suppressing blocking by contact");
293 pw.println(" with emergency services.");
294 }
295
Torbjorn Eklund1050cb02018-11-16 14:05:38 +0100296 private void onHelpCc() {
297 PrintWriter pw = getOutPrintWriter();
298 pw.println("Carrier Config Commands:");
299 pw.println(" cc get-value [-s SLOT_ID] [KEY]");
300 pw.println(" Print carrier config values.");
301 pw.println(" Options are:");
302 pw.println(" -s: The SIM slot ID to read carrier config value for. If no option");
303 pw.println(" is specified, it will choose the default voice SIM slot.");
304 pw.println(" KEY: The key to the carrier config value to print. All values are printed");
305 pw.println(" if KEY is not specified.");
Torbjorn Eklund723527a2019-02-13 11:16:25 +0100306 pw.println(" cc set-value [-s SLOT_ID] [-p] KEY [NEW_VALUE]");
Torbjorn Eklund1050cb02018-11-16 14:05:38 +0100307 pw.println(" Set carrier config KEY to NEW_VALUE.");
308 pw.println(" Options are:");
309 pw.println(" -s: The SIM slot ID to set carrier config value for. If no option");
310 pw.println(" is specified, it will choose the default voice SIM slot.");
Torbjorn Eklund723527a2019-02-13 11:16:25 +0100311 pw.println(" -p: Value will be stored persistent");
Torbjorn Eklund1050cb02018-11-16 14:05:38 +0100312 pw.println(" NEW_VALUE specifies the new value for carrier config KEY. Null will be");
313 pw.println(" used if NEW_VALUE is not set. Strings should be encapsulated with");
314 pw.println(" quotation marks. Spaces needs to be escaped. Example: \"Hello\\ World\"");
315 pw.println(" Separate items in arrays with space . Example: \"item1\" \"item2\"");
316 pw.println(" cc clear-values [-s SLOT_ID]");
317 pw.println(" Clear all carrier override values that has previously been set");
318 pw.println(" with set-value");
319 pw.println(" Options are:");
320 pw.println(" -s: The SIM slot ID to clear carrier config values for. If no option");
321 pw.println(" is specified, it will choose the default voice SIM slot.");
322 }
323
Hui Wang0866fcc2020-10-12 12:14:23 -0700324 private void onHelpGba() {
325 PrintWriter pw = getOutPrintWriter();
326 pw.println("Gba Commands:");
327 pw.println(" gba set-service [-s SLOT_ID] PACKAGE_NAME");
328 pw.println(" Sets the GbaService defined in PACKAGE_NAME to to be the bound.");
329 pw.println(" Options are:");
330 pw.println(" -s: The SIM slot ID to read carrier config value for. If no option");
331 pw.println(" is specified, it will choose the default voice SIM slot.");
332 pw.println(" gba get-service [-s SLOT_ID]");
333 pw.println(" Gets the package name of the currently defined GbaService.");
334 pw.println(" Options are:");
335 pw.println(" -s: The SIM slot ID to read carrier config value for. If no option");
336 pw.println(" is specified, it will choose the default voice SIM slot.");
337 pw.println(" gba set-release [-s SLOT_ID] n");
338 pw.println(" Sets the time to release/unbind GbaService in n milli-second.");
339 pw.println(" Do not release/unbind if n is -1.");
340 pw.println(" Options are:");
341 pw.println(" -s: The SIM slot ID to read carrier config value for. If no option");
342 pw.println(" is specified, it will choose the default voice SIM slot.");
343 pw.println(" gba get-release [-s SLOT_ID]");
344 pw.println(" Gets the time to release/unbind GbaService in n milli-sencond.");
345 pw.println(" Options are:");
346 pw.println(" -s: The SIM slot ID to read carrier config value for. If no option");
347 pw.println(" is specified, it will choose the default voice SIM slot.");
348 }
349
Hui Wang068ab862020-10-31 05:12:53 +0000350 private void onHelpSrc() {
351 PrintWriter pw = getOutPrintWriter();
352 pw.println("RCS VoLTE Single Registration Config Commands:");
353 pw.println(" src set-device-enabled true|false|null");
354 pw.println(" Sets the device config for RCS VoLTE single registration to the value.");
355 pw.println(" The value could be true, false, or null(undefined).");
356 pw.println(" src get-device-enabled");
357 pw.println(" Gets the device config for RCS VoLTE single registration.");
358 pw.println(" src set-carrier-enabled [-s SLOT_ID] true|false|null");
359 pw.println(" Sets the carrier config for RCS VoLTE single registration to the value.");
360 pw.println(" The value could be true, false, or null(undefined).");
361 pw.println(" Options are:");
362 pw.println(" -s: The SIM slot ID to set the config value for. If no option");
363 pw.println(" is specified, it will choose the default voice SIM slot.");
364 pw.println(" src get-carrier-enabled [-s SLOT_ID]");
365 pw.println(" Gets the carrier config for RCS VoLTE single registration.");
366 pw.println(" Options are:");
367 pw.println(" -s: The SIM slot ID to read the config value for. If no option");
368 pw.println(" is specified, it will choose the default voice SIM slot.");
369 }
370
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700371 private int handleImsCommand() {
372 String arg = getNextArg();
373 if (arg == null) {
374 onHelpIms();
375 return 0;
376 }
377
378 switch (arg) {
Brad Ebinger999d3302020-11-25 14:31:39 -0800379 case IMS_SET_IMS_SERVICE: {
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700380 return handleImsSetServiceCommand();
381 }
Brad Ebinger999d3302020-11-25 14:31:39 -0800382 case IMS_GET_IMS_SERVICE: {
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700383 return handleImsGetServiceCommand();
384 }
Brad Ebinger999d3302020-11-25 14:31:39 -0800385 case IMS_CLEAR_SERVICE_OVERRIDE: {
386 return handleImsClearCarrierServiceCommand();
387 }
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700388 case IMS_ENABLE: {
389 return handleEnableIms();
390 }
391 case IMS_DISABLE: {
392 return handleDisableIms();
393 }
Tyler Gunn7bcdc742019-10-04 15:56:59 -0700394 case IMS_CEP: {
395 return handleCepChange();
396 }
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700397 }
398
399 return -1;
400 }
401
Shuo Qianf5125122019-12-16 17:03:07 -0800402 private int handleDataTestModeCommand() {
403 PrintWriter errPw = getErrPrintWriter();
404 String arg = getNextArgRequired();
405 if (arg == null) {
406 onHelpDataTestMode();
407 return 0;
408 }
409 switch (arg) {
410 case DATA_ENABLE: {
411 try {
412 mInterface.enableDataConnectivity();
413 } catch (RemoteException ex) {
414 Log.w(LOG_TAG, "data enable, error " + ex.getMessage());
415 errPw.println("Exception: " + ex.getMessage());
416 return -1;
417 }
418 break;
419 }
420 case DATA_DISABLE: {
421 try {
422 mInterface.disableDataConnectivity();
423 } catch (RemoteException ex) {
424 Log.w(LOG_TAG, "data disable, error " + ex.getMessage());
425 errPw.println("Exception: " + ex.getMessage());
426 return -1;
427 }
428 break;
429 }
430 default:
431 onHelpDataTestMode();
432 break;
433 }
434 return 0;
435 }
436
sqian9d4df8b2019-01-15 18:32:07 -0800437 private int handleEmergencyNumberTestModeCommand() {
438 PrintWriter errPw = getErrPrintWriter();
439 String opt = getNextOption();
440 if (opt == null) {
441 onHelpEmergencyNumber();
442 return 0;
443 }
444
445 switch (opt) {
446 case "-a": {
447 String emergencyNumberCmd = getNextArgRequired();
448 if (emergencyNumberCmd == null
449 || !EmergencyNumber.validateEmergencyNumberAddress(emergencyNumberCmd)) {
sqian9121f982019-03-14 19:45:39 -0700450 errPw.println("An emergency number (only allow '0'-'9', '*', '#' or '+') needs"
sqian9d4df8b2019-01-15 18:32:07 -0800451 + " to be specified after -a in the command ");
452 return -1;
453 }
454 try {
455 mInterface.updateEmergencyNumberListTestMode(
456 EmergencyNumberTracker.ADD_EMERGENCY_NUMBER_TEST_MODE,
457 new EmergencyNumber(emergencyNumberCmd, "", "",
458 EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
459 new ArrayList<String>(),
460 EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST,
461 EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN));
462 } catch (RemoteException ex) {
463 Log.w(LOG_TAG, "emergency-number-test-mode -a " + emergencyNumberCmd
464 + ", error " + ex.getMessage());
465 errPw.println("Exception: " + ex.getMessage());
466 return -1;
467 }
468 break;
469 }
470 case "-c": {
471 try {
472 mInterface.updateEmergencyNumberListTestMode(
473 EmergencyNumberTracker.RESET_EMERGENCY_NUMBER_TEST_MODE, null);
474 } catch (RemoteException ex) {
475 Log.w(LOG_TAG, "emergency-number-test-mode -c " + "error " + ex.getMessage());
476 errPw.println("Exception: " + ex.getMessage());
477 return -1;
478 }
479 break;
480 }
481 case "-r": {
482 String emergencyNumberCmd = getNextArgRequired();
483 if (emergencyNumberCmd == null
484 || !EmergencyNumber.validateEmergencyNumberAddress(emergencyNumberCmd)) {
sqian9121f982019-03-14 19:45:39 -0700485 errPw.println("An emergency number (only allow '0'-'9', '*', '#' or '+') needs"
sqian9d4df8b2019-01-15 18:32:07 -0800486 + " to be specified after -r in the command ");
487 return -1;
488 }
489 try {
490 mInterface.updateEmergencyNumberListTestMode(
491 EmergencyNumberTracker.REMOVE_EMERGENCY_NUMBER_TEST_MODE,
492 new EmergencyNumber(emergencyNumberCmd, "", "",
493 EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
494 new ArrayList<String>(),
495 EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST,
496 EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN));
497 } catch (RemoteException ex) {
498 Log.w(LOG_TAG, "emergency-number-test-mode -r " + emergencyNumberCmd
499 + ", error " + ex.getMessage());
500 errPw.println("Exception: " + ex.getMessage());
501 return -1;
502 }
503 break;
504 }
505 case "-p": {
506 try {
507 getOutPrintWriter().println(mInterface.getEmergencyNumberListTestMode());
508 } catch (RemoteException ex) {
509 Log.w(LOG_TAG, "emergency-number-test-mode -p " + "error " + ex.getMessage());
510 errPw.println("Exception: " + ex.getMessage());
511 return -1;
512 }
513 break;
514 }
515 default:
516 onHelpEmergencyNumber();
517 break;
518 }
519 return 0;
520 }
521
Hall Liud892bec2018-11-30 14:51:45 -0800522 private int handleNumberVerificationCommand() {
523 String arg = getNextArg();
524 if (arg == null) {
525 onHelpNumberVerification();
526 return 0;
527 }
528
Hall Liuca5af3a2018-12-04 16:58:23 -0800529 if (!checkShellUid()) {
530 return -1;
531 }
532
Hall Liud892bec2018-11-30 14:51:45 -0800533 switch (arg) {
534 case NUMBER_VERIFICATION_OVERRIDE_PACKAGE: {
Hall Liud892bec2018-11-30 14:51:45 -0800535 NumberVerificationManager.overrideAuthorizedPackage(getNextArg());
536 return 0;
537 }
Hall Liuca5af3a2018-12-04 16:58:23 -0800538 case NUMBER_VERIFICATION_FAKE_CALL: {
539 boolean val = NumberVerificationManager.getInstance()
540 .checkIncomingCall(getNextArg());
541 getOutPrintWriter().println(val ? "1" : "0");
542 return 0;
543 }
Hall Liud892bec2018-11-30 14:51:45 -0800544 }
545
546 return -1;
547 }
548
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700549 // ims set-ims-service
550 private int handleImsSetServiceCommand() {
551 PrintWriter errPw = getErrPrintWriter();
Brad Ebinger0aa2f242018-04-12 09:49:23 -0700552 int slotId = getDefaultSlot();
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700553 Boolean isCarrierService = null;
Brad Ebinger24c29992019-12-05 13:03:21 -0800554 List<Integer> featuresList = new ArrayList<>();
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700555
556 String opt;
557 while ((opt = getNextOption()) != null) {
558 switch (opt) {
559 case "-s": {
560 try {
561 slotId = Integer.parseInt(getNextArgRequired());
562 } catch (NumberFormatException e) {
563 errPw.println("ims set-ims-service requires an integer as a SLOT_ID.");
564 return -1;
565 }
566 break;
567 }
568 case "-c": {
569 isCarrierService = true;
570 break;
571 }
572 case "-d": {
573 isCarrierService = false;
574 break;
575 }
Brad Ebinger24c29992019-12-05 13:03:21 -0800576 case "-f": {
577 String featureString = getNextArgRequired();
578 String[] features = featureString.split(",");
579 for (int i = 0; i < features.length; i++) {
580 try {
581 Integer result = Integer.parseInt(features[i]);
582 if (result < ImsFeature.FEATURE_EMERGENCY_MMTEL
583 || result >= ImsFeature.FEATURE_MAX) {
584 errPw.println("ims set-ims-service -f " + result
585 + " is an invalid feature.");
586 return -1;
587 }
588 featuresList.add(result);
589 } catch (NumberFormatException e) {
590 errPw.println("ims set-ims-service -f tried to parse " + features[i]
591 + " as an integer.");
592 return -1;
593 }
594 }
595 }
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700596 }
597 }
598 // Mandatory param, either -c or -d
599 if (isCarrierService == null) {
600 errPw.println("ims set-ims-service requires either \"-c\" or \"-d\" to be set.");
601 return -1;
602 }
603
604 String packageName = getNextArg();
605
606 try {
607 if (packageName == null) {
608 packageName = "";
609 }
Brad Ebinger24c29992019-12-05 13:03:21 -0800610 int[] featureArray = new int[featuresList.size()];
611 for (int i = 0; i < featuresList.size(); i++) {
612 featureArray[i] = featuresList.get(i);
613 }
614 boolean result = mInterface.setBoundImsServiceOverride(slotId, isCarrierService,
615 featureArray, packageName);
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700616 if (VDBG) {
617 Log.v(LOG_TAG, "ims set-ims-service -s " + slotId + " "
Brad Ebinger24c29992019-12-05 13:03:21 -0800618 + (isCarrierService ? "-c " : "-d ")
619 + "-f " + featuresList + " "
620 + packageName + ", result=" + result);
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700621 }
622 getOutPrintWriter().println(result);
623 } catch (RemoteException e) {
624 Log.w(LOG_TAG, "ims set-ims-service -s " + slotId + " "
Brad Ebinger24c29992019-12-05 13:03:21 -0800625 + (isCarrierService ? "-c " : "-d ")
626 + "-f " + featuresList + " "
627 + packageName + ", error" + e.getMessage());
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700628 errPw.println("Exception: " + e.getMessage());
629 return -1;
630 }
631 return 0;
632 }
633
Brad Ebinger999d3302020-11-25 14:31:39 -0800634 // ims clear-ims-service-override
635 private int handleImsClearCarrierServiceCommand() {
636 PrintWriter errPw = getErrPrintWriter();
637 int slotId = getDefaultSlot();
638
639 String opt;
640 while ((opt = getNextOption()) != null) {
641 switch (opt) {
642 case "-s": {
643 try {
644 slotId = Integer.parseInt(getNextArgRequired());
645 } catch (NumberFormatException e) {
646 errPw.println("ims set-ims-service requires an integer as a SLOT_ID.");
647 return -1;
648 }
649 break;
650 }
651 }
652 }
653
654 try {
655 boolean result = mInterface.clearCarrierImsServiceOverride(slotId);
656 if (VDBG) {
657 Log.v(LOG_TAG, "ims clear-ims-service-override -s " + slotId
658 + ", result=" + result);
659 }
660 getOutPrintWriter().println(result);
661 } catch (RemoteException e) {
662 Log.w(LOG_TAG, "ims clear-ims-service-override -s " + slotId
663 + ", error" + e.getMessage());
664 errPw.println("Exception: " + e.getMessage());
665 return -1;
666 }
667 return 0;
668 }
669
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700670 // ims get-ims-service
671 private int handleImsGetServiceCommand() {
672 PrintWriter errPw = getErrPrintWriter();
Brad Ebinger0aa2f242018-04-12 09:49:23 -0700673 int slotId = getDefaultSlot();
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700674 Boolean isCarrierService = null;
Brad Ebinger24c29992019-12-05 13:03:21 -0800675 Integer featureType = ImsFeature.FEATURE_MMTEL;
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700676
677 String opt;
678 while ((opt = getNextOption()) != null) {
679 switch (opt) {
680 case "-s": {
681 try {
682 slotId = Integer.parseInt(getNextArgRequired());
683 } catch (NumberFormatException e) {
684 errPw.println("ims set-ims-service requires an integer as a SLOT_ID.");
685 return -1;
686 }
687 break;
688 }
689 case "-c": {
690 isCarrierService = true;
691 break;
692 }
693 case "-d": {
694 isCarrierService = false;
695 break;
696 }
Brad Ebinger24c29992019-12-05 13:03:21 -0800697 case "-f": {
698 try {
699 featureType = Integer.parseInt(getNextArg());
700 } catch (NumberFormatException e) {
701 errPw.println("ims get-ims-service -f requires valid integer as feature.");
702 return -1;
703 }
704 if (featureType < ImsFeature.FEATURE_EMERGENCY_MMTEL
705 || featureType >= ImsFeature.FEATURE_MAX) {
706 errPw.println("ims get-ims-service -f invalid feature.");
707 return -1;
708 }
709 }
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700710 }
711 }
712 // Mandatory param, either -c or -d
713 if (isCarrierService == null) {
Brad Ebinger24c29992019-12-05 13:03:21 -0800714 errPw.println("ims get-ims-service requires either \"-c\" or \"-d\" to be set.");
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700715 return -1;
716 }
717
718 String result;
719 try {
Brad Ebinger24c29992019-12-05 13:03:21 -0800720 result = mInterface.getBoundImsServicePackage(slotId, isCarrierService, featureType);
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700721 } catch (RemoteException e) {
722 return -1;
723 }
724 if (VDBG) {
725 Log.v(LOG_TAG, "ims get-ims-service -s " + slotId + " "
Brad Ebinger24c29992019-12-05 13:03:21 -0800726 + (isCarrierService ? "-c " : "-d ")
727 + (featureType != null ? ("-f " + featureType) : "") + " , returned: "
728 + result);
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700729 }
730 getOutPrintWriter().println(result);
731 return 0;
732 }
733
734 private int handleEnableIms() {
Brad Ebinger0aa2f242018-04-12 09:49:23 -0700735 int slotId = getDefaultSlot();
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700736 String opt;
737 while ((opt = getNextOption()) != null) {
738 switch (opt) {
739 case "-s": {
740 try {
741 slotId = Integer.parseInt(getNextArgRequired());
742 } catch (NumberFormatException e) {
743 getErrPrintWriter().println("ims enable requires an integer as a SLOT_ID.");
744 return -1;
745 }
746 break;
747 }
748 }
749 }
750 try {
751 mInterface.enableIms(slotId);
752 } catch (RemoteException e) {
753 return -1;
754 }
755 if (VDBG) {
756 Log.v(LOG_TAG, "ims enable -s " + slotId);
757 }
758 return 0;
759 }
760
761 private int handleDisableIms() {
Brad Ebinger0aa2f242018-04-12 09:49:23 -0700762 int slotId = getDefaultSlot();
Brad Ebinger4dc095a2018-04-03 15:17:52 -0700763 String opt;
764 while ((opt = getNextOption()) != null) {
765 switch (opt) {
766 case "-s": {
767 try {
768 slotId = Integer.parseInt(getNextArgRequired());
769 } catch (NumberFormatException e) {
770 getErrPrintWriter().println(
771 "ims disable requires an integer as a SLOT_ID.");
772 return -1;
773 }
774 break;
775 }
776 }
777 }
778 try {
779 mInterface.disableIms(slotId);
780 } catch (RemoteException e) {
781 return -1;
782 }
783 if (VDBG) {
784 Log.v(LOG_TAG, "ims disable -s " + slotId);
785 }
786 return 0;
787 }
Brad Ebinger0aa2f242018-04-12 09:49:23 -0700788
Tyler Gunn7bcdc742019-10-04 15:56:59 -0700789 private int handleCepChange() {
790 Log.i(LOG_TAG, "handleCepChange");
791 String opt = getNextArg();
792 if (opt == null) {
793 return -1;
794 }
795 boolean isCepEnabled = opt.equals("enable");
796
797 try {
798 mInterface.setCepEnabled(isCepEnabled);
799 } catch (RemoteException e) {
800 return -1;
801 }
802 return 0;
803 }
804
Brad Ebinger0aa2f242018-04-12 09:49:23 -0700805 private int getDefaultSlot() {
806 int slotId = SubscriptionManager.getDefaultVoicePhoneId();
807 if (slotId <= SubscriptionManager.INVALID_SIM_SLOT_INDEX
808 || slotId == SubscriptionManager.DEFAULT_PHONE_INDEX) {
809 // If there is no default, default to slot 0.
810 slotId = DEFAULT_PHONE_ID;
811 }
812 return slotId;
813 }
sqian2fff4a32018-11-05 14:18:37 -0800814
Torbjorn Eklund723527a2019-02-13 11:16:25 +0100815 // Parse options related to Carrier Config Commands.
816 private CcOptionParseResult parseCcOptions(String tag, boolean allowOptionPersistent) {
Torbjorn Eklund1050cb02018-11-16 14:05:38 +0100817 PrintWriter errPw = getErrPrintWriter();
Torbjorn Eklund723527a2019-02-13 11:16:25 +0100818 CcOptionParseResult result = new CcOptionParseResult();
819 result.mSubId = SubscriptionManager.getDefaultSubscriptionId();
820 result.mPersistent = false;
Torbjorn Eklund1050cb02018-11-16 14:05:38 +0100821
Torbjorn Eklund723527a2019-02-13 11:16:25 +0100822 String opt;
Torbjorn Eklund1050cb02018-11-16 14:05:38 +0100823 while ((opt = getNextOption()) != null) {
824 switch (opt) {
825 case "-s": {
826 try {
Torbjorn Eklund723527a2019-02-13 11:16:25 +0100827 result.mSubId = slotStringToSubId(tag, getNextArgRequired());
828 if (!SubscriptionManager.isValidSubscriptionId(result.mSubId)) {
829 errPw.println(tag + "No valid subscription found.");
830 return null;
831 }
832
Torbjorn Eklund1050cb02018-11-16 14:05:38 +0100833 } catch (IllegalArgumentException e) {
834 // Missing slot id
835 errPw.println(tag + "SLOT_ID expected after -s.");
Torbjorn Eklund723527a2019-02-13 11:16:25 +0100836 return null;
837 }
838 break;
839 }
840 case "-p": {
841 if (allowOptionPersistent) {
842 result.mPersistent = true;
843 } else {
844 errPw.println(tag + "Unexpected option " + opt);
845 return null;
Torbjorn Eklund1050cb02018-11-16 14:05:38 +0100846 }
847 break;
848 }
849 default: {
850 errPw.println(tag + "Unknown option " + opt);
Torbjorn Eklund723527a2019-02-13 11:16:25 +0100851 return null;
Torbjorn Eklund1050cb02018-11-16 14:05:38 +0100852 }
853 }
854 }
Torbjorn Eklund723527a2019-02-13 11:16:25 +0100855 return result;
Torbjorn Eklund1050cb02018-11-16 14:05:38 +0100856 }
857
858 private int slotStringToSubId(String tag, String slotString) {
859 int slotId = -1;
860 try {
861 slotId = Integer.parseInt(slotString);
862 } catch (NumberFormatException e) {
Qiong Liuf25799b2020-09-10 10:13:46 +0800863 getErrPrintWriter().println(tag + slotString + " is not a valid number for SLOT_ID.");
864 return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
865 }
866
867 if (!SubscriptionManager.isValidPhoneId(slotId)) {
Torbjorn Eklund1050cb02018-11-16 14:05:38 +0100868 getErrPrintWriter().println(tag + slotString + " is not a valid SLOT_ID.");
869 return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
870 }
871
Qiong Liuf25799b2020-09-10 10:13:46 +0800872 Phone phone = PhoneFactory.getPhone(slotId);
873 if (phone == null) {
Torbjorn Eklund1050cb02018-11-16 14:05:38 +0100874 getErrPrintWriter().println(tag + "No subscription found in slot " + slotId + ".");
875 return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
876 }
Qiong Liuf25799b2020-09-10 10:13:46 +0800877 return phone.getSubId();
Torbjorn Eklund1050cb02018-11-16 14:05:38 +0100878 }
879
Hall Liud892bec2018-11-30 14:51:45 -0800880 private boolean checkShellUid() {
Hall Liu2ddfc7e2018-12-06 13:09:45 -0800881 // adb can run as root or as shell, depending on whether the device is rooted.
882 return Binder.getCallingUid() == Process.SHELL_UID
883 || Binder.getCallingUid() == Process.ROOT_UID;
Hall Liud892bec2018-11-30 14:51:45 -0800884 }
Torbjorn Eklund1050cb02018-11-16 14:05:38 +0100885
886 private int handleCcCommand() {
887 // Verify that the user is allowed to run the command. Only allowed in rooted device in a
888 // non user build.
Meng Wangc4f61042019-11-21 10:51:05 -0800889 if (Binder.getCallingUid() != Process.ROOT_UID || TelephonyUtils.IS_USER) {
Torbjorn Eklund1050cb02018-11-16 14:05:38 +0100890 getErrPrintWriter().println("cc: Permission denied.");
891 return -1;
892 }
893
894 String arg = getNextArg();
895 if (arg == null) {
896 onHelpCc();
897 return 0;
898 }
899
900 switch (arg) {
901 case CC_GET_VALUE: {
902 return handleCcGetValue();
903 }
904 case CC_SET_VALUE: {
905 return handleCcSetValue();
906 }
907 case CC_CLEAR_VALUES: {
908 return handleCcClearValues();
909 }
910 default: {
911 getErrPrintWriter().println("cc: Unknown argument: " + arg);
912 }
913 }
914 return -1;
915 }
916
917 // cc get-value
918 private int handleCcGetValue() {
919 PrintWriter errPw = getErrPrintWriter();
920 String tag = CARRIER_CONFIG_SUBCOMMAND + " " + CC_GET_VALUE + ": ";
921 String key = null;
922
Torbjorn Eklund723527a2019-02-13 11:16:25 +0100923 // Parse all options
924 CcOptionParseResult options = parseCcOptions(tag, false);
925 if (options == null) {
Torbjorn Eklund1050cb02018-11-16 14:05:38 +0100926 return -1;
927 }
928
929 // Get bundle containing all carrier configuration values.
Torbjorn Eklund723527a2019-02-13 11:16:25 +0100930 PersistableBundle bundle = mCarrierConfigManager.getConfigForSubId(options.mSubId);
Torbjorn Eklund1050cb02018-11-16 14:05:38 +0100931 if (bundle == null) {
Torbjorn Eklund723527a2019-02-13 11:16:25 +0100932 errPw.println(tag + "No carrier config values found for subId " + options.mSubId + ".");
Torbjorn Eklund1050cb02018-11-16 14:05:38 +0100933 return -1;
934 }
935
936 // Get the key.
937 key = getNextArg();
938 if (key != null) {
939 // A key was provided. Verify if it is a valid key
940 if (!bundle.containsKey(key)) {
941 errPw.println(tag + key + " is not a valid key.");
942 return -1;
943 }
944
945 // Print the carrier config value for key.
946 getOutPrintWriter().println(ccValueToString(key, getType(tag, key, bundle), bundle));
947 } else {
948 // No key provided. Show all values.
949 // Iterate over a sorted list of all carrier config keys and print them.
950 TreeSet<String> sortedSet = new TreeSet<String>(bundle.keySet());
951 for (String k : sortedSet) {
952 getOutPrintWriter().println(ccValueToString(k, getType(tag, k, bundle), bundle));
953 }
954 }
955 return 0;
956 }
957
958 // cc set-value
959 private int handleCcSetValue() {
960 PrintWriter errPw = getErrPrintWriter();
961 String tag = CARRIER_CONFIG_SUBCOMMAND + " " + CC_SET_VALUE + ": ";
962
Torbjorn Eklund723527a2019-02-13 11:16:25 +0100963 // Parse all options
964 CcOptionParseResult options = parseCcOptions(tag, true);
965 if (options == null) {
Torbjorn Eklund1050cb02018-11-16 14:05:38 +0100966 return -1;
967 }
968
969 // Get bundle containing all current carrier configuration values.
Torbjorn Eklund723527a2019-02-13 11:16:25 +0100970 PersistableBundle originalValues = mCarrierConfigManager.getConfigForSubId(options.mSubId);
Torbjorn Eklund1050cb02018-11-16 14:05:38 +0100971 if (originalValues == null) {
Torbjorn Eklund723527a2019-02-13 11:16:25 +0100972 errPw.println(tag + "No carrier config values found for subId " + options.mSubId + ".");
Torbjorn Eklund1050cb02018-11-16 14:05:38 +0100973 return -1;
974 }
975
976 // Get the key.
977 String key = getNextArg();
978 if (key == null || key.equals("")) {
979 errPw.println(tag + "KEY is missing");
980 return -1;
981 }
982
983 // Verify if the key is valid
984 if (!originalValues.containsKey(key)) {
985 errPw.println(tag + key + " is not a valid key.");
986 return -1;
987 }
988
989 // Remaining arguments is a list of new values. Add them all into an ArrayList.
990 ArrayList<String> valueList = new ArrayList<String>();
991 while (peekNextArg() != null) {
992 valueList.add(getNextArg());
993 }
994
995 // Find the type of the carrier config value
996 CcType type = getType(tag, key, originalValues);
997 if (type == CcType.UNKNOWN) {
998 errPw.println(tag + "ERROR: Not possible to override key with unknown type.");
999 return -1;
1000 }
1001
1002 // Create an override bundle containing the key and value that should be overriden.
1003 PersistableBundle overrideBundle = getOverrideBundle(tag, type, key, valueList);
1004 if (overrideBundle == null) {
1005 return -1;
1006 }
1007
1008 // Override the value
Torbjorn Eklund723527a2019-02-13 11:16:25 +01001009 mCarrierConfigManager.overrideConfig(options.mSubId, overrideBundle, options.mPersistent);
Torbjorn Eklund1050cb02018-11-16 14:05:38 +01001010
1011 // Find bundle containing all new carrier configuration values after the override.
Torbjorn Eklund723527a2019-02-13 11:16:25 +01001012 PersistableBundle newValues = mCarrierConfigManager.getConfigForSubId(options.mSubId);
Torbjorn Eklund1050cb02018-11-16 14:05:38 +01001013 if (newValues == null) {
Torbjorn Eklund723527a2019-02-13 11:16:25 +01001014 errPw.println(tag + "No carrier config values found for subId " + options.mSubId + ".");
Torbjorn Eklund1050cb02018-11-16 14:05:38 +01001015 return -1;
1016 }
1017
1018 // Print the original and new value.
1019 String originalValueString = ccValueToString(key, type, originalValues);
1020 String newValueString = ccValueToString(key, type, newValues);
1021 getOutPrintWriter().println("Previous value: \n" + originalValueString);
1022 getOutPrintWriter().println("New value: \n" + newValueString);
1023
1024 return 0;
1025 }
1026
1027 // cc clear-values
1028 private int handleCcClearValues() {
1029 PrintWriter errPw = getErrPrintWriter();
1030 String tag = CARRIER_CONFIG_SUBCOMMAND + " " + CC_CLEAR_VALUES + ": ";
1031
Torbjorn Eklund723527a2019-02-13 11:16:25 +01001032 // Parse all options
1033 CcOptionParseResult options = parseCcOptions(tag, false);
1034 if (options == null) {
Torbjorn Eklund1050cb02018-11-16 14:05:38 +01001035 return -1;
1036 }
1037
1038 // Clear all values that has previously been set.
Torbjorn Eklund723527a2019-02-13 11:16:25 +01001039 mCarrierConfigManager.overrideConfig(options.mSubId, null, true);
Torbjorn Eklund1050cb02018-11-16 14:05:38 +01001040 getOutPrintWriter()
1041 .println("All previously set carrier config override values has been cleared");
1042 return 0;
1043 }
1044
1045 private CcType getType(String tag, String key, PersistableBundle bundle) {
1046 // Find the type by checking the type of the current value stored in the bundle.
1047 Object value = bundle.get(key);
1048
1049 if (CC_TYPE_MAP.containsKey(key)) {
1050 return CC_TYPE_MAP.get(key);
1051 } else if (value != null) {
1052 if (value instanceof Boolean) {
1053 return CcType.BOOLEAN;
1054 } else if (value instanceof Double) {
1055 return CcType.DOUBLE;
1056 } else if (value instanceof double[]) {
1057 return CcType.DOUBLE_ARRAY;
1058 } else if (value instanceof Integer) {
1059 return CcType.INT;
1060 } else if (value instanceof int[]) {
1061 return CcType.INT_ARRAY;
1062 } else if (value instanceof Long) {
1063 return CcType.LONG;
1064 } else if (value instanceof long[]) {
1065 return CcType.LONG_ARRAY;
1066 } else if (value instanceof String) {
1067 return CcType.STRING;
1068 } else if (value instanceof String[]) {
1069 return CcType.STRING_ARRAY;
1070 }
1071 } else {
1072 // Current value was null and can therefore not be used in order to find the type.
1073 // Check the name of the key to infer the type. This check is not needed for primitive
1074 // data types (boolean, double, int and long), since they can not be null.
1075 if (key.endsWith("double_array")) {
1076 return CcType.DOUBLE_ARRAY;
1077 }
1078 if (key.endsWith("int_array")) {
1079 return CcType.INT_ARRAY;
1080 }
1081 if (key.endsWith("long_array")) {
1082 return CcType.LONG_ARRAY;
1083 }
1084 if (key.endsWith("string")) {
1085 return CcType.STRING;
1086 }
1087 if (key.endsWith("string_array") || key.endsWith("strings")) {
1088 return CcType.STRING_ARRAY;
1089 }
1090 }
1091
1092 // Not possible to infer the type by looking at the current value or the key.
1093 PrintWriter errPw = getErrPrintWriter();
1094 errPw.println(tag + "ERROR: " + key + " has unknown type.");
1095 return CcType.UNKNOWN;
1096 }
1097
1098 private String ccValueToString(String key, CcType type, PersistableBundle bundle) {
1099 String result;
1100 StringBuilder valueString = new StringBuilder();
1101 String typeString = type.toString();
1102 Object value = bundle.get(key);
1103
1104 if (value == null) {
1105 valueString.append("null");
1106 } else {
1107 switch (type) {
1108 case DOUBLE_ARRAY: {
1109 // Format the string representation of the int array as value1 value2......
1110 double[] valueArray = (double[]) value;
1111 for (int i = 0; i < valueArray.length; i++) {
1112 if (i != 0) {
1113 valueString.append(" ");
1114 }
1115 valueString.append(valueArray[i]);
1116 }
1117 break;
1118 }
1119 case INT_ARRAY: {
1120 // Format the string representation of the int array as value1 value2......
1121 int[] valueArray = (int[]) value;
1122 for (int i = 0; i < valueArray.length; i++) {
1123 if (i != 0) {
1124 valueString.append(" ");
1125 }
1126 valueString.append(valueArray[i]);
1127 }
1128 break;
1129 }
1130 case LONG_ARRAY: {
1131 // Format the string representation of the int array as value1 value2......
1132 long[] valueArray = (long[]) value;
1133 for (int i = 0; i < valueArray.length; i++) {
1134 if (i != 0) {
1135 valueString.append(" ");
1136 }
1137 valueString.append(valueArray[i]);
1138 }
1139 break;
1140 }
1141 case STRING: {
1142 valueString.append("\"" + value.toString() + "\"");
1143 break;
1144 }
1145 case STRING_ARRAY: {
1146 // Format the string representation of the string array as "value1" "value2"....
1147 String[] valueArray = (String[]) value;
1148 for (int i = 0; i < valueArray.length; i++) {
1149 if (i != 0) {
1150 valueString.append(" ");
1151 }
1152 if (valueArray[i] != null) {
1153 valueString.append("\"" + valueArray[i] + "\"");
1154 } else {
1155 valueString.append("null");
1156 }
1157 }
1158 break;
1159 }
1160 default: {
1161 valueString.append(value.toString());
1162 }
1163 }
1164 }
1165 return String.format("%-70s %-15s %s", key, typeString, valueString);
1166 }
1167
1168 private PersistableBundle getOverrideBundle(String tag, CcType type, String key,
1169 ArrayList<String> valueList) {
1170 PrintWriter errPw = getErrPrintWriter();
1171 PersistableBundle bundle = new PersistableBundle();
1172
1173 // First verify that a valid number of values has been provided for the type.
1174 switch (type) {
1175 case BOOLEAN:
1176 case DOUBLE:
1177 case INT:
1178 case LONG: {
1179 if (valueList.size() != 1) {
1180 errPw.println(tag + "Expected 1 value for type " + type
1181 + ". Found: " + valueList.size());
1182 return null;
1183 }
1184 break;
1185 }
1186 case STRING: {
1187 if (valueList.size() > 1) {
1188 errPw.println(tag + "Expected 0 or 1 values for type " + type
1189 + ". Found: " + valueList.size());
1190 return null;
1191 }
1192 break;
1193 }
1194 }
1195
1196 // Parse the value according to type and add it to the Bundle.
1197 switch (type) {
1198 case BOOLEAN: {
1199 if ("true".equalsIgnoreCase(valueList.get(0))) {
1200 bundle.putBoolean(key, true);
1201 } else if ("false".equalsIgnoreCase(valueList.get(0))) {
1202 bundle.putBoolean(key, false);
1203 } else {
1204 errPw.println(tag + "Unable to parse " + valueList.get(0) + " as a " + type);
1205 return null;
1206 }
1207 break;
1208 }
1209 case DOUBLE: {
1210 try {
1211 bundle.putDouble(key, Double.parseDouble(valueList.get(0)));
1212 } catch (NumberFormatException nfe) {
1213 // Not a valid double
1214 errPw.println(tag + "Unable to parse " + valueList.get(0) + " as a " + type);
1215 return null;
1216 }
1217 break;
1218 }
1219 case DOUBLE_ARRAY: {
1220 double[] valueDoubleArray = null;
1221 if (valueList.size() > 0) {
1222 valueDoubleArray = new double[valueList.size()];
1223 for (int i = 0; i < valueList.size(); i++) {
1224 try {
1225 valueDoubleArray[i] = Double.parseDouble(valueList.get(i));
1226 } catch (NumberFormatException nfe) {
1227 // Not a valid double
1228 errPw.println(
1229 tag + "Unable to parse " + valueList.get(i) + " as a double.");
1230 return null;
1231 }
1232 }
1233 }
1234 bundle.putDoubleArray(key, valueDoubleArray);
1235 break;
1236 }
1237 case INT: {
1238 try {
1239 bundle.putInt(key, Integer.parseInt(valueList.get(0)));
1240 } catch (NumberFormatException nfe) {
1241 // Not a valid integer
1242 errPw.println(tag + "Unable to parse " + valueList.get(0) + " as an " + type);
1243 return null;
1244 }
1245 break;
1246 }
1247 case INT_ARRAY: {
1248 int[] valueIntArray = null;
1249 if (valueList.size() > 0) {
1250 valueIntArray = new int[valueList.size()];
1251 for (int i = 0; i < valueList.size(); i++) {
1252 try {
1253 valueIntArray[i] = Integer.parseInt(valueList.get(i));
1254 } catch (NumberFormatException nfe) {
1255 // Not a valid integer
1256 errPw.println(tag
1257 + "Unable to parse " + valueList.get(i) + " as an integer.");
1258 return null;
1259 }
1260 }
1261 }
1262 bundle.putIntArray(key, valueIntArray);
1263 break;
1264 }
1265 case LONG: {
1266 try {
1267 bundle.putLong(key, Long.parseLong(valueList.get(0)));
1268 } catch (NumberFormatException nfe) {
1269 // Not a valid long
1270 errPw.println(tag + "Unable to parse " + valueList.get(0) + " as a " + type);
1271 return null;
1272 }
1273 break;
1274 }
1275 case LONG_ARRAY: {
1276 long[] valueLongArray = null;
1277 if (valueList.size() > 0) {
1278 valueLongArray = new long[valueList.size()];
1279 for (int i = 0; i < valueList.size(); i++) {
1280 try {
1281 valueLongArray[i] = Long.parseLong(valueList.get(i));
1282 } catch (NumberFormatException nfe) {
1283 // Not a valid long
1284 errPw.println(
1285 tag + "Unable to parse " + valueList.get(i) + " as a long");
1286 return null;
1287 }
1288 }
1289 }
1290 bundle.putLongArray(key, valueLongArray);
1291 break;
1292 }
1293 case STRING: {
1294 String value = null;
1295 if (valueList.size() > 0) {
1296 value = valueList.get(0);
1297 }
1298 bundle.putString(key, value);
1299 break;
1300 }
1301 case STRING_ARRAY: {
1302 String[] valueStringArray = null;
1303 if (valueList.size() > 0) {
1304 valueStringArray = new String[valueList.size()];
1305 valueList.toArray(valueStringArray);
1306 }
1307 bundle.putStringArray(key, valueStringArray);
1308 break;
1309 }
1310 }
1311 return bundle;
1312 }
Shuo Qian489d9282020-07-09 11:30:03 -07001313
1314 private int handleEndBlockSuppressionCommand() {
1315 if (!checkShellUid()) {
1316 return -1;
1317 }
1318
1319 if (BlockedNumberContract.SystemContract.getBlockSuppressionStatus(mContext).isSuppressed) {
1320 BlockedNumberContract.SystemContract.endBlockSuppression(mContext);
1321 }
1322 return 0;
1323 }
Hui Wang0866fcc2020-10-12 12:14:23 -07001324
Michele Berionne38c1afa2020-12-28 20:23:16 +00001325 private int handleRestartModemCommand() {
1326 // Verify that the user is allowed to run the command. Only allowed in rooted device in a
1327 // non user build.
1328 if (Binder.getCallingUid() != Process.ROOT_UID || TelephonyUtils.IS_USER) {
1329 getErrPrintWriter().println("RestartModem: Permission denied.");
1330 return -1;
1331 }
1332
1333 boolean result = TelephonyManager.getDefault().rebootRadio();
1334 getOutPrintWriter().println(result);
1335
1336 return result ? 0 : -1;
1337 }
1338
Hui Wang0866fcc2020-10-12 12:14:23 -07001339 private int handleGbaCommand() {
1340 String arg = getNextArg();
1341 if (arg == null) {
1342 onHelpGba();
1343 return 0;
1344 }
1345
1346 switch (arg) {
1347 case GBA_SET_SERVICE: {
1348 return handleGbaSetServiceCommand();
1349 }
1350 case GBA_GET_SERVICE: {
1351 return handleGbaGetServiceCommand();
1352 }
1353 case GBA_SET_RELEASE_TIME: {
1354 return handleGbaSetReleaseCommand();
1355 }
1356 case GBA_GET_RELEASE_TIME: {
1357 return handleGbaGetReleaseCommand();
1358 }
1359 }
1360
1361 return -1;
1362 }
1363
1364 private int getSubId(String cmd) {
1365 int slotId = getDefaultSlot();
1366 String opt = getNextOption();
1367 if (opt != null && opt.equals("-s")) {
1368 try {
1369 slotId = Integer.parseInt(getNextArgRequired());
1370 } catch (NumberFormatException e) {
1371 getErrPrintWriter().println(cmd + " requires an integer as a SLOT_ID.");
1372 return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
1373 }
1374 }
1375 int[] subIds = SubscriptionManager.getSubId(slotId);
1376 return subIds[0];
1377 }
1378
1379 private int handleGbaSetServiceCommand() {
1380 int subId = getSubId("gba set-service");
1381 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
1382 return -1;
1383 }
1384
1385 String packageName = getNextArg();
1386 try {
1387 if (packageName == null) {
1388 packageName = "";
1389 }
1390 boolean result = mInterface.setBoundGbaServiceOverride(subId, packageName);
1391 if (VDBG) {
1392 Log.v(LOG_TAG, "gba set-service -s " + subId + " "
1393 + packageName + ", result=" + result);
1394 }
1395 getOutPrintWriter().println(result);
1396 } catch (RemoteException e) {
1397 Log.w(LOG_TAG, "gba set-service " + subId + " "
1398 + packageName + ", error" + e.getMessage());
1399 getErrPrintWriter().println("Exception: " + e.getMessage());
1400 return -1;
1401 }
1402 return 0;
1403 }
1404
1405 private int handleGbaGetServiceCommand() {
1406 String result;
1407
1408 int subId = getSubId("gba get-service");
1409 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
1410 return -1;
1411 }
1412
1413 try {
1414 result = mInterface.getBoundGbaService(subId);
1415 } catch (RemoteException e) {
1416 return -1;
1417 }
1418 if (VDBG) {
1419 Log.v(LOG_TAG, "gba get-service -s " + subId + ", returned: " + result);
1420 }
1421 getOutPrintWriter().println(result);
1422 return 0;
1423 }
1424
1425 private int handleGbaSetReleaseCommand() {
1426 //the release time value could be -1
1427 int subId = getRemainingArgsCount() > 1 ? getSubId("gba set-release")
1428 : SubscriptionManager.getDefaultSubscriptionId();
1429 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
1430 return -1;
1431 }
1432
1433 String intervalStr = getNextArg();
1434 if (intervalStr == null) {
1435 return -1;
1436 }
1437
1438 try {
1439 int interval = Integer.parseInt(intervalStr);
1440 boolean result = mInterface.setGbaReleaseTimeOverride(subId, interval);
1441 if (VDBG) {
1442 Log.v(LOG_TAG, "gba set-release -s " + subId + " "
1443 + intervalStr + ", result=" + result);
1444 }
1445 getOutPrintWriter().println(result);
1446 } catch (NumberFormatException | RemoteException e) {
1447 Log.w(LOG_TAG, "gba set-release -s " + subId + " "
1448 + intervalStr + ", error" + e.getMessage());
1449 getErrPrintWriter().println("Exception: " + e.getMessage());
1450 return -1;
1451 }
1452 return 0;
1453 }
1454
1455 private int handleGbaGetReleaseCommand() {
1456 int subId = getSubId("gba get-release");
1457 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
1458 return -1;
1459 }
1460
1461 int result = 0;
1462 try {
1463 result = mInterface.getGbaReleaseTime(subId);
1464 } catch (RemoteException e) {
1465 return -1;
1466 }
1467 if (VDBG) {
1468 Log.v(LOG_TAG, "gba get-release -s " + subId + ", returned: " + result);
1469 }
1470 getOutPrintWriter().println(result);
1471 return 0;
1472 }
Hui Wang068ab862020-10-31 05:12:53 +00001473
1474 private int handleSingleRegistrationConfigCommand() {
1475 String arg = getNextArg();
1476 if (arg == null) {
1477 onHelpSrc();
1478 return 0;
1479 }
1480
1481 switch (arg) {
1482 case SRC_SET_DEVICE_ENABLED: {
1483 return handleSrcSetDeviceEnabledCommand();
1484 }
1485 case SRC_GET_DEVICE_ENABLED: {
1486 return handleSrcGetDeviceEnabledCommand();
1487 }
1488 case SRC_SET_CARRIER_ENABLED: {
1489 return handleSrcSetCarrierEnabledCommand();
1490 }
1491 case SRC_GET_CARRIER_ENABLED: {
1492 return handleSrcGetCarrierEnabledCommand();
1493 }
1494 }
1495
1496 return -1;
1497 }
1498
1499 private int handleSrcSetDeviceEnabledCommand() {
1500 String enabledStr = getNextArg();
1501 if (enabledStr == null) {
1502 return -1;
1503 }
1504
1505 try {
1506 mInterface.setDeviceSingleRegistrationEnabledOverride(enabledStr);
1507 if (VDBG) {
1508 Log.v(LOG_TAG, "src set-device-enabled " + enabledStr + ", done");
1509 }
1510 getOutPrintWriter().println("Done");
1511 } catch (NumberFormatException | RemoteException e) {
1512 Log.w(LOG_TAG, "src set-device-enabled " + enabledStr + ", error" + e.getMessage());
1513 getErrPrintWriter().println("Exception: " + e.getMessage());
1514 return -1;
1515 }
1516 return 0;
1517 }
1518
1519 private int handleSrcGetDeviceEnabledCommand() {
1520 boolean result = false;
1521 try {
1522 result = mInterface.getDeviceSingleRegistrationEnabled();
1523 } catch (RemoteException e) {
1524 return -1;
1525 }
1526 if (VDBG) {
1527 Log.v(LOG_TAG, "src get-device-enabled, returned: " + result);
1528 }
1529 getOutPrintWriter().println(result);
1530 return 0;
1531 }
1532
1533 private int handleSrcSetCarrierEnabledCommand() {
1534 //the release time value could be -1
1535 int subId = getRemainingArgsCount() > 1 ? getSubId("src set-carrier-enabled")
1536 : SubscriptionManager.getDefaultSubscriptionId();
1537 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
1538 return -1;
1539 }
1540
1541 String enabledStr = getNextArg();
1542 if (enabledStr == null) {
1543 return -1;
1544 }
1545
1546 try {
1547 boolean result =
1548 mInterface.setCarrierSingleRegistrationEnabledOverride(subId, enabledStr);
1549 if (VDBG) {
1550 Log.v(LOG_TAG, "src set-carrier-enabled -s " + subId + " "
1551 + enabledStr + ", result=" + result);
1552 }
1553 getOutPrintWriter().println(result);
1554 } catch (NumberFormatException | RemoteException e) {
1555 Log.w(LOG_TAG, "src set-carrier-enabled -s " + subId + " "
1556 + enabledStr + ", error" + e.getMessage());
1557 getErrPrintWriter().println("Exception: " + e.getMessage());
1558 return -1;
1559 }
1560 return 0;
1561 }
1562
1563 private int handleSrcGetCarrierEnabledCommand() {
1564 int subId = getSubId("src get-carrier-enabled");
1565 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
1566 return -1;
1567 }
1568
1569 boolean result = false;
1570 try {
1571 result = mInterface.getCarrierSingleRegistrationEnabled(subId);
1572 } catch (RemoteException e) {
1573 return -1;
1574 }
1575 if (VDBG) {
1576 Log.v(LOG_TAG, "src get-carrier-enabled -s " + subId + ", returned: " + result);
1577 }
1578 getOutPrintWriter().println(result);
1579 return 0;
1580 }
Brad Ebinger4dc095a2018-04-03 15:17:52 -07001581}