summaryrefslogtreecommitdiff
path: root/InCallUI/src/com/android/incallui/InCallPresenter.java
diff options
context:
space:
mode:
Diffstat (limited to 'InCallUI/src/com/android/incallui/InCallPresenter.java')
-rw-r--r--InCallUI/src/com/android/incallui/InCallPresenter.java1938
1 files changed, 0 insertions, 1938 deletions
diff --git a/InCallUI/src/com/android/incallui/InCallPresenter.java b/InCallUI/src/com/android/incallui/InCallPresenter.java
deleted file mode 100644
index 0103f61ed..000000000
--- a/InCallUI/src/com/android/incallui/InCallPresenter.java
+++ /dev/null
@@ -1,1938 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.incallui;
-
-import com.google.common.base.Preconditions;
-
-import android.app.ActivityManager.TaskDescription;
-import android.app.FragmentManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.graphics.Point;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.provider.CallLog;
-import android.telecom.DisconnectCause;
-import android.telecom.PhoneAccount;
-import android.telecom.PhoneAccountHandle;
-import android.telecom.TelecomManager;
-import android.telecom.VideoProfile;
-import android.telephony.PhoneStateListener;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.view.View;
-import android.view.Window;
-import android.view.WindowManager;
-
-import com.android.contacts.common.GeoUtil;
-import com.android.contacts.common.compat.CallSdkCompat;
-import com.android.contacts.common.compat.CompatUtils;
-import com.android.contacts.common.compat.telecom.TelecomManagerCompat;
-import com.android.contacts.common.interactions.TouchPointManager;
-import com.android.contacts.common.testing.NeededForTesting;
-import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
-import com.android.dialer.R;
-import com.android.dialer.calllog.CallLogAsyncTaskUtil;
-import com.android.dialer.calllog.CallLogAsyncTaskUtil.OnCallLogQueryFinishedListener;
-import com.android.dialer.database.FilteredNumberAsyncQueryHandler;
-import com.android.dialer.database.FilteredNumberAsyncQueryHandler.OnCheckBlockedListener;
-import com.android.dialer.filterednumber.FilteredNumbersUtil;
-import com.android.dialer.logging.InteractionEvent;
-import com.android.dialer.logging.Logger;
-import com.android.dialer.util.TelecomUtil;
-import com.android.incallui.spam.SpamCallListListener;
-import com.android.incallui.util.TelecomCallUtil;
-import com.android.incalluibind.ObjectFactory;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Takes updates from the CallList and notifies the InCallActivity (UI)
- * of the changes.
- * Responsible for starting the activity for a new call and finishing the activity when all calls
- * are disconnected.
- * Creates and manages the in-call state and provides a listener pattern for the presenters
- * that want to listen in on the in-call state changes.
- * TODO: This class has become more of a state machine at this point. Consider renaming.
- */
-public class InCallPresenter implements CallList.Listener,
- CircularRevealFragment.OnCircularRevealCompleteListener,
- InCallVideoCallCallbackNotifier.SessionModificationListener {
-
- private static final String EXTRA_FIRST_TIME_SHOWN =
- "com.android.incallui.intent.extra.FIRST_TIME_SHOWN";
-
- private static final long BLOCK_QUERY_TIMEOUT_MS = 1000;
-
- private static final Bundle EMPTY_EXTRAS = new Bundle();
-
- private static InCallPresenter sInCallPresenter;
-
- /**
- * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
- * load factor before resizing, 1 means we only expect a single thread to
- * access the map so make only a single shard
- */
- private final Set<InCallStateListener> mListeners = Collections.newSetFromMap(
- new ConcurrentHashMap<InCallStateListener, Boolean>(8, 0.9f, 1));
- private final List<IncomingCallListener> mIncomingCallListeners = new CopyOnWriteArrayList<>();
- private final Set<InCallDetailsListener> mDetailsListeners = Collections.newSetFromMap(
- new ConcurrentHashMap<InCallDetailsListener, Boolean>(8, 0.9f, 1));
- private final Set<CanAddCallListener> mCanAddCallListeners = Collections.newSetFromMap(
- new ConcurrentHashMap<CanAddCallListener, Boolean>(8, 0.9f, 1));
- private final Set<InCallUiListener> mInCallUiListeners = Collections.newSetFromMap(
- new ConcurrentHashMap<InCallUiListener, Boolean>(8, 0.9f, 1));
- private final Set<InCallOrientationListener> mOrientationListeners = Collections.newSetFromMap(
- new ConcurrentHashMap<InCallOrientationListener, Boolean>(8, 0.9f, 1));
- private final Set<InCallEventListener> mInCallEventListeners = Collections.newSetFromMap(
- new ConcurrentHashMap<InCallEventListener, Boolean>(8, 0.9f, 1));
-
- private AudioModeProvider mAudioModeProvider;
- private StatusBarNotifier mStatusBarNotifier;
- private ExternalCallNotifier mExternalCallNotifier;
- private ContactInfoCache mContactInfoCache;
- private Context mContext;
- private CallList mCallList;
- private ExternalCallList mExternalCallList;
- private InCallActivity mInCallActivity;
- private InCallState mInCallState = InCallState.NO_CALLS;
- private ProximitySensor mProximitySensor;
- private boolean mServiceConnected = false;
- private boolean mAccountSelectionCancelled = false;
- private InCallCameraManager mInCallCameraManager = null;
- private AnswerPresenter mAnswerPresenter = new AnswerPresenter();
- private FilteredNumberAsyncQueryHandler mFilteredQueryHandler;
- private CallList.Listener mSpamCallListListener;
-
- /**
- * Whether or not we are currently bound and waiting for Telecom to send us a new call.
- */
- private boolean mBoundAndWaitingForOutgoingCall;
-
- /**
- * If there is no actual call currently in the call list, this will be used as a fallback
- * to determine the theme color for InCallUI.
- */
- private PhoneAccountHandle mPendingPhoneAccountHandle;
-
- /**
- * Determines if the InCall UI is in fullscreen mode or not.
- */
- private boolean mIsFullScreen = false;
-
- private final android.telecom.Call.Callback mCallCallback = new android.telecom.Call.Callback() {
- @Override
- public void onPostDialWait(android.telecom.Call telecomCall,
- String remainingPostDialSequence) {
- final Call call = mCallList.getCallByTelecomCall(telecomCall);
- if (call == null) {
- Log.w(this, "Call not found in call list: " + telecomCall);
- return;
- }
- onPostDialCharWait(call.getId(), remainingPostDialSequence);
- }
-
- @Override
- public void onDetailsChanged(android.telecom.Call telecomCall,
- android.telecom.Call.Details details) {
- final Call call = mCallList.getCallByTelecomCall(telecomCall);
- if (call == null) {
- Log.w(this, "Call not found in call list: " + telecomCall);
- return;
- }
- for (InCallDetailsListener listener : mDetailsListeners) {
- listener.onDetailsChanged(call, details);
- }
- }
-
- @Override
- public void onConferenceableCallsChanged(android.telecom.Call telecomCall,
- List<android.telecom.Call> conferenceableCalls) {
- Log.i(this, "onConferenceableCallsChanged: " + telecomCall);
- onDetailsChanged(telecomCall, telecomCall.getDetails());
- }
- };
-
- private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
- public void onCallStateChanged(int state, String incomingNumber) {
- if (state == TelephonyManager.CALL_STATE_RINGING) {
- if (FilteredNumbersUtil.hasRecentEmergencyCall(mContext)) {
- return;
- }
- // Check if the number is blocked, to silence the ringer.
- String countryIso = GeoUtil.getCurrentCountryIso(mContext);
- mFilteredQueryHandler.isBlockedNumber(
- mOnCheckBlockedListener, incomingNumber, countryIso);
- }
- }
- };
-
- private final OnCheckBlockedListener mOnCheckBlockedListener = new OnCheckBlockedListener() {
- @Override
- public void onCheckComplete(final Integer id) {
- if (id != null) {
- // Silence the ringer now to prevent ringing and vibration before the call is
- // terminated when Telecom attempts to add it.
- TelecomUtil.silenceRinger(mContext);
- }
- }
- };
-
- /**
- * Observes the CallLog to delete the call log entry for the blocked call after it is added.
- * Times out if too much time has passed.
- */
- private class BlockedNumberContentObserver extends ContentObserver {
- private static final int TIMEOUT_MS = 5000;
-
- private Handler mHandler;
- private String mNumber;
- private long mTimeAddedMs;
-
- private Runnable mTimeoutRunnable = new Runnable() {
- @Override
- public void run() {
- unregister();
- }
- };
-
- public BlockedNumberContentObserver(Handler handler, String number, long timeAddedMs) {
- super(handler);
-
- mHandler = handler;
- mNumber = number;
- mTimeAddedMs = timeAddedMs;
- }
-
- @Override
- public void onChange(boolean selfChange) {
- CallLogAsyncTaskUtil.deleteBlockedCall(mContext, mNumber, mTimeAddedMs,
- new OnCallLogQueryFinishedListener() {
- @Override
- public void onQueryFinished(boolean hasEntry) {
- if (mContext != null && hasEntry) {
- unregister();
- }
- }
- });
- }
-
- public void register() {
- if (mContext != null) {
- mContext.getContentResolver().registerContentObserver(
- CallLog.CONTENT_URI, true, this);
- mHandler.postDelayed(mTimeoutRunnable, TIMEOUT_MS);
- }
- }
-
- private void unregister() {
- if (mContext != null) {
- mHandler.removeCallbacks(mTimeoutRunnable);
- mContext.getContentResolver().unregisterContentObserver(this);
- }
- }
- };
-
- /**
- * Is true when the activity has been previously started. Some code needs to know not just if
- * the activity is currently up, but if it had been previously shown in foreground for this
- * in-call session (e.g., StatusBarNotifier). This gets reset when the session ends in the
- * tear-down method.
- */
- private boolean mIsActivityPreviouslyStarted = false;
-
- /**
- * Whether or not InCallService is bound to Telecom.
- */
- private boolean mServiceBound = false;
-
- /**
- * When configuration changes Android kills the current activity and starts a new one.
- * The flag is used to check if full clean up is necessary (activity is stopped and new
- * activity won't be started), or if a new activity will be started right after the current one
- * is destroyed, and therefore no need in release all resources.
- */
- private boolean mIsChangingConfigurations = false;
-
- /** Display colors for the UI. Consists of a primary color and secondary (darker) color */
- private MaterialPalette mThemeColors;
-
- private TelecomManager mTelecomManager;
- private TelephonyManager mTelephonyManager;
-
- public static synchronized InCallPresenter getInstance() {
- if (sInCallPresenter == null) {
- sInCallPresenter = new InCallPresenter();
- }
- return sInCallPresenter;
- }
-
- @NeededForTesting
- static synchronized void setInstance(InCallPresenter inCallPresenter) {
- sInCallPresenter = inCallPresenter;
- }
-
- public InCallState getInCallState() {
- return mInCallState;
- }
-
- public CallList getCallList() {
- return mCallList;
- }
-
- public void setUp(Context context,
- CallList callList,
- ExternalCallList externalCallList,
- AudioModeProvider audioModeProvider,
- StatusBarNotifier statusBarNotifier,
- ExternalCallNotifier externalCallNotifier,
- ContactInfoCache contactInfoCache,
- ProximitySensor proximitySensor) {
- if (mServiceConnected) {
- Log.i(this, "New service connection replacing existing one.");
- // retain the current resources, no need to create new ones.
- Preconditions.checkState(context == mContext);
- Preconditions.checkState(callList == mCallList);
- Preconditions.checkState(audioModeProvider == mAudioModeProvider);
- return;
- }
-
- Preconditions.checkNotNull(context);
- mContext = context;
-
- mContactInfoCache = contactInfoCache;
-
- mStatusBarNotifier = statusBarNotifier;
- mExternalCallNotifier = externalCallNotifier;
- addListener(mStatusBarNotifier);
-
- mAudioModeProvider = audioModeProvider;
-
- mProximitySensor = proximitySensor;
- addListener(mProximitySensor);
-
- addIncomingCallListener(mAnswerPresenter);
- addInCallUiListener(mAnswerPresenter);
-
- mCallList = callList;
- mExternalCallList = externalCallList;
- externalCallList.addExternalCallListener(mExternalCallNotifier);
-
- // This only gets called by the service so this is okay.
- mServiceConnected = true;
-
- // The final thing we do in this set up is add ourselves as a listener to CallList. This
- // will kick off an update and the whole process can start.
- mCallList.addListener(this);
-
- // Create spam call list listener and add it to the list of listeners
- mSpamCallListListener = new SpamCallListListener(context);
- mCallList.addListener(mSpamCallListListener);
-
- VideoPauseController.getInstance().setUp(this);
- InCallVideoCallCallbackNotifier.getInstance().addSessionModificationListener(this);
-
- mFilteredQueryHandler = new FilteredNumberAsyncQueryHandler(context.getContentResolver());
- mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
- mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
- mCallList.setExtendedCallInfoService(
- com.android.dialerbind.ObjectFactory.newExtendedCallInfoService(context));
-
- Log.d(this, "Finished InCallPresenter.setUp");
- }
-
- /**
- * Called when the telephony service has disconnected from us. This will happen when there are
- * no more active calls. However, we may still want to continue showing the UI for
- * certain cases like showing "Call Ended".
- * What we really want is to wait for the activity and the service to both disconnect before we
- * tear things down. This method sets a serviceConnected boolean and calls a secondary method
- * that performs the aforementioned logic.
- */
- public void tearDown() {
- Log.d(this, "tearDown");
- mCallList.clearOnDisconnect();
-
- mServiceConnected = false;
- attemptCleanup();
-
- mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
- VideoPauseController.getInstance().tearDown();
- InCallVideoCallCallbackNotifier.getInstance().removeSessionModificationListener(this);
- }
-
- private void attemptFinishActivity() {
- final boolean doFinish = (mInCallActivity != null && isActivityStarted());
- Log.i(this, "Hide in call UI: " + doFinish);
- if (doFinish) {
- mInCallActivity.setExcludeFromRecents(true);
- mInCallActivity.finish();
-
- if (mAccountSelectionCancelled) {
- // This finish is a result of account selection cancellation
- // do not include activity ending transition
- mInCallActivity.overridePendingTransition(0, 0);
- }
- }
- }
-
- /**
- * Called when the UI begins, and starts the callstate callbacks if necessary.
- */
- public void setActivity(InCallActivity inCallActivity) {
- if (inCallActivity == null) {
- throw new IllegalArgumentException("registerActivity cannot be called with null");
- }
- if (mInCallActivity != null && mInCallActivity != inCallActivity) {
- Log.w(this, "Setting a second activity before destroying the first.");
- }
- updateActivity(inCallActivity);
- }
-
- /**
- * Called when the UI ends. Attempts to tear down everything if necessary. See
- * {@link #tearDown()} for more insight on the tear-down process.
- */
- public void unsetActivity(InCallActivity inCallActivity) {
- if (inCallActivity == null) {
- throw new IllegalArgumentException("unregisterActivity cannot be called with null");
- }
- if (mInCallActivity == null) {
- Log.i(this, "No InCallActivity currently set, no need to unset.");
- return;
- }
- if (mInCallActivity != inCallActivity) {
- Log.w(this, "Second instance of InCallActivity is trying to unregister when another"
- + " instance is active. Ignoring.");
- return;
- }
- updateActivity(null);
- }
-
- /**
- * Updates the current instance of {@link InCallActivity} with the provided one. If a
- * {@code null} activity is provided, it means that the activity was finished and we should
- * attempt to cleanup.
- */
- private void updateActivity(InCallActivity inCallActivity) {
- boolean updateListeners = false;
- boolean doAttemptCleanup = false;
-
- if (inCallActivity != null) {
- if (mInCallActivity == null) {
- updateListeners = true;
- Log.i(this, "UI Initialized");
- } else {
- // since setActivity is called onStart(), it can be called multiple times.
- // This is fine and ignorable, but we do not want to update the world every time
- // this happens (like going to/from background) so we do not set updateListeners.
- }
-
- mInCallActivity = inCallActivity;
- mInCallActivity.setExcludeFromRecents(false);
-
- // By the time the UI finally comes up, the call may already be disconnected.
- // If that's the case, we may need to show an error dialog.
- if (mCallList != null && mCallList.getDisconnectedCall() != null) {
- maybeShowErrorDialogOnDisconnect(mCallList.getDisconnectedCall());
- }
-
- // When the UI comes up, we need to first check the in-call state.
- // If we are showing NO_CALLS, that means that a call probably connected and
- // then immediately disconnected before the UI was able to come up.
- // If we dont have any calls, start tearing down the UI instead.
- // NOTE: This code relies on {@link #mInCallActivity} being set so we run it after
- // it has been set.
- if (mInCallState == InCallState.NO_CALLS) {
- Log.i(this, "UI Initialized, but no calls left. shut down.");
- attemptFinishActivity();
- return;
- }
- } else {
- Log.i(this, "UI Destroyed");
- updateListeners = true;
- mInCallActivity = null;
-
- // We attempt cleanup for the destroy case but only after we recalculate the state
- // to see if we need to come back up or stay shut down. This is why we do the
- // cleanup after the call to onCallListChange() instead of directly here.
- doAttemptCleanup = true;
- }
-
- // Messages can come from the telephony layer while the activity is coming up
- // and while the activity is going down. So in both cases we need to recalculate what
- // state we should be in after they complete.
- // Examples: (1) A new incoming call could come in and then get disconnected before
- // the activity is created.
- // (2) All calls could disconnect and then get a new incoming call before the
- // activity is destroyed.
- //
- // b/1122139 - We previously had a check for mServiceConnected here as well, but there are
- // cases where we need to recalculate the current state even if the service in not
- // connected. In particular the case where startOrFinish() is called while the app is
- // already finish()ing. In that case, we skip updating the state with the knowledge that
- // we will check again once the activity has finished. That means we have to recalculate the
- // state here even if the service is disconnected since we may not have finished a state
- // transition while finish()ing.
- if (updateListeners) {
- onCallListChange(mCallList);
- }
-
- if (doAttemptCleanup) {
- attemptCleanup();
- }
- }
-
- private boolean mAwaitingCallListUpdate = false;
-
- public void onBringToForeground(boolean showDialpad) {
- Log.i(this, "Bringing UI to foreground.");
- bringToForeground(showDialpad);
- }
-
- public void onCallAdded(final android.telecom.Call call) {
- LatencyReport latencyReport = new LatencyReport(call);
- if (shouldAttemptBlocking(call)) {
- maybeBlockCall(call, latencyReport);
- } else {
- latencyReport.onCallBlockingDone();
- if (call.getDetails()
- .hasProperty(CallSdkCompat.Details.PROPERTY_IS_EXTERNAL_CALL)) {
- mExternalCallList.onCallAdded(call);
- } else {
- mCallList.onCallAdded(call, latencyReport);
- }
- }
-
- // Since a call has been added we are no longer waiting for Telecom to send us a call.
- setBoundAndWaitingForOutgoingCall(false, null);
- call.registerCallback(mCallCallback);
- }
-
- private boolean shouldAttemptBlocking(android.telecom.Call call) {
- if (call.getState() != android.telecom.Call.STATE_RINGING) {
- return false;
- }
- if (TelecomCallUtil.isEmergencyCall(call)) {
- Log.i(this, "Not attempting to block incoming emergency call");
- return false;
- }
- if (FilteredNumbersUtil.hasRecentEmergencyCall(mContext)) {
- Log.i(this, "Not attempting to block incoming call due to recent emergency call");
- return false;
- }
- if (call.getDetails().hasProperty(CallSdkCompat.Details.PROPERTY_IS_EXTERNAL_CALL)) {
- return false;
- }
-
- return true;
- }
-
- /**
- * Checks whether a call should be blocked, and blocks it if so. Otherwise, it adds the call
- * to the CallList so it can proceed as normal. There is a timeout, so if the function for
- * checking whether a function is blocked does not return in a reasonable time, we proceed
- * with adding the call anyways.
- */
- private void maybeBlockCall(final android.telecom.Call call,
- final LatencyReport latencyReport) {
- final String countryIso = GeoUtil.getCurrentCountryIso(mContext);
- final String number = TelecomCallUtil.getNumber(call);
- final long timeAdded = System.currentTimeMillis();
-
- // Though AtomicBoolean's can be scary, don't fear, as in this case it is only used on the
- // main UI thread. It is needed so we can change its value within different scopes, since
- // that cannot be done with a final boolean.
- final AtomicBoolean hasTimedOut = new AtomicBoolean(false);
-
- final Handler handler = new Handler();
-
- // Proceed if the query is slow; the call may still be blocked after the query returns.
- final Runnable runnable = new Runnable() {
- public void run() {
- hasTimedOut.set(true);
- latencyReport.onCallBlockingDone();
- mCallList.onCallAdded(call, latencyReport);
- }
- };
- handler.postDelayed(runnable, BLOCK_QUERY_TIMEOUT_MS);
-
- OnCheckBlockedListener onCheckBlockedListener = new OnCheckBlockedListener() {
- @Override
- public void onCheckComplete(final Integer id) {
- if (!hasTimedOut.get()) {
- handler.removeCallbacks(runnable);
- }
- if (id == null) {
- if (!hasTimedOut.get()) {
- latencyReport.onCallBlockingDone();
- mCallList.onCallAdded(call, latencyReport);
- }
- } else {
- Log.i(this, "Rejecting incoming call from blocked number");
- call.reject(false, null);
- Logger.logInteraction(InteractionEvent.CALL_BLOCKED);
-
- mFilteredQueryHandler.incrementFilteredCount(id);
-
- // Register observer to update the call log.
- // BlockedNumberContentObserver will unregister after successful log or timeout.
- BlockedNumberContentObserver contentObserver =
- new BlockedNumberContentObserver(new Handler(), number, timeAdded);
- contentObserver.register();
- }
- }
- };
-
- final boolean success = mFilteredQueryHandler.isBlockedNumber(
- onCheckBlockedListener, number, countryIso);
- if (!success) {
- Log.d(this, "checkForBlockedCall: invalid number, skipping block checking");
- if (!hasTimedOut.get()) {
- handler.removeCallbacks(runnable);
-
- latencyReport.onCallBlockingDone();
- mCallList.onCallAdded(call, latencyReport);
- }
- }
- }
-
- public void onCallRemoved(android.telecom.Call call) {
- if (call.getDetails()
- .hasProperty(CallSdkCompat.Details.PROPERTY_IS_EXTERNAL_CALL)) {
- mExternalCallList.onCallRemoved(call);
- } else {
- mCallList.onCallRemoved(call);
- call.unregisterCallback(mCallCallback);
- }
- }
-
- public void onCanAddCallChanged(boolean canAddCall) {
- for (CanAddCallListener listener : mCanAddCallListeners) {
- listener.onCanAddCallChanged(canAddCall);
- }
- }
-
- /**
- * Called when there is a change to the call list.
- * Sets the In-Call state for the entire in-call app based on the information it gets from
- * CallList. Dispatches the in-call state to all listeners. Can trigger the creation or
- * destruction of the UI based on the states that is calculates.
- */
- @Override
- public void onCallListChange(CallList callList) {
- if (mInCallActivity != null && mInCallActivity.getCallCardFragment() != null &&
- mInCallActivity.getCallCardFragment().isAnimating()) {
- mAwaitingCallListUpdate = true;
- return;
- }
- if (callList == null) {
- return;
- }
-
- mAwaitingCallListUpdate = false;
-
- InCallState newState = getPotentialStateFromCallList(callList);
- InCallState oldState = mInCallState;
- Log.d(this, "onCallListChange oldState= " + oldState + " newState=" + newState);
- newState = startOrFinishUi(newState);
- Log.d(this, "onCallListChange newState changed to " + newState);
-
- // Set the new state before announcing it to the world
- Log.i(this, "Phone switching state: " + oldState + " -> " + newState);
- mInCallState = newState;
-
- // notify listeners of new state
- for (InCallStateListener listener : mListeners) {
- Log.d(this, "Notify " + listener + " of state " + mInCallState.toString());
- listener.onStateChange(oldState, mInCallState, callList);
- }
-
- if (isActivityStarted()) {
- final boolean hasCall = callList.getActiveOrBackgroundCall() != null ||
- callList.getOutgoingCall() != null;
- mInCallActivity.dismissKeyguard(hasCall);
- }
- }
-
- /**
- * Called when there is a new incoming call.
- *
- * @param call
- */
- @Override
- public void onIncomingCall(Call call) {
- InCallState newState = startOrFinishUi(InCallState.INCOMING);
- InCallState oldState = mInCallState;
-
- Log.i(this, "Phone switching state: " + oldState + " -> " + newState);
- mInCallState = newState;
-
- for (IncomingCallListener listener : mIncomingCallListeners) {
- listener.onIncomingCall(oldState, mInCallState, call);
- }
- }
-
- @Override
- public void onUpgradeToVideo(Call call) {
- //NO-OP
- }
- /**
- * Called when a call becomes disconnected. Called everytime an existing call
- * changes from being connected (incoming/outgoing/active) to disconnected.
- */
- @Override
- public void onDisconnect(Call call) {
- maybeShowErrorDialogOnDisconnect(call);
-
- // We need to do the run the same code as onCallListChange.
- onCallListChange(mCallList);
-
- if (isActivityStarted()) {
- mInCallActivity.dismissKeyguard(false);
- }
-
- if (call.isEmergencyCall()) {
- FilteredNumbersUtil.recordLastEmergencyCallTime(mContext);
- }
- }
-
- @Override
- public void onUpgradeToVideoRequest(Call call, int videoState) {
- Log.d(this, "onUpgradeToVideoRequest call = " + call + " video state = " + videoState);
-
- if (call == null) {
- return;
- }
-
- call.setRequestedVideoState(videoState);
- }
-
- /**
- * Given the call list, return the state in which the in-call screen should be.
- */
- public InCallState getPotentialStateFromCallList(CallList callList) {
-
- InCallState newState = InCallState.NO_CALLS;
-
- if (callList == null) {
- return newState;
- }
- if (callList.getIncomingCall() != null) {
- newState = InCallState.INCOMING;
- } else if (callList.getWaitingForAccountCall() != null) {
- newState = InCallState.WAITING_FOR_ACCOUNT;
- } else if (callList.getPendingOutgoingCall() != null) {
- newState = InCallState.PENDING_OUTGOING;
- } else if (callList.getOutgoingCall() != null) {
- newState = InCallState.OUTGOING;
- } else if (callList.getActiveCall() != null ||
- callList.getBackgroundCall() != null ||
- callList.getDisconnectedCall() != null ||
- callList.getDisconnectingCall() != null) {
- newState = InCallState.INCALL;
- }
-
- if (newState == InCallState.NO_CALLS) {
- if (mBoundAndWaitingForOutgoingCall) {
- return InCallState.OUTGOING;
- }
- }
-
- return newState;
- }
-
- public boolean isBoundAndWaitingForOutgoingCall() {
- return mBoundAndWaitingForOutgoingCall;
- }
-
- public void setBoundAndWaitingForOutgoingCall(boolean isBound, PhoneAccountHandle handle) {
- // NOTE: It is possible for there to be a race and have handle become null before
- // the circular reveal starts. This should not cause any problems because CallCardFragment
- // should fallback to the actual call in the CallList at that point in time to determine
- // the theme color.
- Log.i(this, "setBoundAndWaitingForOutgoingCall: " + isBound);
- mBoundAndWaitingForOutgoingCall = isBound;
- mPendingPhoneAccountHandle = handle;
- if (isBound && mInCallState == InCallState.NO_CALLS) {
- mInCallState = InCallState.OUTGOING;
- }
- }
-
- @Override
- public void onCircularRevealComplete(FragmentManager fm) {
- if (mInCallActivity != null) {
- mInCallActivity.showCallCardFragment(true);
- mInCallActivity.getCallCardFragment().animateForNewOutgoingCall();
- CircularRevealFragment.endCircularReveal(mInCallActivity.getFragmentManager());
- }
- }
-
- public void onShrinkAnimationComplete() {
- if (mAwaitingCallListUpdate) {
- onCallListChange(mCallList);
- }
- }
-
- public void addIncomingCallListener(IncomingCallListener listener) {
- Preconditions.checkNotNull(listener);
- mIncomingCallListeners.add(listener);
- }
-
- public void removeIncomingCallListener(IncomingCallListener listener) {
- if (listener != null) {
- mIncomingCallListeners.remove(listener);
- }
- }
-
- public void addListener(InCallStateListener listener) {
- Preconditions.checkNotNull(listener);
- mListeners.add(listener);
- }
-
- public void removeListener(InCallStateListener listener) {
- if (listener != null) {
- mListeners.remove(listener);
- }
- }
-
- public void addDetailsListener(InCallDetailsListener listener) {
- Preconditions.checkNotNull(listener);
- mDetailsListeners.add(listener);
- }
-
- public void removeDetailsListener(InCallDetailsListener listener) {
- if (listener != null) {
- mDetailsListeners.remove(listener);
- }
- }
-
- public void addCanAddCallListener(CanAddCallListener listener) {
- Preconditions.checkNotNull(listener);
- mCanAddCallListeners.add(listener);
- }
-
- public void removeCanAddCallListener(CanAddCallListener listener) {
- if (listener != null) {
- mCanAddCallListeners.remove(listener);
- }
- }
-
- public void addOrientationListener(InCallOrientationListener listener) {
- Preconditions.checkNotNull(listener);
- mOrientationListeners.add(listener);
- }
-
- public void removeOrientationListener(InCallOrientationListener listener) {
- if (listener != null) {
- mOrientationListeners.remove(listener);
- }
- }
-
- public void addInCallEventListener(InCallEventListener listener) {
- Preconditions.checkNotNull(listener);
- mInCallEventListeners.add(listener);
- }
-
- public void removeInCallEventListener(InCallEventListener listener) {
- if (listener != null) {
- mInCallEventListeners.remove(listener);
- }
- }
-
- public ProximitySensor getProximitySensor() {
- return mProximitySensor;
- }
-
- public void handleAccountSelection(PhoneAccountHandle accountHandle, boolean setDefault) {
- if (mCallList != null) {
- Call call = mCallList.getWaitingForAccountCall();
- if (call != null) {
- String callId = call.getId();
- TelecomAdapter.getInstance().phoneAccountSelected(callId, accountHandle, setDefault);
- }
- }
- }
-
- public void cancelAccountSelection() {
- mAccountSelectionCancelled = true;
- if (mCallList != null) {
- Call call = mCallList.getWaitingForAccountCall();
- if (call != null) {
- String callId = call.getId();
- TelecomAdapter.getInstance().disconnectCall(callId);
- }
- }
- }
-
- /**
- * Hangs up any active or outgoing calls.
- */
- public void hangUpOngoingCall(Context context) {
- // By the time we receive this intent, we could be shut down and call list
- // could be null. Bail in those cases.
- if (mCallList == null) {
- if (mStatusBarNotifier == null) {
- // The In Call UI has crashed but the notification still stayed up. We should not
- // come to this stage.
- StatusBarNotifier.clearAllCallNotifications(context);
- }
- return;
- }
-
- Call call = mCallList.getOutgoingCall();
- if (call == null) {
- call = mCallList.getActiveOrBackgroundCall();
- }
-
- if (call != null) {
- TelecomAdapter.getInstance().disconnectCall(call.getId());
- call.setState(Call.State.DISCONNECTING);
- mCallList.onUpdate(call);
- }
- }
-
- /**
- * Answers any incoming call.
- */
- public void answerIncomingCall(Context context, int videoState) {
- // By the time we receive this intent, we could be shut down and call list
- // could be null. Bail in those cases.
- if (mCallList == null) {
- StatusBarNotifier.clearAllCallNotifications(context);
- return;
- }
-
- Call call = mCallList.getIncomingCall();
- if (call != null) {
- TelecomAdapter.getInstance().answerCall(call.getId(), videoState);
- showInCall(false, false/* newOutgoingCall */);
- }
- }
-
- /**
- * Declines any incoming call.
- */
- public void declineIncomingCall(Context context) {
- // By the time we receive this intent, we could be shut down and call list
- // could be null. Bail in those cases.
- if (mCallList == null) {
- StatusBarNotifier.clearAllCallNotifications(context);
- return;
- }
-
- Call call = mCallList.getIncomingCall();
- if (call != null) {
- TelecomAdapter.getInstance().rejectCall(call.getId(), false, null);
- }
- }
-
- public void acceptUpgradeRequest(int videoState, Context context) {
- Log.d(this, " acceptUpgradeRequest videoState " + videoState);
- // Bail if we have been shut down and the call list is null.
- if (mCallList == null) {
- StatusBarNotifier.clearAllCallNotifications(context);
- Log.e(this, " acceptUpgradeRequest mCallList is empty so returning");
- return;
- }
-
- Call call = mCallList.getVideoUpgradeRequestCall();
- if (call != null) {
- VideoProfile videoProfile = new VideoProfile(videoState);
- call.getVideoCall().sendSessionModifyResponse(videoProfile);
- call.setSessionModificationState(Call.SessionModificationState.NO_REQUEST);
- }
- }
-
- public void declineUpgradeRequest(Context context) {
- Log.d(this, " declineUpgradeRequest");
- // Bail if we have been shut down and the call list is null.
- if (mCallList == null) {
- StatusBarNotifier.clearAllCallNotifications(context);
- Log.e(this, " declineUpgradeRequest mCallList is empty so returning");
- return;
- }
-
- Call call = mCallList.getVideoUpgradeRequestCall();
- if (call != null) {
- VideoProfile videoProfile =
- new VideoProfile(call.getVideoState());
- call.getVideoCall().sendSessionModifyResponse(videoProfile);
- call.setSessionModificationState(Call.SessionModificationState.NO_REQUEST);
- }
- }
-
- /*package*/
- void declineUpgradeRequest() {
- // Pass mContext if InCallActivity is destroyed.
- // Ex: When user pressed back key while in active call and
- // then modify request is received followed by MT call.
- declineUpgradeRequest(mInCallActivity != null ? mInCallActivity : mContext);
- }
-
- /**
- * Returns true if the incall app is the foreground application.
- */
- public boolean isShowingInCallUi() {
- return (isActivityStarted() && mInCallActivity.isVisible());
- }
-
- /**
- * Returns true if the activity has been created and is running.
- * Returns true as long as activity is not destroyed or finishing. This ensures that we return
- * true even if the activity is paused (not in foreground).
- */
- public boolean isActivityStarted() {
- return (mInCallActivity != null &&
- !mInCallActivity.isDestroyed() &&
- !mInCallActivity.isFinishing());
- }
-
- public boolean isActivityPreviouslyStarted() {
- return mIsActivityPreviouslyStarted;
- }
-
- /**
- * Determines if the In-Call app is currently changing configuration.
- *
- * @return {@code true} if the In-Call app is changing configuration.
- */
- public boolean isChangingConfigurations() {
- return mIsChangingConfigurations;
- }
-
- /**
- * Tracks whether the In-Call app is currently in the process of changing configuration (i.e.
- * screen orientation).
- */
- /*package*/
- void updateIsChangingConfigurations() {
- mIsChangingConfigurations = false;
- if (mInCallActivity != null) {
- mIsChangingConfigurations = mInCallActivity.isChangingConfigurations();
- }
- Log.v(this, "updateIsChangingConfigurations = " + mIsChangingConfigurations);
- }
-
-
- /**
- * Called when the activity goes in/out of the foreground.
- */
- public void onUiShowing(boolean showing) {
- // We need to update the notification bar when we leave the UI because that
- // could trigger it to show again.
- if (mStatusBarNotifier != null) {
- mStatusBarNotifier.updateNotification(mInCallState, mCallList);
- }
-
- if (mProximitySensor != null) {
- mProximitySensor.onInCallShowing(showing);
- }
-
- Intent broadcastIntent = ObjectFactory.getUiReadyBroadcastIntent(mContext);
- if (broadcastIntent != null) {
- broadcastIntent.putExtra(EXTRA_FIRST_TIME_SHOWN, !mIsActivityPreviouslyStarted);
-
- if (showing) {
- Log.d(this, "Sending sticky broadcast: ", broadcastIntent);
- mContext.sendStickyBroadcast(broadcastIntent);
- } else {
- Log.d(this, "Removing sticky broadcast: ", broadcastIntent);
- mContext.removeStickyBroadcast(broadcastIntent);
- }
- }
-
- if (showing) {
- mIsActivityPreviouslyStarted = true;
- } else {
- updateIsChangingConfigurations();
- }
-
- for (InCallUiListener listener : mInCallUiListeners) {
- listener.onUiShowing(showing);
- }
- }
-
- public void addInCallUiListener(InCallUiListener listener) {
- mInCallUiListeners.add(listener);
- }
-
- public boolean removeInCallUiListener(InCallUiListener listener) {
- return mInCallUiListeners.remove(listener);
- }
-
- /*package*/
- void onActivityStarted() {
- Log.d(this, "onActivityStarted");
- notifyVideoPauseController(true);
- }
-
- /*package*/
- void onActivityStopped() {
- Log.d(this, "onActivityStopped");
- notifyVideoPauseController(false);
- }
-
- private void notifyVideoPauseController(boolean showing) {
- Log.d(this, "notifyVideoPauseController: mIsChangingConfigurations=" +
- mIsChangingConfigurations);
- if (!mIsChangingConfigurations) {
- VideoPauseController.getInstance().onUiShowing(showing);
- }
- }
-
- /**
- * Brings the app into the foreground if possible.
- */
- public void bringToForeground(boolean showDialpad) {
- // Before we bring the incall UI to the foreground, we check to see if:
- // 1. It is not currently in the foreground
- // 2. We are in a state where we want to show the incall ui (i.e. there are calls to
- // be displayed)
- // If the activity hadn't actually been started previously, yet there are still calls
- // present (e.g. a call was accepted by a bluetooth or wired headset), we want to
- // bring it up the UI regardless.
- if (!isShowingInCallUi() && mInCallState != InCallState.NO_CALLS) {
- showInCall(showDialpad, false /* newOutgoingCall */);
- }
- }
-
- public void onPostDialCharWait(String callId, String chars) {
- if (isActivityStarted()) {
- mInCallActivity.showPostCharWaitDialog(callId, chars);
- }
- }
-
- /**
- * Handles the green CALL key while in-call.
- * @return true if we consumed the event.
- */
- public boolean handleCallKey() {
- Log.v(this, "handleCallKey");
-
- // The green CALL button means either "Answer", "Unhold", or
- // "Swap calls", or can be a no-op, depending on the current state
- // of the Phone.
-
- /**
- * INCOMING CALL
- */
- final CallList calls = mCallList;
- final Call incomingCall = calls.getIncomingCall();
- Log.v(this, "incomingCall: " + incomingCall);
-
- // (1) Attempt to answer a call
- if (incomingCall != null) {
- TelecomAdapter.getInstance().answerCall(
- incomingCall.getId(), VideoProfile.STATE_AUDIO_ONLY);
- return true;
- }
-
- /**
- * STATE_ACTIVE CALL
- */
- final Call activeCall = calls.getActiveCall();
- if (activeCall != null) {
- // TODO: This logic is repeated from CallButtonPresenter.java. We should
- // consolidate this logic.
- final boolean canMerge = activeCall.can(
- android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE);
- final boolean canSwap = activeCall.can(
- android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE);
-
- Log.v(this, "activeCall: " + activeCall + ", canMerge: " + canMerge +
- ", canSwap: " + canSwap);
-
- // (2) Attempt actions on conference calls
- if (canMerge) {
- TelecomAdapter.getInstance().merge(activeCall.getId());
- return true;
- } else if (canSwap) {
- TelecomAdapter.getInstance().swap(activeCall.getId());
- return true;
- }
- }
-
- /**
- * BACKGROUND CALL
- */
- final Call heldCall = calls.getBackgroundCall();
- if (heldCall != null) {
- // We have a hold call so presumeable it will always support HOLD...but
- // there is no harm in double checking.
- final boolean canHold = heldCall.can(android.telecom.Call.Details.CAPABILITY_HOLD);
-
- Log.v(this, "heldCall: " + heldCall + ", canHold: " + canHold);
-
- // (4) unhold call
- if (heldCall.getState() == Call.State.ONHOLD && canHold) {
- TelecomAdapter.getInstance().unholdCall(heldCall.getId());
- return true;
- }
- }
-
- // Always consume hard keys
- return true;
- }
-
- /**
- * A dialog could have prevented in-call screen from being previously finished.
- * This function checks to see if there should be any UI left and if not attempts
- * to tear down the UI.
- */
- public void onDismissDialog() {
- Log.i(this, "Dialog dismissed");
- if (mInCallState == InCallState.NO_CALLS) {
- attemptFinishActivity();
- attemptCleanup();
- }
- }
-
- /**
- * Toggles whether the application is in fullscreen mode or not.
- *
- * @return {@code true} if in-call is now in fullscreen mode.
- */
- public boolean toggleFullscreenMode() {
- boolean isFullScreen = !mIsFullScreen;
- Log.v(this, "toggleFullscreenMode = " + isFullScreen);
- setFullScreen(isFullScreen);
- return mIsFullScreen;
- }
-
- /**
- * Clears the previous fullscreen state.
- */
- public void clearFullscreen() {
- mIsFullScreen = false;
- }
-
- /**
- * Changes the fullscreen mode of the in-call UI.
- *
- * @param isFullScreen {@code true} if in-call should be in fullscreen mode, {@code false}
- * otherwise.
- */
- public void setFullScreen(boolean isFullScreen) {
- setFullScreen(isFullScreen, false /* force */);
- }
-
- /**
- * Changes the fullscreen mode of the in-call UI.
- *
- * @param isFullScreen {@code true} if in-call should be in fullscreen mode, {@code false}
- * otherwise.
- * @param force {@code true} if fullscreen mode should be set regardless of its current state.
- */
- public void setFullScreen(boolean isFullScreen, boolean force) {
- Log.v(this, "setFullScreen = " + isFullScreen);
-
- // As a safeguard, ensure we cannot enter fullscreen if the dialpad is shown.
- if (isDialpadVisible()) {
- isFullScreen = false;
- Log.v(this, "setFullScreen overridden as dialpad is shown = " + isFullScreen);
- }
-
- if (mIsFullScreen == isFullScreen && !force) {
- Log.v(this, "setFullScreen ignored as already in that state.");
- return;
- }
- mIsFullScreen = isFullScreen;
- notifyFullscreenModeChange(mIsFullScreen);
- }
-
- /**
- * @return {@code true} if the in-call ui is currently in fullscreen mode, {@code false}
- * otherwise.
- */
- public boolean isFullscreen() {
- return mIsFullScreen;
- }
-
-
- /**
- * Called by the {@link VideoCallPresenter} to inform of a change in full screen video status.
- *
- * @param isFullscreenMode {@code True} if entering full screen mode.
- */
- public void notifyFullscreenModeChange(boolean isFullscreenMode) {
- for (InCallEventListener listener : mInCallEventListeners) {
- listener.onFullscreenModeChanged(isFullscreenMode);
- }
- }
-
- /**
- * Called by the {@link CallCardPresenter} to inform of a change in visibility of the secondary
- * caller info bar.
- *
- * @param isVisible {@code true} if the secondary caller info is visible, {@code false}
- * otherwise.
- * @param height the height of the secondary caller info bar.
- */
- public void notifySecondaryCallerInfoVisibilityChanged(boolean isVisible, int height) {
- for (InCallEventListener listener : mInCallEventListeners) {
- listener.onSecondaryCallerInfoVisibilityChanged(isVisible, height);
- }
- }
-
-
- /**
- * For some disconnected causes, we show a dialog. This calls into the activity to show
- * the dialog if appropriate for the call.
- */
- private void maybeShowErrorDialogOnDisconnect(Call call) {
- // For newly disconnected calls, we may want to show a dialog on specific error conditions
- if (isActivityStarted() && call.getState() == Call.State.DISCONNECTED) {
- if (call.getAccountHandle() == null && !call.isConferenceCall()) {
- setDisconnectCauseForMissingAccounts(call);
- }
- mInCallActivity.maybeShowErrorDialogOnDisconnect(call.getDisconnectCause());
- }
- }
-
- /**
- * When the state of in-call changes, this is the first method to get called. It determines if
- * the UI needs to be started or finished depending on the new state and does it.
- */
- private InCallState startOrFinishUi(InCallState newState) {
- Log.d(this, "startOrFinishUi: " + mInCallState + " -> " + newState);
-
- // TODO: Consider a proper state machine implementation
-
- // If the state isn't changing we have already done any starting/stopping of activities in
- // a previous pass...so lets cut out early
- if (newState == mInCallState) {
- return newState;
- }
-
- // A new Incoming call means that the user needs to be notified of the the call (since
- // it wasn't them who initiated it). We do this through full screen notifications and
- // happens indirectly through {@link StatusBarNotifier}.
- //
- // The process for incoming calls is as follows:
- //
- // 1) CallList - Announces existence of new INCOMING call
- // 2) InCallPresenter - Gets announcement and calculates that the new InCallState
- // - should be set to INCOMING.
- // 3) InCallPresenter - This method is called to see if we need to start or finish
- // the app given the new state.
- // 4) StatusBarNotifier - Listens to InCallState changes. InCallPresenter calls
- // StatusBarNotifier explicitly to issue a FullScreen Notification
- // that will either start the InCallActivity or show the user a
- // top-level notification dialog if the user is in an immersive app.
- // That notification can also start the InCallActivity.
- // 5) InCallActivity - Main activity starts up and at the end of its onCreate will
- // call InCallPresenter::setActivity() to let the presenter
- // know that start-up is complete.
- //
- // [ AND NOW YOU'RE IN THE CALL. voila! ]
- //
- // Our app is started using a fullScreen notification. We need to do this whenever
- // we get an incoming call. Depending on the current context of the device, either a
- // incoming call HUN or the actual InCallActivity will be shown.
- final boolean startIncomingCallSequence = (InCallState.INCOMING == newState);
-
- // A dialog to show on top of the InCallUI to select a PhoneAccount
- final boolean showAccountPicker = (InCallState.WAITING_FOR_ACCOUNT == newState);
-
- // A new outgoing call indicates that the user just now dialed a number and when that
- // happens we need to display the screen immediately or show an account picker dialog if
- // no default is set. However, if the main InCallUI is already visible, we do not want to
- // re-initiate the start-up animation, so we do not need to do anything here.
- //
- // It is also possible to go into an intermediate state where the call has been initiated
- // but Telecom has not yet returned with the details of the call (handle, gateway, etc.).
- // This pending outgoing state can also launch the call screen.
- //
- // This is different from the incoming call sequence because we do not need to shock the
- // user with a top-level notification. Just show the call UI normally.
- final boolean mainUiNotVisible = !isShowingInCallUi() || !getCallCardFragmentVisible();
- boolean showCallUi = InCallState.OUTGOING == newState && mainUiNotVisible;
-
- // Direct transition from PENDING_OUTGOING -> INCALL means that there was an error in the
- // outgoing call process, so the UI should be brought up to show an error dialog.
- showCallUi |= (InCallState.PENDING_OUTGOING == mInCallState
- && InCallState.INCALL == newState && !isShowingInCallUi());
-
- // Another exception - InCallActivity is in charge of disconnecting a call with no
- // valid accounts set. Bring the UI up if this is true for the current pending outgoing
- // call so that:
- // 1) The call can be disconnected correctly
- // 2) The UI comes up and correctly displays the error dialog.
- // TODO: Remove these special case conditions by making InCallPresenter a true state
- // machine. Telecom should also be the component responsible for disconnecting a call
- // with no valid accounts.
- showCallUi |= InCallState.PENDING_OUTGOING == newState && mainUiNotVisible
- && isCallWithNoValidAccounts(mCallList.getPendingOutgoingCall());
-
- // The only time that we have an instance of mInCallActivity and it isn't started is
- // when it is being destroyed. In that case, lets avoid bringing up another instance of
- // the activity. When it is finally destroyed, we double check if we should bring it back
- // up so we aren't going to lose anything by avoiding a second startup here.
- boolean activityIsFinishing = mInCallActivity != null && !isActivityStarted();
- if (activityIsFinishing) {
- Log.i(this, "Undo the state change: " + newState + " -> " + mInCallState);
- return mInCallState;
- }
-
- if (showCallUi || showAccountPicker) {
- Log.i(this, "Start in call UI");
- showInCall(false /* showDialpad */, !showAccountPicker /* newOutgoingCall */);
- } else if (startIncomingCallSequence) {
- Log.i(this, "Start Full Screen in call UI");
-
- // We're about the bring up the in-call UI for an incoming call. If we still have
- // dialogs up, we need to clear them out before showing incoming screen.
- if (isActivityStarted()) {
- mInCallActivity.dismissPendingDialogs();
- }
- if (!startUi(newState)) {
- // startUI refused to start the UI. This indicates that it needed to restart the
- // activity. When it finally restarts, it will call us back, so we do not actually
- // change the state yet (we return mInCallState instead of newState).
- return mInCallState;
- }
- } else if (newState == InCallState.NO_CALLS) {
- // The new state is the no calls state. Tear everything down.
- attemptFinishActivity();
- attemptCleanup();
- }
-
- return newState;
- }
-
- /**
- * Determines whether or not a call has no valid phone accounts that can be used to make the
- * call with. Emergency calls do not require a phone account.
- *
- * @param call to check accounts for.
- * @return {@code true} if the call has no call capable phone accounts set, {@code false} if
- * the call contains a phone account that could be used to initiate it with, or is an emergency
- * call.
- */
- public static boolean isCallWithNoValidAccounts(Call call) {
- if (call != null && !call.isEmergencyCall()) {
- Bundle extras = call.getIntentExtras();
-
- if (extras == null) {
- extras = EMPTY_EXTRAS;
- }
-
- final List<PhoneAccountHandle> phoneAccountHandles = extras
- .getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
-
- if ((call.getAccountHandle() == null &&
- (phoneAccountHandles == null || phoneAccountHandles.isEmpty()))) {
- Log.i(InCallPresenter.getInstance(), "No valid accounts for call " + call);
- return true;
- }
- }
- return false;
- }
-
- /**
- * Sets the DisconnectCause for a call that was disconnected because it was missing a
- * PhoneAccount or PhoneAccounts to select from.
- * @param call
- */
- private void setDisconnectCauseForMissingAccounts(Call call) {
- android.telecom.Call telecomCall = call.getTelecomCall();
-
- Bundle extras = telecomCall.getDetails().getIntentExtras();
- // Initialize the extras bundle to avoid NPE
- if (extras == null) {
- extras = new Bundle();
- }
-
- final List<PhoneAccountHandle> phoneAccountHandles = extras.getParcelableArrayList(
- android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
-
- if (phoneAccountHandles == null || phoneAccountHandles.isEmpty()) {
- String scheme = telecomCall.getDetails().getHandle().getScheme();
- final String errorMsg = PhoneAccount.SCHEME_TEL.equals(scheme) ?
- mContext.getString(R.string.callFailed_simError) :
- mContext.getString(R.string.incall_error_supp_service_unknown);
- DisconnectCause disconnectCause =
- new DisconnectCause(DisconnectCause.ERROR, null, errorMsg, errorMsg);
- call.setDisconnectCause(disconnectCause);
- }
- }
-
- private boolean startUi(InCallState inCallState) {
- boolean isCallWaiting = mCallList.getActiveCall() != null &&
- mCallList.getIncomingCall() != null;
-
- // If the screen is off, we need to make sure it gets turned on for incoming calls.
- // This normally works just fine thanks to FLAG_TURN_SCREEN_ON but that only works
- // when the activity is first created. Therefore, to ensure the screen is turned on
- // for the call waiting case, we finish() the current activity and start a new one.
- // There should be no jank from this since the screen is already off and will remain so
- // until our new activity is up.
-
- if (isCallWaiting) {
- if (mProximitySensor.isScreenReallyOff() && isActivityStarted()) {
- Log.i(this, "Restarting InCallActivity to turn screen on for call waiting");
- mInCallActivity.finish();
- // When the activity actually finishes, we will start it again if there are
- // any active calls, so we do not need to start it explicitly here. Note, we
- // actually get called back on this function to restart it.
-
- // We return false to indicate that we did not actually start the UI.
- return false;
- } else {
- showInCall(false, false);
- }
- } else {
- mStatusBarNotifier.updateNotification(inCallState, mCallList);
- }
- return true;
- }
-
- /**
- * Checks to see if both the UI is gone and the service is disconnected. If so, tear it all
- * down.
- */
- private void attemptCleanup() {
- boolean shouldCleanup = (mInCallActivity == null && !mServiceConnected &&
- mInCallState == InCallState.NO_CALLS);
- Log.i(this, "attemptCleanup? " + shouldCleanup);
-
- if (shouldCleanup) {
- mIsActivityPreviouslyStarted = false;
- mIsChangingConfigurations = false;
-
- // blow away stale contact info so that we get fresh data on
- // the next set of calls
- if (mContactInfoCache != null) {
- mContactInfoCache.clearCache();
- }
- mContactInfoCache = null;
-
- if (mProximitySensor != null) {
- removeListener(mProximitySensor);
- mProximitySensor.tearDown();
- }
- mProximitySensor = null;
-
- mAudioModeProvider = null;
-
- if (mStatusBarNotifier != null) {
- removeListener(mStatusBarNotifier);
- }
- if (mExternalCallNotifier != null && mExternalCallList != null) {
- mExternalCallList.removeExternalCallListener(mExternalCallNotifier);
- }
- mStatusBarNotifier = null;
-
- if (mCallList != null) {
- mCallList.removeListener(this);
- mCallList.removeListener(mSpamCallListListener);
- }
- mCallList = null;
-
- mContext = null;
- mInCallActivity = null;
-
- mListeners.clear();
- mIncomingCallListeners.clear();
- mDetailsListeners.clear();
- mCanAddCallListeners.clear();
- mOrientationListeners.clear();
- mInCallEventListeners.clear();
-
- Log.d(this, "Finished InCallPresenter.CleanUp");
- }
- }
-
- public void showInCall(final boolean showDialpad, final boolean newOutgoingCall) {
- Log.i(this, "Showing InCallActivity");
- mContext.startActivity(getInCallIntent(showDialpad, newOutgoingCall));
- }
-
- public void onServiceBind() {
- mServiceBound = true;
- }
-
- public void onServiceUnbind() {
- InCallPresenter.getInstance().setBoundAndWaitingForOutgoingCall(false, null);
- mServiceBound = false;
- }
-
- public boolean isServiceBound() {
- return mServiceBound;
- }
-
- public void maybeStartRevealAnimation(Intent intent) {
- if (intent == null || mInCallActivity != null) {
- return;
- }
- final Bundle extras = intent.getBundleExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
- if (extras == null) {
- // Incoming call, just show the in-call UI directly.
- return;
- }
-
- if (extras.containsKey(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS)) {
- // Account selection dialog will show up so don't show the animation.
- return;
- }
-
- final PhoneAccountHandle accountHandle =
- intent.getParcelableExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
- final Point touchPoint = extras.getParcelable(TouchPointManager.TOUCH_POINT);
-
- InCallPresenter.getInstance().setBoundAndWaitingForOutgoingCall(true, accountHandle);
-
- final Intent incallIntent = getInCallIntent(false, true);
- incallIntent.putExtra(TouchPointManager.TOUCH_POINT, touchPoint);
- mContext.startActivity(incallIntent);
- }
-
- public Intent getInCallIntent(boolean showDialpad, boolean newOutgoingCall) {
- final Intent intent = new Intent(Intent.ACTION_MAIN, null);
- intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
-
- intent.setClass(mContext, InCallActivity.class);
- if (showDialpad) {
- intent.putExtra(InCallActivity.SHOW_DIALPAD_EXTRA, true);
- }
- intent.putExtra(InCallActivity.NEW_OUTGOING_CALL_EXTRA, newOutgoingCall);
- return intent;
- }
-
- /**
- * Retrieves the current in-call camera manager instance, creating if necessary.
- *
- * @return The {@link InCallCameraManager}.
- */
- public InCallCameraManager getInCallCameraManager() {
- synchronized(this) {
- if (mInCallCameraManager == null) {
- mInCallCameraManager = new InCallCameraManager(mContext);
- }
-
- return mInCallCameraManager;
- }
- }
-
- /**
- * Notifies listeners of changes in orientation and notify calls of rotation angle change.
- *
- * @param orientation The screen orientation of the device (one of:
- * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_0},
- * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_90},
- * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_180},
- * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_270}).
- */
- public void onDeviceOrientationChange(int orientation) {
- Log.d(this, "onDeviceOrientationChange: orientation= " + orientation);
-
- if (mCallList != null) {
- mCallList.notifyCallsOfDeviceRotation(orientation);
- } else {
- Log.w(this, "onDeviceOrientationChange: CallList is null.");
- }
-
- // Notify listeners of device orientation changed.
- for (InCallOrientationListener listener : mOrientationListeners) {
- listener.onDeviceOrientationChanged(orientation);
- }
- }
-
- /**
- * Configures the in-call UI activity so it can change orientations or not. Enables the
- * orientation event listener if allowOrientationChange is true, disables it if false.
- *
- * @param allowOrientationChange {@code True} if the in-call UI can change between portrait
- * and landscape. {@Code False} if the in-call UI should be locked in portrait.
- */
- public void setInCallAllowsOrientationChange(boolean allowOrientationChange) {
- if (mInCallActivity == null) {
- Log.e(this, "InCallActivity is null. Can't set requested orientation.");
- return;
- }
-
- if (!allowOrientationChange) {
- mInCallActivity.setRequestedOrientation(
- InCallOrientationEventListener.NO_SENSOR_SCREEN_ORIENTATION);
- } else {
- // Using SCREEN_ORIENTATION_FULL_SENSOR allows for reverse-portrait orientation, where
- // SCREEN_ORIENTATION_SENSOR does not.
- mInCallActivity.setRequestedOrientation(
- InCallOrientationEventListener.FULL_SENSOR_SCREEN_ORIENTATION);
- }
- mInCallActivity.enableInCallOrientationEventListener(allowOrientationChange);
- }
-
- public void enableScreenTimeout(boolean enable) {
- Log.v(this, "enableScreenTimeout: value=" + enable);
- if (mInCallActivity == null) {
- Log.e(this, "enableScreenTimeout: InCallActivity is null.");
- return;
- }
-
- final Window window = mInCallActivity.getWindow();
- if (enable) {
- window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- } else {
- window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- }
- }
-
- /**
- * Returns the space available beside the call card.
- *
- * @return The space beside the call card.
- */
- public float getSpaceBesideCallCard() {
- if (mInCallActivity != null && mInCallActivity.getCallCardFragment() != null) {
- return mInCallActivity.getCallCardFragment().getSpaceBesideCallCard();
- }
- return 0;
- }
-
- /**
- * Returns whether the call card fragment is currently visible.
- *
- * @return True if the call card fragment is visible.
- */
- public boolean getCallCardFragmentVisible() {
- if (mInCallActivity != null && mInCallActivity.getCallCardFragment() != null) {
- return mInCallActivity.getCallCardFragment().isVisible();
- }
- return false;
- }
-
- /**
- * Hides or shows the conference manager fragment.
- *
- * @param show {@code true} if the conference manager should be shown, {@code false} if it
- * should be hidden.
- */
- public void showConferenceCallManager(boolean show) {
- if (mInCallActivity == null) {
- return;
- }
-
- mInCallActivity.showConferenceFragment(show);
- }
-
- /**
- * Determines if the dialpad is visible.
- *
- * @return {@code true} if the dialpad is visible, {@code false} otherwise.
- */
- public boolean isDialpadVisible() {
- if (mInCallActivity == null) {
- return false;
- }
- return mInCallActivity.isDialpadVisible();
- }
-
- /**
- * @return True if the application is currently running in a right-to-left locale.
- */
- public static boolean isRtl() {
- return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) ==
- View.LAYOUT_DIRECTION_RTL;
- }
-
- /**
- * Extract background color from call object. The theme colors will include a primary color
- * and a secondary color.
- */
- public void setThemeColors() {
- // This method will set the background to default if the color is PhoneAccount.NO_COLOR.
- mThemeColors = getColorsFromCall(mCallList.getFirstCall());
-
- if (mInCallActivity == null) {
- return;
- }
-
- final Resources resources = mInCallActivity.getResources();
- final int color;
- if (resources.getBoolean(R.bool.is_layout_landscape)) {
- // TODO use ResourcesCompat.getColor(Resources, int, Resources.Theme) when available
- // {@link Resources#getColor(int)} used for compatibility
- color = resources.getColor(R.color.statusbar_background_color);
- } else {
- color = mThemeColors.mSecondaryColor;
- }
-
- mInCallActivity.getWindow().setStatusBarColor(color);
- final TaskDescription td = new TaskDescription(
- resources.getString(R.string.notification_ongoing_call), null, color);
- mInCallActivity.setTaskDescription(td);
- }
-
- /**
- * @return A palette for colors to display in the UI.
- */
- public MaterialPalette getThemeColors() {
- return mThemeColors;
- }
-
- private MaterialPalette getColorsFromCall(Call call) {
- if (call == null) {
- return getColorsFromPhoneAccountHandle(mPendingPhoneAccountHandle);
- } else {
- if (call.isSpam()) {
- Resources resources = mContext.getResources();
- return new InCallUIMaterialColorMapUtils(
- resources).calculatePrimaryAndSecondaryColor(
- resources.getColor(R.color.incall_call_spam_background_color));
- } else {
- return getColorsFromPhoneAccountHandle(call.getAccountHandle());
- }
- }
- }
-
- private MaterialPalette getColorsFromPhoneAccountHandle(PhoneAccountHandle phoneAccountHandle) {
- int highlightColor = PhoneAccount.NO_HIGHLIGHT_COLOR;
- if (phoneAccountHandle != null) {
- final TelecomManager tm = getTelecomManager();
-
- if (tm != null) {
- final PhoneAccount account =
- TelecomManagerCompat.getPhoneAccount(tm, phoneAccountHandle);
- // For single-sim devices, there will be no selected highlight color, so the phone
- // account will default to NO_HIGHLIGHT_COLOR.
- if (account != null && CompatUtils.isLollipopMr1Compatible()) {
- highlightColor = account.getHighlightColor();
- }
- }
- }
- return new InCallUIMaterialColorMapUtils(
- mContext.getResources()).calculatePrimaryAndSecondaryColor(highlightColor);
- }
-
- /**
- * @return An instance of TelecomManager.
- */
- public TelecomManager getTelecomManager() {
- if (mTelecomManager == null) {
- mTelecomManager = (TelecomManager)
- mContext.getSystemService(Context.TELECOM_SERVICE);
- }
- return mTelecomManager;
- }
-
- /**
- * @return An instance of TelephonyManager
- */
- public TelephonyManager getTelephonyManager() {
- return mTelephonyManager;
- }
-
- InCallActivity getActivity() {
- return mInCallActivity;
- }
-
- AnswerPresenter getAnswerPresenter() {
- return mAnswerPresenter;
- }
-
- ExternalCallNotifier getExternalCallNotifier() {
- return mExternalCallNotifier;
- }
-
- /**
- * Private constructor. Must use getInstance() to get this singleton.
- */
- private InCallPresenter() {
- }
-
- /**
- * All the main states of InCallActivity.
- */
- public enum InCallState {
- // InCall Screen is off and there are no calls
- NO_CALLS,
-
- // Incoming-call screen is up
- INCOMING,
-
- // In-call experience is showing
- INCALL,
-
- // Waiting for user input before placing outgoing call
- WAITING_FOR_ACCOUNT,
-
- // UI is starting up but no call has been initiated yet.
- // The UI is waiting for Telecom to respond.
- PENDING_OUTGOING,
-
- // User is dialing out
- OUTGOING;
-
- public boolean isIncoming() {
- return (this == INCOMING);
- }
-
- public boolean isConnectingOrConnected() {
- return (this == INCOMING ||
- this == OUTGOING ||
- this == INCALL);
- }
- }
-
- /**
- * Interface implemented by classes that need to know about the InCall State.
- */
- public interface InCallStateListener {
- // TODO: Enhance state to contain the call objects instead of passing CallList
- public void onStateChange(InCallState oldState, InCallState newState, CallList callList);
- }
-
- public interface IncomingCallListener {
- public void onIncomingCall(InCallState oldState, InCallState newState, Call call);
- }
-
- public interface CanAddCallListener {
- public void onCanAddCallChanged(boolean canAddCall);
- }
-
- public interface InCallDetailsListener {
- public void onDetailsChanged(Call call, android.telecom.Call.Details details);
- }
-
- public interface InCallOrientationListener {
- public void onDeviceOrientationChanged(int orientation);
- }
-
- /**
- * Interface implemented by classes that need to know about events which occur within the
- * In-Call UI. Used as a means of communicating between fragments that make up the UI.
- */
- public interface InCallEventListener {
- public void onFullscreenModeChanged(boolean isFullscreenMode);
- public void onSecondaryCallerInfoVisibilityChanged(boolean isVisible, int height);
- }
-
- public interface InCallUiListener {
- void onUiShowing(boolean showing);
- }
-}