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