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