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.contacts;
18
19import com.android.internal.telephony.ITelephony;
20
21import android.app.AlertDialog;
22import android.app.KeyguardManager;
23import android.app.ProgressDialog;
24import android.content.AsyncQueryHandler;
25import android.content.ContentResolver;
26import android.content.Context;
27import android.content.DialogInterface;
28import android.content.Intent;
29import android.database.Cursor;
30import android.net.Uri;
31import android.os.RemoteException;
32import android.os.ServiceManager;
33import android.provider.Telephony.Intents;
34import android.telephony.PhoneNumberUtils;
35import android.telephony.TelephonyManager;
36import android.util.Log;
37import android.view.WindowManager;
38import android.widget.EditText;
39import android.widget.Toast;
40
41/**
42 * Helper class to listen for some magic character sequences
43 * that are handled specially by the dialer.
44 *
45 * Note the Phone app also handles these sequences too (in a couple of
46 * relativly obscure places in the UI), so there's a separate version of
47 * this class under apps/Phone.
48 *
49 * TODO: there's lots of duplicated code between this class and the
50 * corresponding class under apps/Phone.  Let's figure out a way to
51 * unify these two classes (in the framework? in a common shared library?)
52 */
53public class SpecialCharSequenceMgr {
54    private static final String TAG = "SpecialCharSequenceMgr";
55    private static final String MMI_IMEI_DISPLAY = "*#06#";
56
57    /** This class is never instantiated. */
58    private SpecialCharSequenceMgr() {
59    }
60
61    public static boolean handleChars(Context context, String input, EditText textField) {
62        return handleChars(context, input, false, textField);
63    }
64
65    static boolean handleChars(Context context, String input) {
66        return handleChars(context, input, false, null);
67    }
68
69    static boolean handleChars(Context context, String input, boolean useSystemWindow,
70            EditText textField) {
71
72        //get rid of the separators so that the string gets parsed correctly
73        String dialString = PhoneNumberUtils.stripSeparators(input);
74
75        if (handleIMEIDisplay(context, dialString, useSystemWindow)
76                || handlePinEntry(context, dialString)
77                || handleAdnEntry(context, dialString, textField)
78                || handleSecretCode(context, dialString)) {
79            return true;
80        }
81
82        return false;
83    }
84
85    /**
86     * Handles secret codes to launch arbitrary activities in the form of *#*#<code>#*#*.
87     * If a secret code is encountered an Intent is started with the android_secret_code://<code>
88     * URI.
89     *
90     * @param context the context to use
91     * @param input the text to check for a secret code in
92     * @return true if a secret code was encountered
93     */
94    static boolean handleSecretCode(Context context, String input) {
95        // Secret codes are in the form *#*#<code>#*#*
96        int len = input.length();
97        if (len > 8 && input.startsWith("*#*#") && input.endsWith("#*#*")) {
98            Intent intent = new Intent(Intents.SECRET_CODE_ACTION,
99                    Uri.parse("android_secret_code://" + input.substring(4, len - 4)));
100            context.sendBroadcast(intent);
101            return true;
102        }
103
104        return false;
105    }
106
107    /**
108     * Handle ADN requests by filling in the SIM contact number into the requested
109     * EditText.
110     *
111     * This code works alongside the Asynchronous query handler {@link QueryHandler}
112     * and query cancel handler implemented in {@link SimContactQueryCookie}.
113     */
114    static boolean handleAdnEntry(Context context, String input, EditText textField) {
115        /* ADN entries are of the form "N(N)(N)#" */
116
117        // if the phone is keyguard-restricted, then just ignore this
118        // input.  We want to make sure that sim card contacts are NOT
119        // exposed unless the phone is unlocked, and this code can be
120        // accessed from the emergency dialer.
121        KeyguardManager keyguardManager =
122                (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
123        if (keyguardManager.inKeyguardRestrictedInputMode()) {
124            return false;
125        }
126
127        int len = input.length();
128        if ((len > 1) && (len < 5) && (input.endsWith("#"))) {
129            try {
130                // get the ordinal number of the sim contact
131                int index = Integer.parseInt(input.substring(0, len-1));
132
133                // The original code that navigated to a SIM Contacts list view did not
134                // highlight the requested contact correctly, a requirement for PTCRB
135                // certification.  This behaviour is consistent with the UI paradigm
136                // for touch-enabled lists, so it does not make sense to try to work
137                // around it.  Instead we fill in the the requested phone number into
138                // the dialer text field.
139
140                // create the async query handler
141                QueryHandler handler = new QueryHandler (context.getContentResolver());
142
143                // create the cookie object
144                SimContactQueryCookie sc = new SimContactQueryCookie(index - 1, handler,
145                        ADN_QUERY_TOKEN);
146
147                // setup the cookie fields
148                sc.contactNum = index - 1;
149                sc.setTextField(textField);
150
151                // create the progress dialog
152                sc.progressDialog = new ProgressDialog(context);
153                sc.progressDialog.setTitle(R.string.simContacts_title);
154                sc.progressDialog.setMessage(context.getText(R.string.simContacts_emptyLoading));
155                sc.progressDialog.setIndeterminate(true);
156                sc.progressDialog.setCancelable(true);
157                sc.progressDialog.setOnCancelListener(sc);
158                sc.progressDialog.getWindow().addFlags(
159                        WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
160
161                // display the progress dialog
162                sc.progressDialog.show();
163
164                // run the query.
165                handler.startQuery(ADN_QUERY_TOKEN, sc, Uri.parse("content://icc/adn"),
166                        new String[]{ADN_PHONE_NUMBER_COLUMN_NAME}, null, null, null);
167                return true;
168            } catch (NumberFormatException ex) {
169                // Ignore
170            }
171        }
172        return false;
173    }
174
175    static boolean handlePinEntry(Context context, String input) {
176        if ((input.startsWith("**04") || input.startsWith("**05")) && input.endsWith("#")) {
177            try {
178                return ITelephony.Stub.asInterface(ServiceManager.getService("phone"))
179                        .handlePinMmi(input);
180            } catch (RemoteException e) {
181                Log.e(TAG, "Failed to handlePinMmi due to remote exception");
182                return false;
183            }
184        }
185        return false;
186    }
187
188    static boolean handleIMEIDisplay(Context context, String input, boolean useSystemWindow) {
189        if (input.equals(MMI_IMEI_DISPLAY)) {
190            int phoneType = ((TelephonyManager)context.getSystemService(
191                    Context.TELEPHONY_SERVICE)).getCurrentPhoneType();
192
193            if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
194                showIMEIPanel(context, useSystemWindow);
195                return true;
196            } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
197                showMEIDPanel(context, useSystemWindow);
198                return true;
199            }
200        }
201
202        return false;
203    }
204
205    // TODO: Combine showIMEIPanel() and showMEIDPanel() into a single
206    // generic "showDeviceIdPanel()" method, like in the apps/Phone
207    // version of SpecialCharSequenceMgr.java.  (This will require moving
208    // the phone app's TelephonyCapabilities.getDeviceIdLabel() method
209    // into the telephony framework, though.)
210
211    static void showIMEIPanel(Context context, boolean useSystemWindow) {
212        String imeiStr = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE))
213                .getDeviceId();
214
215        AlertDialog alert = new AlertDialog.Builder(context)
216                .setTitle(R.string.imei)
217                .setMessage(imeiStr)
218                .setPositiveButton(android.R.string.ok, null)
219                .setCancelable(false)
220                .show();
221    }
222
223    static void showMEIDPanel(Context context, boolean useSystemWindow) {
224        String meidStr = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE))
225                .getDeviceId();
226
227        AlertDialog alert = new AlertDialog.Builder(context)
228                .setTitle(R.string.meid)
229                .setMessage(meidStr)
230                .setPositiveButton(android.R.string.ok, null)
231                .setCancelable(false)
232                .show();
233    }
234
235    /*******
236     * This code is used to handle SIM Contact queries
237     *******/
238    private static final String ADN_PHONE_NUMBER_COLUMN_NAME = "number";
239    private static final String ADN_NAME_COLUMN_NAME = "name";
240    private static final int ADN_QUERY_TOKEN = -1;
241
242    /**
243     * Cookie object that contains everything we need to communicate to the
244     * handler's onQuery Complete, as well as what we need in order to cancel
245     * the query (if requested).
246     *
247     * Note, access to the textField field is going to be synchronized, because
248     * the user can request a cancel at any time through the UI.
249     */
250    private static class SimContactQueryCookie implements DialogInterface.OnCancelListener{
251        public ProgressDialog progressDialog;
252        public int contactNum;
253
254        // Used to identify the query request.
255        private int mToken;
256        private QueryHandler mHandler;
257
258        // The text field we're going to update
259        private EditText textField;
260
261        public SimContactQueryCookie(int number, QueryHandler handler, int token) {
262            contactNum = number;
263            mHandler = handler;
264            mToken = token;
265        }
266
267        /**
268         * Synchronized getter for the EditText.
269         */
270        public synchronized EditText getTextField() {
271            return textField;
272        }
273
274        /**
275         * Synchronized setter for the EditText.
276         */
277        public synchronized void setTextField(EditText text) {
278            textField = text;
279        }
280
281        /**
282         * Cancel the ADN query by stopping the operation and signaling
283         * the cookie that a cancel request is made.
284         */
285        public synchronized void onCancel(DialogInterface dialog) {
286            // close the progress dialog
287            if (progressDialog != null) {
288                progressDialog.dismiss();
289            }
290
291            // setting the textfield to null ensures that the UI does NOT get
292            // updated.
293            textField = null;
294
295            // Cancel the operation if possible.
296            mHandler.cancelOperation(mToken);
297        }
298    }
299
300    /**
301     * Asynchronous query handler that services requests to look up ADNs
302     *
303     * Queries originate from {@link handleAdnEntry}.
304     */
305    private static class QueryHandler extends AsyncQueryHandler {
306
307        public QueryHandler(ContentResolver cr) {
308            super(cr);
309        }
310
311        /**
312         * Override basic onQueryComplete to fill in the textfield when
313         * we're handed the ADN cursor.
314         */
315        @Override
316        protected void onQueryComplete(int token, Object cookie, Cursor c) {
317            SimContactQueryCookie sc = (SimContactQueryCookie) cookie;
318
319            // close the progress dialog.
320            sc.progressDialog.dismiss();
321
322            // get the EditText to update or see if the request was cancelled.
323            EditText text = sc.getTextField();
324
325            // if the textview is valid, and the cursor is valid and postionable
326            // on the Nth number, then we update the text field and display a
327            // toast indicating the caller name.
328            if ((c != null) && (text != null) && (c.moveToPosition(sc.contactNum))) {
329                String name = c.getString(c.getColumnIndexOrThrow(ADN_NAME_COLUMN_NAME));
330                String number = c.getString(c.getColumnIndexOrThrow(ADN_PHONE_NUMBER_COLUMN_NAME));
331
332                // fill the text in.
333                text.getText().replace(0, 0, number);
334
335                // display the name as a toast
336                Context context = sc.progressDialog.getContext();
337                name = context.getString(R.string.menu_callNumber, name);
338                Toast.makeText(context, name, Toast.LENGTH_SHORT)
339                    .show();
340            }
341        }
342    }
343}
344