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