ImsPhoneCallTracker.java revision 782c44d28a8944478b6906aa5c86a14ffd0e63db
1/* 2 * Copyright (C) 2013 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 17package com.android.internal.telephony.imsphone; 18 19import android.app.PendingIntent; 20import android.content.BroadcastReceiver; 21import android.content.Context; 22import android.content.Intent; 23import android.content.IntentFilter; 24import android.content.SharedPreferences; 25import android.net.ConnectivityManager; 26import android.net.NetworkInfo; 27import android.net.Uri; 28import android.os.AsyncResult; 29import android.os.Bundle; 30import android.os.Handler; 31import android.os.Message; 32import android.os.PersistableBundle; 33import android.os.Registrant; 34import android.os.RegistrantList; 35import android.os.RemoteException; 36import android.os.SystemProperties; 37import android.preference.PreferenceManager; 38import android.provider.Settings; 39import android.telecom.ConferenceParticipant; 40import android.telecom.VideoProfile; 41import android.telephony.CarrierConfigManager; 42import android.telephony.DisconnectCause; 43import android.telephony.PhoneNumberUtils; 44import android.telephony.Rlog; 45import android.telephony.ServiceState; 46import android.telephony.SubscriptionManager; 47import android.telephony.TelephonyManager; 48import android.telephony.ims.ImsServiceProxy; 49import android.telephony.ims.feature.ImsFeature; 50import android.text.TextUtils; 51import android.util.ArrayMap; 52import android.util.Log; 53import android.util.Pair; 54 55import com.android.ims.ImsCall; 56import com.android.ims.ImsCallProfile; 57import com.android.ims.ImsConfig; 58import com.android.ims.ImsConfigListener; 59import com.android.ims.ImsConnectionStateListener; 60import com.android.ims.ImsEcbm; 61import com.android.ims.ImsException; 62import com.android.ims.ImsManager; 63import com.android.ims.ImsMultiEndpoint; 64import com.android.ims.ImsReasonInfo; 65import com.android.ims.ImsServiceClass; 66import com.android.ims.ImsSuppServiceNotification; 67import com.android.ims.ImsUtInterface; 68import com.android.ims.internal.IImsVideoCallProvider; 69import com.android.ims.internal.ImsVideoCallProviderWrapper; 70import com.android.internal.annotations.VisibleForTesting; 71import com.android.internal.telephony.Call; 72import com.android.internal.telephony.CallStateException; 73import com.android.internal.telephony.CallTracker; 74import com.android.internal.telephony.CommandException; 75import com.android.internal.telephony.CommandsInterface; 76import com.android.internal.telephony.Connection; 77import com.android.internal.telephony.Phone; 78import com.android.internal.telephony.PhoneConstants; 79import com.android.internal.telephony.TelephonyProperties; 80import com.android.internal.telephony.TelephonyProto.ImsConnectionState; 81import com.android.internal.telephony.TelephonyProto.TelephonyCallSession; 82import com.android.internal.telephony.TelephonyProto.TelephonyCallSession.Event.ImsCommand; 83import com.android.internal.telephony.dataconnection.DataEnabledSettings; 84import com.android.internal.telephony.gsm.SuppServiceNotification; 85import com.android.internal.telephony.metrics.TelephonyMetrics; 86 87import java.io.FileDescriptor; 88import java.io.PrintWriter; 89import java.util.ArrayList; 90import java.util.HashMap; 91import java.util.List; 92import java.util.Map; 93import java.util.regex.Pattern; 94 95/** 96 * {@hide} 97 */ 98public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { 99 static final String LOG_TAG = "ImsPhoneCallTracker"; 100 static final String VERBOSE_STATE_TAG = "IPCTState"; 101 102 public interface PhoneStateListener { 103 void onPhoneStateChanged(PhoneConstants.State oldState, PhoneConstants.State newState); 104 } 105 106 private static final boolean DBG = true; 107 108 // When true, dumps the state of ImsPhoneCallTracker after changes to foreground and background 109 // calls. This is helpful for debugging. It is also possible to enable this at runtime by 110 // setting the IPCTState log tag to VERBOSE. 111 private static final boolean FORCE_VERBOSE_STATE_LOGGING = false; /* stopship if true */ 112 private static final boolean VERBOSE_STATE_LOGGING = FORCE_VERBOSE_STATE_LOGGING || 113 Rlog.isLoggable(VERBOSE_STATE_TAG, Log.VERBOSE); 114 115 //Indices map to ImsConfig.FeatureConstants 116 private boolean[] mImsFeatureEnabled = {false, false, false, false, false, false}; 117 private final String[] mImsFeatureStrings = {"VoLTE", "ViLTE", "VoWiFi", "ViWiFi", 118 "UTLTE", "UTWiFi"}; 119 120 private TelephonyMetrics mMetrics; 121 122 private BroadcastReceiver mReceiver = new BroadcastReceiver() { 123 @Override 124 public void onReceive(Context context, Intent intent) { 125 if (intent.getAction().equals(ImsManager.ACTION_IMS_INCOMING_CALL)) { 126 if (DBG) log("onReceive : incoming call intent"); 127 128 if (mImsManager == null) return; 129 130 if (mServiceId < 0) return; 131 132 try { 133 // Network initiated USSD will be treated by mImsUssdListener 134 boolean isUssd = intent.getBooleanExtra(ImsManager.EXTRA_USSD, false); 135 if (isUssd) { 136 if (DBG) log("onReceive : USSD"); 137 mUssdSession = mImsManager.takeCall(mServiceId, intent, mImsUssdListener); 138 if (mUssdSession != null) { 139 mUssdSession.accept(ImsCallProfile.CALL_TYPE_VOICE); 140 } 141 return; 142 } 143 144 boolean isUnknown = intent.getBooleanExtra(ImsManager.EXTRA_IS_UNKNOWN_CALL, 145 false); 146 if (DBG) { 147 log("onReceive : isUnknown = " + isUnknown + 148 " fg = " + mForegroundCall.getState() + 149 " bg = " + mBackgroundCall.getState()); 150 } 151 152 // Normal MT/Unknown call 153 ImsCall imsCall = mImsManager.takeCall(mServiceId, intent, mImsCallListener); 154 ImsPhoneConnection conn = new ImsPhoneConnection(mPhone, imsCall, 155 ImsPhoneCallTracker.this, 156 (isUnknown? mForegroundCall: mRingingCall), isUnknown); 157 158 // If there is an active call. 159 if (mForegroundCall.hasConnections()) { 160 ImsCall activeCall = mForegroundCall.getFirstConnection().getImsCall(); 161 boolean answeringWillDisconnect = 162 shouldDisconnectActiveCallOnAnswer(activeCall, imsCall); 163 conn.setActiveCallDisconnectedOnAnswer(answeringWillDisconnect); 164 } 165 conn.setAllowAddCallDuringVideoCall(mAllowAddCallDuringVideoCall); 166 addConnection(conn); 167 168 setVideoCallProvider(conn, imsCall); 169 170 TelephonyMetrics.getInstance().writeOnImsCallReceive(mPhone.getPhoneId(), 171 imsCall.getSession()); 172 173 if (isUnknown) { 174 mPhone.notifyUnknownConnection(conn); 175 } else { 176 if ((mForegroundCall.getState() != ImsPhoneCall.State.IDLE) || 177 (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE)) { 178 conn.update(imsCall, ImsPhoneCall.State.WAITING); 179 } 180 181 mPhone.notifyNewRingingConnection(conn); 182 mPhone.notifyIncomingRing(); 183 } 184 185 updatePhoneState(); 186 mPhone.notifyPreciseCallStateChanged(); 187 } catch (ImsException e) { 188 loge("onReceive : exception " + e); 189 } catch (RemoteException e) { 190 } 191 } else if (intent.getAction().equals( 192 CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) { 193 int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, 194 SubscriptionManager.INVALID_SUBSCRIPTION_ID); 195 if (subId == mPhone.getSubId()) { 196 cacheCarrierConfiguration(subId); 197 log("onReceive : Updating mAllowEmergencyVideoCalls = " + 198 mAllowEmergencyVideoCalls); 199 } 200 } 201 } 202 }; 203 204 //***** Constants 205 206 static final int MAX_CONNECTIONS = 7; 207 static final int MAX_CONNECTIONS_PER_CALL = 5; 208 209 private static final int EVENT_HANGUP_PENDINGMO = 18; 210 private static final int EVENT_RESUME_BACKGROUND = 19; 211 private static final int EVENT_DIAL_PENDINGMO = 20; 212 private static final int EVENT_EXIT_ECBM_BEFORE_PENDINGMO = 21; 213 private static final int EVENT_VT_DATA_USAGE_UPDATE = 22; 214 private static final int EVENT_DATA_ENABLED_CHANGED = 23; 215 private static final int EVENT_GET_IMS_SERVICE = 24; 216 private static final int EVENT_CHECK_FOR_WIFI_HANDOVER = 25; 217 private static final int EVENT_CLEAR_DISCONNECTING_CONN = 26; 218 219 private static final int TIMEOUT_HANGUP_PENDINGMO = 500; 220 221 // The number of milliseconds the CallTracker will wait before manually disconnecting the 222 // connection due to the modem not responding. 223 private static final int TIMEOUT_CLEAR_DISCONNECTING_CONN = 5000; 224 225 // Initial condition for ims connection retry. 226 private static final int IMS_RETRY_STARTING_TIMEOUT_MS = 500; // ms 227 // Ceiling bitshift amount for service query timeout, calculated as: 228 // 2^mImsServiceRetryCount * IMS_RETRY_STARTING_TIMEOUT_MS, where 229 // mImsServiceRetryCount ∊ [0, CEILING_SERVICE_RETRY_COUNT]. 230 private static final int CEILING_SERVICE_RETRY_COUNT = 6; 231 232 private static final int HANDOVER_TO_WIFI_TIMEOUT_MS = 60000; // ms 233 234 //***** Instance Variables 235 private ArrayList<ImsPhoneConnection> mConnections = new ArrayList<ImsPhoneConnection>(); 236 private RegistrantList mVoiceCallEndedRegistrants = new RegistrantList(); 237 private RegistrantList mVoiceCallStartedRegistrants = new RegistrantList(); 238 239 public ImsPhoneCall mRingingCall = new ImsPhoneCall(this, ImsPhoneCall.CONTEXT_RINGING); 240 public ImsPhoneCall mForegroundCall = new ImsPhoneCall(this, 241 ImsPhoneCall.CONTEXT_FOREGROUND); 242 public ImsPhoneCall mBackgroundCall = new ImsPhoneCall(this, 243 ImsPhoneCall.CONTEXT_BACKGROUND); 244 public ImsPhoneCall mHandoverCall = new ImsPhoneCall(this, ImsPhoneCall.CONTEXT_HANDOVER); 245 246 // Hold aggregated video call data usage for each video call since boot. 247 // The ImsCall's call id is the key of the map. 248 private final HashMap<Integer, Long> mVtDataUsageMap = new HashMap<>(); 249 private volatile long mTotalVtDataUsage = 0; 250 251 private ImsPhoneConnection mPendingMO; 252 private int mClirMode = CommandsInterface.CLIR_DEFAULT; 253 private Object mSyncHold = new Object(); 254 255 private ImsCall mUssdSession = null; 256 private Message mPendingUssd = null; 257 258 ImsPhone mPhone; 259 260 private boolean mDesiredMute = false; // false = mute off 261 private boolean mOnHoldToneStarted = false; 262 private int mOnHoldToneId = -1; 263 264 private PhoneConstants.State mState = PhoneConstants.State.IDLE; 265 266 private int mImsServiceRetryCount; 267 private ImsManager mImsManager; 268 private int mServiceId = -1; 269 270 private Call.SrvccState mSrvccState = Call.SrvccState.NONE; 271 272 private boolean mIsInEmergencyCall = false; 273 274 private int pendingCallClirMode; 275 private int mPendingCallVideoState; 276 private Bundle mPendingIntentExtras; 277 private boolean pendingCallInEcm = false; 278 private boolean mSwitchingFgAndBgCalls = false; 279 private ImsCall mCallExpectedToResume = null; 280 private boolean mAllowEmergencyVideoCalls = false; 281 private boolean mIgnoreDataEnabledChangedForVideoCalls = false; 282 283 /** 284 * Listeners to changes in the phone state. Intended for use by other interested IMS components 285 * without the need to register a full blown {@link android.telephony.PhoneStateListener}. 286 */ 287 private List<PhoneStateListener> mPhoneStateListeners = new ArrayList<>(); 288 289 /** 290 * Carrier configuration option which determines if video calls which have been downgraded to an 291 * audio call should be treated as if they are still video calls. 292 */ 293 private boolean mTreatDowngradedVideoCallsAsVideoCalls = false; 294 295 /** 296 * Carrier configuration option which determines if an ongoing video call over wifi should be 297 * dropped when an audio call is answered. 298 */ 299 private boolean mDropVideoCallWhenAnsweringAudioCall = false; 300 301 /** 302 * Carrier configuration option which determines whether adding a call during a video call 303 * should be allowed. 304 */ 305 private boolean mAllowAddCallDuringVideoCall = true; 306 307 /** 308 * Carrier configuration option which determines whether to notify the connection if a handover 309 * to wifi fails. 310 */ 311 private boolean mNotifyVtHandoverToWifiFail = false; 312 313 /** 314 * Carrier configuration option which determines whether the carrier supports downgrading a 315 * TX/RX/TX-RX video call directly to an audio-only call. 316 */ 317 private boolean mSupportDowngradeVtToAudio = false; 318 319 /** 320 * Carrier configuration option which determines whether the carrier wants to inform the user 321 * when a video call is handed over from WIFI to LTE. 322 * See {@link CarrierConfigManager#KEY_NOTIFY_HANDOVER_VIDEO_FROM_WIFI_TO_LTE_BOOL} for more 323 * information. 324 */ 325 private boolean mNotifyHandoverVideoFromWifiToLTE = false; 326 327 /** 328 * Carrier configuration option which defines a mapping from pairs of 329 * {@link ImsReasonInfo#getCode()} and {@link ImsReasonInfo#getExtraMessage()} values to a new 330 * {@code ImsReasonInfo#CODE_*} value. 331 * 332 * See {@link CarrierConfigManager#KEY_IMS_REASONINFO_MAPPING_STRING_ARRAY}. 333 */ 334 private Map<Pair<Integer, String>, Integer> mImsReasonCodeMap = new ArrayMap<>(); 335 336 private ImsServiceProxy.INotifyStatusChanged mNotifyFeatureRemovedCallback = () -> { 337 try { 338 if (mImsManager.getImsServiceStatus() != ImsFeature.STATE_READY) { 339 retryGetImsService(); 340 } 341 } catch (ImsException e) { 342 // Could not get the ImsService, retry! 343 retryGetImsService(); 344 } 345 }; 346 347 //***** Events 348 349 350 //***** Constructors 351 352 public ImsPhoneCallTracker(ImsPhone phone) { 353 this.mPhone = phone; 354 355 mMetrics = TelephonyMetrics.getInstance(); 356 357 IntentFilter intentfilter = new IntentFilter(); 358 intentfilter.addAction(ImsManager.ACTION_IMS_INCOMING_CALL); 359 intentfilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED); 360 mPhone.getContext().registerReceiver(mReceiver, intentfilter); 361 cacheCarrierConfiguration(mPhone.getSubId()); 362 363 mPhone.getDefaultPhone().registerForDataEnabledChanged( 364 this, EVENT_DATA_ENABLED_CHANGED, null); 365 366 mImsServiceRetryCount = 0; 367 // Send a message to connect to the Ims Service and open a connection through 368 // getImsService(). 369 sendEmptyMessage(EVENT_GET_IMS_SERVICE); 370 } 371 372 private PendingIntent createIncomingCallPendingIntent() { 373 Intent intent = new Intent(ImsManager.ACTION_IMS_INCOMING_CALL); 374 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 375 return PendingIntent.getBroadcast(mPhone.getContext(), 0, intent, 376 PendingIntent.FLAG_UPDATE_CURRENT); 377 } 378 379 private void getImsService() throws ImsException { 380 if (DBG) log("getImsService"); 381 mImsManager = ImsManager.getInstance(mPhone.getContext(), mPhone.getPhoneId()); 382 // Adding to set, will be safe adding multiple times. 383 mImsManager.addNotifyStatusChangedCallback(mNotifyFeatureRemovedCallback); 384 if (mImsManager.getImsServiceStatus() != ImsFeature.STATE_READY) { 385 // We can not call "open" until the ims service is ready 386 throw new ImsException("getImsServiceStatus()", 387 ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN); 388 } 389 mImsServiceRetryCount = 0; 390 mServiceId = mImsManager.open(ImsServiceClass.MMTEL, 391 createIncomingCallPendingIntent(), 392 mImsConnectionStateListener); 393 394 mImsManager.setImsConfigListener(mImsConfigListener); 395 396 // Get the ECBM interface and set IMSPhone's listener object for notifications 397 getEcbmInterface().setEcbmStateListener(mPhone.getImsEcbmStateListener()); 398 if (mPhone.isInEcm()) { 399 // Call exit ECBM which will invoke onECBMExited 400 mPhone.exitEmergencyCallbackMode(); 401 } 402 int mPreferredTtyMode = Settings.Secure.getInt( 403 mPhone.getContext().getContentResolver(), 404 Settings.Secure.PREFERRED_TTY_MODE, 405 Phone.TTY_MODE_OFF); 406 mImsManager.setUiTTYMode(mPhone.getContext(), mPreferredTtyMode, null); 407 408 ImsMultiEndpoint multiEndpoint = getMultiEndpointInterface(); 409 if (multiEndpoint != null) { 410 multiEndpoint.setExternalCallStateListener( 411 mPhone.getExternalCallTracker().getExternalCallStateListener()); 412 } 413 } 414 415 public void dispose() { 416 if (DBG) log("dispose"); 417 mRingingCall.dispose(); 418 mBackgroundCall.dispose(); 419 mForegroundCall.dispose(); 420 mHandoverCall.dispose(); 421 422 clearDisconnected(); 423 mPhone.getContext().unregisterReceiver(mReceiver); 424 mPhone.getDefaultPhone().unregisterForDataEnabledChanged(this); 425 removeMessages(EVENT_GET_IMS_SERVICE); 426 } 427 428 @Override 429 protected void finalize() { 430 log("ImsPhoneCallTracker finalized"); 431 } 432 433 //***** Instance Methods 434 435 //***** Public Methods 436 @Override 437 public void registerForVoiceCallStarted(Handler h, int what, Object obj) { 438 Registrant r = new Registrant(h, what, obj); 439 mVoiceCallStartedRegistrants.add(r); 440 } 441 442 @Override 443 public void unregisterForVoiceCallStarted(Handler h) { 444 mVoiceCallStartedRegistrants.remove(h); 445 } 446 447 @Override 448 public void registerForVoiceCallEnded(Handler h, int what, Object obj) { 449 Registrant r = new Registrant(h, what, obj); 450 mVoiceCallEndedRegistrants.add(r); 451 } 452 453 @Override 454 public void unregisterForVoiceCallEnded(Handler h) { 455 mVoiceCallEndedRegistrants.remove(h); 456 } 457 458 public Connection dial(String dialString, int videoState, Bundle intentExtras) throws 459 CallStateException { 460 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mPhone.getContext()); 461 int oirMode = sp.getInt(Phone.CLIR_KEY, CommandsInterface.CLIR_DEFAULT); 462 return dial(dialString, oirMode, videoState, intentExtras); 463 } 464 465 /** 466 * oirMode is one of the CLIR_ constants 467 */ 468 synchronized Connection 469 dial(String dialString, int clirMode, int videoState, Bundle intentExtras) 470 throws CallStateException { 471 boolean isPhoneInEcmMode = isPhoneInEcbMode(); 472 boolean isEmergencyNumber = PhoneNumberUtils.isEmergencyNumber(dialString); 473 474 if (DBG) log("dial clirMode=" + clirMode); 475 476 // note that this triggers call state changed notif 477 clearDisconnected(); 478 479 if (mImsManager == null) { 480 throw new CallStateException("service not available"); 481 } 482 483 if (!canDial()) { 484 throw new CallStateException("cannot dial in current state"); 485 } 486 487 if (isPhoneInEcmMode && isEmergencyNumber) { 488 handleEcmTimer(ImsPhone.CANCEL_ECM_TIMER); 489 } 490 491 // If the call is to an emergency number and the carrier does not support video emergency 492 // calls, dial as an audio-only call. 493 if (isEmergencyNumber && VideoProfile.isVideo(videoState) && 494 !mAllowEmergencyVideoCalls) { 495 loge("dial: carrier does not support video emergency calls; downgrade to audio-only"); 496 videoState = VideoProfile.STATE_AUDIO_ONLY; 497 } 498 499 boolean holdBeforeDial = false; 500 501 // The new call must be assigned to the foreground call. 502 // That call must be idle, so place anything that's 503 // there on hold 504 if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) { 505 if (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE) { 506 //we should have failed in !canDial() above before we get here 507 throw new CallStateException("cannot dial in current state"); 508 } 509 // foreground call is empty for the newly dialed connection 510 holdBeforeDial = true; 511 // Cache the video state for pending MO call. 512 mPendingCallVideoState = videoState; 513 mPendingIntentExtras = intentExtras; 514 switchWaitingOrHoldingAndActive(); 515 } 516 517 ImsPhoneCall.State fgState = ImsPhoneCall.State.IDLE; 518 ImsPhoneCall.State bgState = ImsPhoneCall.State.IDLE; 519 520 mClirMode = clirMode; 521 522 synchronized (mSyncHold) { 523 if (holdBeforeDial) { 524 fgState = mForegroundCall.getState(); 525 bgState = mBackgroundCall.getState(); 526 527 //holding foreground call failed 528 if (fgState == ImsPhoneCall.State.ACTIVE) { 529 throw new CallStateException("cannot dial in current state"); 530 } 531 532 //holding foreground call succeeded 533 if (bgState == ImsPhoneCall.State.HOLDING) { 534 holdBeforeDial = false; 535 } 536 } 537 538 mPendingMO = new ImsPhoneConnection(mPhone, 539 checkForTestEmergencyNumber(dialString), this, mForegroundCall, 540 isEmergencyNumber); 541 mPendingMO.setVideoState(videoState); 542 } 543 addConnection(mPendingMO); 544 545 if (!holdBeforeDial) { 546 if ((!isPhoneInEcmMode) || (isPhoneInEcmMode && isEmergencyNumber)) { 547 dialInternal(mPendingMO, clirMode, videoState, intentExtras); 548 } else { 549 try { 550 getEcbmInterface().exitEmergencyCallbackMode(); 551 } catch (ImsException e) { 552 e.printStackTrace(); 553 throw new CallStateException("service not available"); 554 } 555 mPhone.setOnEcbModeExitResponse(this, EVENT_EXIT_ECM_RESPONSE_CDMA, null); 556 pendingCallClirMode = clirMode; 557 mPendingCallVideoState = videoState; 558 pendingCallInEcm = true; 559 } 560 } 561 562 updatePhoneState(); 563 mPhone.notifyPreciseCallStateChanged(); 564 565 return mPendingMO; 566 } 567 568 /** 569 * Caches frequently used carrier configuration items locally. 570 * 571 * @param subId The sub id. 572 */ 573 private void cacheCarrierConfiguration(int subId) { 574 CarrierConfigManager carrierConfigManager = (CarrierConfigManager) 575 mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 576 if (carrierConfigManager == null) { 577 loge("cacheCarrierConfiguration: No carrier config service found."); 578 return; 579 } 580 581 PersistableBundle carrierConfig = carrierConfigManager.getConfigForSubId(subId); 582 if (carrierConfig == null) { 583 loge("cacheCarrierConfiguration: Empty carrier config."); 584 return; 585 } 586 587 mAllowEmergencyVideoCalls = 588 carrierConfig.getBoolean(CarrierConfigManager.KEY_ALLOW_EMERGENCY_VIDEO_CALLS_BOOL); 589 mTreatDowngradedVideoCallsAsVideoCalls = 590 carrierConfig.getBoolean( 591 CarrierConfigManager.KEY_TREAT_DOWNGRADED_VIDEO_CALLS_AS_VIDEO_CALLS_BOOL); 592 mDropVideoCallWhenAnsweringAudioCall = 593 carrierConfig.getBoolean( 594 CarrierConfigManager.KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL); 595 mAllowAddCallDuringVideoCall = 596 carrierConfig.getBoolean( 597 CarrierConfigManager.KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL); 598 mNotifyVtHandoverToWifiFail = carrierConfig.getBoolean( 599 CarrierConfigManager.KEY_NOTIFY_VT_HANDOVER_TO_WIFI_FAILURE_BOOL); 600 mSupportDowngradeVtToAudio = carrierConfig.getBoolean( 601 CarrierConfigManager.KEY_SUPPORT_DOWNGRADE_VT_TO_AUDIO_BOOL); 602 mNotifyHandoverVideoFromWifiToLTE = carrierConfig.getBoolean( 603 CarrierConfigManager.KEY_NOTIFY_VT_HANDOVER_TO_WIFI_FAILURE_BOOL); 604 mIgnoreDataEnabledChangedForVideoCalls = carrierConfig.getBoolean( 605 CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS); 606 607 String[] mappings = carrierConfig 608 .getStringArray(CarrierConfigManager.KEY_IMS_REASONINFO_MAPPING_STRING_ARRAY); 609 if (mappings != null && mappings.length > 0) { 610 for (String mapping : mappings) { 611 String[] values = mapping.split(Pattern.quote("|")); 612 if (values.length != 3) { 613 continue; 614 } 615 616 try { 617 Integer fromCode; 618 if (values[0].equals("*")) { 619 fromCode = null; 620 } else { 621 fromCode = Integer.parseInt(values[0]); 622 } 623 String message = values[1]; 624 int toCode = Integer.parseInt(values[2]); 625 626 addReasonCodeRemapping(fromCode, message, toCode); 627 log("Loaded ImsReasonInfo mapping : fromCode = " + 628 fromCode == null ? "any" : fromCode + " ; message = " + 629 message + " ; toCode = " + toCode); 630 } catch (NumberFormatException nfe) { 631 loge("Invalid ImsReasonInfo mapping found: " + mapping); 632 } 633 } 634 } else { 635 log("No carrier ImsReasonInfo mappings defined."); 636 } 637 } 638 639 private void handleEcmTimer(int action) { 640 mPhone.handleTimerInEmergencyCallbackMode(action); 641 switch (action) { 642 case ImsPhone.CANCEL_ECM_TIMER: 643 break; 644 case ImsPhone.RESTART_ECM_TIMER: 645 break; 646 default: 647 log("handleEcmTimer, unsupported action " + action); 648 } 649 } 650 651 private void dialInternal(ImsPhoneConnection conn, int clirMode, int videoState, 652 Bundle intentExtras) { 653 654 if (conn == null) { 655 return; 656 } 657 658 if (conn.getAddress()== null || conn.getAddress().length() == 0 659 || conn.getAddress().indexOf(PhoneNumberUtils.WILD) >= 0) { 660 // Phone number is invalid 661 conn.setDisconnectCause(DisconnectCause.INVALID_NUMBER); 662 sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO); 663 return; 664 } 665 666 // Always unmute when initiating a new call 667 setMute(false); 668 int serviceType = PhoneNumberUtils.isEmergencyNumber(conn.getAddress()) ? 669 ImsCallProfile.SERVICE_TYPE_EMERGENCY : ImsCallProfile.SERVICE_TYPE_NORMAL; 670 int callType = ImsCallProfile.getCallTypeFromVideoState(videoState); 671 //TODO(vt): Is this sufficient? At what point do we know the video state of the call? 672 conn.setVideoState(videoState); 673 674 try { 675 String[] callees = new String[] { conn.getAddress() }; 676 ImsCallProfile profile = mImsManager.createCallProfile(mServiceId, 677 serviceType, callType); 678 profile.setCallExtraInt(ImsCallProfile.EXTRA_OIR, clirMode); 679 680 // Translate call subject intent-extra from Telecom-specific extra key to the 681 // ImsCallProfile key. 682 if (intentExtras != null) { 683 if (intentExtras.containsKey(android.telecom.TelecomManager.EXTRA_CALL_SUBJECT)) { 684 intentExtras.putString(ImsCallProfile.EXTRA_DISPLAY_TEXT, 685 cleanseInstantLetteringMessage(intentExtras.getString( 686 android.telecom.TelecomManager.EXTRA_CALL_SUBJECT)) 687 ); 688 } 689 690 if (intentExtras.containsKey(ImsCallProfile.EXTRA_IS_CALL_PULL)) { 691 profile.mCallExtras.putBoolean(ImsCallProfile.EXTRA_IS_CALL_PULL, 692 intentExtras.getBoolean(ImsCallProfile.EXTRA_IS_CALL_PULL)); 693 int dialogId = intentExtras.getInt( 694 ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID); 695 conn.setIsPulledCall(true); 696 conn.setPulledDialogId(dialogId); 697 } 698 699 // Pack the OEM-specific call extras. 700 profile.mCallExtras.putBundle(ImsCallProfile.EXTRA_OEM_EXTRAS, intentExtras); 701 702 // NOTE: Extras to be sent over the network are packed into the 703 // intentExtras individually, with uniquely defined keys. 704 // These key-value pairs are processed by IMS Service before 705 // being sent to the lower layers/to the network. 706 } 707 708 ImsCall imsCall = mImsManager.makeCall(mServiceId, profile, 709 callees, mImsCallListener); 710 conn.setImsCall(imsCall); 711 712 mMetrics.writeOnImsCallStart(mPhone.getPhoneId(), 713 imsCall.getSession()); 714 715 setVideoCallProvider(conn, imsCall); 716 conn.setAllowAddCallDuringVideoCall(mAllowAddCallDuringVideoCall); 717 } catch (ImsException e) { 718 loge("dialInternal : " + e); 719 conn.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED); 720 sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO); 721 } catch (RemoteException e) { 722 } 723 } 724 725 /** 726 * Accepts a call with the specified video state. The video state is the video state that the 727 * user has agreed upon in the InCall UI. 728 * 729 * @param videoState The video State 730 * @throws CallStateException 731 */ 732 public void acceptCall (int videoState) throws CallStateException { 733 if (DBG) log("acceptCall"); 734 735 if (mForegroundCall.getState().isAlive() 736 && mBackgroundCall.getState().isAlive()) { 737 throw new CallStateException("cannot accept call"); 738 } 739 740 if ((mRingingCall.getState() == ImsPhoneCall.State.WAITING) 741 && mForegroundCall.getState().isAlive()) { 742 setMute(false); 743 744 boolean answeringWillDisconnect = false; 745 ImsCall activeCall = mForegroundCall.getImsCall(); 746 ImsCall ringingCall = mRingingCall.getImsCall(); 747 if (mForegroundCall.hasConnections() && mRingingCall.hasConnections()) { 748 answeringWillDisconnect = 749 shouldDisconnectActiveCallOnAnswer(activeCall, ringingCall); 750 } 751 752 // Cache video state for pending MT call. 753 mPendingCallVideoState = videoState; 754 755 if (answeringWillDisconnect) { 756 // We need to disconnect the foreground call before answering the background call. 757 mForegroundCall.hangup(); 758 try { 759 ringingCall.accept(ImsCallProfile.getCallTypeFromVideoState(videoState)); 760 } catch (ImsException e) { 761 throw new CallStateException("cannot accept call"); 762 } 763 } else { 764 switchWaitingOrHoldingAndActive(); 765 } 766 } else if (mRingingCall.getState().isRinging()) { 767 if (DBG) log("acceptCall: incoming..."); 768 // Always unmute when answering a new call 769 setMute(false); 770 try { 771 ImsCall imsCall = mRingingCall.getImsCall(); 772 if (imsCall != null) { 773 imsCall.accept(ImsCallProfile.getCallTypeFromVideoState(videoState)); 774 mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(), 775 ImsCommand.IMS_CMD_ACCEPT); 776 } else { 777 throw new CallStateException("no valid ims call"); 778 } 779 } catch (ImsException e) { 780 throw new CallStateException("cannot accept call"); 781 } 782 } else { 783 throw new CallStateException("phone not ringing"); 784 } 785 } 786 787 public void rejectCall () throws CallStateException { 788 if (DBG) log("rejectCall"); 789 790 if (mRingingCall.getState().isRinging()) { 791 hangup(mRingingCall); 792 } else { 793 throw new CallStateException("phone not ringing"); 794 } 795 } 796 797 798 private void switchAfterConferenceSuccess() { 799 if (DBG) log("switchAfterConferenceSuccess fg =" + mForegroundCall.getState() + 800 ", bg = " + mBackgroundCall.getState()); 801 802 if (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING) { 803 log("switchAfterConferenceSuccess"); 804 mForegroundCall.switchWith(mBackgroundCall); 805 } 806 } 807 808 public void switchWaitingOrHoldingAndActive() throws CallStateException { 809 if (DBG) log("switchWaitingOrHoldingAndActive"); 810 811 if (mRingingCall.getState() == ImsPhoneCall.State.INCOMING) { 812 throw new CallStateException("cannot be in the incoming state"); 813 } 814 815 if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) { 816 ImsCall imsCall = mForegroundCall.getImsCall(); 817 if (imsCall == null) { 818 throw new CallStateException("no ims call"); 819 } 820 821 // Swap the ImsCalls pointed to by the foreground and background ImsPhoneCalls. 822 // If hold or resume later fails, we will swap them back. 823 boolean switchingWithWaitingCall = mBackgroundCall.getImsCall() == null && 824 mRingingCall != null && 825 mRingingCall.getState() == ImsPhoneCall.State.WAITING; 826 827 mSwitchingFgAndBgCalls = true; 828 if (switchingWithWaitingCall) { 829 mCallExpectedToResume = mRingingCall.getImsCall(); 830 } else { 831 mCallExpectedToResume = mBackgroundCall.getImsCall(); 832 } 833 mForegroundCall.switchWith(mBackgroundCall); 834 835 // Hold the foreground call; once the foreground call is held, the background call will 836 // be resumed. 837 try { 838 imsCall.hold(); 839 mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(), 840 ImsCommand.IMS_CMD_HOLD); 841 842 // If there is no background call to resume, then don't expect there to be a switch. 843 if (mCallExpectedToResume == null) { 844 log("mCallExpectedToResume is null"); 845 mSwitchingFgAndBgCalls = false; 846 } 847 } catch (ImsException e) { 848 mForegroundCall.switchWith(mBackgroundCall); 849 throw new CallStateException(e.getMessage()); 850 } 851 } else if (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING) { 852 resumeWaitingOrHolding(); 853 } 854 } 855 856 public void 857 conference() { 858 if (DBG) log("conference"); 859 860 ImsCall fgImsCall = mForegroundCall.getImsCall(); 861 if (fgImsCall == null) { 862 log("conference no foreground ims call"); 863 return; 864 } 865 866 ImsCall bgImsCall = mBackgroundCall.getImsCall(); 867 if (bgImsCall == null) { 868 log("conference no background ims call"); 869 return; 870 } 871 872 // Keep track of the connect time of the earliest call so that it can be set on the 873 // {@code ImsConference} when it is created. 874 long foregroundConnectTime = mForegroundCall.getEarliestConnectTime(); 875 long backgroundConnectTime = mBackgroundCall.getEarliestConnectTime(); 876 long conferenceConnectTime; 877 if (foregroundConnectTime > 0 && backgroundConnectTime > 0) { 878 conferenceConnectTime = Math.min(mForegroundCall.getEarliestConnectTime(), 879 mBackgroundCall.getEarliestConnectTime()); 880 log("conference - using connect time = " + conferenceConnectTime); 881 } else if (foregroundConnectTime > 0) { 882 log("conference - bg call connect time is 0; using fg = " + foregroundConnectTime); 883 conferenceConnectTime = foregroundConnectTime; 884 } else { 885 log("conference - fg call connect time is 0; using bg = " + backgroundConnectTime); 886 conferenceConnectTime = backgroundConnectTime; 887 } 888 889 ImsPhoneConnection foregroundConnection = mForegroundCall.getFirstConnection(); 890 if (foregroundConnection != null) { 891 foregroundConnection.setConferenceConnectTime(conferenceConnectTime); 892 } 893 894 try { 895 fgImsCall.merge(bgImsCall); 896 } catch (ImsException e) { 897 log("conference " + e.getMessage()); 898 } 899 } 900 901 public void 902 explicitCallTransfer() { 903 //TODO : implement 904 } 905 906 public void 907 clearDisconnected() { 908 if (DBG) log("clearDisconnected"); 909 910 internalClearDisconnected(); 911 912 updatePhoneState(); 913 mPhone.notifyPreciseCallStateChanged(); 914 } 915 916 public boolean 917 canConference() { 918 return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE 919 && mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING 920 && !mBackgroundCall.isFull() 921 && !mForegroundCall.isFull(); 922 } 923 924 public boolean 925 canDial() { 926 boolean ret; 927 int serviceState = mPhone.getServiceState().getState(); 928 String disableCall = SystemProperties.get( 929 TelephonyProperties.PROPERTY_DISABLE_CALL, "false"); 930 931 ret = (serviceState != ServiceState.STATE_POWER_OFF) 932 && mPendingMO == null 933 && !mRingingCall.isRinging() 934 && !disableCall.equals("true") 935 && (!mForegroundCall.getState().isAlive() 936 || !mBackgroundCall.getState().isAlive()); 937 938 return ret; 939 } 940 941 public boolean 942 canTransfer() { 943 return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE 944 && mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING; 945 } 946 947 //***** Private Instance Methods 948 949 private void 950 internalClearDisconnected() { 951 mRingingCall.clearDisconnected(); 952 mForegroundCall.clearDisconnected(); 953 mBackgroundCall.clearDisconnected(); 954 mHandoverCall.clearDisconnected(); 955 } 956 957 private void 958 updatePhoneState() { 959 PhoneConstants.State oldState = mState; 960 961 boolean isPendingMOIdle = mPendingMO == null || !mPendingMO.getState().isAlive(); 962 963 if (mRingingCall.isRinging()) { 964 mState = PhoneConstants.State.RINGING; 965 } else if (!isPendingMOIdle || !mForegroundCall.isIdle() || !mBackgroundCall.isIdle()) { 966 // There is a non-idle call, so we're off the hook. 967 mState = PhoneConstants.State.OFFHOOK; 968 } else { 969 mState = PhoneConstants.State.IDLE; 970 } 971 972 if (mState == PhoneConstants.State.IDLE && oldState != mState) { 973 mVoiceCallEndedRegistrants.notifyRegistrants( 974 new AsyncResult(null, null, null)); 975 } else if (oldState == PhoneConstants.State.IDLE && oldState != mState) { 976 mVoiceCallStartedRegistrants.notifyRegistrants ( 977 new AsyncResult(null, null, null)); 978 } 979 980 if (DBG) { 981 log("updatePhoneState pendingMo = " + (mPendingMO == null ? "null" 982 : mPendingMO.getState()) + ", fg= " + mForegroundCall.getState() + "(" 983 + mForegroundCall.getConnections().size() + "), bg= " + mBackgroundCall 984 .getState() + "(" + mBackgroundCall.getConnections().size() + ")"); 985 log("updatePhoneState oldState=" + oldState + ", newState=" + mState); 986 } 987 988 if (mState != oldState) { 989 mPhone.notifyPhoneStateChanged(); 990 mMetrics.writePhoneState(mPhone.getPhoneId(), mState); 991 notifyPhoneStateChanged(oldState, mState); 992 } 993 } 994 995 private void 996 handleRadioNotAvailable() { 997 // handlePollCalls will clear out its 998 // call list when it gets the CommandException 999 // error result from this 1000 pollCallsWhenSafe(); 1001 } 1002 1003 private void 1004 dumpState() { 1005 List l; 1006 1007 log("Phone State:" + mState); 1008 1009 log("Ringing call: " + mRingingCall.toString()); 1010 1011 l = mRingingCall.getConnections(); 1012 for (int i = 0, s = l.size(); i < s; i++) { 1013 log(l.get(i).toString()); 1014 } 1015 1016 log("Foreground call: " + mForegroundCall.toString()); 1017 1018 l = mForegroundCall.getConnections(); 1019 for (int i = 0, s = l.size(); i < s; i++) { 1020 log(l.get(i).toString()); 1021 } 1022 1023 log("Background call: " + mBackgroundCall.toString()); 1024 1025 l = mBackgroundCall.getConnections(); 1026 for (int i = 0, s = l.size(); i < s; i++) { 1027 log(l.get(i).toString()); 1028 } 1029 1030 } 1031 1032 //***** Called from ImsPhone 1033 1034 public void setUiTTYMode(int uiTtyMode, Message onComplete) { 1035 if (mImsManager == null) { 1036 mPhone.sendErrorResponse(onComplete, getImsManagerIsNullException()); 1037 return; 1038 } 1039 1040 try { 1041 mImsManager.setUiTTYMode(mPhone.getContext(), uiTtyMode, onComplete); 1042 } catch (ImsException e) { 1043 loge("setTTYMode : " + e); 1044 mPhone.sendErrorResponse(onComplete, e); 1045 } 1046 } 1047 1048 public void setMute(boolean mute) { 1049 mDesiredMute = mute; 1050 mForegroundCall.setMute(mute); 1051 } 1052 1053 public boolean getMute() { 1054 return mDesiredMute; 1055 } 1056 1057 public void sendDtmf(char c, Message result) { 1058 if (DBG) log("sendDtmf"); 1059 1060 ImsCall imscall = mForegroundCall.getImsCall(); 1061 if (imscall != null) { 1062 imscall.sendDtmf(c, result); 1063 } 1064 } 1065 1066 public void 1067 startDtmf(char c) { 1068 if (DBG) log("startDtmf"); 1069 1070 ImsCall imscall = mForegroundCall.getImsCall(); 1071 if (imscall != null) { 1072 imscall.startDtmf(c); 1073 } else { 1074 loge("startDtmf : no foreground call"); 1075 } 1076 } 1077 1078 public void 1079 stopDtmf() { 1080 if (DBG) log("stopDtmf"); 1081 1082 ImsCall imscall = mForegroundCall.getImsCall(); 1083 if (imscall != null) { 1084 imscall.stopDtmf(); 1085 } else { 1086 loge("stopDtmf : no foreground call"); 1087 } 1088 } 1089 1090 //***** Called from ImsPhoneConnection 1091 1092 public void hangup (ImsPhoneConnection conn) throws CallStateException { 1093 if (DBG) log("hangup connection"); 1094 1095 if (conn.getOwner() != this) { 1096 throw new CallStateException ("ImsPhoneConnection " + conn 1097 + "does not belong to ImsPhoneCallTracker " + this); 1098 } 1099 1100 hangup(conn.getCall()); 1101 } 1102 1103 //***** Called from ImsPhoneCall 1104 1105 public void hangup (ImsPhoneCall call) throws CallStateException { 1106 if (DBG) log("hangup call"); 1107 1108 if (call.getConnections().size() == 0) { 1109 throw new CallStateException("no connections"); 1110 } 1111 1112 ImsCall imsCall = call.getImsCall(); 1113 // Get first connection that is associated with imsCall. 1114 ImsPhoneConnection imsPhoneConnection = call.getFirstConnection(); 1115 boolean rejectCall = false; 1116 1117 if (call == mRingingCall) { 1118 if (Phone.DEBUG_PHONE) log("(ringing) hangup incoming"); 1119 rejectCall = true; 1120 } else if (call == mForegroundCall) { 1121 if (call.isDialingOrAlerting()) { 1122 if (Phone.DEBUG_PHONE) { 1123 log("(foregnd) hangup dialing or alerting..."); 1124 } 1125 } else { 1126 if (Phone.DEBUG_PHONE) { 1127 log("(foregnd) hangup foreground"); 1128 } 1129 //held call will be resumed by onCallTerminated 1130 } 1131 } else if (call == mBackgroundCall) { 1132 if (Phone.DEBUG_PHONE) { 1133 log("(backgnd) hangup waiting or background"); 1134 } 1135 } else { 1136 throw new CallStateException ("ImsPhoneCall " + call + 1137 "does not belong to ImsPhoneCallTracker " + this); 1138 } 1139 1140 call.onHangupLocal(); 1141 // Schedule a cleaning event for the ImsPhoneCall. If the modem has not responded in 1142 // TIMEOUT_CLEAR_DISCONNECTING_CONN milliseconds, manually disconnect the connection. 1143 if (!hasMessages(EVENT_CLEAR_DISCONNECTING_CONN, imsPhoneConnection)) { 1144 sendMessageDelayed(obtainMessage(EVENT_CLEAR_DISCONNECTING_CONN, imsPhoneConnection), 1145 TIMEOUT_CLEAR_DISCONNECTING_CONN); 1146 } 1147 1148 try { 1149 if (imsCall != null) { 1150 if (rejectCall) { 1151 imsCall.reject(ImsReasonInfo.CODE_USER_DECLINE); 1152 mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(), 1153 ImsCommand.IMS_CMD_REJECT); 1154 } else { 1155 imsCall.terminate(ImsReasonInfo.CODE_USER_TERMINATED); 1156 mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(), 1157 ImsCommand.IMS_CMD_TERMINATE); 1158 } 1159 } else if (mPendingMO != null && call == mForegroundCall) { 1160 // is holding a foreground call 1161 mPendingMO.update(null, ImsPhoneCall.State.DISCONNECTED); 1162 mPendingMO.onDisconnect(); 1163 removeConnection(mPendingMO); 1164 mPendingMO = null; 1165 updatePhoneState(); 1166 removeMessages(EVENT_DIAL_PENDINGMO); 1167 } 1168 } catch (ImsException e) { 1169 throw new CallStateException(e.getMessage()); 1170 } 1171 1172 mPhone.notifyPreciseCallStateChanged(); 1173 } 1174 1175 void callEndCleanupHandOverCallIfAny() { 1176 if (mHandoverCall.mConnections.size() > 0) { 1177 if (DBG) log("callEndCleanupHandOverCallIfAny, mHandoverCall.mConnections=" 1178 + mHandoverCall.mConnections); 1179 mHandoverCall.mConnections.clear(); 1180 mConnections.clear(); 1181 mState = PhoneConstants.State.IDLE; 1182 } 1183 } 1184 1185 /* package */ 1186 void resumeWaitingOrHolding() throws CallStateException { 1187 if (DBG) log("resumeWaitingOrHolding"); 1188 1189 try { 1190 if (mForegroundCall.getState().isAlive()) { 1191 //resume foreground call after holding background call 1192 //they were switched before holding 1193 ImsCall imsCall = mForegroundCall.getImsCall(); 1194 if (imsCall != null) { 1195 imsCall.resume(); 1196 mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(), 1197 ImsCommand.IMS_CMD_RESUME); 1198 } 1199 } else if (mRingingCall.getState() == ImsPhoneCall.State.WAITING) { 1200 //accept waiting call after holding background call 1201 ImsCall imsCall = mRingingCall.getImsCall(); 1202 if (imsCall != null) { 1203 imsCall.accept( 1204 ImsCallProfile.getCallTypeFromVideoState(mPendingCallVideoState)); 1205 mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(), 1206 ImsCommand.IMS_CMD_ACCEPT); 1207 } 1208 } else { 1209 //Just resume background call. 1210 //To distinguish resuming call with swapping calls 1211 //we do not switch calls.here 1212 //ImsPhoneConnection.update will chnage the parent when completed 1213 ImsCall imsCall = mBackgroundCall.getImsCall(); 1214 if (imsCall != null) { 1215 imsCall.resume(); 1216 mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(), 1217 ImsCommand.IMS_CMD_RESUME); 1218 } 1219 } 1220 } catch (ImsException e) { 1221 throw new CallStateException(e.getMessage()); 1222 } 1223 } 1224 1225 public void sendUSSD (String ussdString, Message response) { 1226 if (DBG) log("sendUSSD"); 1227 1228 try { 1229 if (mUssdSession != null) { 1230 mUssdSession.sendUssd(ussdString); 1231 AsyncResult.forMessage(response, null, null); 1232 response.sendToTarget(); 1233 return; 1234 } 1235 1236 if (mImsManager == null) { 1237 mPhone.sendErrorResponse(response, getImsManagerIsNullException()); 1238 return; 1239 } 1240 1241 String[] callees = new String[] { ussdString }; 1242 ImsCallProfile profile = mImsManager.createCallProfile(mServiceId, 1243 ImsCallProfile.SERVICE_TYPE_NORMAL, ImsCallProfile.CALL_TYPE_VOICE); 1244 profile.setCallExtraInt(ImsCallProfile.EXTRA_DIALSTRING, 1245 ImsCallProfile.DIALSTRING_USSD); 1246 1247 mUssdSession = mImsManager.makeCall(mServiceId, profile, 1248 callees, mImsUssdListener); 1249 } catch (ImsException e) { 1250 loge("sendUSSD : " + e); 1251 mPhone.sendErrorResponse(response, e); 1252 } 1253 } 1254 1255 public void cancelUSSD() { 1256 if (mUssdSession == null) return; 1257 1258 try { 1259 mUssdSession.terminate(ImsReasonInfo.CODE_USER_TERMINATED); 1260 } catch (ImsException e) { 1261 } 1262 1263 } 1264 1265 private synchronized ImsPhoneConnection findConnection(final ImsCall imsCall) { 1266 for (ImsPhoneConnection conn : mConnections) { 1267 if (conn.getImsCall() == imsCall) { 1268 return conn; 1269 } 1270 } 1271 return null; 1272 } 1273 1274 private synchronized void removeConnection(ImsPhoneConnection conn) { 1275 mConnections.remove(conn); 1276 // If not emergency call is remaining, notify emergency call registrants 1277 if (mIsInEmergencyCall) { 1278 boolean isEmergencyCallInList = false; 1279 // if no emergency calls pending, set this to false 1280 for (ImsPhoneConnection imsPhoneConnection : mConnections) { 1281 if (imsPhoneConnection != null && imsPhoneConnection.isEmergency() == true) { 1282 isEmergencyCallInList = true; 1283 break; 1284 } 1285 } 1286 1287 if (!isEmergencyCallInList) { 1288 mIsInEmergencyCall = false; 1289 mPhone.sendEmergencyCallStateChange(false); 1290 } 1291 } 1292 } 1293 1294 private synchronized void addConnection(ImsPhoneConnection conn) { 1295 mConnections.add(conn); 1296 if (conn.isEmergency()) { 1297 mIsInEmergencyCall = true; 1298 mPhone.sendEmergencyCallStateChange(true); 1299 } 1300 } 1301 1302 private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause) { 1303 if (DBG) log("processCallStateChange " + imsCall + " state=" + state + " cause=" + cause); 1304 // This method is called on onCallUpdate() where there is not necessarily a call state 1305 // change. In these situations, we'll ignore the state related updates and only process 1306 // the change in media capabilities (as expected). The default is to not ignore state 1307 // changes so we do not change existing behavior. 1308 processCallStateChange(imsCall, state, cause, false /* do not ignore state update */); 1309 } 1310 1311 private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause, 1312 boolean ignoreState) { 1313 if (DBG) { 1314 log("processCallStateChange state=" + state + " cause=" + cause 1315 + " ignoreState=" + ignoreState); 1316 } 1317 1318 if (imsCall == null) return; 1319 1320 boolean changed = false; 1321 ImsPhoneConnection conn = findConnection(imsCall); 1322 1323 if (conn == null) { 1324 // TODO : what should be done? 1325 return; 1326 } 1327 1328 // processCallStateChange is triggered for onCallUpdated as well. 1329 // onCallUpdated should not modify the state of the call 1330 // It should modify only other capabilities of call through updateMediaCapabilities 1331 // State updates will be triggered through individual callbacks 1332 // i.e. onCallHeld, onCallResume, etc and conn.update will be responsible for the update 1333 conn.updateMediaCapabilities(imsCall); 1334 if (ignoreState) { 1335 conn.updateAddressDisplay(imsCall); 1336 conn.updateExtras(imsCall); 1337 1338 maybeSetVideoCallProvider(conn, imsCall); 1339 return; 1340 } 1341 1342 changed = conn.update(imsCall, state); 1343 if (state == ImsPhoneCall.State.DISCONNECTED) { 1344 changed = conn.onDisconnect(cause) || changed; 1345 //detach the disconnected connections 1346 conn.getCall().detach(conn); 1347 removeConnection(conn); 1348 } 1349 1350 if (changed) { 1351 if (conn.getCall() == mHandoverCall) return; 1352 updatePhoneState(); 1353 mPhone.notifyPreciseCallStateChanged(); 1354 } 1355 } 1356 1357 private void maybeSetVideoCallProvider(ImsPhoneConnection conn, ImsCall imsCall) { 1358 android.telecom.Connection.VideoProvider connVideoProvider = conn.getVideoProvider(); 1359 if (connVideoProvider != null || imsCall.getCallSession().getVideoCallProvider() == null) { 1360 return; 1361 } 1362 1363 try { 1364 setVideoCallProvider(conn, imsCall); 1365 } catch (RemoteException e) { 1366 loge("maybeSetVideoCallProvider: exception " + e); 1367 } 1368 } 1369 1370 /** 1371 * Adds a reason code remapping, for test purposes. 1372 * 1373 * @param fromCode The from code, or {@code null} if all. 1374 * @param message The message to map. 1375 * @param toCode The code to remap to. 1376 */ 1377 @VisibleForTesting 1378 public void addReasonCodeRemapping(Integer fromCode, String message, Integer toCode) { 1379 mImsReasonCodeMap.put(new Pair<>(fromCode, message), toCode); 1380 } 1381 1382 /** 1383 * Returns the {@link ImsReasonInfo#getCode()}, potentially remapping to a new value based on 1384 * the {@link ImsReasonInfo#getCode()} and {@link ImsReasonInfo#getExtraMessage()}. 1385 * 1386 * See {@link #mImsReasonCodeMap}. 1387 * 1388 * @param reasonInfo The {@link ImsReasonInfo}. 1389 * @return The remapped code. 1390 */ 1391 @VisibleForTesting 1392 public int maybeRemapReasonCode(ImsReasonInfo reasonInfo) { 1393 int code = reasonInfo.getCode(); 1394 1395 Pair<Integer, String> toCheck = new Pair<>(code, reasonInfo.getExtraMessage()); 1396 Pair<Integer, String> wildcardToCheck = new Pair<>(null, reasonInfo.getExtraMessage()); 1397 if (mImsReasonCodeMap.containsKey(toCheck)) { 1398 int toCode = mImsReasonCodeMap.get(toCheck); 1399 1400 log("maybeRemapReasonCode : fromCode = " + reasonInfo.getCode() + " ; message = " 1401 + reasonInfo.getExtraMessage() + " ; toCode = " + toCode); 1402 return toCode; 1403 } else if (mImsReasonCodeMap.containsKey(wildcardToCheck)) { 1404 // Handle the case where a wildcard is specified for the fromCode; in this case we will 1405 // match without caring about the fromCode. 1406 int toCode = mImsReasonCodeMap.get(wildcardToCheck); 1407 1408 log("maybeRemapReasonCode : fromCode(wildcard) = " + reasonInfo.getCode() + 1409 " ; message = " + reasonInfo.getExtraMessage() + " ; toCode = " + toCode); 1410 return toCode; 1411 } 1412 return code; 1413 } 1414 1415 private int getDisconnectCauseFromReasonInfo(ImsReasonInfo reasonInfo) { 1416 int cause = DisconnectCause.ERROR_UNSPECIFIED; 1417 1418 //int type = reasonInfo.getReasonType(); 1419 int code = maybeRemapReasonCode(reasonInfo); 1420 switch (code) { 1421 case ImsReasonInfo.CODE_SIP_BAD_ADDRESS: 1422 case ImsReasonInfo.CODE_SIP_NOT_REACHABLE: 1423 return DisconnectCause.NUMBER_UNREACHABLE; 1424 1425 case ImsReasonInfo.CODE_SIP_BUSY: 1426 return DisconnectCause.BUSY; 1427 1428 case ImsReasonInfo.CODE_USER_TERMINATED: 1429 return DisconnectCause.LOCAL; 1430 1431 case ImsReasonInfo.CODE_LOCAL_CALL_DECLINE: 1432 case ImsReasonInfo.CODE_REMOTE_CALL_DECLINE: 1433 // If the call has been declined locally (on this device), or on remotely (on 1434 // another device using multiendpoint functionality), mark it as rejected. 1435 return DisconnectCause.INCOMING_REJECTED; 1436 1437 case ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE: 1438 return DisconnectCause.NORMAL; 1439 1440 case ImsReasonInfo.CODE_SIP_FORBIDDEN: 1441 return DisconnectCause.SERVER_ERROR; 1442 1443 case ImsReasonInfo.CODE_SIP_REDIRECTED: 1444 case ImsReasonInfo.CODE_SIP_BAD_REQUEST: 1445 case ImsReasonInfo.CODE_SIP_NOT_ACCEPTABLE: 1446 case ImsReasonInfo.CODE_SIP_USER_REJECTED: 1447 case ImsReasonInfo.CODE_SIP_GLOBAL_ERROR: 1448 return DisconnectCause.SERVER_ERROR; 1449 1450 case ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE: 1451 case ImsReasonInfo.CODE_SIP_NOT_FOUND: 1452 case ImsReasonInfo.CODE_SIP_SERVER_ERROR: 1453 return DisconnectCause.SERVER_UNREACHABLE; 1454 1455 case ImsReasonInfo.CODE_LOCAL_NETWORK_ROAMING: 1456 case ImsReasonInfo.CODE_LOCAL_NETWORK_IP_CHANGED: 1457 case ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN: 1458 case ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE: 1459 case ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED: 1460 case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_LTE_COVERAGE: 1461 case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_SERVICE: 1462 case ImsReasonInfo.CODE_LOCAL_CALL_VCC_ON_PROGRESSING: 1463 return DisconnectCause.OUT_OF_SERVICE; 1464 1465 case ImsReasonInfo.CODE_SIP_REQUEST_TIMEOUT: 1466 case ImsReasonInfo.CODE_TIMEOUT_1XX_WAITING: 1467 case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER: 1468 case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE: 1469 return DisconnectCause.TIMED_OUT; 1470 1471 case ImsReasonInfo.CODE_LOCAL_LOW_BATTERY: 1472 case ImsReasonInfo.CODE_LOCAL_POWER_OFF: 1473 return DisconnectCause.POWER_OFF; 1474 1475 case ImsReasonInfo.CODE_FDN_BLOCKED: 1476 return DisconnectCause.FDN_BLOCKED; 1477 1478 case ImsReasonInfo.CODE_ANSWERED_ELSEWHERE: 1479 return DisconnectCause.ANSWERED_ELSEWHERE; 1480 1481 case ImsReasonInfo.CODE_CALL_END_CAUSE_CALL_PULL: 1482 return DisconnectCause.CALL_PULLED; 1483 1484 case ImsReasonInfo.CODE_MAXIMUM_NUMBER_OF_CALLS_REACHED: 1485 return DisconnectCause.MAXIMUM_NUMBER_OF_CALLS_REACHED; 1486 1487 case ImsReasonInfo.CODE_DATA_DISABLED: 1488 return DisconnectCause.DATA_DISABLED; 1489 1490 case ImsReasonInfo.CODE_DATA_LIMIT_REACHED: 1491 return DisconnectCause.DATA_LIMIT_REACHED; 1492 1493 case ImsReasonInfo.CODE_WIFI_LOST: 1494 return DisconnectCause.WIFI_LOST; 1495 default: 1496 } 1497 1498 return cause; 1499 } 1500 1501 /** 1502 * @return true if the phone is in Emergency Callback mode, otherwise false 1503 */ 1504 private boolean isPhoneInEcbMode() { 1505 return SystemProperties.getBoolean(TelephonyProperties.PROPERTY_INECM_MODE, false); 1506 } 1507 1508 /** 1509 * Before dialing pending MO request, check for the Emergency Callback mode. 1510 * If device is in Emergency callback mode, then exit the mode before dialing pending MO. 1511 */ 1512 private void dialPendingMO() { 1513 boolean isPhoneInEcmMode = isPhoneInEcbMode(); 1514 boolean isEmergencyNumber = mPendingMO.isEmergency(); 1515 if ((!isPhoneInEcmMode) || (isPhoneInEcmMode && isEmergencyNumber)) { 1516 sendEmptyMessage(EVENT_DIAL_PENDINGMO); 1517 } else { 1518 sendEmptyMessage(EVENT_EXIT_ECBM_BEFORE_PENDINGMO); 1519 } 1520 } 1521 1522 /** 1523 * Listen to the IMS call state change 1524 */ 1525 private ImsCall.Listener mImsCallListener = new ImsCall.Listener() { 1526 @Override 1527 public void onCallProgressing(ImsCall imsCall) { 1528 if (DBG) log("onCallProgressing"); 1529 1530 mPendingMO = null; 1531 processCallStateChange(imsCall, ImsPhoneCall.State.ALERTING, 1532 DisconnectCause.NOT_DISCONNECTED); 1533 mMetrics.writeOnImsCallProgressing(mPhone.getPhoneId(), imsCall.getCallSession()); 1534 } 1535 1536 @Override 1537 public void onCallStarted(ImsCall imsCall) { 1538 if (DBG) log("onCallStarted"); 1539 1540 if (mSwitchingFgAndBgCalls) { 1541 // If we put a call on hold to answer an incoming call, we should reset the 1542 // variables that keep track of the switch here. 1543 if (mCallExpectedToResume != null && mCallExpectedToResume == imsCall) { 1544 if (DBG) log("onCallStarted: starting a call as a result of a switch."); 1545 mSwitchingFgAndBgCalls = false; 1546 mCallExpectedToResume = null; 1547 } 1548 } 1549 1550 mPendingMO = null; 1551 processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE, 1552 DisconnectCause.NOT_DISCONNECTED); 1553 1554 if (mNotifyVtHandoverToWifiFail && 1555 !imsCall.isWifiCall() && imsCall.isVideoCall() && isWifiConnected()) { 1556 // Schedule check to see if handover succeeded. 1557 sendMessageDelayed(obtainMessage(EVENT_CHECK_FOR_WIFI_HANDOVER, imsCall), 1558 HANDOVER_TO_WIFI_TIMEOUT_MS); 1559 } 1560 1561 mMetrics.writeOnImsCallStarted(mPhone.getPhoneId(), imsCall.getCallSession()); 1562 } 1563 1564 @Override 1565 public void onCallUpdated(ImsCall imsCall) { 1566 if (DBG) log("onCallUpdated"); 1567 if (imsCall == null) { 1568 return; 1569 } 1570 ImsPhoneConnection conn = findConnection(imsCall); 1571 if (conn != null) { 1572 processCallStateChange(imsCall, conn.getCall().mState, 1573 DisconnectCause.NOT_DISCONNECTED, true /*ignore state update*/); 1574 mMetrics.writeImsCallState(mPhone.getPhoneId(), 1575 imsCall.getCallSession(), conn.getCall().mState); 1576 } 1577 } 1578 1579 /** 1580 * onCallStartFailed will be invoked when: 1581 * case 1) Dialing fails 1582 * case 2) Ringing call is disconnected by local or remote user 1583 */ 1584 @Override 1585 public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) { 1586 if (DBG) log("onCallStartFailed reasonCode=" + reasonInfo.getCode()); 1587 1588 if (mSwitchingFgAndBgCalls) { 1589 // If we put a call on hold to answer an incoming call, we should reset the 1590 // variables that keep track of the switch here. 1591 if (mCallExpectedToResume != null && mCallExpectedToResume == imsCall) { 1592 if (DBG) log("onCallStarted: starting a call as a result of a switch."); 1593 mSwitchingFgAndBgCalls = false; 1594 mCallExpectedToResume = null; 1595 } 1596 } 1597 1598 if (mPendingMO != null) { 1599 // To initiate dialing circuit-switched call 1600 if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED 1601 && mBackgroundCall.getState() == ImsPhoneCall.State.IDLE 1602 && mRingingCall.getState() == ImsPhoneCall.State.IDLE) { 1603 mForegroundCall.detach(mPendingMO); 1604 removeConnection(mPendingMO); 1605 mPendingMO.finalize(); 1606 mPendingMO = null; 1607 mPhone.initiateSilentRedial(); 1608 return; 1609 } else { 1610 mPendingMO = null; 1611 int cause = getDisconnectCauseFromReasonInfo(reasonInfo); 1612 processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED, cause); 1613 } 1614 mMetrics.writeOnImsCallStartFailed(mPhone.getPhoneId(), imsCall.getCallSession(), 1615 reasonInfo); 1616 } 1617 } 1618 1619 @Override 1620 public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) { 1621 if (DBG) log("onCallTerminated reasonCode=" + reasonInfo.getCode()); 1622 1623 int cause = getDisconnectCauseFromReasonInfo(reasonInfo); 1624 ImsPhoneConnection conn = findConnection(imsCall); 1625 if (DBG) log("cause = " + cause + " conn = " + conn); 1626 1627 if (conn != null) { 1628 android.telecom.Connection.VideoProvider videoProvider = conn.getVideoProvider(); 1629 if (videoProvider instanceof ImsVideoCallProviderWrapper) { 1630 ImsVideoCallProviderWrapper wrapper = (ImsVideoCallProviderWrapper) 1631 videoProvider; 1632 1633 wrapper.removeImsVideoProviderCallback(conn); 1634 } 1635 } 1636 if (mOnHoldToneId == System.identityHashCode(conn)) { 1637 if (conn != null && mOnHoldToneStarted) { 1638 mPhone.stopOnHoldTone(conn); 1639 } 1640 mOnHoldToneStarted = false; 1641 mOnHoldToneId = -1; 1642 } 1643 if (conn != null) { 1644 if (conn.isPulledCall() && ( 1645 reasonInfo.getCode() == ImsReasonInfo.CODE_CALL_PULL_OUT_OF_SYNC || 1646 reasonInfo.getCode() == ImsReasonInfo.CODE_SIP_TEMPRARILY_UNAVAILABLE || 1647 reasonInfo.getCode() == ImsReasonInfo.CODE_SIP_FORBIDDEN) && 1648 mPhone != null && mPhone.getExternalCallTracker() != null) { 1649 1650 log("Call pull failed."); 1651 // Call was being pulled, but the call pull has failed -- inform the associated 1652 // TelephonyConnection that the pull failed, and provide it with the original 1653 // external connection which was pulled so that it can be swapped back. 1654 conn.onCallPullFailed(mPhone.getExternalCallTracker() 1655 .getConnectionById(conn.getPulledDialogId())); 1656 // Do not mark as disconnected; the call will just change from being a regular 1657 // call to being an external call again. 1658 cause = DisconnectCause.NOT_DISCONNECTED; 1659 1660 } else if (conn.isIncoming() && conn.getConnectTime() == 0 1661 && cause != DisconnectCause.ANSWERED_ELSEWHERE) { 1662 // Missed 1663 if (cause == DisconnectCause.NORMAL) { 1664 cause = DisconnectCause.INCOMING_MISSED; 1665 } else { 1666 cause = DisconnectCause.INCOMING_REJECTED; 1667 } 1668 if (DBG) log("Incoming connection of 0 connect time detected - translated " + 1669 "cause = " + cause); 1670 } 1671 } 1672 1673 if (cause == DisconnectCause.NORMAL && conn != null && conn.getImsCall().isMerged()) { 1674 // Call was terminated while it is merged instead of a remote disconnect. 1675 cause = DisconnectCause.IMS_MERGED_SUCCESSFULLY; 1676 } 1677 1678 mMetrics.writeOnImsCallTerminated(mPhone.getPhoneId(), imsCall.getCallSession(), 1679 reasonInfo); 1680 1681 processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED, cause); 1682 if (mForegroundCall.getState() != ImsPhoneCall.State.ACTIVE) { 1683 if (mRingingCall.getState().isRinging()) { 1684 // Drop pending MO. We should address incoming call first 1685 mPendingMO = null; 1686 } else if (mPendingMO != null) { 1687 sendEmptyMessage(EVENT_DIAL_PENDINGMO); 1688 } 1689 } 1690 1691 if (mSwitchingFgAndBgCalls) { 1692 if (DBG) { 1693 log("onCallTerminated: Call terminated in the midst of Switching " + 1694 "Fg and Bg calls."); 1695 } 1696 // If we are the in midst of swapping FG and BG calls and the call that was 1697 // terminated was the one that we expected to resume, we need to swap the FG and 1698 // BG calls back. 1699 if (imsCall == mCallExpectedToResume) { 1700 if (DBG) { 1701 log("onCallTerminated: switching " + mForegroundCall + " with " 1702 + mBackgroundCall); 1703 } 1704 mForegroundCall.switchWith(mBackgroundCall); 1705 } 1706 // This call terminated in the midst of a switch after the other call was held, so 1707 // resume it back to ACTIVE state since the switch failed. 1708 log("onCallTerminated: foreground call in state " + mForegroundCall.getState() + 1709 " and ringing call in state " + (mRingingCall == null ? "null" : 1710 mRingingCall.getState().toString())); 1711 1712 if (mForegroundCall.getState() == ImsPhoneCall.State.HOLDING || 1713 mRingingCall.getState() == ImsPhoneCall.State.WAITING) { 1714 sendEmptyMessage(EVENT_RESUME_BACKGROUND); 1715 mSwitchingFgAndBgCalls = false; 1716 mCallExpectedToResume = null; 1717 } 1718 } 1719 } 1720 1721 @Override 1722 public void onCallHeld(ImsCall imsCall) { 1723 if (DBG) { 1724 if (mForegroundCall.getImsCall() == imsCall) { 1725 log("onCallHeld (fg) " + imsCall); 1726 } else if (mBackgroundCall.getImsCall() == imsCall) { 1727 log("onCallHeld (bg) " + imsCall); 1728 } 1729 } 1730 1731 synchronized (mSyncHold) { 1732 ImsPhoneCall.State oldState = mBackgroundCall.getState(); 1733 processCallStateChange(imsCall, ImsPhoneCall.State.HOLDING, 1734 DisconnectCause.NOT_DISCONNECTED); 1735 1736 // Note: If we're performing a switchWaitingOrHoldingAndActive, the call to 1737 // processCallStateChange above may have caused the mBackgroundCall and 1738 // mForegroundCall references below to change meaning. Watch out for this if you 1739 // are reading through this code. 1740 if (oldState == ImsPhoneCall.State.ACTIVE) { 1741 // Note: This case comes up when we have just held a call in response to a 1742 // switchWaitingOrHoldingAndActive. We now need to resume the background call. 1743 // The EVENT_RESUME_BACKGROUND causes resumeWaitingOrHolding to be called. 1744 if ((mForegroundCall.getState() == ImsPhoneCall.State.HOLDING) 1745 || (mRingingCall.getState() == ImsPhoneCall.State.WAITING)) { 1746 sendEmptyMessage(EVENT_RESUME_BACKGROUND); 1747 } else { 1748 //when multiple connections belong to background call, 1749 //only the first callback reaches here 1750 //otherwise the oldState is already HOLDING 1751 if (mPendingMO != null) { 1752 dialPendingMO(); 1753 } 1754 1755 // In this case there will be no call resumed, so we can assume that we 1756 // are done switching fg and bg calls now. 1757 // This may happen if there is no BG call and we are holding a call so that 1758 // we can dial another one. 1759 mSwitchingFgAndBgCalls = false; 1760 } 1761 } else if (oldState == ImsPhoneCall.State.IDLE && mSwitchingFgAndBgCalls) { 1762 // The other call terminated in the midst of a switch before this call was held, 1763 // so resume the foreground call back to ACTIVE state since the switch failed. 1764 if (mForegroundCall.getState() == ImsPhoneCall.State.HOLDING) { 1765 sendEmptyMessage(EVENT_RESUME_BACKGROUND); 1766 mSwitchingFgAndBgCalls = false; 1767 mCallExpectedToResume = null; 1768 } 1769 } 1770 } 1771 mMetrics.writeOnImsCallHeld(mPhone.getPhoneId(), imsCall.getCallSession()); 1772 } 1773 1774 @Override 1775 public void onCallHoldFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) { 1776 if (DBG) log("onCallHoldFailed reasonCode=" + reasonInfo.getCode()); 1777 1778 synchronized (mSyncHold) { 1779 ImsPhoneCall.State bgState = mBackgroundCall.getState(); 1780 if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED) { 1781 // disconnected while processing hold 1782 if (mPendingMO != null) { 1783 dialPendingMO(); 1784 } 1785 } else if (bgState == ImsPhoneCall.State.ACTIVE) { 1786 mForegroundCall.switchWith(mBackgroundCall); 1787 1788 if (mPendingMO != null) { 1789 mPendingMO.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED); 1790 sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO); 1791 } 1792 } 1793 mPhone.notifySuppServiceFailed(Phone.SuppService.HOLD); 1794 } 1795 mMetrics.writeOnImsCallHoldFailed(mPhone.getPhoneId(), imsCall.getCallSession(), 1796 reasonInfo); 1797 } 1798 1799 @Override 1800 public void onCallResumed(ImsCall imsCall) { 1801 if (DBG) log("onCallResumed"); 1802 1803 // If we are the in midst of swapping FG and BG calls and the call we end up resuming 1804 // is not the one we expected, we likely had a resume failure and we need to swap the 1805 // FG and BG calls back. 1806 if (mSwitchingFgAndBgCalls) { 1807 if (imsCall != mCallExpectedToResume) { 1808 // If the call which resumed isn't as expected, we need to swap back to the 1809 // previous configuration; the swap has failed. 1810 if (DBG) { 1811 log("onCallResumed : switching " + mForegroundCall + " with " 1812 + mBackgroundCall); 1813 } 1814 mForegroundCall.switchWith(mBackgroundCall); 1815 } else { 1816 // The call which resumed is the one we expected to resume, so we can clear out 1817 // the mSwitchingFgAndBgCalls flag. 1818 if (DBG) { 1819 log("onCallResumed : expected call resumed."); 1820 } 1821 } 1822 mSwitchingFgAndBgCalls = false; 1823 mCallExpectedToResume = null; 1824 } 1825 processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE, 1826 DisconnectCause.NOT_DISCONNECTED); 1827 mMetrics.writeOnImsCallResumed(mPhone.getPhoneId(), imsCall.getCallSession()); 1828 } 1829 1830 @Override 1831 public void onCallResumeFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) { 1832 if (mSwitchingFgAndBgCalls) { 1833 // If we are in the midst of swapping the FG and BG calls and 1834 // we got a resume fail, we need to swap back the FG and BG calls. 1835 // Since the FG call was held, will also try to resume the same. 1836 if (imsCall == mCallExpectedToResume) { 1837 if (DBG) { 1838 log("onCallResumeFailed : switching " + mForegroundCall + " with " 1839 + mBackgroundCall); 1840 } 1841 mForegroundCall.switchWith(mBackgroundCall); 1842 if (mForegroundCall.getState() == ImsPhoneCall.State.HOLDING) { 1843 sendEmptyMessage(EVENT_RESUME_BACKGROUND); 1844 } 1845 } 1846 1847 //Call swap is done, reset the relevant variables 1848 mCallExpectedToResume = null; 1849 mSwitchingFgAndBgCalls = false; 1850 } 1851 mPhone.notifySuppServiceFailed(Phone.SuppService.RESUME); 1852 mMetrics.writeOnImsCallResumeFailed(mPhone.getPhoneId(), imsCall.getCallSession(), 1853 reasonInfo); 1854 } 1855 1856 @Override 1857 public void onCallResumeReceived(ImsCall imsCall) { 1858 if (DBG) log("onCallResumeReceived"); 1859 ImsPhoneConnection conn = findConnection(imsCall); 1860 if (conn != null) { 1861 if (mOnHoldToneStarted) { 1862 mPhone.stopOnHoldTone(conn); 1863 mOnHoldToneStarted = false; 1864 } 1865 1866 conn.onConnectionEvent(android.telecom.Connection.EVENT_CALL_REMOTELY_UNHELD, null); 1867 } 1868 1869 SuppServiceNotification supp = new SuppServiceNotification(); 1870 // Type of notification: 0 = MO; 1 = MT 1871 // Refer SuppServiceNotification class documentation. 1872 supp.notificationType = 1; 1873 supp.code = SuppServiceNotification.MT_CODE_CALL_RETRIEVED; 1874 mPhone.notifySuppSvcNotification(supp); 1875 mMetrics.writeOnImsCallResumeReceived(mPhone.getPhoneId(), imsCall.getCallSession()); 1876 } 1877 1878 @Override 1879 public void onCallHoldReceived(ImsCall imsCall) { 1880 if (DBG) log("onCallHoldReceived"); 1881 1882 ImsPhoneConnection conn = findConnection(imsCall); 1883 if (conn != null) { 1884 if (!mOnHoldToneStarted && ImsPhoneCall.isLocalTone(imsCall) && 1885 conn.getState() == ImsPhoneCall.State.ACTIVE) { 1886 mPhone.startOnHoldTone(conn); 1887 mOnHoldToneStarted = true; 1888 mOnHoldToneId = System.identityHashCode(conn); 1889 } 1890 1891 conn.onConnectionEvent(android.telecom.Connection.EVENT_CALL_REMOTELY_HELD, null); 1892 } 1893 1894 SuppServiceNotification supp = new SuppServiceNotification(); 1895 // Type of notification: 0 = MO; 1 = MT 1896 // Refer SuppServiceNotification class documentation. 1897 supp.notificationType = 1; 1898 supp.code = SuppServiceNotification.MT_CODE_CALL_ON_HOLD; 1899 mPhone.notifySuppSvcNotification(supp); 1900 mMetrics.writeOnImsCallHoldReceived(mPhone.getPhoneId(), imsCall.getCallSession()); 1901 } 1902 1903 @Override 1904 public void onCallSuppServiceReceived(ImsCall call, 1905 ImsSuppServiceNotification suppServiceInfo) { 1906 if (DBG) log("onCallSuppServiceReceived: suppServiceInfo=" + suppServiceInfo); 1907 1908 SuppServiceNotification supp = new SuppServiceNotification(); 1909 supp.notificationType = suppServiceInfo.notificationType; 1910 supp.code = suppServiceInfo.code; 1911 supp.index = suppServiceInfo.index; 1912 supp.number = suppServiceInfo.number; 1913 supp.history = suppServiceInfo.history; 1914 1915 mPhone.notifySuppSvcNotification(supp); 1916 } 1917 1918 @Override 1919 public void onCallMerged(final ImsCall call, final ImsCall peerCall, boolean swapCalls) { 1920 if (DBG) log("onCallMerged"); 1921 1922 ImsPhoneCall foregroundImsPhoneCall = findConnection(call).getCall(); 1923 ImsPhoneConnection peerConnection = findConnection(peerCall); 1924 ImsPhoneCall peerImsPhoneCall = peerConnection == null ? null 1925 : peerConnection.getCall(); 1926 1927 if (swapCalls) { 1928 switchAfterConferenceSuccess(); 1929 } 1930 foregroundImsPhoneCall.merge(peerImsPhoneCall, ImsPhoneCall.State.ACTIVE); 1931 1932 try { 1933 final ImsPhoneConnection conn = findConnection(call); 1934 log("onCallMerged: ImsPhoneConnection=" + conn); 1935 log("onCallMerged: CurrentVideoProvider=" + conn.getVideoProvider()); 1936 setVideoCallProvider(conn, call); 1937 log("onCallMerged: CurrentVideoProvider=" + conn.getVideoProvider()); 1938 } catch (Exception e) { 1939 loge("onCallMerged: exception " + e); 1940 } 1941 1942 // After merge complete, update foreground as Active 1943 // and background call as Held, if background call exists 1944 processCallStateChange(mForegroundCall.getImsCall(), ImsPhoneCall.State.ACTIVE, 1945 DisconnectCause.NOT_DISCONNECTED); 1946 if (peerConnection != null) { 1947 processCallStateChange(mBackgroundCall.getImsCall(), ImsPhoneCall.State.HOLDING, 1948 DisconnectCause.NOT_DISCONNECTED); 1949 } 1950 1951 // Check if the merge was requested by an existing conference call. In that 1952 // case, no further action is required. 1953 if (!call.isMergeRequestedByConf()) { 1954 log("onCallMerged :: calling onMultipartyStateChanged()"); 1955 onMultipartyStateChanged(call, true); 1956 } else { 1957 log("onCallMerged :: Merge requested by existing conference."); 1958 // Reset the flag. 1959 call.resetIsMergeRequestedByConf(false); 1960 } 1961 logState(); 1962 } 1963 1964 @Override 1965 public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) { 1966 if (DBG) log("onCallMergeFailed reasonInfo=" + reasonInfo); 1967 1968 // TODO: the call to notifySuppServiceFailed throws up the "merge failed" dialog 1969 // We should move this into the InCallService so that it is handled appropriately 1970 // based on the user facing UI. 1971 mPhone.notifySuppServiceFailed(Phone.SuppService.CONFERENCE); 1972 1973 // Start plumbing this even through Telecom so other components can take 1974 // appropriate action. 1975 ImsPhoneConnection conn = findConnection(call); 1976 if (conn != null) { 1977 conn.onConferenceMergeFailed(); 1978 } 1979 } 1980 1981 /** 1982 * Called when the state of IMS conference participant(s) has changed. 1983 * 1984 * @param call the call object that carries out the IMS call. 1985 * @param participants the participant(s) and their new state information. 1986 */ 1987 @Override 1988 public void onConferenceParticipantsStateChanged(ImsCall call, 1989 List<ConferenceParticipant> participants) { 1990 if (DBG) log("onConferenceParticipantsStateChanged"); 1991 1992 ImsPhoneConnection conn = findConnection(call); 1993 if (conn != null) { 1994 conn.updateConferenceParticipants(participants); 1995 } 1996 } 1997 1998 @Override 1999 public void onCallSessionTtyModeReceived(ImsCall call, int mode) { 2000 mPhone.onTtyModeReceived(mode); 2001 } 2002 2003 @Override 2004 public void onCallHandover(ImsCall imsCall, int srcAccessTech, int targetAccessTech, 2005 ImsReasonInfo reasonInfo) { 2006 if (DBG) { 2007 log("onCallHandover :: srcAccessTech=" + srcAccessTech + ", targetAccessTech=" + 2008 targetAccessTech + ", reasonInfo=" + reasonInfo); 2009 } 2010 2011 boolean isHandoverToWifi = srcAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN && 2012 targetAccessTech == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN; 2013 if (isHandoverToWifi) { 2014 // If we handed over to wifi successfully, don't check for failure in the future. 2015 removeMessages(EVENT_CHECK_FOR_WIFI_HANDOVER); 2016 } 2017 2018 boolean isHandoverFromWifi = 2019 srcAccessTech == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN && 2020 targetAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN; 2021 if (mNotifyHandoverVideoFromWifiToLTE && isHandoverFromWifi && imsCall.isVideoCall()) { 2022 log("onCallHandover :: notifying of WIFI to LTE handover."); 2023 ImsPhoneConnection conn = findConnection(imsCall); 2024 if (conn != null) { 2025 conn.onConnectionEvent( 2026 TelephonyManager.EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE, null); 2027 } else { 2028 loge("onCallHandover :: failed to notify of handover; connection is null."); 2029 } 2030 } 2031 2032 mMetrics.writeOnImsCallHandoverEvent(mPhone.getPhoneId(), 2033 TelephonyCallSession.Event.Type.IMS_CALL_HANDOVER, imsCall.getCallSession(), 2034 srcAccessTech, targetAccessTech, reasonInfo); 2035 } 2036 2037 @Override 2038 public void onCallHandoverFailed(ImsCall imsCall, int srcAccessTech, int targetAccessTech, 2039 ImsReasonInfo reasonInfo) { 2040 if (DBG) { 2041 log("onCallHandoverFailed :: srcAccessTech=" + srcAccessTech + 2042 ", targetAccessTech=" + targetAccessTech + ", reasonInfo=" + reasonInfo); 2043 } 2044 mMetrics.writeOnImsCallHandoverEvent(mPhone.getPhoneId(), 2045 TelephonyCallSession.Event.Type.IMS_CALL_HANDOVER_FAILED, 2046 imsCall.getCallSession(), srcAccessTech, targetAccessTech, reasonInfo); 2047 2048 boolean isHandoverToWifi = srcAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN && 2049 targetAccessTech == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN; 2050 ImsPhoneConnection conn = findConnection(imsCall); 2051 if (conn != null && isHandoverToWifi) { 2052 log("onCallHandoverFailed - handover to WIFI Failed"); 2053 2054 // If we know we failed to handover, don't check for failure in the future. 2055 removeMessages(EVENT_CHECK_FOR_WIFI_HANDOVER); 2056 2057 if (mNotifyVtHandoverToWifiFail) { 2058 // Only notify others if carrier config indicates to do so. 2059 conn.onHandoverToWifiFailed(); 2060 } 2061 } 2062 } 2063 2064 /** 2065 * Handles a change to the multiparty state for an {@code ImsCall}. Notifies the associated 2066 * {@link ImsPhoneConnection} of the change. 2067 * 2068 * @param imsCall The IMS call. 2069 * @param isMultiParty {@code true} if the call became multiparty, {@code false} 2070 * otherwise. 2071 */ 2072 @Override 2073 public void onMultipartyStateChanged(ImsCall imsCall, boolean isMultiParty) { 2074 if (DBG) log("onMultipartyStateChanged to " + (isMultiParty ? "Y" : "N")); 2075 2076 ImsPhoneConnection conn = findConnection(imsCall); 2077 if (conn != null) { 2078 conn.updateMultipartyState(isMultiParty); 2079 } 2080 } 2081 }; 2082 2083 /** 2084 * Listen to the IMS call state change 2085 */ 2086 private ImsCall.Listener mImsUssdListener = new ImsCall.Listener() { 2087 @Override 2088 public void onCallStarted(ImsCall imsCall) { 2089 if (DBG) log("mImsUssdListener onCallStarted"); 2090 2091 if (imsCall == mUssdSession) { 2092 if (mPendingUssd != null) { 2093 AsyncResult.forMessage(mPendingUssd); 2094 mPendingUssd.sendToTarget(); 2095 mPendingUssd = null; 2096 } 2097 } 2098 } 2099 2100 @Override 2101 public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) { 2102 if (DBG) log("mImsUssdListener onCallStartFailed reasonCode=" + reasonInfo.getCode()); 2103 2104 onCallTerminated(imsCall, reasonInfo); 2105 } 2106 2107 @Override 2108 public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) { 2109 if (DBG) log("mImsUssdListener onCallTerminated reasonCode=" + reasonInfo.getCode()); 2110 removeMessages(EVENT_CHECK_FOR_WIFI_HANDOVER); 2111 2112 if (imsCall == mUssdSession) { 2113 mUssdSession = null; 2114 if (mPendingUssd != null) { 2115 CommandException ex = 2116 new CommandException(CommandException.Error.GENERIC_FAILURE); 2117 AsyncResult.forMessage(mPendingUssd, null, ex); 2118 mPendingUssd.sendToTarget(); 2119 mPendingUssd = null; 2120 } 2121 } 2122 imsCall.close(); 2123 } 2124 2125 @Override 2126 public void onCallUssdMessageReceived(ImsCall call, 2127 int mode, String ussdMessage) { 2128 if (DBG) log("mImsUssdListener onCallUssdMessageReceived mode=" + mode); 2129 2130 int ussdMode = -1; 2131 2132 switch(mode) { 2133 case ImsCall.USSD_MODE_REQUEST: 2134 ussdMode = CommandsInterface.USSD_MODE_REQUEST; 2135 break; 2136 2137 case ImsCall.USSD_MODE_NOTIFY: 2138 ussdMode = CommandsInterface.USSD_MODE_NOTIFY; 2139 break; 2140 } 2141 2142 mPhone.onIncomingUSSD(ussdMode, ussdMessage); 2143 } 2144 }; 2145 2146 /** 2147 * Listen to the IMS service state change 2148 * 2149 */ 2150 private ImsConnectionStateListener mImsConnectionStateListener = 2151 new ImsConnectionStateListener() { 2152 @Override 2153 public void onImsConnected() { 2154 if (DBG) log("onImsConnected"); 2155 mPhone.setServiceState(ServiceState.STATE_IN_SERVICE); 2156 mPhone.setImsRegistered(true); 2157 mMetrics.writeOnImsConnectionState(mPhone.getPhoneId(), 2158 ImsConnectionState.State.CONNECTED, null); 2159 } 2160 2161 @Override 2162 public void onImsDisconnected(ImsReasonInfo imsReasonInfo) { 2163 if (DBG) log("onImsDisconnected imsReasonInfo=" + imsReasonInfo); 2164 mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE); 2165 mPhone.setImsRegistered(false); 2166 mPhone.processDisconnectReason(imsReasonInfo); 2167 mMetrics.writeOnImsConnectionState(mPhone.getPhoneId(), 2168 ImsConnectionState.State.DISCONNECTED, imsReasonInfo); 2169 } 2170 2171 @Override 2172 public void onImsProgressing() { 2173 if (DBG) log("onImsProgressing"); 2174 mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE); 2175 mPhone.setImsRegistered(false); 2176 mMetrics.writeOnImsConnectionState(mPhone.getPhoneId(), 2177 ImsConnectionState.State.PROGRESSING, null); 2178 } 2179 2180 @Override 2181 public void onImsResumed() { 2182 if (DBG) log("onImsResumed"); 2183 mPhone.setServiceState(ServiceState.STATE_IN_SERVICE); 2184 mMetrics.writeOnImsConnectionState(mPhone.getPhoneId(), 2185 ImsConnectionState.State.RESUMED, null); 2186 } 2187 2188 @Override 2189 public void onImsSuspended() { 2190 if (DBG) log("onImsSuspended"); 2191 mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE); 2192 mMetrics.writeOnImsConnectionState(mPhone.getPhoneId(), 2193 ImsConnectionState.State.SUSPENDED, null); 2194 2195 } 2196 2197 @Override 2198 public void onFeatureCapabilityChanged(int serviceClass, 2199 int[] enabledFeatures, int[] disabledFeatures) { 2200 if (serviceClass == ImsServiceClass.MMTEL) { 2201 boolean tmpIsVideoCallEnabled = isVideoCallEnabled(); 2202 // Check enabledFeatures to determine capabilities. We ignore disabledFeatures. 2203 StringBuilder sb; 2204 if (DBG) { 2205 sb = new StringBuilder(120); 2206 sb.append("onFeatureCapabilityChanged: "); 2207 } 2208 for (int i = ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE; 2209 i <= ImsConfig.FeatureConstants.FEATURE_TYPE_UT_OVER_WIFI && 2210 i < enabledFeatures.length; i++) { 2211 if (enabledFeatures[i] == i) { 2212 // If the feature is set to its own integer value it is enabled. 2213 if (DBG) { 2214 sb.append(mImsFeatureStrings[i]); 2215 sb.append(":true "); 2216 } 2217 2218 mImsFeatureEnabled[i] = true; 2219 } else if (enabledFeatures[i] 2220 == ImsConfig.FeatureConstants.FEATURE_TYPE_UNKNOWN) { 2221 // FEATURE_TYPE_UNKNOWN indicates that a feature is disabled. 2222 if (DBG) { 2223 sb.append(mImsFeatureStrings[i]); 2224 sb.append(":false "); 2225 } 2226 2227 mImsFeatureEnabled[i] = false; 2228 } else { 2229 // Feature has unknown state; it is not its own value or -1. 2230 if (DBG) { 2231 loge("onFeatureCapabilityChanged(" + i + ", " + mImsFeatureStrings[i] 2232 + "): unexpectedValue=" + enabledFeatures[i]); 2233 } 2234 } 2235 } 2236 if (DBG) { 2237 log(sb.toString()); 2238 } 2239 if (tmpIsVideoCallEnabled != isVideoCallEnabled()) { 2240 mPhone.notifyForVideoCapabilityChanged(isVideoCallEnabled()); 2241 } 2242 2243 // TODO: Use the ImsCallSession or ImsCallProfile to tell the initial Wifi state and 2244 // {@link ImsCallSession.Listener#callSessionHandover} to listen for changes to 2245 // wifi capability caused by a handover. 2246 if (DBG) log("onFeatureCapabilityChanged: isVolteEnabled=" + isVolteEnabled() 2247 + ", isVideoCallEnabled=" + isVideoCallEnabled() 2248 + ", isVowifiEnabled=" + isVowifiEnabled() 2249 + ", isUtEnabled=" + isUtEnabled()); 2250 for (ImsPhoneConnection connection : mConnections) { 2251 connection.updateWifiState(); 2252 } 2253 2254 mPhone.onFeatureCapabilityChanged(); 2255 2256 mMetrics.writeOnImsCapabilities( 2257 mPhone.getPhoneId(), mImsFeatureEnabled); 2258 } 2259 } 2260 2261 @Override 2262 public void onVoiceMessageCountChanged(int count) { 2263 if (DBG) log("onVoiceMessageCountChanged :: count=" + count); 2264 mPhone.mDefaultPhone.setVoiceMessageCount(count); 2265 } 2266 2267 @Override 2268 public void registrationAssociatedUriChanged(Uri[] uris) { 2269 if (DBG) log("registrationAssociatedUriChanged"); 2270 mPhone.setCurrentSubscriberUris(uris); 2271 } 2272 }; 2273 2274 private ImsConfigListener.Stub mImsConfigListener = new ImsConfigListener.Stub() { 2275 @Override 2276 public void onGetFeatureResponse(int feature, int network, int value, int status) {} 2277 2278 @Override 2279 public void onSetFeatureResponse(int feature, int network, int value, int status) { 2280 mMetrics.writeImsSetFeatureValue( 2281 mPhone.getPhoneId(), feature, network, value, status); 2282 } 2283 2284 @Override 2285 public void onGetVideoQuality(int status, int quality) {} 2286 2287 @Override 2288 public void onSetVideoQuality(int status) {} 2289 2290 }; 2291 2292 public ImsUtInterface getUtInterface() throws ImsException { 2293 if (mImsManager == null) { 2294 throw getImsManagerIsNullException(); 2295 } 2296 2297 ImsUtInterface ut = mImsManager.getSupplementaryServiceConfiguration(); 2298 return ut; 2299 } 2300 2301 private void transferHandoverConnections(ImsPhoneCall call) { 2302 if (call.mConnections != null) { 2303 for (Connection c : call.mConnections) { 2304 c.mPreHandoverState = call.mState; 2305 log ("Connection state before handover is " + c.getStateBeforeHandover()); 2306 } 2307 } 2308 if (mHandoverCall.mConnections == null ) { 2309 mHandoverCall.mConnections = call.mConnections; 2310 } else { // Multi-call SRVCC 2311 mHandoverCall.mConnections.addAll(call.mConnections); 2312 } 2313 if (mHandoverCall.mConnections != null) { 2314 if (call.getImsCall() != null) { 2315 call.getImsCall().close(); 2316 } 2317 for (Connection c : mHandoverCall.mConnections) { 2318 ((ImsPhoneConnection)c).changeParent(mHandoverCall); 2319 ((ImsPhoneConnection)c).releaseWakeLock(); 2320 } 2321 } 2322 if (call.getState().isAlive()) { 2323 log ("Call is alive and state is " + call.mState); 2324 mHandoverCall.mState = call.mState; 2325 } 2326 call.mConnections.clear(); 2327 call.mState = ImsPhoneCall.State.IDLE; 2328 } 2329 2330 /* package */ 2331 void notifySrvccState(Call.SrvccState state) { 2332 if (DBG) log("notifySrvccState state=" + state); 2333 2334 mSrvccState = state; 2335 2336 if (mSrvccState == Call.SrvccState.COMPLETED) { 2337 transferHandoverConnections(mForegroundCall); 2338 transferHandoverConnections(mBackgroundCall); 2339 transferHandoverConnections(mRingingCall); 2340 } 2341 } 2342 2343 //****** Overridden from Handler 2344 2345 @Override 2346 public void 2347 handleMessage (Message msg) { 2348 AsyncResult ar; 2349 if (DBG) log("handleMessage what=" + msg.what); 2350 2351 switch (msg.what) { 2352 case EVENT_HANGUP_PENDINGMO: 2353 if (mPendingMO != null) { 2354 mPendingMO.onDisconnect(); 2355 removeConnection(mPendingMO); 2356 mPendingMO = null; 2357 } 2358 mPendingIntentExtras = null; 2359 updatePhoneState(); 2360 mPhone.notifyPreciseCallStateChanged(); 2361 break; 2362 case EVENT_RESUME_BACKGROUND: 2363 try { 2364 resumeWaitingOrHolding(); 2365 } catch (CallStateException e) { 2366 if (Phone.DEBUG_PHONE) { 2367 loge("handleMessage EVENT_RESUME_BACKGROUND exception=" + e); 2368 } 2369 } 2370 break; 2371 case EVENT_DIAL_PENDINGMO: 2372 dialInternal(mPendingMO, mClirMode, mPendingCallVideoState, mPendingIntentExtras); 2373 mPendingIntentExtras = null; 2374 break; 2375 2376 case EVENT_EXIT_ECBM_BEFORE_PENDINGMO: 2377 if (mPendingMO != null) { 2378 //Send ECBM exit request 2379 try { 2380 getEcbmInterface().exitEmergencyCallbackMode(); 2381 mPhone.setOnEcbModeExitResponse(this, EVENT_EXIT_ECM_RESPONSE_CDMA, null); 2382 pendingCallClirMode = mClirMode; 2383 pendingCallInEcm = true; 2384 } catch (ImsException e) { 2385 e.printStackTrace(); 2386 mPendingMO.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED); 2387 sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO); 2388 } 2389 } 2390 break; 2391 2392 case EVENT_EXIT_ECM_RESPONSE_CDMA: 2393 // no matter the result, we still do the same here 2394 if (pendingCallInEcm) { 2395 dialInternal(mPendingMO, pendingCallClirMode, 2396 mPendingCallVideoState, mPendingIntentExtras); 2397 mPendingIntentExtras = null; 2398 pendingCallInEcm = false; 2399 } 2400 mPhone.unsetOnEcbModeExitResponse(this); 2401 break; 2402 case EVENT_VT_DATA_USAGE_UPDATE: 2403 ar = (AsyncResult) msg.obj; 2404 ImsCall call = (ImsCall) ar.userObj; 2405 Long usage = (long) ar.result; 2406 log("VT data usage update. usage = " + usage + ", imsCall = " + call); 2407 2408 Long oldUsage = 0L; 2409 if (mVtDataUsageMap.containsKey(call.uniqueId)) { 2410 oldUsage = mVtDataUsageMap.get(call.uniqueId); 2411 } 2412 mTotalVtDataUsage += (usage - oldUsage); 2413 mVtDataUsageMap.put(call.uniqueId, usage); 2414 break; 2415 case EVENT_DATA_ENABLED_CHANGED: 2416 ar = (AsyncResult) msg.obj; 2417 if (ar.result instanceof Pair) { 2418 Pair<Boolean, Integer> p = (Pair<Boolean, Integer>) ar.result; 2419 onDataEnabledChanged(p.first, p.second); 2420 } 2421 break; 2422 case EVENT_GET_IMS_SERVICE: 2423 try { 2424 getImsService(); 2425 } catch (ImsException e) { 2426 loge("getImsService: " + e); 2427 retryGetImsService(); 2428 } 2429 break; 2430 case EVENT_CHECK_FOR_WIFI_HANDOVER: 2431 if (msg.obj instanceof ImsCall) { 2432 ImsCall imsCall = (ImsCall) msg.obj; 2433 if (!imsCall.isWifiCall()) { 2434 // Call did not handover to wifi, notify of handover failure. 2435 ImsPhoneConnection conn = findConnection(imsCall); 2436 if (conn != null) { 2437 conn.onHandoverToWifiFailed(); 2438 } 2439 } 2440 } 2441 break; 2442 case EVENT_CLEAR_DISCONNECTING_CONN: 2443 if (msg.obj instanceof ImsPhoneConnection) { 2444 ImsPhoneConnection imsPhoneConnection = (ImsPhoneConnection) msg.obj; 2445 removeMessages(EVENT_CLEAR_DISCONNECTING_CONN, imsPhoneConnection); 2446 2447 // We have timed out waiting for the modem while disconnecting this connection. 2448 // Manually disconnect to avoid tracking an invalid call. 2449 if (imsPhoneConnection != null && imsPhoneConnection.isDisconnecting()) { 2450 Rlog.e(LOG_TAG, "No response from modem. Manually disconnecting: " + 2451 imsPhoneConnection); 2452 imsPhoneConnection.onDisconnect(); 2453 } 2454 } 2455 break; 2456 } 2457 } 2458 2459 @Override 2460 protected void log(String msg) { 2461 Rlog.d(LOG_TAG, "[ImsPhoneCallTracker] " + msg); 2462 } 2463 2464 protected void loge(String msg) { 2465 Rlog.e(LOG_TAG, "[ImsPhoneCallTracker] " + msg); 2466 } 2467 2468 /** 2469 * Logs the current state of the ImsPhoneCallTracker. Useful for debugging issues with 2470 * call tracking. 2471 */ 2472 /* package */ 2473 void logState() { 2474 if (!VERBOSE_STATE_LOGGING) { 2475 return; 2476 } 2477 2478 StringBuilder sb = new StringBuilder(); 2479 sb.append("Current IMS PhoneCall State:\n"); 2480 sb.append(" Foreground: "); 2481 sb.append(mForegroundCall); 2482 sb.append("\n"); 2483 sb.append(" Background: "); 2484 sb.append(mBackgroundCall); 2485 sb.append("\n"); 2486 sb.append(" Ringing: "); 2487 sb.append(mRingingCall); 2488 sb.append("\n"); 2489 sb.append(" Handover: "); 2490 sb.append(mHandoverCall); 2491 sb.append("\n"); 2492 Rlog.v(LOG_TAG, sb.toString()); 2493 } 2494 2495 @Override 2496 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 2497 pw.println("ImsPhoneCallTracker extends:"); 2498 super.dump(fd, pw, args); 2499 pw.println(" mVoiceCallEndedRegistrants=" + mVoiceCallEndedRegistrants); 2500 pw.println(" mVoiceCallStartedRegistrants=" + mVoiceCallStartedRegistrants); 2501 pw.println(" mRingingCall=" + mRingingCall); 2502 pw.println(" mForegroundCall=" + mForegroundCall); 2503 pw.println(" mBackgroundCall=" + mBackgroundCall); 2504 pw.println(" mHandoverCall=" + mHandoverCall); 2505 pw.println(" mPendingMO=" + mPendingMO); 2506 //pw.println(" mHangupPendingMO=" + mHangupPendingMO); 2507 pw.println(" mPhone=" + mPhone); 2508 pw.println(" mDesiredMute=" + mDesiredMute); 2509 pw.println(" mState=" + mState); 2510 for (int i = 0; i < mImsFeatureEnabled.length; i++) { 2511 pw.println(" " + mImsFeatureStrings[i] + ": " 2512 + ((mImsFeatureEnabled[i]) ? "enabled" : "disabled")); 2513 } 2514 pw.println(" mTotalVtDataUsage=" + mTotalVtDataUsage); 2515 for (Map.Entry<Integer, Long> entry : mVtDataUsageMap.entrySet()) { 2516 pw.println(" id=" + entry.getKey() + " ,usage=" + entry.getValue()); 2517 } 2518 2519 pw.flush(); 2520 pw.println("++++++++++++++++++++++++++++++++"); 2521 2522 try { 2523 if (mImsManager != null) { 2524 mImsManager.dump(fd, pw, args); 2525 } 2526 } catch (Exception e) { 2527 e.printStackTrace(); 2528 } 2529 2530 if (mConnections != null && mConnections.size() > 0) { 2531 pw.println("mConnections:"); 2532 for (int i = 0; i < mConnections.size(); i++) { 2533 pw.println(" [" + i + "]: " + mConnections.get(i)); 2534 } 2535 } 2536 } 2537 2538 @Override 2539 protected void handlePollCalls(AsyncResult ar) { 2540 } 2541 2542 /* package */ 2543 ImsEcbm getEcbmInterface() throws ImsException { 2544 if (mImsManager == null) { 2545 throw getImsManagerIsNullException(); 2546 } 2547 2548 ImsEcbm ecbm = mImsManager.getEcbmInterface(mServiceId); 2549 return ecbm; 2550 } 2551 2552 /* package */ 2553 ImsMultiEndpoint getMultiEndpointInterface() throws ImsException { 2554 if (mImsManager == null) { 2555 throw getImsManagerIsNullException(); 2556 } 2557 2558 try { 2559 return mImsManager.getMultiEndpointInterface(mServiceId); 2560 } catch (ImsException e) { 2561 if (e.getCode() == ImsReasonInfo.CODE_MULTIENDPOINT_NOT_SUPPORTED) { 2562 return null; 2563 } else { 2564 throw e; 2565 } 2566 2567 } 2568 } 2569 2570 public boolean isInEmergencyCall() { 2571 return mIsInEmergencyCall; 2572 } 2573 2574 public boolean isVolteEnabled() { 2575 return mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE]; 2576 } 2577 2578 public boolean isVowifiEnabled() { 2579 return mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_WIFI]; 2580 } 2581 2582 public boolean isVideoCallEnabled() { 2583 return (mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE] 2584 || mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_WIFI]); 2585 } 2586 2587 @Override 2588 public PhoneConstants.State getState() { 2589 return mState; 2590 } 2591 2592 private void retryGetImsService() { 2593 //Leave mImsManager as null, then CallStateException will be thrown when dialing 2594 mImsManager = null; 2595 // Exponential backoff during retry, limited to 32 seconds. 2596 loge("getImsService: Retrying getting ImsService..."); 2597 removeMessages(EVENT_GET_IMS_SERVICE); 2598 sendEmptyMessageDelayed(EVENT_GET_IMS_SERVICE, 2599 (1 << mImsServiceRetryCount) * IMS_RETRY_STARTING_TIMEOUT_MS); 2600 if (mImsServiceRetryCount <= CEILING_SERVICE_RETRY_COUNT) { 2601 mImsServiceRetryCount++; 2602 } 2603 } 2604 2605 private void setVideoCallProvider(ImsPhoneConnection conn, ImsCall imsCall) 2606 throws RemoteException { 2607 IImsVideoCallProvider imsVideoCallProvider = 2608 imsCall.getCallSession().getVideoCallProvider(); 2609 if (imsVideoCallProvider != null) { 2610 ImsVideoCallProviderWrapper imsVideoCallProviderWrapper = 2611 new ImsVideoCallProviderWrapper(imsVideoCallProvider); 2612 conn.setVideoProvider(imsVideoCallProviderWrapper); 2613 imsVideoCallProviderWrapper.registerForDataUsageUpdate 2614 (this, EVENT_VT_DATA_USAGE_UPDATE, imsCall); 2615 imsVideoCallProviderWrapper.addImsVideoProviderCallback(conn); 2616 } 2617 } 2618 2619 public boolean isUtEnabled() { 2620 return (mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_UT_OVER_LTE] 2621 || mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_UT_OVER_WIFI]); 2622 } 2623 2624 /** 2625 * Given a call subject, removes any characters considered by the current carrier to be 2626 * invalid, as well as escaping (using \) any characters which the carrier requires to be 2627 * escaped. 2628 * 2629 * @param callSubject The call subject. 2630 * @return The call subject with invalid characters removed and escaping applied as required. 2631 */ 2632 private String cleanseInstantLetteringMessage(String callSubject) { 2633 if (TextUtils.isEmpty(callSubject)) { 2634 return callSubject; 2635 } 2636 2637 // Get the carrier config for the current sub. 2638 CarrierConfigManager configMgr = (CarrierConfigManager) 2639 mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 2640 // Bail if we can't find the carrier config service. 2641 if (configMgr == null) { 2642 return callSubject; 2643 } 2644 2645 PersistableBundle carrierConfig = configMgr.getConfigForSubId(mPhone.getSubId()); 2646 // Bail if no carrier config found. 2647 if (carrierConfig == null) { 2648 return callSubject; 2649 } 2650 2651 // Try to replace invalid characters 2652 String invalidCharacters = carrierConfig.getString( 2653 CarrierConfigManager.KEY_CARRIER_INSTANT_LETTERING_INVALID_CHARS_STRING); 2654 if (!TextUtils.isEmpty(invalidCharacters)) { 2655 callSubject = callSubject.replaceAll(invalidCharacters, ""); 2656 } 2657 2658 // Try to escape characters which need to be escaped. 2659 String escapedCharacters = carrierConfig.getString( 2660 CarrierConfigManager.KEY_CARRIER_INSTANT_LETTERING_ESCAPED_CHARS_STRING); 2661 if (!TextUtils.isEmpty(escapedCharacters)) { 2662 callSubject = escapeChars(escapedCharacters, callSubject); 2663 } 2664 return callSubject; 2665 } 2666 2667 /** 2668 * Given a source string, return a string where a set of characters are escaped using the 2669 * backslash character. 2670 * 2671 * @param toEscape The characters to escape with a backslash. 2672 * @param source The source string. 2673 * @return The source string with characters escaped. 2674 */ 2675 private String escapeChars(String toEscape, String source) { 2676 StringBuilder escaped = new StringBuilder(); 2677 for (char c : source.toCharArray()) { 2678 if (toEscape.contains(Character.toString(c))) { 2679 escaped.append("\\"); 2680 } 2681 escaped.append(c); 2682 } 2683 2684 return escaped.toString(); 2685 } 2686 2687 /** 2688 * Initiates a pull of an external call. 2689 * 2690 * Initiates a pull by making a dial request with the {@link ImsCallProfile#EXTRA_IS_CALL_PULL} 2691 * extra specified. We call {@link ImsPhone#notifyUnknownConnection(Connection)} which notifies 2692 * Telecom of the new dialed connection. The 2693 * {@code PstnIncomingCallNotifier#maybeSwapWithUnknownConnection} logic ensures that the new 2694 * {@link ImsPhoneConnection} resulting from the dial gets swapped with the 2695 * {@link ImsExternalConnection}, which effectively makes the external call become a regular 2696 * call. Magic! 2697 * 2698 * @param number The phone number of the call to be pulled. 2699 * @param videoState The desired video state of the pulled call. 2700 * @param dialogId The {@link ImsExternalConnection#getCallId()} dialog id associated with the 2701 * call which is being pulled. 2702 */ 2703 @Override 2704 public void pullExternalCall(String number, int videoState, int dialogId) { 2705 Bundle extras = new Bundle(); 2706 extras.putBoolean(ImsCallProfile.EXTRA_IS_CALL_PULL, true); 2707 extras.putInt(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID, dialogId); 2708 try { 2709 Connection connection = dial(number, videoState, extras); 2710 mPhone.notifyUnknownConnection(connection); 2711 } catch (CallStateException e) { 2712 loge("pullExternalCall failed - " + e); 2713 } 2714 } 2715 2716 private ImsException getImsManagerIsNullException() { 2717 return new ImsException("no ims manager", ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); 2718 } 2719 2720 /** 2721 * Determines if answering an incoming call will cause the active call to be disconnected. 2722 * <p> 2723 * This will be the case if 2724 * {@link CarrierConfigManager#KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL} is 2725 * {@code true} for the carrier, the active call is a video call over WIFI, and the incoming 2726 * call is an audio call. 2727 * 2728 * @param activeCall The active call. 2729 * @param incomingCall The incoming call. 2730 * @return {@code true} if answering the incoming call will cause the active call to be 2731 * disconnected, {@code false} otherwise. 2732 */ 2733 private boolean shouldDisconnectActiveCallOnAnswer(ImsCall activeCall, 2734 ImsCall incomingCall) { 2735 2736 if (!mDropVideoCallWhenAnsweringAudioCall) { 2737 return false; 2738 } 2739 2740 boolean isActiveCallVideo = activeCall.isVideoCall() || 2741 (mTreatDowngradedVideoCallsAsVideoCalls && activeCall.wasVideoCall()); 2742 boolean isActiveCallOnWifi = activeCall.isWifiCall(); 2743 boolean isVoWifiEnabled = mImsManager.isWfcEnabledByPlatform(mPhone.getContext()) && 2744 mImsManager.isWfcEnabledByUser(mPhone.getContext()); 2745 boolean isIncomingCallAudio = !incomingCall.isVideoCall(); 2746 log("shouldDisconnectActiveCallOnAnswer : isActiveCallVideo=" + isActiveCallVideo + 2747 " isActiveCallOnWifi=" + isActiveCallOnWifi + " isIncomingCallAudio=" + 2748 isIncomingCallAudio + " isVowifiEnabled=" + isVoWifiEnabled); 2749 2750 return isActiveCallVideo && isActiveCallOnWifi && isIncomingCallAudio && !isVoWifiEnabled; 2751 } 2752 2753 /** Get aggregated video call data usage since boot. 2754 * 2755 * @return data usage in bytes 2756 */ 2757 public long getVtDataUsage() { 2758 2759 // If there is an ongoing VT call, request the latest VT usage from the modem. The latest 2760 // usage will return asynchronously so it won't be counted in this round, but it will be 2761 // eventually counted when next getVtDataUsage is called. 2762 if (mState != PhoneConstants.State.IDLE) { 2763 for (ImsPhoneConnection conn : mConnections) { 2764 android.telecom.Connection.VideoProvider videoProvider = conn.getVideoProvider(); 2765 if (videoProvider != null) { 2766 videoProvider.onRequestConnectionDataUsage(); 2767 } 2768 } 2769 } 2770 2771 return mTotalVtDataUsage; 2772 } 2773 2774 public void registerPhoneStateListener(PhoneStateListener listener) { 2775 mPhoneStateListeners.add(listener); 2776 } 2777 2778 public void unregisterPhoneStateListener(PhoneStateListener listener) { 2779 mPhoneStateListeners.remove(listener); 2780 } 2781 2782 /** 2783 * Notifies local telephony listeners of changes to the IMS phone state. 2784 * 2785 * @param oldState The old state. 2786 * @param newState The new state. 2787 */ 2788 private void notifyPhoneStateChanged(PhoneConstants.State oldState, 2789 PhoneConstants.State newState) { 2790 2791 for (PhoneStateListener listener : mPhoneStateListeners) { 2792 listener.onPhoneStateChanged(oldState, newState); 2793 } 2794 } 2795 2796 /** Modify video call to a new video state. 2797 * 2798 * @param imsCall IMS call to be modified 2799 * @param newVideoState New video state. (Refer to VideoProfile) 2800 */ 2801 private void modifyVideoCall(ImsCall imsCall, int newVideoState) { 2802 ImsPhoneConnection conn = findConnection(imsCall); 2803 if (conn != null) { 2804 int oldVideoState = conn.getVideoState(); 2805 if (conn.getVideoProvider() != null) { 2806 conn.getVideoProvider().onSendSessionModifyRequest( 2807 new VideoProfile(oldVideoState), new VideoProfile(newVideoState)); 2808 } 2809 } 2810 } 2811 2812 /** 2813 * Handler of data enabled changed event 2814 * @param enabled True if data is enabled, otherwise disabled. 2815 * @param reason Reason for data enabled/disabled (see {@code REASON_*} in 2816 * {@link DataEnabledSettings}. 2817 */ 2818 private void onDataEnabledChanged(boolean enabled, int reason) { 2819 2820 log("onDataEnabledChanged: enabled=" + enabled + ", reason=" + reason); 2821 2822 ImsManager.getInstance(mPhone.getContext(), mPhone.getPhoneId()).setDataEnabled(enabled); 2823 2824 if (mIgnoreDataEnabledChangedForVideoCalls) { 2825 log("Ignore data " + ((enabled) ? "enabled" : "disabled") + " due to carrier policy."); 2826 return; 2827 } 2828 2829 if (!enabled) { 2830 int reasonCode; 2831 if (reason == DataEnabledSettings.REASON_POLICY_DATA_ENABLED) { 2832 reasonCode = ImsReasonInfo.CODE_DATA_LIMIT_REACHED; 2833 } else if (reason == DataEnabledSettings.REASON_USER_DATA_ENABLED) { 2834 reasonCode = ImsReasonInfo.CODE_DATA_DISABLED; 2835 } else { 2836 // Unexpected code, default to data disabled. 2837 reasonCode = ImsReasonInfo.CODE_DATA_DISABLED; 2838 } 2839 2840 // If data is disabled while there are ongoing VT calls which are not taking place over 2841 // wifi, then they should be disconnected to prevent the user from incurring further 2842 // data charges. 2843 for (ImsPhoneConnection conn : mConnections) { 2844 ImsCall imsCall = conn.getImsCall(); 2845 if (imsCall != null && imsCall.isVideoCall() && !imsCall.isWifiCall()) { 2846 if (conn.hasCapabilities( 2847 Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL | 2848 Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE)) { 2849 2850 // If the carrier supports downgrading to voice, then we can simply issue a 2851 // downgrade to voice instead of terminating the call. 2852 if (reasonCode == ImsReasonInfo.CODE_DATA_DISABLED) { 2853 conn.onConnectionEvent(TelephonyManager.EVENT_DOWNGRADE_DATA_DISABLED, 2854 null); 2855 } else if (reasonCode == ImsReasonInfo.CODE_DATA_LIMIT_REACHED) { 2856 conn.onConnectionEvent( 2857 TelephonyManager.EVENT_DOWNGRADE_DATA_LIMIT_REACHED, null); 2858 } 2859 modifyVideoCall(imsCall, VideoProfile.STATE_AUDIO_ONLY); 2860 } else { 2861 // If the carrier does not support downgrading to voice, the only choice we 2862 // have is to terminate the call. 2863 try { 2864 imsCall.terminate(ImsReasonInfo.CODE_USER_TERMINATED, reasonCode); 2865 } catch (ImsException ie) { 2866 loge("Couldn't terminate call " + imsCall); 2867 } 2868 } 2869 } 2870 } 2871 } 2872 2873 // This will call into updateVideoCallFeatureValue and eventually all clients will be 2874 // asynchronously notified that the availability of VT over LTE has changed. 2875 ImsManager.updateImsServiceConfig(mPhone.getContext(), mPhone.getPhoneId(), true); 2876 } 2877 2878 /** 2879 * @return {@code true} if the device is connected to a WIFI network, {@code false} otherwise. 2880 */ 2881 private boolean isWifiConnected() { 2882 ConnectivityManager cm = (ConnectivityManager) mPhone.getContext() 2883 .getSystemService(Context.CONNECTIVITY_SERVICE); 2884 if (cm != null) { 2885 NetworkInfo ni = cm.getActiveNetworkInfo(); 2886 if (ni != null && ni.isConnected()) { 2887 return ni.getType() == ConnectivityManager.TYPE_WIFI; 2888 } 2889 } 2890 return false; 2891 } 2892 2893 /** 2894 * @return {@code true} if downgrading of a video call to audio is supported. 2895 */ 2896 public boolean isCarrierDowngradeOfVtCallSupported() { 2897 return mSupportDowngradeVtToAudio; 2898 } 2899} 2900