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.Context;
22import android.content.Intent;
23import android.net.Uri;
24import android.provider.Telephony.Intents;
25import com.android.internal.telephony.PhoneFactory;
26import com.android.internal.telephony.Phone;
27import android.telephony.PhoneNumberUtils;
28import android.util.Log;
29import android.view.WindowManager;
30
31/**
32 * Helper class to listen for some magic character sequences
33 * that are handled specially by the Phone app.
34 *
35 * TODO: there's lots of duplicated code between this class and the
36 * corresponding class under apps/Contacts.  Let's figure out a way to
37 * unify these two classes (in the framework? in a common shared library?)
38 */
39public class SpecialCharSequenceMgr {
40    private static final String TAG = PhoneApp.LOG_TAG;
41    private static final boolean DBG = false;
42
43    private static final String MMI_IMEI_DISPLAY = "*#06#";
44
45    /** This class is never instantiated. */
46    private SpecialCharSequenceMgr() {
47    }
48
49    /**
50     * Check for special strings of digits from an input
51     * string.
52     * @param context input Context for the events we handle.
53     * @param input the dial string to be examined.
54     */
55    static boolean handleChars(Context context, String input) {
56        return handleChars(context, input, null);
57    }
58
59    /**
60     * Generally used for the Personal Unblocking Key (PUK) unlocking
61     * case, where we want to be able to maintain a handle to the
62     * calling activity so that we can close it or otherwise display
63     * indication if the PUK code is recognized.
64     *
65     * NOTE: The counterpart to this file in Contacts does
66     * NOT contain the special PUK handling code, since it
67     * does NOT need it.  When the device gets into PUK-
68     * locked state, the keyguard comes up and the only way
69     * to unlock the device is through the Emergency dialer,
70     * which is still in the Phone App.
71     *
72     * @param context input Context for the events we handle.
73     * @param input the dial string to be examined.
74     * @param pukInputActivity activity that originated this
75     * PUK call, tracked so that we can close it or otherwise
76     * indicate that special character sequence is
77     * successfully processed. Can be null.
78     * @return true if the input was a special string which has been
79     * handled.
80     */
81    static boolean handleChars(Context context,
82                               String input,
83                               Activity pukInputActivity) {
84
85        //get rid of the separators so that the string gets parsed correctly
86        String dialString = PhoneNumberUtils.stripSeparators(input);
87
88        if (handleIMEIDisplay(context, dialString)
89            || handlePinEntry(context, dialString, pukInputActivity)
90            || handleAdnEntry(context, dialString)
91            || handleSecretCode(context, dialString)) {
92            return true;
93        }
94
95        return false;
96    }
97
98    /**
99     * Variant of handleChars() that looks for the subset of "special
100     * sequences" that are available even if the device is locked.
101     *
102     * (Specifically, these are the sequences that you're allowed to type
103     * in the Emergency Dialer, which is accessible *without* unlocking
104     * the device.)
105     */
106    static boolean handleCharsForLockedDevice(Context context,
107                                              String input,
108                                              Activity pukInputActivity) {
109        // Get rid of the separators so that the string gets parsed correctly
110        String dialString = PhoneNumberUtils.stripSeparators(input);
111
112        // The only sequences available on a locked device are the "**04"
113        // or "**05" sequences that allow you to enter PIN or PUK-related
114        // codes.  (e.g. for the case where you're currently locked out of
115        // your phone, and need to change the PIN!  The only way to do
116        // that is via the Emergency Dialer.)
117
118        if (handlePinEntry(context, dialString, pukInputActivity)) {
119            return true;
120        }
121
122        return false;
123    }
124
125    /**
126     * Handles secret codes to launch arbitrary activities in the form of *#*#<code>#*#*.
127     * If a secret code is encountered an Intent is started with the android_secret_code://<code>
128     * URI.
129     *
130     * @param context the context to use
131     * @param input the text to check for a secret code in
132     * @return true if a secret code was encountered
133     */
134    static private boolean handleSecretCode(Context context, String input) {
135        // Secret codes are in the form *#*#<code>#*#*
136        int len = input.length();
137        if (len > 8 && input.startsWith("*#*#") && input.endsWith("#*#*")) {
138            Intent intent = new Intent(Intents.SECRET_CODE_ACTION,
139                    Uri.parse("android_secret_code://" + input.substring(4, len - 4)));
140            context.sendBroadcast(intent);
141            return true;
142        }
143
144        return false;
145    }
146
147    static private boolean handleAdnEntry(Context context, String input) {
148        /* ADN entries are of the form "N(N)(N)#" */
149
150        // if the phone is keyguard-restricted, then just ignore this
151        // input.  We want to make sure that sim card contacts are NOT
152        // exposed unless the phone is unlocked, and this code can be
153        // accessed from the emergency dialer.
154        if (PhoneApp.getInstance().getKeyguardManager().inKeyguardRestrictedInputMode()) {
155            return false;
156        }
157
158        int len = input.length();
159        if ((len > 1) && (len < 5) && (input.endsWith("#"))) {
160            try {
161                int index = Integer.parseInt(input.substring(0, len-1));
162                Intent intent = new Intent(Intent.ACTION_PICK);
163
164                intent.setClassName("com.android.phone",
165                                    "com.android.phone.SimContacts");
166                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
167                intent.putExtra("index", index);
168                PhoneApp.getInstance().startActivity(intent);
169
170                return true;
171            } catch (NumberFormatException ex) {}
172        }
173        return false;
174    }
175
176    static private boolean handlePinEntry(Context context, String input,
177                                          Activity pukInputActivity) {
178        // TODO: The string constants here should be removed in favor
179        // of some call to a static the MmiCode class that determines
180        // if a dialstring is an MMI code.
181        if ((input.startsWith("**04") || input.startsWith("**05"))
182                && input.endsWith("#")) {
183            PhoneApp app = PhoneApp.getInstance();
184            boolean isMMIHandled = app.phone.handlePinMmi(input);
185
186            // if the PUK code is recognized then indicate to the
187            // phone app that an attempt to unPUK the device was
188            // made with this activity.  The PUK code may still
189            // fail though, but we won't know until the MMI code
190            // returns a result.
191            if (isMMIHandled && input.startsWith("**05")) {
192                app.setPukEntryActivity(pukInputActivity);
193            }
194            return isMMIHandled;
195        }
196        return false;
197    }
198
199    static private boolean handleIMEIDisplay(Context context,
200                                             String input) {
201        if (input.equals(MMI_IMEI_DISPLAY)) {
202            int phoneType = PhoneApp.getInstance().phone.getPhoneType();
203            if (phoneType == Phone.PHONE_TYPE_CDMA) {
204                showMEIDPanel(context);
205                return true;
206            } else if (phoneType == Phone.PHONE_TYPE_GSM) {
207                showIMEIPanel(context);
208                return true;
209            }
210        }
211
212        return false;
213    }
214
215    // TODO: showIMEIPanel and showMEIDPanel are almost cut and paste
216    // clones. Refactor.
217    static private void showIMEIPanel(Context context) {
218        if (DBG) log("showIMEIPanel");
219
220        String imeiStr = PhoneFactory.getDefaultPhone().getDeviceId();
221
222        AlertDialog alert = new AlertDialog.Builder(context)
223                .setTitle(R.string.imei)
224                .setMessage(imeiStr)
225                .setPositiveButton(R.string.ok, null)
226                .setCancelable(false)
227                .show();
228        alert.getWindow().setType(WindowManager.LayoutParams.TYPE_PRIORITY_PHONE);
229    }
230
231    static private void showMEIDPanel(Context context) {
232        if (DBG) log("showMEIDPanel");
233
234        String meidStr = PhoneFactory.getDefaultPhone().getDeviceId();
235
236        AlertDialog alert = new AlertDialog.Builder(context)
237                .setTitle(R.string.meid)
238                .setMessage(meidStr)
239                .setPositiveButton(R.string.ok, null)
240                .setCancelable(false)
241                .show();
242        alert.getWindow().setType(WindowManager.LayoutParams.TYPE_PRIORITY_PHONE);
243    }
244
245    private static void log(String msg) {
246        Log.d(TAG, "[SpecialCharSequenceMgr] " + msg);
247    }
248}
249