/* * 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.phone; import android.app.Activity; import android.app.AlertDialog; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.os.UserManager; import android.provider.Settings; import android.telephony.PhoneNumberUtils; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.Log; import android.view.WindowManager; import com.android.internal.telephony.IccCardConstants; import com.android.internal.telephony.Phone; import com.android.internal.telephony.TelephonyCapabilities; import java.util.ArrayList; import java.util.List; /** * Helper class to listen for some magic dialpad character sequences * that are handled specially by the Phone app. * * Note the Contacts app also handles these sequences too, so there's a * separate version of this class under apps/Contacts. * * In fact, the most common use case for these special sequences is typing * them from the regular "Dialer" used for outgoing calls, which is part * of the contacts app; see DialtactsActivity and DialpadFragment. * *This* version of SpecialCharSequenceMgr is used for only a few * relatively obscure places in the UI: * - The "SIM network unlock" PIN entry screen (see * IccNetworkDepersonalizationPanel.java) * - The emergency dialer (see EmergencyDialer.java). * * TODO: there's lots of duplicated code between this class and the * corresponding class under apps/Contacts. 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 = PhoneGlobals.LOG_TAG; private static final boolean DBG = false; private static final String MMI_IMEI_DISPLAY = "*#06#"; private static final String MMI_REGULATORY_INFO_DISPLAY = "*#07#"; /** This class is never instantiated. */ private SpecialCharSequenceMgr() { } /** * Check for special strings of digits from an input * string. * @param context input Context for the events we handle. * @param input the dial string to be examined. */ static boolean handleChars(Context context, String input) { return handleChars(context, input, null); } /** * Generally used for the Personal Unblocking Key (PUK) unlocking * case, where we want to be able to maintain a handle to the * calling activity so that we can close it or otherwise display * indication if the PUK code is recognized. * * NOTE: The counterpart to this file in Contacts does * NOT contain the special PUK handling code, since it * does NOT need it. When the device gets into PUK- * locked state, the keyguard comes up and the only way * to unlock the device is through the Emergency dialer, * which is still in the Phone App. * * @param context input Context for the events we handle. * @param input the dial string to be examined. * @param pukInputActivity activity that originated this * PUK call, tracked so that we can close it or otherwise * indicate that special character sequence is * successfully processed. Can be null. * @return true if the input was a special string which has been * handled. */ static boolean handleChars(Context context, String input, Activity pukInputActivity) { //get rid of the separators so that the string gets parsed correctly String dialString = PhoneNumberUtils.stripSeparators(input); if (handleIMEIDisplay(context, dialString) || handleRegulatoryInfoDisplay(context, dialString) || handlePinEntry(context, dialString, pukInputActivity) || handleAdnEntry(context, dialString) || handleSecretCode(dialString)) { return true; } return false; } /** * Variant of handleChars() that looks for the subset of "special * sequences" that are available even if the device is locked. * * (Specifically, these are the sequences that you're allowed to type * in the Emergency Dialer, which is accessible *without* unlocking * the device.) */ static boolean handleCharsForLockedDevice(Context context, String input, Activity pukInputActivity) { // Get rid of the separators so that the string gets parsed correctly String dialString = PhoneNumberUtils.stripSeparators(input); // The only sequences available on a locked device are the "**04" // or "**05" sequences that allow you to enter PIN or PUK-related // codes. (e.g. for the case where you're currently locked out of // your phone, and need to change the PIN! The only way to do // that is via the Emergency Dialer.) if (handlePinEntry(context, dialString, pukInputActivity)) { return true; } return false; } /** * Handles secret codes to launch arbitrary receivers in the form of *#*##*#*. * If a secret code is encountered, an broadcast intent is sent with the * android_secret_code:// URI. * * @param input the text to check for a secret code in * @return true if a secret code was encountered and intent is sent out */ static private boolean handleSecretCode(String input) { // Secret codes are in the form *#*##*#* int len = input.length(); if (len > 8 && input.startsWith("*#*#") && input.endsWith("#*#*")) { final Phone phone = PhoneGlobals.getPhone(); phone.sendDialerSpecialCode(input.substring(4, len - 4)); return true; } return false; } static private boolean handleAdnEntry(Context context, String input) { /* ADN entries are of the form "N(N)(N)#" */ // 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. if (PhoneGlobals.getInstance().getKeyguardManager().inKeyguardRestrictedInputMode()) { return false; } int len = input.length(); if ((len > 1) && (len < 5) && (input.endsWith("#"))) { try { int index = Integer.parseInt(input.substring(0, len-1)); Intent intent = new Intent(Intent.ACTION_PICK); intent.setClassName("com.android.phone", "com.android.phone.SimContacts"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra("index", index); PhoneGlobals.getInstance().startActivity(intent); return true; } catch (NumberFormatException ex) {} } return false; } private static IccCardConstants.State getSimState(int slotId, Context context) { final TelephonyManager tele = TelephonyManager.from(context); int simState = tele.getSimState(slotId); IccCardConstants.State state; try { state = IccCardConstants.State.intToState(simState); } catch (IllegalArgumentException ex) { Log.w(TAG, "Unknown sim state: " + simState); state = IccCardConstants.State.UNKNOWN; } return state; } private static int getNextSubIdForState(IccCardConstants.State state, Context context) { SubscriptionManager subscriptionManager = SubscriptionManager.from(context); List list = subscriptionManager.getActiveSubscriptionInfoList(); if (list == null) { // getActiveSubscriptionInfoList was null callers expect an empty list. list = new ArrayList<>(); } int resultId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; int bestSlotId = Integer.MAX_VALUE; // Favor lowest slot first for (int i = 0; i < list.size(); i++) { final SubscriptionInfo info = list.get(i); final int id = info.getSubscriptionId(); if (state == getSimState(info.getSimSlotIndex(), context) && bestSlotId > info.getSimSlotIndex()) { resultId = id; bestSlotId = info.getSimSlotIndex(); } } return resultId; } static private boolean handlePinEntry(Context context, String input, Activity pukInputActivity) { // TODO: The string constants here should be removed in favor // of some call to a static the MmiCode class that determines // if a dialstring is an MMI code. if ((input.startsWith("**04") || input.startsWith("**05")) && input.endsWith("#")) { UserManager userManager = (UserManager) pukInputActivity .getSystemService(Context.USER_SERVICE); if (userManager.isSystemUser()) { PhoneGlobals app = PhoneGlobals.getInstance(); Phone phone; int subId; if (input.startsWith("**04")) { subId = getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED, context); } else { subId = getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED, context); } if (SubscriptionManager.isValidSubscriptionId(subId)) { log("get phone with subId: " + subId); phone = PhoneGlobals.getPhone(subId); } else { log("get default phone"); phone = PhoneGlobals.getPhone(); } boolean isMMIHandled = phone.handlePinMmi(input); // if the PUK code is recognized then indicate to the // phone app that an attempt to unPUK the device was // made with this activity. The PUK code may still // fail though, but we won't know until the MMI code // returns a result. if (isMMIHandled && input.startsWith("**05")) { app.setPukEntryActivity(pukInputActivity); } return isMMIHandled; } else { AlertDialog dialog = new AlertDialog.Builder(context) .setMessage(R.string.pin_puk_system_user_only) .setPositiveButton(R.string.ok, null) .setCancelable(true).create(); dialog.show(); dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND); dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); return true; } } return false; } static private boolean handleIMEIDisplay(Context context, String input) { if (input.equals(MMI_IMEI_DISPLAY)) { showDeviceIdPanel(context); return true; } return false; } static private void showDeviceIdPanel(Context context) { if (DBG) log("showDeviceIdPanel()..."); Phone phone = PhoneGlobals.getPhone(); int labelId = TelephonyCapabilities.getDeviceIdLabel(phone); String deviceId = phone.getDeviceId(); AlertDialog alert = new AlertDialog.Builder(context) .setTitle(labelId) .setMessage(deviceId) .setPositiveButton(R.string.ok, null) .setCancelable(false) .create(); alert.getWindow().setType(WindowManager.LayoutParams.TYPE_PRIORITY_PHONE); alert.show(); } private static boolean handleRegulatoryInfoDisplay(Context context, String input) { if (input.equals(MMI_REGULATORY_INFO_DISPLAY)) { log("handleRegulatoryInfoDisplay() sending intent to settings app"); Intent showRegInfoIntent = new Intent(Settings.ACTION_SHOW_REGULATORY_INFO); try { context.startActivity(showRegInfoIntent); } catch (ActivityNotFoundException e) { Log.e(TAG, "startActivity() failed: " + e); } return true; } return false; } private static void log(String msg) { Log.d(TAG, "[SpecialCharSequenceMgr] " + msg); } }