InCallPresenter.java revision df06e232852a93d8238f3cacaab4d704de7e1216
1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.incallui; 18 19import android.content.Context; 20import android.content.Intent; 21import android.telecomm.CallCapabilities; 22import android.telecomm.Phone; 23 24import com.google.common.base.Preconditions; 25import com.google.common.collect.Sets; 26import com.google.common.collect.Lists; 27 28import java.util.ArrayList; 29import java.util.Set; 30 31/** 32 * Takes updates from the CallList and notifies the InCallActivity (UI) 33 * of the changes. 34 * Responsible for starting the activity for a new call and finishing the activity when all calls 35 * are disconnected. 36 * Creates and manages the in-call state and provides a listener pattern for the presenters 37 * that want to listen in on the in-call state changes. 38 * TODO: This class has become more of a state machine at this point. Consider renaming. 39 */ 40public class InCallPresenter implements CallList.Listener, InCallPhoneListener { 41 42 private static InCallPresenter sInCallPresenter; 43 44 private final Set<InCallStateListener> mListeners = Sets.newHashSet(); 45 private final ArrayList<IncomingCallListener> mIncomingCallListeners = Lists.newArrayList(); 46 47 private AudioModeProvider mAudioModeProvider; 48 private StatusBarNotifier mStatusBarNotifier; 49 private ContactInfoCache mContactInfoCache; 50 private Context mContext; 51 private CallList mCallList; 52 private InCallActivity mInCallActivity; 53 private InCallState mInCallState = InCallState.NO_CALLS; 54 private ProximitySensor mProximitySensor; 55 private boolean mServiceConnected = false; 56 57 private final Phone.Listener mPhoneListener = new Phone.Listener() { 58 @Override 59 public void onBringToForeground(Phone phone, boolean showDialpad) { 60 Log.i(this, "Bringing UI to foreground."); 61 bringToForeground(showDialpad); 62 } 63 @Override 64 public void onCallAdded(Phone phone, android.telecomm.Call call) { 65 call.addListener(mCallListener); 66 } 67 @Override 68 public void onCallRemoved(Phone phone, android.telecomm.Call call) { 69 call.removeListener(mCallListener); 70 } 71 }; 72 73 private final android.telecomm.Call.Listener mCallListener = 74 new android.telecomm.Call.Listener() { 75 @Override 76 public void onPostDialWait(android.telecomm.Call call, String remainingPostDialSequence) { 77 onPostDialCharWait( 78 CallList.getInstance().getCallByTelecommCall(call).getId(), 79 remainingPostDialSequence); 80 } 81 }; 82 83 /** 84 * Is true when the activity has been previously started. Some code needs to know not just if 85 * the activity is currently up, but if it had been previously shown in foreground for this 86 * in-call session (e.g., StatusBarNotifier). This gets reset when the session ends in the 87 * tear-down method. 88 */ 89 private boolean mIsActivityPreviouslyStarted = false; 90 91 private Phone mPhone; 92 93 public static synchronized InCallPresenter getInstance() { 94 if (sInCallPresenter == null) { 95 sInCallPresenter = new InCallPresenter(); 96 } 97 return sInCallPresenter; 98 } 99 100 @Override 101 public void setPhone(Phone phone) { 102 mPhone = phone; 103 mPhone.addListener(mPhoneListener); 104 } 105 106 @Override 107 public void clearPhone() { 108 mPhone.removeListener(mPhoneListener); 109 mPhone = null; 110 } 111 112 public InCallState getInCallState() { 113 return mInCallState; 114 } 115 116 public CallList getCallList() { 117 return mCallList; 118 } 119 120 public void setUp(Context context, CallList callList, AudioModeProvider audioModeProvider) { 121 if (mServiceConnected) { 122 Log.i(this, "New service connection replacing existing one."); 123 // retain the current resources, no need to create new ones. 124 Preconditions.checkState(context == mContext); 125 Preconditions.checkState(callList == mCallList); 126 Preconditions.checkState(audioModeProvider == mAudioModeProvider); 127 return; 128 } 129 130 Preconditions.checkNotNull(context); 131 mContext = context; 132 133 mContactInfoCache = ContactInfoCache.getInstance(context); 134 135 mStatusBarNotifier = new StatusBarNotifier(context, mContactInfoCache); 136 addListener(mStatusBarNotifier); 137 138 mAudioModeProvider = audioModeProvider; 139 140 mProximitySensor = new ProximitySensor(context, mAudioModeProvider); 141 addListener(mProximitySensor); 142 143 mCallList = callList; 144 145 // This only gets called by the service so this is okay. 146 mServiceConnected = true; 147 148 // The final thing we do in this set up is add ourselves as a listener to CallList. This 149 // will kick off an update and the whole process can start. 150 mCallList.addListener(this); 151 152 Log.d(this, "Finished InCallPresenter.setUp"); 153 } 154 155 /** 156 * Called when the telephony service has disconnected from us. This will happen when there are 157 * no more active calls. However, we may still want to continue showing the UI for 158 * certain cases like showing "Call Ended". 159 * What we really want is to wait for the activity and the service to both disconnect before we 160 * tear things down. This method sets a serviceConnected boolean and calls a secondary method 161 * that performs the aforementioned logic. 162 */ 163 public void tearDown() { 164 Log.d(this, "tearDown"); 165 mServiceConnected = false; 166 attemptCleanup(); 167 } 168 169 private void attemptFinishActivity() { 170 final boolean doFinish = (mInCallActivity != null && isActivityStarted()); 171 Log.i(this, "Hide in call UI: " + doFinish); 172 173 if (doFinish) { 174 mInCallActivity.finish(); 175 } 176 } 177 178 /** 179 * Called when the UI begins or ends. Starts the callstate callbacks if the UI just began. 180 * Attempts to tear down everything if the UI just ended. See #tearDown for more insight on 181 * the tear-down process. 182 */ 183 public void setActivity(InCallActivity inCallActivity) { 184 boolean updateListeners = false; 185 boolean doAttemptCleanup = false; 186 187 if (inCallActivity != null) { 188 if (mInCallActivity == null) { 189 updateListeners = true; 190 Log.i(this, "UI Initialized"); 191 } else if (mInCallActivity != inCallActivity) { 192 Log.wtf(this, "Setting a second activity before destroying the first."); 193 } else { 194 // since setActivity is called onStart(), it can be called multiple times. 195 // This is fine and ignorable, but we do not want to update the world every time 196 // this happens (like going to/from background) so we do not set updateListeners. 197 } 198 199 mInCallActivity = inCallActivity; 200 201 // By the time the UI finally comes up, the call may already be disconnected. 202 // If that's the case, we may need to show an error dialog. 203 if (mCallList != null && mCallList.getDisconnectedCall() != null) { 204 maybeShowErrorDialogOnDisconnect(mCallList.getDisconnectedCall()); 205 } 206 207 // When the UI comes up, we need to first check the in-call state. 208 // If we are showing NO_CALLS, that means that a call probably connected and 209 // then immediately disconnected before the UI was able to come up. 210 // If we dont have any calls, start tearing down the UI instead. 211 // NOTE: This code relies on {@link #mInCallActivity} being set so we run it after 212 // it has been set. 213 if (mInCallState == InCallState.NO_CALLS) { 214 Log.i(this, "UI Intialized, but no calls left. shut down."); 215 attemptFinishActivity(); 216 return; 217 } 218 } else { 219 Log.i(this, "UI Destroyed)"); 220 updateListeners = true; 221 mInCallActivity = null; 222 223 // We attempt cleanup for the destroy case but only after we recalculate the state 224 // to see if we need to come back up or stay shut down. This is why we do the cleanup 225 // after the call to onCallListChange() instead of directly here. 226 doAttemptCleanup = true; 227 } 228 229 // Messages can come from the telephony layer while the activity is coming up 230 // and while the activity is going down. So in both cases we need to recalculate what 231 // state we should be in after they complete. 232 // Examples: (1) A new incoming call could come in and then get disconnected before 233 // the activity is created. 234 // (2) All calls could disconnect and then get a new incoming call before the 235 // activity is destroyed. 236 // 237 // b/1122139 - We previously had a check for mServiceConnected here as well, but there are 238 // cases where we need to recalculate the current state even if the service in not 239 // connected. In particular the case where startOrFinish() is called while the app is 240 // already finish()ing. In that case, we skip updating the state with the knowledge that 241 // we will check again once the activity has finished. That means we have to recalculate the 242 // state here even if the service is disconnected since we may not have finished a state 243 // transition while finish()ing. 244 if (updateListeners) { 245 onCallListChange(mCallList); 246 } 247 248 if (doAttemptCleanup) { 249 attemptCleanup(); 250 } 251 } 252 253 /** 254 * Called when there is a change to the call list. 255 * Sets the In-Call state for the entire in-call app based on the information it gets from 256 * CallList. Dispatches the in-call state to all listeners. Can trigger the creation or 257 * destruction of the UI based on the states that is calculates. 258 */ 259 @Override 260 public void onCallListChange(CallList callList) { 261 if (callList == null) { 262 return; 263 } 264 InCallState newState = getPotentialStateFromCallList(callList); 265 newState = startOrFinishUi(newState); 266 267 // Set the new state before announcing it to the world 268 Log.i(this, "Phone switching state: " + mInCallState + " -> " + newState); 269 mInCallState = newState; 270 271 // notify listeners of new state 272 for (InCallStateListener listener : mListeners) { 273 Log.d(this, "Notify " + listener + " of state " + mInCallState.toString()); 274 listener.onStateChange(mInCallState, callList); 275 } 276 277 if (isActivityStarted()) { 278 final boolean hasCall = callList.getActiveOrBackgroundCall() != null || 279 callList.getOutgoingCall() != null; 280 mInCallActivity.dismissKeyguard(hasCall); 281 } 282 } 283 284 /** 285 * Called when there is a new incoming call. 286 * 287 * @param call 288 */ 289 @Override 290 public void onIncomingCall(Call call) { 291 InCallState newState = startOrFinishUi(InCallState.INCOMING); 292 293 Log.i(this, "Phone switching state: " + mInCallState + " -> " + newState); 294 mInCallState = newState; 295 296 for (IncomingCallListener listener : mIncomingCallListeners) { 297 listener.onIncomingCall(mInCallState, call); 298 } 299 } 300 301 /** 302 * Called when a call becomes disconnected. Called everytime an existing call 303 * changes from being connected (incoming/outgoing/active) to disconnected. 304 */ 305 @Override 306 public void onDisconnect(Call call) { 307 hideDialpadForDisconnect(); 308 maybeShowErrorDialogOnDisconnect(call); 309 310 // We need to do the run the same code as onCallListChange. 311 onCallListChange(CallList.getInstance()); 312 313 if (isActivityStarted()) { 314 mInCallActivity.dismissKeyguard(false); 315 } 316 } 317 318 /** 319 * Given the call list, return the state in which the in-call screen should be. 320 */ 321 public static InCallState getPotentialStateFromCallList(CallList callList) { 322 323 InCallState newState = InCallState.NO_CALLS; 324 325 if (callList == null) { 326 return newState; 327 } 328 if (callList.getIncomingCall() != null) { 329 newState = InCallState.INCOMING; 330 } else if (callList.getOutgoingCall() != null) { 331 newState = InCallState.OUTGOING; 332 } else if (callList.getActiveCall() != null || 333 callList.getBackgroundCall() != null || 334 callList.getDisconnectedCall() != null || 335 callList.getDisconnectingCall() != null) { 336 newState = InCallState.INCALL; 337 } 338 339 return newState; 340 } 341 342 public void addIncomingCallListener(IncomingCallListener listener) { 343 Preconditions.checkNotNull(listener); 344 mIncomingCallListeners.add(listener); 345 } 346 347 public void removeIncomingCallListener(IncomingCallListener listener) { 348 Preconditions.checkNotNull(listener); 349 mIncomingCallListeners.remove(listener); 350 } 351 352 public void addListener(InCallStateListener listener) { 353 Preconditions.checkNotNull(listener); 354 mListeners.add(listener); 355 } 356 357 public void removeListener(InCallStateListener listener) { 358 Preconditions.checkNotNull(listener); 359 mListeners.remove(listener); 360 } 361 362 public ProximitySensor getProximitySensor() { 363 return mProximitySensor; 364 } 365 366 /** 367 * Hangs up any active or outgoing calls. 368 */ 369 public void hangUpOngoingCall(Context context) { 370 // By the time we receive this intent, we could be shut down and call list 371 // could be null. Bail in those cases. 372 if (mCallList == null) { 373 if (mStatusBarNotifier == null) { 374 // The In Call UI has crashed but the notification still stayed up. We should not 375 // come to this stage. 376 StatusBarNotifier.clearInCallNotification(context); 377 } 378 return; 379 } 380 381 Call call = mCallList.getOutgoingCall(); 382 if (call == null) { 383 call = mCallList.getActiveOrBackgroundCall(); 384 } 385 386 if (call != null) { 387 TelecommAdapter.getInstance().disconnectCall(call.getId()); 388 } 389 } 390 391 /** 392 * Answers any incoming call. 393 */ 394 public void answerIncomingCall(Context context) { 395 // By the time we receive this intent, we could be shut down and call list 396 // could be null. Bail in those cases. 397 if (mCallList == null) { 398 StatusBarNotifier.clearInCallNotification(context); 399 return; 400 } 401 402 Call call = mCallList.getIncomingCall(); 403 if (call != null) { 404 TelecommAdapter.getInstance().answerCall(call.getId()); 405 showInCall(false, false/* newOutgoingCall */); 406 } 407 } 408 409 /** 410 * Declines any incoming call. 411 */ 412 public void declineIncomingCall(Context context) { 413 // By the time we receive this intent, we could be shut down and call list 414 // could be null. Bail in those cases. 415 if (mCallList == null) { 416 StatusBarNotifier.clearInCallNotification(context); 417 return; 418 } 419 420 Call call = mCallList.getIncomingCall(); 421 if (call != null) { 422 TelecommAdapter.getInstance().rejectCall(call.getId(), false, null); 423 } 424 } 425 426 /** 427 * Returns true if the incall app is the foreground application. 428 */ 429 public boolean isShowingInCallUi() { 430 return (isActivityStarted() && mInCallActivity.isForegroundActivity()); 431 } 432 433 /** 434 * Returns true of the activity has been created and is running. 435 * Returns true as long as activity is not destroyed or finishing. This ensures that we return 436 * true even if the activity is paused (not in foreground). 437 */ 438 public boolean isActivityStarted() { 439 return (mInCallActivity != null && 440 !mInCallActivity.isDestroyed() && 441 !mInCallActivity.isFinishing()); 442 } 443 444 public boolean isActivityPreviouslyStarted() { 445 return mIsActivityPreviouslyStarted; 446 } 447 448 /** 449 * Called when the activity goes in/out of the foreground. 450 */ 451 public void onUiShowing(boolean showing) { 452 // We need to update the notification bar when we leave the UI because that 453 // could trigger it to show again. 454 if (mStatusBarNotifier != null) { 455 mStatusBarNotifier.updateNotification(mInCallState, mCallList); 456 } 457 458 if (mProximitySensor != null) { 459 mProximitySensor.onInCallShowing(showing); 460 } 461 462 if (showing) { 463 mIsActivityPreviouslyStarted = true; 464 } 465 } 466 467 /** 468 * Brings the app into the foreground if possible. 469 */ 470 public void bringToForeground(boolean showDialpad) { 471 // Before we bring the incall UI to the foreground, we check to see if: 472 // 1. We've already started the activity once for this session 473 // 2. If it exists, the activity is not already in the foreground 474 // 3. We are in a state where we want to show the incall ui 475 if (mIsActivityPreviouslyStarted && !isShowingInCallUi() && 476 mInCallState != InCallState.NO_CALLS) { 477 showInCall(showDialpad, false /* newOutgoingCall */); 478 } 479 } 480 481 public void onPostDialCharWait(String callId, String chars) { 482 if (isActivityStarted()) { 483 mInCallActivity.showPostCharWaitDialog(callId, chars); 484 } 485 } 486 487 /** 488 * Handles the green CALL key while in-call. 489 * @return true if we consumed the event. 490 */ 491 public boolean handleCallKey() { 492 Log.v(this, "handleCallKey"); 493 494 // The green CALL button means either "Answer", "Unhold", or 495 // "Swap calls", or can be a no-op, depending on the current state 496 // of the Phone. 497 498 /** 499 * INCOMING CALL 500 */ 501 final CallList calls = CallList.getInstance(); 502 final Call incomingCall = calls.getIncomingCall(); 503 Log.v(this, "incomingCall: " + incomingCall); 504 505 // (1) Attempt to answer a call 506 if (incomingCall != null) { 507 TelecommAdapter.getInstance().answerCall(incomingCall.getId()); 508 return true; 509 } 510 511 /** 512 * ACTIVE CALL 513 */ 514 final Call activeCall = calls.getActiveCall(); 515 if (activeCall != null) { 516 // TODO: This logic is repeated from CallButtonPresenter.java. We should 517 // consolidate this logic. 518 final boolean isGeneric = activeCall.can(CallCapabilities.GENERIC_CONFERENCE); 519 final boolean canMerge = activeCall.can(CallCapabilities.MERGE_CALLS); 520 final boolean canSwap = activeCall.can(CallCapabilities.SWAP_CALLS); 521 522 Log.v(this, "activeCall: " + activeCall + ", isGeneric: " + isGeneric + ", canMerge: " + 523 canMerge + ", canSwap: " + canSwap); 524 525 // (2) Attempt actions on Generic conference calls 526 if (activeCall.isConferenceCall() && isGeneric) { 527 if (canMerge) { 528 TelecommAdapter.getInstance().merge(activeCall.getId()); 529 return true; 530 } else if (canSwap) { 531 TelecommAdapter.getInstance().swap(activeCall.getId()); 532 return true; 533 } 534 } 535 536 // (3) Swap calls 537 if (canSwap) { 538 TelecommAdapter.getInstance().swap(activeCall.getId()); 539 return true; 540 } 541 } 542 543 /** 544 * BACKGROUND CALL 545 */ 546 final Call heldCall = calls.getBackgroundCall(); 547 if (heldCall != null) { 548 // We have a hold call so presumeable it will always support HOLD...but 549 // there is no harm in double checking. 550 final boolean canHold = heldCall.can(CallCapabilities.HOLD); 551 552 Log.v(this, "heldCall: " + heldCall + ", canHold: " + canHold); 553 554 // (4) unhold call 555 if (heldCall.getState() == Call.State.ONHOLD && canHold) { 556 TelecommAdapter.getInstance().unholdCall(heldCall.getId()); 557 return true; 558 } 559 } 560 561 // Always consume hard keys 562 return true; 563 } 564 565 /** 566 * A dialog could have prevented in-call screen from being previously finished. 567 * This function checks to see if there should be any UI left and if not attempts 568 * to tear down the UI. 569 */ 570 public void onDismissDialog() { 571 Log.i(this, "Dialog dismissed"); 572 if (mInCallState == InCallState.NO_CALLS) { 573 attemptFinishActivity(); 574 attemptCleanup(); 575 } 576 } 577 578 /** 579 * For some disconnected causes, we show a dialog. This calls into the activity to show 580 * the dialog if appropriate for the call. 581 */ 582 private void maybeShowErrorDialogOnDisconnect(Call call) { 583 // For newly disconnected calls, we may want to show a dialog on specific error conditions 584 if (isActivityStarted() && call.getState() == Call.State.DISCONNECTED) { 585 mInCallActivity.maybeShowErrorDialogOnDisconnect(call.getDisconnectCause()); 586 } 587 } 588 589 /** 590 * Hides the dialpad. Called when a call is disconnected (Requires hiding dialpad). 591 */ 592 private void hideDialpadForDisconnect() { 593 if (isActivityStarted()) { 594 mInCallActivity.hideDialpadForDisconnect(); 595 } 596 } 597 598 /** 599 * When the state of in-call changes, this is the first method to get called. It determines if 600 * the UI needs to be started or finished depending on the new state and does it. 601 */ 602 private InCallState startOrFinishUi(InCallState newState) { 603 Log.d(this, "startOrFinishUi: " + mInCallState + " -> " + newState); 604 605 // TODO: Consider a proper state machine implementation 606 607 // If the state isn't changing, we have already done any starting/stopping of 608 // activities in a previous pass...so lets cut out early 609 if (newState == mInCallState) { 610 return newState; 611 } 612 613 // A new Incoming call means that the user needs to be notified of the the call (since 614 // it wasn't them who initiated it). We do this through full screen notifications and 615 // happens indirectly through {@link StatusBarListener}. 616 // 617 // The process for incoming calls is as follows: 618 // 619 // 1) CallList - Announces existence of new INCOMING call 620 // 2) InCallPresenter - Gets announcement and calculates that the new InCallState 621 // - should be set to INCOMING. 622 // 3) InCallPresenter - This method is called to see if we need to start or finish 623 // the app given the new state. 624 // 4) StatusBarNotifier - Listens to InCallState changes. InCallPresenter calls 625 // StatusBarNotifier explicitly to issue a FullScreen Notification 626 // that will either start the InCallActivity or show the user a 627 // top-level notification dialog if the user is in an immersive app. 628 // That notification can also start the InCallActivity. 629 // 5) InCallActivity - Main activity starts up and at the end of its onCreate will 630 // call InCallPresenter::setActivity() to let the presenter 631 // know that start-up is complete. 632 // 633 // [ AND NOW YOU'RE IN THE CALL. voila! ] 634 // 635 // Our app is started using a fullScreen notification. We need to do this whenever 636 // we get an incoming call. 637 final boolean startStartupSequence = (InCallState.INCOMING == newState); 638 639 // A new outgoing call indicates that the user just now dialed a number and when that 640 // happens we need to display the screen immediateley. 641 // 642 // This is different from the incoming call sequence because we do not need to shock the 643 // user with a top-level notification. Just show the call UI normally. 644 final boolean showCallUi = (InCallState.OUTGOING == newState); 645 646 // TODO: Can we be suddenly in a call without it having been in the outgoing or incoming 647 // state? I havent seen that but if it can happen, the code below should be enabled. 648 // showCallUi |= (InCallState.INCALL && !isActivityStarted()); 649 650 // The only time that we have an instance of mInCallActivity and it isn't started is 651 // when it is being destroyed. In that case, lets avoid bringing up another instance of 652 // the activity. When it is finally destroyed, we double check if we should bring it back 653 // up so we aren't going to lose anything by avoiding a second startup here. 654 boolean activityIsFinishing = mInCallActivity != null && !isActivityStarted(); 655 if (activityIsFinishing) { 656 Log.i(this, "Undo the state change: " + newState + " -> " + mInCallState); 657 return mInCallState; 658 } 659 660 if (showCallUi) { 661 Log.i(this, "Start in call UI"); 662 showInCall(false /* showDialpad */, true /* newOutgoingCall */); 663 } else if (startStartupSequence) { 664 Log.i(this, "Start Full Screen in call UI"); 665 666 // We're about the bring up the in-call UI for an incoming call. If we still have 667 // dialogs up, we need to clear them out before showing incoming screen. 668 if (isActivityStarted()) { 669 mInCallActivity.dismissPendingDialogs(); 670 } 671 startUi(newState); 672 } else if (newState == InCallState.NO_CALLS) { 673 // The new state is the no calls state. Tear everything down. 674 attemptFinishActivity(); 675 attemptCleanup(); 676 } 677 678 return newState; 679 } 680 681 private void startUi(InCallState inCallState) { 682 final Call incomingCall = mCallList.getIncomingCall(); 683 final boolean isCallWaiting = (incomingCall != null && 684 incomingCall.getState() == Call.State.CALL_WAITING); 685 686 // If the screen is off, we need to make sure it gets turned on for incoming calls. 687 // This normally works just fine thanks to FLAG_TURN_SCREEN_ON but that only works 688 // when the activity is first created. Therefore, to ensure the screen is turned on 689 // for the call waiting case, we finish() the current activity and start a new one. 690 // There should be no jank from this since the screen is already off and will remain so 691 // until our new activity is up. 692 if (mProximitySensor.isScreenReallyOff() && isCallWaiting) { 693 if (isActivityStarted()) { 694 mInCallActivity.finish(); 695 } 696 mInCallActivity = null; 697 } 698 699 mStatusBarNotifier.updateNotification(inCallState, mCallList); 700 } 701 702 /** 703 * Checks to see if both the UI is gone and the service is disconnected. If so, tear it all 704 * down. 705 */ 706 private void attemptCleanup() { 707 boolean shouldCleanup = (mInCallActivity == null && !mServiceConnected && 708 mInCallState == InCallState.NO_CALLS); 709 Log.i(this, "attemptCleanup? " + shouldCleanup); 710 711 if (shouldCleanup) { 712 mIsActivityPreviouslyStarted = false; 713 714 // blow away stale contact info so that we get fresh data on 715 // the next set of calls 716 if (mContactInfoCache != null) { 717 mContactInfoCache.clearCache(); 718 } 719 mContactInfoCache = null; 720 721 if (mProximitySensor != null) { 722 removeListener(mProximitySensor); 723 mProximitySensor.tearDown(); 724 } 725 mProximitySensor = null; 726 727 mAudioModeProvider = null; 728 729 if (mStatusBarNotifier != null) { 730 removeListener(mStatusBarNotifier); 731 } 732 mStatusBarNotifier = null; 733 734 if (mCallList != null) { 735 mCallList.removeListener(this); 736 } 737 mCallList = null; 738 739 mContext = null; 740 mInCallActivity = null; 741 742 mListeners.clear(); 743 mIncomingCallListeners.clear(); 744 745 Log.d(this, "Finished InCallPresenter.CleanUp"); 746 } 747 } 748 749 private void showInCall(boolean showDialpad, boolean newOutgoingCall) { 750 mContext.startActivity(getInCallIntent(showDialpad, newOutgoingCall)); 751 } 752 753 public Intent getInCallIntent(boolean showDialpad, boolean newOutgoingCall) { 754 final Intent intent = new Intent(Intent.ACTION_MAIN, null); 755 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 756 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 757 | Intent.FLAG_ACTIVITY_NO_USER_ACTION); 758 intent.setClass(mContext, InCallActivity.class); 759 if (showDialpad) { 760 intent.putExtra(InCallActivity.SHOW_DIALPAD_EXTRA, true); 761 } 762 763 intent.putExtra(InCallActivity.NEW_OUTGOING_CALL, newOutgoingCall); 764 return intent; 765 } 766 767 /** 768 * Private constructor. Must use getInstance() to get this singleton. 769 */ 770 private InCallPresenter() { 771 } 772 773 /** 774 * All the main states of InCallActivity. 775 */ 776 public enum InCallState { 777 // InCall Screen is off and there are no calls 778 NO_CALLS, 779 780 // Incoming-call screen is up 781 INCOMING, 782 783 // In-call experience is showing 784 INCALL, 785 786 // User is dialing out 787 OUTGOING; 788 789 public boolean isIncoming() { 790 return (this == INCOMING); 791 } 792 793 public boolean isConnectingOrConnected() { 794 return (this == INCOMING || 795 this == OUTGOING || 796 this == INCALL); 797 } 798 } 799 800 /** 801 * Interface implemented by classes that need to know about the InCall State. 802 */ 803 public interface InCallStateListener { 804 // TODO: Enhance state to contain the call objects instead of passing CallList 805 public void onStateChange(InCallState state, CallList callList); 806 } 807 808 public interface IncomingCallListener { 809 public void onIncomingCall(InCallState state, Call call); 810 } 811} 812