Home | History | Annotate | Download | only in contacts
      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.contacts;
     18 
     19 import com.android.internal.telephony.ITelephony;
     20 
     21 import android.app.AlertDialog;
     22 import android.app.KeyguardManager;
     23 import android.app.ProgressDialog;
     24 import android.content.AsyncQueryHandler;
     25 import android.content.ContentResolver;
     26 import android.content.Context;
     27 import android.content.DialogInterface;
     28 import android.content.Intent;
     29 import android.database.Cursor;
     30 import android.net.Uri;
     31 import android.os.RemoteException;
     32 import android.os.ServiceManager;
     33 import android.provider.Telephony.Intents;
     34 import android.telephony.PhoneNumberUtils;
     35 import android.telephony.TelephonyManager;
     36 import android.util.Log;
     37 import android.view.WindowManager;
     38 import android.widget.EditText;
     39 import 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  */
     53 public 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