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