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