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