summaryrefslogtreecommitdiffstats
path: root/src/com/android/incallui/InCallPresenter.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/incallui/InCallPresenter.java')
-rw-r--r--src/com/android/incallui/InCallPresenter.java348
1 files changed, 320 insertions, 28 deletions
diff --git a/src/com/android/incallui/InCallPresenter.java b/src/com/android/incallui/InCallPresenter.java
index d26e48aa..743f667d 100644
--- a/src/com/android/incallui/InCallPresenter.java
+++ b/src/com/android/incallui/InCallPresenter.java
@@ -16,19 +16,22 @@
package com.android.incallui;
-import android.Manifest;
-import android.app.PendingIntent;
+import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.ActivityNotFoundException;
import android.content.pm.ActivityInfo;
+import android.graphics.Point;
+import android.net.Uri;
import android.os.Bundle;
+import android.os.Handler;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
-import android.telecom.PhoneCapabilities;
import android.telecom.Phone;
import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
+import android.telephony.PhoneNumberUtils;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.view.Surface;
@@ -37,6 +40,9 @@ import android.view.Window;
import android.view.WindowManager;
import com.google.common.base.Preconditions;
+
+import com.android.contacts.common.interactions.TouchPointManager;
+import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
import com.android.incalluibind.ObjectFactory;
import java.util.Collections;
@@ -60,6 +66,8 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
private static final String EXTRA_FIRST_TIME_SHOWN =
"com.android.incallui.intent.extra.FIRST_TIME_SHOWN";
+ private static final Bundle EMPTY_EXTRAS = new Bundle();
+
private static InCallPresenter sInCallPresenter;
/**
@@ -74,6 +82,8 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
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(
@@ -159,9 +169,34 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
*/
private boolean mIsChangingConfigurations = false;
+ /**
+ * Whether or not to wait for the circular reveal animation to be started, to avoid stopping
+ * the circular reveal animation activity before the animation is initiated.
+ */
+ private boolean mWaitForRevealAnimationStart = false;
+
+ /**
+ * Whether or not the CircularRevealAnimationActivity has started.
+ */
+ private boolean mCircularRevealActivityStarted = false;
+
+ private boolean mShowDialpadOnStart = false;
+
+ /**
+ * Whether or not InCallService is bound to Telecom.
+ */
+ private boolean mServiceBound = false;
+
private Phone mPhone;
private int mLastDisconnectCause = DisconnectCause.ERROR;
+ private Handler mHandler = new Handler();
+
+ /** Display colors for the UI. Consists of a primary color and secondary (darker) color */
+ private MaterialPalette mThemeColors;
+
+ private TelecomManager mTelecomManager;
+
public static synchronized InCallPresenter getInstance() {
if (sInCallPresenter == null) {
sInCallPresenter = new InCallPresenter();
@@ -246,6 +281,13 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
}
private void attemptFinishActivity() {
+ mWaitForRevealAnimationStart = false;
+
+ Context context = mContext != null ? mContext : mInCallActivity;
+ if (context != null) {
+ CircularRevealActivity.sendClearDisplayBroadcast(context);
+ }
+
final boolean doFinish = (mInCallActivity != null && isActivityStarted());
Log.i(this, "Hide in call UI: " + doFinish);
@@ -270,11 +312,44 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
}
/**
- * Called when the UI begins or ends. Starts the callstate callbacks if the UI just began.
- * Attempts to tear down everything if the UI just ended. See #tearDown for more insight on
- * the tear-down process.
+ * 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.wtf(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;
@@ -282,8 +357,6 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
if (mInCallActivity == null) {
updateListeners = true;
Log.i(this, "UI Initialized");
- } else if (mInCallActivity != inCallActivity) {
- Log.wtf(this, "Setting a second activity before destroying the first.");
} 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
@@ -305,18 +378,18 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
// 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 Intialized, but no calls left. shut down.");
+ Log.i(this, "UI Initialized, but no calls left. shut down.");
attemptFinishActivity();
return;
}
} else {
- Log.i(this, "UI Destroyed)");
+ 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.
+ // 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;
}
@@ -705,7 +778,11 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
if (showing) {
mIsActivityPreviouslyStarted = true;
} else {
- updateIsChangingConfigurations();
+ CircularRevealActivity.sendClearDisplayBroadcast(mContext);
+ }
+
+ for (InCallUiListener listener : mInCallUiListeners) {
+ listener.onUiShowing(showing);
}
}
@@ -729,6 +806,14 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
}
}
+ public void addInCallUiListener(InCallUiListener listener) {
+ mInCallUiListeners.add(listener);
+ }
+
+ public boolean removeInCallUiListener(InCallUiListener listener) {
+ return mInCallUiListeners.remove(listener);
+ }
+
/**
* Brings the app into the foreground if possible.
*/
@@ -783,8 +868,10 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
if (activeCall != null) {
// TODO: This logic is repeated from CallButtonPresenter.java. We should
// consolidate this logic.
- final boolean canMerge = activeCall.can(PhoneCapabilities.MERGE_CONFERENCE);
- final boolean canSwap = activeCall.can(PhoneCapabilities.SWAP_CONFERENCE);
+ 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);
@@ -806,7 +893,7 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
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(PhoneCapabilities.HOLD);
+ final boolean canHold = heldCall.can(android.telecom.Call.Details.CAPABILITY_HOLD);
Log.v(this, "heldCall: " + heldCall + ", canHold: " + canHold);
@@ -939,12 +1026,23 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
// 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();
- final boolean showCallUi = ((InCallState.PENDING_OUTGOING == newState ||
- InCallState.OUTGOING == newState) && mainUiNotVisible);
-
- // TODO: Can we be suddenly in a call without it having been in the outgoing or incoming
- // state? I havent seen that but if it can happen, the code below should be enabled.
- // showCallUi |= (InCallState.INCALL && !isActivityStarted());
+ 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 && !isActivityStarted());
+
+ // 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(CallList.getInstance().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
@@ -983,6 +1081,43 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
}
/**
+ * 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 && !isEmergencyCall(call)) {
+ Bundle extras = call.getTelecommCall().getDetails().getExtras();
+
+ 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;
+ }
+
+ private static boolean isEmergencyCall(Call call) {
+ final Uri handle = call.getHandle();
+ if (handle == null) {
+ return false;
+ }
+ return PhoneNumberUtils.isEmergencyNumber(handle.getSchemeSpecificPart());
+ }
+
+ /**
* Sets the DisconnectCause for a call that was disconnected because it was missing a
* PhoneAccount or PhoneAccounts to select from.
* @param call
@@ -1030,6 +1165,7 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
Log.i(this, "Start UI " + " anyOtherSubActive:" + anyOtherSubActive);
if (isCallWaiting || anyOtherSubActive) {
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
@@ -1103,21 +1239,104 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
}
}
- private void showInCall(boolean showDialpad, boolean newOutgoingCall) {
- mContext.startActivity(getInCallIntent(showDialpad, newOutgoingCall));
+ public void showInCall(final boolean showDialpad, final boolean newOutgoingCall) {
+ if (mCircularRevealActivityStarted) {
+ mWaitForRevealAnimationStart = true;
+ mShowDialpadOnStart = showDialpad;
+ Log.i(this, "Waiting for circular reveal completion to show InCallActivity");
+ } else {
+ Log.i(this, "Showing InCallActivity immediately");
+ mContext.startActivity(getInCallIntent(showDialpad, newOutgoingCall,
+ newOutgoingCall /* showCircularReveal */));
+ }
+ }
+
+ public void onCircularRevealStarted(final Activity activity) {
+ mCircularRevealActivityStarted = false;
+ if (mWaitForRevealAnimationStart) {
+ mWaitForRevealAnimationStart = false;
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Log.i(this, "Showing InCallActivity after circular reveal");
+ final Intent intent =
+ getInCallIntent(mShowDialpadOnStart, true, false, false);
+ activity.startActivity(intent);
+ mShowDialpadOnStart = false;
+ }
+ });
+ } else if (!mServiceBound) {
+ CircularRevealActivity.sendClearDisplayBroadcast(mContext);
+ return;
+ }
}
- public Intent getInCallIntent(boolean showDialpad, boolean newOutgoingCall) {
- final Intent intent = new Intent(Intent.ACTION_MAIN, null);
+ public void onServiceBind() {
+ mServiceBound = true;
+ }
+
+ public void onServiceUnbind() {
+ 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 MaterialPalette colors = getColorsFromPhoneAccountHandle(accountHandle);
+ final Point touchPoint = extras.getParcelable(TouchPointManager.TOUCH_POINT);
+
+ mCircularRevealActivityStarted = true;
+ mContext.startActivity(getAnimationIntent(touchPoint, colors));
+ }
+
+ private Intent getAnimationIntent(Point touchPoint, MaterialPalette palette) {
+ final Intent intent = new Intent(mContext, CircularRevealActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
| Intent.FLAG_ACTIVITY_NO_USER_ACTION);
+ intent.putExtra(TouchPointManager.TOUCH_POINT, touchPoint);
+ intent.putExtra(CircularRevealActivity.EXTRA_THEME_COLORS, palette);
+ return intent;
+ }
+
+ public Intent getInCallIntent(boolean showDialpad, boolean newOutgoingCall,
+ boolean showCircularReveal) {
+ return getInCallIntent(showDialpad, newOutgoingCall, showCircularReveal, true);
+ }
+
+ public Intent getInCallIntent(boolean showDialpad, boolean newOutgoingCall,
+ boolean showCircularReveal, boolean newTask) {
+ final Intent intent = new Intent(Intent.ACTION_MAIN, null);
+ intent.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+ | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
+ if (newTask) {
+ intent.addFlags(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, newOutgoingCall);
+ intent.putExtra(InCallActivity.NEW_OUTGOING_CALL_EXTRA, newOutgoingCall);
+ intent.putExtra(InCallActivity.SHOW_CIRCULAR_REVEAL_EXTRA, showCircularReveal);
return intent;
}
@@ -1262,6 +1481,20 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
}
/**
+ * 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.showConferenceCallManager(show);
+ }
+
+ /**
* @return True if the application is currently running in a right-to-left locale.
*/
public static boolean isRtl() {
@@ -1270,6 +1503,61 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
}
/**
+ * 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(CallList.getInstance().getFirstCall());
+
+ if (mInCallActivity == null) {
+ return;
+ }
+
+ mInCallActivity.getWindow().setStatusBarColor(mThemeColors.mSecondaryColor);
+ }
+
+ /**
+ * @return A palette for colors to display in the UI.
+ */
+ public MaterialPalette getThemeColors() {
+ return mThemeColors;
+ }
+
+ private MaterialPalette getColorsFromCall(Call call) {
+ return getColorsFromPhoneAccountHandle(call == null ? null : 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 = tm.getPhoneAccount(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) {
+ 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;
+ }
+
+ /**
* Private constructor. Must use getInstance() to get this singleton.
*/
private InCallPresenter() {
@@ -1340,4 +1628,8 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
public interface InCallEventListener {
public void onFullScreenVideoStateChanged(boolean isFullScreenVideo);
}
+
+ public interface InCallUiListener {
+ void onUiShowing(boolean showing);
+ }
}