1 /* 2 * Copyright (C) 2014 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.services.telephony; 18 19 import android.annotation.NonNull; 20 import android.app.AlertDialog; 21 import android.app.Dialog; 22 import android.content.ActivityNotFoundException; 23 import android.content.BroadcastReceiver; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.DialogInterface; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.net.Uri; 30 import android.os.Bundle; 31 import android.os.Handler; 32 import android.os.HandlerThread; 33 import android.os.Looper; 34 import android.telecom.Conference; 35 import android.telecom.Connection; 36 import android.telecom.ConnectionRequest; 37 import android.telecom.ConnectionService; 38 import android.telecom.DisconnectCause; 39 import android.telecom.PhoneAccount; 40 import android.telecom.PhoneAccountHandle; 41 import android.telecom.TelecomManager; 42 import android.telecom.VideoProfile; 43 import android.telephony.CarrierConfigManager; 44 import android.telephony.PhoneNumberUtils; 45 import android.telephony.RadioAccessFamily; 46 import android.telephony.ServiceState; 47 import android.telephony.SubscriptionManager; 48 import android.telephony.TelephonyManager; 49 import android.telephony.emergency.EmergencyNumber; 50 import android.text.TextUtils; 51 import android.util.Pair; 52 import android.view.WindowManager; 53 54 import com.android.internal.annotations.VisibleForTesting; 55 import com.android.internal.telephony.Call; 56 import com.android.internal.telephony.CallStateException; 57 import com.android.internal.telephony.GsmCdmaPhone; 58 import com.android.internal.telephony.IccCard; 59 import com.android.internal.telephony.IccCardConstants; 60 import com.android.internal.telephony.Phone; 61 import com.android.internal.telephony.PhoneConstants; 62 import com.android.internal.telephony.PhoneFactory; 63 import com.android.internal.telephony.PhoneSwitcher; 64 import com.android.internal.telephony.RIL; 65 import com.android.internal.telephony.SubscriptionController; 66 import com.android.internal.telephony.imsphone.ImsExternalCallTracker; 67 import com.android.internal.telephony.imsphone.ImsPhone; 68 import com.android.internal.telephony.imsphone.ImsPhoneConnection; 69 import com.android.phone.MMIDialogActivity; 70 import com.android.phone.PhoneUtils; 71 import com.android.phone.R; 72 import com.android.phone.settings.SuppServicesUiUtil; 73 74 import java.lang.ref.WeakReference; 75 import java.util.ArrayList; 76 import java.util.Arrays; 77 import java.util.Collection; 78 import java.util.Collections; 79 import java.util.HashMap; 80 import java.util.LinkedList; 81 import java.util.List; 82 import java.util.Map; 83 import java.util.Queue; 84 import java.util.concurrent.CompletableFuture; 85 import java.util.concurrent.TimeUnit; 86 import java.util.regex.Pattern; 87 88 import javax.annotation.Nullable; 89 90 /** 91 * Service for making GSM and CDMA connections. 92 */ 93 public class TelephonyConnectionService extends ConnectionService { 94 95 // Timeout before we continue with the emergency call without waiting for DDS switch response 96 // from the modem. 97 private static final int DEFAULT_DATA_SWITCH_TIMEOUT_MS = 1000; 98 99 // If configured, reject attempts to dial numbers matching this pattern. 100 private static final Pattern CDMA_ACTIVATION_CODE_REGEX_PATTERN = 101 Pattern.compile("\\*228[0-9]{0,2}"); 102 103 private final TelephonyConnectionServiceProxy mTelephonyConnectionServiceProxy = 104 new TelephonyConnectionServiceProxy() { 105 @Override 106 public Collection<Connection> getAllConnections() { 107 return TelephonyConnectionService.this.getAllConnections(); 108 } 109 @Override 110 public void addConference(TelephonyConference mTelephonyConference) { 111 TelephonyConnectionService.this.addTelephonyConference(mTelephonyConference); 112 } 113 @Override 114 public void addConference(ImsConference mImsConference) { 115 TelephonyConnectionService.this.addTelephonyConference(mImsConference); 116 } 117 @Override 118 public void addExistingConnection(PhoneAccountHandle phoneAccountHandle, 119 Connection connection) { 120 TelephonyConnectionService.this 121 .addExistingConnection(phoneAccountHandle, connection); 122 } 123 @Override 124 public void addExistingConnection(PhoneAccountHandle phoneAccountHandle, 125 Connection connection, Conference conference) { 126 TelephonyConnectionService.this 127 .addExistingConnection(phoneAccountHandle, connection, conference); 128 } 129 @Override 130 public void addConnectionToConferenceController(TelephonyConnection connection) { 131 TelephonyConnectionService.this.addConnectionToConferenceController(connection); 132 } 133 }; 134 135 private final BroadcastReceiver mTtyBroadcastReceiver = new BroadcastReceiver() { 136 @Override 137 public void onReceive(Context context, Intent intent) { 138 String action = intent.getAction(); 139 Log.v(this, "onReceive, action: %s", action); 140 if (action.equals(TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED)) { 141 int newPreferredTtyMode = intent.getIntExtra( 142 TelecomManager.EXTRA_TTY_PREFERRED_MODE, TelecomManager.TTY_MODE_OFF); 143 144 boolean isTtyNowEnabled = newPreferredTtyMode != TelecomManager.TTY_MODE_OFF; 145 if (isTtyNowEnabled != mIsTtyEnabled) { 146 handleTtyModeChange(isTtyNowEnabled); 147 } 148 } 149 } 150 }; 151 152 private final TelephonyConferenceController mTelephonyConferenceController = 153 new TelephonyConferenceController(mTelephonyConnectionServiceProxy); 154 private final CdmaConferenceController mCdmaConferenceController = 155 new CdmaConferenceController(this); 156 private ImsConferenceController mImsConferenceController; 157 158 private ComponentName mExpectedComponentName = null; 159 private RadioOnHelper mRadioOnHelper; 160 private EmergencyTonePlayer mEmergencyTonePlayer; 161 private HoldTracker mHoldTracker; 162 private boolean mIsTtyEnabled; 163 164 // Contains one TelephonyConnection that has placed a call and a memory of which Phones it has 165 // already tried to connect with. There should be only one TelephonyConnection trying to place a 166 // call at one time. We also only access this cache from a TelephonyConnection that wishes to 167 // redial, so we use a WeakReference that will become stale once the TelephonyConnection is 168 // destroyed. 169 @VisibleForTesting 170 public Pair<WeakReference<TelephonyConnection>, Queue<Phone>> mEmergencyRetryCache; 171 private Handler mDdsSwitchHandler; 172 private HandlerThread mHandlerThread; 173 private DeviceState mDeviceState = new DeviceState(); 174 175 /** 176 * Keeps track of the status of a SIM slot. 177 */ 178 private static class SlotStatus { 179 public int slotId; 180 // RAT capabilities 181 public int capabilities; 182 // By default, we will assume that the slots are not locked. 183 public boolean isLocked = false; 184 // Is the emergency number associated with the slot 185 public boolean hasDialedEmergencyNumber = false; 186 //SimState 187 public int simState; 188 SlotStatus(int slotId, int capabilities)189 public SlotStatus(int slotId, int capabilities) { 190 this.slotId = slotId; 191 this.capabilities = capabilities; 192 } 193 } 194 195 /** 196 * SubscriptionManager dependencies for testing. 197 */ 198 @VisibleForTesting 199 public interface SubscriptionManagerProxy { getDefaultVoicePhoneId()200 int getDefaultVoicePhoneId(); getSimStateForSlotIdx(int slotId)201 int getSimStateForSlotIdx(int slotId); getPhoneId(int subId)202 int getPhoneId(int subId); 203 } 204 205 private SubscriptionManagerProxy mSubscriptionManagerProxy = new SubscriptionManagerProxy() { 206 @Override 207 public int getDefaultVoicePhoneId() { 208 return SubscriptionManager.getDefaultVoicePhoneId(); 209 } 210 211 @Override 212 public int getSimStateForSlotIdx(int slotId) { 213 return SubscriptionManager.getSimStateForSlotIndex(slotId); 214 } 215 216 @Override 217 public int getPhoneId(int subId) { 218 return SubscriptionManager.getPhoneId(subId); 219 } 220 }; 221 222 /** 223 * TelephonyManager dependencies for testing. 224 */ 225 @VisibleForTesting 226 public interface TelephonyManagerProxy { getPhoneCount()227 int getPhoneCount(); hasIccCard(int slotId)228 boolean hasIccCard(int slotId); isCurrentEmergencyNumber(String number)229 boolean isCurrentEmergencyNumber(String number); getCurrentEmergencyNumberList()230 Map<Integer, List<EmergencyNumber>> getCurrentEmergencyNumberList(); 231 } 232 233 private TelephonyManagerProxy mTelephonyManagerProxy; 234 235 private class TelephonyManagerProxyImpl implements TelephonyManagerProxy { 236 private final TelephonyManager mTelephonyManager; 237 238 TelephonyManagerProxyImpl(Context context)239 TelephonyManagerProxyImpl(Context context) { 240 mTelephonyManager = new TelephonyManager(context); 241 } 242 243 @Override getPhoneCount()244 public int getPhoneCount() { 245 return mTelephonyManager.getPhoneCount(); 246 } 247 248 @Override hasIccCard(int slotId)249 public boolean hasIccCard(int slotId) { 250 return mTelephonyManager.hasIccCard(slotId); 251 } 252 253 @Override isCurrentEmergencyNumber(String number)254 public boolean isCurrentEmergencyNumber(String number) { 255 try { 256 return mTelephonyManager.isEmergencyNumber(number); 257 } catch (IllegalStateException ise) { 258 return false; 259 } 260 } 261 262 @Override getCurrentEmergencyNumberList()263 public Map<Integer, List<EmergencyNumber>> getCurrentEmergencyNumberList() { 264 try { 265 return mTelephonyManager.getEmergencyNumberList(); 266 } catch (IllegalStateException ise) { 267 return new HashMap<>(); 268 } 269 } 270 } 271 272 /** 273 * PhoneFactory Dependencies for testing. 274 */ 275 @VisibleForTesting 276 public interface PhoneFactoryProxy { getPhone(int index)277 Phone getPhone(int index); getDefaultPhone()278 Phone getDefaultPhone(); getPhones()279 Phone[] getPhones(); 280 } 281 282 private PhoneFactoryProxy mPhoneFactoryProxy = new PhoneFactoryProxy() { 283 @Override 284 public Phone getPhone(int index) { 285 return PhoneFactory.getPhone(index); 286 } 287 288 @Override 289 public Phone getDefaultPhone() { 290 return PhoneFactory.getDefaultPhone(); 291 } 292 293 @Override 294 public Phone[] getPhones() { 295 return PhoneFactory.getPhones(); 296 } 297 }; 298 299 /** 300 * PhoneUtils dependencies for testing. 301 */ 302 @VisibleForTesting 303 public interface PhoneUtilsProxy { getSubIdForPhoneAccountHandle(PhoneAccountHandle accountHandle)304 int getSubIdForPhoneAccountHandle(PhoneAccountHandle accountHandle); makePstnPhoneAccountHandle(Phone phone)305 PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone); makePstnPhoneAccountHandleWithPrefix(Phone phone, String prefix, boolean isEmergency)306 PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix(Phone phone, String prefix, 307 boolean isEmergency); 308 } 309 310 private PhoneUtilsProxy mPhoneUtilsProxy = new PhoneUtilsProxy() { 311 @Override 312 public int getSubIdForPhoneAccountHandle(PhoneAccountHandle accountHandle) { 313 return PhoneUtils.getSubIdForPhoneAccountHandle(accountHandle); 314 } 315 316 @Override 317 public PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) { 318 return PhoneUtils.makePstnPhoneAccountHandle(phone); 319 } 320 321 @Override 322 public PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix(Phone phone, String prefix, 323 boolean isEmergency) { 324 return PhoneUtils.makePstnPhoneAccountHandleWithPrefix(phone, prefix, isEmergency); 325 } 326 }; 327 328 /** 329 * PhoneNumberUtils dependencies for testing. 330 */ 331 @VisibleForTesting 332 public interface PhoneNumberUtilsProxy { convertToEmergencyNumber(Context context, String number)333 String convertToEmergencyNumber(Context context, String number); 334 } 335 336 private PhoneNumberUtilsProxy mPhoneNumberUtilsProxy = new PhoneNumberUtilsProxy() { 337 @Override 338 public String convertToEmergencyNumber(Context context, String number) { 339 return PhoneNumberUtils.convertToEmergencyNumber(context, number); 340 } 341 }; 342 343 /** 344 * PhoneSwitcher dependencies for testing. 345 */ 346 @VisibleForTesting 347 public interface PhoneSwitcherProxy { getPhoneSwitcher()348 PhoneSwitcher getPhoneSwitcher(); 349 } 350 351 private PhoneSwitcherProxy mPhoneSwitcherProxy = new PhoneSwitcherProxy() { 352 @Override 353 public PhoneSwitcher getPhoneSwitcher() { 354 return PhoneSwitcher.getInstance(); 355 } 356 }; 357 358 /** 359 * Factory for Handler creation in order to remove flakiness during t esting. 360 */ 361 @VisibleForTesting 362 public interface HandlerFactory { createHandlerThread(String name)363 HandlerThread createHandlerThread(String name); createHandler(Looper looper)364 Handler createHandler(Looper looper); 365 } 366 367 private HandlerFactory mHandlerFactory = new HandlerFactory() { 368 @Override 369 public HandlerThread createHandlerThread(String name) { 370 return new HandlerThread(name); 371 } 372 373 @Override 374 public Handler createHandler(Looper looper) { 375 return new Handler(looper); 376 } 377 }; 378 379 /** 380 * DisconnectCause depends on PhoneGlobals in order to get a system context. Mock out 381 * dependency for testing. 382 */ 383 @VisibleForTesting 384 public interface DisconnectCauseFactory { toTelecomDisconnectCause(int telephonyDisconnectCause, String reason)385 DisconnectCause toTelecomDisconnectCause(int telephonyDisconnectCause, String reason); toTelecomDisconnectCause(int telephonyDisconnectCause, String reason, int phoneId)386 DisconnectCause toTelecomDisconnectCause(int telephonyDisconnectCause, 387 String reason, int phoneId); 388 } 389 390 private DisconnectCauseFactory mDisconnectCauseFactory = new DisconnectCauseFactory() { 391 @Override 392 public DisconnectCause toTelecomDisconnectCause(int telephonyDisconnectCause, 393 String reason) { 394 return DisconnectCauseUtil.toTelecomDisconnectCause(telephonyDisconnectCause, reason); 395 } 396 397 @Override 398 public DisconnectCause toTelecomDisconnectCause(int telephonyDisconnectCause, String reason, 399 int phoneId) { 400 return DisconnectCauseUtil.toTelecomDisconnectCause(telephonyDisconnectCause, reason, 401 phoneId); 402 } 403 }; 404 405 /** 406 * Overrides SubscriptionManager dependencies for testing. 407 */ 408 @VisibleForTesting setSubscriptionManagerProxy(SubscriptionManagerProxy proxy)409 public void setSubscriptionManagerProxy(SubscriptionManagerProxy proxy) { 410 mSubscriptionManagerProxy = proxy; 411 } 412 413 /** 414 * Overrides TelephonyManager dependencies for testing. 415 */ 416 @VisibleForTesting setTelephonyManagerProxy(TelephonyManagerProxy proxy)417 public void setTelephonyManagerProxy(TelephonyManagerProxy proxy) { 418 mTelephonyManagerProxy = proxy; 419 } 420 421 /** 422 * Overrides PhoneFactory dependencies for testing. 423 */ 424 @VisibleForTesting setPhoneFactoryProxy(PhoneFactoryProxy proxy)425 public void setPhoneFactoryProxy(PhoneFactoryProxy proxy) { 426 mPhoneFactoryProxy = proxy; 427 } 428 429 /** 430 * Overrides configuration and settings dependencies for testing. 431 */ 432 @VisibleForTesting setDeviceState(DeviceState state)433 public void setDeviceState(DeviceState state) { 434 mDeviceState = state; 435 } 436 437 /** 438 * Overrides radioOnHelper for testing. 439 */ 440 @VisibleForTesting setRadioOnHelper(RadioOnHelper radioOnHelper)441 public void setRadioOnHelper(RadioOnHelper radioOnHelper) { 442 mRadioOnHelper = radioOnHelper; 443 } 444 445 /** 446 * Overrides PhoneSwitcher dependencies for testing. 447 */ 448 @VisibleForTesting setPhoneSwitcherProxy(PhoneSwitcherProxy proxy)449 public void setPhoneSwitcherProxy(PhoneSwitcherProxy proxy) { 450 mPhoneSwitcherProxy = proxy; 451 } 452 453 /** 454 * Overrides PhoneNumberUtils dependencies for testing. 455 */ 456 @VisibleForTesting setPhoneNumberUtilsProxy(PhoneNumberUtilsProxy proxy)457 public void setPhoneNumberUtilsProxy(PhoneNumberUtilsProxy proxy) { 458 mPhoneNumberUtilsProxy = proxy; 459 } 460 461 /** 462 * Overrides PhoneUtils dependencies for testing. 463 */ 464 @VisibleForTesting setPhoneUtilsProxy(PhoneUtilsProxy proxy)465 public void setPhoneUtilsProxy(PhoneUtilsProxy proxy) { 466 mPhoneUtilsProxy = proxy; 467 } 468 469 /** 470 * Override Handler creation factory for testing. 471 */ 472 @VisibleForTesting setHandlerFactory(HandlerFactory handlerFactory)473 public void setHandlerFactory(HandlerFactory handlerFactory) { 474 mHandlerFactory = handlerFactory; 475 } 476 477 /** 478 * Override DisconnectCause creation for testing. 479 */ 480 @VisibleForTesting setDisconnectCauseFactory(DisconnectCauseFactory factory)481 public void setDisconnectCauseFactory(DisconnectCauseFactory factory) { 482 mDisconnectCauseFactory = factory; 483 } 484 485 /** 486 * A listener to actionable events specific to the TelephonyConnection. 487 */ 488 private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener = 489 new TelephonyConnection.TelephonyConnectionListener() { 490 @Override 491 public void onOriginalConnectionConfigured(TelephonyConnection c) { 492 addConnectionToConferenceController(c); 493 } 494 495 @Override 496 public void onOriginalConnectionRetry(TelephonyConnection c, boolean isPermanentFailure) { 497 retryOutgoingOriginalConnection(c, isPermanentFailure); 498 } 499 }; 500 501 private final TelephonyConferenceBase.TelephonyConferenceListener mTelephonyConferenceListener = 502 new TelephonyConferenceBase.TelephonyConferenceListener() { 503 @Override 504 public void onConferenceMembershipChanged(Connection connection) { 505 mHoldTracker.updateHoldCapability(connection.getPhoneAccountHandle()); 506 } 507 }; 508 509 @Override onCreate()510 public void onCreate() { 511 super.onCreate(); 512 mImsConferenceController = new ImsConferenceController( 513 TelecomAccountRegistry.getInstance(this), 514 mTelephonyConnectionServiceProxy, 515 // FeatureFlagProxy; used to determine if standalone call emulation is enabled. 516 // TODO: Move to carrier config 517 () -> true); 518 setTelephonyManagerProxy(new TelephonyManagerProxyImpl(getApplicationContext())); 519 mExpectedComponentName = new ComponentName(this, this.getClass()); 520 mEmergencyTonePlayer = new EmergencyTonePlayer(this); 521 TelecomAccountRegistry.getInstance(this).setTelephonyConnectionService(this); 522 mHoldTracker = new HoldTracker(); 523 mIsTtyEnabled = mDeviceState.isTtyModeEnabled(this); 524 525 IntentFilter intentFilter = new IntentFilter( 526 TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED); 527 registerReceiver(mTtyBroadcastReceiver, intentFilter); 528 mHandlerThread = mHandlerFactory.createHandlerThread("DdsSwitchHandlerThread"); 529 mHandlerThread.start(); 530 Looper looper = mHandlerThread.getLooper(); 531 mDdsSwitchHandler = mHandlerFactory.createHandler(looper); 532 } 533 534 @Override onUnbind(Intent intent)535 public boolean onUnbind(Intent intent) { 536 unregisterReceiver(mTtyBroadcastReceiver); 537 mHandlerThread.quitSafely(); 538 return super.onUnbind(intent); 539 } 540 placeOutgoingConference(ConnectionRequest request, Connection resultConnection, Phone phone)541 private Conference placeOutgoingConference(ConnectionRequest request, 542 Connection resultConnection, Phone phone) { 543 if (resultConnection instanceof TelephonyConnection) { 544 return placeOutgoingConference((TelephonyConnection) resultConnection, phone, request); 545 } 546 return null; 547 } 548 placeOutgoingConference(TelephonyConnection conferenceHostConnection, Phone phone, ConnectionRequest request)549 private Conference placeOutgoingConference(TelephonyConnection conferenceHostConnection, 550 Phone phone, ConnectionRequest request) { 551 updatePhoneAccount(conferenceHostConnection, phone); 552 com.android.internal.telephony.Connection originalConnection = null; 553 try { 554 originalConnection = phone.startConference( 555 getParticipantsToDial(request.getParticipants()), 556 new ImsPhone.ImsDialArgs.Builder() 557 .setVideoState(request.getVideoState()) 558 .setRttTextStream(conferenceHostConnection.getRttTextStream()) 559 .build()); 560 } catch (CallStateException e) { 561 Log.e(this, e, "placeOutgoingConference, phone.startConference exception: " + e); 562 handleCallStateException(e, conferenceHostConnection, phone); 563 return null; 564 } 565 566 if (originalConnection == null) { 567 Log.d(this, "placeOutgoingConference, phone.startConference returned null"); 568 conferenceHostConnection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 569 android.telephony.DisconnectCause.OUTGOING_FAILURE, 570 "conferenceHostConnection is null", 571 phone.getPhoneId())); 572 conferenceHostConnection.clearOriginalConnection(); 573 conferenceHostConnection.destroy(); 574 } else { 575 conferenceHostConnection.setOriginalConnection(originalConnection); 576 } 577 578 return prepareConference(conferenceHostConnection, request.getAccountHandle()); 579 } 580 prepareConference(Connection conn, PhoneAccountHandle phoneAccountHandle)581 Conference prepareConference(Connection conn, PhoneAccountHandle phoneAccountHandle) { 582 if (!(conn instanceof TelephonyConnection)) { 583 Log.w(this, "prepareConference returning NULL conference"); 584 return null; 585 } 586 587 TelephonyConnection connection = (TelephonyConnection)conn; 588 589 ImsConference conference = new ImsConference(TelecomAccountRegistry.getInstance(this), 590 mTelephonyConnectionServiceProxy, connection, 591 phoneAccountHandle, () -> true, 592 ImsConferenceController.getCarrierConfig(connection.getPhone())); 593 mImsConferenceController.addConference(conference); 594 conference.setVideoState(connection, 595 connection.getVideoState()); 596 conference.setVideoProvider(connection, 597 connection.getVideoProvider()); 598 conference.setStatusHints(connection.getStatusHints()); 599 conference.setAddress(connection.getAddress(), 600 connection.getAddressPresentation()); 601 conference.setCallerDisplayName(connection.getCallerDisplayName(), 602 connection.getCallerDisplayNamePresentation()); 603 conference.setParticipants(connection.getParticipants()); 604 return conference; 605 } 606 607 @Override onCreateIncomingConference( @ullable PhoneAccountHandle connectionManagerPhoneAccount, @NonNull final ConnectionRequest request)608 public @Nullable Conference onCreateIncomingConference( 609 @Nullable PhoneAccountHandle connectionManagerPhoneAccount, 610 @NonNull final ConnectionRequest request) { 611 Log.i(this, "onCreateIncomingConference, request: " + request); 612 Connection connection = onCreateIncomingConnection(connectionManagerPhoneAccount, request); 613 Log.d(this, "onCreateIncomingConference, connection: %s", connection); 614 if (connection == null) { 615 Log.i(this, "onCreateIncomingConference, implementation returned null connection."); 616 return Conference.createFailedConference( 617 new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONNECTION"), 618 request.getAccountHandle()); 619 } 620 621 final Phone phone = getPhoneForAccount(request.getAccountHandle(), 622 false /* isEmergencyCall*/, null /* not an emergency call */); 623 if (phone == null) { 624 Log.d(this, "onCreateIncomingConference, phone is null"); 625 return Conference.createFailedConference( 626 DisconnectCauseUtil.toTelecomDisconnectCause( 627 android.telephony.DisconnectCause.OUT_OF_SERVICE, 628 "Phone is null"), 629 request.getAccountHandle()); 630 } 631 632 return prepareConference(connection, request.getAccountHandle()); 633 } 634 635 @Override onCreateOutgoingConference( @ullable PhoneAccountHandle connectionManagerPhoneAccount, @NonNull final ConnectionRequest request)636 public @Nullable Conference onCreateOutgoingConference( 637 @Nullable PhoneAccountHandle connectionManagerPhoneAccount, 638 @NonNull final ConnectionRequest request) { 639 Log.i(this, "onCreateOutgoingConference, request: " + request); 640 Connection connection = onCreateOutgoingConnection(connectionManagerPhoneAccount, request); 641 Log.d(this, "onCreateOutgoingConference, connection: %s", connection); 642 if (connection == null) { 643 Log.i(this, "onCreateOutgoingConference, implementation returned null connection."); 644 return Conference.createFailedConference( 645 new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONNECTION"), 646 request.getAccountHandle()); 647 } 648 649 final Phone phone = getPhoneForAccount(request.getAccountHandle(), 650 false /* isEmergencyCall*/, null /* not an emergency call */); 651 if (phone == null) { 652 Log.d(this, "onCreateOutgoingConference, phone is null"); 653 return Conference.createFailedConference( 654 DisconnectCauseUtil.toTelecomDisconnectCause( 655 android.telephony.DisconnectCause.OUT_OF_SERVICE, 656 "Phone is null"), 657 request.getAccountHandle()); 658 } 659 660 return placeOutgoingConference(request, connection, phone); 661 } 662 getParticipantsToDial(List<Uri> participants)663 private String[] getParticipantsToDial(List<Uri> participants) { 664 String[] participantsToDial = new String[participants.size()]; 665 int i = 0; 666 for (Uri participant : participants) { 667 participantsToDial[i] = participant.getSchemeSpecificPart(); 668 i++; 669 } 670 return participantsToDial; 671 } 672 673 @Override onCreateOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, final ConnectionRequest request)674 public Connection onCreateOutgoingConnection( 675 PhoneAccountHandle connectionManagerPhoneAccount, 676 final ConnectionRequest request) { 677 Log.i(this, "onCreateOutgoingConnection, request: " + request); 678 679 Uri handle = request.getAddress(); 680 boolean isAdhocConference = request.isAdhocConferenceCall(); 681 682 if (!isAdhocConference && handle == null) { 683 Log.d(this, "onCreateOutgoingConnection, handle is null"); 684 return Connection.createFailedConnection( 685 mDisconnectCauseFactory.toTelecomDisconnectCause( 686 android.telephony.DisconnectCause.NO_PHONE_NUMBER_SUPPLIED, 687 "No phone number supplied")); 688 } 689 690 String scheme = handle.getScheme(); 691 String number; 692 if (PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) { 693 // TODO: We don't check for SecurityException here (requires 694 // CALL_PRIVILEGED permission). 695 final Phone phone = getPhoneForAccount(request.getAccountHandle(), 696 false /* isEmergencyCall */, null /* not an emergency call */); 697 if (phone == null) { 698 Log.d(this, "onCreateOutgoingConnection, phone is null"); 699 return Connection.createFailedConnection( 700 mDisconnectCauseFactory.toTelecomDisconnectCause( 701 android.telephony.DisconnectCause.OUT_OF_SERVICE, 702 "Phone is null")); 703 } 704 number = phone.getVoiceMailNumber(); 705 if (TextUtils.isEmpty(number)) { 706 Log.d(this, "onCreateOutgoingConnection, no voicemail number set."); 707 return Connection.createFailedConnection( 708 mDisconnectCauseFactory.toTelecomDisconnectCause( 709 android.telephony.DisconnectCause.VOICEMAIL_NUMBER_MISSING, 710 "Voicemail scheme provided but no voicemail number set.", 711 phone.getPhoneId())); 712 } 713 714 // Convert voicemail: to tel: 715 handle = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); 716 } else { 717 if (!PhoneAccount.SCHEME_TEL.equals(scheme)) { 718 Log.d(this, "onCreateOutgoingConnection, Handle %s is not type tel", scheme); 719 return Connection.createFailedConnection( 720 mDisconnectCauseFactory.toTelecomDisconnectCause( 721 android.telephony.DisconnectCause.INVALID_NUMBER, 722 "Handle scheme is not type tel")); 723 } 724 725 number = handle.getSchemeSpecificPart(); 726 if (TextUtils.isEmpty(number)) { 727 Log.d(this, "onCreateOutgoingConnection, unable to parse number"); 728 return Connection.createFailedConnection( 729 mDisconnectCauseFactory.toTelecomDisconnectCause( 730 android.telephony.DisconnectCause.INVALID_NUMBER, 731 "Unable to parse number")); 732 } 733 734 final Phone phone = getPhoneForAccount(request.getAccountHandle(), 735 false /* isEmergencyCall*/, null /* not an emergency call */); 736 if (phone != null && CDMA_ACTIVATION_CODE_REGEX_PATTERN.matcher(number).matches()) { 737 // Obtain the configuration for the outgoing phone's SIM. If the outgoing number 738 // matches the *228 regex pattern, fail the call. This number is used for OTASP, and 739 // when dialed could lock LTE SIMs to 3G if not prohibited.. 740 boolean disableActivation = false; 741 CarrierConfigManager cfgManager = (CarrierConfigManager) 742 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 743 if (cfgManager != null) { 744 disableActivation = cfgManager.getConfigForSubId(phone.getSubId()) 745 .getBoolean(CarrierConfigManager.KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL); 746 } 747 748 if (disableActivation) { 749 return Connection.createFailedConnection( 750 mDisconnectCauseFactory.toTelecomDisconnectCause( 751 android.telephony.DisconnectCause 752 .CDMA_ALREADY_ACTIVATED, 753 "Tried to dial *228", 754 phone.getPhoneId())); 755 } 756 } 757 } 758 759 final boolean isEmergencyNumber = mTelephonyManagerProxy.isCurrentEmergencyNumber(number); 760 // Find out if this is a test emergency number 761 final boolean isTestEmergencyNumber = isEmergencyNumberTestNumber(number); 762 763 // Convert into emergency number if necessary 764 // This is required in some regions (e.g. Taiwan). 765 if (isEmergencyNumber) { 766 final Phone phone = getPhoneForAccount(request.getAccountHandle(), false, 767 handle.getSchemeSpecificPart()); 768 // We only do the conversion if the phone is not in service. The un-converted 769 // emergency numbers will go to the correct destination when the phone is in-service, 770 // so they will only need the special emergency call setup when the phone is out of 771 // service. 772 if (phone == null || phone.getServiceState().getState() 773 != ServiceState.STATE_IN_SERVICE) { 774 String convertedNumber = mPhoneNumberUtilsProxy.convertToEmergencyNumber(this, 775 number); 776 if (!TextUtils.equals(convertedNumber, number)) { 777 Log.i(this, "onCreateOutgoingConnection, converted to emergency number"); 778 number = convertedNumber; 779 handle = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); 780 } 781 } 782 } 783 final String numberToDial = number; 784 785 final boolean isAirplaneModeOn = mDeviceState.isAirplaneModeOn(this); 786 787 boolean needToTurnOnRadio = (isEmergencyNumber && (!isRadioOn() || isAirplaneModeOn)) 788 || isRadioPowerDownOnBluetooth(); 789 790 // Get the right phone object from the account data passed in. 791 final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber, 792 /* Note: when not an emergency, handle can be null for unknown callers */ 793 handle == null ? null : handle.getSchemeSpecificPart()); 794 795 if (needToTurnOnRadio) { 796 final Uri resultHandle = handle; 797 final int originalPhoneType = phone.getPhoneType(); 798 final Connection resultConnection = getTelephonyConnection(request, numberToDial, 799 isEmergencyNumber, resultHandle, phone); 800 if (mRadioOnHelper == null) { 801 mRadioOnHelper = new RadioOnHelper(this); 802 } 803 mRadioOnHelper.triggerRadioOnAndListen(new RadioOnStateListener.Callback() { 804 @Override 805 public void onComplete(RadioOnStateListener listener, boolean isRadioReady) { 806 handleOnComplete(isRadioReady, isEmergencyNumber, resultConnection, request, 807 numberToDial, resultHandle, originalPhoneType, phone); 808 } 809 810 @Override 811 public boolean isOkToCall(Phone phone, int serviceState) { 812 // HAL 1.4 introduced a new variant of dial for emergency calls, which includes 813 // an isTesting parameter. For HAL 1.4+, do not wait for IN_SERVICE, this will 814 // be handled at the RIL/vendor level by emergencyDial(...). 815 boolean waitForInServiceToDialEmergency = isTestEmergencyNumber 816 && phone.getHalVersion().less(RIL.RADIO_HAL_VERSION_1_4); 817 if (isEmergencyNumber && !waitForInServiceToDialEmergency) { 818 // We currently only look to make sure that the radio is on before dialing. 819 // We should be able to make emergency calls at any time after the radio has 820 // been powered on and isn't in the UNAVAILABLE state, even if it is 821 // reporting the OUT_OF_SERVICE state. 822 return (phone.getState() == PhoneConstants.State.OFFHOOK) 823 || phone.getServiceStateTracker().isRadioOn(); 824 } else { 825 // Wait until we are in service and ready to make calls. This can happen 826 // when we power down the radio on bluetooth to save power on watches or if 827 // it is a test emergency number and we have to wait for the device to move 828 // IN_SERVICE before the call can take place over normal routing. 829 return (phone.getState() == PhoneConstants.State.OFFHOOK) 830 // Do not wait for voice in service on opportunistic SIMs. 831 || SubscriptionController.getInstance().isOpportunistic( 832 phone.getSubId()) 833 || serviceState == ServiceState.STATE_IN_SERVICE; 834 } 835 } 836 }, isEmergencyNumber && !isTestEmergencyNumber, phone); 837 // Return the still unconnected GsmConnection and wait for the Radios to boot before 838 // connecting it to the underlying Phone. 839 return resultConnection; 840 } else { 841 if (!canAddCall() && !isEmergencyNumber) { 842 Log.d(this, "onCreateOutgoingConnection, cannot add call ."); 843 return Connection.createFailedConnection( 844 new DisconnectCause(DisconnectCause.ERROR, 845 getApplicationContext().getText( 846 R.string.incall_error_cannot_add_call), 847 getApplicationContext().getText( 848 R.string.incall_error_cannot_add_call), 849 "Add call restricted due to ongoing video call")); 850 } 851 852 if (!isEmergencyNumber) { 853 final Connection resultConnection = getTelephonyConnection(request, numberToDial, 854 false, handle, phone); 855 if (isAdhocConference) { 856 if (resultConnection instanceof TelephonyConnection) { 857 TelephonyConnection conn = (TelephonyConnection)resultConnection; 858 conn.setParticipants(request.getParticipants()); 859 } 860 return resultConnection; 861 } else { 862 return placeOutgoingConnection(request, resultConnection, phone); 863 } 864 } else { 865 final Connection resultConnection = getTelephonyConnection(request, numberToDial, 866 true, handle, phone); 867 mDdsSwitchHandler.post(new Runnable() { 868 @Override 869 public void run() { 870 boolean result = delayDialForDdsSwitch(phone); 871 Log.i(this, 872 "onCreateOutgoingConn - delayDialForDdsSwitch result = " + result); 873 placeOutgoingConnection(request, resultConnection, phone); 874 } 875 }); 876 return resultConnection; 877 } 878 } 879 } 880 placeOutgoingConnection(ConnectionRequest request, Connection resultConnection, Phone phone)881 private Connection placeOutgoingConnection(ConnectionRequest request, 882 Connection resultConnection, Phone phone) { 883 // If there was a failure, the resulting connection will not be a TelephonyConnection, 884 // so don't place the call! 885 if (resultConnection instanceof TelephonyConnection) { 886 if (request.getExtras() != null && request.getExtras().getBoolean( 887 TelecomManager.EXTRA_USE_ASSISTED_DIALING, false)) { 888 ((TelephonyConnection) resultConnection).setIsUsingAssistedDialing(true); 889 } 890 placeOutgoingConnection((TelephonyConnection) resultConnection, phone, request); 891 } 892 return resultConnection; 893 } 894 isEmergencyNumberTestNumber(String number)895 private boolean isEmergencyNumberTestNumber(String number) { 896 number = PhoneNumberUtils.stripSeparators(number); 897 Map<Integer, List<EmergencyNumber>> list = 898 mTelephonyManagerProxy.getCurrentEmergencyNumberList(); 899 // Do not worry about which subscription the test emergency call is on yet, only detect that 900 // it is an emergency. 901 for (Integer sub : list.keySet()) { 902 for (EmergencyNumber eNumber : list.get(sub)) { 903 if (number.equals(eNumber.getNumber()) 904 && eNumber.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST)) { 905 Log.i(this, "isEmergencyNumberTestNumber: " + number + " has been detected as " 906 + "a test emergency number.,"); 907 return true; 908 } 909 } 910 } 911 return false; 912 } 913 914 /** 915 * Whether the cellular radio is power off because the device is on Bluetooth. 916 */ isRadioPowerDownOnBluetooth()917 private boolean isRadioPowerDownOnBluetooth() { 918 final boolean allowed = mDeviceState.isRadioPowerDownAllowedOnBluetooth(this); 919 final int cellOn = mDeviceState.getCellOnStatus(this); 920 return (allowed && cellOn == PhoneConstants.CELL_ON_FLAG && !isRadioOn()); 921 } 922 923 /** 924 * Handle the onComplete callback of RadioOnStateListener. 925 */ handleOnComplete(boolean isRadioReady, boolean isEmergencyNumber, Connection originalConnection, ConnectionRequest request, String numberToDial, Uri handle, int originalPhoneType, Phone phone)926 private void handleOnComplete(boolean isRadioReady, boolean isEmergencyNumber, 927 Connection originalConnection, ConnectionRequest request, String numberToDial, 928 Uri handle, int originalPhoneType, Phone phone) { 929 // Make sure the Call has not already been canceled by the user. 930 if (originalConnection.getState() == Connection.STATE_DISCONNECTED) { 931 Log.i(this, "Call disconnected before the outgoing call was placed. Skipping call " 932 + "placement."); 933 if (isEmergencyNumber) { 934 // If call is already canceled by the user, notify modem to exit emergency call 935 // mode by sending radio on with forEmergencyCall=false. 936 for (Phone curPhone : mPhoneFactoryProxy.getPhones()) { 937 curPhone.setRadioPower(true, false, false, true); 938 } 939 } 940 return; 941 } 942 if (isRadioReady) { 943 if (!isEmergencyNumber) { 944 adjustAndPlaceOutgoingConnection(phone, originalConnection, request, numberToDial, 945 handle, originalPhoneType, false); 946 } else { 947 mDdsSwitchHandler.post(new Runnable() { 948 @Override 949 public void run() { 950 boolean result = delayDialForDdsSwitch(phone); 951 Log.i(this, "handleOnComplete - delayDialForDdsSwitch result = " + result); 952 adjustAndPlaceOutgoingConnection(phone, originalConnection, request, 953 numberToDial, handle, originalPhoneType, true); 954 } 955 }); 956 } 957 958 } else { 959 Log.w(this, "onCreateOutgoingConnection, failed to turn on radio"); 960 closeOrDestroyConnection(originalConnection, 961 mDisconnectCauseFactory.toTelecomDisconnectCause( 962 android.telephony.DisconnectCause.POWER_OFF, 963 "Failed to turn on radio.")); 964 } 965 } 966 adjustAndPlaceOutgoingConnection(Phone phone, Connection connectionToEvaluate, ConnectionRequest request, String numberToDial, Uri handle, int originalPhoneType, boolean isEmergencyNumber)967 private void adjustAndPlaceOutgoingConnection(Phone phone, Connection connectionToEvaluate, 968 ConnectionRequest request, String numberToDial, Uri handle, int originalPhoneType, 969 boolean isEmergencyNumber) { 970 // If the PhoneType of the Phone being used is different than the Default Phone, then we 971 // need to create a new Connection using that PhoneType and replace it in Telecom. 972 if (phone.getPhoneType() != originalPhoneType) { 973 Connection repConnection = getTelephonyConnection(request, numberToDial, 974 isEmergencyNumber, handle, phone); 975 // If there was a failure, the resulting connection will not be a TelephonyConnection, 976 // so don't place the call, just return! 977 if (repConnection instanceof TelephonyConnection) { 978 placeOutgoingConnection((TelephonyConnection) repConnection, phone, request); 979 } 980 // Notify Telecom of the new Connection type. 981 // TODO: Switch out the underlying connection instead of creating a new 982 // one and causing UI Jank. 983 boolean noActiveSimCard = SubscriptionController.getInstance() 984 .getActiveSubInfoCount(phone.getContext().getOpPackageName(), 985 null) == 0; 986 // If there's no active sim card and the device is in emergency mode, use E account. 987 addExistingConnection(mPhoneUtilsProxy.makePstnPhoneAccountHandleWithPrefix( 988 phone, "", isEmergencyNumber && noActiveSimCard), repConnection); 989 // Remove the old connection from Telecom after. 990 closeOrDestroyConnection(connectionToEvaluate, 991 mDisconnectCauseFactory.toTelecomDisconnectCause( 992 android.telephony.DisconnectCause.OUTGOING_CANCELED, 993 "Reconnecting outgoing Emergency Call.", 994 phone.getPhoneId())); 995 } else { 996 placeOutgoingConnection((TelephonyConnection) connectionToEvaluate, phone, request); 997 } 998 } 999 1000 /** 1001 * @return {@code true} if any other call is disabling the ability to add calls, {@code false} 1002 * otherwise. 1003 */ canAddCall()1004 private boolean canAddCall() { 1005 Collection<Connection> connections = getAllConnections(); 1006 for (Connection connection : connections) { 1007 if (connection.getExtras() != null && 1008 connection.getExtras().getBoolean(Connection.EXTRA_DISABLE_ADD_CALL, false)) { 1009 return false; 1010 } 1011 } 1012 return true; 1013 } 1014 getTelephonyConnection(final ConnectionRequest request, final String number, boolean isEmergencyNumber, final Uri handle, Phone phone)1015 private Connection getTelephonyConnection(final ConnectionRequest request, final String number, 1016 boolean isEmergencyNumber, final Uri handle, Phone phone) { 1017 1018 if (phone == null) { 1019 final Context context = getApplicationContext(); 1020 if (mDeviceState.shouldCheckSimStateBeforeOutgoingCall(this)) { 1021 // Check SIM card state before the outgoing call. 1022 // Start the SIM unlock activity if PIN_REQUIRED. 1023 final Phone defaultPhone = mPhoneFactoryProxy.getDefaultPhone(); 1024 final IccCard icc = defaultPhone.getIccCard(); 1025 IccCardConstants.State simState = IccCardConstants.State.UNKNOWN; 1026 if (icc != null) { 1027 simState = icc.getState(); 1028 } 1029 if (simState == IccCardConstants.State.PIN_REQUIRED) { 1030 final String simUnlockUiPackage = context.getResources().getString( 1031 R.string.config_simUnlockUiPackage); 1032 final String simUnlockUiClass = context.getResources().getString( 1033 R.string.config_simUnlockUiClass); 1034 if (simUnlockUiPackage != null && simUnlockUiClass != null) { 1035 Intent simUnlockIntent = new Intent().setComponent(new ComponentName( 1036 simUnlockUiPackage, simUnlockUiClass)); 1037 simUnlockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 1038 try { 1039 context.startActivity(simUnlockIntent); 1040 } catch (ActivityNotFoundException exception) { 1041 Log.e(this, exception, "Unable to find SIM unlock UI activity."); 1042 } 1043 } 1044 return Connection.createFailedConnection( 1045 mDisconnectCauseFactory.toTelecomDisconnectCause( 1046 android.telephony.DisconnectCause.OUT_OF_SERVICE, 1047 "SIM_STATE_PIN_REQUIRED")); 1048 } 1049 } 1050 1051 Log.d(this, "onCreateOutgoingConnection, phone is null"); 1052 return Connection.createFailedConnection( 1053 mDisconnectCauseFactory.toTelecomDisconnectCause( 1054 android.telephony.DisconnectCause.OUT_OF_SERVICE, "Phone is null")); 1055 } 1056 1057 // Check both voice & data RAT to enable normal CS call, 1058 // when voice RAT is OOS but Data RAT is present. 1059 int state = phone.getServiceState().getState(); 1060 if (state == ServiceState.STATE_OUT_OF_SERVICE) { 1061 int dataNetType = phone.getServiceState().getDataNetworkType(); 1062 if (dataNetType == TelephonyManager.NETWORK_TYPE_LTE || 1063 dataNetType == TelephonyManager.NETWORK_TYPE_LTE_CA) { 1064 state = phone.getServiceState().getDataRegistrationState(); 1065 } 1066 } 1067 1068 // If we're dialing a non-emergency number and the phone is in ECM mode, reject the call if 1069 // carrier configuration specifies that we cannot make non-emergency calls in ECM mode. 1070 if (!isEmergencyNumber && phone.isInEcm()) { 1071 boolean allowNonEmergencyCalls = true; 1072 CarrierConfigManager cfgManager = (CarrierConfigManager) 1073 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 1074 if (cfgManager != null) { 1075 allowNonEmergencyCalls = cfgManager.getConfigForSubId(phone.getSubId()) 1076 .getBoolean(CarrierConfigManager.KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL); 1077 } 1078 1079 if (!allowNonEmergencyCalls) { 1080 return Connection.createFailedConnection( 1081 mDisconnectCauseFactory.toTelecomDisconnectCause( 1082 android.telephony.DisconnectCause.CDMA_NOT_EMERGENCY, 1083 "Cannot make non-emergency call in ECM mode.", 1084 phone.getPhoneId())); 1085 } 1086 } 1087 1088 if (!isEmergencyNumber) { 1089 switch (state) { 1090 case ServiceState.STATE_IN_SERVICE: 1091 case ServiceState.STATE_EMERGENCY_ONLY: 1092 break; 1093 case ServiceState.STATE_OUT_OF_SERVICE: 1094 if (phone.isUtEnabled() && number.endsWith("#")) { 1095 Log.d(this, "onCreateOutgoingConnection dial for UT"); 1096 break; 1097 } else { 1098 return Connection.createFailedConnection( 1099 mDisconnectCauseFactory.toTelecomDisconnectCause( 1100 android.telephony.DisconnectCause.OUT_OF_SERVICE, 1101 "ServiceState.STATE_OUT_OF_SERVICE", 1102 phone.getPhoneId())); 1103 } 1104 case ServiceState.STATE_POWER_OFF: 1105 // Don't disconnect if radio is power off because the device is on Bluetooth. 1106 if (isRadioPowerDownOnBluetooth()) { 1107 break; 1108 } 1109 return Connection.createFailedConnection( 1110 mDisconnectCauseFactory.toTelecomDisconnectCause( 1111 android.telephony.DisconnectCause.POWER_OFF, 1112 "ServiceState.STATE_POWER_OFF", 1113 phone.getPhoneId())); 1114 default: 1115 Log.d(this, "onCreateOutgoingConnection, unknown service state: %d", state); 1116 return Connection.createFailedConnection( 1117 mDisconnectCauseFactory.toTelecomDisconnectCause( 1118 android.telephony.DisconnectCause.OUTGOING_FAILURE, 1119 "Unknown service state " + state, 1120 phone.getPhoneId())); 1121 } 1122 } 1123 1124 final boolean isTtyModeEnabled = mDeviceState.isTtyModeEnabled(this); 1125 if (VideoProfile.isVideo(request.getVideoState()) && isTtyModeEnabled 1126 && !isEmergencyNumber) { 1127 return Connection.createFailedConnection(mDisconnectCauseFactory.toTelecomDisconnectCause( 1128 android.telephony.DisconnectCause.VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED, 1129 null, phone.getPhoneId())); 1130 } 1131 1132 // Check for additional limits on CDMA phones. 1133 final Connection failedConnection = checkAdditionalOutgoingCallLimits(phone); 1134 if (failedConnection != null) { 1135 return failedConnection; 1136 } 1137 1138 // Check roaming status to see if we should block custom call forwarding codes 1139 if (blockCallForwardingNumberWhileRoaming(phone, number)) { 1140 return Connection.createFailedConnection( 1141 mDisconnectCauseFactory.toTelecomDisconnectCause( 1142 android.telephony.DisconnectCause.DIALED_CALL_FORWARDING_WHILE_ROAMING, 1143 "Call forwarding while roaming", 1144 phone.getPhoneId())); 1145 } 1146 1147 1148 final TelephonyConnection connection = 1149 createConnectionFor(phone, null, true /* isOutgoing */, request.getAccountHandle(), 1150 request.getTelecomCallId(), request.isAdhocConferenceCall()); 1151 if (connection == null) { 1152 return Connection.createFailedConnection( 1153 mDisconnectCauseFactory.toTelecomDisconnectCause( 1154 android.telephony.DisconnectCause.OUTGOING_FAILURE, 1155 "Invalid phone type", 1156 phone.getPhoneId())); 1157 } 1158 connection.setAddress(handle, PhoneConstants.PRESENTATION_ALLOWED); 1159 connection.setTelephonyConnectionInitializing(); 1160 connection.setTelephonyVideoState(request.getVideoState()); 1161 connection.setRttTextStream(request.getRttTextStream()); 1162 connection.setTtyEnabled(isTtyModeEnabled); 1163 connection.setIsAdhocConferenceCall(request.isAdhocConferenceCall()); 1164 connection.setParticipants(request.getParticipants()); 1165 return connection; 1166 } 1167 1168 @Override onCreateIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1169 public Connection onCreateIncomingConnection( 1170 PhoneAccountHandle connectionManagerPhoneAccount, 1171 ConnectionRequest request) { 1172 Log.i(this, "onCreateIncomingConnection, request: " + request); 1173 // If there is an incoming emergency CDMA Call (while the phone is in ECBM w/ No SIM), 1174 // make sure the PhoneAccount lookup retrieves the default Emergency Phone. 1175 PhoneAccountHandle accountHandle = request.getAccountHandle(); 1176 boolean isEmergency = false; 1177 if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals( 1178 accountHandle.getId())) { 1179 Log.i(this, "Emergency PhoneAccountHandle is being used for incoming call... " + 1180 "Treat as an Emergency Call."); 1181 isEmergency = true; 1182 } 1183 Phone phone = getPhoneForAccount(accountHandle, isEmergency, 1184 /* Note: when not an emergency, handle can be null for unknown callers */ 1185 request.getAddress() == null ? null : request.getAddress().getSchemeSpecificPart()); 1186 if (phone == null) { 1187 return Connection.createFailedConnection( 1188 mDisconnectCauseFactory.toTelecomDisconnectCause( 1189 android.telephony.DisconnectCause.ERROR_UNSPECIFIED, 1190 "Phone is null")); 1191 } 1192 1193 Call call = phone.getRingingCall(); 1194 if (!call.getState().isRinging()) { 1195 Log.i(this, "onCreateIncomingConnection, no ringing call"); 1196 Connection connection = Connection.createFailedConnection( 1197 mDisconnectCauseFactory.toTelecomDisconnectCause( 1198 android.telephony.DisconnectCause.INCOMING_MISSED, 1199 "Found no ringing call", 1200 phone.getPhoneId())); 1201 Bundle extras = request.getExtras(); 1202 1203 long time = extras.getLong(TelecomManager.EXTRA_CALL_CREATED_EPOCH_TIME_MILLIS); 1204 if (time != 0) { 1205 Log.i(this, "onCreateIncomingConnection. Set connect time info."); 1206 connection.setConnectTimeMillis(time); 1207 } 1208 1209 Uri address = extras.getParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS); 1210 if (address != null) { 1211 Log.i(this, "onCreateIncomingConnection. Set caller id info."); 1212 connection.setAddress(address, TelecomManager.PRESENTATION_ALLOWED); 1213 } 1214 1215 return connection; 1216 } 1217 1218 com.android.internal.telephony.Connection originalConnection = 1219 call.getState() == Call.State.WAITING ? 1220 call.getLatestConnection() : call.getEarliestConnection(); 1221 if (isOriginalConnectionKnown(originalConnection)) { 1222 Log.i(this, "onCreateIncomingConnection, original connection already registered"); 1223 return Connection.createCanceledConnection(); 1224 } 1225 1226 TelephonyConnection connection = 1227 createConnectionFor(phone, originalConnection, false /* isOutgoing */, 1228 request.getAccountHandle(), request.getTelecomCallId(), 1229 request.isAdhocConferenceCall()); 1230 handleIncomingRtt(request, originalConnection); 1231 if (connection == null) { 1232 return Connection.createCanceledConnection(); 1233 } else { 1234 connection.setTtyEnabled(mDeviceState.isTtyModeEnabled(getApplicationContext())); 1235 return connection; 1236 } 1237 } 1238 handleIncomingRtt(ConnectionRequest request, com.android.internal.telephony.Connection originalConnection)1239 private void handleIncomingRtt(ConnectionRequest request, 1240 com.android.internal.telephony.Connection originalConnection) { 1241 if (originalConnection == null 1242 || originalConnection.getPhoneType() != PhoneConstants.PHONE_TYPE_IMS) { 1243 if (request.isRequestingRtt()) { 1244 Log.w(this, "Requesting RTT on non-IMS call, ignoring"); 1245 } 1246 return; 1247 } 1248 1249 ImsPhoneConnection imsOriginalConnection = (ImsPhoneConnection) originalConnection; 1250 if (!request.isRequestingRtt()) { 1251 if (imsOriginalConnection.isRttEnabledForCall()) { 1252 Log.w(this, "Incoming call requested RTT but we did not get a RttTextStream"); 1253 } 1254 return; 1255 } 1256 1257 Log.i(this, "Setting RTT stream on ImsPhoneConnection in case we need it later"); 1258 imsOriginalConnection.setCurrentRttTextStream(request.getRttTextStream()); 1259 1260 if (!imsOriginalConnection.isRttEnabledForCall()) { 1261 if (request.isRequestingRtt()) { 1262 Log.w(this, "Incoming call processed as RTT but did not come in as one. Ignoring"); 1263 } 1264 return; 1265 } 1266 1267 Log.i(this, "Setting the call to be answered with RTT on."); 1268 imsOriginalConnection.getImsCall().setAnswerWithRtt(); 1269 } 1270 1271 /** 1272 * Called by the {@link ConnectionService} when a newly created {@link Connection} has been 1273 * added to the {@link ConnectionService} and sent to Telecom. Here it is safe to send 1274 * connection events. 1275 * 1276 * @param connection the {@link Connection}. 1277 */ 1278 @Override onCreateConnectionComplete(Connection connection)1279 public void onCreateConnectionComplete(Connection connection) { 1280 if (connection instanceof TelephonyConnection) { 1281 TelephonyConnection telephonyConnection = (TelephonyConnection) connection; 1282 maybeSendInternationalCallEvent(telephonyConnection); 1283 } 1284 } 1285 1286 @Override onCreateIncomingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1287 public void onCreateIncomingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount, 1288 ConnectionRequest request) { 1289 Log.i(this, "onCreateIncomingConnectionFailed, request: " + request); 1290 // If there is an incoming emergency CDMA Call (while the phone is in ECBM w/ No SIM), 1291 // make sure the PhoneAccount lookup retrieves the default Emergency Phone. 1292 PhoneAccountHandle accountHandle = request.getAccountHandle(); 1293 boolean isEmergency = false; 1294 if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals( 1295 accountHandle.getId())) { 1296 Log.w(this, "onCreateIncomingConnectionFailed:Emergency call failed... "); 1297 isEmergency = true; 1298 } 1299 Phone phone = getPhoneForAccount(accountHandle, isEmergency, 1300 /* Note: when not an emergency, handle can be null for unknown callers */ 1301 request.getAddress() == null ? null : request.getAddress().getSchemeSpecificPart()); 1302 if (phone == null) { 1303 Log.w(this, "onCreateIncomingConnectionFailed: can not find corresponding phone."); 1304 return; 1305 } 1306 1307 Call call = phone.getRingingCall(); 1308 if (!call.getState().isRinging()) { 1309 Log.w(this, "onCreateIncomingConnectionFailed, no ringing call found for failed call"); 1310 return; 1311 } 1312 1313 com.android.internal.telephony.Connection originalConnection = 1314 call.getState() == Call.State.WAITING 1315 ? call.getLatestConnection() : call.getEarliestConnection(); 1316 TelephonyConnection knownConnection = 1317 getConnectionForOriginalConnection(originalConnection); 1318 if (knownConnection != null) { 1319 Log.w(this, "onCreateIncomingConnectionFailed, original connection already registered." 1320 + " Hanging it up."); 1321 knownConnection.onAbort(); 1322 return; 1323 } 1324 1325 TelephonyConnection connection = 1326 createConnectionFor(phone, originalConnection, false /* isOutgoing */, 1327 request.getAccountHandle(), request.getTelecomCallId()); 1328 if (connection == null) { 1329 Log.w(this, "onCreateIncomingConnectionFailed, TelephonyConnection created as null, " 1330 + "ignoring."); 1331 return; 1332 } 1333 1334 // We have to do all of this work because in some cases, hanging up the call maps to 1335 // different underlying signaling (CDMA), which is already encapsulated in 1336 // TelephonyConnection. 1337 connection.onReject(); 1338 connection.close(); 1339 } 1340 1341 /** 1342 * Called by the {@link ConnectionService} when a newly created {@link Conference} has been 1343 * added to the {@link ConnectionService} and sent to Telecom. Here it is safe to send 1344 * connection events. 1345 * 1346 * @param conference the {@link Conference}. 1347 */ 1348 @Override onCreateConferenceComplete(Conference conference)1349 public void onCreateConferenceComplete(Conference conference) { 1350 if (conference instanceof ImsConference) { 1351 ImsConference imsConference = (ImsConference)conference; 1352 TelephonyConnection telephonyConnection = 1353 (TelephonyConnection)(imsConference.getConferenceHost()); 1354 maybeSendInternationalCallEvent(telephonyConnection); 1355 } 1356 } 1357 onCreateIncomingConferenceFailed(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1358 public void onCreateIncomingConferenceFailed(PhoneAccountHandle connectionManagerPhoneAccount, 1359 ConnectionRequest request) { 1360 Log.i(this, "onCreateIncomingConferenceFailed, request: " + request); 1361 onCreateIncomingConnectionFailed(connectionManagerPhoneAccount, request); 1362 } 1363 1364 @Override triggerConferenceRecalculate()1365 public void triggerConferenceRecalculate() { 1366 if (mTelephonyConferenceController.shouldRecalculate()) { 1367 mTelephonyConferenceController.recalculate(); 1368 } 1369 } 1370 1371 @Override onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1372 public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, 1373 ConnectionRequest request) { 1374 Log.i(this, "onCreateUnknownConnection, request: " + request); 1375 // Use the registered emergency Phone if the PhoneAccountHandle is set to Telephony's 1376 // Emergency PhoneAccount 1377 PhoneAccountHandle accountHandle = request.getAccountHandle(); 1378 boolean isEmergency = false; 1379 if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals( 1380 accountHandle.getId())) { 1381 Log.i(this, "Emergency PhoneAccountHandle is being used for unknown call... " + 1382 "Treat as an Emergency Call."); 1383 isEmergency = true; 1384 } 1385 Phone phone = getPhoneForAccount(accountHandle, isEmergency, 1386 /* Note: when not an emergency, handle can be null for unknown callers */ 1387 request.getAddress() == null ? null : request.getAddress().getSchemeSpecificPart()); 1388 if (phone == null) { 1389 return Connection.createFailedConnection( 1390 mDisconnectCauseFactory.toTelecomDisconnectCause( 1391 android.telephony.DisconnectCause.ERROR_UNSPECIFIED, 1392 "Phone is null")); 1393 } 1394 Bundle extras = request.getExtras(); 1395 1396 final List<com.android.internal.telephony.Connection> allConnections = new ArrayList<>(); 1397 1398 // Handle the case where an unknown connection has an IMS external call ID specified; we can 1399 // skip the rest of the guesswork and just grad that unknown call now. 1400 if (phone.getImsPhone() != null && extras != null && 1401 extras.containsKey(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID)) { 1402 1403 ImsPhone imsPhone = (ImsPhone) phone.getImsPhone(); 1404 ImsExternalCallTracker externalCallTracker = imsPhone.getExternalCallTracker(); 1405 int externalCallId = extras.getInt(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID, 1406 -1); 1407 1408 if (externalCallTracker != null) { 1409 com.android.internal.telephony.Connection connection = 1410 externalCallTracker.getConnectionById(externalCallId); 1411 1412 if (connection != null) { 1413 allConnections.add(connection); 1414 } 1415 } 1416 } 1417 1418 if (allConnections.isEmpty()) { 1419 final Call ringingCall = phone.getRingingCall(); 1420 if (ringingCall.hasConnections()) { 1421 allConnections.addAll(ringingCall.getConnections()); 1422 } 1423 final Call foregroundCall = phone.getForegroundCall(); 1424 if ((foregroundCall.getState() != Call.State.DISCONNECTED) 1425 && (foregroundCall.hasConnections())) { 1426 allConnections.addAll(foregroundCall.getConnections()); 1427 } 1428 if (phone.getImsPhone() != null) { 1429 final Call imsFgCall = phone.getImsPhone().getForegroundCall(); 1430 if ((imsFgCall.getState() != Call.State.DISCONNECTED) && imsFgCall 1431 .hasConnections()) { 1432 allConnections.addAll(imsFgCall.getConnections()); 1433 } 1434 } 1435 final Call backgroundCall = phone.getBackgroundCall(); 1436 if (backgroundCall.hasConnections()) { 1437 allConnections.addAll(phone.getBackgroundCall().getConnections()); 1438 } 1439 } 1440 1441 com.android.internal.telephony.Connection unknownConnection = null; 1442 for (com.android.internal.telephony.Connection telephonyConnection : allConnections) { 1443 if (!isOriginalConnectionKnown(telephonyConnection)) { 1444 unknownConnection = telephonyConnection; 1445 Log.d(this, "onCreateUnknownConnection: conn = " + unknownConnection); 1446 break; 1447 } 1448 } 1449 1450 if (unknownConnection == null) { 1451 Log.i(this, "onCreateUnknownConnection, did not find previously unknown connection."); 1452 return Connection.createCanceledConnection(); 1453 } 1454 1455 // We should rely on the originalConnection to get the video state. The request coming 1456 // from Telecom does not know the video state of the unknown call. 1457 int videoState = unknownConnection != null ? unknownConnection.getVideoState() : 1458 VideoProfile.STATE_AUDIO_ONLY; 1459 1460 TelephonyConnection connection = 1461 createConnectionFor(phone, unknownConnection, 1462 !unknownConnection.isIncoming() /* isOutgoing */, 1463 request.getAccountHandle(), request.getTelecomCallId() 1464 ); 1465 1466 if (connection == null) { 1467 return Connection.createCanceledConnection(); 1468 } else { 1469 connection.updateState(); 1470 return connection; 1471 } 1472 } 1473 1474 /** 1475 * Conferences two connections. 1476 * 1477 * Note: The {@link android.telecom.RemoteConnection#setConferenceableConnections(List)} API has 1478 * a limitation in that it can only specify conferenceables which are instances of 1479 * {@link android.telecom.RemoteConnection}. In the case of an {@link ImsConference}, the 1480 * regular {@link Connection#setConferenceables(List)} API properly handles being able to merge 1481 * a {@link Conference} and a {@link Connection}. As a result when, merging a 1482 * {@link android.telecom.RemoteConnection} into a {@link android.telecom.RemoteConference} 1483 * require merging a {@link ConferenceParticipantConnection} which is a child of the 1484 * {@link Conference} with a {@link TelephonyConnection}. The 1485 * {@link ConferenceParticipantConnection} class does not have the capability to initiate a 1486 * conference merge, so we need to call 1487 * {@link TelephonyConnection#performConference(Connection)} on either {@code connection1} or 1488 * {@code connection2}, one of which is an instance of {@link TelephonyConnection}. 1489 * 1490 * @param connection1 A connection to merge into a conference call. 1491 * @param connection2 A connection to merge into a conference call. 1492 */ 1493 @Override onConference(Connection connection1, Connection connection2)1494 public void onConference(Connection connection1, Connection connection2) { 1495 if (connection1 instanceof TelephonyConnection) { 1496 ((TelephonyConnection) connection1).performConference(connection2); 1497 } else if (connection2 instanceof TelephonyConnection) { 1498 ((TelephonyConnection) connection2).performConference(connection1); 1499 } else { 1500 Log.w(this, "onConference - cannot merge connections " + 1501 "Connection1: %s, Connection2: %2", connection1, connection2); 1502 } 1503 } 1504 1505 @Override onConnectionAdded(Connection connection)1506 public void onConnectionAdded(Connection connection) { 1507 if (connection instanceof Holdable && !isExternalConnection(connection)) { 1508 mHoldTracker.addHoldable( 1509 connection.getPhoneAccountHandle(), (Holdable) connection); 1510 } 1511 } 1512 1513 @Override onConnectionRemoved(Connection connection)1514 public void onConnectionRemoved(Connection connection) { 1515 if (connection instanceof Holdable && !isExternalConnection(connection)) { 1516 mHoldTracker.removeHoldable(connection.getPhoneAccountHandle(), (Holdable) connection); 1517 } 1518 } 1519 1520 @Override onConferenceAdded(Conference conference)1521 public void onConferenceAdded(Conference conference) { 1522 if (conference instanceof Holdable) { 1523 mHoldTracker.addHoldable(conference.getPhoneAccountHandle(), (Holdable) conference); 1524 } 1525 } 1526 1527 @Override onConferenceRemoved(Conference conference)1528 public void onConferenceRemoved(Conference conference) { 1529 if (conference instanceof Holdable) { 1530 mHoldTracker.removeHoldable(conference.getPhoneAccountHandle(), (Holdable) conference); 1531 } 1532 } 1533 isExternalConnection(Connection connection)1534 private boolean isExternalConnection(Connection connection) { 1535 return (connection.getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) 1536 == Connection.PROPERTY_IS_EXTERNAL_CALL; 1537 } 1538 blockCallForwardingNumberWhileRoaming(Phone phone, String number)1539 private boolean blockCallForwardingNumberWhileRoaming(Phone phone, String number) { 1540 if (phone == null || TextUtils.isEmpty(number) || !phone.getServiceState().getRoaming()) { 1541 return false; 1542 } 1543 String[] blockPrefixes = null; 1544 CarrierConfigManager cfgManager = (CarrierConfigManager) 1545 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 1546 if (cfgManager != null) { 1547 blockPrefixes = cfgManager.getConfigForSubId(phone.getSubId()).getStringArray( 1548 CarrierConfigManager.KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY); 1549 } 1550 1551 if (blockPrefixes != null) { 1552 for (String prefix : blockPrefixes) { 1553 if (number.startsWith(prefix)) { 1554 return true; 1555 } 1556 } 1557 } 1558 return false; 1559 } 1560 isRadioOn()1561 private boolean isRadioOn() { 1562 boolean result = false; 1563 for (Phone phone : mPhoneFactoryProxy.getPhones()) { 1564 result |= phone.isRadioOn(); 1565 } 1566 return result; 1567 } 1568 makeCachedConnectionPhonePair( TelephonyConnection c)1569 private Pair<WeakReference<TelephonyConnection>, Queue<Phone>> makeCachedConnectionPhonePair( 1570 TelephonyConnection c) { 1571 Queue<Phone> phones = new LinkedList<>(Arrays.asList(mPhoneFactoryProxy.getPhones())); 1572 return new Pair<>(new WeakReference<>(c), phones); 1573 } 1574 1575 // Update the mEmergencyRetryCache by removing the Phone used to call the last failed emergency 1576 // number and then moving it to the back of the queue if it is not a permanent failure cause 1577 // from the modem. updateCachedConnectionPhonePair(TelephonyConnection c, boolean isPermanentFailure)1578 private void updateCachedConnectionPhonePair(TelephonyConnection c, 1579 boolean isPermanentFailure) { 1580 // No cache exists, create a new one. 1581 if (mEmergencyRetryCache == null) { 1582 Log.i(this, "updateCachedConnectionPhonePair, cache is null. Generating new cache"); 1583 mEmergencyRetryCache = makeCachedConnectionPhonePair(c); 1584 // Cache is stale, create a new one with the new TelephonyConnection. 1585 } else if (mEmergencyRetryCache.first.get() != c) { 1586 Log.i(this, "updateCachedConnectionPhonePair, cache is stale. Regenerating."); 1587 mEmergencyRetryCache = makeCachedConnectionPhonePair(c); 1588 } 1589 1590 Queue<Phone> cachedPhones = mEmergencyRetryCache.second; 1591 // Need to refer default phone considering ImsPhone because 1592 // cachedPhones is a list that contains default phones. 1593 Phone phoneUsed = c.getPhone().getDefaultPhone(); 1594 if (phoneUsed == null) { 1595 return; 1596 } 1597 // Remove phone used from the list, but for temporary fail cause, it will be added 1598 // back to list further in this method. However in case of permanent failure, the 1599 // phone shouldn't be reused, hence it will not be added back again. 1600 cachedPhones.remove(phoneUsed); 1601 Log.i(this, "updateCachedConnectionPhonePair, isPermanentFailure:" + isPermanentFailure); 1602 if (!isPermanentFailure) { 1603 // In case of temporary failure, add the phone back, this will result adding it 1604 // to tail of list mEmergencyRetryCache.second, giving other phone more 1605 // priority and that is what we want. 1606 cachedPhones.offer(phoneUsed); 1607 } 1608 } 1609 1610 /** 1611 * Updates a cache containing all of the slots that are available for redial at any point. 1612 * 1613 * - If a Connection returns with the disconnect cause EMERGENCY_TEMP_FAILURE, keep that phone 1614 * in the cache, but move it to the lowest priority in the list. Then, place the emergency call 1615 * on the next phone in the list. 1616 * - If a Connection returns with the disconnect cause EMERGENCY_PERM_FAILURE, remove that phone 1617 * from the cache and pull another phone from the cache to place the emergency call. 1618 * 1619 * This will continue until there are no more slots to dial on. 1620 */ 1621 @VisibleForTesting retryOutgoingOriginalConnection(TelephonyConnection c, boolean isPermanentFailure)1622 public void retryOutgoingOriginalConnection(TelephonyConnection c, boolean isPermanentFailure) { 1623 int phoneId = (c.getPhone() == null) ? -1 : c.getPhone().getPhoneId(); 1624 updateCachedConnectionPhonePair(c, isPermanentFailure); 1625 // Pull next phone to use from the cache or null if it is empty 1626 Phone newPhoneToUse = (mEmergencyRetryCache.second != null) 1627 ? mEmergencyRetryCache.second.peek() : null; 1628 if (newPhoneToUse != null) { 1629 int videoState = c.getVideoState(); 1630 Bundle connExtras = c.getExtras(); 1631 Log.i(this, "retryOutgoingOriginalConnection, redialing on Phone Id: " + newPhoneToUse); 1632 c.clearOriginalConnection(); 1633 if (phoneId != newPhoneToUse.getPhoneId()) updatePhoneAccount(c, newPhoneToUse); 1634 placeOutgoingConnection(c, newPhoneToUse, videoState, connExtras); 1635 } else { 1636 // We have run out of Phones to use. Disconnect the call and destroy the connection. 1637 Log.i(this, "retryOutgoingOriginalConnection, no more Phones to use. Disconnecting."); 1638 closeOrDestroyConnection(c, new DisconnectCause(DisconnectCause.ERROR)); 1639 } 1640 } 1641 updatePhoneAccount(TelephonyConnection connection, Phone phone)1642 private void updatePhoneAccount(TelephonyConnection connection, Phone phone) { 1643 PhoneAccountHandle pHandle = mPhoneUtilsProxy.makePstnPhoneAccountHandle(phone); 1644 // For ECall handling on MSIM, until the request reaches here (i.e PhoneApp), we don't know 1645 // on which phone account ECall can be placed. After deciding, we should notify Telecom of 1646 // the change so that the proper PhoneAccount can be displayed. 1647 Log.i(this, "updatePhoneAccount setPhoneAccountHandle, account = " + pHandle); 1648 connection.setPhoneAccountHandle(pHandle); 1649 } 1650 placeOutgoingConnection( TelephonyConnection connection, Phone phone, ConnectionRequest request)1651 private void placeOutgoingConnection( 1652 TelephonyConnection connection, Phone phone, ConnectionRequest request) { 1653 placeOutgoingConnection(connection, phone, request.getVideoState(), request.getExtras()); 1654 } 1655 placeOutgoingConnection( TelephonyConnection connection, Phone phone, int videoState, Bundle extras)1656 private void placeOutgoingConnection( 1657 TelephonyConnection connection, Phone phone, int videoState, Bundle extras) { 1658 1659 String number = (connection.getAddress() != null) 1660 ? connection.getAddress().getSchemeSpecificPart() 1661 : ""; 1662 1663 if (showDataDialog(phone, number)) { 1664 connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 1665 android.telephony.DisconnectCause.DIALED_MMI, "UT is not available")); 1666 return; 1667 } 1668 1669 com.android.internal.telephony.Connection originalConnection = null; 1670 try { 1671 if (phone != null) { 1672 EmergencyNumber emergencyNumber = 1673 phone.getEmergencyNumberTracker().getEmergencyNumber(number); 1674 if (emergencyNumber != null) { 1675 phone.notifyOutgoingEmergencyCall(emergencyNumber); 1676 if (!getAllConnections().isEmpty()) { 1677 if (!shouldHoldForEmergencyCall(phone)) { 1678 // If we do not support holding ongoing calls for an outgoing 1679 // emergency call, disconnect the ongoing calls. 1680 for (Connection c : getAllConnections()) { 1681 if (!c.equals(connection) 1682 && c.getState() != Connection.STATE_DISCONNECTED 1683 && c instanceof TelephonyConnection) { 1684 ((TelephonyConnection) c).hangup( 1685 android.telephony.DisconnectCause 1686 .OUTGOING_EMERGENCY_CALL_PLACED); 1687 } 1688 } 1689 for (Conference c : getAllConferences()) { 1690 if (c.getState() != Connection.STATE_DISCONNECTED 1691 && c instanceof Conference) { 1692 ((Conference) c).onDisconnect(); 1693 } 1694 } 1695 } else if (!isVideoCallHoldAllowed(phone)) { 1696 // If we do not support holding ongoing video call for an outgoing 1697 // emergency call, disconnect the ongoing video call. 1698 for (Connection c : getAllConnections()) { 1699 if (!c.equals(connection) 1700 && c.getState() == Connection.STATE_ACTIVE 1701 && VideoProfile.isVideo(c.getVideoState()) 1702 && c instanceof TelephonyConnection) { 1703 ((TelephonyConnection) c).hangup( 1704 android.telephony.DisconnectCause 1705 .OUTGOING_EMERGENCY_CALL_PLACED); 1706 break; 1707 } 1708 } 1709 } 1710 } 1711 } 1712 originalConnection = phone.dial(number, new ImsPhone.ImsDialArgs.Builder() 1713 .setVideoState(videoState) 1714 .setIntentExtras(extras) 1715 .setRttTextStream(connection.getRttTextStream()) 1716 .build()); 1717 } 1718 } catch (CallStateException e) { 1719 Log.e(this, e, "placeOutgoingConnection, phone.dial exception: " + e); 1720 handleCallStateException(e, connection, phone); 1721 return; 1722 } 1723 1724 if (originalConnection == null) { 1725 int telephonyDisconnectCause = android.telephony.DisconnectCause.OUTGOING_FAILURE; 1726 // On GSM phones, null connection means that we dialed an MMI code 1727 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM || 1728 phone.isUtEnabled()) { 1729 Log.d(this, "dialed MMI code"); 1730 int subId = phone.getSubId(); 1731 Log.d(this, "subId: "+subId); 1732 telephonyDisconnectCause = android.telephony.DisconnectCause.DIALED_MMI; 1733 final Intent intent = new Intent(this, MMIDialogActivity.class); 1734 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 1735 Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 1736 if (SubscriptionManager.isValidSubscriptionId(subId)) { 1737 SubscriptionManager.putSubscriptionIdExtra(intent, subId); 1738 } 1739 startActivity(intent); 1740 } 1741 Log.d(this, "placeOutgoingConnection, phone.dial returned null"); 1742 connection.setTelephonyConnectionDisconnected( 1743 mDisconnectCauseFactory.toTelecomDisconnectCause(telephonyDisconnectCause, 1744 "Connection is null", phone.getPhoneId())); 1745 connection.close(); 1746 } else { 1747 connection.setOriginalConnection(originalConnection); 1748 } 1749 } 1750 isVideoCallHoldAllowed(Phone phone)1751 private boolean isVideoCallHoldAllowed(Phone phone) { 1752 CarrierConfigManager cfgManager = (CarrierConfigManager) 1753 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 1754 if (cfgManager == null) { 1755 // For some reason CarrierConfigManager is unavailable, return default 1756 Log.w(this, "isVideoCallHoldAllowed: couldn't get CarrierConfigManager"); 1757 return true; 1758 } 1759 return cfgManager.getConfigForSubId(phone.getSubId()).getBoolean( 1760 CarrierConfigManager.KEY_ALLOW_HOLD_VIDEO_CALL_BOOL, true); 1761 } 1762 shouldHoldForEmergencyCall(Phone phone)1763 private boolean shouldHoldForEmergencyCall(Phone phone) { 1764 CarrierConfigManager cfgManager = (CarrierConfigManager) 1765 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 1766 if (cfgManager == null) { 1767 // For some reason CarrierConfigManager is unavailable, return default 1768 Log.w(this, "shouldHoldForEmergencyCall: couldn't get CarrierConfigManager"); 1769 return true; 1770 } 1771 return cfgManager.getConfigForSubId(phone.getSubId()).getBoolean( 1772 CarrierConfigManager.KEY_ALLOW_HOLD_CALL_DURING_EMERGENCY_BOOL, true); 1773 } 1774 handleCallStateException(CallStateException e, TelephonyConnection connection, Phone phone)1775 private void handleCallStateException(CallStateException e, TelephonyConnection connection, 1776 Phone phone) { 1777 int cause = android.telephony.DisconnectCause.OUTGOING_FAILURE; 1778 switch (e.getError()) { 1779 case CallStateException.ERROR_OUT_OF_SERVICE: 1780 cause = android.telephony.DisconnectCause.OUT_OF_SERVICE; 1781 break; 1782 case CallStateException.ERROR_POWER_OFF: 1783 cause = android.telephony.DisconnectCause.POWER_OFF; 1784 break; 1785 case CallStateException.ERROR_ALREADY_DIALING: 1786 cause = android.telephony.DisconnectCause.ALREADY_DIALING; 1787 break; 1788 case CallStateException.ERROR_CALL_RINGING: 1789 cause = android.telephony.DisconnectCause.CANT_CALL_WHILE_RINGING; 1790 break; 1791 case CallStateException.ERROR_CALLING_DISABLED: 1792 cause = android.telephony.DisconnectCause.CALLING_DISABLED; 1793 break; 1794 case CallStateException.ERROR_TOO_MANY_CALLS: 1795 cause = android.telephony.DisconnectCause.TOO_MANY_ONGOING_CALLS; 1796 break; 1797 case CallStateException.ERROR_OTASP_PROVISIONING_IN_PROCESS: 1798 cause = android.telephony.DisconnectCause.OTASP_PROVISIONING_IN_PROCESS; 1799 break; 1800 } 1801 connection.setTelephonyConnectionDisconnected( 1802 DisconnectCauseUtil.toTelecomDisconnectCause(cause, e.getMessage(), 1803 phone.getPhoneId())); 1804 connection.close(); 1805 } 1806 createConnectionFor( Phone phone, com.android.internal.telephony.Connection originalConnection, boolean isOutgoing, PhoneAccountHandle phoneAccountHandle, String telecomCallId)1807 private TelephonyConnection createConnectionFor( 1808 Phone phone, 1809 com.android.internal.telephony.Connection originalConnection, 1810 boolean isOutgoing, 1811 PhoneAccountHandle phoneAccountHandle, 1812 String telecomCallId) { 1813 return createConnectionFor(phone, originalConnection, isOutgoing, phoneAccountHandle, 1814 telecomCallId, false); 1815 } 1816 createConnectionFor( Phone phone, com.android.internal.telephony.Connection originalConnection, boolean isOutgoing, PhoneAccountHandle phoneAccountHandle, String telecomCallId, boolean isAdhocConference)1817 private TelephonyConnection createConnectionFor( 1818 Phone phone, 1819 com.android.internal.telephony.Connection originalConnection, 1820 boolean isOutgoing, 1821 PhoneAccountHandle phoneAccountHandle, 1822 String telecomCallId, 1823 boolean isAdhocConference) { 1824 TelephonyConnection returnConnection = null; 1825 int phoneType = phone.getPhoneType(); 1826 int callDirection = isOutgoing ? android.telecom.Call.Details.DIRECTION_OUTGOING 1827 : android.telecom.Call.Details.DIRECTION_INCOMING; 1828 if (phoneType == TelephonyManager.PHONE_TYPE_GSM) { 1829 returnConnection = new GsmConnection(originalConnection, telecomCallId, callDirection); 1830 } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) { 1831 boolean allowsMute = allowsMute(phone); 1832 returnConnection = new CdmaConnection(originalConnection, mEmergencyTonePlayer, 1833 allowsMute, callDirection, telecomCallId); 1834 } 1835 if (returnConnection != null) { 1836 if (!isAdhocConference) { 1837 // Listen to Telephony specific callbacks from the connection 1838 returnConnection.addTelephonyConnectionListener(mTelephonyConnectionListener); 1839 } 1840 returnConnection.setVideoPauseSupported( 1841 TelecomAccountRegistry.getInstance(this).isVideoPauseSupported( 1842 phoneAccountHandle)); 1843 returnConnection.setManageImsConferenceCallSupported( 1844 TelecomAccountRegistry.getInstance(this).isManageImsConferenceCallSupported( 1845 phoneAccountHandle)); 1846 returnConnection.setShowPreciseFailedCause( 1847 TelecomAccountRegistry.getInstance(this).isShowPreciseFailedCause( 1848 phoneAccountHandle)); 1849 returnConnection.setTelephonyConnectionService(this); 1850 } 1851 return returnConnection; 1852 } 1853 isOriginalConnectionKnown( com.android.internal.telephony.Connection originalConnection)1854 private boolean isOriginalConnectionKnown( 1855 com.android.internal.telephony.Connection originalConnection) { 1856 return (getConnectionForOriginalConnection(originalConnection) != null); 1857 } 1858 getConnectionForOriginalConnection( com.android.internal.telephony.Connection originalConnection)1859 private TelephonyConnection getConnectionForOriginalConnection( 1860 com.android.internal.telephony.Connection originalConnection) { 1861 for (Connection connection : getAllConnections()) { 1862 if (connection instanceof TelephonyConnection) { 1863 TelephonyConnection telephonyConnection = (TelephonyConnection) connection; 1864 if (telephonyConnection.getOriginalConnection() == originalConnection) { 1865 return telephonyConnection; 1866 } 1867 } 1868 } 1869 return null; 1870 } 1871 1872 /** 1873 * Determines which {@link Phone} will be used to place the call. 1874 * @param accountHandle The {@link PhoneAccountHandle} which was sent from Telecom to place the 1875 * call on. 1876 * @param isEmergency {@code true} if this is an emergency call, {@code false} otherwise. 1877 * @param emergencyNumberAddress When {@code isEmergency} is {@code true}, will be the phone 1878 * of the emergency call. Otherwise, this can be {@code null} . 1879 * @return 1880 */ getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency, @Nullable String emergencyNumberAddress)1881 private Phone getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency, 1882 @Nullable String emergencyNumberAddress) { 1883 Phone chosenPhone = null; 1884 int subId = mPhoneUtilsProxy.getSubIdForPhoneAccountHandle(accountHandle); 1885 if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 1886 int phoneId = mSubscriptionManagerProxy.getPhoneId(subId); 1887 chosenPhone = mPhoneFactoryProxy.getPhone(phoneId); 1888 } 1889 // If this is an emergency call and the phone we originally planned to make this call 1890 // with is not in service or was invalid, try to find one that is in service, using the 1891 // default as a last chance backup. 1892 if (isEmergency && (chosenPhone == null || !isAvailableForEmergencyCalls(chosenPhone))) { 1893 Log.d(this, "getPhoneForAccount: phone for phone acct handle %s is out of service " 1894 + "or invalid for emergency call.", accountHandle); 1895 chosenPhone = getPhoneForEmergencyCall(emergencyNumberAddress); 1896 Log.d(this, "getPhoneForAccount: using subId: " + 1897 (chosenPhone == null ? "null" : chosenPhone.getSubId())); 1898 } 1899 return chosenPhone; 1900 } 1901 1902 /** 1903 * If needed, block until the the default data is is switched for outgoing emergency call, or 1904 * timeout expires. 1905 */ delayDialForDdsSwitch(Phone phone)1906 private boolean delayDialForDdsSwitch(Phone phone) { 1907 if (phone == null) { 1908 return true; 1909 } 1910 try { 1911 return possiblyOverrideDefaultDataForEmergencyCall(phone).get( 1912 DEFAULT_DATA_SWITCH_TIMEOUT_MS, TimeUnit.MILLISECONDS); 1913 } catch (Exception e) { 1914 Log.w(this, "delayDialForDdsSwitch - exception= " 1915 + e.getMessage()); 1916 return false; 1917 } 1918 } 1919 1920 /** 1921 * If needed, block until Default Data subscription is switched for outgoing emergency call. 1922 * 1923 * In some cases, we need to try to switch the Default Data subscription before placing the 1924 * emergency call on DSDS devices. This includes the following situation: 1925 * - The modem does not support processing GNSS SUPL requests on the non-default data 1926 * subscription. For some carriers that do not provide a control plane fallback mechanism, the 1927 * SUPL request will be dropped and we will not be able to get the user's location for the 1928 * emergency call. In this case, we need to swap default data temporarily. 1929 * @param phone Evaluates whether or not the default data should be moved to the phone 1930 * specified. Should not be null. 1931 */ possiblyOverrideDefaultDataForEmergencyCall( @onNull Phone phone)1932 private CompletableFuture<Boolean> possiblyOverrideDefaultDataForEmergencyCall( 1933 @NonNull Phone phone) { 1934 int phoneCount = mTelephonyManagerProxy.getPhoneCount(); 1935 // Do not override DDS if this is a single SIM device. 1936 if (phoneCount <= PhoneConstants.MAX_PHONE_COUNT_SINGLE_SIM) { 1937 return CompletableFuture.completedFuture(Boolean.TRUE); 1938 } 1939 1940 // Do not switch Default data if this device supports emergency SUPL on non-DDS. 1941 final boolean gnssSuplRequiresDefaultData = 1942 mDeviceState.isSuplDdsSwitchRequiredForEmergencyCall(this); 1943 if (!gnssSuplRequiresDefaultData) { 1944 Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS, does not " 1945 + "require DDS switch."); 1946 return CompletableFuture.completedFuture(Boolean.TRUE); 1947 } 1948 1949 CarrierConfigManager cfgManager = (CarrierConfigManager) 1950 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 1951 if (cfgManager == null) { 1952 // For some reason CarrierConfigManager is unavailable. Do not block emergency call. 1953 Log.w(this, "possiblyOverrideDefaultDataForEmergencyCall: couldn't get" 1954 + "CarrierConfigManager"); 1955 return CompletableFuture.completedFuture(Boolean.TRUE); 1956 } 1957 1958 // Only override default data if we are IN_SERVICE already. 1959 if (!isAvailableForEmergencyCalls(phone)) { 1960 Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS"); 1961 return CompletableFuture.completedFuture(Boolean.TRUE); 1962 } 1963 1964 // Only override default data if we are not roaming, we do not want to switch onto a network 1965 // that only supports data plane only (if we do not know). 1966 boolean isRoaming = phone.getServiceState().getVoiceRoaming(); 1967 // In some roaming conditions, we know the roaming network doesn't support control plane 1968 // fallback even though the home operator does. For these operators we will need to do a DDS 1969 // switch anyway to make sure the SUPL request doesn't fail. 1970 boolean roamingNetworkSupportsControlPlaneFallback = true; 1971 String[] dataPlaneRoamPlmns = cfgManager.getConfigForSubId(phone.getSubId()).getStringArray( 1972 CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY); 1973 if (dataPlaneRoamPlmns != null && Arrays.asList(dataPlaneRoamPlmns).contains( 1974 phone.getServiceState().getOperatorNumeric())) { 1975 roamingNetworkSupportsControlPlaneFallback = false; 1976 } 1977 if (isRoaming && roamingNetworkSupportsControlPlaneFallback) { 1978 Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: roaming network is assumed " 1979 + "to support CP fallback, not switching DDS."); 1980 return CompletableFuture.completedFuture(Boolean.TRUE); 1981 } 1982 // Do not try to swap default data if we support CS fallback or it is assumed that the 1983 // roaming network supports control plane fallback, we do not want to introduce 1984 // a lag in emergency call setup time if possible. 1985 final boolean supportsCpFallback = cfgManager.getConfigForSubId(phone.getSubId()) 1986 .getInt(CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT, 1987 CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_ONLY) 1988 != CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY; 1989 if (supportsCpFallback && roamingNetworkSupportsControlPlaneFallback) { 1990 Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS, carrier " 1991 + "supports CP fallback."); 1992 return CompletableFuture.completedFuture(Boolean.TRUE); 1993 } 1994 1995 // Get extension time, may be 0 for some carriers that support ECBM as well. Use 1996 // CarrierConfig default if format fails. 1997 int extensionTime = 0; 1998 try { 1999 extensionTime = Integer.parseInt(cfgManager.getConfigForSubId(phone.getSubId()) 2000 .getString(CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "0")); 2001 } catch (NumberFormatException e) { 2002 // Just use default. 2003 } 2004 CompletableFuture<Boolean> modemResultFuture = new CompletableFuture<>(); 2005 try { 2006 Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: overriding DDS for " 2007 + extensionTime + "seconds"); 2008 mPhoneSwitcherProxy.getPhoneSwitcher().overrideDefaultDataForEmergency( 2009 phone.getPhoneId(), extensionTime, modemResultFuture); 2010 // Catch all exceptions, we want to continue with emergency call if possible. 2011 } catch (Exception e) { 2012 Log.w(this, "possiblyOverrideDefaultDataForEmergencyCall: exception = " 2013 + e.getMessage()); 2014 modemResultFuture = CompletableFuture.completedFuture(Boolean.FALSE); 2015 } 2016 return modemResultFuture; 2017 } 2018 2019 /** 2020 * Get the Phone to use for an emergency call of the given emergency number address: 2021 * a) If there are multiple Phones with the Subscriptions that support the emergency number 2022 * address, and one of them is the default voice Phone, consider the default voice phone 2023 * if 1.4 HAL is supported, or if it is available for emergency call. 2024 * b) If there are multiple Phones with the Subscriptions that support the emergency number 2025 * address, and none of them is the default voice Phone, use one of these Phones if 1.4 HAL 2026 * is supported, or if it is available for emergency call. 2027 * c) If there is no Phone that supports the emergency call for the address, use the defined 2028 * Priority list to select the Phone via {@link #getFirstPhoneForEmergencyCall}. 2029 */ getPhoneForEmergencyCall(String emergencyNumberAddress)2030 public Phone getPhoneForEmergencyCall(String emergencyNumberAddress) { 2031 // Find the list of available Phones for the given emergency number address 2032 List<Phone> potentialEmergencyPhones = new ArrayList<>(); 2033 int defaultVoicePhoneId = mSubscriptionManagerProxy.getDefaultVoicePhoneId(); 2034 for (Phone phone : mPhoneFactoryProxy.getPhones()) { 2035 if (phone.getEmergencyNumberTracker() != null) { 2036 if (phone.getEmergencyNumberTracker().isEmergencyNumber( 2037 emergencyNumberAddress, true)) { 2038 if (phone.getHalVersion().greaterOrEqual(RIL.RADIO_HAL_VERSION_1_4) 2039 || isAvailableForEmergencyCalls(phone)) { 2040 // a) 2041 if (phone.getPhoneId() == defaultVoicePhoneId) { 2042 Log.i(this, "getPhoneForEmergencyCall, Phone Id that supports" 2043 + " emergency number: " + phone.getPhoneId()); 2044 return phone; 2045 } 2046 potentialEmergencyPhones.add(phone); 2047 } 2048 } 2049 } 2050 } 2051 // b) 2052 if (potentialEmergencyPhones.size() > 0) { 2053 Log.i(this, "getPhoneForEmergencyCall, Phone Id that supports emergency number:" 2054 + potentialEmergencyPhones.get(0).getPhoneId()); 2055 return getFirstPhoneForEmergencyCall(potentialEmergencyPhones); 2056 } 2057 // c) 2058 return getFirstPhoneForEmergencyCall(); 2059 } 2060 2061 @VisibleForTesting getFirstPhoneForEmergencyCall()2062 public Phone getFirstPhoneForEmergencyCall() { 2063 return getFirstPhoneForEmergencyCall(null); 2064 } 2065 2066 /** 2067 * Retrieves the most sensible Phone to use for an emergency call using the following Priority 2068 * list (for multi-SIM devices): 2069 * 1) The User's SIM preference for Voice calling 2070 * 2) The First Phone that is currently IN_SERVICE or is available for emergency calling 2071 * 3) Prioritize phones that have the dialed emergency number as part of their emergency 2072 * number list 2073 * 4) If there is a PUK locked SIM, compare the SIMs that are not PUK locked. If all the SIMs 2074 * are locked, skip to condition 5). 2075 * 5) The Phone with more Capabilities. 2076 * 6) The First Phone that has a SIM card in it (Starting from Slot 0...N) 2077 * 7) The Default Phone (Currently set as Slot 0) 2078 */ 2079 @VisibleForTesting getFirstPhoneForEmergencyCall(List<Phone> phonesWithEmergencyNumber)2080 public Phone getFirstPhoneForEmergencyCall(List<Phone> phonesWithEmergencyNumber) { 2081 // 1) 2082 int phoneId = mSubscriptionManagerProxy.getDefaultVoicePhoneId(); 2083 if (phoneId != SubscriptionManager.INVALID_PHONE_INDEX) { 2084 Phone defaultPhone = mPhoneFactoryProxy.getPhone(phoneId); 2085 if (defaultPhone != null && isAvailableForEmergencyCalls(defaultPhone)) { 2086 if (phonesWithEmergencyNumber == null 2087 || phonesWithEmergencyNumber.contains(defaultPhone)) { 2088 return defaultPhone; 2089 } 2090 } 2091 } 2092 2093 Phone firstPhoneWithSim = null; 2094 int phoneCount = mTelephonyManagerProxy.getPhoneCount(); 2095 List<SlotStatus> phoneSlotStatus = new ArrayList<>(phoneCount); 2096 for (int i = 0; i < phoneCount; i++) { 2097 Phone phone = mPhoneFactoryProxy.getPhone(i); 2098 if (phone == null) { 2099 continue; 2100 } 2101 // 2) 2102 if (isAvailableForEmergencyCalls(phone)) { 2103 if (phonesWithEmergencyNumber == null 2104 || phonesWithEmergencyNumber.contains(phone)) { 2105 // the slot has the radio on & state is in service. 2106 Log.i(this, 2107 "getFirstPhoneForEmergencyCall, radio on & in service, Phone Id:" + i); 2108 return phone; 2109 } 2110 } 2111 // 5) 2112 // Store the RAF Capabilities for sorting later. 2113 int radioAccessFamily = phone.getRadioAccessFamily(); 2114 SlotStatus status = new SlotStatus(i, radioAccessFamily); 2115 phoneSlotStatus.add(status); 2116 Log.i(this, "getFirstPhoneForEmergencyCall, RAF:" + 2117 Integer.toHexString(radioAccessFamily) + " saved for Phone Id:" + i); 2118 // 4) 2119 // Report Slot's PIN/PUK lock status for sorting later. 2120 int simState = mSubscriptionManagerProxy.getSimStateForSlotIdx(i); 2121 // Record SimState. 2122 status.simState = simState; 2123 if (simState == TelephonyManager.SIM_STATE_PIN_REQUIRED || 2124 simState == TelephonyManager.SIM_STATE_PUK_REQUIRED) { 2125 status.isLocked = true; 2126 } 2127 // 3) Store if the Phone has the corresponding emergency number 2128 if (phonesWithEmergencyNumber != null) { 2129 for (Phone phoneWithEmergencyNumber : phonesWithEmergencyNumber) { 2130 if (phoneWithEmergencyNumber != null 2131 && phoneWithEmergencyNumber.getPhoneId() == i) { 2132 status.hasDialedEmergencyNumber = true; 2133 } 2134 } 2135 } 2136 // 6) 2137 if (firstPhoneWithSim == null && mTelephonyManagerProxy.hasIccCard(i)) { 2138 // The slot has a SIM card inserted, but is not in service, so keep track of this 2139 // Phone. Do not return because we want to make sure that none of the other Phones 2140 // are in service (because that is always faster). 2141 firstPhoneWithSim = phone; 2142 Log.i(this, "getFirstPhoneForEmergencyCall, SIM card inserted, Phone Id:" + 2143 firstPhoneWithSim.getPhoneId()); 2144 } 2145 } 2146 // 7) 2147 if (firstPhoneWithSim == null && phoneSlotStatus.isEmpty()) { 2148 if (phonesWithEmergencyNumber == null || phonesWithEmergencyNumber.isEmpty()) { 2149 // No Phones available, get the default 2150 Log.i(this, "getFirstPhoneForEmergencyCall, return default phone"); 2151 return mPhoneFactoryProxy.getDefaultPhone(); 2152 } 2153 return phonesWithEmergencyNumber.get(0); 2154 } else { 2155 // 5) 2156 final int defaultPhoneId = mPhoneFactoryProxy.getDefaultPhone().getPhoneId(); 2157 final Phone firstOccupiedSlot = firstPhoneWithSim; 2158 if (!phoneSlotStatus.isEmpty()) { 2159 // Only sort if there are enough elements to do so. 2160 if (phoneSlotStatus.size() > 1) { 2161 Collections.sort(phoneSlotStatus, (o1, o2) -> { 2162 if (!o1.hasDialedEmergencyNumber && o2.hasDialedEmergencyNumber) { 2163 return -1; 2164 } 2165 if (o1.hasDialedEmergencyNumber && !o2.hasDialedEmergencyNumber) { 2166 return 1; 2167 } 2168 // Sort by non-absent SIM. 2169 if (o1.simState == TelephonyManager.SIM_STATE_ABSENT 2170 && o2.simState != TelephonyManager.SIM_STATE_ABSENT) { 2171 return -1; 2172 } 2173 if (o2.simState == TelephonyManager.SIM_STATE_ABSENT 2174 && o1.simState != TelephonyManager.SIM_STATE_ABSENT) { 2175 return 1; 2176 } 2177 // First start by seeing if either of the phone slots are locked. If they 2178 // are, then sort by non-locked SIM first. If they are both locked, sort 2179 // by capability instead. 2180 if (o1.isLocked && !o2.isLocked) { 2181 return -1; 2182 } 2183 if (o2.isLocked && !o1.isLocked) { 2184 return 1; 2185 } 2186 // sort by number of RadioAccessFamily Capabilities. 2187 int compare = RadioAccessFamily.compare(o1.capabilities, o2.capabilities); 2188 if (compare == 0) { 2189 if (firstOccupiedSlot != null) { 2190 // If the RAF capability is the same, choose based on whether or 2191 // not any of the slots are occupied with a SIM card (if both 2192 // are, always choose the first). 2193 if (o1.slotId == firstOccupiedSlot.getPhoneId()) { 2194 return 1; 2195 } else if (o2.slotId == firstOccupiedSlot.getPhoneId()) { 2196 return -1; 2197 } 2198 } else { 2199 // No slots have SIMs detected in them, so weight the default 2200 // Phone Id greater than the others. 2201 if (o1.slotId == defaultPhoneId) { 2202 return 1; 2203 } else if (o2.slotId == defaultPhoneId) { 2204 return -1; 2205 } 2206 } 2207 } 2208 return compare; 2209 }); 2210 } 2211 int mostCapablePhoneId = phoneSlotStatus.get(phoneSlotStatus.size() - 1).slotId; 2212 Log.i(this, "getFirstPhoneForEmergencyCall, Using Phone Id: " + mostCapablePhoneId + 2213 "with highest capability"); 2214 return mPhoneFactoryProxy.getPhone(mostCapablePhoneId); 2215 } else { 2216 // 6) 2217 return firstPhoneWithSim; 2218 } 2219 } 2220 } 2221 2222 /** 2223 * Returns true if the state of the Phone is IN_SERVICE or available for emergency calling only. 2224 */ isAvailableForEmergencyCalls(Phone phone)2225 private boolean isAvailableForEmergencyCalls(Phone phone) { 2226 return ServiceState.STATE_IN_SERVICE == phone.getServiceState().getState() || 2227 phone.getServiceState().isEmergencyOnly(); 2228 } 2229 2230 /** 2231 * Determines if the connection should allow mute. 2232 * 2233 * @param phone The current phone. 2234 * @return {@code True} if the connection should allow mute. 2235 */ allowsMute(Phone phone)2236 private boolean allowsMute(Phone phone) { 2237 // For CDMA phones, check if we are in Emergency Callback Mode (ECM). Mute is disallowed 2238 // in ECM mode. 2239 if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) { 2240 if (phone.isInEcm()) { 2241 return false; 2242 } 2243 } 2244 2245 return true; 2246 } 2247 getTelephonyConnectionListener()2248 TelephonyConnection.TelephonyConnectionListener getTelephonyConnectionListener() { 2249 return mTelephonyConnectionListener; 2250 } 2251 2252 /** 2253 * When a {@link TelephonyConnection} has its underlying original connection configured, 2254 * we need to add it to the correct conference controller. 2255 * 2256 * @param connection The connection to be added to the controller 2257 */ addConnectionToConferenceController(TelephonyConnection connection)2258 public void addConnectionToConferenceController(TelephonyConnection connection) { 2259 // TODO: Need to revisit what happens when the original connection for the 2260 // TelephonyConnection changes. If going from CDMA --> GSM (for example), the 2261 // instance of TelephonyConnection will still be a CdmaConnection, not a GsmConnection. 2262 // The CDMA conference controller makes the assumption that it will only have CDMA 2263 // connections in it, while the other conference controllers aren't as restrictive. Really, 2264 // when we go between CDMA and GSM we should replace the TelephonyConnection. 2265 if (connection.isImsConnection()) { 2266 Log.d(this, "Adding IMS connection to conference controller: " + connection); 2267 mImsConferenceController.add(connection); 2268 mTelephonyConferenceController.remove(connection); 2269 if (connection instanceof CdmaConnection) { 2270 mCdmaConferenceController.remove((CdmaConnection) connection); 2271 } 2272 } else { 2273 int phoneType = connection.getCall().getPhone().getPhoneType(); 2274 if (phoneType == TelephonyManager.PHONE_TYPE_GSM) { 2275 Log.d(this, "Adding GSM connection to conference controller: " + connection); 2276 mTelephonyConferenceController.add(connection); 2277 if (connection instanceof CdmaConnection) { 2278 mCdmaConferenceController.remove((CdmaConnection) connection); 2279 } 2280 } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA && 2281 connection instanceof CdmaConnection) { 2282 Log.d(this, "Adding CDMA connection to conference controller: " + connection); 2283 mCdmaConferenceController.add((CdmaConnection) connection); 2284 mTelephonyConferenceController.remove(connection); 2285 } 2286 Log.d(this, "Removing connection from IMS conference controller: " + connection); 2287 mImsConferenceController.remove(connection); 2288 } 2289 } 2290 2291 /** 2292 * Create a new CDMA connection. CDMA connections have additional limitations when creating 2293 * additional calls which are handled in this method. Specifically, CDMA has a "FLASH" command 2294 * that can be used for three purposes: merging a call, swapping unmerged calls, and adding 2295 * a new outgoing call. The function of the flash command depends on the context of the current 2296 * set of calls. This method will prevent an outgoing call from being made if it is not within 2297 * the right circumstances to support adding a call. 2298 */ checkAdditionalOutgoingCallLimits(Phone phone)2299 private Connection checkAdditionalOutgoingCallLimits(Phone phone) { 2300 if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) { 2301 // Check to see if any CDMA conference calls exist, and if they do, check them for 2302 // limitations. 2303 for (Conference conference : getAllConferences()) { 2304 if (conference instanceof CdmaConference) { 2305 CdmaConference cdmaConf = (CdmaConference) conference; 2306 2307 // If the CDMA conference has not been merged, add-call will not work, so fail 2308 // this request to add a call. 2309 if ((cdmaConf.getConnectionCapabilities() 2310 & Connection.CAPABILITY_MERGE_CONFERENCE) != 0) { 2311 return Connection.createFailedConnection(new DisconnectCause( 2312 DisconnectCause.RESTRICTED, 2313 null, 2314 getResources().getString(R.string.callFailed_cdma_call_limit), 2315 "merge-capable call exists, prevent flash command.")); 2316 } 2317 } 2318 } 2319 } 2320 2321 return null; // null means nothing went wrong, and call should continue. 2322 } 2323 2324 /** 2325 * For outgoing dialed calls, potentially send a ConnectionEvent if the user is on WFC and is 2326 * dialing an international number. 2327 * @param telephonyConnection The connection. 2328 */ maybeSendInternationalCallEvent(TelephonyConnection telephonyConnection)2329 private void maybeSendInternationalCallEvent(TelephonyConnection telephonyConnection) { 2330 if (telephonyConnection == null || telephonyConnection.getPhone() == null || 2331 telephonyConnection.getPhone().getDefaultPhone() == null) { 2332 return; 2333 } 2334 Phone phone = telephonyConnection.getPhone().getDefaultPhone(); 2335 if (phone instanceof GsmCdmaPhone) { 2336 GsmCdmaPhone gsmCdmaPhone = (GsmCdmaPhone) phone; 2337 if (telephonyConnection.isOutgoingCall() && 2338 gsmCdmaPhone.isNotificationOfWfcCallRequired( 2339 telephonyConnection.getOriginalConnection().getOrigDialString())) { 2340 // Send connection event to InCall UI to inform the user of the fact they 2341 // are potentially placing an international call on WFC. 2342 Log.i(this, "placeOutgoingConnection - sending international call on WFC " + 2343 "confirmation event"); 2344 telephonyConnection.sendTelephonyConnectionEvent( 2345 TelephonyManager.EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC, null); 2346 } 2347 } 2348 } 2349 handleTtyModeChange(boolean isTtyEnabled)2350 private void handleTtyModeChange(boolean isTtyEnabled) { 2351 Log.i(this, "handleTtyModeChange; isTtyEnabled=%b", isTtyEnabled); 2352 mIsTtyEnabled = isTtyEnabled; 2353 for (Connection connection : getAllConnections()) { 2354 if (connection instanceof TelephonyConnection) { 2355 TelephonyConnection telephonyConnection = (TelephonyConnection) connection; 2356 telephonyConnection.setTtyEnabled(isTtyEnabled); 2357 } 2358 } 2359 } 2360 closeOrDestroyConnection(Connection connection, DisconnectCause cause)2361 private void closeOrDestroyConnection(Connection connection, DisconnectCause cause) { 2362 if (connection instanceof TelephonyConnection) { 2363 TelephonyConnection telephonyConnection = (TelephonyConnection) connection; 2364 telephonyConnection.setTelephonyConnectionDisconnected(cause); 2365 // Close destroys the connection and notifies TelephonyConnection listeners. 2366 telephonyConnection.close(); 2367 } else { 2368 connection.setDisconnected(cause); 2369 connection.destroy(); 2370 } 2371 } 2372 showDataDialog(Phone phone, String number)2373 private boolean showDataDialog(Phone phone, String number) { 2374 boolean ret = false; 2375 final Context context = getApplicationContext(); 2376 String suppKey = MmiCodeUtil.getSuppServiceKey(number); 2377 if (suppKey != null) { 2378 boolean clirOverUtPrecautions = false; 2379 boolean cfOverUtPrecautions = false; 2380 boolean cbOverUtPrecautions = false; 2381 boolean cwOverUtPrecautions = false; 2382 2383 CarrierConfigManager cfgManager = (CarrierConfigManager) 2384 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 2385 if (cfgManager != null) { 2386 clirOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId()) 2387 .getBoolean(CarrierConfigManager.KEY_CALLER_ID_OVER_UT_WARNING_BOOL); 2388 cfOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId()) 2389 .getBoolean(CarrierConfigManager.KEY_CALL_FORWARDING_OVER_UT_WARNING_BOOL); 2390 cbOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId()) 2391 .getBoolean(CarrierConfigManager.KEY_CALL_BARRING_OVER_UT_WARNING_BOOL); 2392 cwOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId()) 2393 .getBoolean(CarrierConfigManager.KEY_CALL_WAITING_OVER_UT_WARNING_BOOL); 2394 } 2395 2396 boolean isSsOverUtPrecautions = SuppServicesUiUtil 2397 .isSsOverUtPrecautions(context, phone); 2398 if (isSsOverUtPrecautions) { 2399 boolean showDialog = false; 2400 if (suppKey == MmiCodeUtil.BUTTON_CLIR_KEY && clirOverUtPrecautions) { 2401 showDialog = true; 2402 } else if (suppKey == MmiCodeUtil.CALL_FORWARDING_KEY && cfOverUtPrecautions) { 2403 showDialog = true; 2404 } else if (suppKey == MmiCodeUtil.CALL_BARRING_KEY && cbOverUtPrecautions) { 2405 showDialog = true; 2406 } else if (suppKey == MmiCodeUtil.BUTTON_CW_KEY && cwOverUtPrecautions) { 2407 showDialog = true; 2408 } 2409 2410 if (showDialog) { 2411 Log.d(this, "Creating UT Data enable dialog"); 2412 String message = SuppServicesUiUtil.makeMessage(context, suppKey, phone); 2413 AlertDialog.Builder builder = new AlertDialog.Builder(context); 2414 DialogInterface.OnClickListener networkSettingsClickListener = 2415 new Dialog.OnClickListener() { 2416 @Override 2417 public void onClick(DialogInterface dialog, int which) { 2418 Intent intent = new Intent(Intent.ACTION_MAIN); 2419 ComponentName mobileNetworkSettingsComponent 2420 = new ComponentName( 2421 context.getString( 2422 R.string.mobile_network_settings_package), 2423 context.getString( 2424 R.string.mobile_network_settings_class)); 2425 intent.setComponent(mobileNetworkSettingsComponent); 2426 context.startActivity(intent); 2427 } 2428 }; 2429 Dialog dialog = builder.setMessage(message) 2430 .setNeutralButton(context.getResources().getString( 2431 R.string.settings_label), 2432 networkSettingsClickListener) 2433 .setPositiveButton(context.getResources().getString( 2434 R.string.supp_service_over_ut_precautions_dialog_dismiss), null) 2435 .create(); 2436 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); 2437 dialog.show(); 2438 ret = true; 2439 } 2440 } 2441 } 2442 return ret; 2443 } 2444 2445 /** 2446 * Adds a {@link Conference} to the telephony ConnectionService and registers a listener for 2447 * changes to the conference. Should be used instead of {@link #addConference(Conference)}. 2448 * @param conference The conference. 2449 */ addTelephonyConference(@onNull TelephonyConferenceBase conference)2450 public void addTelephonyConference(@NonNull TelephonyConferenceBase conference) { 2451 addConference(conference); 2452 conference.addTelephonyConferenceListener(mTelephonyConferenceListener); 2453 } 2454 } 2455