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