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.ComponentName;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.net.Uri;
26 import android.provider.Settings;
27 
28 import com.android.internal.telephony.TelephonyIntents;
29 import com.android.internal.telephony.Phone;
30 
31 import android.telephony.PhoneNumberUtils;
32 import android.util.Log;
33 import android.view.WindowManager;
34 
35 import com.android.internal.telephony.TelephonyCapabilities;
36 
37 /**
38  * Helper class to listen for some magic dialpad character sequences
39  * that are handled specially by the Phone app.
40  *
41  * Note the Contacts app also handles these sequences too, so there's a
42  * separate version of this class under apps/Contacts.
43  *
44  * In fact, the most common use case for these special sequences is typing
45  * them from the regular "Dialer" used for outgoing calls, which is part
46  * of the contacts app; see DialtactsActivity and DialpadFragment.
47  * *This* version of SpecialCharSequenceMgr is used for only a few
48  * relatively obscure places in the UI:
49  * - The "SIM network unlock" PIN entry screen (see
50  *   IccNetworkDepersonalizationPanel.java)
51  * - The emergency dialer (see EmergencyDialer.java).
52  *
53  * TODO: there's lots of duplicated code between this class and the
54  * corresponding class under apps/Contacts.  Let's figure out a way to
55  * unify these two classes (in the framework? in a common shared library?)
56  */
57 public class SpecialCharSequenceMgr {
58     private static final String TAG = PhoneGlobals.LOG_TAG;
59     private static final boolean DBG = false;
60 
61     private static final String MMI_IMEI_DISPLAY = "*#06#";
62     private static final String MMI_REGULATORY_INFO_DISPLAY = "*#07#";
63 
64     /** This class is never instantiated. */
SpecialCharSequenceMgr()65     private SpecialCharSequenceMgr() {
66     }
67 
68     /**
69      * Check for special strings of digits from an input
70      * string.
71      * @param context input Context for the events we handle.
72      * @param input the dial string to be examined.
73      */
handleChars(Context context, String input)74     static boolean handleChars(Context context, String input) {
75         return handleChars(context, input, null);
76     }
77 
78     /**
79      * Generally used for the Personal Unblocking Key (PUK) unlocking
80      * case, where we want to be able to maintain a handle to the
81      * calling activity so that we can close it or otherwise display
82      * indication if the PUK code is recognized.
83      *
84      * NOTE: The counterpart to this file in Contacts does
85      * NOT contain the special PUK handling code, since it
86      * does NOT need it.  When the device gets into PUK-
87      * locked state, the keyguard comes up and the only way
88      * to unlock the device is through the Emergency dialer,
89      * which is still in the Phone App.
90      *
91      * @param context input Context for the events we handle.
92      * @param input the dial string to be examined.
93      * @param pukInputActivity activity that originated this
94      * PUK call, tracked so that we can close it or otherwise
95      * indicate that special character sequence is
96      * successfully processed. Can be null.
97      * @return true if the input was a special string which has been
98      * handled.
99      */
handleChars(Context context, String input, Activity pukInputActivity)100     static boolean handleChars(Context context,
101                                String input,
102                                Activity pukInputActivity) {
103 
104         //get rid of the separators so that the string gets parsed correctly
105         String dialString = PhoneNumberUtils.stripSeparators(input);
106 
107         if (handleIMEIDisplay(context, dialString)
108             || handleRegulatoryInfoDisplay(context, dialString)
109             || handlePinEntry(context, dialString, pukInputActivity)
110             || handleAdnEntry(context, dialString)
111             || handleSecretCode(context, dialString)) {
112             return true;
113         }
114 
115         return false;
116     }
117 
118     /**
119      * Variant of handleChars() that looks for the subset of "special
120      * sequences" that are available even if the device is locked.
121      *
122      * (Specifically, these are the sequences that you're allowed to type
123      * in the Emergency Dialer, which is accessible *without* unlocking
124      * the device.)
125      */
handleCharsForLockedDevice(Context context, String input, Activity pukInputActivity)126     static boolean handleCharsForLockedDevice(Context context,
127                                               String input,
128                                               Activity pukInputActivity) {
129         // Get rid of the separators so that the string gets parsed correctly
130         String dialString = PhoneNumberUtils.stripSeparators(input);
131 
132         // The only sequences available on a locked device are the "**04"
133         // or "**05" sequences that allow you to enter PIN or PUK-related
134         // codes.  (e.g. for the case where you're currently locked out of
135         // your phone, and need to change the PIN!  The only way to do
136         // that is via the Emergency Dialer.)
137 
138         if (handlePinEntry(context, dialString, pukInputActivity)) {
139             return true;
140         }
141 
142         return false;
143     }
144 
145     /**
146      * Handles secret codes to launch arbitrary activities in the form of *#*#<code>#*#*.
147      * If a secret code is encountered an Intent is started with the android_secret_code://<code>
148      * URI.
149      *
150      * @param context the context to use
151      * @param input the text to check for a secret code in
152      * @return true if a secret code was encountered
153      */
handleSecretCode(Context context, String input)154     static private boolean handleSecretCode(Context context, String input) {
155         // Secret codes are in the form *#*#<code>#*#*
156         int len = input.length();
157         if (len > 8 && input.startsWith("*#*#") && input.endsWith("#*#*")) {
158             Intent intent = new Intent(TelephonyIntents.SECRET_CODE_ACTION,
159                     Uri.parse("android_secret_code://" + input.substring(4, len - 4)));
160             context.sendBroadcast(intent);
161             return true;
162         }
163 
164         return false;
165     }
166 
handleAdnEntry(Context context, String input)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 
handlePinEntry(Context context, String input, Activity pukInputActivity)196     static private boolean handlePinEntry(Context context, String input,
197                                           Activity pukInputActivity) {
198         // TODO: The string constants here should be removed in favor
199         // of some call to a static the MmiCode class that determines
200         // if a dialstring is an MMI code.
201         if ((input.startsWith("**04") || input.startsWith("**05"))
202                 && input.endsWith("#")) {
203             PhoneGlobals app = PhoneGlobals.getInstance();
204             Phone phone = PhoneGlobals.getPhone();
205             boolean isMMIHandled = phone.handlePinMmi(input);
206 
207             // if the PUK code is recognized then indicate to the
208             // phone app that an attempt to unPUK the device was
209             // made with this activity.  The PUK code may still
210             // fail though, but we won't know until the MMI code
211             // returns a result.
212             if (isMMIHandled && input.startsWith("**05")) {
213                 app.setPukEntryActivity(pukInputActivity);
214             }
215             return isMMIHandled;
216         }
217         return false;
218     }
219 
handleIMEIDisplay(Context context, String input)220     static private boolean handleIMEIDisplay(Context context,
221                                              String input) {
222         if (input.equals(MMI_IMEI_DISPLAY)) {
223             showDeviceIdPanel(context);
224             return true;
225         }
226 
227         return false;
228     }
229 
showDeviceIdPanel(Context context)230     static private void showDeviceIdPanel(Context context) {
231         if (DBG) log("showDeviceIdPanel()...");
232 
233         Phone phone = PhoneGlobals.getPhone();
234         int labelId = TelephonyCapabilities.getDeviceIdLabel(phone);
235         String deviceId = phone.getDeviceId();
236 
237         AlertDialog alert = new AlertDialog.Builder(context)
238                 .setTitle(labelId)
239                 .setMessage(deviceId)
240                 .setPositiveButton(R.string.ok, null)
241                 .setCancelable(false)
242                 .create();
243         alert.getWindow().setType(WindowManager.LayoutParams.TYPE_PRIORITY_PHONE);
244         alert.show();
245     }
246 
handleRegulatoryInfoDisplay(Context context, String input)247     private static boolean handleRegulatoryInfoDisplay(Context context, String input) {
248         if (input.equals(MMI_REGULATORY_INFO_DISPLAY)) {
249             log("handleRegulatoryInfoDisplay() sending intent to settings app");
250             Intent showRegInfoIntent = new Intent(Settings.ACTION_SHOW_REGULATORY_INFO);
251             try {
252                 context.startActivity(showRegInfoIntent);
253             } catch (ActivityNotFoundException e) {
254                 Log.e(TAG, "startActivity() failed: " + e);
255             }
256             return true;
257         }
258         return false;
259     }
260 
log(String msg)261     private static void log(String msg) {
262         Log.d(TAG, "[SpecialCharSequenceMgr] " + msg);
263     }
264 }
265