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