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