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