blob: 514b2c9629ea1f27a159234ca74a23664ccf90b4 [file] [log] [blame]
Santos Cordon7d4ddf62013-07-10 11:58:08 -07001/*
2 * Copyright (C) 2006 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
19import android.app.Activity;
20import android.app.AlertDialog;
21import android.content.ActivityNotFoundException;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070022import android.content.Context;
23import android.content.Intent;
Jeff Sharkey236e67c2014-04-16 17:21:29 -070024import android.provider.Settings;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070025import android.telephony.PhoneNumberUtils;
Youming Yec3fb55d2019-03-20 11:14:07 -070026import android.telephony.SubscriptionInfo;
27import android.telephony.SubscriptionManager;
28import android.telephony.TelephonyManager;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070029import android.util.Log;
30import android.view.WindowManager;
31
Youming Yec3fb55d2019-03-20 11:14:07 -070032import com.android.internal.telephony.IccCardConstants;
Aravind Sreekumarafc08c52018-04-10 15:34:32 -070033import com.android.internal.telephony.Phone;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070034import com.android.internal.telephony.TelephonyCapabilities;
35
Youming Yec3fb55d2019-03-20 11:14:07 -070036import java.util.ArrayList;
37import java.util.List;
38
Santos Cordon7d4ddf62013-07-10 11:58:08 -070039/**
40 * Helper class to listen for some magic dialpad character sequences
41 * that are handled specially by the Phone app.
42 *
43 * Note the Contacts app also handles these sequences too, so there's a
44 * separate version of this class under apps/Contacts.
45 *
46 * In fact, the most common use case for these special sequences is typing
47 * them from the regular "Dialer" used for outgoing calls, which is part
48 * of the contacts app; see DialtactsActivity and DialpadFragment.
49 * *This* version of SpecialCharSequenceMgr is used for only a few
50 * relatively obscure places in the UI:
51 * - The "SIM network unlock" PIN entry screen (see
52 * IccNetworkDepersonalizationPanel.java)
53 * - The emergency dialer (see EmergencyDialer.java).
54 *
55 * TODO: there's lots of duplicated code between this class and the
56 * corresponding class under apps/Contacts. Let's figure out a way to
57 * unify these two classes (in the framework? in a common shared library?)
58 */
59public class SpecialCharSequenceMgr {
60 private static final String TAG = PhoneGlobals.LOG_TAG;
61 private static final boolean DBG = false;
62
63 private static final String MMI_IMEI_DISPLAY = "*#06#";
64 private static final String MMI_REGULATORY_INFO_DISPLAY = "*#07#";
65
66 /** This class is never instantiated. */
67 private SpecialCharSequenceMgr() {
68 }
69
70 /**
71 * Check for special strings of digits from an input
72 * string.
73 * @param context input Context for the events we handle.
74 * @param input the dial string to be examined.
75 */
76 static boolean handleChars(Context context, String input) {
77 return handleChars(context, input, null);
78 }
79
80 /**
81 * Generally used for the Personal Unblocking Key (PUK) unlocking
82 * case, where we want to be able to maintain a handle to the
83 * calling activity so that we can close it or otherwise display
84 * indication if the PUK code is recognized.
85 *
86 * NOTE: The counterpart to this file in Contacts does
87 * NOT contain the special PUK handling code, since it
88 * does NOT need it. When the device gets into PUK-
89 * locked state, the keyguard comes up and the only way
90 * to unlock the device is through the Emergency dialer,
91 * which is still in the Phone App.
92 *
93 * @param context input Context for the events we handle.
94 * @param input the dial string to be examined.
95 * @param pukInputActivity activity that originated this
96 * PUK call, tracked so that we can close it or otherwise
97 * indicate that special character sequence is
98 * successfully processed. Can be null.
99 * @return true if the input was a special string which has been
100 * handled.
101 */
102 static boolean handleChars(Context context,
103 String input,
104 Activity pukInputActivity) {
105
106 //get rid of the separators so that the string gets parsed correctly
107 String dialString = PhoneNumberUtils.stripSeparators(input);
108
109 if (handleIMEIDisplay(context, dialString)
110 || handleRegulatoryInfoDisplay(context, dialString)
111 || handlePinEntry(context, dialString, pukInputActivity)
112 || handleAdnEntry(context, dialString)
fionaxue559f972017-01-24 22:31:12 -0800113 || handleSecretCode(dialString)) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700114 return true;
115 }
116
117 return false;
118 }
119
120 /**
121 * Variant of handleChars() that looks for the subset of "special
122 * sequences" that are available even if the device is locked.
123 *
124 * (Specifically, these are the sequences that you're allowed to type
125 * in the Emergency Dialer, which is accessible *without* unlocking
126 * the device.)
127 */
128 static boolean handleCharsForLockedDevice(Context context,
129 String input,
130 Activity pukInputActivity) {
131 // Get rid of the separators so that the string gets parsed correctly
132 String dialString = PhoneNumberUtils.stripSeparators(input);
133
134 // The only sequences available on a locked device are the "**04"
135 // or "**05" sequences that allow you to enter PIN or PUK-related
136 // codes. (e.g. for the case where you're currently locked out of
137 // your phone, and need to change the PIN! The only way to do
138 // that is via the Emergency Dialer.)
139
140 if (handlePinEntry(context, dialString, pukInputActivity)) {
141 return true;
142 }
143
144 return false;
145 }
146
147 /**
fionaxue559f972017-01-24 22:31:12 -0800148 * Handles secret codes to launch arbitrary receivers in the form of *#*#<code>#*#*.
149 * If a secret code is encountered, an broadcast intent is sent with the
150 * android_secret_code://<code> URI.
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700151 *
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700152 * @param input the text to check for a secret code in
fionaxue559f972017-01-24 22:31:12 -0800153 * @return true if a secret code was encountered and intent is sent out
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700154 */
fionaxue559f972017-01-24 22:31:12 -0800155 static private boolean handleSecretCode(String input) {
fionaxu235cc5e2017-03-06 22:25:57 -0800156 // Secret codes are in the form *#*#<code>#*#*
157 int len = input.length();
158 if (len > 8 && input.startsWith("*#*#") && input.endsWith("#*#*")) {
159 final Phone phone = PhoneGlobals.getPhone();
160 phone.sendDialerSpecialCode(input.substring(4, len - 4));
161 return true;
162 }
163 return false;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700164 }
165
166 static private boolean handleAdnEntry(Context context, String input) {
167 /* ADN entries are of the form "N(N)(N)#" */
168
169 // if the phone is keyguard-restricted, then just ignore this
170 // input. We want to make sure that sim card contacts are NOT
171 // exposed unless the phone is unlocked, and this code can be
172 // accessed from the emergency dialer.
173 if (PhoneGlobals.getInstance().getKeyguardManager().inKeyguardRestrictedInputMode()) {
174 return false;
175 }
176
177 int len = input.length();
178 if ((len > 1) && (len < 5) && (input.endsWith("#"))) {
179 try {
180 int index = Integer.parseInt(input.substring(0, len-1));
181 Intent intent = new Intent(Intent.ACTION_PICK);
182
183 intent.setClassName("com.android.phone",
184 "com.android.phone.SimContacts");
185 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
186 intent.putExtra("index", index);
187 PhoneGlobals.getInstance().startActivity(intent);
188
189 return true;
190 } catch (NumberFormatException ex) {}
191 }
192 return false;
193 }
194
Youming Yec3fb55d2019-03-20 11:14:07 -0700195 private static IccCardConstants.State getSimState(int slotId, Context context) {
196 final TelephonyManager tele = TelephonyManager.from(context);
197 int simState = tele.getSimState(slotId);
198 IccCardConstants.State state;
199 try {
200 state = IccCardConstants.State.intToState(simState);
201 } catch (IllegalArgumentException ex) {
202 Log.w(TAG, "Unknown sim state: " + simState);
203 state = IccCardConstants.State.UNKNOWN;
204 }
205 return state;
206 }
207
208 private static int getNextSubIdForState(IccCardConstants.State state, Context context) {
209 SubscriptionManager subscriptionManager = SubscriptionManager.from(context);
210 List<SubscriptionInfo> list = subscriptionManager.getActiveSubscriptionInfoList();
211 if (list == null) {
212 // getActiveSubscriptionInfoList was null callers expect an empty list.
213 list = new ArrayList<>();
214 }
215 int resultId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
216 int bestSlotId = Integer.MAX_VALUE; // Favor lowest slot first
217 for (int i = 0; i < list.size(); i++) {
218 final SubscriptionInfo info = list.get(i);
219 final int id = info.getSubscriptionId();
220 if (state == getSimState(info.getSimSlotIndex(), context)
221 && bestSlotId > info.getSimSlotIndex()) {
222 resultId = id;
223 bestSlotId = info.getSimSlotIndex();
224 }
225 }
226 return resultId;
227 }
228
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700229 static private boolean handlePinEntry(Context context, String input,
230 Activity pukInputActivity) {
231 // TODO: The string constants here should be removed in favor
232 // of some call to a static the MmiCode class that determines
233 // if a dialstring is an MMI code.
234 if ((input.startsWith("**04") || input.startsWith("**05"))
235 && input.endsWith("#")) {
236 PhoneGlobals app = PhoneGlobals.getInstance();
Youming Yec3fb55d2019-03-20 11:14:07 -0700237 Phone phone;
238 int subId;
239 if (input.startsWith("**04")) {
240 subId = getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED, context);
241 } else {
242 subId = getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED, context);
243 }
244 if (SubscriptionManager.isValidSubscriptionId(subId)) {
245 log("get phone with subId: " + subId);
246 phone = PhoneGlobals.getPhone(subId);
247 } else {
248 log("get default phone");
249 phone = PhoneGlobals.getPhone();
250 }
Stuart Scottdcf40a92014-12-09 10:45:01 -0800251 boolean isMMIHandled = phone.handlePinMmi(input);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700252
253 // if the PUK code is recognized then indicate to the
254 // phone app that an attempt to unPUK the device was
255 // made with this activity. The PUK code may still
256 // fail though, but we won't know until the MMI code
257 // returns a result.
258 if (isMMIHandled && input.startsWith("**05")) {
259 app.setPukEntryActivity(pukInputActivity);
260 }
261 return isMMIHandled;
262 }
263 return false;
264 }
265
266 static private boolean handleIMEIDisplay(Context context,
267 String input) {
268 if (input.equals(MMI_IMEI_DISPLAY)) {
269 showDeviceIdPanel(context);
270 return true;
271 }
272
273 return false;
274 }
275
276 static private void showDeviceIdPanel(Context context) {
277 if (DBG) log("showDeviceIdPanel()...");
278
279 Phone phone = PhoneGlobals.getPhone();
280 int labelId = TelephonyCapabilities.getDeviceIdLabel(phone);
281 String deviceId = phone.getDeviceId();
282
283 AlertDialog alert = new AlertDialog.Builder(context)
284 .setTitle(labelId)
285 .setMessage(deviceId)
286 .setPositiveButton(R.string.ok, null)
287 .setCancelable(false)
288 .create();
289 alert.getWindow().setType(WindowManager.LayoutParams.TYPE_PRIORITY_PHONE);
290 alert.show();
291 }
292
293 private static boolean handleRegulatoryInfoDisplay(Context context, String input) {
294 if (input.equals(MMI_REGULATORY_INFO_DISPLAY)) {
295 log("handleRegulatoryInfoDisplay() sending intent to settings app");
Jeff Sharkey236e67c2014-04-16 17:21:29 -0700296 Intent showRegInfoIntent = new Intent(Settings.ACTION_SHOW_REGULATORY_INFO);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700297 try {
298 context.startActivity(showRegInfoIntent);
299 } catch (ActivityNotFoundException e) {
300 Log.e(TAG, "startActivity() failed: " + e);
301 }
302 return true;
303 }
304 return false;
305 }
306
307 private static void log(String msg) {
308 Log.d(TAG, "[SpecialCharSequenceMgr] " + msg);
309 }
310}