diff options
author | Chiao Cheng <chiaocheng@google.com> | 2012-08-24 14:19:37 -0700 |
---|---|---|
committer | Chiao Cheng <chiaocheng@google.com> | 2012-08-30 16:06:08 -0700 |
commit | 91197049c458f07092b31501d2ed512180b13d58 (patch) | |
tree | ba868289b2bf82e504b1d8ffd09cfa2a0a765f27 /src/com/android/dialer/SpecialCharSequenceMgr.java | |
parent | 8a372c2c67c3cf6bd5a6d4c999d68094938e365c (diff) | |
download | android_packages_apps_Dialer-91197049c458f07092b31501d2ed512180b13d58.tar.gz android_packages_apps_Dialer-91197049c458f07092b31501d2ed512180b13d58.tar.bz2 android_packages_apps_Dialer-91197049c458f07092b31501d2ed512180b13d58.zip |
Moving more classes from contacts into dialer.
- These classes are only used by dialer code.
- Fixed import order.
Bug: 6993891
Change-Id: I7941a029989c4793b766fdc77a4666f9f99b750a
Diffstat (limited to 'src/com/android/dialer/SpecialCharSequenceMgr.java')
-rw-r--r-- | src/com/android/dialer/SpecialCharSequenceMgr.java | 407 |
1 files changed, 407 insertions, 0 deletions
diff --git a/src/com/android/dialer/SpecialCharSequenceMgr.java b/src/com/android/dialer/SpecialCharSequenceMgr.java new file mode 100644 index 000000000..5b88c8daa --- /dev/null +++ b/src/com/android/dialer/SpecialCharSequenceMgr.java @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dialer; + +import android.app.AlertDialog; +import android.app.KeyguardManager; +import android.app.ProgressDialog; +import android.content.AsyncQueryHandler; +import android.content.ContentResolver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Looper; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.telephony.PhoneNumberUtils; +import android.telephony.TelephonyManager; +import android.util.Log; +import android.view.WindowManager; +import android.widget.EditText; +import android.widget.Toast; + +import com.android.contacts.R; +import com.android.internal.telephony.ITelephony; +import com.android.internal.telephony.TelephonyCapabilities; +import com.android.internal.telephony.TelephonyIntents; + +/** + * Helper class to listen for some magic character sequences + * that are handled specially by the dialer. + * + * Note the Phone app also handles these sequences too (in a couple of + * relativly obscure places in the UI), so there's a separate version of + * this class under apps/Phone. + * + * TODO: there's lots of duplicated code between this class and the + * corresponding class under apps/Phone. Let's figure out a way to + * unify these two classes (in the framework? in a common shared library?) + */ +public class SpecialCharSequenceMgr { + private static final String TAG = "SpecialCharSequenceMgr"; + private static final String MMI_IMEI_DISPLAY = "*#06#"; + + /** + * Remembers the previous {@link QueryHandler} and cancel the operation when needed, to + * prevent possible crash. + * + * QueryHandler may call {@link ProgressDialog#dismiss()} when the screen is already gone, + * which will cause the app crash. This variable enables the class to prevent the crash + * on {@link #cleanup()}. + * + * TODO: Remove this and replace it (and {@link #cleanup()}) with better implementation. + * One complication is that we have SpecialCharSequencMgr in Phone package too, which has + * *slightly* different implementation. Note that Phone package doesn't have this problem, + * so the class on Phone side doesn't have this functionality. + * Fundamental fix would be to have one shared implementation and resolve this corner case more + * gracefully. + */ + private static QueryHandler sPreviousAdnQueryHandler; + + /** This class is never instantiated. */ + private SpecialCharSequenceMgr() { + } + + public static boolean handleChars(Context context, String input, EditText textField) { + return handleChars(context, input, false, textField); + } + + static boolean handleChars(Context context, String input) { + return handleChars(context, input, false, null); + } + + static boolean handleChars(Context context, String input, boolean useSystemWindow, + EditText textField) { + + //get rid of the separators so that the string gets parsed correctly + String dialString = PhoneNumberUtils.stripSeparators(input); + + if (handleIMEIDisplay(context, dialString, useSystemWindow) + || handlePinEntry(context, dialString) + || handleAdnEntry(context, dialString, textField) + || handleSecretCode(context, dialString)) { + return true; + } + + return false; + } + + /** + * Cleanup everything around this class. Must be run inside the main thread. + * + * This should be called when the screen becomes background. + */ + public static void cleanup() { + if (Looper.myLooper() != Looper.getMainLooper()) { + Log.wtf(TAG, "cleanup() is called outside the main thread"); + return; + } + + if (sPreviousAdnQueryHandler != null) { + sPreviousAdnQueryHandler.cancel(); + sPreviousAdnQueryHandler = null; + } + } + + /** + * Handles secret codes to launch arbitrary activities in the form of *#*#<code>#*#*. + * If a secret code is encountered an Intent is started with the android_secret_code://<code> + * URI. + * + * @param context the context to use + * @param input the text to check for a secret code in + * @return true if a secret code was encountered + */ + static boolean handleSecretCode(Context context, String input) { + // Secret codes are in the form *#*#<code>#*#* + int len = input.length(); + if (len > 8 && input.startsWith("*#*#") && input.endsWith("#*#*")) { + Intent intent = new Intent(TelephonyIntents.SECRET_CODE_ACTION, + Uri.parse("android_secret_code://" + input.substring(4, len - 4))); + context.sendBroadcast(intent); + return true; + } + + return false; + } + + /** + * Handle ADN requests by filling in the SIM contact number into the requested + * EditText. + * + * This code works alongside the Asynchronous query handler {@link QueryHandler} + * and query cancel handler implemented in {@link SimContactQueryCookie}. + */ + static boolean handleAdnEntry(Context context, String input, EditText textField) { + /* ADN entries are of the form "N(N)(N)#" */ + + TelephonyManager telephonyManager = + (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + if (telephonyManager == null + || !TelephonyCapabilities.supportsAdn(telephonyManager.getCurrentPhoneType())) { + return false; + } + + // if the phone is keyguard-restricted, then just ignore this + // input. We want to make sure that sim card contacts are NOT + // exposed unless the phone is unlocked, and this code can be + // accessed from the emergency dialer. + KeyguardManager keyguardManager = + (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); + if (keyguardManager.inKeyguardRestrictedInputMode()) { + return false; + } + + int len = input.length(); + if ((len > 1) && (len < 5) && (input.endsWith("#"))) { + try { + // get the ordinal number of the sim contact + int index = Integer.parseInt(input.substring(0, len-1)); + + // The original code that navigated to a SIM Contacts list view did not + // highlight the requested contact correctly, a requirement for PTCRB + // certification. This behaviour is consistent with the UI paradigm + // for touch-enabled lists, so it does not make sense to try to work + // around it. Instead we fill in the the requested phone number into + // the dialer text field. + + // create the async query handler + QueryHandler handler = new QueryHandler (context.getContentResolver()); + + // create the cookie object + SimContactQueryCookie sc = new SimContactQueryCookie(index - 1, handler, + ADN_QUERY_TOKEN); + + // setup the cookie fields + sc.contactNum = index - 1; + sc.setTextField(textField); + + // create the progress dialog + sc.progressDialog = new ProgressDialog(context); + sc.progressDialog.setTitle(R.string.simContacts_title); + sc.progressDialog.setMessage(context.getText(R.string.simContacts_emptyLoading)); + sc.progressDialog.setIndeterminate(true); + sc.progressDialog.setCancelable(true); + sc.progressDialog.setOnCancelListener(sc); + sc.progressDialog.getWindow().addFlags( + WindowManager.LayoutParams.FLAG_BLUR_BEHIND); + + // display the progress dialog + sc.progressDialog.show(); + + // run the query. + handler.startQuery(ADN_QUERY_TOKEN, sc, Uri.parse("content://icc/adn"), + new String[]{ADN_PHONE_NUMBER_COLUMN_NAME}, null, null, null); + + if (sPreviousAdnQueryHandler != null) { + // It is harmless to call cancel() even after the handler's gone. + sPreviousAdnQueryHandler.cancel(); + } + sPreviousAdnQueryHandler = handler; + return true; + } catch (NumberFormatException ex) { + // Ignore + } + } + return false; + } + + static boolean handlePinEntry(Context context, String input) { + if ((input.startsWith("**04") || input.startsWith("**05")) && input.endsWith("#")) { + try { + return ITelephony.Stub.asInterface(ServiceManager.getService("phone")) + .handlePinMmi(input); + } catch (RemoteException e) { + Log.e(TAG, "Failed to handlePinMmi due to remote exception"); + return false; + } + } + return false; + } + + static boolean handleIMEIDisplay(Context context, String input, boolean useSystemWindow) { + TelephonyManager telephonyManager = + (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + if (telephonyManager != null && input.equals(MMI_IMEI_DISPLAY)) { + int phoneType = telephonyManager.getCurrentPhoneType(); + if (phoneType == TelephonyManager.PHONE_TYPE_GSM) { + showIMEIPanel(context, useSystemWindow, telephonyManager); + return true; + } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) { + showMEIDPanel(context, useSystemWindow, telephonyManager); + return true; + } + } + + return false; + } + + // TODO: Combine showIMEIPanel() and showMEIDPanel() into a single + // generic "showDeviceIdPanel()" method, like in the apps/Phone + // version of SpecialCharSequenceMgr.java. (This will require moving + // the phone app's TelephonyCapabilities.getDeviceIdLabel() method + // into the telephony framework, though.) + + private static void showIMEIPanel(Context context, boolean useSystemWindow, + TelephonyManager telephonyManager) { + String imeiStr = telephonyManager.getDeviceId(); + + AlertDialog alert = new AlertDialog.Builder(context) + .setTitle(R.string.imei) + .setMessage(imeiStr) + .setPositiveButton(android.R.string.ok, null) + .setCancelable(false) + .show(); + } + + private static void showMEIDPanel(Context context, boolean useSystemWindow, + TelephonyManager telephonyManager) { + String meidStr = telephonyManager.getDeviceId(); + + AlertDialog alert = new AlertDialog.Builder(context) + .setTitle(R.string.meid) + .setMessage(meidStr) + .setPositiveButton(android.R.string.ok, null) + .setCancelable(false) + .show(); + } + + /******* + * This code is used to handle SIM Contact queries + *******/ + private static final String ADN_PHONE_NUMBER_COLUMN_NAME = "number"; + private static final String ADN_NAME_COLUMN_NAME = "name"; + private static final int ADN_QUERY_TOKEN = -1; + + /** + * Cookie object that contains everything we need to communicate to the + * handler's onQuery Complete, as well as what we need in order to cancel + * the query (if requested). + * + * Note, access to the textField field is going to be synchronized, because + * the user can request a cancel at any time through the UI. + */ + private static class SimContactQueryCookie implements DialogInterface.OnCancelListener{ + public ProgressDialog progressDialog; + public int contactNum; + + // Used to identify the query request. + private int mToken; + private QueryHandler mHandler; + + // The text field we're going to update + private EditText textField; + + public SimContactQueryCookie(int number, QueryHandler handler, int token) { + contactNum = number; + mHandler = handler; + mToken = token; + } + + /** + * Synchronized getter for the EditText. + */ + public synchronized EditText getTextField() { + return textField; + } + + /** + * Synchronized setter for the EditText. + */ + public synchronized void setTextField(EditText text) { + textField = text; + } + + /** + * Cancel the ADN query by stopping the operation and signaling + * the cookie that a cancel request is made. + */ + public synchronized void onCancel(DialogInterface dialog) { + // close the progress dialog + if (progressDialog != null) { + progressDialog.dismiss(); + } + + // setting the textfield to null ensures that the UI does NOT get + // updated. + textField = null; + + // Cancel the operation if possible. + mHandler.cancelOperation(mToken); + } + } + + /** + * Asynchronous query handler that services requests to look up ADNs + * + * Queries originate from {@link handleAdnEntry}. + */ + private static class QueryHandler extends AsyncQueryHandler { + + private boolean mCanceled; + + public QueryHandler(ContentResolver cr) { + super(cr); + } + + /** + * Override basic onQueryComplete to fill in the textfield when + * we're handed the ADN cursor. + */ + @Override + protected void onQueryComplete(int token, Object cookie, Cursor c) { + sPreviousAdnQueryHandler = null; + if (mCanceled) { + return; + } + + SimContactQueryCookie sc = (SimContactQueryCookie) cookie; + + // close the progress dialog. + sc.progressDialog.dismiss(); + + // get the EditText to update or see if the request was cancelled. + EditText text = sc.getTextField(); + + // if the textview is valid, and the cursor is valid and postionable + // on the Nth number, then we update the text field and display a + // toast indicating the caller name. + if ((c != null) && (text != null) && (c.moveToPosition(sc.contactNum))) { + String name = c.getString(c.getColumnIndexOrThrow(ADN_NAME_COLUMN_NAME)); + String number = c.getString(c.getColumnIndexOrThrow(ADN_PHONE_NUMBER_COLUMN_NAME)); + + // fill the text in. + text.getText().replace(0, 0, number); + + // display the name as a toast + Context context = sc.progressDialog.getContext(); + name = context.getString(R.string.menu_callNumber, name); + Toast.makeText(context, name, Toast.LENGTH_SHORT) + .show(); + } + } + + public void cancel() { + mCanceled = true; + // Ask AsyncQueryHandler to cancel the whole request. This will fails when the + // query already started. + cancelOperation(ADN_QUERY_TOKEN); + } + } +} |