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