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.net.Uri; 26 import android.os.Handler; 27 import android.os.Message; 28 import android.os.PersistableBundle; 29 import android.telecom.PhoneAccount; 30 import android.telecom.PhoneAccountHandle; 31 import android.telecom.VideoProfile; 32 import android.telephony.CarrierConfigManager; 33 import android.telephony.PhoneNumberUtils; 34 import android.telephony.SubscriptionManager; 35 import android.text.TextUtils; 36 import android.util.Log; 37 import android.view.ContextThemeWrapper; 38 import android.view.KeyEvent; 39 import android.view.LayoutInflater; 40 import android.view.View; 41 import android.view.WindowManager; 42 import android.widget.EditText; 43 import android.widget.Toast; 44 45 import com.android.internal.telephony.Call; 46 import com.android.internal.telephony.CallManager; 47 import com.android.internal.telephony.CallStateException; 48 import com.android.internal.telephony.CallerInfo; 49 import com.android.internal.telephony.CallerInfoAsyncQuery; 50 import com.android.internal.telephony.Connection; 51 import com.android.internal.telephony.IccCard; 52 import com.android.internal.telephony.MmiCode; 53 import com.android.internal.telephony.Phone; 54 import com.android.internal.telephony.PhoneConstants; 55 import com.android.internal.telephony.PhoneFactory; 56 import com.android.internal.telephony.TelephonyCapabilities; 57 import com.android.phone.CallGatewayManager.RawGatewayInfo; 58 import com.android.phone.settings.SuppServicesUiUtil; 59 60 import java.util.Arrays; 61 import java.util.List; 62 63 /** 64 * Misc utilities for the Phone app. 65 */ 66 public class PhoneUtils { 67 public static final String EMERGENCY_ACCOUNT_HANDLE_ID = "E"; 68 private static final String LOG_TAG = "PhoneUtils"; 69 private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2); 70 71 // Do not check in with VDBG = true, since that may write PII to the system log. 72 private static final boolean VDBG = false; 73 74 // Return codes from placeCall() 75 public static final int CALL_STATUS_DIALED = 0; // The number was successfully dialed 76 public static final int CALL_STATUS_DIALED_MMI = 1; // The specified number was an MMI code 77 public static final int CALL_STATUS_FAILED = 2; // The call failed 78 79 // USSD string length for MMI operations 80 static final int MIN_USSD_LEN = 1; 81 static final int MAX_USSD_LEN = 160; 82 83 /** Define for not a special CNAP string */ 84 private static final int CNAP_SPECIAL_CASE_NO = -1; 85 86 /** 87 * Theme to use for dialogs displayed by utility methods in this class. This is needed 88 * because these dialogs are displayed using the application context, which does not resolve 89 * the dialog theme correctly. 90 */ 91 private static final int THEME = com.android.internal.R.style.Theme_DeviceDefault_Dialog_Alert; 92 93 /** USSD information used to aggregate all USSD messages */ 94 private static AlertDialog sUssdDialog = null; 95 private static StringBuilder sUssdMsg = new StringBuilder(); 96 97 private static final ComponentName PSTN_CONNECTION_SERVICE_COMPONENT = 98 new ComponentName("com.android.phone", 99 "com.android.services.telephony.TelephonyConnectionService"); 100 101 /** This class is never instantiated. */ PhoneUtils()102 private PhoneUtils() { 103 } 104 105 /** 106 * For a CDMA phone, advance the call state upon making a new 107 * outgoing call. 108 * 109 * <pre> 110 * IDLE -> SINGLE_ACTIVE 111 * or 112 * SINGLE_ACTIVE -> THRWAY_ACTIVE 113 * </pre> 114 * @param app The phone instance. 115 */ updateCdmaCallStateOnNewOutgoingCall(PhoneGlobals app, Connection connection)116 private static void updateCdmaCallStateOnNewOutgoingCall(PhoneGlobals app, 117 Connection connection) { 118 if (app.cdmaPhoneCallState.getCurrentCallState() == 119 CdmaPhoneCallState.PhoneCallState.IDLE) { 120 // This is the first outgoing call. Set the Phone Call State to ACTIVE 121 app.cdmaPhoneCallState.setCurrentCallState( 122 CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE); 123 } else { 124 // This is the second outgoing call. Set the Phone Call State to 3WAY 125 app.cdmaPhoneCallState.setCurrentCallState( 126 CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE); 127 128 // TODO: Remove this code. 129 //app.getCallModeler().setCdmaOutgoing3WayCall(connection); 130 } 131 } 132 133 /** 134 * @see placeCall below 135 */ placeCall(Context context, Phone phone, String number, Uri contactRef, boolean isEmergencyCall)136 public static int placeCall(Context context, Phone phone, String number, Uri contactRef, 137 boolean isEmergencyCall) { 138 return placeCall(context, phone, number, contactRef, isEmergencyCall, 139 CallGatewayManager.EMPTY_INFO, null); 140 } 141 142 /** 143 * Dial the number using the phone passed in. 144 * 145 * If the connection is establised, this method issues a sync call 146 * that may block to query the caller info. 147 * TODO: Change the logic to use the async query. 148 * 149 * @param context To perform the CallerInfo query. 150 * @param phone the Phone object. 151 * @param number to be dialed as requested by the user. This is 152 * NOT the phone number to connect to. It is used only to build the 153 * call card and to update the call log. See above for restrictions. 154 * @param contactRef that triggered the call. Typically a 'tel:' 155 * uri but can also be a 'content://contacts' one. 156 * @param isEmergencyCall indicates that whether or not this is an 157 * emergency call 158 * @param gatewayUri Is the address used to setup the connection, null 159 * if not using a gateway 160 * @param callGateway Class for setting gateway data on a successful call. 161 * 162 * @return either CALL_STATUS_DIALED or CALL_STATUS_FAILED 163 */ placeCall(Context context, Phone phone, String number, Uri contactRef, boolean isEmergencyCall, RawGatewayInfo gatewayInfo, CallGatewayManager callGateway)164 public static int placeCall(Context context, Phone phone, String number, Uri contactRef, 165 boolean isEmergencyCall, RawGatewayInfo gatewayInfo, CallGatewayManager callGateway) { 166 final Uri gatewayUri = gatewayInfo.gatewayUri; 167 168 if (VDBG) { 169 log("placeCall()... number: '" + number + "'" 170 + ", GW:'" + gatewayUri + "'" 171 + ", contactRef:" + contactRef 172 + ", isEmergencyCall: " + isEmergencyCall); 173 } else { 174 log("placeCall()... number: " + toLogSafePhoneNumber(number) 175 + ", GW: " + (gatewayUri != null ? "non-null" : "null") 176 + ", emergency? " + isEmergencyCall); 177 } 178 final PhoneGlobals app = PhoneGlobals.getInstance(); 179 180 boolean useGateway = false; 181 if (null != gatewayUri && 182 !isEmergencyCall && 183 PhoneUtils.isRoutableViaGateway(number)) { // Filter out MMI, OTA and other codes. 184 useGateway = true; 185 } 186 187 int status = CALL_STATUS_DIALED; 188 Connection connection; 189 String numberToDial; 190 if (useGateway) { 191 // TODO: 'tel' should be a constant defined in framework base 192 // somewhere (it is in webkit.) 193 if (null == gatewayUri || !PhoneAccount.SCHEME_TEL.equals(gatewayUri.getScheme())) { 194 Log.e(LOG_TAG, "Unsupported URL:" + gatewayUri); 195 return CALL_STATUS_FAILED; 196 } 197 198 // We can use getSchemeSpecificPart because we don't allow # 199 // in the gateway numbers (treated a fragment delim.) However 200 // if we allow more complex gateway numbers sequence (with 201 // passwords or whatnot) that use #, this may break. 202 // TODO: Need to support MMI codes. 203 numberToDial = gatewayUri.getSchemeSpecificPart(); 204 } else { 205 numberToDial = number; 206 } 207 208 try { 209 connection = app.mCM.dial(phone, numberToDial, VideoProfile.STATE_AUDIO_ONLY); 210 } catch (CallStateException ex) { 211 // CallStateException means a new outgoing call is not currently 212 // possible: either no more call slots exist, or there's another 213 // call already in the process of dialing or ringing. 214 Log.w(LOG_TAG, "Exception from app.mCM.dial()", ex); 215 return CALL_STATUS_FAILED; 216 217 // Note that it's possible for CallManager.dial() to return 218 // null *without* throwing an exception; that indicates that 219 // we dialed an MMI (see below). 220 } 221 222 int phoneType = phone.getPhoneType(); 223 224 // On GSM phones, null is returned for MMI codes 225 if (null == connection) { 226 status = CALL_STATUS_FAILED; 227 } else { 228 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) { 229 updateCdmaCallStateOnNewOutgoingCall(app, connection); 230 } 231 232 if (gatewayUri == null) { 233 // phone.dial() succeeded: we're now in a normal phone call. 234 // attach the URI to the CallerInfo Object if it is there, 235 // otherwise just attach the Uri Reference. 236 // if the uri does not have a "content" scheme, then we treat 237 // it as if it does NOT have a unique reference. 238 String content = context.getContentResolver().SCHEME_CONTENT; 239 if ((contactRef != null) && (contactRef.getScheme().equals(content))) { 240 Object userDataObject = connection.getUserData(); 241 if (userDataObject == null) { 242 connection.setUserData(contactRef); 243 } else { 244 // TODO: This branch is dead code, we have 245 // just created the connection which has 246 // no user data (null) by default. 247 if (userDataObject instanceof CallerInfo) { 248 ((CallerInfo) userDataObject).contactRefUri = contactRef; 249 } else { 250 ((CallerInfoToken) userDataObject).currentInfo.contactRefUri = 251 contactRef; 252 } 253 } 254 } 255 } 256 257 startGetCallerInfo(context, connection, null, null, gatewayInfo); 258 } 259 260 return status; 261 } 262 toLogSafePhoneNumber(String number)263 /* package */ static String toLogSafePhoneNumber(String number) { 264 // For unknown number, log empty string. 265 if (number == null) { 266 return ""; 267 } 268 269 if (VDBG) { 270 // When VDBG is true we emit PII. 271 return number; 272 } 273 274 // Do exactly same thing as Uri#toSafeString() does, which will enable us to compare 275 // sanitized phone numbers. 276 StringBuilder builder = new StringBuilder(); 277 for (int i = 0; i < number.length(); i++) { 278 char c = number.charAt(i); 279 if (c == '-' || c == '@' || c == '.') { 280 builder.append(c); 281 } else { 282 builder.append('x'); 283 } 284 } 285 return builder.toString(); 286 } 287 288 /** 289 * Handle the MMIInitiate message and put up an alert that lets 290 * the user cancel the operation, if applicable. 291 * 292 * @param context context to get strings. 293 * @param mmiCode the MmiCode object being started. 294 * @param buttonCallbackMessage message to post when button is clicked. 295 * @param previousAlert a previous alert used in this activity. 296 * @return the dialog handle 297 */ displayMMIInitiate(Context context, MmiCode mmiCode, Message buttonCallbackMessage, Dialog previousAlert)298 static Dialog displayMMIInitiate(Context context, 299 MmiCode mmiCode, 300 Message buttonCallbackMessage, 301 Dialog previousAlert) { 302 log("displayMMIInitiate: " + android.telecom.Log.pii(mmiCode.toString())); 303 if (previousAlert != null) { 304 previousAlert.dismiss(); 305 } 306 307 // The UI paradigm we are using now requests that all dialogs have 308 // user interaction, and that any other messages to the user should 309 // be by way of Toasts. 310 // 311 // In adhering to this request, all MMI initiating "OK" dialogs 312 // (non-cancelable MMIs) that end up being closed when the MMI 313 // completes (thereby showing a completion dialog) are being 314 // replaced with Toasts. 315 // 316 // As a side effect, moving to Toasts for the non-cancelable MMIs 317 // also means that buttonCallbackMessage (which was tied into "OK") 318 // is no longer invokable for these dialogs. This is not a problem 319 // since the only callback messages we supported were for cancelable 320 // MMIs anyway. 321 // 322 // A cancelable MMI is really just a USSD request. The term 323 // "cancelable" here means that we can cancel the request when the 324 // system prompts us for a response, NOT while the network is 325 // processing the MMI request. Any request to cancel a USSD while 326 // the network is NOT ready for a response may be ignored. 327 // 328 // With this in mind, we replace the cancelable alert dialog with 329 // a progress dialog, displayed until we receive a request from 330 // the the network. For more information, please see the comments 331 // in the displayMMIComplete() method below. 332 // 333 // Anything that is NOT a USSD request is a normal MMI request, 334 // which will bring up a toast (desribed above). 335 336 boolean isCancelable = (mmiCode != null) && mmiCode.isCancelable(); 337 338 if (!isCancelable) { 339 log("displayMMIInitiate: not a USSD code, displaying status toast."); 340 CharSequence text = context.getText(R.string.mmiStarted); 341 Toast.makeText(context, text, Toast.LENGTH_SHORT) 342 .show(); 343 return null; 344 } else { 345 log("displayMMIInitiate: running USSD code, displaying intermediate progress."); 346 347 // create the indeterminate progress dialog and display it. 348 ProgressDialog pd = new ProgressDialog(context, THEME); 349 pd.setMessage(context.getText(R.string.ussdRunning)); 350 pd.setCancelable(false); 351 pd.setIndeterminate(true); 352 pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); 353 354 pd.show(); 355 356 return pd; 357 } 358 359 } 360 361 /** 362 * Handle the MMIComplete message and fire off an intent to display 363 * the message. 364 * 365 * @param context context to get strings. 366 * @param mmiCode MMI result. 367 * @param previousAlert a previous alert used in this activity. 368 */ displayMMIComplete(final Phone phone, Context context, final MmiCode mmiCode, Message dismissCallbackMessage, AlertDialog previousAlert)369 static void displayMMIComplete(final Phone phone, Context context, final MmiCode mmiCode, 370 Message dismissCallbackMessage, 371 AlertDialog previousAlert) { 372 final PhoneGlobals app = PhoneGlobals.getInstance(); 373 CharSequence text; 374 int title = 0; // title for the progress dialog, if needed. 375 MmiCode.State state = mmiCode.getState(); 376 377 log("displayMMIComplete: state=" + state); 378 379 switch (state) { 380 case PENDING: 381 // USSD code asking for feedback from user. 382 text = mmiCode.getMessage(); 383 log("displayMMIComplete: using text from PENDING MMI message: '" + text + "'"); 384 break; 385 case CANCELLED: 386 text = null; 387 break; 388 case COMPLETE: 389 PersistableBundle b = null; 390 if (SubscriptionManager.isValidSubscriptionId(phone.getSubId())) { 391 b = app.getCarrierConfigForSubId( 392 phone.getSubId()); 393 } else { 394 b = app.getCarrierConfig(); 395 } 396 397 if (b.getBoolean(CarrierConfigManager.KEY_USE_CALLER_ID_USSD_BOOL)) { 398 text = SuppServicesUiUtil.handleCallerIdUssdResponse(app, context, phone, 399 mmiCode); 400 if (mmiCode.getMessage() != null && !text.equals(mmiCode.getMessage())) { 401 break; 402 } 403 } 404 405 if (app.getPUKEntryActivity() != null) { 406 // if an attempt to unPUK the device was made, we specify 407 // the title and the message here. 408 title = com.android.internal.R.string.PinMmi; 409 text = context.getText(R.string.puk_unlocked); 410 break; 411 } 412 // All other conditions for the COMPLETE mmi state will cause 413 // the case to fall through to message logic in common with 414 // the FAILED case. 415 416 case FAILED: 417 text = mmiCode.getMessage(); 418 log("displayMMIComplete (failed): using text from MMI message: '" + text + "'"); 419 break; 420 default: 421 throw new IllegalStateException("Unexpected MmiCode state: " + state); 422 } 423 424 if (previousAlert != null) { 425 previousAlert.dismiss(); 426 } 427 428 // Check to see if a UI exists for the PUK activation. If it does 429 // exist, then it indicates that we're trying to unblock the PUK. 430 if ((app.getPUKEntryActivity() != null) && (state == MmiCode.State.COMPLETE)) { 431 if (DBG) log("displaying PUK unblocking progress dialog."); 432 433 // create the progress dialog, make sure the flags and type are 434 // set correctly. 435 ProgressDialog pd = new ProgressDialog(app, THEME); 436 pd.setTitle(title); 437 pd.setMessage(text); 438 pd.setCancelable(false); 439 pd.setIndeterminate(true); 440 pd.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); 441 pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); 442 443 // display the dialog 444 pd.show(); 445 446 // indicate to the Phone app that the progress dialog has 447 // been assigned for the PUK unlock / SIM READY process. 448 app.setPukEntryProgressDialog(pd); 449 450 } else if ((app.getPUKEntryActivity() != null) && (state == MmiCode.State.FAILED)) { 451 createUssdDialog(app, context, text, 452 WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 453 // In case of failure to unlock, we'll need to reset the 454 // PUK unlock activity, so that the user may try again. 455 app.setPukEntryActivity(null); 456 } else { 457 // In case of failure to unlock, we'll need to reset the 458 // PUK unlock activity, so that the user may try again. 459 if (app.getPUKEntryActivity() != null) { 460 app.setPukEntryActivity(null); 461 } 462 463 // A USSD in a pending state means that it is still 464 // interacting with the user. 465 if (state != MmiCode.State.PENDING) { 466 createUssdDialog(app, context, text, 467 WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); 468 } else { 469 log("displayMMIComplete: USSD code has requested user input. Constructing input " 470 + "dialog."); 471 472 // USSD MMI code that is interacting with the user. The 473 // basic set of steps is this: 474 // 1. User enters a USSD request 475 // 2. We recognize the request and displayMMIInitiate 476 // (above) creates a progress dialog. 477 // 3. Request returns and we get a PENDING or COMPLETE 478 // message. 479 // 4. These MMI messages are caught in the PhoneApp 480 // (onMMIComplete) and the InCallScreen 481 // (mHandler.handleMessage) which bring up this dialog 482 // and closes the original progress dialog, 483 // respectively. 484 // 5. If the message is anything other than PENDING, 485 // we are done, and the alert dialog (directly above) 486 // displays the outcome. 487 // 6. If the network is requesting more information from 488 // the user, the MMI will be in a PENDING state, and 489 // we display this dialog with the message. 490 // 7. User input, or cancel requests result in a return 491 // to step 1. Keep in mind that this is the only 492 // time that a USSD should be canceled. 493 494 // inflate the layout with the scrolling text area for the dialog. 495 ContextThemeWrapper contextThemeWrapper = 496 new ContextThemeWrapper(context, R.style.DialerAlertDialogTheme); 497 LayoutInflater inflater = (LayoutInflater) contextThemeWrapper.getSystemService( 498 Context.LAYOUT_INFLATER_SERVICE); 499 View dialogView = inflater.inflate(R.layout.dialog_ussd_response, null); 500 501 // get the input field. 502 final EditText inputText = (EditText) dialogView.findViewById(R.id.input_field); 503 504 // specify the dialog's click listener, with SEND and CANCEL logic. 505 final DialogInterface.OnClickListener mUSSDDialogListener = 506 new DialogInterface.OnClickListener() { 507 public void onClick(DialogInterface dialog, int whichButton) { 508 switch (whichButton) { 509 case DialogInterface.BUTTON_POSITIVE: 510 // As per spec 24.080, valid length of ussd string 511 // is 1 - 160. If length is out of the range then 512 // display toast message & Cancel MMI operation. 513 if (inputText.length() < MIN_USSD_LEN 514 || inputText.length() > MAX_USSD_LEN) { 515 Toast.makeText(app, 516 app.getResources().getString(R.string.enter_input, 517 MIN_USSD_LEN, MAX_USSD_LEN), 518 Toast.LENGTH_LONG).show(); 519 if (mmiCode.isCancelable()) { 520 mmiCode.cancel(); 521 } 522 } else { 523 phone.sendUssdResponse(inputText.getText().toString()); 524 } 525 break; 526 case DialogInterface.BUTTON_NEGATIVE: 527 if (mmiCode.isCancelable()) { 528 mmiCode.cancel(); 529 } 530 break; 531 } 532 } 533 }; 534 535 // build the dialog 536 final AlertDialog newDialog = new AlertDialog.Builder(contextThemeWrapper) 537 .setMessage(text) 538 .setView(dialogView) 539 .setPositiveButton(R.string.send_button, mUSSDDialogListener) 540 .setNegativeButton(R.string.cancel, mUSSDDialogListener) 541 .setCancelable(false) 542 .create(); 543 544 // attach the key listener to the dialog's input field and make 545 // sure focus is set. 546 final View.OnKeyListener mUSSDDialogInputListener = 547 new View.OnKeyListener() { 548 public boolean onKey(View v, int keyCode, KeyEvent event) { 549 switch (keyCode) { 550 case KeyEvent.KEYCODE_CALL: 551 case KeyEvent.KEYCODE_ENTER: 552 if(event.getAction() == KeyEvent.ACTION_DOWN) { 553 phone.sendUssdResponse(inputText.getText().toString()); 554 newDialog.dismiss(); 555 } 556 return true; 557 } 558 return false; 559 } 560 }; 561 inputText.setOnKeyListener(mUSSDDialogInputListener); 562 inputText.requestFocus(); 563 564 // set the window properties of the dialog 565 newDialog.getWindow().setType( 566 WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); 567 newDialog.getWindow().addFlags( 568 WindowManager.LayoutParams.FLAG_DIM_BEHIND); 569 570 // now show the dialog! 571 newDialog.show(); 572 573 newDialog.getButton(DialogInterface.BUTTON_POSITIVE) 574 .setTextColor(context.getResources().getColor(R.color.dialer_theme_color)); 575 newDialog.getButton(DialogInterface.BUTTON_NEGATIVE) 576 .setTextColor(context.getResources().getColor(R.color.dialer_theme_color)); 577 } 578 } 579 } 580 581 /** 582 * It displays the message dialog for user about the mmi code result message. 583 * 584 * @param app This is {@link PhoneGlobals} 585 * @param context Context to get strings. 586 * @param text This is message's result. 587 * @param windowType The new window type. {@link WindowManager.LayoutParams}. 588 */ createUssdDialog(PhoneGlobals app, Context context, CharSequence text, int windowType)589 public static void createUssdDialog(PhoneGlobals app, Context context, CharSequence text, 590 int windowType) { 591 log("displayMMIComplete: MMI code has finished running."); 592 593 log("displayMMIComplete: Extended NW displayMMIInitiate (" + text + ")"); 594 if (text == null || text.length() == 0) { 595 return; 596 } 597 598 // displaying system alert dialog on the screen instead of 599 // using another activity to display the message. This 600 // places the message at the forefront of the UI. 601 602 if (sUssdDialog == null) { 603 sUssdDialog = new AlertDialog.Builder(context, THEME) 604 .setPositiveButton(R.string.ok, null) 605 .setCancelable(true) 606 .setOnDismissListener(new DialogInterface.OnDismissListener() { 607 @Override 608 public void onDismiss(DialogInterface dialog) { 609 sUssdMsg.setLength(0); 610 } 611 }) 612 .create(); 613 614 sUssdDialog.getWindow().setType(windowType); 615 sUssdDialog.getWindow().addFlags( 616 WindowManager.LayoutParams.FLAG_DIM_BEHIND); 617 } 618 if (sUssdMsg.length() != 0) { 619 sUssdMsg 620 .insert(0, "\n") 621 .insert(0, app.getResources().getString(R.string.ussd_dialog_sep)) 622 .insert(0, "\n"); 623 } 624 sUssdMsg.insert(0, text); 625 sUssdDialog.setMessage(sUssdMsg.toString()); 626 sUssdDialog.show(); 627 } 628 629 /** 630 * Cancels the current pending MMI operation, if applicable. 631 * @return true if we canceled an MMI operation, or false 632 * if the current pending MMI wasn't cancelable 633 * or if there was no current pending MMI at all. 634 * 635 * @see displayMMIInitiate 636 */ cancelMmiCode(Phone phone)637 static boolean cancelMmiCode(Phone phone) { 638 List<? extends MmiCode> pendingMmis = phone.getPendingMmiCodes(); 639 int count = pendingMmis.size(); 640 if (DBG) log("cancelMmiCode: num pending MMIs = " + count); 641 642 boolean canceled = false; 643 if (count > 0) { 644 // assume that we only have one pending MMI operation active at a time. 645 // I don't think it's possible to enter multiple MMI codes concurrently 646 // in the phone UI, because during the MMI operation, an Alert panel 647 // is displayed, which prevents more MMI code from being entered. 648 MmiCode mmiCode = pendingMmis.get(0); 649 if (mmiCode.isCancelable()) { 650 mmiCode.cancel(); 651 canceled = true; 652 } 653 } 654 return canceled; 655 } 656 657 /** 658 * Returns the caller-id info corresponding to the specified Connection. 659 * (This is just a simple wrapper around CallerInfo.getCallerInfo(): we 660 * extract a phone number from the specified Connection, and feed that 661 * number into CallerInfo.getCallerInfo().) 662 * 663 * The returned CallerInfo may be null in certain error cases, like if the 664 * specified Connection was null, or if we weren't able to get a valid 665 * phone number from the Connection. 666 * 667 * Finally, if the getCallerInfo() call did succeed, we save the resulting 668 * CallerInfo object in the "userData" field of the Connection. 669 * 670 * NOTE: This API should be avoided, with preference given to the 671 * asynchronous startGetCallerInfo API. 672 */ getCallerInfo(Context context, Connection c)673 static CallerInfo getCallerInfo(Context context, Connection c) { 674 CallerInfo info = null; 675 676 if (c != null) { 677 //See if there is a URI attached. If there is, this means 678 //that there is no CallerInfo queried yet, so we'll need to 679 //replace the URI with a full CallerInfo object. 680 Object userDataObject = c.getUserData(); 681 if (userDataObject instanceof Uri) { 682 info = CallerInfo.getCallerInfo(context, (Uri) userDataObject); 683 if (info != null) { 684 c.setUserData(info); 685 } 686 } else { 687 if (userDataObject instanceof CallerInfoToken) { 688 //temporary result, while query is running 689 info = ((CallerInfoToken) userDataObject).currentInfo; 690 } else { 691 //final query result 692 info = (CallerInfo) userDataObject; 693 } 694 if (info == null) { 695 // No URI, or Existing CallerInfo, so we'll have to make do with 696 // querying a new CallerInfo using the connection's phone number. 697 String number = c.getAddress(); 698 699 if (DBG) log("getCallerInfo: number = " + toLogSafePhoneNumber(number)); 700 701 if (!TextUtils.isEmpty(number)) { 702 info = CallerInfo.getCallerInfo(context, number); 703 if (info != null) { 704 c.setUserData(info); 705 } 706 } 707 } 708 } 709 } 710 return info; 711 } 712 713 /** 714 * Class returned by the startGetCallerInfo call to package a temporary 715 * CallerInfo Object, to be superceded by the CallerInfo Object passed 716 * into the listener when the query with token mAsyncQueryToken is complete. 717 */ 718 public static class CallerInfoToken { 719 /**indicates that there will no longer be updates to this request.*/ 720 public boolean isFinal; 721 722 public CallerInfo currentInfo; 723 public CallerInfoAsyncQuery asyncQuery; 724 } 725 726 /** 727 * place a temporary callerinfo object in the hands of the caller and notify 728 * caller when the actual query is done. 729 */ startGetCallerInfo(Context context, Connection c, CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie, RawGatewayInfo info)730 static CallerInfoToken startGetCallerInfo(Context context, Connection c, 731 CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie, 732 RawGatewayInfo info) { 733 CallerInfoToken cit; 734 735 if (c == null) { 736 //TODO: perhaps throw an exception here. 737 cit = new CallerInfoToken(); 738 cit.asyncQuery = null; 739 return cit; 740 } 741 742 Object userDataObject = c.getUserData(); 743 744 // There are now 3 states for the Connection's userData object: 745 // 746 // (1) Uri - query has not been executed yet 747 // 748 // (2) CallerInfoToken - query is executing, but has not completed. 749 // 750 // (3) CallerInfo - query has executed. 751 // 752 // In each case we have slightly different behaviour: 753 // 1. If the query has not been executed yet (Uri or null), we start 754 // query execution asynchronously, and note it by attaching a 755 // CallerInfoToken as the userData. 756 // 2. If the query is executing (CallerInfoToken), we've essentially 757 // reached a state where we've received multiple requests for the 758 // same callerInfo. That means that once the query is complete, 759 // we'll need to execute the additional listener requested. 760 // 3. If the query has already been executed (CallerInfo), we just 761 // return the CallerInfo object as expected. 762 // 4. Regarding isFinal - there are cases where the CallerInfo object 763 // will not be attached, like when the number is empty (caller id 764 // blocking). This flag is used to indicate that the 765 // CallerInfoToken object is going to be permanent since no 766 // query results will be returned. In the case where a query 767 // has been completed, this flag is used to indicate to the caller 768 // that the data will not be updated since it is valid. 769 // 770 // Note: For the case where a number is NOT retrievable, we leave 771 // the CallerInfo as null in the CallerInfoToken. This is 772 // something of a departure from the original code, since the old 773 // code manufactured a CallerInfo object regardless of the query 774 // outcome. From now on, we will append an empty CallerInfo 775 // object, to mirror previous behaviour, and to avoid Null Pointer 776 // Exceptions. 777 778 if (userDataObject instanceof Uri) { 779 // State (1): query has not been executed yet 780 781 //create a dummy callerinfo, populate with what we know from URI. 782 cit = new CallerInfoToken(); 783 cit.currentInfo = new CallerInfo(); 784 cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context, 785 (Uri) userDataObject, sCallerInfoQueryListener, c); 786 cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie); 787 cit.isFinal = false; 788 789 c.setUserData(cit); 790 791 if (DBG) log("startGetCallerInfo: query based on Uri: " + userDataObject); 792 793 } else if (userDataObject == null) { 794 // No URI, or Existing CallerInfo, so we'll have to make do with 795 // querying a new CallerInfo using the connection's phone number. 796 String number = c.getAddress(); 797 798 if (info != null && info != CallGatewayManager.EMPTY_INFO) { 799 // Gateway number, the connection number is actually the gateway number. 800 // need to lookup via dialed number. 801 number = info.trueNumber; 802 } 803 804 if (DBG) { 805 log("PhoneUtils.startGetCallerInfo: new query for phone number..."); 806 log("- number (address): " + toLogSafePhoneNumber(number)); 807 log("- c: " + c); 808 log("- phone: " + c.getCall().getPhone()); 809 int phoneType = c.getCall().getPhone().getPhoneType(); 810 log("- phoneType: " + phoneType); 811 switch (phoneType) { 812 case PhoneConstants.PHONE_TYPE_NONE: log(" ==> PHONE_TYPE_NONE"); break; 813 case PhoneConstants.PHONE_TYPE_GSM: log(" ==> PHONE_TYPE_GSM"); break; 814 case PhoneConstants.PHONE_TYPE_IMS: log(" ==> PHONE_TYPE_IMS"); break; 815 case PhoneConstants.PHONE_TYPE_CDMA: log(" ==> PHONE_TYPE_CDMA"); break; 816 case PhoneConstants.PHONE_TYPE_SIP: log(" ==> PHONE_TYPE_SIP"); break; 817 case PhoneConstants.PHONE_TYPE_THIRD_PARTY: 818 log(" ==> PHONE_TYPE_THIRD_PARTY"); 819 break; 820 default: log(" ==> Unknown phone type"); break; 821 } 822 } 823 824 cit = new CallerInfoToken(); 825 cit.currentInfo = new CallerInfo(); 826 827 // Store CNAP information retrieved from the Connection (we want to do this 828 // here regardless of whether the number is empty or not). 829 cit.currentInfo.cnapName = c.getCnapName(); 830 cit.currentInfo.name = cit.currentInfo.cnapName; // This can still get overwritten 831 // by ContactInfo later 832 cit.currentInfo.numberPresentation = c.getNumberPresentation(); 833 cit.currentInfo.namePresentation = c.getCnapNamePresentation(); 834 835 if (VDBG) { 836 log("startGetCallerInfo: number = " + number); 837 log("startGetCallerInfo: CNAP Info from FW(1): name=" 838 + cit.currentInfo.cnapName 839 + ", Name/Number Pres=" + cit.currentInfo.numberPresentation); 840 } 841 842 // handling case where number is null (caller id hidden) as well. 843 if (!TextUtils.isEmpty(number)) { 844 // Check for special CNAP cases and modify the CallerInfo accordingly 845 // to be sure we keep the right information to display/log later 846 number = modifyForSpecialCnapCases(context, cit.currentInfo, number, 847 cit.currentInfo.numberPresentation); 848 849 cit.currentInfo.phoneNumber = number; 850 // For scenarios where we may receive a valid number from the network but a 851 // restricted/unavailable presentation, we do not want to perform a contact query 852 // (see note on isFinal above). So we set isFinal to true here as well. 853 if (cit.currentInfo.numberPresentation != PhoneConstants.PRESENTATION_ALLOWED) { 854 cit.isFinal = true; 855 } else { 856 if (DBG) log("==> Actually starting CallerInfoAsyncQuery.startQuery()..."); 857 cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context, 858 number, sCallerInfoQueryListener, c); 859 cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie); 860 cit.isFinal = false; 861 } 862 } else { 863 // This is the case where we are querying on a number that 864 // is null or empty, like a caller whose caller id is 865 // blocked or empty (CLIR). The previous behaviour was to 866 // throw a null CallerInfo object back to the user, but 867 // this departure is somewhat cleaner. 868 if (DBG) log("startGetCallerInfo: No query to start, send trivial reply."); 869 cit.isFinal = true; // please see note on isFinal, above. 870 } 871 872 c.setUserData(cit); 873 874 if (DBG) { 875 log("startGetCallerInfo: query based on number: " + toLogSafePhoneNumber(number)); 876 } 877 878 } else if (userDataObject instanceof CallerInfoToken) { 879 // State (2): query is executing, but has not completed. 880 881 // just tack on this listener to the queue. 882 cit = (CallerInfoToken) userDataObject; 883 884 // handling case where number is null (caller id hidden) as well. 885 if (cit.asyncQuery != null) { 886 cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie); 887 888 if (DBG) log("startGetCallerInfo: query already running, adding listener: " + 889 listener.getClass().toString()); 890 } else { 891 // handling case where number/name gets updated later on by the network 892 String updatedNumber = c.getAddress(); 893 894 if (info != null) { 895 // Gateway number, the connection number is actually the gateway number. 896 // need to lookup via dialed number. 897 updatedNumber = info.trueNumber; 898 } 899 900 if (DBG) { 901 log("startGetCallerInfo: updatedNumber initially = " 902 + toLogSafePhoneNumber(updatedNumber)); 903 } 904 if (!TextUtils.isEmpty(updatedNumber)) { 905 // Store CNAP information retrieved from the Connection 906 cit.currentInfo.cnapName = c.getCnapName(); 907 // This can still get overwritten by ContactInfo 908 cit.currentInfo.name = cit.currentInfo.cnapName; 909 cit.currentInfo.numberPresentation = c.getNumberPresentation(); 910 cit.currentInfo.namePresentation = c.getCnapNamePresentation(); 911 912 updatedNumber = modifyForSpecialCnapCases(context, cit.currentInfo, 913 updatedNumber, cit.currentInfo.numberPresentation); 914 915 cit.currentInfo.phoneNumber = updatedNumber; 916 if (DBG) { 917 log("startGetCallerInfo: updatedNumber=" 918 + toLogSafePhoneNumber(updatedNumber)); 919 } 920 if (VDBG) { 921 log("startGetCallerInfo: CNAP Info from FW(2): name=" 922 + cit.currentInfo.cnapName 923 + ", Name/Number Pres=" + cit.currentInfo.numberPresentation); 924 } else if (DBG) { 925 log("startGetCallerInfo: CNAP Info from FW(2)"); 926 } 927 // For scenarios where we may receive a valid number from the network but a 928 // restricted/unavailable presentation, we do not want to perform a contact query 929 // (see note on isFinal above). So we set isFinal to true here as well. 930 if (cit.currentInfo.numberPresentation != PhoneConstants.PRESENTATION_ALLOWED) { 931 cit.isFinal = true; 932 } else { 933 cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context, 934 updatedNumber, sCallerInfoQueryListener, c); 935 cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie); 936 cit.isFinal = false; 937 } 938 } else { 939 if (DBG) log("startGetCallerInfo: No query to attach to, send trivial reply."); 940 if (cit.currentInfo == null) { 941 cit.currentInfo = new CallerInfo(); 942 } 943 // Store CNAP information retrieved from the Connection 944 cit.currentInfo.cnapName = c.getCnapName(); // This can still get 945 // overwritten by ContactInfo 946 cit.currentInfo.name = cit.currentInfo.cnapName; 947 cit.currentInfo.numberPresentation = c.getNumberPresentation(); 948 cit.currentInfo.namePresentation = c.getCnapNamePresentation(); 949 950 if (VDBG) { 951 log("startGetCallerInfo: CNAP Info from FW(3): name=" 952 + cit.currentInfo.cnapName 953 + ", Name/Number Pres=" + cit.currentInfo.numberPresentation); 954 } else if (DBG) { 955 log("startGetCallerInfo: CNAP Info from FW(3)"); 956 } 957 cit.isFinal = true; // please see note on isFinal, above. 958 } 959 } 960 } else { 961 // State (3): query is complete. 962 963 // The connection's userDataObject is a full-fledged 964 // CallerInfo instance. Wrap it in a CallerInfoToken and 965 // return it to the user. 966 967 cit = new CallerInfoToken(); 968 cit.currentInfo = (CallerInfo) userDataObject; 969 cit.asyncQuery = null; 970 cit.isFinal = true; 971 // since the query is already done, call the listener. 972 if (DBG) log("startGetCallerInfo: query already done, returning CallerInfo"); 973 if (DBG) log("==> cit.currentInfo = " + cit.currentInfo); 974 } 975 return cit; 976 } 977 978 /** 979 * Static CallerInfoAsyncQuery.OnQueryCompleteListener instance that 980 * we use with all our CallerInfoAsyncQuery.startQuery() requests. 981 */ 982 private static final int QUERY_TOKEN = -1; 983 static CallerInfoAsyncQuery.OnQueryCompleteListener sCallerInfoQueryListener = 984 new CallerInfoAsyncQuery.OnQueryCompleteListener () { 985 /** 986 * When the query completes, we stash the resulting CallerInfo 987 * object away in the Connection's "userData" (where it will 988 * later be retrieved by the in-call UI.) 989 */ 990 public void onQueryComplete(int token, Object cookie, CallerInfo ci) { 991 if (DBG) log("query complete, updating connection.userdata"); 992 Connection conn = (Connection) cookie; 993 994 // Added a check if CallerInfo is coming from ContactInfo or from Connection. 995 // If no ContactInfo, then we want to use CNAP information coming from network 996 if (DBG) log("- onQueryComplete: CallerInfo:" + ci); 997 if (ci.contactExists || ci.isEmergencyNumber() || ci.isVoiceMailNumber()) { 998 // If the number presentation has not been set by 999 // the ContactInfo, use the one from the 1000 // connection. 1001 1002 // TODO: Need a new util method to merge the info 1003 // from the Connection in a CallerInfo object. 1004 // Here 'ci' is a new CallerInfo instance read 1005 // from the DB. It has lost all the connection 1006 // info preset before the query (see PhoneUtils 1007 // line 1334). We should have a method to merge 1008 // back into this new instance the info from the 1009 // connection object not set by the DB. If the 1010 // Connection already has a CallerInfo instance in 1011 // userData, then we could use this instance to 1012 // fill 'ci' in. The same routine could be used in 1013 // PhoneUtils. 1014 if (0 == ci.numberPresentation) { 1015 ci.numberPresentation = conn.getNumberPresentation(); 1016 } 1017 } else { 1018 // No matching contact was found for this number. 1019 // Return a new CallerInfo based solely on the CNAP 1020 // information from the network. 1021 1022 CallerInfo newCi = getCallerInfo(null, conn); 1023 1024 // ...but copy over the (few) things we care about 1025 // from the original CallerInfo object: 1026 if (newCi != null) { 1027 newCi.phoneNumber = ci.phoneNumber; // To get formatted phone number 1028 newCi.geoDescription = ci.geoDescription; // To get geo description string 1029 ci = newCi; 1030 } 1031 } 1032 1033 if (DBG) log("==> Stashing CallerInfo " + ci + " into the connection..."); 1034 conn.setUserData(ci); 1035 } 1036 }; 1037 1038 1039 /** 1040 * Returns a single "name" for the specified given a CallerInfo object. 1041 * If the name is null, return defaultString as the default value, usually 1042 * context.getString(R.string.unknown). 1043 */ getCompactNameFromCallerInfo(CallerInfo ci, Context context)1044 static String getCompactNameFromCallerInfo(CallerInfo ci, Context context) { 1045 if (DBG) log("getCompactNameFromCallerInfo: info = " + ci); 1046 1047 String compactName = null; 1048 if (ci != null) { 1049 if (TextUtils.isEmpty(ci.name)) { 1050 // Perform any modifications for special CNAP cases to 1051 // the phone number being displayed, if applicable. 1052 compactName = modifyForSpecialCnapCases(context, ci, ci.phoneNumber, 1053 ci.numberPresentation); 1054 } else { 1055 // Don't call modifyForSpecialCnapCases on regular name. See b/2160795. 1056 compactName = ci.name; 1057 } 1058 } 1059 1060 if ((compactName == null) || (TextUtils.isEmpty(compactName))) { 1061 // If we're still null/empty here, then check if we have a presentation 1062 // string that takes precedence that we could return, otherwise display 1063 // "unknown" string. 1064 if (ci != null && ci.numberPresentation == PhoneConstants.PRESENTATION_RESTRICTED) { 1065 compactName = context.getString(R.string.private_num); 1066 } else if (ci != null && ci.numberPresentation == PhoneConstants.PRESENTATION_PAYPHONE) { 1067 compactName = context.getString(R.string.payphone); 1068 } else { 1069 compactName = context.getString(R.string.unknown); 1070 } 1071 } 1072 if (VDBG) log("getCompactNameFromCallerInfo: compactName=" + compactName); 1073 return compactName; 1074 } 1075 isInEmergencyCall(CallManager cm)1076 static boolean isInEmergencyCall(CallManager cm) { 1077 Call fgCall = cm.getActiveFgCall(); 1078 // isIdle includes checks for the DISCONNECTING/DISCONNECTED state. 1079 if(!fgCall.isIdle()) { 1080 for (Connection cn : fgCall.getConnections()) { 1081 if (PhoneNumberUtils.isLocalEmergencyNumber(PhoneGlobals.getInstance(), 1082 cn.getAddress())) { 1083 return true; 1084 } 1085 } 1086 } 1087 return false; 1088 } 1089 1090 // 1091 // Misc UI policy helper functions 1092 // 1093 1094 /** 1095 * Based on the input CNAP number string, 1096 * @return _RESTRICTED or _UNKNOWN for all the special CNAP strings. 1097 * Otherwise, return CNAP_SPECIAL_CASE_NO. 1098 */ checkCnapSpecialCases(String n)1099 private static int checkCnapSpecialCases(String n) { 1100 if (n.equals("PRIVATE") || 1101 n.equals("P") || 1102 n.equals("RES")) { 1103 if (DBG) log("checkCnapSpecialCases, PRIVATE string: " + n); 1104 return PhoneConstants.PRESENTATION_RESTRICTED; 1105 } else if (n.equals("UNAVAILABLE") || 1106 n.equals("UNKNOWN") || 1107 n.equals("UNA") || 1108 n.equals("U")) { 1109 if (DBG) log("checkCnapSpecialCases, UNKNOWN string: " + n); 1110 return PhoneConstants.PRESENTATION_UNKNOWN; 1111 } else { 1112 if (DBG) log("checkCnapSpecialCases, normal str. number: " + n); 1113 return CNAP_SPECIAL_CASE_NO; 1114 } 1115 } 1116 1117 /** 1118 * Handles certain "corner cases" for CNAP. When we receive weird phone numbers 1119 * from the network to indicate different number presentations, convert them to 1120 * expected number and presentation values within the CallerInfo object. 1121 * @param number number we use to verify if we are in a corner case 1122 * @param presentation presentation value used to verify if we are in a corner case 1123 * @return the new String that should be used for the phone number 1124 */ modifyForSpecialCnapCases(Context context, CallerInfo ci, String number, int presentation)1125 /* package */ static String modifyForSpecialCnapCases(Context context, CallerInfo ci, 1126 String number, int presentation) { 1127 // Obviously we return number if ci == null, but still return number if 1128 // number == null, because in these cases the correct string will still be 1129 // displayed/logged after this function returns based on the presentation value. 1130 if (ci == null || number == null) return number; 1131 1132 if (DBG) { 1133 log("modifyForSpecialCnapCases: initially, number=" 1134 + toLogSafePhoneNumber(number) 1135 + ", presentation=" + presentation + " ci " + ci); 1136 } 1137 1138 // "ABSENT NUMBER" is a possible value we could get from the network as the 1139 // phone number, so if this happens, change it to "Unknown" in the CallerInfo 1140 // and fix the presentation to be the same. 1141 final String[] absentNumberValues = 1142 context.getResources().getStringArray(R.array.absent_num); 1143 if (Arrays.asList(absentNumberValues).contains(number) 1144 && presentation == PhoneConstants.PRESENTATION_ALLOWED) { 1145 number = context.getString(R.string.unknown); 1146 ci.numberPresentation = PhoneConstants.PRESENTATION_UNKNOWN; 1147 } 1148 1149 // Check for other special "corner cases" for CNAP and fix them similarly. Corner 1150 // cases only apply if we received an allowed presentation from the network, so check 1151 // if we think we have an allowed presentation, or if the CallerInfo presentation doesn't 1152 // match the presentation passed in for verification (meaning we changed it previously 1153 // because it's a corner case and we're being called from a different entry point). 1154 if (ci.numberPresentation == PhoneConstants.PRESENTATION_ALLOWED 1155 || (ci.numberPresentation != presentation 1156 && presentation == PhoneConstants.PRESENTATION_ALLOWED)) { 1157 int cnapSpecialCase = checkCnapSpecialCases(number); 1158 if (cnapSpecialCase != CNAP_SPECIAL_CASE_NO) { 1159 // For all special strings, change number & numberPresentation. 1160 if (cnapSpecialCase == PhoneConstants.PRESENTATION_RESTRICTED) { 1161 number = context.getString(R.string.private_num); 1162 } else if (cnapSpecialCase == PhoneConstants.PRESENTATION_UNKNOWN) { 1163 number = context.getString(R.string.unknown); 1164 } 1165 if (DBG) { 1166 log("SpecialCnap: number=" + toLogSafePhoneNumber(number) 1167 + "; presentation now=" + cnapSpecialCase); 1168 } 1169 ci.numberPresentation = cnapSpecialCase; 1170 } 1171 } 1172 if (DBG) { 1173 log("modifyForSpecialCnapCases: returning number string=" 1174 + toLogSafePhoneNumber(number)); 1175 } 1176 return number; 1177 } 1178 1179 // 1180 // Support for 3rd party phone service providers. 1181 // 1182 1183 /** 1184 * Check if a phone number can be route through a 3rd party 1185 * gateway. The number must be a global phone number in numerical 1186 * form (1-800-666-SEXY won't work). 1187 * 1188 * MMI codes and the like cannot be used as a dial number for the 1189 * gateway either. 1190 * 1191 * @param number To be dialed via a 3rd party gateway. 1192 * @return true If the number can be routed through the 3rd party network. 1193 */ isRoutableViaGateway(String number)1194 private static boolean isRoutableViaGateway(String number) { 1195 if (TextUtils.isEmpty(number)) { 1196 return false; 1197 } 1198 number = PhoneNumberUtils.stripSeparators(number); 1199 if (!number.equals(PhoneNumberUtils.convertKeypadLettersToDigits(number))) { 1200 return false; 1201 } 1202 number = PhoneNumberUtils.extractNetworkPortion(number); 1203 return PhoneNumberUtils.isGlobalPhoneNumber(number); 1204 } 1205 1206 /** 1207 * Returns whether the phone is in ECM ("Emergency Callback Mode") or not. 1208 */ isPhoneInEcm(Phone phone)1209 /* package */ static boolean isPhoneInEcm(Phone phone) { 1210 if ((phone != null) && TelephonyCapabilities.supportsEcm(phone)) { 1211 return phone.isInEcm(); 1212 } 1213 return false; 1214 } 1215 1216 /** 1217 * Returns true when the given call is in INCOMING state and there's no foreground phone call, 1218 * meaning the call is the first real incoming call the phone is having. 1219 */ isRealIncomingCall(Call.State state)1220 public static boolean isRealIncomingCall(Call.State state) { 1221 return (state == Call.State.INCOMING && !PhoneGlobals.getInstance().mCM.hasActiveFgCall()); 1222 } 1223 1224 // 1225 // General phone and call state debugging/testing code 1226 // 1227 log(String msg)1228 private static void log(String msg) { 1229 Log.d(LOG_TAG, msg); 1230 } 1231 makePstnPhoneAccountHandle(String id)1232 public static PhoneAccountHandle makePstnPhoneAccountHandle(String id) { 1233 return makePstnPhoneAccountHandleWithPrefix(id, "", false); 1234 } 1235 makePstnPhoneAccountHandle(int phoneId)1236 public static PhoneAccountHandle makePstnPhoneAccountHandle(int phoneId) { 1237 return makePstnPhoneAccountHandle(PhoneFactory.getPhone(phoneId)); 1238 } 1239 makePstnPhoneAccountHandle(Phone phone)1240 public static PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) { 1241 return makePstnPhoneAccountHandleWithPrefix(phone, "", false); 1242 } 1243 makePstnPhoneAccountHandleWithPrefix( Phone phone, String prefix, boolean isEmergency)1244 public static PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix( 1245 Phone phone, String prefix, boolean isEmergency) { 1246 // TODO: Should use some sort of special hidden flag to decorate this account as 1247 // an emergency-only account 1248 String id = isEmergency ? EMERGENCY_ACCOUNT_HANDLE_ID : prefix + 1249 String.valueOf(phone.getFullIccSerialNumber()); 1250 return makePstnPhoneAccountHandleWithPrefix(id, prefix, isEmergency); 1251 } 1252 makePstnPhoneAccountHandleWithPrefix( String id, String prefix, boolean isEmergency)1253 public static PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix( 1254 String id, String prefix, boolean isEmergency) { 1255 ComponentName pstnConnectionServiceName = getPstnConnectionServiceName(); 1256 return new PhoneAccountHandle(pstnConnectionServiceName, id); 1257 } 1258 getSubIdForPhoneAccount(PhoneAccount phoneAccount)1259 public static int getSubIdForPhoneAccount(PhoneAccount phoneAccount) { 1260 if (phoneAccount != null 1261 && phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { 1262 return getSubIdForPhoneAccountHandle(phoneAccount.getAccountHandle()); 1263 } 1264 return SubscriptionManager.INVALID_SUBSCRIPTION_ID; 1265 } 1266 getSubIdForPhoneAccountHandle(PhoneAccountHandle handle)1267 public static int getSubIdForPhoneAccountHandle(PhoneAccountHandle handle) { 1268 Phone phone = getPhoneForPhoneAccountHandle(handle); 1269 if (phone != null) { 1270 return phone.getSubId(); 1271 } 1272 return SubscriptionManager.INVALID_SUBSCRIPTION_ID; 1273 } 1274 getPhoneForPhoneAccountHandle(PhoneAccountHandle handle)1275 public static Phone getPhoneForPhoneAccountHandle(PhoneAccountHandle handle) { 1276 if (handle != null && handle.getComponentName().equals(getPstnConnectionServiceName())) { 1277 return getPhoneFromIccId(handle.getId()); 1278 } 1279 return null; 1280 } 1281 1282 /** 1283 * Determine if a given phone account corresponds to an active SIM 1284 * 1285 * @param sm An instance of the subscription manager so it is not recreated for each calling of 1286 * this method. 1287 * @param handle The handle for the phone account to check 1288 * @return {@code true} If there is an active SIM for this phone account, 1289 * {@code false} otherwise. 1290 */ isPhoneAccountActive(SubscriptionManager sm, PhoneAccountHandle handle)1291 public static boolean isPhoneAccountActive(SubscriptionManager sm, PhoneAccountHandle handle) { 1292 return sm.getActiveSubscriptionInfoForIccIndex(handle.getId()) != null; 1293 } 1294 getPstnConnectionServiceName()1295 private static ComponentName getPstnConnectionServiceName() { 1296 return PSTN_CONNECTION_SERVICE_COMPONENT; 1297 } 1298 getPhoneFromIccId(String iccId)1299 private static Phone getPhoneFromIccId(String iccId) { 1300 if (!TextUtils.isEmpty(iccId)) { 1301 for (Phone phone : PhoneFactory.getPhones()) { 1302 String phoneIccId = phone.getFullIccSerialNumber(); 1303 if (iccId.equals(phoneIccId)) { 1304 return phone; 1305 } 1306 } 1307 } 1308 return null; 1309 } 1310 1311 /** 1312 * Register ICC status for all phones. 1313 */ registerIccStatus(Handler handler, int event)1314 static final void registerIccStatus(Handler handler, int event) { 1315 for (Phone phone : PhoneFactory.getPhones()) { 1316 IccCard sim = phone.getIccCard(); 1317 if (sim != null) { 1318 if (VDBG) Log.v(LOG_TAG, "register for ICC status, phone " + phone.getPhoneId()); 1319 sim.registerForNetworkLocked(handler, event, phone); 1320 } 1321 } 1322 } 1323 1324 /** 1325 * Register ICC status for all phones. 1326 */ registerIccStatus(Handler handler, int event, int phoneId)1327 static final void registerIccStatus(Handler handler, int event, int phoneId) { 1328 Phone[] phones = PhoneFactory.getPhones(); 1329 IccCard sim = phones[phoneId].getIccCard(); 1330 if (sim != null) { 1331 if (VDBG) { 1332 Log.v(LOG_TAG, "register for ICC status, phone " + phones[phoneId].getPhoneId()); 1333 } 1334 sim.registerForNetworkLocked(handler, event, phones[phoneId]); 1335 } 1336 } 1337 1338 /** 1339 * Unregister ICC status for a specific phone. 1340 */ unregisterIccStatus(Handler handler, int phoneId)1341 static final void unregisterIccStatus(Handler handler, int phoneId) { 1342 Phone[] phones = PhoneFactory.getPhones(); 1343 IccCard sim = phones[phoneId].getIccCard(); 1344 if (sim != null) { 1345 if (VDBG) { 1346 Log.v(LOG_TAG, "unregister for ICC status, phone " + phones[phoneId].getPhoneId()); 1347 } 1348 sim.unregisterForNetworkLocked(handler); 1349 } 1350 } 1351 1352 /** 1353 * Set the radio power on/off state for all phones. 1354 * 1355 * @param enabled true means on, false means off. 1356 */ setRadioPower(boolean enabled)1357 static final void setRadioPower(boolean enabled) { 1358 for (Phone phone : PhoneFactory.getPhones()) { 1359 phone.setRadioPower(enabled); 1360 } 1361 } 1362 } 1363