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