blob: a889c6d4802b8b4eb0496ad14fb8cec7895e9616 [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 Miyakawac79d01c2012-04-11 09:17:39 -0700153 TelephonyManager telephonyManager =
154 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
155 if (telephonyManager == null
156 || !TelephonyCapabilities.supportsAdn(telephonyManager.getCurrentPhoneType())) {
Daisuke Miyakawae382df02012-03-28 20:39:05 -0700157 return false;
158 }
159
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800160 // if the phone is keyguard-restricted, then just ignore this
161 // input. We want to make sure that sim card contacts are NOT
162 // exposed unless the phone is unlocked, and this code can be
163 // accessed from the emergency dialer.
164 KeyguardManager keyguardManager =
165 (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
166 if (keyguardManager.inKeyguardRestrictedInputMode()) {
167 return false;
168 }
169
170 int len = input.length();
171 if ((len > 1) && (len < 5) && (input.endsWith("#"))) {
172 try {
173 // get the ordinal number of the sim contact
174 int index = Integer.parseInt(input.substring(0, len-1));
175
176 // The original code that navigated to a SIM Contacts list view did not
177 // highlight the requested contact correctly, a requirement for PTCRB
178 // certification. This behaviour is consistent with the UI paradigm
179 // for touch-enabled lists, so it does not make sense to try to work
180 // around it. Instead we fill in the the requested phone number into
181 // the dialer text field.
182
183 // create the async query handler
184 QueryHandler handler = new QueryHandler (context.getContentResolver());
185
186 // create the cookie object
187 SimContactQueryCookie sc = new SimContactQueryCookie(index - 1, handler,
188 ADN_QUERY_TOKEN);
189
190 // setup the cookie fields
191 sc.contactNum = index - 1;
192 sc.setTextField(textField);
193
194 // create the progress dialog
195 sc.progressDialog = new ProgressDialog(context);
196 sc.progressDialog.setTitle(R.string.simContacts_title);
197 sc.progressDialog.setMessage(context.getText(R.string.simContacts_emptyLoading));
198 sc.progressDialog.setIndeterminate(true);
199 sc.progressDialog.setCancelable(true);
200 sc.progressDialog.setOnCancelListener(sc);
201 sc.progressDialog.getWindow().addFlags(
202 WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
203
204 // display the progress dialog
205 sc.progressDialog.show();
206
207 // run the query.
Wink Saville6618ff52009-04-02 11:00:56 -0700208 handler.startQuery(ADN_QUERY_TOKEN, sc, Uri.parse("content://icc/adn"),
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800209 new String[]{ADN_PHONE_NUMBER_COLUMN_NAME}, null, null, null);
Daisuke Miyakawa353934b2012-03-08 09:40:05 -0800210
211 if (sPreviousAdnQueryHandler != null) {
212 // It is harmless to call cancel() even after the handler's gone.
213 sPreviousAdnQueryHandler.cancel();
214 }
215 sPreviousAdnQueryHandler = handler;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800216 return true;
217 } catch (NumberFormatException ex) {
218 // Ignore
219 }
220 }
221 return false;
222 }
223
224 static boolean handlePinEntry(Context context, String input) {
225 if ((input.startsWith("**04") || input.startsWith("**05")) && input.endsWith("#")) {
226 try {
227 return ITelephony.Stub.asInterface(ServiceManager.getService("phone"))
228 .handlePinMmi(input);
229 } catch (RemoteException e) {
230 Log.e(TAG, "Failed to handlePinMmi due to remote exception");
231 return false;
232 }
233 }
234 return false;
235 }
236
237 static boolean handleIMEIDisplay(Context context, String input, boolean useSystemWindow) {
Daisuke Miyakawac79d01c2012-04-11 09:17:39 -0700238 TelephonyManager telephonyManager =
239 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
240 if (telephonyManager != null && input.equals(MMI_IMEI_DISPLAY)) {
241 int phoneType = telephonyManager.getCurrentPhoneType();
Li Zhed9a31682009-07-30 14:08:42 +0800242 if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
Daisuke Miyakawac79d01c2012-04-11 09:17:39 -0700243 showIMEIPanel(context, useSystemWindow, telephonyManager);
Tracy Pu29a1cd82009-07-02 14:39:18 -0400244 return true;
Li Zhed9a31682009-07-30 14:08:42 +0800245 } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
Daisuke Miyakawac79d01c2012-04-11 09:17:39 -0700246 showMEIDPanel(context, useSystemWindow, telephonyManager);
Wink Saville6618ff52009-04-02 11:00:56 -0700247 return true;
248 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800249 }
250
251 return false;
252 }
253
David Brown5c386e82011-09-09 13:59:26 -0700254 // TODO: Combine showIMEIPanel() and showMEIDPanel() into a single
255 // generic "showDeviceIdPanel()" method, like in the apps/Phone
256 // version of SpecialCharSequenceMgr.java. (This will require moving
257 // the phone app's TelephonyCapabilities.getDeviceIdLabel() method
258 // into the telephony framework, though.)
259
Daisuke Miyakawac79d01c2012-04-11 09:17:39 -0700260 private static void showIMEIPanel(Context context, boolean useSystemWindow,
261 TelephonyManager telephonyManager) {
262 String imeiStr = telephonyManager.getDeviceId();
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800263
264 AlertDialog alert = new AlertDialog.Builder(context)
265 .setTitle(R.string.imei)
266 .setMessage(imeiStr)
267 .setPositiveButton(android.R.string.ok, null)
268 .setCancelable(false)
269 .show();
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800270 }
271
Daisuke Miyakawac79d01c2012-04-11 09:17:39 -0700272 private static void showMEIDPanel(Context context, boolean useSystemWindow,
273 TelephonyManager telephonyManager) {
274 String meidStr = telephonyManager.getDeviceId();
Tracy Pu29a1cd82009-07-02 14:39:18 -0400275
276 AlertDialog alert = new AlertDialog.Builder(context)
277 .setTitle(R.string.meid)
278 .setMessage(meidStr)
279 .setPositiveButton(android.R.string.ok, null)
280 .setCancelable(false)
281 .show();
Tracy Pu29a1cd82009-07-02 14:39:18 -0400282 }
283
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800284 /*******
285 * This code is used to handle SIM Contact queries
286 *******/
287 private static final String ADN_PHONE_NUMBER_COLUMN_NAME = "number";
288 private static final String ADN_NAME_COLUMN_NAME = "name";
289 private static final int ADN_QUERY_TOKEN = -1;
290
291 /**
292 * Cookie object that contains everything we need to communicate to the
293 * handler's onQuery Complete, as well as what we need in order to cancel
294 * the query (if requested).
295 *
296 * Note, access to the textField field is going to be synchronized, because
297 * the user can request a cancel at any time through the UI.
298 */
299 private static class SimContactQueryCookie implements DialogInterface.OnCancelListener{
300 public ProgressDialog progressDialog;
301 public int contactNum;
302
303 // Used to identify the query request.
304 private int mToken;
305 private QueryHandler mHandler;
306
307 // The text field we're going to update
308 private EditText textField;
309
310 public SimContactQueryCookie(int number, QueryHandler handler, int token) {
311 contactNum = number;
312 mHandler = handler;
313 mToken = token;
314 }
315
316 /**
317 * Synchronized getter for the EditText.
318 */
319 public synchronized EditText getTextField() {
320 return textField;
321 }
322
323 /**
324 * Synchronized setter for the EditText.
325 */
326 public synchronized void setTextField(EditText text) {
327 textField = text;
328 }
329
330 /**
331 * Cancel the ADN query by stopping the operation and signaling
332 * the cookie that a cancel request is made.
333 */
334 public synchronized void onCancel(DialogInterface dialog) {
335 // close the progress dialog
336 if (progressDialog != null) {
337 progressDialog.dismiss();
338 }
339
340 // setting the textfield to null ensures that the UI does NOT get
341 // updated.
342 textField = null;
343
344 // Cancel the operation if possible.
345 mHandler.cancelOperation(mToken);
346 }
347 }
348
349 /**
350 * Asynchronous query handler that services requests to look up ADNs
351 *
352 * Queries originate from {@link handleAdnEntry}.
353 */
354 private static class QueryHandler extends AsyncQueryHandler {
355
Daisuke Miyakawa353934b2012-03-08 09:40:05 -0800356 private boolean mCanceled;
357
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800358 public QueryHandler(ContentResolver cr) {
359 super(cr);
360 }
361
362 /**
363 * Override basic onQueryComplete to fill in the textfield when
364 * we're handed the ADN cursor.
365 */
366 @Override
367 protected void onQueryComplete(int token, Object cookie, Cursor c) {
Daisuke Miyakawa353934b2012-03-08 09:40:05 -0800368 sPreviousAdnQueryHandler = null;
369 if (mCanceled) {
370 return;
371 }
372
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800373 SimContactQueryCookie sc = (SimContactQueryCookie) cookie;
374
375 // close the progress dialog.
376 sc.progressDialog.dismiss();
377
378 // get the EditText to update or see if the request was cancelled.
379 EditText text = sc.getTextField();
380
381 // if the textview is valid, and the cursor is valid and postionable
382 // on the Nth number, then we update the text field and display a
383 // toast indicating the caller name.
384 if ((c != null) && (text != null) && (c.moveToPosition(sc.contactNum))) {
385 String name = c.getString(c.getColumnIndexOrThrow(ADN_NAME_COLUMN_NAME));
386 String number = c.getString(c.getColumnIndexOrThrow(ADN_PHONE_NUMBER_COLUMN_NAME));
387
388 // fill the text in.
389 text.getText().replace(0, 0, number);
390
391 // display the name as a toast
392 Context context = sc.progressDialog.getContext();
393 name = context.getString(R.string.menu_callNumber, name);
394 Toast.makeText(context, name, Toast.LENGTH_SHORT)
395 .show();
396 }
397 }
Daisuke Miyakawa353934b2012-03-08 09:40:05 -0800398
399 public void cancel() {
400 mCanceled = true;
401 // Ask AsyncQueryHandler to cancel the whole request. This will fails when the
402 // query already started.
403 cancelOperation(ADN_QUERY_TOKEN);
404 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800405 }
406}