• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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