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