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