1 /* 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 17 package com.android.phone; 18 19 import android.app.Activity; 20 import android.app.AlertDialog; 21 import android.content.ActivityNotFoundException; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.provider.Settings; 25 import android.telephony.PhoneNumberUtils; 26 import android.telephony.SubscriptionInfo; 27 import android.telephony.SubscriptionManager; 28 import android.telephony.TelephonyManager; 29 import android.util.Log; 30 import android.view.WindowManager; 31 32 import com.android.internal.telephony.IccCardConstants; 33 import com.android.internal.telephony.Phone; 34 import com.android.internal.telephony.TelephonyCapabilities; 35 36 import java.util.ArrayList; 37 import java.util.List; 38 39 /** 40 * Helper class to listen for some magic dialpad character sequences 41 * that are handled specially by the Phone app. 42 * 43 * Note the Contacts app also handles these sequences too, so there's a 44 * separate version of this class under apps/Contacts. 45 * 46 * In fact, the most common use case for these special sequences is typing 47 * them from the regular "Dialer" used for outgoing calls, which is part 48 * of the contacts app; see DialtactsActivity and DialpadFragment. 49 * *This* version of SpecialCharSequenceMgr is used for only a few 50 * relatively obscure places in the UI: 51 * - The "SIM network unlock" PIN entry screen (see 52 * IccNetworkDepersonalizationPanel.java) 53 * - The emergency dialer (see EmergencyDialer.java). 54 * 55 * TODO: there's lots of duplicated code between this class and the 56 * corresponding class under apps/Contacts. Let's figure out a way to 57 * unify these two classes (in the framework? in a common shared library?) 58 */ 59 public class SpecialCharSequenceMgr { 60 private static final String TAG = PhoneGlobals.LOG_TAG; 61 private static final boolean DBG = false; 62 63 private static final String MMI_IMEI_DISPLAY = "*#06#"; 64 private static final String MMI_REGULATORY_INFO_DISPLAY = "*#07#"; 65 66 /** This class is never instantiated. */ SpecialCharSequenceMgr()67 private SpecialCharSequenceMgr() { 68 } 69 70 /** 71 * Check for special strings of digits from an input 72 * string. 73 * @param context input Context for the events we handle. 74 * @param input the dial string to be examined. 75 */ handleChars(Context context, String input)76 static boolean handleChars(Context context, String input) { 77 return handleChars(context, input, null); 78 } 79 80 /** 81 * Generally used for the Personal Unblocking Key (PUK) unlocking 82 * case, where we want to be able to maintain a handle to the 83 * calling activity so that we can close it or otherwise display 84 * indication if the PUK code is recognized. 85 * 86 * NOTE: The counterpart to this file in Contacts does 87 * NOT contain the special PUK handling code, since it 88 * does NOT need it. When the device gets into PUK- 89 * locked state, the keyguard comes up and the only way 90 * to unlock the device is through the Emergency dialer, 91 * which is still in the Phone App. 92 * 93 * @param context input Context for the events we handle. 94 * @param input the dial string to be examined. 95 * @param pukInputActivity activity that originated this 96 * PUK call, tracked so that we can close it or otherwise 97 * indicate that special character sequence is 98 * successfully processed. Can be null. 99 * @return true if the input was a special string which has been 100 * handled. 101 */ handleChars(Context context, String input, Activity pukInputActivity)102 static boolean handleChars(Context context, 103 String input, 104 Activity pukInputActivity) { 105 106 //get rid of the separators so that the string gets parsed correctly 107 String dialString = PhoneNumberUtils.stripSeparators(input); 108 109 if (handleIMEIDisplay(context, dialString) 110 || handleRegulatoryInfoDisplay(context, dialString) 111 || handlePinEntry(context, dialString, pukInputActivity) 112 || handleAdnEntry(context, dialString) 113 || handleSecretCode(dialString)) { 114 return true; 115 } 116 117 return false; 118 } 119 120 /** 121 * Variant of handleChars() that looks for the subset of "special 122 * sequences" that are available even if the device is locked. 123 * 124 * (Specifically, these are the sequences that you're allowed to type 125 * in the Emergency Dialer, which is accessible *without* unlocking 126 * the device.) 127 */ handleCharsForLockedDevice(Context context, String input, Activity pukInputActivity)128 static boolean handleCharsForLockedDevice(Context context, 129 String input, 130 Activity pukInputActivity) { 131 // Get rid of the separators so that the string gets parsed correctly 132 String dialString = PhoneNumberUtils.stripSeparators(input); 133 134 // The only sequences available on a locked device are the "**04" 135 // or "**05" sequences that allow you to enter PIN or PUK-related 136 // codes. (e.g. for the case where you're currently locked out of 137 // your phone, and need to change the PIN! The only way to do 138 // that is via the Emergency Dialer.) 139 140 if (handlePinEntry(context, dialString, pukInputActivity)) { 141 return true; 142 } 143 144 return false; 145 } 146 147 /** 148 * Handles secret codes to launch arbitrary receivers in the form of *#*#<code>#*#*. 149 * If a secret code is encountered, an broadcast intent is sent with the 150 * android_secret_code://<code> URI. 151 * 152 * @param input the text to check for a secret code in 153 * @return true if a secret code was encountered and intent is sent out 154 */ handleSecretCode(String input)155 static private boolean handleSecretCode(String input) { 156 // Secret codes are in the form *#*#<code>#*#* 157 int len = input.length(); 158 if (len > 8 && input.startsWith("*#*#") && input.endsWith("#*#*")) { 159 final Phone phone = PhoneGlobals.getPhone(); 160 phone.sendDialerSpecialCode(input.substring(4, len - 4)); 161 return true; 162 } 163 return false; 164 } 165 handleAdnEntry(Context context, String input)166 static private boolean handleAdnEntry(Context context, String input) { 167 /* ADN entries are of the form "N(N)(N)#" */ 168 169 // if the phone is keyguard-restricted, then just ignore this 170 // input. We want to make sure that sim card contacts are NOT 171 // exposed unless the phone is unlocked, and this code can be 172 // accessed from the emergency dialer. 173 if (PhoneGlobals.getInstance().getKeyguardManager().inKeyguardRestrictedInputMode()) { 174 return false; 175 } 176 177 int len = input.length(); 178 if ((len > 1) && (len < 5) && (input.endsWith("#"))) { 179 try { 180 int index = Integer.parseInt(input.substring(0, len-1)); 181 Intent intent = new Intent(Intent.ACTION_PICK); 182 183 intent.setClassName("com.android.phone", 184 "com.android.phone.SimContacts"); 185 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 186 intent.putExtra("index", index); 187 PhoneGlobals.getInstance().startActivity(intent); 188 189 return true; 190 } catch (NumberFormatException ex) {} 191 } 192 return false; 193 } 194 getSimState(int slotId, Context context)195 private static IccCardConstants.State getSimState(int slotId, Context context) { 196 final TelephonyManager tele = TelephonyManager.from(context); 197 int simState = tele.getSimState(slotId); 198 IccCardConstants.State state; 199 try { 200 state = IccCardConstants.State.intToState(simState); 201 } catch (IllegalArgumentException ex) { 202 Log.w(TAG, "Unknown sim state: " + simState); 203 state = IccCardConstants.State.UNKNOWN; 204 } 205 return state; 206 } 207 getNextSubIdForState(IccCardConstants.State state, Context context)208 private static int getNextSubIdForState(IccCardConstants.State state, Context context) { 209 SubscriptionManager subscriptionManager = SubscriptionManager.from(context); 210 List<SubscriptionInfo> list = subscriptionManager.getActiveSubscriptionInfoList(); 211 if (list == null) { 212 // getActiveSubscriptionInfoList was null callers expect an empty list. 213 list = new ArrayList<>(); 214 } 215 int resultId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 216 int bestSlotId = Integer.MAX_VALUE; // Favor lowest slot first 217 for (int i = 0; i < list.size(); i++) { 218 final SubscriptionInfo info = list.get(i); 219 final int id = info.getSubscriptionId(); 220 if (state == getSimState(info.getSimSlotIndex(), context) 221 && bestSlotId > info.getSimSlotIndex()) { 222 resultId = id; 223 bestSlotId = info.getSimSlotIndex(); 224 } 225 } 226 return resultId; 227 } 228 handlePinEntry(Context context, String input, Activity pukInputActivity)229 static private boolean handlePinEntry(Context context, String input, 230 Activity pukInputActivity) { 231 // TODO: The string constants here should be removed in favor 232 // of some call to a static the MmiCode class that determines 233 // if a dialstring is an MMI code. 234 if ((input.startsWith("**04") || input.startsWith("**05")) 235 && input.endsWith("#")) { 236 PhoneGlobals app = PhoneGlobals.getInstance(); 237 Phone phone; 238 int subId; 239 if (input.startsWith("**04")) { 240 subId = getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED, context); 241 } else { 242 subId = getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED, context); 243 } 244 if (SubscriptionManager.isValidSubscriptionId(subId)) { 245 log("get phone with subId: " + subId); 246 phone = PhoneGlobals.getPhone(subId); 247 } else { 248 log("get default phone"); 249 phone = PhoneGlobals.getPhone(); 250 } 251 boolean isMMIHandled = phone.handlePinMmi(input); 252 253 // if the PUK code is recognized then indicate to the 254 // phone app that an attempt to unPUK the device was 255 // made with this activity. The PUK code may still 256 // fail though, but we won't know until the MMI code 257 // returns a result. 258 if (isMMIHandled && input.startsWith("**05")) { 259 app.setPukEntryActivity(pukInputActivity); 260 } 261 return isMMIHandled; 262 } 263 return false; 264 } 265 handleIMEIDisplay(Context context, String input)266 static private boolean handleIMEIDisplay(Context context, 267 String input) { 268 if (input.equals(MMI_IMEI_DISPLAY)) { 269 showDeviceIdPanel(context); 270 return true; 271 } 272 273 return false; 274 } 275 showDeviceIdPanel(Context context)276 static private void showDeviceIdPanel(Context context) { 277 if (DBG) log("showDeviceIdPanel()..."); 278 279 Phone phone = PhoneGlobals.getPhone(); 280 int labelId = TelephonyCapabilities.getDeviceIdLabel(phone); 281 String deviceId = phone.getDeviceId(); 282 283 AlertDialog alert = new AlertDialog.Builder(context) 284 .setTitle(labelId) 285 .setMessage(deviceId) 286 .setPositiveButton(R.string.ok, null) 287 .setCancelable(false) 288 .create(); 289 alert.getWindow().setType(WindowManager.LayoutParams.TYPE_PRIORITY_PHONE); 290 alert.show(); 291 } 292 handleRegulatoryInfoDisplay(Context context, String input)293 private static boolean handleRegulatoryInfoDisplay(Context context, String input) { 294 if (input.equals(MMI_REGULATORY_INFO_DISPLAY)) { 295 log("handleRegulatoryInfoDisplay() sending intent to settings app"); 296 Intent showRegInfoIntent = new Intent(Settings.ACTION_SHOW_REGULATORY_INFO); 297 try { 298 context.startActivity(showRegInfoIntent); 299 } catch (ActivityNotFoundException e) { 300 Log.e(TAG, "startActivity() failed: " + e); 301 } 302 return true; 303 } 304 return false; 305 } 306 log(String msg)307 private static void log(String msg) { 308 Log.d(TAG, "[SpecialCharSequenceMgr] " + msg); 309 } 310 } 311