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 = 472 new AlertDialog.Builder(contextThemeWrapper) 473 .setMessage(text) 474 .setView(dialogView) 475 .setPositiveButton(R.string.send_button, mUSSDDialogListener) 476 .setNegativeButton(R.string.cancel, mUSSDDialogListener) 477 .setCancelable(false) 478 .create(); 479 480 // attach the key listener to the dialog's input field and make 481 // sure focus is set. 482 final View.OnKeyListener mUSSDDialogInputListener = 483 new View.OnKeyListener() { 484 public boolean onKey(View v, int keyCode, KeyEvent event) { 485 switch (keyCode) { 486 case KeyEvent.KEYCODE_CALL: 487 case KeyEvent.KEYCODE_ENTER: 488 if(event.getAction() == KeyEvent.ACTION_DOWN) { 489 phone.sendUssdResponse(inputText.getText().toString()); 490 newDialog.dismiss(); 491 } 492 return true; 493 } 494 return false; 495 } 496 }; 497 inputText.setOnKeyListener(mUSSDDialogInputListener); 498 inputText.requestFocus(); 499 500 // set the window properties of the dialog 501 newDialog.getWindow().setType( 502 WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); 503 newDialog.getWindow().addFlags( 504 WindowManager.LayoutParams.FLAG_DIM_BEHIND); 505 506 // now show the dialog! 507 newDialog.show(); 508 509 newDialog.getButton(DialogInterface.BUTTON_POSITIVE) 510 .setTextColor(context.getResources().getColor(R.color.dialer_theme_color)); 511 newDialog.getButton(DialogInterface.BUTTON_NEGATIVE) 512 .setTextColor(context.getResources().getColor(R.color.dialer_theme_color)); 513 } 514 515 if (mmiCode.isNetworkInitiatedUssd()) { 516 playSound(context); 517 } 518 } 519 } 520 playSound(Context context)521 private static void playSound(Context context) { 522 AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 523 int callsRingerMode = audioManager.getRingerMode(); 524 525 if (callsRingerMode == AudioManager.RINGER_MODE_NORMAL) { 526 log("playSound : RINGER_MODE_NORMAL"); 527 try { 528 Uri notificationUri = RingtoneManager.getDefaultUri( 529 RingtoneManager.TYPE_NOTIFICATION); 530 MediaPlayer mediaPlayer = new MediaPlayer(); 531 mediaPlayer.setDataSource(context, notificationUri); 532 AudioAttributes aa = new AudioAttributes.Builder() 533 .setLegacyStreamType(AudioManager.STREAM_NOTIFICATION) 534 .setUsage(AudioAttributes.USAGE_NOTIFICATION) 535 .build(); 536 mediaPlayer.setAudioAttributes(aa); 537 mediaPlayer.setLooping(false); 538 mediaPlayer.prepare(); 539 mediaPlayer.start(); 540 } catch (IOException e) { 541 log("playSound exception : " + e); 542 } 543 } else if (callsRingerMode == AudioManager.RINGER_MODE_VIBRATE) { 544 log("playSound : RINGER_MODE_VIBRATE"); 545 Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); 546 // Use NotificationManagerService#DEFAULT_VIBRATE_PATTERN if 547 // R.array.config_defaultNotificationVibePattern is not defined. 548 long[] pattern = getLongArray(context.getResources(), 549 R.array.config_defaultNotificationVibePattern, DEFAULT_VIBRATE_PATTERN); 550 vibrator.vibrate(VibrationEffect.createWaveform(pattern, -1), 551 new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION) 552 .build()); 553 } 554 } 555 getLongArray(Resources r, int resid, long[] def)556 private static long[] getLongArray(Resources r, int resid, long[] def) { 557 int[] ar = r.getIntArray(resid); 558 if (ar == null) { 559 return def; 560 } 561 final int len = ar.length; 562 long[] out = new long[len]; 563 for (int i = 0; i < len; i++) { 564 out[i] = ar[i]; 565 } 566 return out; 567 } 568 569 /** 570 * It displays the message dialog for user about the mmi code result message. 571 * 572 * @param app This is {@link PhoneGlobals} 573 * @param context Context to get strings. 574 * @param text This is message's result. 575 * @param phone This is phone to create sssd dialog. 576 * @param windowType The new window type. {@link WindowManager.LayoutParams}. 577 */ createUssdDialog(PhoneGlobals app, Context context, CharSequence text, Phone phone, int windowType)578 public static void createUssdDialog(PhoneGlobals app, Context context, CharSequence text, 579 Phone phone, int windowType) { 580 log("displayMMIComplete: MMI code has finished running."); 581 582 log("displayMMIComplete: Extended NW displayMMIInitiate (" + text + ")"); 583 if (text == null || text.length() == 0) { 584 return; 585 } 586 587 // displaying system alert dialog on the screen instead of 588 // using another activity to display the message. This 589 // places the message at the forefront of the UI. 590 AlertDialog ussdDialog = new AlertDialog.Builder(context, THEME) 591 .setPositiveButton(R.string.ok, null) 592 .setCancelable(true) 593 .setOnDismissListener(new DialogInterface.OnDismissListener() { 594 @Override 595 public void onDismiss(DialogInterface dialog) { 596 sUssdMsg.setLength(0); 597 } 598 }) 599 .create(); 600 601 ussdDialog.getWindow().setType(windowType); 602 ussdDialog.getWindow().addFlags( 603 WindowManager.LayoutParams.FLAG_DIM_BEHIND); 604 605 if (sUssdMsg.length() != 0) { 606 sUssdMsg.insert(0, "\n") 607 .insert(0, app.getResources().getString(R.string.ussd_dialog_sep)) 608 .insert(0, "\n"); 609 } 610 if (phone != null && phone.getCarrierName() != null) { 611 ussdDialog.setTitle(app.getResources().getString(R.string.carrier_mmi_msg_title, 612 phone.getCarrierName())); 613 } else { 614 ussdDialog 615 .setTitle(app.getResources().getString(R.string.default_carrier_mmi_msg_title)); 616 } 617 sUssdMsg.insert(0, text); 618 ussdDialog.setMessage(sUssdMsg.toString()); 619 ussdDialog.show(); 620 } 621 622 /** 623 * Cancels the current pending MMI operation, if applicable. 624 * @return true if we canceled an MMI operation, or false 625 * if the current pending MMI wasn't cancelable 626 * or if there was no current pending MMI at all. 627 * 628 * @see displayMMIInitiate 629 */ cancelMmiCode(Phone phone)630 static boolean cancelMmiCode(Phone phone) { 631 List<? extends MmiCode> pendingMmis = phone.getPendingMmiCodes(); 632 int count = pendingMmis.size(); 633 if (DBG) log("cancelMmiCode: num pending MMIs = " + count); 634 635 boolean canceled = false; 636 if (count > 0) { 637 // assume that we only have one pending MMI operation active at a time. 638 // I don't think it's possible to enter multiple MMI codes concurrently 639 // in the phone UI, because during the MMI operation, an Alert panel 640 // is displayed, which prevents more MMI code from being entered. 641 MmiCode mmiCode = pendingMmis.get(0); 642 if (mmiCode.isCancelable()) { 643 mmiCode.cancel(); 644 canceled = true; 645 } 646 } 647 return canceled; 648 } 649 650 651 // 652 // Misc UI policy helper functions 653 // 654 655 /** 656 * Check if a phone number can be route through a 3rd party 657 * gateway. The number must be a global phone number in numerical 658 * form (1-800-666-SEXY won't work). 659 * 660 * MMI codes and the like cannot be used as a dial number for the 661 * gateway either. 662 * 663 * @param number To be dialed via a 3rd party gateway. 664 * @return true If the number can be routed through the 3rd party network. 665 */ isRoutableViaGateway(String number)666 private static boolean isRoutableViaGateway(String number) { 667 if (TextUtils.isEmpty(number)) { 668 return false; 669 } 670 number = PhoneNumberUtils.stripSeparators(number); 671 if (!number.equals(PhoneNumberUtils.convertKeypadLettersToDigits(number))) { 672 return false; 673 } 674 number = PhoneNumberUtils.extractNetworkPortion(number); 675 return PhoneNumberUtils.isGlobalPhoneNumber(number); 676 } 677 678 /** 679 * Returns whether the phone is in ECM ("Emergency Callback Mode") or not. 680 */ isPhoneInEcm(Phone phone)681 /* package */ static boolean isPhoneInEcm(Phone phone) { 682 if ((phone != null) && TelephonyCapabilities.supportsEcm(phone)) { 683 return phone.isInEcm(); 684 } 685 return false; 686 } 687 688 /** 689 * Returns true when the given call is in INCOMING state and there's no foreground phone call, 690 * meaning the call is the first real incoming call the phone is having. 691 */ isRealIncomingCall(Call.State state)692 public static boolean isRealIncomingCall(Call.State state) { 693 return (state == Call.State.INCOMING && !PhoneGlobals.getInstance().mCM.hasActiveFgCall()); 694 } 695 696 // 697 // General phone and call state debugging/testing code 698 // 699 log(String msg)700 private static void log(String msg) { 701 Log.d(LOG_TAG, msg); 702 } 703 makePstnPhoneAccountHandle(String id)704 public static PhoneAccountHandle makePstnPhoneAccountHandle(String id) { 705 return makePstnPhoneAccountHandleWithPrefix(id, "", false); 706 } 707 makePstnPhoneAccountHandle(int phoneId)708 public static PhoneAccountHandle makePstnPhoneAccountHandle(int phoneId) { 709 return makePstnPhoneAccountHandle(PhoneFactory.getPhone(phoneId)); 710 } 711 makePstnPhoneAccountHandle(Phone phone)712 public static PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) { 713 return makePstnPhoneAccountHandleWithPrefix(phone, "", false); 714 } 715 makePstnPhoneAccountHandleWithPrefix( Phone phone, String prefix, boolean isEmergency)716 public static PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix( 717 Phone phone, String prefix, boolean isEmergency) { 718 // TODO: Should use some sort of special hidden flag to decorate this account as 719 // an emergency-only account 720 String id = isEmergency ? EMERGENCY_ACCOUNT_HANDLE_ID : prefix + 721 String.valueOf(phone.getSubId()); 722 return makePstnPhoneAccountHandleWithPrefix(id, prefix, isEmergency); 723 } 724 makePstnPhoneAccountHandleWithPrefix( String id, String prefix, boolean isEmergency)725 public static PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix( 726 String id, String prefix, boolean isEmergency) { 727 ComponentName pstnConnectionServiceName = getPstnConnectionServiceName(); 728 return new PhoneAccountHandle(pstnConnectionServiceName, id); 729 } 730 getSubIdForPhoneAccount(PhoneAccount phoneAccount)731 public static int getSubIdForPhoneAccount(PhoneAccount phoneAccount) { 732 if (phoneAccount != null 733 && phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { 734 return getSubIdForPhoneAccountHandle(phoneAccount.getAccountHandle()); 735 } 736 return SubscriptionManager.INVALID_SUBSCRIPTION_ID; 737 } 738 getSubIdForPhoneAccountHandle(PhoneAccountHandle handle)739 public static int getSubIdForPhoneAccountHandle(PhoneAccountHandle handle) { 740 Phone phone = getPhoneForPhoneAccountHandle(handle); 741 if (phone != null) { 742 return phone.getSubId(); 743 } 744 return SubscriptionManager.INVALID_SUBSCRIPTION_ID; 745 } 746 getPhoneForPhoneAccountHandle(PhoneAccountHandle handle)747 public static Phone getPhoneForPhoneAccountHandle(PhoneAccountHandle handle) { 748 if (handle != null && handle.getComponentName().equals(getPstnConnectionServiceName())) { 749 return getPhoneFromSubId(handle.getId()); 750 } 751 return null; 752 } 753 754 /** 755 * Determine if a given phone account corresponds to an active SIM 756 * 757 * @param sm An instance of the subscription manager so it is not recreated for each calling of 758 * this method. 759 * @param handle The handle for the phone account to check 760 * @return {@code true} If there is an active SIM for this phone account, 761 * {@code false} otherwise. 762 */ isPhoneAccountActive(SubscriptionManager sm, PhoneAccountHandle handle)763 public static boolean isPhoneAccountActive(SubscriptionManager sm, PhoneAccountHandle handle) { 764 return sm.getActiveSubscriptionInfo(Integer.parseInt(handle.getId())) != null; 765 } 766 getPstnConnectionServiceName()767 private static ComponentName getPstnConnectionServiceName() { 768 return PSTN_CONNECTION_SERVICE_COMPONENT; 769 } 770 getPhoneFromSubId(String subId)771 private static Phone getPhoneFromSubId(String subId) { 772 if (!TextUtils.isEmpty(subId)) { 773 for (Phone phone : PhoneFactory.getPhones()) { 774 String phoneSubId = Integer.toString(phone.getSubId()); 775 if (subId.equals(phoneSubId)) { 776 return phone; 777 } 778 } 779 } 780 return null; 781 } 782 783 /** 784 * Register ICC status for all phones. 785 */ registerIccStatus(Handler handler, int event)786 static final void registerIccStatus(Handler handler, int event) { 787 for (Phone phone : PhoneFactory.getPhones()) { 788 IccCard sim = phone.getIccCard(); 789 if (sim != null) { 790 if (VDBG) Log.v(LOG_TAG, "register for ICC status, phone " + phone.getPhoneId()); 791 sim.registerForNetworkLocked(handler, event, phone); 792 } 793 } 794 } 795 796 /** 797 * Register ICC status for all phones. 798 */ registerIccStatus(Handler handler, int event, int phoneId)799 static final void registerIccStatus(Handler handler, int event, int phoneId) { 800 Phone[] phones = PhoneFactory.getPhones(); 801 IccCard sim = phones[phoneId].getIccCard(); 802 if (sim != null) { 803 if (VDBG) { 804 Log.v(LOG_TAG, "register for ICC status, phone " + phones[phoneId].getPhoneId()); 805 } 806 sim.registerForNetworkLocked(handler, event, phones[phoneId]); 807 } 808 } 809 810 /** 811 * Unregister ICC status for a specific phone. 812 */ unregisterIccStatus(Handler handler, int phoneId)813 static final void unregisterIccStatus(Handler handler, int phoneId) { 814 Phone[] phones = PhoneFactory.getPhones(); 815 IccCard sim = phones[phoneId].getIccCard(); 816 if (sim != null) { 817 if (VDBG) { 818 Log.v(LOG_TAG, "unregister for ICC status, phone " + phones[phoneId].getPhoneId()); 819 } 820 sim.unregisterForNetworkLocked(handler); 821 } 822 } 823 824 /** 825 * Set the radio power on/off state for all phones. 826 * 827 * @param enabled true means on, false means off. 828 */ setRadioPower(boolean enabled)829 static final void setRadioPower(boolean enabled) { 830 for (Phone phone : PhoneFactory.getPhones()) { 831 phone.setRadioPower(enabled); 832 } 833 } 834 } 835