blob: 5b88c8daae97abf129fe5c2d919a151c8013b19a [file] [log] [blame]
Chiao Cheng91197042012-08-24 14:19:37 -07001/*
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
17package com.android.dialer;
18
19import android.app.AlertDialog;
20import android.app.KeyguardManager;
21import android.app.ProgressDialog;
22import android.content.AsyncQueryHandler;
23import android.content.ContentResolver;
24import android.content.Context;
25import android.content.DialogInterface;
26import android.content.Intent;
27import android.database.Cursor;
28import android.net.Uri;
29import android.os.Looper;
30import android.os.RemoteException;
31import android.os.ServiceManager;
32import android.telephony.PhoneNumberUtils;
33import android.telephony.TelephonyManager;
34import android.util.Log;
35import android.view.WindowManager;
36import android.widget.EditText;
37import android.widget.Toast;
38
39import com.android.contacts.R;
40import com.android.internal.telephony.ITelephony;
41import com.android.internal.telephony.TelephonyCapabilities;
42import com.android.internal.telephony.TelephonyIntents;
43
44/**
45 * Helper class to listen for some magic character sequences
46 * that are handled specially by the dialer.
47 *
48 * Note the Phone app also handles these sequences too (in a couple of
49 * relativly obscure places in the UI), so there's a separate version of
50 * this class under apps/Phone.
51 *
52 * TODO: there's lots of duplicated code between this class and the
53 * corresponding class under apps/Phone. Let's figure out a way to
54 * unify these two classes (in the framework? in a common shared library?)
55 */
56public class SpecialCharSequenceMgr {
57 private static final String TAG = "SpecialCharSequenceMgr";
58 private static final String MMI_IMEI_DISPLAY = "*#06#";
59
60 /**
61 * Remembers the previous {@link QueryHandler} and cancel the operation when needed, to
62 * prevent possible crash.
63 *
64 * QueryHandler may call {@link ProgressDialog#dismiss()} when the screen is already gone,
65 * which will cause the app crash. This variable enables the class to prevent the crash
66 * on {@link #cleanup()}.
67 *
68 * TODO: Remove this and replace it (and {@link #cleanup()}) with better implementation.
69 * One complication is that we have SpecialCharSequencMgr in Phone package too, which has
70 * *slightly* different implementation. Note that Phone package doesn't have this problem,
71 * so the class on Phone side doesn't have this functionality.
72 * Fundamental fix would be to have one shared implementation and resolve this corner case more
73 * gracefully.
74 */
75 private static QueryHandler sPreviousAdnQueryHandler;
76
77 /** This class is never instantiated. */
78 private SpecialCharSequenceMgr() {
79 }
80
81 public static boolean handleChars(Context context, String input, EditText textField) {
82 return handleChars(context, input, false, textField);
83 }
84
85 static boolean handleChars(Context context, String input) {
86 return handleChars(context, input, false, null);
87 }
88
89 static boolean handleChars(Context context, String input, boolean useSystemWindow,
90 EditText textField) {
91
92 //get rid of the separators so that the string gets parsed correctly
93 String dialString = PhoneNumberUtils.stripSeparators(input);
94
95 if (handleIMEIDisplay(context, dialString, useSystemWindow)
96 || handlePinEntry(context, dialString)
97 || handleAdnEntry(context, dialString, textField)
98 || handleSecretCode(context, dialString)) {
99 return true;
100 }
101
102 return false;
103 }
104
105 /**
106 * Cleanup everything around this class. Must be run inside the main thread.
107 *
108 * This should be called when the screen becomes background.
109 */
110 public static void cleanup() {
111 if (Looper.myLooper() != Looper.getMainLooper()) {
112 Log.wtf(TAG, "cleanup() is called outside the main thread");
113 return;
114 }
115
116 if (sPreviousAdnQueryHandler != null) {
117 sPreviousAdnQueryHandler.cancel();
118 sPreviousAdnQueryHandler = null;
119 }
120 }
121
122 /**
123 * Handles secret codes to launch arbitrary activities in the form of *#*#<code>#*#*.
124 * If a secret code is encountered an Intent is started with the android_secret_code://<code>
125 * URI.
126 *
127 * @param context the context to use
128 * @param input the text to check for a secret code in
129 * @return true if a secret code was encountered
130 */
131 static boolean handleSecretCode(Context context, String input) {
132 // Secret codes are in the form *#*#<code>#*#*
133 int len = input.length();
134 if (len > 8 && input.startsWith("*#*#") && input.endsWith("#*#*")) {
135 Intent intent = new Intent(TelephonyIntents.SECRET_CODE_ACTION,
136 Uri.parse("android_secret_code://" + input.substring(4, len - 4)));
137 context.sendBroadcast(intent);
138 return true;
139 }
140
141 return false;
142 }
143
144 /**
145 * Handle ADN requests by filling in the SIM contact number into the requested
146 * EditText.
147 *
148 * This code works alongside the Asynchronous query handler {@link QueryHandler}
149 * and query cancel handler implemented in {@link SimContactQueryCookie}.
150 */
151 static boolean handleAdnEntry(Context context, String input, EditText textField) {
152 /* ADN entries are of the form "N(N)(N)#" */
153
154 TelephonyManager telephonyManager =
155 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
156 if (telephonyManager == null
157 || !TelephonyCapabilities.supportsAdn(telephonyManager.getCurrentPhoneType())) {
158 return false;
159 }
160
161 // if the phone is keyguard-restricted, then just ignore this
162 // input. We want to make sure that sim card contacts are NOT
163 // exposed unless the phone is unlocked, and this code can be
164 // accessed from the emergency dialer.
165 KeyguardManager keyguardManager =
166 (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
167 if (keyguardManager.inKeyguardRestrictedInputMode()) {
168 return false;
169 }
170
171 int len = input.length();
172 if ((len > 1) && (len < 5) && (input.endsWith("#"))) {
173 try {
174 // get the ordinal number of the sim contact
175 int index = Integer.parseInt(input.substring(0, len-1));
176
177 // The original code that navigated to a SIM Contacts list view did not
178 // highlight the requested contact correctly, a requirement for PTCRB
179 // certification. This behaviour is consistent with the UI paradigm
180 // for touch-enabled lists, so it does not make sense to try to work
181 // around it. Instead we fill in the the requested phone number into
182 // the dialer text field.
183
184 // create the async query handler
185 QueryHandler handler = new QueryHandler (context.getContentResolver());
186
187 // create the cookie object
188 SimContactQueryCookie sc = new SimContactQueryCookie(index - 1, handler,
189 ADN_QUERY_TOKEN);
190
191 // setup the cookie fields
192 sc.contactNum = index - 1;
193 sc.setTextField(textField);
194
195 // create the progress dialog
196 sc.progressDialog = new ProgressDialog(context);
197 sc.progressDialog.setTitle(R.string.simContacts_title);
198 sc.progressDialog.setMessage(context.getText(R.string.simContacts_emptyLoading));
199 sc.progressDialog.setIndeterminate(true);
200 sc.progressDialog.setCancelable(true);
201 sc.progressDialog.setOnCancelListener(sc);
202 sc.progressDialog.getWindow().addFlags(
203 WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
204
205 // display the progress dialog
206 sc.progressDialog.show();
207
208 // run the query.
209 handler.startQuery(ADN_QUERY_TOKEN, sc, Uri.parse("content://icc/adn"),
210 new String[]{ADN_PHONE_NUMBER_COLUMN_NAME}, null, null, null);
211
212 if (sPreviousAdnQueryHandler != null) {
213 // It is harmless to call cancel() even after the handler's gone.
214 sPreviousAdnQueryHandler.cancel();
215 }
216 sPreviousAdnQueryHandler = handler;
217 return true;
218 } catch (NumberFormatException ex) {
219 // Ignore
220 }
221 }
222 return false;
223 }
224
225 static boolean handlePinEntry(Context context, String input) {
226 if ((input.startsWith("**04") || input.startsWith("**05")) && input.endsWith("#")) {
227 try {
228 return ITelephony.Stub.asInterface(ServiceManager.getService("phone"))
229 .handlePinMmi(input);
230 } catch (RemoteException e) {
231 Log.e(TAG, "Failed to handlePinMmi due to remote exception");
232 return false;
233 }
234 }
235 return false;
236 }
237
238 static boolean handleIMEIDisplay(Context context, String input, boolean useSystemWindow) {
239 TelephonyManager telephonyManager =
240 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
241 if (telephonyManager != null && input.equals(MMI_IMEI_DISPLAY)) {
242 int phoneType = telephonyManager.getCurrentPhoneType();
243 if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
244 showIMEIPanel(context, useSystemWindow, telephonyManager);
245 return true;
246 } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
247 showMEIDPanel(context, useSystemWindow, telephonyManager);
248 return true;
249 }
250 }
251
252 return false;
253 }
254
255 // TODO: Combine showIMEIPanel() and showMEIDPanel() into a single
256 // generic "showDeviceIdPanel()" method, like in the apps/Phone
257 // version of SpecialCharSequenceMgr.java. (This will require moving
258 // the phone app's TelephonyCapabilities.getDeviceIdLabel() method
259 // into the telephony framework, though.)
260
261 private static void showIMEIPanel(Context context, boolean useSystemWindow,
262 TelephonyManager telephonyManager) {
263 String imeiStr = telephonyManager.getDeviceId();
264
265 AlertDialog alert = new AlertDialog.Builder(context)
266 .setTitle(R.string.imei)
267 .setMessage(imeiStr)
268 .setPositiveButton(android.R.string.ok, null)
269 .setCancelable(false)
270 .show();
271 }
272
273 private static void showMEIDPanel(Context context, boolean useSystemWindow,
274 TelephonyManager telephonyManager) {
275 String meidStr = telephonyManager.getDeviceId();
276
277 AlertDialog alert = new AlertDialog.Builder(context)
278 .setTitle(R.string.meid)
279 .setMessage(meidStr)
280 .setPositiveButton(android.R.string.ok, null)
281 .setCancelable(false)
282 .show();
283 }
284
285 /*******
286 * This code is used to handle SIM Contact queries
287 *******/
288 private static final String ADN_PHONE_NUMBER_COLUMN_NAME = "number";
289 private static final String ADN_NAME_COLUMN_NAME = "name";
290 private static final int ADN_QUERY_TOKEN = -1;
291
292 /**
293 * Cookie object that contains everything we need to communicate to the
294 * handler's onQuery Complete, as well as what we need in order to cancel
295 * the query (if requested).
296 *
297 * Note, access to the textField field is going to be synchronized, because
298 * the user can request a cancel at any time through the UI.
299 */
300 private static class SimContactQueryCookie implements DialogInterface.OnCancelListener{
301 public ProgressDialog progressDialog;
302 public int contactNum;
303
304 // Used to identify the query request.
305 private int mToken;
306 private QueryHandler mHandler;
307
308 // The text field we're going to update
309 private EditText textField;
310
311 public SimContactQueryCookie(int number, QueryHandler handler, int token) {
312 contactNum = number;
313 mHandler = handler;
314 mToken = token;
315 }
316
317 /**
318 * Synchronized getter for the EditText.
319 */
320 public synchronized EditText getTextField() {
321 return textField;
322 }
323
324 /**
325 * Synchronized setter for the EditText.
326 */
327 public synchronized void setTextField(EditText text) {
328 textField = text;
329 }
330
331 /**
332 * Cancel the ADN query by stopping the operation and signaling
333 * the cookie that a cancel request is made.
334 */
335 public synchronized void onCancel(DialogInterface dialog) {
336 // close the progress dialog
337 if (progressDialog != null) {
338 progressDialog.dismiss();
339 }
340
341 // setting the textfield to null ensures that the UI does NOT get
342 // updated.
343 textField = null;
344
345 // Cancel the operation if possible.
346 mHandler.cancelOperation(mToken);
347 }
348 }
349
350 /**
351 * Asynchronous query handler that services requests to look up ADNs
352 *
353 * Queries originate from {@link handleAdnEntry}.
354 */
355 private static class QueryHandler extends AsyncQueryHandler {
356
357 private boolean mCanceled;
358
359 public QueryHandler(ContentResolver cr) {
360 super(cr);
361 }
362
363 /**
364 * Override basic onQueryComplete to fill in the textfield when
365 * we're handed the ADN cursor.
366 */
367 @Override
368 protected void onQueryComplete(int token, Object cookie, Cursor c) {
369 sPreviousAdnQueryHandler = null;
370 if (mCanceled) {
371 return;
372 }
373
374 SimContactQueryCookie sc = (SimContactQueryCookie) cookie;
375
376 // close the progress dialog.
377 sc.progressDialog.dismiss();
378
379 // get the EditText to update or see if the request was cancelled.
380 EditText text = sc.getTextField();
381
382 // if the textview is valid, and the cursor is valid and postionable
383 // on the Nth number, then we update the text field and display a
384 // toast indicating the caller name.
385 if ((c != null) && (text != null) && (c.moveToPosition(sc.contactNum))) {
386 String name = c.getString(c.getColumnIndexOrThrow(ADN_NAME_COLUMN_NAME));
387 String number = c.getString(c.getColumnIndexOrThrow(ADN_PHONE_NUMBER_COLUMN_NAME));
388
389 // fill the text in.
390 text.getText().replace(0, 0, number);
391
392 // display the name as a toast
393 Context context = sc.progressDialog.getContext();
394 name = context.getString(R.string.menu_callNumber, name);
395 Toast.makeText(context, name, Toast.LENGTH_SHORT)
396 .show();
397 }
398 }
399
400 public void cancel() {
401 mCanceled = true;
402 // Ask AsyncQueryHandler to cancel the whole request. This will fails when the
403 // query already started.
404 cancelOperation(ADN_QUERY_TOKEN);
405 }
406 }
407}