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