blob: b5deab8218ed54dadd2af0623168fe5ae7e66a27 [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;
31import android.os.RemoteException;
32import android.os.ServiceManager;
33import android.provider.Telephony.Intents;
34import android.telephony.PhoneNumberUtils;
35import android.telephony.TelephonyManager;
36import android.util.Log;
37import android.view.WindowManager;
38import android.widget.EditText;
39import android.widget.Toast;
40
41/**
42 * Helper class to listen for some magic character sequences
43 * that are handled specially by the dialer.
David Browna307b772010-03-11 15:32:52 -080044 *
David Brown5c386e82011-09-09 13:59:26 -070045 * Note the Phone app also handles these sequences too (in a couple of
46 * relativly obscure places in the UI), so there's a separate version of
47 * this class under apps/Phone.
48 *
David Browna307b772010-03-11 15:32:52 -080049 * TODO: there's lots of duplicated code between this class and the
50 * corresponding class under apps/Phone. Let's figure out a way to
51 * unify these two classes (in the framework? in a common shared library?)
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080052 */
53public class SpecialCharSequenceMgr {
54 private static final String TAG = "SpecialCharSequenceMgr";
55 private static final String MMI_IMEI_DISPLAY = "*#06#";
56
57 /** This class is never instantiated. */
58 private SpecialCharSequenceMgr() {
59 }
60
Daniel Lehmann09fba0a2011-05-05 18:25:17 -070061 public static boolean handleChars(Context context, String input, EditText textField) {
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080062 return handleChars(context, input, false, textField);
63 }
64
65 static boolean handleChars(Context context, String input) {
66 return handleChars(context, input, false, null);
67 }
68
69 static boolean handleChars(Context context, String input, boolean useSystemWindow,
70 EditText textField) {
71
72 //get rid of the separators so that the string gets parsed correctly
73 String dialString = PhoneNumberUtils.stripSeparators(input);
74
75 if (handleIMEIDisplay(context, dialString, useSystemWindow)
76 || handlePinEntry(context, dialString)
77 || handleAdnEntry(context, dialString, textField)
78 || handleSecretCode(context, dialString)) {
79 return true;
80 }
81
82 return false;
83 }
84
85 /**
86 * Handles secret codes to launch arbitrary activities in the form of *#*#<code>#*#*.
87 * If a secret code is encountered an Intent is started with the android_secret_code://<code>
88 * URI.
89 *
90 * @param context the context to use
91 * @param input the text to check for a secret code in
92 * @return true if a secret code was encountered
93 */
94 static boolean handleSecretCode(Context context, String input) {
95 // Secret codes are in the form *#*#<code>#*#*
96 int len = input.length();
97 if (len > 8 && input.startsWith("*#*#") && input.endsWith("#*#*")) {
98 Intent intent = new Intent(Intents.SECRET_CODE_ACTION,
99 Uri.parse("android_secret_code://" + input.substring(4, len - 4)));
100 context.sendBroadcast(intent);
101 return true;
102 }
103
104 return false;
105 }
106
107 /**
108 * Handle ADN requests by filling in the SIM contact number into the requested
109 * EditText.
110 *
111 * This code works alongside the Asynchronous query handler {@link QueryHandler}
112 * and query cancel handler implemented in {@link SimContactQueryCookie}.
113 */
114 static boolean handleAdnEntry(Context context, String input, EditText textField) {
115 /* ADN entries are of the form "N(N)(N)#" */
116
117 // if the phone is keyguard-restricted, then just ignore this
118 // input. We want to make sure that sim card contacts are NOT
119 // exposed unless the phone is unlocked, and this code can be
120 // accessed from the emergency dialer.
121 KeyguardManager keyguardManager =
122 (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
123 if (keyguardManager.inKeyguardRestrictedInputMode()) {
124 return false;
125 }
126
127 int len = input.length();
128 if ((len > 1) && (len < 5) && (input.endsWith("#"))) {
129 try {
130 // get the ordinal number of the sim contact
131 int index = Integer.parseInt(input.substring(0, len-1));
132
133 // The original code that navigated to a SIM Contacts list view did not
134 // highlight the requested contact correctly, a requirement for PTCRB
135 // certification. This behaviour is consistent with the UI paradigm
136 // for touch-enabled lists, so it does not make sense to try to work
137 // around it. Instead we fill in the the requested phone number into
138 // the dialer text field.
139
140 // create the async query handler
141 QueryHandler handler = new QueryHandler (context.getContentResolver());
142
143 // create the cookie object
144 SimContactQueryCookie sc = new SimContactQueryCookie(index - 1, handler,
145 ADN_QUERY_TOKEN);
146
147 // setup the cookie fields
148 sc.contactNum = index - 1;
149 sc.setTextField(textField);
150
151 // create the progress dialog
152 sc.progressDialog = new ProgressDialog(context);
153 sc.progressDialog.setTitle(R.string.simContacts_title);
154 sc.progressDialog.setMessage(context.getText(R.string.simContacts_emptyLoading));
155 sc.progressDialog.setIndeterminate(true);
156 sc.progressDialog.setCancelable(true);
157 sc.progressDialog.setOnCancelListener(sc);
158 sc.progressDialog.getWindow().addFlags(
159 WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
160
161 // display the progress dialog
162 sc.progressDialog.show();
163
164 // run the query.
Wink Saville6618ff52009-04-02 11:00:56 -0700165 handler.startQuery(ADN_QUERY_TOKEN, sc, Uri.parse("content://icc/adn"),
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800166 new String[]{ADN_PHONE_NUMBER_COLUMN_NAME}, null, null, null);
167 return true;
168 } catch (NumberFormatException ex) {
169 // Ignore
170 }
171 }
172 return false;
173 }
174
175 static boolean handlePinEntry(Context context, String input) {
176 if ((input.startsWith("**04") || input.startsWith("**05")) && input.endsWith("#")) {
177 try {
178 return ITelephony.Stub.asInterface(ServiceManager.getService("phone"))
179 .handlePinMmi(input);
180 } catch (RemoteException e) {
181 Log.e(TAG, "Failed to handlePinMmi due to remote exception");
182 return false;
183 }
184 }
185 return false;
186 }
187
188 static boolean handleIMEIDisplay(Context context, String input, boolean useSystemWindow) {
189 if (input.equals(MMI_IMEI_DISPLAY)) {
Li Zhed9a31682009-07-30 14:08:42 +0800190 int phoneType = ((TelephonyManager)context.getSystemService(
Wink Saville18d52582011-02-02 11:29:49 -0800191 Context.TELEPHONY_SERVICE)).getCurrentPhoneType();
Wink Saville6618ff52009-04-02 11:00:56 -0700192
Li Zhed9a31682009-07-30 14:08:42 +0800193 if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
Tracy Pu29a1cd82009-07-02 14:39:18 -0400194 showIMEIPanel(context, useSystemWindow);
195 return true;
Li Zhed9a31682009-07-30 14:08:42 +0800196 } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
Tracy Pu29a1cd82009-07-02 14:39:18 -0400197 showMEIDPanel(context, useSystemWindow);
Wink Saville6618ff52009-04-02 11:00:56 -0700198 return true;
199 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800200 }
201
202 return false;
203 }
204
David Brown5c386e82011-09-09 13:59:26 -0700205 // TODO: Combine showIMEIPanel() and showMEIDPanel() into a single
206 // generic "showDeviceIdPanel()" method, like in the apps/Phone
207 // version of SpecialCharSequenceMgr.java. (This will require moving
208 // the phone app's TelephonyCapabilities.getDeviceIdLabel() method
209 // into the telephony framework, though.)
210
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800211 static void showIMEIPanel(Context context, boolean useSystemWindow) {
212 String imeiStr = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE))
213 .getDeviceId();
214
215 AlertDialog alert = new AlertDialog.Builder(context)
216 .setTitle(R.string.imei)
217 .setMessage(imeiStr)
218 .setPositiveButton(android.R.string.ok, null)
219 .setCancelable(false)
220 .show();
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800221 }
222
Tracy Pu29a1cd82009-07-02 14:39:18 -0400223 static void showMEIDPanel(Context context, boolean useSystemWindow) {
224 String meidStr = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE))
225 .getDeviceId();
226
227 AlertDialog alert = new AlertDialog.Builder(context)
228 .setTitle(R.string.meid)
229 .setMessage(meidStr)
230 .setPositiveButton(android.R.string.ok, null)
231 .setCancelable(false)
232 .show();
Tracy Pu29a1cd82009-07-02 14:39:18 -0400233 }
234
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800235 /*******
236 * This code is used to handle SIM Contact queries
237 *******/
238 private static final String ADN_PHONE_NUMBER_COLUMN_NAME = "number";
239 private static final String ADN_NAME_COLUMN_NAME = "name";
240 private static final int ADN_QUERY_TOKEN = -1;
241
242 /**
243 * Cookie object that contains everything we need to communicate to the
244 * handler's onQuery Complete, as well as what we need in order to cancel
245 * the query (if requested).
246 *
247 * Note, access to the textField field is going to be synchronized, because
248 * the user can request a cancel at any time through the UI.
249 */
250 private static class SimContactQueryCookie implements DialogInterface.OnCancelListener{
251 public ProgressDialog progressDialog;
252 public int contactNum;
253
254 // Used to identify the query request.
255 private int mToken;
256 private QueryHandler mHandler;
257
258 // The text field we're going to update
259 private EditText textField;
260
261 public SimContactQueryCookie(int number, QueryHandler handler, int token) {
262 contactNum = number;
263 mHandler = handler;
264 mToken = token;
265 }
266
267 /**
268 * Synchronized getter for the EditText.
269 */
270 public synchronized EditText getTextField() {
271 return textField;
272 }
273
274 /**
275 * Synchronized setter for the EditText.
276 */
277 public synchronized void setTextField(EditText text) {
278 textField = text;
279 }
280
281 /**
282 * Cancel the ADN query by stopping the operation and signaling
283 * the cookie that a cancel request is made.
284 */
285 public synchronized void onCancel(DialogInterface dialog) {
286 // close the progress dialog
287 if (progressDialog != null) {
288 progressDialog.dismiss();
289 }
290
291 // setting the textfield to null ensures that the UI does NOT get
292 // updated.
293 textField = null;
294
295 // Cancel the operation if possible.
296 mHandler.cancelOperation(mToken);
297 }
298 }
299
300 /**
301 * Asynchronous query handler that services requests to look up ADNs
302 *
303 * Queries originate from {@link handleAdnEntry}.
304 */
305 private static class QueryHandler extends AsyncQueryHandler {
306
307 public QueryHandler(ContentResolver cr) {
308 super(cr);
309 }
310
311 /**
312 * Override basic onQueryComplete to fill in the textfield when
313 * we're handed the ADN cursor.
314 */
315 @Override
316 protected void onQueryComplete(int token, Object cookie, Cursor c) {
317 SimContactQueryCookie sc = (SimContactQueryCookie) cookie;
318
319 // close the progress dialog.
320 sc.progressDialog.dismiss();
321
322 // get the EditText to update or see if the request was cancelled.
323 EditText text = sc.getTextField();
324
325 // if the textview is valid, and the cursor is valid and postionable
326 // on the Nth number, then we update the text field and display a
327 // toast indicating the caller name.
328 if ((c != null) && (text != null) && (c.moveToPosition(sc.contactNum))) {
329 String name = c.getString(c.getColumnIndexOrThrow(ADN_NAME_COLUMN_NAME));
330 String number = c.getString(c.getColumnIndexOrThrow(ADN_PHONE_NUMBER_COLUMN_NAME));
331
332 // fill the text in.
333 text.getText().replace(0, 0, number);
334
335 // display the name as a toast
336 Context context = sc.progressDialog.getContext();
337 name = context.getString(R.string.menu_callNumber, name);
338 Toast.makeText(context, name, Toast.LENGTH_SHORT)
339 .show();
340 }
341 }
342 }
343}