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.AlertDialog; 20 import android.app.Dialog; 21 import android.app.ProgressDialog; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.DialogInterface; 25 import android.content.res.Resources; 26 import android.media.AudioAttributes; 27 import android.media.AudioManager; 28 import android.media.MediaPlayer; 29 import android.media.RingtoneManager; 30 import android.net.Uri; 31 import android.os.Handler; 32 import android.os.Message; 33 import android.os.PersistableBundle; 34 import android.os.VibrationEffect; 35 import android.os.Vibrator; 36 import android.telecom.PhoneAccount; 37 import android.telecom.PhoneAccountHandle; 38 import android.telecom.VideoProfile; 39 import android.telephony.CarrierConfigManager; 40 import android.telephony.PhoneNumberUtils; 41 import android.telephony.SubscriptionManager; 42 import android.text.TextUtils; 43 import android.util.Log; 44 import android.view.ContextThemeWrapper; 45 import android.view.KeyEvent; 46 import android.view.LayoutInflater; 47 import android.view.View; 48 import android.view.WindowManager; 49 import android.widget.EditText; 50 import android.widget.Toast; 51 52 import com.android.internal.telephony.Call; 53 import com.android.internal.telephony.CallStateException; 54 import com.android.internal.telephony.Connection; 55 import com.android.internal.telephony.IccCard; 56 import com.android.internal.telephony.MmiCode; 57 import com.android.internal.telephony.Phone; 58 import com.android.internal.telephony.PhoneConstants; 59 import com.android.internal.telephony.PhoneFactory; 60 import com.android.internal.telephony.TelephonyCapabilities; 61 import com.android.phone.settings.SuppServicesUiUtil; 62 import com.android.telephony.Rlog; 63 64 import java.io.IOException; 65 import java.util.List; 66 67 /** 68 * Misc utilities for the Phone app. 69 */ 70 public class PhoneUtils { 71 public static final String EMERGENCY_ACCOUNT_HANDLE_ID = "E"; 72 private static final String LOG_TAG = "PhoneUtils"; 73 private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2); 74 75 // Do not check in with VDBG = true, since that may write PII to the system log. 76 private static final boolean VDBG = false; 77 78 // Return codes from placeCall() 79 public static final int CALL_STATUS_DIALED = 0; // The number was successfully dialed 80 public static final int CALL_STATUS_DIALED_MMI = 1; // The specified number was an MMI code 81 public static final int CALL_STATUS_FAILED = 2; // The call failed 82 83 // USSD string length for MMI operations 84 static final int MIN_USSD_LEN = 1; 85 static final int MAX_USSD_LEN = 160; 86 87 /** Define for not a special CNAP string */ 88 private static final int CNAP_SPECIAL_CASE_NO = -1; 89 90 /** Define for default vibrate pattern if res cannot be found */ 91 private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250}; 92 93 /** 94 * Theme to use for dialogs displayed by utility methods in this class. This is needed 95 * because these dialogs are displayed using the application context, which does not resolve 96 * the dialog theme correctly. 97 */ 98 private static final int THEME = com.android.internal.R.style.Theme_DeviceDefault_Dialog_Alert; 99 100 /** USSD information used to aggregate all USSD messages */ 101 private static StringBuilder sUssdMsg = new StringBuilder(); 102 103 private static final ComponentName PSTN_CONNECTION_SERVICE_COMPONENT = 104 new ComponentName("com.android.phone", 105 "com.android.services.telephony.TelephonyConnectionService"); 106 107 /** This class is never instantiated. */ PhoneUtils()108 private PhoneUtils() { 109 } 110 111 /** 112 * For a CDMA phone, advance the call state upon making a new 113 * outgoing call. 114 * 115 * <pre> 116 * IDLE -> SINGLE_ACTIVE 117 * or 118 * SINGLE_ACTIVE -> THRWAY_ACTIVE 119 * </pre> 120 * @param app The phone instance. 121 */ updateCdmaCallStateOnNewOutgoingCall(PhoneGlobals app, Connection connection)122 private static void updateCdmaCallStateOnNewOutgoingCall(PhoneGlobals app, 123 Connection connection) { 124 if (app.cdmaPhoneCallState.getCurrentCallState() == 125 CdmaPhoneCallState.PhoneCallState.IDLE) { 126 // This is the first outgoing call. Set the Phone Call State to ACTIVE 127 app.cdmaPhoneCallState.setCurrentCallState( 128 CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE); 129 } else { 130 // This is the second outgoing call. Set the Phone Call State to 3WAY 131 app.cdmaPhoneCallState.setCurrentCallState( 132 CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE); 133 134 // TODO: Remove this code. 135 //app.getCallModeler().setCdmaOutgoing3WayCall(connection); 136 } 137 } 138 139 /** 140 * Dial the number using the phone passed in. 141 * 142 * @param context To perform the CallerInfo query. 143 * @param phone the Phone object. 144 * @param number to be dialed as requested by the user. This is 145 * NOT the phone number to connect to. It is used only to build the 146 * call card and to update the call log. See above for restrictions. 147 * 148 * @return either CALL_STATUS_DIALED or CALL_STATUS_FAILED 149 */ placeOtaspCall(Context context, Phone phone, String number)150 public static int placeOtaspCall(Context context, Phone phone, String number) { 151 final Uri gatewayUri = null; 152 153 if (VDBG) { 154 log("placeCall()... number: '" + number + "'" 155 + ", GW:'" + gatewayUri + "'"); 156 } else { 157 log("placeCall()... number: " + toLogSafePhoneNumber(number) 158 + ", GW: " + (gatewayUri != null ? "non-null" : "null")); 159 } 160 final PhoneGlobals app = PhoneGlobals.getInstance(); 161 162 boolean useGateway = false; 163 Uri contactRef = null; 164 165 int status = CALL_STATUS_DIALED; 166 Connection connection; 167 String numberToDial; 168 numberToDial = number; 169 170 try { 171 connection = app.mCM.dial(phone, numberToDial, VideoProfile.STATE_AUDIO_ONLY); 172 } catch (CallStateException ex) { 173 // CallStateException means a new outgoing call is not currently 174 // possible: either no more call slots exist, or there's another 175 // call already in the process of dialing or ringing. 176 Log.w(LOG_TAG, "Exception from app.mCM.dial()", ex); 177 return CALL_STATUS_FAILED; 178 179 // Note that it's possible for CallManager.dial() to return 180 // null *without* throwing an exception; that indicates that 181 // we dialed an MMI (see below). 182 } 183 184 int phoneType = phone.getPhoneType(); 185 186 // On GSM phones, null is returned for MMI codes 187 if (null == connection) { 188 status = CALL_STATUS_FAILED; 189 } else { 190 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) { 191 updateCdmaCallStateOnNewOutgoingCall(app, connection); 192 } 193 } 194 195 return status; 196 } 197 toLogSafePhoneNumber(String number)198 /* package */ static String toLogSafePhoneNumber(String number) { 199 // For unknown number, log empty string. 200 if (number == null) { 201 return ""; 202 } 203 204 if (VDBG) { 205 // When VDBG is true we emit PII. 206 return number; 207 } 208 209 // Do exactly same thing as Uri#toSafeString() does, which will enable us to compare 210 // sanitized phone numbers. 211 StringBuilder builder = new StringBuilder(); 212 for (int i = 0; i < number.length(); i++) { 213 char c = number.charAt(i); 214 if (c == '-' || c == '@' || c == '.') { 215 builder.append(c); 216 } else { 217 builder.append('x'); 218 } 219 } 220 return builder.toString(); 221 } 222 223 /** 224 * Handle the MMIInitiate message and put up an alert that lets 225 * the user cancel the operation, if applicable. 226 * 227 * @param context context to get strings. 228 * @param mmiCode the MmiCode object being started. 229 * @param buttonCallbackMessage message to post when button is clicked. 230 * @param previousAlert a previous alert used in this activity. 231 * @return the dialog handle 232 */ displayMMIInitiate(Context context, MmiCode mmiCode, Message buttonCallbackMessage, Dialog previousAlert)233 static Dialog displayMMIInitiate(Context context, 234 MmiCode mmiCode, 235 Message buttonCallbackMessage, 236 Dialog previousAlert) { 237 log("displayMMIInitiate: " + Rlog.pii(LOG_TAG, mmiCode.toString())); 238 if (previousAlert != null) { 239 previousAlert.dismiss(); 240 } 241 242 // The UI paradigm we are using now requests that all dialogs have 243 // user interaction, and that any other messages to the user should 244 // be by way of Toasts. 245 // 246 // In adhering to this request, all MMI initiating "OK" dialogs 247 // (non-cancelable MMIs) that end up being closed when the MMI 248 // completes (thereby showing a completion dialog) are being 249 // replaced with Toasts. 250 // 251 // As a side effect, moving to Toasts for the non-cancelable MMIs 252 // also means that buttonCallbackMessage (which was tied into "OK") 253 // is no longer invokable for these dialogs. This is not a problem 254 // since the only callback messages we supported were for cancelable 255 // MMIs anyway. 256 // 257 // A cancelable MMI is really just a USSD request. The term 258 // "cancelable" here means that we can cancel the request when the 259 // system prompts us for a response, NOT while the network is 260 // processing the MMI request. Any request to cancel a USSD while 261 // the network is NOT ready for a response may be ignored. 262 // 263 // With this in mind, we replace the cancelable alert dialog with 264 // a progress dialog, displayed until we receive a request from 265 // the the network. For more information, please see the comments 266 // in the displayMMIComplete() method below. 267 // 268 // Anything that is NOT a USSD request is a normal MMI request, 269 // which will bring up a toast (desribed above). 270 271 boolean isCancelable = (mmiCode != null) && mmiCode.isCancelable(); 272 273 if (!isCancelable) { 274 log("displayMMIInitiate: not a USSD code, displaying status toast."); 275 CharSequence text = context.getText(R.string.mmiStarted); 276 Toast.makeText(context, text, Toast.LENGTH_SHORT) 277 .show(); 278 return null; 279 } else { 280 log("displayMMIInitiate: running USSD code, displaying intermediate progress."); 281 282 // create the indeterminate progress dialog and display it. 283 ProgressDialog pd = new ProgressDialog(context, THEME); 284 pd.setMessage(context.getText(R.string.ussdRunning)); 285 pd.setCancelable(false); 286 pd.setIndeterminate(true); 287 pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); 288 289 pd.show(); 290 291 return pd; 292 } 293 294 } 295 296 /** 297 * Handle the MMIComplete message and fire off an intent to display 298 * the message. 299 * 300 * @param context context to get strings. 301 * @param mmiCode MMI result. 302 * @param previousAlert a previous alert used in this activity. 303 */ displayMMIComplete(final Phone phone, Context context, final MmiCode mmiCode, Message dismissCallbackMessage, AlertDialog previousAlert)304 static void displayMMIComplete(final Phone phone, Context context, final MmiCode mmiCode, 305 Message dismissCallbackMessage, 306 AlertDialog previousAlert) { 307 final PhoneGlobals app = PhoneGlobals.getInstance(); 308 CharSequence text; 309 int title = 0; // title for the progress dialog, if needed. 310 MmiCode.State state = mmiCode.getState(); 311 312 log("displayMMIComplete: state=" + state); 313 314 switch (state) { 315 case PENDING: 316 // USSD code asking for feedback from user. 317 text = mmiCode.getMessage(); 318 log("displayMMIComplete: using text from PENDING MMI message: '" + text + "'"); 319 break; 320 case CANCELLED: 321 text = null; 322 break; 323 case COMPLETE: 324 PersistableBundle b = null; 325 if (SubscriptionManager.isValidSubscriptionId(phone.getSubId())) { 326 b = app.getCarrierConfigForSubId( 327 phone.getSubId()); 328 } else { 329 b = app.getCarrierConfig(); 330 } 331 332 if (b.getBoolean(CarrierConfigManager.KEY_USE_CALLER_ID_USSD_BOOL)) { 333 text = SuppServicesUiUtil.handleCallerIdUssdResponse(app, context, phone, 334 mmiCode); 335 if (mmiCode.getMessage() != null && !text.equals(mmiCode.getMessage())) { 336 break; 337 } 338 } 339 340 if (app.getPUKEntryActivity() != null) { 341 // if an attempt to unPUK the device was made, we specify 342 // the title and the message here. 343 title = com.android.internal.R.string.PinMmi; 344 text = context.getText(R.string.puk_unlocked); 345 break; 346 } 347 // All other conditions for the COMPLETE mmi state will cause 348 // the case to fall through to message logic in common with 349 // the FAILED case. 350 351 case FAILED: 352 text = mmiCode.getMessage(); 353 log("displayMMIComplete (failed): using text from MMI message: '" + text + "'"); 354 break; 355 default: 356 throw new IllegalStateException("Unexpected MmiCode state: " + state); 357 } 358 359 if (previousAlert != null) { 360 previousAlert.dismiss(); 361 } 362 363 // Check to see if a UI exists for the PUK activation. If it does 364 // exist, then it indicates that we're trying to unblock the PUK. 365 if ((app.getPUKEntryActivity() != null) && (state == MmiCode.State.COMPLETE)) { 366 log("displaying PUK unblocking progress dialog."); 367 368 // create the progress dialog, make sure the flags and type are 369 // set correctly. 370 ProgressDialog pd = new ProgressDialog(app, THEME); 371 pd.setTitle(title); 372 pd.setMessage(text); 373 pd.setCancelable(false); 374 pd.setIndeterminate(true); 375 pd.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); 376 pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); 377 378 // display the dialog 379 pd.show(); 380 381 // indicate to the Phone app that the progress dialog has 382 // been assigned for the PUK unlock / SIM READY process. 383 app.setPukEntryProgressDialog(pd); 384 385 } else if ((app.getPUKEntryActivity() != null) && (state == MmiCode.State.FAILED)) { 386 createUssdDialog(app, context, text, phone, 387 WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 388 // In case of failure to unlock, we'll need to reset the 389 // PUK unlock activity, so that the user may try again. 390 app.setPukEntryActivity(null); 391 } else { 392 // In case of failure to unlock, we'll need to reset the 393 // PUK unlock activity, so that the user may try again. 394 if (app.getPUKEntryActivity() != null) { 395 app.setPukEntryActivity(null); 396 } 397 398 // A USSD in a pending state means that it is still 399 // interacting with the user. 400 if (state != MmiCode.State.PENDING) { 401 createUssdDialog(app, context, text, phone, 402 WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); 403 } else { 404 log("displayMMIComplete: USSD code has requested user input. Constructing input " 405 + "dialog."); 406 407 // USSD MMI code that is interacting with the user. The 408 // basic set of steps is this: 409 // 1. User enters a USSD request 410 // 2. We recognize the request and displayMMIInitiate 411 // (above) creates a progress dialog. 412 // 3. Request returns and we get a PENDING or COMPLETE 413 // message. 414 // 4. These MMI messages are caught in the PhoneApp 415 // (onMMIComplete) and the InCallScreen 416 // (mHandler.handleMessage) which bring up this dialog 417 // and closes the original progress dialog, 418 // respectively. 419 // 5. If the message is anything other than PENDING, 420 // we are done, and the alert dialog (directly above) 421 // displays the outcome. 422 // 6. If the network is requesting more information from 423 // the user, the MMI will be in a PENDING state, and 424 // we display this dialog with the message. 425 // 7. User input, or cancel requests result in a return 426 // to step 1. Keep in mind that this is the only 427 // time that a USSD should be canceled. 428 429 // inflate the layout with the scrolling text area for the dialog. 430 ContextThemeWrapper contextThemeWrapper = 431 new ContextThemeWrapper(context, R.style.DialerAlertDialogTheme); 432 LayoutInflater inflater = (LayoutInflater) contextThemeWrapper.getSystemService( 433 Context.LAYOUT_INFLATER_SERVICE); 434 View dialogView = inflater.inflate(R.layout.dialog_ussd_response, null); 435 436 // get the input field. 437 final EditText inputText = (EditText) dialogView.findViewById(R.id.input_field); 438 439 // specify the dialog's click listener, with SEND and CANCEL logic. 440 final DialogInterface.OnClickListener mUSSDDialogListener = 441 new DialogInterface.OnClickListener() { 442 public void onClick(DialogInterface dialog, int whichButton) { 443 switch (whichButton) { 444 case DialogInterface.BUTTON_POSITIVE: 445 // As per spec 24.080, valid length of ussd string 446 // is 1 - 160. If length is out of the range then 447 // display toast message & Cancel MMI operation. 448 if (inputText.length() < MIN_USSD_LEN 449 || inputText.length() > MAX_USSD_LEN) { 450 Toast.makeText(app, 451 app.getResources().getString(R.string.enter_input, 452 MIN_USSD_LEN, MAX_USSD_LEN), 453 Toast.LENGTH_LONG).show(); 454 if (mmiCode.isCancelable()) { 455 mmiCode.cancel(); 456 } 457 } else { 458 phone.sendUssdResponse(inputText.getText().toString()); 459 } 460 break; 461 case DialogInterface.BUTTON_NEGATIVE: 462 if (mmiCode.isCancelable()) { 463 mmiCode.cancel(); 464 } 465 break; 466 } 467 } 468 }; 469 470 // build the dialog 471 final AlertDialog newDialog = new AlertDialog.Builder(contextThemeWrapper) 472 .setMessage(text) 473 .setView(dialogView) 474 .setPositiveButton(R.string.send_button, mUSSDDialogListener) 475 .setNegativeButton(R.string.cancel, mUSSDDialogListener) 476 .setCancelable(false) 477 .create(); 478 479 // attach the key listener to the dialog's input field and make 480 // sure focus is set. 481 final View.OnKeyListener mUSSDDialogInputListener = 482 new View.OnKeyListener() { 483 public boolean onKey(View v, int keyCode, KeyEvent event) { 484 switch (keyCode) { 485 case KeyEvent.KEYCODE_CALL: 486 case KeyEvent.KEYCODE_ENTER: 487 if(event.getAction() == KeyEvent.ACTION_DOWN) { 488 phone.sendUssdResponse(inputText.getText().toString()); 489 newDialog.dismiss(); 490 } 491 return true; 492 } 493 return false; 494 } 495 }; 496 inputText.setOnKeyListener(mUSSDDialogInputListener); 497 inputText.requestFocus(); 498 499 // set the window properties of the dialog 500 newDialog.getWindow().setType( 501 WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); 502 newDialog.getWindow().addFlags( 503 WindowManager.LayoutParams.FLAG_DIM_BEHIND); 504 505 // now show the dialog! 506 newDialog.show(); 507 508 newDialog.getButton(DialogInterface.BUTTON_POSITIVE) 509 .setTextColor(context.getResources().getColor(R.color.dialer_theme_color)); 510 newDialog.getButton(DialogInterface.BUTTON_NEGATIVE) 511 .setTextColor(context.getResources().getColor(R.color.dialer_theme_color)); 512 } 513 514 if (mmiCode.isNetworkInitiatedUssd()) { 515 playSound(context); 516 } 517 } 518 } 519 playSound(Context context)520 private static void playSound(Context context) { 521 AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 522 int callsRingerMode = audioManager.getRingerMode(); 523 524 if (callsRingerMode == AudioManager.RINGER_MODE_NORMAL) { 525 log("playSound : RINGER_MODE_NORMAL"); 526 try { 527 Uri notificationUri = RingtoneManager.getDefaultUri( 528 RingtoneManager.TYPE_NOTIFICATION); 529 MediaPlayer mediaPlayer = new MediaPlayer(); 530 mediaPlayer.setDataSource(context, notificationUri); 531 AudioAttributes aa = new AudioAttributes.Builder() 532 .setLegacyStreamType(AudioManager.STREAM_NOTIFICATION) 533 .setUsage(AudioAttributes.USAGE_NOTIFICATION) 534 .build(); 535 mediaPlayer.setAudioAttributes(aa); 536 mediaPlayer.setLooping(false); 537 mediaPlayer.prepare(); 538 mediaPlayer.start(); 539 } catch (IOException e) { 540 log("playSound exception : " + e); 541 } 542 } else if (callsRingerMode == AudioManager.RINGER_MODE_VIBRATE) { 543 log("playSound : RINGER_MODE_VIBRATE"); 544 Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); 545 // Use NotificationManagerService#DEFAULT_VIBRATE_PATTERN if 546 // R.array.config_defaultNotificationVibePattern is not defined. 547 long[] pattern = getLongArray(context.getResources(), 548 R.array.config_defaultNotificationVibePattern, DEFAULT_VIBRATE_PATTERN); 549 vibrator.vibrate(VibrationEffect.createWaveform(pattern, -1)); 550 } 551 } 552 getLongArray(Resources r, int resid, long[] def)553 private static long[] getLongArray(Resources r, int resid, long[] def) { 554 int[] ar = r.getIntArray(resid); 555 if (ar == null) { 556 return def; 557 } 558 final int len = ar.length; 559 long[] out = new long[len]; 560 for (int i = 0; i < len; i++) { 561 out[i] = ar[i]; 562 } 563 return out; 564 } 565 566 /** 567 * It displays the message dialog for user about the mmi code result message. 568 * 569 * @param app This is {@link PhoneGlobals} 570 * @param context Context to get strings. 571 * @param text This is message's result. 572 * @param phone This is phone to create sssd dialog. 573 * @param windowType The new window type. {@link WindowManager.LayoutParams}. 574 */ createUssdDialog(PhoneGlobals app, Context context, CharSequence text, Phone phone, int windowType)575 public static void createUssdDialog(PhoneGlobals app, Context context, CharSequence text, 576 Phone phone, int windowType) { 577 log("displayMMIComplete: MMI code has finished running."); 578 579 log("displayMMIComplete: Extended NW displayMMIInitiate (" + text + ")"); 580 if (text == null || text.length() == 0) { 581 return; 582 } 583 584 // displaying system alert dialog on the screen instead of 585 // using another activity to display the message. This 586 // places the message at the forefront of the UI. 587 AlertDialog ussdDialog = new AlertDialog.Builder(context, THEME) 588 .setPositiveButton(R.string.ok, null) 589 .setCancelable(true) 590 .setOnDismissListener(new DialogInterface.OnDismissListener() { 591 @Override 592 public void onDismiss(DialogInterface dialog) { 593 sUssdMsg.setLength(0); 594 } 595 }) 596 .create(); 597 598 ussdDialog.getWindow().setType(windowType); 599 ussdDialog.getWindow().addFlags( 600 WindowManager.LayoutParams.FLAG_DIM_BEHIND); 601 602 if (sUssdMsg.length() != 0) { 603 sUssdMsg.insert(0, "\n") 604 .insert(0, app.getResources().getString(R.string.ussd_dialog_sep)) 605 .insert(0, "\n"); 606 } 607 if (phone != null && phone.getCarrierName() != null) { 608 ussdDialog.setTitle(app.getResources().getString(R.string.carrier_mmi_msg_title, 609 phone.getCarrierName())); 610 } else { 611 ussdDialog 612 .setTitle(app.getResources().getString(R.string.default_carrier_mmi_msg_title)); 613 } 614 sUssdMsg.insert(0, text); 615 ussdDialog.setMessage(sUssdMsg.toString()); 616 ussdDialog.show(); 617 } 618 619 /** 620 * Cancels the current pending MMI operation, if applicable. 621 * @return true if we canceled an MMI operation, or false 622 * if the current pending MMI wasn't cancelable 623 * or if there was no current pending MMI at all. 624 * 625 * @see displayMMIInitiate 626 */ cancelMmiCode(Phone phone)627 static boolean cancelMmiCode(Phone phone) { 628 List<? extends MmiCode> pendingMmis = phone.getPendingMmiCodes(); 629 int count = pendingMmis.size(); 630 if (DBG) log("cancelMmiCode: num pending MMIs = " + count); 631 632 boolean canceled = false; 633 if (count > 0) { 634 // assume that we only have one pending MMI operation active at a time. 635 // I don't think it's possible to enter multiple MMI codes concurrently 636 // in the phone UI, because during the MMI operation, an Alert panel 637 // is displayed, which prevents more MMI code from being entered. 638 MmiCode mmiCode = pendingMmis.get(0); 639 if (mmiCode.isCancelable()) { 640 mmiCode.cancel(); 641 canceled = true; 642 } 643 } 644 return canceled; 645 } 646 647 648 // 649 // Misc UI policy helper functions 650 // 651 652 /** 653 * Check if a phone number can be route through a 3rd party 654 * gateway. The number must be a global phone number in numerical 655 * form (1-800-666-SEXY won't work). 656 * 657 * MMI codes and the like cannot be used as a dial number for the 658 * gateway either. 659 * 660 * @param number To be dialed via a 3rd party gateway. 661 * @return true If the number can be routed through the 3rd party network. 662 */ isRoutableViaGateway(String number)663 private static boolean isRoutableViaGateway(String number) { 664 if (TextUtils.isEmpty(number)) { 665 return false; 666 } 667 number = PhoneNumberUtils.stripSeparators(number); 668 if (!number.equals(PhoneNumberUtils.convertKeypadLettersToDigits(number))) { 669 return false; 670 } 671 number = PhoneNumberUtils.extractNetworkPortion(number); 672 return PhoneNumberUtils.isGlobalPhoneNumber(number); 673 } 674 675 /** 676 * Returns whether the phone is in ECM ("Emergency Callback Mode") or not. 677 */ isPhoneInEcm(Phone phone)678 /* package */ static boolean isPhoneInEcm(Phone phone) { 679 if ((phone != null) && TelephonyCapabilities.supportsEcm(phone)) { 680 return phone.isInEcm(); 681 } 682 return false; 683 } 684 685 /** 686 * Returns true when the given call is in INCOMING state and there's no foreground phone call, 687 * meaning the call is the first real incoming call the phone is having. 688 */ isRealIncomingCall(Call.State state)689 public static boolean isRealIncomingCall(Call.State state) { 690 return (state == Call.State.INCOMING && !PhoneGlobals.getInstance().mCM.hasActiveFgCall()); 691 } 692 693 // 694 // General phone and call state debugging/testing code 695 // 696 log(String msg)697 private static void log(String msg) { 698 Log.d(LOG_TAG, msg); 699 } 700 makePstnPhoneAccountHandle(String id)701 public static PhoneAccountHandle makePstnPhoneAccountHandle(String id) { 702 return makePstnPhoneAccountHandleWithPrefix(id, "", false); 703 } 704 makePstnPhoneAccountHandle(int phoneId)705 public static PhoneAccountHandle makePstnPhoneAccountHandle(int phoneId) { 706 return makePstnPhoneAccountHandle(PhoneFactory.getPhone(phoneId)); 707 } 708 makePstnPhoneAccountHandle(Phone phone)709 public static PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) { 710 return makePstnPhoneAccountHandleWithPrefix(phone, "", false); 711 } 712 makePstnPhoneAccountHandleWithPrefix( Phone phone, String prefix, boolean isEmergency)713 public static PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix( 714 Phone phone, String prefix, boolean isEmergency) { 715 // TODO: Should use some sort of special hidden flag to decorate this account as 716 // an emergency-only account 717 String id = isEmergency ? EMERGENCY_ACCOUNT_HANDLE_ID : prefix + 718 String.valueOf(phone.getFullIccSerialNumber()); 719 return makePstnPhoneAccountHandleWithPrefix(id, prefix, isEmergency); 720 } 721 makePstnPhoneAccountHandleWithPrefix( String id, String prefix, boolean isEmergency)722 public static PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix( 723 String id, String prefix, boolean isEmergency) { 724 ComponentName pstnConnectionServiceName = getPstnConnectionServiceName(); 725 return new PhoneAccountHandle(pstnConnectionServiceName, id); 726 } 727 getSubIdForPhoneAccount(PhoneAccount phoneAccount)728 public static int getSubIdForPhoneAccount(PhoneAccount phoneAccount) { 729 if (phoneAccount != null 730 && phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { 731 return getSubIdForPhoneAccountHandle(phoneAccount.getAccountHandle()); 732 } 733 return SubscriptionManager.INVALID_SUBSCRIPTION_ID; 734 } 735 getSubIdForPhoneAccountHandle(PhoneAccountHandle handle)736 public static int getSubIdForPhoneAccountHandle(PhoneAccountHandle handle) { 737 Phone phone = getPhoneForPhoneAccountHandle(handle); 738 if (phone != null) { 739 return phone.getSubId(); 740 } 741 return SubscriptionManager.INVALID_SUBSCRIPTION_ID; 742 } 743 getPhoneForPhoneAccountHandle(PhoneAccountHandle handle)744 public static Phone getPhoneForPhoneAccountHandle(PhoneAccountHandle handle) { 745 if (handle != null && handle.getComponentName().equals(getPstnConnectionServiceName())) { 746 return getPhoneFromIccId(handle.getId()); 747 } 748 return null; 749 } 750 751 /** 752 * Determine if a given phone account corresponds to an active SIM 753 * 754 * @param sm An instance of the subscription manager so it is not recreated for each calling of 755 * this method. 756 * @param handle The handle for the phone account to check 757 * @return {@code true} If there is an active SIM for this phone account, 758 * {@code false} otherwise. 759 */ isPhoneAccountActive(SubscriptionManager sm, PhoneAccountHandle handle)760 public static boolean isPhoneAccountActive(SubscriptionManager sm, PhoneAccountHandle handle) { 761 return sm.getActiveSubscriptionInfoForIcc(handle.getId()) != null; 762 } 763 getPstnConnectionServiceName()764 private static ComponentName getPstnConnectionServiceName() { 765 return PSTN_CONNECTION_SERVICE_COMPONENT; 766 } 767 getPhoneFromIccId(String iccId)768 private static Phone getPhoneFromIccId(String iccId) { 769 if (!TextUtils.isEmpty(iccId)) { 770 for (Phone phone : PhoneFactory.getPhones()) { 771 String phoneIccId = phone.getFullIccSerialNumber(); 772 if (iccId.equals(phoneIccId)) { 773 return phone; 774 } 775 } 776 } 777 return null; 778 } 779 780 /** 781 * Register ICC status for all phones. 782 */ registerIccStatus(Handler handler, int event)783 static final void registerIccStatus(Handler handler, int event) { 784 for (Phone phone : PhoneFactory.getPhones()) { 785 IccCard sim = phone.getIccCard(); 786 if (sim != null) { 787 if (VDBG) Log.v(LOG_TAG, "register for ICC status, phone " + phone.getPhoneId()); 788 sim.registerForNetworkLocked(handler, event, phone); 789 } 790 } 791 } 792 793 /** 794 * Register ICC status for all phones. 795 */ registerIccStatus(Handler handler, int event, int phoneId)796 static final void registerIccStatus(Handler handler, int event, int phoneId) { 797 Phone[] phones = PhoneFactory.getPhones(); 798 IccCard sim = phones[phoneId].getIccCard(); 799 if (sim != null) { 800 if (VDBG) { 801 Log.v(LOG_TAG, "register for ICC status, phone " + phones[phoneId].getPhoneId()); 802 } 803 sim.registerForNetworkLocked(handler, event, phones[phoneId]); 804 } 805 } 806 807 /** 808 * Unregister ICC status for a specific phone. 809 */ unregisterIccStatus(Handler handler, int phoneId)810 static final void unregisterIccStatus(Handler handler, int phoneId) { 811 Phone[] phones = PhoneFactory.getPhones(); 812 IccCard sim = phones[phoneId].getIccCard(); 813 if (sim != null) { 814 if (VDBG) { 815 Log.v(LOG_TAG, "unregister for ICC status, phone " + phones[phoneId].getPhoneId()); 816 } 817 sim.unregisterForNetworkLocked(handler); 818 } 819 } 820 821 /** 822 * Set the radio power on/off state for all phones. 823 * 824 * @param enabled true means on, false means off. 825 */ setRadioPower(boolean enabled)826 static final void setRadioPower(boolean enabled) { 827 for (Phone phone : PhoneFactory.getPhones()) { 828 phone.setRadioPower(enabled); 829 } 830 } 831 } 832