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 17 package com.android.incallui; 18 19 import com.google.common.base.Preconditions; 20 21 import android.app.ActivityManager.TaskDescription; 22 import android.app.FragmentManager; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.res.Resources; 26 import android.database.ContentObserver; 27 import android.graphics.Point; 28 import android.os.Bundle; 29 import android.os.Handler; 30 import android.provider.CallLog; 31 import android.telecom.DisconnectCause; 32 import android.telecom.PhoneAccount; 33 import android.telecom.PhoneAccountHandle; 34 import android.telecom.TelecomManager; 35 import android.telecom.VideoProfile; 36 import android.telephony.PhoneStateListener; 37 import android.telephony.TelephonyManager; 38 import android.text.TextUtils; 39 import android.view.View; 40 import android.view.Window; 41 import android.view.WindowManager; 42 43 import com.android.contacts.common.GeoUtil; 44 import com.android.contacts.common.compat.CompatUtils; 45 import com.android.contacts.common.compat.telecom.TelecomManagerCompat; 46 import com.android.contacts.common.interactions.TouchPointManager; 47 import com.android.contacts.common.testing.NeededForTesting; 48 import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette; 49 import com.android.dialer.R; 50 import com.android.dialer.calllog.CallLogAsyncTaskUtil; 51 import com.android.dialer.calllog.CallLogAsyncTaskUtil.OnCallLogQueryFinishedListener; 52 import com.android.dialer.database.FilteredNumberAsyncQueryHandler; 53 import com.android.dialer.database.FilteredNumberAsyncQueryHandler.OnCheckBlockedListener; 54 import com.android.dialer.filterednumber.FilteredNumbersUtil; 55 import com.android.dialer.logging.InteractionEvent; 56 import com.android.dialer.logging.Logger; 57 import com.android.dialer.util.TelecomUtil; 58 import com.android.incallui.util.TelecomCallUtil; 59 import com.android.incalluibind.ObjectFactory; 60 61 import java.util.Collections; 62 import java.util.List; 63 import java.util.Locale; 64 import java.util.Set; 65 import java.util.concurrent.ConcurrentHashMap; 66 import java.util.concurrent.CopyOnWriteArrayList; 67 import java.util.concurrent.atomic.AtomicBoolean; 68 69 /** 70 * Takes updates from the CallList and notifies the InCallActivity (UI) 71 * of the changes. 72 * Responsible for starting the activity for a new call and finishing the activity when all calls 73 * are disconnected. 74 * Creates and manages the in-call state and provides a listener pattern for the presenters 75 * that want to listen in on the in-call state changes. 76 * TODO: This class has become more of a state machine at this point. Consider renaming. 77 */ 78 public class InCallPresenter implements CallList.Listener, 79 CircularRevealFragment.OnCircularRevealCompleteListener, 80 InCallVideoCallCallbackNotifier.SessionModificationListener { 81 82 private static final String EXTRA_FIRST_TIME_SHOWN = 83 "com.android.incallui.intent.extra.FIRST_TIME_SHOWN"; 84 85 private static final long BLOCK_QUERY_TIMEOUT_MS = 1000; 86 87 private static final Bundle EMPTY_EXTRAS = new Bundle(); 88 89 private static InCallPresenter sInCallPresenter; 90 91 /** 92 * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is 93 * load factor before resizing, 1 means we only expect a single thread to 94 * access the map so make only a single shard 95 */ 96 private final Set<InCallStateListener> mListeners = Collections.newSetFromMap( 97 new ConcurrentHashMap<InCallStateListener, Boolean>(8, 0.9f, 1)); 98 private final List<IncomingCallListener> mIncomingCallListeners = new CopyOnWriteArrayList<>(); 99 private final Set<InCallDetailsListener> mDetailsListeners = Collections.newSetFromMap( 100 new ConcurrentHashMap<InCallDetailsListener, Boolean>(8, 0.9f, 1)); 101 private final Set<CanAddCallListener> mCanAddCallListeners = Collections.newSetFromMap( 102 new ConcurrentHashMap<CanAddCallListener, Boolean>(8, 0.9f, 1)); 103 private final Set<InCallUiListener> mInCallUiListeners = Collections.newSetFromMap( 104 new ConcurrentHashMap<InCallUiListener, Boolean>(8, 0.9f, 1)); 105 private final Set<InCallOrientationListener> mOrientationListeners = Collections.newSetFromMap( 106 new ConcurrentHashMap<InCallOrientationListener, Boolean>(8, 0.9f, 1)); 107 private final Set<InCallEventListener> mInCallEventListeners = Collections.newSetFromMap( 108 new ConcurrentHashMap<InCallEventListener, Boolean>(8, 0.9f, 1)); 109 110 private AudioModeProvider mAudioModeProvider; 111 private StatusBarNotifier mStatusBarNotifier; 112 private ContactInfoCache mContactInfoCache; 113 private Context mContext; 114 private CallList mCallList; 115 private InCallActivity mInCallActivity; 116 private InCallState mInCallState = InCallState.NO_CALLS; 117 private ProximitySensor mProximitySensor; 118 private boolean mServiceConnected = false; 119 private boolean mAccountSelectionCancelled = false; 120 private InCallCameraManager mInCallCameraManager = null; 121 private AnswerPresenter mAnswerPresenter = new AnswerPresenter(); 122 private FilteredNumberAsyncQueryHandler mFilteredQueryHandler; 123 124 /** 125 * Whether or not we are currently bound and waiting for Telecom to send us a new call. 126 */ 127 private boolean mBoundAndWaitingForOutgoingCall; 128 129 /** 130 * If there is no actual call currently in the call list, this will be used as a fallback 131 * to determine the theme color for InCallUI. 132 */ 133 private PhoneAccountHandle mPendingPhoneAccountHandle; 134 135 /** 136 * Determines if the InCall UI is in fullscreen mode or not. 137 */ 138 private boolean mIsFullScreen = false; 139 140 private final android.telecom.Call.Callback mCallCallback = new android.telecom.Call.Callback() { 141 @Override 142 public void onPostDialWait(android.telecom.Call telecomCall, 143 String remainingPostDialSequence) { 144 final Call call = mCallList.getCallByTelecomCall(telecomCall); 145 if (call == null) { 146 Log.w(this, "Call not found in call list: " + telecomCall); 147 return; 148 } 149 onPostDialCharWait(call.getId(), remainingPostDialSequence); 150 } 151 152 @Override 153 public void onDetailsChanged(android.telecom.Call telecomCall, 154 android.telecom.Call.Details details) { 155 final Call call = mCallList.getCallByTelecomCall(telecomCall); 156 if (call == null) { 157 Log.w(this, "Call not found in call list: " + telecomCall); 158 return; 159 } 160 for (InCallDetailsListener listener : mDetailsListeners) { 161 listener.onDetailsChanged(call, details); 162 } 163 } 164 165 @Override 166 public void onConferenceableCallsChanged(android.telecom.Call telecomCall, 167 List<android.telecom.Call> conferenceableCalls) { 168 Log.i(this, "onConferenceableCallsChanged: " + telecomCall); 169 onDetailsChanged(telecomCall, telecomCall.getDetails()); 170 } 171 }; 172 173 private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { 174 public void onCallStateChanged(int state, String incomingNumber) { 175 if (state == TelephonyManager.CALL_STATE_RINGING) { 176 if (FilteredNumbersUtil.hasRecentEmergencyCall(mContext)) { 177 return; 178 } 179 // Check if the number is blocked, to silence the ringer. 180 String countryIso = GeoUtil.getCurrentCountryIso(mContext); 181 mFilteredQueryHandler.isBlockedNumber( 182 mOnCheckBlockedListener, incomingNumber, countryIso); 183 } 184 } 185 }; 186 187 private final OnCheckBlockedListener mOnCheckBlockedListener = new OnCheckBlockedListener() { 188 @Override 189 public void onCheckComplete(final Integer id) { 190 if (id != null) { 191 // Silence the ringer now to prevent ringing and vibration before the call is 192 // terminated when Telecom attempts to add it. 193 TelecomUtil.silenceRinger(mContext); 194 } 195 } 196 }; 197 198 /** 199 * Observes the CallLog to delete the call log entry for the blocked call after it is added. 200 * Times out if too much time has passed. 201 */ 202 private class BlockedNumberContentObserver extends ContentObserver { 203 private static final int TIMEOUT_MS = 5000; 204 205 private Handler mHandler; 206 private String mNumber; 207 private long mTimeAddedMs; 208 209 private Runnable mTimeoutRunnable = new Runnable() { 210 @Override 211 public void run() { 212 unregister(); 213 } 214 }; 215 BlockedNumberContentObserver(Handler handler, String number, long timeAddedMs)216 public BlockedNumberContentObserver(Handler handler, String number, long timeAddedMs) { 217 super(handler); 218 219 mHandler = handler; 220 mNumber = number; 221 mTimeAddedMs = timeAddedMs; 222 } 223 224 @Override onChange(boolean selfChange)225 public void onChange(boolean selfChange) { 226 CallLogAsyncTaskUtil.deleteBlockedCall(mContext, mNumber, mTimeAddedMs, 227 new OnCallLogQueryFinishedListener() { 228 @Override 229 public void onQueryFinished(boolean hasEntry) { 230 if (mContext != null && hasEntry) { 231 unregister(); 232 } 233 } 234 }); 235 } 236 register()237 public void register() { 238 if (mContext != null) { 239 mContext.getContentResolver().registerContentObserver( 240 CallLog.CONTENT_URI, true, this); 241 mHandler.postDelayed(mTimeoutRunnable, TIMEOUT_MS); 242 } 243 } 244 unregister()245 private void unregister() { 246 if (mContext != null) { 247 mHandler.removeCallbacks(mTimeoutRunnable); 248 mContext.getContentResolver().unregisterContentObserver(this); 249 } 250 } 251 }; 252 253 /** 254 * Is true when the activity has been previously started. Some code needs to know not just if 255 * the activity is currently up, but if it had been previously shown in foreground for this 256 * in-call session (e.g., StatusBarNotifier). This gets reset when the session ends in the 257 * tear-down method. 258 */ 259 private boolean mIsActivityPreviouslyStarted = false; 260 261 /** 262 * Whether or not InCallService is bound to Telecom. 263 */ 264 private boolean mServiceBound = false; 265 266 /** 267 * When configuration changes Android kills the current activity and starts a new one. 268 * The flag is used to check if full clean up is necessary (activity is stopped and new 269 * activity won't be started), or if a new activity will be started right after the current one 270 * is destroyed, and therefore no need in release all resources. 271 */ 272 private boolean mIsChangingConfigurations = false; 273 274 /** Display colors for the UI. Consists of a primary color and secondary (darker) color */ 275 private MaterialPalette mThemeColors; 276 277 private TelecomManager mTelecomManager; 278 private TelephonyManager mTelephonyManager; 279 getInstance()280 public static synchronized InCallPresenter getInstance() { 281 if (sInCallPresenter == null) { 282 sInCallPresenter = new InCallPresenter(); 283 } 284 return sInCallPresenter; 285 } 286 287 @NeededForTesting setInstance(InCallPresenter inCallPresenter)288 static synchronized void setInstance(InCallPresenter inCallPresenter) { 289 sInCallPresenter = inCallPresenter; 290 } 291 getInCallState()292 public InCallState getInCallState() { 293 return mInCallState; 294 } 295 getCallList()296 public CallList getCallList() { 297 return mCallList; 298 } 299 setUp(Context context, CallList callList, AudioModeProvider audioModeProvider, StatusBarNotifier statusBarNotifier, ContactInfoCache contactInfoCache, ProximitySensor proximitySensor)300 public void setUp(Context context, 301 CallList callList, 302 AudioModeProvider audioModeProvider, 303 StatusBarNotifier statusBarNotifier, 304 ContactInfoCache contactInfoCache, 305 ProximitySensor proximitySensor) { 306 if (mServiceConnected) { 307 Log.i(this, "New service connection replacing existing one."); 308 // retain the current resources, no need to create new ones. 309 Preconditions.checkState(context == mContext); 310 Preconditions.checkState(callList == mCallList); 311 Preconditions.checkState(audioModeProvider == mAudioModeProvider); 312 return; 313 } 314 315 Preconditions.checkNotNull(context); 316 mContext = context; 317 318 mContactInfoCache = contactInfoCache; 319 320 mStatusBarNotifier = statusBarNotifier; 321 addListener(mStatusBarNotifier); 322 323 mAudioModeProvider = audioModeProvider; 324 325 mProximitySensor = proximitySensor; 326 addListener(mProximitySensor); 327 328 addIncomingCallListener(mAnswerPresenter); 329 addInCallUiListener(mAnswerPresenter); 330 331 mCallList = callList; 332 333 // This only gets called by the service so this is okay. 334 mServiceConnected = true; 335 336 // The final thing we do in this set up is add ourselves as a listener to CallList. This 337 // will kick off an update and the whole process can start. 338 mCallList.addListener(this); 339 340 VideoPauseController.getInstance().setUp(this); 341 InCallVideoCallCallbackNotifier.getInstance().addSessionModificationListener(this); 342 343 mFilteredQueryHandler = new FilteredNumberAsyncQueryHandler(context.getContentResolver()); 344 mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 345 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); 346 mCallList.setFilteredNumberQueryHandler(mFilteredQueryHandler); 347 348 Log.d(this, "Finished InCallPresenter.setUp"); 349 } 350 351 /** 352 * Called when the telephony service has disconnected from us. This will happen when there are 353 * no more active calls. However, we may still want to continue showing the UI for 354 * certain cases like showing "Call Ended". 355 * What we really want is to wait for the activity and the service to both disconnect before we 356 * tear things down. This method sets a serviceConnected boolean and calls a secondary method 357 * that performs the aforementioned logic. 358 */ tearDown()359 public void tearDown() { 360 Log.d(this, "tearDown"); 361 mCallList.clearOnDisconnect(); 362 363 mServiceConnected = false; 364 attemptCleanup(); 365 366 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE); 367 VideoPauseController.getInstance().tearDown(); 368 InCallVideoCallCallbackNotifier.getInstance().removeSessionModificationListener(this); 369 } 370 attemptFinishActivity()371 private void attemptFinishActivity() { 372 final boolean doFinish = (mInCallActivity != null && isActivityStarted()); 373 Log.i(this, "Hide in call UI: " + doFinish); 374 if (doFinish) { 375 mInCallActivity.setExcludeFromRecents(true); 376 mInCallActivity.finish(); 377 378 if (mAccountSelectionCancelled) { 379 // This finish is a result of account selection cancellation 380 // do not include activity ending transition 381 mInCallActivity.overridePendingTransition(0, 0); 382 } 383 } 384 } 385 386 /** 387 * Called when the UI begins, and starts the callstate callbacks if necessary. 388 */ setActivity(InCallActivity inCallActivity)389 public void setActivity(InCallActivity inCallActivity) { 390 if (inCallActivity == null) { 391 throw new IllegalArgumentException("registerActivity cannot be called with null"); 392 } 393 if (mInCallActivity != null && mInCallActivity != inCallActivity) { 394 Log.w(this, "Setting a second activity before destroying the first."); 395 } 396 updateActivity(inCallActivity); 397 } 398 399 /** 400 * Called when the UI ends. Attempts to tear down everything if necessary. See 401 * {@link #tearDown()} for more insight on the tear-down process. 402 */ unsetActivity(InCallActivity inCallActivity)403 public void unsetActivity(InCallActivity inCallActivity) { 404 if (inCallActivity == null) { 405 throw new IllegalArgumentException("unregisterActivity cannot be called with null"); 406 } 407 if (mInCallActivity == null) { 408 Log.i(this, "No InCallActivity currently set, no need to unset."); 409 return; 410 } 411 if (mInCallActivity != inCallActivity) { 412 Log.w(this, "Second instance of InCallActivity is trying to unregister when another" 413 + " instance is active. Ignoring."); 414 return; 415 } 416 updateActivity(null); 417 } 418 419 /** 420 * Updates the current instance of {@link InCallActivity} with the provided one. If a 421 * {@code null} activity is provided, it means that the activity was finished and we should 422 * attempt to cleanup. 423 */ updateActivity(InCallActivity inCallActivity)424 private void updateActivity(InCallActivity inCallActivity) { 425 boolean updateListeners = false; 426 boolean doAttemptCleanup = false; 427 428 if (inCallActivity != null) { 429 if (mInCallActivity == null) { 430 updateListeners = true; 431 Log.i(this, "UI Initialized"); 432 } else { 433 // since setActivity is called onStart(), it can be called multiple times. 434 // This is fine and ignorable, but we do not want to update the world every time 435 // this happens (like going to/from background) so we do not set updateListeners. 436 } 437 438 mInCallActivity = inCallActivity; 439 mInCallActivity.setExcludeFromRecents(false); 440 441 // By the time the UI finally comes up, the call may already be disconnected. 442 // If that's the case, we may need to show an error dialog. 443 if (mCallList != null && mCallList.getDisconnectedCall() != null) { 444 maybeShowErrorDialogOnDisconnect(mCallList.getDisconnectedCall()); 445 } 446 447 // When the UI comes up, we need to first check the in-call state. 448 // If we are showing NO_CALLS, that means that a call probably connected and 449 // then immediately disconnected before the UI was able to come up. 450 // If we dont have any calls, start tearing down the UI instead. 451 // NOTE: This code relies on {@link #mInCallActivity} being set so we run it after 452 // it has been set. 453 if (mInCallState == InCallState.NO_CALLS) { 454 Log.i(this, "UI Initialized, but no calls left. shut down."); 455 attemptFinishActivity(); 456 return; 457 } 458 } else { 459 Log.i(this, "UI Destroyed"); 460 updateListeners = true; 461 mInCallActivity = null; 462 463 // We attempt cleanup for the destroy case but only after we recalculate the state 464 // to see if we need to come back up or stay shut down. This is why we do the 465 // cleanup after the call to onCallListChange() instead of directly here. 466 doAttemptCleanup = true; 467 } 468 469 // Messages can come from the telephony layer while the activity is coming up 470 // and while the activity is going down. So in both cases we need to recalculate what 471 // state we should be in after they complete. 472 // Examples: (1) A new incoming call could come in and then get disconnected before 473 // the activity is created. 474 // (2) All calls could disconnect and then get a new incoming call before the 475 // activity is destroyed. 476 // 477 // b/1122139 - We previously had a check for mServiceConnected here as well, but there are 478 // cases where we need to recalculate the current state even if the service in not 479 // connected. In particular the case where startOrFinish() is called while the app is 480 // already finish()ing. In that case, we skip updating the state with the knowledge that 481 // we will check again once the activity has finished. That means we have to recalculate the 482 // state here even if the service is disconnected since we may not have finished a state 483 // transition while finish()ing. 484 if (updateListeners) { 485 onCallListChange(mCallList); 486 } 487 488 if (doAttemptCleanup) { 489 attemptCleanup(); 490 } 491 } 492 493 private boolean mAwaitingCallListUpdate = false; 494 onBringToForeground(boolean showDialpad)495 public void onBringToForeground(boolean showDialpad) { 496 Log.i(this, "Bringing UI to foreground."); 497 bringToForeground(showDialpad); 498 } 499 onCallAdded(final android.telecom.Call call)500 public void onCallAdded(final android.telecom.Call call) { 501 if (shouldAttemptBlocking(call)) { 502 maybeBlockCall(call); 503 } else { 504 mCallList.onCallAdded(call); 505 } 506 507 // Since a call has been added we are no longer waiting for Telecom to send us a call. 508 setBoundAndWaitingForOutgoingCall(false, null); 509 call.registerCallback(mCallCallback); 510 } 511 shouldAttemptBlocking(android.telecom.Call call)512 private boolean shouldAttemptBlocking(android.telecom.Call call) { 513 if (call.getState() != android.telecom.Call.STATE_RINGING) { 514 return false; 515 } 516 if (TelecomCallUtil.isEmergencyCall(call)) { 517 Log.i(this, "Not attempting to block incoming emergency call"); 518 return false; 519 } 520 if (FilteredNumbersUtil.hasRecentEmergencyCall(mContext)) { 521 Log.i(this, "Not attempting to block incoming call due to recent emergency call"); 522 return false; 523 } 524 525 return true; 526 } 527 528 /** 529 * Checks whether a call should be blocked, and blocks it if so. Otherwise, it adds the call 530 * to the CallList so it can proceed as normal. There is a timeout, so if the function for 531 * checking whether a function is blocked does not return in a reasonable time, we proceed 532 * with adding the call anyways. 533 */ maybeBlockCall(final android.telecom.Call call)534 private void maybeBlockCall(final android.telecom.Call call) { 535 final String countryIso = GeoUtil.getCurrentCountryIso(mContext); 536 final String number = TelecomCallUtil.getNumber(call); 537 final long timeAdded = System.currentTimeMillis(); 538 539 // Though AtomicBoolean's can be scary, don't fear, as in this case it is only used on the 540 // main UI thread. It is needed so we can change its value within different scopes, since 541 // that cannot be done with a final boolean. 542 final AtomicBoolean hasTimedOut = new AtomicBoolean(false); 543 544 final Handler handler = new Handler(); 545 546 // Proceed if the query is slow; the call may still be blocked after the query returns. 547 final Runnable runnable = new Runnable() { 548 public void run() { 549 hasTimedOut.set(true); 550 mCallList.onCallAdded(call); 551 } 552 }; 553 handler.postDelayed(runnable, BLOCK_QUERY_TIMEOUT_MS); 554 555 OnCheckBlockedListener onCheckBlockedListener = new OnCheckBlockedListener() { 556 @Override 557 public void onCheckComplete(final Integer id) { 558 if (!hasTimedOut.get()) { 559 handler.removeCallbacks(runnable); 560 } 561 if (id == null) { 562 if (!hasTimedOut.get()) { 563 mCallList.onCallAdded(call); 564 } 565 } else { 566 Log.i(this, "Rejecting incoming call from blocked number"); 567 call.reject(false, null); 568 Logger.logInteraction(InteractionEvent.CALL_BLOCKED); 569 570 mFilteredQueryHandler.incrementFilteredCount(id); 571 572 // Register observer to update the call log. 573 // BlockedNumberContentObserver will unregister after successful log or timeout. 574 BlockedNumberContentObserver contentObserver = 575 new BlockedNumberContentObserver(new Handler(), number, timeAdded); 576 contentObserver.register(); 577 } 578 } 579 }; 580 581 final boolean success = mFilteredQueryHandler.isBlockedNumber( 582 onCheckBlockedListener, number, countryIso); 583 if (!success) { 584 Log.d(this, "checkForBlockedCall: invalid number, skipping block checking"); 585 if (!hasTimedOut.get()) { 586 handler.removeCallbacks(runnable); 587 mCallList.onCallAdded(call); 588 } 589 } 590 } 591 onCallRemoved(android.telecom.Call call)592 public void onCallRemoved(android.telecom.Call call) { 593 mCallList.onCallRemoved(call); 594 call.unregisterCallback(mCallCallback); 595 } 596 onCanAddCallChanged(boolean canAddCall)597 public void onCanAddCallChanged(boolean canAddCall) { 598 for (CanAddCallListener listener : mCanAddCallListeners) { 599 listener.onCanAddCallChanged(canAddCall); 600 } 601 } 602 603 /** 604 * Called when there is a change to the call list. 605 * Sets the In-Call state for the entire in-call app based on the information it gets from 606 * CallList. Dispatches the in-call state to all listeners. Can trigger the creation or 607 * destruction of the UI based on the states that is calculates. 608 */ 609 @Override onCallListChange(CallList callList)610 public void onCallListChange(CallList callList) { 611 if (mInCallActivity != null && mInCallActivity.getCallCardFragment() != null && 612 mInCallActivity.getCallCardFragment().isAnimating()) { 613 mAwaitingCallListUpdate = true; 614 return; 615 } 616 if (callList == null) { 617 return; 618 } 619 620 mAwaitingCallListUpdate = false; 621 622 InCallState newState = getPotentialStateFromCallList(callList); 623 InCallState oldState = mInCallState; 624 Log.d(this, "onCallListChange oldState= " + oldState + " newState=" + newState); 625 newState = startOrFinishUi(newState); 626 Log.d(this, "onCallListChange newState changed to " + newState); 627 628 // Set the new state before announcing it to the world 629 Log.i(this, "Phone switching state: " + oldState + " -> " + newState); 630 mInCallState = newState; 631 632 // notify listeners of new state 633 for (InCallStateListener listener : mListeners) { 634 Log.d(this, "Notify " + listener + " of state " + mInCallState.toString()); 635 listener.onStateChange(oldState, mInCallState, callList); 636 } 637 638 if (isActivityStarted()) { 639 final boolean hasCall = callList.getActiveOrBackgroundCall() != null || 640 callList.getOutgoingCall() != null; 641 mInCallActivity.dismissKeyguard(hasCall); 642 } 643 } 644 645 /** 646 * Called when there is a new incoming call. 647 * 648 * @param call 649 */ 650 @Override onIncomingCall(Call call)651 public void onIncomingCall(Call call) { 652 InCallState newState = startOrFinishUi(InCallState.INCOMING); 653 InCallState oldState = mInCallState; 654 655 Log.i(this, "Phone switching state: " + oldState + " -> " + newState); 656 mInCallState = newState; 657 658 for (IncomingCallListener listener : mIncomingCallListeners) { 659 listener.onIncomingCall(oldState, mInCallState, call); 660 } 661 } 662 663 @Override onUpgradeToVideo(Call call)664 public void onUpgradeToVideo(Call call) { 665 //NO-OP 666 } 667 /** 668 * Called when a call becomes disconnected. Called everytime an existing call 669 * changes from being connected (incoming/outgoing/active) to disconnected. 670 */ 671 @Override onDisconnect(Call call)672 public void onDisconnect(Call call) { 673 maybeShowErrorDialogOnDisconnect(call); 674 675 // We need to do the run the same code as onCallListChange. 676 onCallListChange(mCallList); 677 678 if (isActivityStarted()) { 679 mInCallActivity.dismissKeyguard(false); 680 } 681 682 if (call.isEmergencyCall()) { 683 FilteredNumbersUtil.recordLastEmergencyCallTime(mContext); 684 } 685 } 686 687 @Override onUpgradeToVideoRequest(Call call, int videoState)688 public void onUpgradeToVideoRequest(Call call, int videoState) { 689 Log.d(this, "onUpgradeToVideoRequest call = " + call + " video state = " + videoState); 690 691 if (call == null) { 692 return; 693 } 694 695 call.setRequestedVideoState(videoState); 696 } 697 698 /** 699 * Given the call list, return the state in which the in-call screen should be. 700 */ getPotentialStateFromCallList(CallList callList)701 public InCallState getPotentialStateFromCallList(CallList callList) { 702 703 InCallState newState = InCallState.NO_CALLS; 704 705 if (callList == null) { 706 return newState; 707 } 708 if (callList.getIncomingCall() != null) { 709 newState = InCallState.INCOMING; 710 } else if (callList.getWaitingForAccountCall() != null) { 711 newState = InCallState.WAITING_FOR_ACCOUNT; 712 } else if (callList.getPendingOutgoingCall() != null) { 713 newState = InCallState.PENDING_OUTGOING; 714 } else if (callList.getOutgoingCall() != null) { 715 newState = InCallState.OUTGOING; 716 } else if (callList.getActiveCall() != null || 717 callList.getBackgroundCall() != null || 718 callList.getDisconnectedCall() != null || 719 callList.getDisconnectingCall() != null) { 720 newState = InCallState.INCALL; 721 } 722 723 if (newState == InCallState.NO_CALLS) { 724 if (mBoundAndWaitingForOutgoingCall) { 725 return InCallState.OUTGOING; 726 } 727 } 728 729 return newState; 730 } 731 isBoundAndWaitingForOutgoingCall()732 public boolean isBoundAndWaitingForOutgoingCall() { 733 return mBoundAndWaitingForOutgoingCall; 734 } 735 setBoundAndWaitingForOutgoingCall(boolean isBound, PhoneAccountHandle handle)736 public void setBoundAndWaitingForOutgoingCall(boolean isBound, PhoneAccountHandle handle) { 737 // NOTE: It is possible for there to be a race and have handle become null before 738 // the circular reveal starts. This should not cause any problems because CallCardFragment 739 // should fallback to the actual call in the CallList at that point in time to determine 740 // the theme color. 741 Log.i(this, "setBoundAndWaitingForOutgoingCall: " + isBound); 742 mBoundAndWaitingForOutgoingCall = isBound; 743 mPendingPhoneAccountHandle = handle; 744 if (isBound && mInCallState == InCallState.NO_CALLS) { 745 mInCallState = InCallState.OUTGOING; 746 } 747 } 748 749 @Override onCircularRevealComplete(FragmentManager fm)750 public void onCircularRevealComplete(FragmentManager fm) { 751 if (mInCallActivity != null) { 752 mInCallActivity.showCallCardFragment(true); 753 mInCallActivity.getCallCardFragment().animateForNewOutgoingCall(); 754 CircularRevealFragment.endCircularReveal(mInCallActivity.getFragmentManager()); 755 } 756 } 757 onShrinkAnimationComplete()758 public void onShrinkAnimationComplete() { 759 if (mAwaitingCallListUpdate) { 760 onCallListChange(mCallList); 761 } 762 } 763 addIncomingCallListener(IncomingCallListener listener)764 public void addIncomingCallListener(IncomingCallListener listener) { 765 Preconditions.checkNotNull(listener); 766 mIncomingCallListeners.add(listener); 767 } 768 removeIncomingCallListener(IncomingCallListener listener)769 public void removeIncomingCallListener(IncomingCallListener listener) { 770 if (listener != null) { 771 mIncomingCallListeners.remove(listener); 772 } 773 } 774 addListener(InCallStateListener listener)775 public void addListener(InCallStateListener listener) { 776 Preconditions.checkNotNull(listener); 777 mListeners.add(listener); 778 } 779 removeListener(InCallStateListener listener)780 public void removeListener(InCallStateListener listener) { 781 if (listener != null) { 782 mListeners.remove(listener); 783 } 784 } 785 addDetailsListener(InCallDetailsListener listener)786 public void addDetailsListener(InCallDetailsListener listener) { 787 Preconditions.checkNotNull(listener); 788 mDetailsListeners.add(listener); 789 } 790 removeDetailsListener(InCallDetailsListener listener)791 public void removeDetailsListener(InCallDetailsListener listener) { 792 if (listener != null) { 793 mDetailsListeners.remove(listener); 794 } 795 } 796 addCanAddCallListener(CanAddCallListener listener)797 public void addCanAddCallListener(CanAddCallListener listener) { 798 Preconditions.checkNotNull(listener); 799 mCanAddCallListeners.add(listener); 800 } 801 removeCanAddCallListener(CanAddCallListener listener)802 public void removeCanAddCallListener(CanAddCallListener listener) { 803 if (listener != null) { 804 mCanAddCallListeners.remove(listener); 805 } 806 } 807 addOrientationListener(InCallOrientationListener listener)808 public void addOrientationListener(InCallOrientationListener listener) { 809 Preconditions.checkNotNull(listener); 810 mOrientationListeners.add(listener); 811 } 812 removeOrientationListener(InCallOrientationListener listener)813 public void removeOrientationListener(InCallOrientationListener listener) { 814 if (listener != null) { 815 mOrientationListeners.remove(listener); 816 } 817 } 818 addInCallEventListener(InCallEventListener listener)819 public void addInCallEventListener(InCallEventListener listener) { 820 Preconditions.checkNotNull(listener); 821 mInCallEventListeners.add(listener); 822 } 823 removeInCallEventListener(InCallEventListener listener)824 public void removeInCallEventListener(InCallEventListener listener) { 825 if (listener != null) { 826 mInCallEventListeners.remove(listener); 827 } 828 } 829 getProximitySensor()830 public ProximitySensor getProximitySensor() { 831 return mProximitySensor; 832 } 833 handleAccountSelection(PhoneAccountHandle accountHandle, boolean setDefault)834 public void handleAccountSelection(PhoneAccountHandle accountHandle, boolean setDefault) { 835 if (mCallList != null) { 836 Call call = mCallList.getWaitingForAccountCall(); 837 if (call != null) { 838 String callId = call.getId(); 839 TelecomAdapter.getInstance().phoneAccountSelected(callId, accountHandle, setDefault); 840 } 841 } 842 } 843 cancelAccountSelection()844 public void cancelAccountSelection() { 845 mAccountSelectionCancelled = true; 846 if (mCallList != null) { 847 Call call = mCallList.getWaitingForAccountCall(); 848 if (call != null) { 849 String callId = call.getId(); 850 TelecomAdapter.getInstance().disconnectCall(callId); 851 } 852 } 853 } 854 855 /** 856 * Hangs up any active or outgoing calls. 857 */ hangUpOngoingCall(Context context)858 public void hangUpOngoingCall(Context context) { 859 // By the time we receive this intent, we could be shut down and call list 860 // could be null. Bail in those cases. 861 if (mCallList == null) { 862 if (mStatusBarNotifier == null) { 863 // The In Call UI has crashed but the notification still stayed up. We should not 864 // come to this stage. 865 StatusBarNotifier.clearAllCallNotifications(context); 866 } 867 return; 868 } 869 870 Call call = mCallList.getOutgoingCall(); 871 if (call == null) { 872 call = mCallList.getActiveOrBackgroundCall(); 873 } 874 875 if (call != null) { 876 TelecomAdapter.getInstance().disconnectCall(call.getId()); 877 call.setState(Call.State.DISCONNECTING); 878 mCallList.onUpdate(call); 879 } 880 } 881 882 /** 883 * Answers any incoming call. 884 */ answerIncomingCall(Context context, int videoState)885 public void answerIncomingCall(Context context, int videoState) { 886 // By the time we receive this intent, we could be shut down and call list 887 // could be null. Bail in those cases. 888 if (mCallList == null) { 889 StatusBarNotifier.clearAllCallNotifications(context); 890 return; 891 } 892 893 Call call = mCallList.getIncomingCall(); 894 if (call != null) { 895 TelecomAdapter.getInstance().answerCall(call.getId(), videoState); 896 showInCall(false, false/* newOutgoingCall */); 897 } 898 } 899 900 /** 901 * Declines any incoming call. 902 */ declineIncomingCall(Context context)903 public void declineIncomingCall(Context context) { 904 // By the time we receive this intent, we could be shut down and call list 905 // could be null. Bail in those cases. 906 if (mCallList == null) { 907 StatusBarNotifier.clearAllCallNotifications(context); 908 return; 909 } 910 911 Call call = mCallList.getIncomingCall(); 912 if (call != null) { 913 TelecomAdapter.getInstance().rejectCall(call.getId(), false, null); 914 } 915 } 916 acceptUpgradeRequest(int videoState, Context context)917 public void acceptUpgradeRequest(int videoState, Context context) { 918 Log.d(this, " acceptUpgradeRequest videoState " + videoState); 919 // Bail if we have been shut down and the call list is null. 920 if (mCallList == null) { 921 StatusBarNotifier.clearAllCallNotifications(context); 922 Log.e(this, " acceptUpgradeRequest mCallList is empty so returning"); 923 return; 924 } 925 926 Call call = mCallList.getVideoUpgradeRequestCall(); 927 if (call != null) { 928 VideoProfile videoProfile = new VideoProfile(videoState); 929 call.getVideoCall().sendSessionModifyResponse(videoProfile); 930 call.setSessionModificationState(Call.SessionModificationState.NO_REQUEST); 931 } 932 } 933 declineUpgradeRequest(Context context)934 public void declineUpgradeRequest(Context context) { 935 Log.d(this, " declineUpgradeRequest"); 936 // Bail if we have been shut down and the call list is null. 937 if (mCallList == null) { 938 StatusBarNotifier.clearAllCallNotifications(context); 939 Log.e(this, " declineUpgradeRequest mCallList is empty so returning"); 940 return; 941 } 942 943 Call call = mCallList.getVideoUpgradeRequestCall(); 944 if (call != null) { 945 VideoProfile videoProfile = 946 new VideoProfile(call.getVideoState()); 947 call.getVideoCall().sendSessionModifyResponse(videoProfile); 948 call.setSessionModificationState(Call.SessionModificationState.NO_REQUEST); 949 } 950 } 951 952 /*package*/ declineUpgradeRequest()953 void declineUpgradeRequest() { 954 // Pass mContext if InCallActivity is destroyed. 955 // Ex: When user pressed back key while in active call and 956 // then modify request is received followed by MT call. 957 declineUpgradeRequest(mInCallActivity != null ? mInCallActivity : mContext); 958 } 959 960 /** 961 * Returns true if the incall app is the foreground application. 962 */ isShowingInCallUi()963 public boolean isShowingInCallUi() { 964 return (isActivityStarted() && mInCallActivity.isVisible()); 965 } 966 967 /** 968 * Returns true if the activity has been created and is running. 969 * Returns true as long as activity is not destroyed or finishing. This ensures that we return 970 * true even if the activity is paused (not in foreground). 971 */ isActivityStarted()972 public boolean isActivityStarted() { 973 return (mInCallActivity != null && 974 !mInCallActivity.isDestroyed() && 975 !mInCallActivity.isFinishing()); 976 } 977 isActivityPreviouslyStarted()978 public boolean isActivityPreviouslyStarted() { 979 return mIsActivityPreviouslyStarted; 980 } 981 982 /** 983 * Determines if the In-Call app is currently changing configuration. 984 * 985 * @return {@code true} if the In-Call app is changing configuration. 986 */ isChangingConfigurations()987 public boolean isChangingConfigurations() { 988 return mIsChangingConfigurations; 989 } 990 991 /** 992 * Tracks whether the In-Call app is currently in the process of changing configuration (i.e. 993 * screen orientation). 994 */ 995 /*package*/ updateIsChangingConfigurations()996 void updateIsChangingConfigurations() { 997 mIsChangingConfigurations = false; 998 if (mInCallActivity != null) { 999 mIsChangingConfigurations = mInCallActivity.isChangingConfigurations(); 1000 } 1001 Log.v(this, "updateIsChangingConfigurations = " + mIsChangingConfigurations); 1002 } 1003 1004 1005 /** 1006 * Called when the activity goes in/out of the foreground. 1007 */ onUiShowing(boolean showing)1008 public void onUiShowing(boolean showing) { 1009 // We need to update the notification bar when we leave the UI because that 1010 // could trigger it to show again. 1011 if (mStatusBarNotifier != null) { 1012 mStatusBarNotifier.updateNotification(mInCallState, mCallList); 1013 } 1014 1015 if (mProximitySensor != null) { 1016 mProximitySensor.onInCallShowing(showing); 1017 } 1018 1019 Intent broadcastIntent = ObjectFactory.getUiReadyBroadcastIntent(mContext); 1020 if (broadcastIntent != null) { 1021 broadcastIntent.putExtra(EXTRA_FIRST_TIME_SHOWN, !mIsActivityPreviouslyStarted); 1022 1023 if (showing) { 1024 Log.d(this, "Sending sticky broadcast: ", broadcastIntent); 1025 mContext.sendStickyBroadcast(broadcastIntent); 1026 } else { 1027 Log.d(this, "Removing sticky broadcast: ", broadcastIntent); 1028 mContext.removeStickyBroadcast(broadcastIntent); 1029 } 1030 } 1031 1032 if (showing) { 1033 mIsActivityPreviouslyStarted = true; 1034 } else { 1035 updateIsChangingConfigurations(); 1036 } 1037 1038 for (InCallUiListener listener : mInCallUiListeners) { 1039 listener.onUiShowing(showing); 1040 } 1041 } 1042 addInCallUiListener(InCallUiListener listener)1043 public void addInCallUiListener(InCallUiListener listener) { 1044 mInCallUiListeners.add(listener); 1045 } 1046 removeInCallUiListener(InCallUiListener listener)1047 public boolean removeInCallUiListener(InCallUiListener listener) { 1048 return mInCallUiListeners.remove(listener); 1049 } 1050 1051 /*package*/ onActivityStarted()1052 void onActivityStarted() { 1053 Log.d(this, "onActivityStarted"); 1054 notifyVideoPauseController(true); 1055 } 1056 1057 /*package*/ onActivityStopped()1058 void onActivityStopped() { 1059 Log.d(this, "onActivityStopped"); 1060 notifyVideoPauseController(false); 1061 } 1062 notifyVideoPauseController(boolean showing)1063 private void notifyVideoPauseController(boolean showing) { 1064 Log.d(this, "notifyVideoPauseController: mIsChangingConfigurations=" + 1065 mIsChangingConfigurations); 1066 if (!mIsChangingConfigurations) { 1067 VideoPauseController.getInstance().onUiShowing(showing); 1068 } 1069 } 1070 1071 /** 1072 * Brings the app into the foreground if possible. 1073 */ bringToForeground(boolean showDialpad)1074 public void bringToForeground(boolean showDialpad) { 1075 // Before we bring the incall UI to the foreground, we check to see if: 1076 // 1. It is not currently in the foreground 1077 // 2. We are in a state where we want to show the incall ui (i.e. there are calls to 1078 // be displayed) 1079 // If the activity hadn't actually been started previously, yet there are still calls 1080 // present (e.g. a call was accepted by a bluetooth or wired headset), we want to 1081 // bring it up the UI regardless. 1082 if (!isShowingInCallUi() && mInCallState != InCallState.NO_CALLS) { 1083 showInCall(showDialpad, false /* newOutgoingCall */); 1084 } 1085 } 1086 onPostDialCharWait(String callId, String chars)1087 public void onPostDialCharWait(String callId, String chars) { 1088 if (isActivityStarted()) { 1089 mInCallActivity.showPostCharWaitDialog(callId, chars); 1090 } 1091 } 1092 1093 /** 1094 * Handles the green CALL key while in-call. 1095 * @return true if we consumed the event. 1096 */ handleCallKey()1097 public boolean handleCallKey() { 1098 Log.v(this, "handleCallKey"); 1099 1100 // The green CALL button means either "Answer", "Unhold", or 1101 // "Swap calls", or can be a no-op, depending on the current state 1102 // of the Phone. 1103 1104 /** 1105 * INCOMING CALL 1106 */ 1107 final CallList calls = mCallList; 1108 final Call incomingCall = calls.getIncomingCall(); 1109 Log.v(this, "incomingCall: " + incomingCall); 1110 1111 // (1) Attempt to answer a call 1112 if (incomingCall != null) { 1113 TelecomAdapter.getInstance().answerCall( 1114 incomingCall.getId(), VideoProfile.STATE_AUDIO_ONLY); 1115 return true; 1116 } 1117 1118 /** 1119 * STATE_ACTIVE CALL 1120 */ 1121 final Call activeCall = calls.getActiveCall(); 1122 if (activeCall != null) { 1123 // TODO: This logic is repeated from CallButtonPresenter.java. We should 1124 // consolidate this logic. 1125 final boolean canMerge = activeCall.can( 1126 android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE); 1127 final boolean canSwap = activeCall.can( 1128 android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE); 1129 1130 Log.v(this, "activeCall: " + activeCall + ", canMerge: " + canMerge + 1131 ", canSwap: " + canSwap); 1132 1133 // (2) Attempt actions on conference calls 1134 if (canMerge) { 1135 TelecomAdapter.getInstance().merge(activeCall.getId()); 1136 return true; 1137 } else if (canSwap) { 1138 TelecomAdapter.getInstance().swap(activeCall.getId()); 1139 return true; 1140 } 1141 } 1142 1143 /** 1144 * BACKGROUND CALL 1145 */ 1146 final Call heldCall = calls.getBackgroundCall(); 1147 if (heldCall != null) { 1148 // We have a hold call so presumeable it will always support HOLD...but 1149 // there is no harm in double checking. 1150 final boolean canHold = heldCall.can(android.telecom.Call.Details.CAPABILITY_HOLD); 1151 1152 Log.v(this, "heldCall: " + heldCall + ", canHold: " + canHold); 1153 1154 // (4) unhold call 1155 if (heldCall.getState() == Call.State.ONHOLD && canHold) { 1156 TelecomAdapter.getInstance().unholdCall(heldCall.getId()); 1157 return true; 1158 } 1159 } 1160 1161 // Always consume hard keys 1162 return true; 1163 } 1164 1165 /** 1166 * A dialog could have prevented in-call screen from being previously finished. 1167 * This function checks to see if there should be any UI left and if not attempts 1168 * to tear down the UI. 1169 */ onDismissDialog()1170 public void onDismissDialog() { 1171 Log.i(this, "Dialog dismissed"); 1172 if (mInCallState == InCallState.NO_CALLS) { 1173 attemptFinishActivity(); 1174 attemptCleanup(); 1175 } 1176 } 1177 1178 /** 1179 * Toggles whether the application is in fullscreen mode or not. 1180 * 1181 * @return {@code true} if in-call is now in fullscreen mode. 1182 */ toggleFullscreenMode()1183 public boolean toggleFullscreenMode() { 1184 boolean isFullScreen = !mIsFullScreen; 1185 Log.v(this, "toggleFullscreenMode = " + isFullScreen); 1186 setFullScreen(isFullScreen); 1187 return mIsFullScreen; 1188 } 1189 1190 /** 1191 * Clears the previous fullscreen state. 1192 */ clearFullscreen()1193 public void clearFullscreen() { 1194 mIsFullScreen = false; 1195 } 1196 1197 /** 1198 * Changes the fullscreen mode of the in-call UI. 1199 * 1200 * @param isFullScreen {@code true} if in-call should be in fullscreen mode, {@code false} 1201 * otherwise. 1202 */ setFullScreen(boolean isFullScreen)1203 public void setFullScreen(boolean isFullScreen) { 1204 setFullScreen(isFullScreen, false /* force */); 1205 } 1206 1207 /** 1208 * Changes the fullscreen mode of the in-call UI. 1209 * 1210 * @param isFullScreen {@code true} if in-call should be in fullscreen mode, {@code false} 1211 * otherwise. 1212 * @param force {@code true} if fullscreen mode should be set regardless of its current state. 1213 */ setFullScreen(boolean isFullScreen, boolean force)1214 public void setFullScreen(boolean isFullScreen, boolean force) { 1215 Log.v(this, "setFullScreen = " + isFullScreen); 1216 1217 // As a safeguard, ensure we cannot enter fullscreen if the dialpad is shown. 1218 if (isDialpadVisible()) { 1219 isFullScreen = false; 1220 Log.v(this, "setFullScreen overridden as dialpad is shown = " + isFullScreen); 1221 } 1222 1223 if (mIsFullScreen == isFullScreen && !force) { 1224 Log.v(this, "setFullScreen ignored as already in that state."); 1225 return; 1226 } 1227 mIsFullScreen = isFullScreen; 1228 notifyFullscreenModeChange(mIsFullScreen); 1229 } 1230 1231 /** 1232 * @return {@code true} if the in-call ui is currently in fullscreen mode, {@code false} 1233 * otherwise. 1234 */ isFullscreen()1235 public boolean isFullscreen() { 1236 return mIsFullScreen; 1237 } 1238 1239 1240 /** 1241 * Called by the {@link VideoCallPresenter} to inform of a change in full screen video status. 1242 * 1243 * @param isFullscreenMode {@code True} if entering full screen mode. 1244 */ notifyFullscreenModeChange(boolean isFullscreenMode)1245 public void notifyFullscreenModeChange(boolean isFullscreenMode) { 1246 for (InCallEventListener listener : mInCallEventListeners) { 1247 listener.onFullscreenModeChanged(isFullscreenMode); 1248 } 1249 } 1250 1251 /** 1252 * Called by the {@link CallCardPresenter} to inform of a change in visibility of the secondary 1253 * caller info bar. 1254 * 1255 * @param isVisible {@code true} if the secondary caller info is visible, {@code false} 1256 * otherwise. 1257 * @param height the height of the secondary caller info bar. 1258 */ notifySecondaryCallerInfoVisibilityChanged(boolean isVisible, int height)1259 public void notifySecondaryCallerInfoVisibilityChanged(boolean isVisible, int height) { 1260 for (InCallEventListener listener : mInCallEventListeners) { 1261 listener.onSecondaryCallerInfoVisibilityChanged(isVisible, height); 1262 } 1263 } 1264 1265 1266 /** 1267 * For some disconnected causes, we show a dialog. This calls into the activity to show 1268 * the dialog if appropriate for the call. 1269 */ maybeShowErrorDialogOnDisconnect(Call call)1270 private void maybeShowErrorDialogOnDisconnect(Call call) { 1271 // For newly disconnected calls, we may want to show a dialog on specific error conditions 1272 if (isActivityStarted() && call.getState() == Call.State.DISCONNECTED) { 1273 if (call.getAccountHandle() == null && !call.isConferenceCall()) { 1274 setDisconnectCauseForMissingAccounts(call); 1275 } 1276 mInCallActivity.maybeShowErrorDialogOnDisconnect(call.getDisconnectCause()); 1277 } 1278 } 1279 1280 /** 1281 * When the state of in-call changes, this is the first method to get called. It determines if 1282 * the UI needs to be started or finished depending on the new state and does it. 1283 */ startOrFinishUi(InCallState newState)1284 private InCallState startOrFinishUi(InCallState newState) { 1285 Log.d(this, "startOrFinishUi: " + mInCallState + " -> " + newState); 1286 1287 // TODO: Consider a proper state machine implementation 1288 1289 // If the state isn't changing we have already done any starting/stopping of activities in 1290 // a previous pass...so lets cut out early 1291 if (newState == mInCallState) { 1292 return newState; 1293 } 1294 1295 // A new Incoming call means that the user needs to be notified of the the call (since 1296 // it wasn't them who initiated it). We do this through full screen notifications and 1297 // happens indirectly through {@link StatusBarNotifier}. 1298 // 1299 // The process for incoming calls is as follows: 1300 // 1301 // 1) CallList - Announces existence of new INCOMING call 1302 // 2) InCallPresenter - Gets announcement and calculates that the new InCallState 1303 // - should be set to INCOMING. 1304 // 3) InCallPresenter - This method is called to see if we need to start or finish 1305 // the app given the new state. 1306 // 4) StatusBarNotifier - Listens to InCallState changes. InCallPresenter calls 1307 // StatusBarNotifier explicitly to issue a FullScreen Notification 1308 // that will either start the InCallActivity or show the user a 1309 // top-level notification dialog if the user is in an immersive app. 1310 // That notification can also start the InCallActivity. 1311 // 5) InCallActivity - Main activity starts up and at the end of its onCreate will 1312 // call InCallPresenter::setActivity() to let the presenter 1313 // know that start-up is complete. 1314 // 1315 // [ AND NOW YOU'RE IN THE CALL. voila! ] 1316 // 1317 // Our app is started using a fullScreen notification. We need to do this whenever 1318 // we get an incoming call. Depending on the current context of the device, either a 1319 // incoming call HUN or the actual InCallActivity will be shown. 1320 final boolean startIncomingCallSequence = (InCallState.INCOMING == newState); 1321 1322 // A dialog to show on top of the InCallUI to select a PhoneAccount 1323 final boolean showAccountPicker = (InCallState.WAITING_FOR_ACCOUNT == newState); 1324 1325 // A new outgoing call indicates that the user just now dialed a number and when that 1326 // happens we need to display the screen immediately or show an account picker dialog if 1327 // no default is set. However, if the main InCallUI is already visible, we do not want to 1328 // re-initiate the start-up animation, so we do not need to do anything here. 1329 // 1330 // It is also possible to go into an intermediate state where the call has been initiated 1331 // but Telecom has not yet returned with the details of the call (handle, gateway, etc.). 1332 // This pending outgoing state can also launch the call screen. 1333 // 1334 // This is different from the incoming call sequence because we do not need to shock the 1335 // user with a top-level notification. Just show the call UI normally. 1336 final boolean mainUiNotVisible = !isShowingInCallUi() || !getCallCardFragmentVisible(); 1337 boolean showCallUi = InCallState.OUTGOING == newState && mainUiNotVisible; 1338 1339 // Direct transition from PENDING_OUTGOING -> INCALL means that there was an error in the 1340 // outgoing call process, so the UI should be brought up to show an error dialog. 1341 showCallUi |= (InCallState.PENDING_OUTGOING == mInCallState 1342 && InCallState.INCALL == newState && !isShowingInCallUi()); 1343 1344 // Another exception - InCallActivity is in charge of disconnecting a call with no 1345 // valid accounts set. Bring the UI up if this is true for the current pending outgoing 1346 // call so that: 1347 // 1) The call can be disconnected correctly 1348 // 2) The UI comes up and correctly displays the error dialog. 1349 // TODO: Remove these special case conditions by making InCallPresenter a true state 1350 // machine. Telecom should also be the component responsible for disconnecting a call 1351 // with no valid accounts. 1352 showCallUi |= InCallState.PENDING_OUTGOING == newState && mainUiNotVisible 1353 && isCallWithNoValidAccounts(mCallList.getPendingOutgoingCall()); 1354 1355 // The only time that we have an instance of mInCallActivity and it isn't started is 1356 // when it is being destroyed. In that case, lets avoid bringing up another instance of 1357 // the activity. When it is finally destroyed, we double check if we should bring it back 1358 // up so we aren't going to lose anything by avoiding a second startup here. 1359 boolean activityIsFinishing = mInCallActivity != null && !isActivityStarted(); 1360 if (activityIsFinishing) { 1361 Log.i(this, "Undo the state change: " + newState + " -> " + mInCallState); 1362 return mInCallState; 1363 } 1364 1365 if (showCallUi || showAccountPicker) { 1366 Log.i(this, "Start in call UI"); 1367 showInCall(false /* showDialpad */, !showAccountPicker /* newOutgoingCall */); 1368 } else if (startIncomingCallSequence) { 1369 Log.i(this, "Start Full Screen in call UI"); 1370 1371 // We're about the bring up the in-call UI for an incoming call. If we still have 1372 // dialogs up, we need to clear them out before showing incoming screen. 1373 if (isActivityStarted()) { 1374 mInCallActivity.dismissPendingDialogs(); 1375 } 1376 if (!startUi(newState)) { 1377 // startUI refused to start the UI. This indicates that it needed to restart the 1378 // activity. When it finally restarts, it will call us back, so we do not actually 1379 // change the state yet (we return mInCallState instead of newState). 1380 return mInCallState; 1381 } 1382 } else if (newState == InCallState.NO_CALLS) { 1383 // The new state is the no calls state. Tear everything down. 1384 attemptFinishActivity(); 1385 attemptCleanup(); 1386 } 1387 1388 return newState; 1389 } 1390 1391 /** 1392 * Determines whether or not a call has no valid phone accounts that can be used to make the 1393 * call with. Emergency calls do not require a phone account. 1394 * 1395 * @param call to check accounts for. 1396 * @return {@code true} if the call has no call capable phone accounts set, {@code false} if 1397 * the call contains a phone account that could be used to initiate it with, or is an emergency 1398 * call. 1399 */ isCallWithNoValidAccounts(Call call)1400 public static boolean isCallWithNoValidAccounts(Call call) { 1401 if (call != null && !call.isEmergencyCall()) { 1402 Bundle extras = call.getIntentExtras(); 1403 1404 if (extras == null) { 1405 extras = EMPTY_EXTRAS; 1406 } 1407 1408 final List<PhoneAccountHandle> phoneAccountHandles = extras 1409 .getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS); 1410 1411 if ((call.getAccountHandle() == null && 1412 (phoneAccountHandles == null || phoneAccountHandles.isEmpty()))) { 1413 Log.i(InCallPresenter.getInstance(), "No valid accounts for call " + call); 1414 return true; 1415 } 1416 } 1417 return false; 1418 } 1419 1420 /** 1421 * Sets the DisconnectCause for a call that was disconnected because it was missing a 1422 * PhoneAccount or PhoneAccounts to select from. 1423 * @param call 1424 */ setDisconnectCauseForMissingAccounts(Call call)1425 private void setDisconnectCauseForMissingAccounts(Call call) { 1426 android.telecom.Call telecomCall = call.getTelecomCall(); 1427 1428 Bundle extras = telecomCall.getDetails().getIntentExtras(); 1429 // Initialize the extras bundle to avoid NPE 1430 if (extras == null) { 1431 extras = new Bundle(); 1432 } 1433 1434 final List<PhoneAccountHandle> phoneAccountHandles = extras.getParcelableArrayList( 1435 android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS); 1436 1437 if (phoneAccountHandles == null || phoneAccountHandles.isEmpty()) { 1438 String scheme = telecomCall.getDetails().getHandle().getScheme(); 1439 final String errorMsg = PhoneAccount.SCHEME_TEL.equals(scheme) ? 1440 mContext.getString(R.string.callFailed_simError) : 1441 mContext.getString(R.string.incall_error_supp_service_unknown); 1442 DisconnectCause disconnectCause = 1443 new DisconnectCause(DisconnectCause.ERROR, null, errorMsg, errorMsg); 1444 call.setDisconnectCause(disconnectCause); 1445 } 1446 } 1447 startUi(InCallState inCallState)1448 private boolean startUi(InCallState inCallState) { 1449 boolean isCallWaiting = mCallList.getActiveCall() != null && 1450 mCallList.getIncomingCall() != null; 1451 1452 // If the screen is off, we need to make sure it gets turned on for incoming calls. 1453 // This normally works just fine thanks to FLAG_TURN_SCREEN_ON but that only works 1454 // when the activity is first created. Therefore, to ensure the screen is turned on 1455 // for the call waiting case, we finish() the current activity and start a new one. 1456 // There should be no jank from this since the screen is already off and will remain so 1457 // until our new activity is up. 1458 1459 if (isCallWaiting) { 1460 if (mProximitySensor.isScreenReallyOff() && isActivityStarted()) { 1461 Log.i(this, "Restarting InCallActivity to turn screen on for call waiting"); 1462 mInCallActivity.finish(); 1463 // When the activity actually finishes, we will start it again if there are 1464 // any active calls, so we do not need to start it explicitly here. Note, we 1465 // actually get called back on this function to restart it. 1466 1467 // We return false to indicate that we did not actually start the UI. 1468 return false; 1469 } else { 1470 showInCall(false, false); 1471 } 1472 } else { 1473 mStatusBarNotifier.updateNotification(inCallState, mCallList); 1474 } 1475 return true; 1476 } 1477 1478 /** 1479 * Checks to see if both the UI is gone and the service is disconnected. If so, tear it all 1480 * down. 1481 */ attemptCleanup()1482 private void attemptCleanup() { 1483 boolean shouldCleanup = (mInCallActivity == null && !mServiceConnected && 1484 mInCallState == InCallState.NO_CALLS); 1485 Log.i(this, "attemptCleanup? " + shouldCleanup); 1486 1487 if (shouldCleanup) { 1488 mIsActivityPreviouslyStarted = false; 1489 mIsChangingConfigurations = false; 1490 1491 // blow away stale contact info so that we get fresh data on 1492 // the next set of calls 1493 if (mContactInfoCache != null) { 1494 mContactInfoCache.clearCache(); 1495 } 1496 mContactInfoCache = null; 1497 1498 if (mProximitySensor != null) { 1499 removeListener(mProximitySensor); 1500 mProximitySensor.tearDown(); 1501 } 1502 mProximitySensor = null; 1503 1504 mAudioModeProvider = null; 1505 1506 if (mStatusBarNotifier != null) { 1507 removeListener(mStatusBarNotifier); 1508 } 1509 mStatusBarNotifier = null; 1510 1511 if (mCallList != null) { 1512 mCallList.removeListener(this); 1513 } 1514 mCallList = null; 1515 1516 mContext = null; 1517 mInCallActivity = null; 1518 1519 mListeners.clear(); 1520 mIncomingCallListeners.clear(); 1521 mDetailsListeners.clear(); 1522 mCanAddCallListeners.clear(); 1523 mOrientationListeners.clear(); 1524 mInCallEventListeners.clear(); 1525 1526 Log.d(this, "Finished InCallPresenter.CleanUp"); 1527 } 1528 } 1529 showInCall(final boolean showDialpad, final boolean newOutgoingCall)1530 public void showInCall(final boolean showDialpad, final boolean newOutgoingCall) { 1531 Log.i(this, "Showing InCallActivity"); 1532 mContext.startActivity(getInCallIntent(showDialpad, newOutgoingCall)); 1533 } 1534 onServiceBind()1535 public void onServiceBind() { 1536 mServiceBound = true; 1537 } 1538 onServiceUnbind()1539 public void onServiceUnbind() { 1540 InCallPresenter.getInstance().setBoundAndWaitingForOutgoingCall(false, null); 1541 mServiceBound = false; 1542 } 1543 isServiceBound()1544 public boolean isServiceBound() { 1545 return mServiceBound; 1546 } 1547 maybeStartRevealAnimation(Intent intent)1548 public void maybeStartRevealAnimation(Intent intent) { 1549 if (intent == null || mInCallActivity != null) { 1550 return; 1551 } 1552 final Bundle extras = intent.getBundleExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS); 1553 if (extras == null) { 1554 // Incoming call, just show the in-call UI directly. 1555 return; 1556 } 1557 1558 if (extras.containsKey(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS)) { 1559 // Account selection dialog will show up so don't show the animation. 1560 return; 1561 } 1562 1563 final PhoneAccountHandle accountHandle = 1564 intent.getParcelableExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE); 1565 final Point touchPoint = extras.getParcelable(TouchPointManager.TOUCH_POINT); 1566 1567 InCallPresenter.getInstance().setBoundAndWaitingForOutgoingCall(true, accountHandle); 1568 1569 final Intent incallIntent = getInCallIntent(false, true); 1570 incallIntent.putExtra(TouchPointManager.TOUCH_POINT, touchPoint); 1571 mContext.startActivity(incallIntent); 1572 } 1573 getInCallIntent(boolean showDialpad, boolean newOutgoingCall)1574 public Intent getInCallIntent(boolean showDialpad, boolean newOutgoingCall) { 1575 final Intent intent = new Intent(Intent.ACTION_MAIN, null); 1576 intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK); 1577 1578 intent.setClass(mContext, InCallActivity.class); 1579 if (showDialpad) { 1580 intent.putExtra(InCallActivity.SHOW_DIALPAD_EXTRA, true); 1581 } 1582 intent.putExtra(InCallActivity.NEW_OUTGOING_CALL_EXTRA, newOutgoingCall); 1583 return intent; 1584 } 1585 1586 /** 1587 * Retrieves the current in-call camera manager instance, creating if necessary. 1588 * 1589 * @return The {@link InCallCameraManager}. 1590 */ getInCallCameraManager()1591 public InCallCameraManager getInCallCameraManager() { 1592 synchronized(this) { 1593 if (mInCallCameraManager == null) { 1594 mInCallCameraManager = new InCallCameraManager(mContext); 1595 } 1596 1597 return mInCallCameraManager; 1598 } 1599 } 1600 1601 /** 1602 * Notifies listeners of changes in orientation and notify calls of rotation angle change. 1603 * 1604 * @param orientation The screen orientation of the device (one of: 1605 * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_0}, 1606 * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_90}, 1607 * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_180}, 1608 * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_270}). 1609 */ onDeviceOrientationChange(int orientation)1610 public void onDeviceOrientationChange(int orientation) { 1611 Log.d(this, "onDeviceOrientationChange: orientation= " + orientation); 1612 1613 if (mCallList != null) { 1614 mCallList.notifyCallsOfDeviceRotation(orientation); 1615 } else { 1616 Log.w(this, "onDeviceOrientationChange: CallList is null."); 1617 } 1618 1619 // Notify listeners of device orientation changed. 1620 for (InCallOrientationListener listener : mOrientationListeners) { 1621 listener.onDeviceOrientationChanged(orientation); 1622 } 1623 } 1624 1625 /** 1626 * Configures the in-call UI activity so it can change orientations or not. Enables the 1627 * orientation event listener if allowOrientationChange is true, disables it if false. 1628 * 1629 * @param allowOrientationChange {@code True} if the in-call UI can change between portrait 1630 * and landscape. {@Code False} if the in-call UI should be locked in portrait. 1631 */ setInCallAllowsOrientationChange(boolean allowOrientationChange)1632 public void setInCallAllowsOrientationChange(boolean allowOrientationChange) { 1633 if (mInCallActivity == null) { 1634 Log.e(this, "InCallActivity is null. Can't set requested orientation."); 1635 return; 1636 } 1637 1638 if (!allowOrientationChange) { 1639 mInCallActivity.setRequestedOrientation( 1640 InCallOrientationEventListener.NO_SENSOR_SCREEN_ORIENTATION); 1641 } else { 1642 // Using SCREEN_ORIENTATION_FULL_SENSOR allows for reverse-portrait orientation, where 1643 // SCREEN_ORIENTATION_SENSOR does not. 1644 mInCallActivity.setRequestedOrientation( 1645 InCallOrientationEventListener.FULL_SENSOR_SCREEN_ORIENTATION); 1646 } 1647 mInCallActivity.enableInCallOrientationEventListener(allowOrientationChange); 1648 } 1649 enableScreenTimeout(boolean enable)1650 public void enableScreenTimeout(boolean enable) { 1651 Log.v(this, "enableScreenTimeout: value=" + enable); 1652 if (mInCallActivity == null) { 1653 Log.e(this, "enableScreenTimeout: InCallActivity is null."); 1654 return; 1655 } 1656 1657 final Window window = mInCallActivity.getWindow(); 1658 if (enable) { 1659 window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1660 } else { 1661 window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1662 } 1663 } 1664 1665 /** 1666 * Returns the space available beside the call card. 1667 * 1668 * @return The space beside the call card. 1669 */ getSpaceBesideCallCard()1670 public float getSpaceBesideCallCard() { 1671 if (mInCallActivity != null && mInCallActivity.getCallCardFragment() != null) { 1672 return mInCallActivity.getCallCardFragment().getSpaceBesideCallCard(); 1673 } 1674 return 0; 1675 } 1676 1677 /** 1678 * Returns whether the call card fragment is currently visible. 1679 * 1680 * @return True if the call card fragment is visible. 1681 */ getCallCardFragmentVisible()1682 public boolean getCallCardFragmentVisible() { 1683 if (mInCallActivity != null && mInCallActivity.getCallCardFragment() != null) { 1684 return mInCallActivity.getCallCardFragment().isVisible(); 1685 } 1686 return false; 1687 } 1688 1689 /** 1690 * Hides or shows the conference manager fragment. 1691 * 1692 * @param show {@code true} if the conference manager should be shown, {@code false} if it 1693 * should be hidden. 1694 */ showConferenceCallManager(boolean show)1695 public void showConferenceCallManager(boolean show) { 1696 if (mInCallActivity == null) { 1697 return; 1698 } 1699 1700 mInCallActivity.showConferenceFragment(show); 1701 } 1702 1703 /** 1704 * Determines if the dialpad is visible. 1705 * 1706 * @return {@code true} if the dialpad is visible, {@code false} otherwise. 1707 */ isDialpadVisible()1708 public boolean isDialpadVisible() { 1709 if (mInCallActivity == null) { 1710 return false; 1711 } 1712 return mInCallActivity.isDialpadVisible(); 1713 } 1714 1715 /** 1716 * @return True if the application is currently running in a right-to-left locale. 1717 */ isRtl()1718 public static boolean isRtl() { 1719 return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == 1720 View.LAYOUT_DIRECTION_RTL; 1721 } 1722 1723 /** 1724 * Extract background color from call object. The theme colors will include a primary color 1725 * and a secondary color. 1726 */ setThemeColors()1727 public void setThemeColors() { 1728 // This method will set the background to default if the color is PhoneAccount.NO_COLOR. 1729 mThemeColors = getColorsFromCall(mCallList.getFirstCall()); 1730 1731 if (mInCallActivity == null) { 1732 return; 1733 } 1734 1735 final Resources resources = mInCallActivity.getResources(); 1736 final int color; 1737 if (resources.getBoolean(R.bool.is_layout_landscape)) { 1738 // TODO use ResourcesCompat.getColor(Resources, int, Resources.Theme) when available 1739 // {@link Resources#getColor(int)} used for compatibility 1740 color = resources.getColor(R.color.statusbar_background_color); 1741 } else { 1742 color = mThemeColors.mSecondaryColor; 1743 } 1744 1745 mInCallActivity.getWindow().setStatusBarColor(color); 1746 final TaskDescription td = new TaskDescription( 1747 resources.getString(R.string.notification_ongoing_call), null, color); 1748 mInCallActivity.setTaskDescription(td); 1749 } 1750 1751 /** 1752 * @return A palette for colors to display in the UI. 1753 */ getThemeColors()1754 public MaterialPalette getThemeColors() { 1755 return mThemeColors; 1756 } 1757 getColorsFromCall(Call call)1758 private MaterialPalette getColorsFromCall(Call call) { 1759 if (call == null) { 1760 return getColorsFromPhoneAccountHandle(mPendingPhoneAccountHandle); 1761 } else { 1762 return getColorsFromPhoneAccountHandle(call.getAccountHandle()); 1763 } 1764 } 1765 getColorsFromPhoneAccountHandle(PhoneAccountHandle phoneAccountHandle)1766 private MaterialPalette getColorsFromPhoneAccountHandle(PhoneAccountHandle phoneAccountHandle) { 1767 int highlightColor = PhoneAccount.NO_HIGHLIGHT_COLOR; 1768 if (phoneAccountHandle != null) { 1769 final TelecomManager tm = getTelecomManager(); 1770 1771 if (tm != null) { 1772 final PhoneAccount account = 1773 TelecomManagerCompat.getPhoneAccount(tm, phoneAccountHandle); 1774 // For single-sim devices, there will be no selected highlight color, so the phone 1775 // account will default to NO_HIGHLIGHT_COLOR. 1776 if (account != null && CompatUtils.isLollipopMr1Compatible()) { 1777 highlightColor = account.getHighlightColor(); 1778 } 1779 } 1780 } 1781 return new InCallUIMaterialColorMapUtils( 1782 mContext.getResources()).calculatePrimaryAndSecondaryColor(highlightColor); 1783 } 1784 1785 /** 1786 * @return An instance of TelecomManager. 1787 */ getTelecomManager()1788 public TelecomManager getTelecomManager() { 1789 if (mTelecomManager == null) { 1790 mTelecomManager = (TelecomManager) 1791 mContext.getSystemService(Context.TELECOM_SERVICE); 1792 } 1793 return mTelecomManager; 1794 } 1795 1796 /** 1797 * @return An instance of TelephonyManager 1798 */ getTelephonyManager()1799 public TelephonyManager getTelephonyManager() { 1800 return mTelephonyManager; 1801 } 1802 getActivity()1803 InCallActivity getActivity() { 1804 return mInCallActivity; 1805 } 1806 getAnswerPresenter()1807 AnswerPresenter getAnswerPresenter() { 1808 return mAnswerPresenter; 1809 } 1810 1811 /** 1812 * Private constructor. Must use getInstance() to get this singleton. 1813 */ InCallPresenter()1814 private InCallPresenter() { 1815 } 1816 1817 /** 1818 * All the main states of InCallActivity. 1819 */ 1820 public enum InCallState { 1821 // InCall Screen is off and there are no calls 1822 NO_CALLS, 1823 1824 // Incoming-call screen is up 1825 INCOMING, 1826 1827 // In-call experience is showing 1828 INCALL, 1829 1830 // Waiting for user input before placing outgoing call 1831 WAITING_FOR_ACCOUNT, 1832 1833 // UI is starting up but no call has been initiated yet. 1834 // The UI is waiting for Telecom to respond. 1835 PENDING_OUTGOING, 1836 1837 // User is dialing out 1838 OUTGOING; 1839 isIncoming()1840 public boolean isIncoming() { 1841 return (this == INCOMING); 1842 } 1843 isConnectingOrConnected()1844 public boolean isConnectingOrConnected() { 1845 return (this == INCOMING || 1846 this == OUTGOING || 1847 this == INCALL); 1848 } 1849 } 1850 1851 /** 1852 * Interface implemented by classes that need to know about the InCall State. 1853 */ 1854 public interface InCallStateListener { 1855 // TODO: Enhance state to contain the call objects instead of passing CallList onStateChange(InCallState oldState, InCallState newState, CallList callList)1856 public void onStateChange(InCallState oldState, InCallState newState, CallList callList); 1857 } 1858 1859 public interface IncomingCallListener { onIncomingCall(InCallState oldState, InCallState newState, Call call)1860 public void onIncomingCall(InCallState oldState, InCallState newState, Call call); 1861 } 1862 1863 public interface CanAddCallListener { onCanAddCallChanged(boolean canAddCall)1864 public void onCanAddCallChanged(boolean canAddCall); 1865 } 1866 1867 public interface InCallDetailsListener { onDetailsChanged(Call call, android.telecom.Call.Details details)1868 public void onDetailsChanged(Call call, android.telecom.Call.Details details); 1869 } 1870 1871 public interface InCallOrientationListener { onDeviceOrientationChanged(int orientation)1872 public void onDeviceOrientationChanged(int orientation); 1873 } 1874 1875 /** 1876 * Interface implemented by classes that need to know about events which occur within the 1877 * In-Call UI. Used as a means of communicating between fragments that make up the UI. 1878 */ 1879 public interface InCallEventListener { onFullscreenModeChanged(boolean isFullscreenMode)1880 public void onFullscreenModeChanged(boolean isFullscreenMode); onSecondaryCallerInfoVisibilityChanged(boolean isVisible, int height)1881 public void onSecondaryCallerInfoVisibilityChanged(boolean isVisible, int height); 1882 } 1883 1884 public interface InCallUiListener { onUiShowing(boolean showing)1885 void onUiShowing(boolean showing); 1886 } 1887 } 1888