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