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.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 
    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
    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 
    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 
    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 
    283     public static synchronized InCallPresenter getInstance() {
    284         if (sInCallPresenter == null) {
    285             sInCallPresenter = new InCallPresenter();
    286         }
    287         return sInCallPresenter;
    288     }
    289 
    290     @NeededForTesting
    291     static synchronized void setInstance(InCallPresenter inCallPresenter) {
    292         sInCallPresenter = inCallPresenter;
    293     }
    294 
    295     public InCallState getInCallState() {
    296         return mInCallState;
    297     }
    298 
    299     public CallList getCallList() {
    300         return mCallList;
    301     }
    302 
    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      */
    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 
    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      */
    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      */
    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      */
    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 
    503     public void onBringToForeground(boolean showDialpad) {
    504         Log.i(this, "Bringing UI to foreground.");
    505         bringToForeground(showDialpad);
    506     }
    507 
    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 
    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      */
    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 
    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 
    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
    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
    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
    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
    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
    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      */
    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 
    753     public boolean isBoundAndWaitingForOutgoingCall() {
    754         return mBoundAndWaitingForOutgoingCall;
    755     }
    756 
    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
    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 
    779     public void onShrinkAnimationComplete() {
    780         if (mAwaitingCallListUpdate) {
    781             onCallListChange(mCallList);
    782         }
    783     }
    784 
    785     public void addIncomingCallListener(IncomingCallListener listener) {
    786         Preconditions.checkNotNull(listener);
    787         mIncomingCallListeners.add(listener);
    788     }
    789 
    790     public void removeIncomingCallListener(IncomingCallListener listener) {
    791         if (listener != null) {
    792             mIncomingCallListeners.remove(listener);
    793         }
    794     }
    795 
    796     public void addListener(InCallStateListener listener) {
    797         Preconditions.checkNotNull(listener);
    798         mListeners.add(listener);
    799     }
    800 
    801     public void removeListener(InCallStateListener listener) {
    802         if (listener != null) {
    803             mListeners.remove(listener);
    804         }
    805     }
    806 
    807     public void addDetailsListener(InCallDetailsListener listener) {
    808         Preconditions.checkNotNull(listener);
    809         mDetailsListeners.add(listener);
    810     }
    811 
    812     public void removeDetailsListener(InCallDetailsListener listener) {
    813         if (listener != null) {
    814             mDetailsListeners.remove(listener);
    815         }
    816     }
    817 
    818     public void addCanAddCallListener(CanAddCallListener listener) {
    819         Preconditions.checkNotNull(listener);
    820         mCanAddCallListeners.add(listener);
    821     }
    822 
    823     public void removeCanAddCallListener(CanAddCallListener listener) {
    824         if (listener != null) {
    825             mCanAddCallListeners.remove(listener);
    826         }
    827     }
    828 
    829     public void addOrientationListener(InCallOrientationListener listener) {
    830         Preconditions.checkNotNull(listener);
    831         mOrientationListeners.add(listener);
    832     }
    833 
    834     public void removeOrientationListener(InCallOrientationListener listener) {
    835         if (listener != null) {
    836             mOrientationListeners.remove(listener);
    837         }
    838     }
    839 
    840     public void addInCallEventListener(InCallEventListener listener) {
    841         Preconditions.checkNotNull(listener);
    842         mInCallEventListeners.add(listener);
    843     }
    844 
    845     public void removeInCallEventListener(InCallEventListener listener) {
    846         if (listener != null) {
    847             mInCallEventListeners.remove(listener);
    848         }
    849     }
    850 
    851     public ProximitySensor getProximitySensor() {
    852         return mProximitySensor;
    853     }
    854 
    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 
    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      */
    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      */
    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      */
    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 
    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 
    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*/
    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      */
    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      */
    993     public boolean isActivityStarted() {
    994         return (mInCallActivity != null &&
    995                 !mInCallActivity.isDestroyed() &&
    996                 !mInCallActivity.isFinishing());
    997     }
    998 
    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      */
   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*/
   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      */
   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 
   1064     public void addInCallUiListener(InCallUiListener listener) {
   1065         mInCallUiListeners.add(listener);
   1066     }
   1067 
   1068     public boolean removeInCallUiListener(InCallUiListener listener) {
   1069         return mInCallUiListeners.remove(listener);
   1070     }
   1071 
   1072     /*package*/
   1073     void onActivityStarted() {
   1074         Log.d(this, "onActivityStarted");
   1075         notifyVideoPauseController(true);
   1076     }
   1077 
   1078     /*package*/
   1079     void onActivityStopped() {
   1080         Log.d(this, "onActivityStopped");
   1081         notifyVideoPauseController(false);
   1082     }
   1083 
   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      */
   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 
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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 
   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      */
   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 
   1554     public void showInCall(final boolean showDialpad, final boolean newOutgoingCall) {
   1555         Log.i(this, "Showing InCallActivity");
   1556         mContext.startActivity(getInCallIntent(showDialpad, newOutgoingCall));
   1557     }
   1558 
   1559     public void onServiceBind() {
   1560         mServiceBound = true;
   1561     }
   1562 
   1563     public void onServiceUnbind() {
   1564         InCallPresenter.getInstance().setBoundAndWaitingForOutgoingCall(false, null);
   1565         mServiceBound = false;
   1566     }
   1567 
   1568     public boolean isServiceBound() {
   1569         return mServiceBound;
   1570     }
   1571 
   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 
   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      */
   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      */
   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      */
   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 
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   1778     public MaterialPalette getThemeColors() {
   1779         return mThemeColors;
   1780     }
   1781 
   1782     private MaterialPalette getColorsFromCall(Call call) {
   1783         if (call == null) {
   1784             return getColorsFromPhoneAccountHandle(mPendingPhoneAccountHandle);
   1785         } else {
   1786             return getColorsFromPhoneAccountHandle(call.getAccountHandle());
   1787         }
   1788     }
   1789 
   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      */
   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      */
   1823     public TelephonyManager getTelephonyManager() {
   1824         return mTelephonyManager;
   1825     }
   1826 
   1827     InCallActivity getActivity() {
   1828         return mInCallActivity;
   1829     }
   1830 
   1831     AnswerPresenter getAnswerPresenter() {
   1832         return mAnswerPresenter;
   1833     }
   1834 
   1835     ExternalCallNotifier getExternalCallNotifier() {
   1836         return mExternalCallNotifier;
   1837     }
   1838 
   1839     /**
   1840      * Private constructor. Must use getInstance() to get this singleton.
   1841      */
   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 
   1868         public boolean isIncoming() {
   1869             return (this == INCOMING);
   1870         }
   1871 
   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
   1884         public void onStateChange(InCallState oldState, InCallState newState, CallList callList);
   1885     }
   1886 
   1887     public interface IncomingCallListener {
   1888         public void onIncomingCall(InCallState oldState, InCallState newState, Call call);
   1889     }
   1890 
   1891     public interface CanAddCallListener {
   1892         public void onCanAddCallChanged(boolean canAddCall);
   1893     }
   1894 
   1895     public interface InCallDetailsListener {
   1896         public void onDetailsChanged(Call call, android.telecom.Call.Details details);
   1897     }
   1898 
   1899     public interface InCallOrientationListener {
   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 {
   1908         public void onFullscreenModeChanged(boolean isFullscreenMode);
   1909         public void onSecondaryCallerInfoVisibilityChanged(boolean isVisible, int height);
   1910     }
   1911 
   1912     public interface InCallUiListener {
   1913         void onUiShowing(boolean showing);
   1914     }
   1915 }
   1916