1 /*
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 
17 package com.android.phone;
18 
19 import android.app.Activity;
20 import android.app.AlertDialog;
21 import android.content.ActivityNotFoundException;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.os.UserManager;
25 import android.provider.Settings;
26 import android.telephony.PhoneNumberUtils;
27 import android.telephony.SubscriptionInfo;
28 import android.telephony.SubscriptionManager;
29 import android.telephony.TelephonyManager;
30 import android.util.Log;
31 import android.view.WindowManager;
32 
33 import com.android.internal.telephony.IccCardConstants;
34 import com.android.internal.telephony.Phone;
35 import com.android.internal.telephony.TelephonyCapabilities;
36 import com.android.internal.telephony.flags.Flags;
37 
38 import java.util.ArrayList;
39 import java.util.List;
40 
41 /**
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  */
61 public 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. */
SpecialCharSequenceMgr()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      */
handleChars(Context context, String input)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      */
handleChars(Context context, String input, Activity pukInputActivity)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)
115             || handleSecretCode(dialString)) {
116             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      */
handleCharsForLockedDevice(Context context, String input, Activity pukInputActivity)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     /**
150      * 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.
153      *
154      * @param input the text to check for a secret code in
155      * @return true if a secret code was encountered and intent is sent out
156      */
handleSecretCode(String input)157     static private boolean handleSecretCode(String input) {
158         // 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;
166     }
167 
handleAdnEntry(Context context, String input)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 
getSimState(int slotId, Context context)197     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 
getNextSubIdForState(IccCardConstants.State state, Context context)210     private static int getNextSubIdForState(IccCardConstants.State state, Context context) {
211         SubscriptionManager subscriptionManager = SubscriptionManager.from(context);
212         if (Flags.workProfileApiSplit()) {
213             subscriptionManager = subscriptionManager.createForAllUserProfiles();
214         }
215         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 
handlePinEntry(Context context, String input, Activity pukInputActivity)234     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("#")) {
241             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);
260 
261                 // 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 {
271                 AlertDialog dialog = FrameworksUtils.makeAlertDialogBuilder(context)
272                         .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;
279             }
280         }
281         return false;
282     }
283 
handleIMEIDisplay(Context context, String input)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 
showDeviceIdPanel(Context context)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 
301         AlertDialog alert = FrameworksUtils.makeAlertDialogBuilder(context)
302                 .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 
handleRegulatoryInfoDisplay(Context context, String input)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");
314             Intent showRegInfoIntent = new Intent(Settings.ACTION_SHOW_REGULATORY_INFO);
315             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 
log(String msg)325     private static void log(String msg) {
326         Log.d(TAG, "[SpecialCharSequenceMgr] " + msg);
327     }
328 }
329