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