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