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