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