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