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
17package com.android.phone;
18
19import android.app.Activity;
20import android.app.AlertDialog;
21import android.content.ActivityNotFoundException;
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.Intent;
25import android.net.Uri;
26import android.provider.Settings;
27
28import com.android.internal.telephony.TelephonyIntents;
29import com.android.internal.telephony.Phone;
30
31import android.telephony.PhoneNumberUtils;
32import android.util.Log;
33import android.view.WindowManager;
34
35import 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 */
57public 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. */
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     */
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     */
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     */
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     */
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
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
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
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
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
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
261    private static void log(String msg) {
262        Log.d(TAG, "[SpecialCharSequenceMgr] " + msg);
263    }
264}
265