summaryrefslogtreecommitdiff
path: root/java/com/android/incallui/VideoPauseController.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/android/incallui/VideoPauseController.java')
-rw-r--r--java/com/android/incallui/VideoPauseController.java416
1 files changed, 416 insertions, 0 deletions
diff --git a/java/com/android/incallui/VideoPauseController.java b/java/com/android/incallui/VideoPauseController.java
new file mode 100644
index 000000000..2b4357704
--- /dev/null
+++ b/java/com/android/incallui/VideoPauseController.java
@@ -0,0 +1,416 @@
+/*
+ * Copyright (C) 2015 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 android.support.annotation.NonNull;
+import android.telecom.VideoProfile;
+import com.android.incallui.InCallPresenter.InCallState;
+import com.android.incallui.InCallPresenter.InCallStateListener;
+import com.android.incallui.InCallPresenter.IncomingCallListener;
+import com.android.incallui.call.CallList;
+import com.android.incallui.call.DialerCall;
+import com.android.incallui.call.DialerCall.State;
+import com.android.incallui.call.VideoUtils;
+import java.util.Objects;
+
+/**
+ * This class is responsible for generating video pause/resume requests when the InCall UI is sent
+ * to the background and subsequently brought back to the foreground.
+ */
+class VideoPauseController implements InCallStateListener, IncomingCallListener {
+
+ private static final String TAG = "VideoPauseController";
+ private static VideoPauseController sVideoPauseController;
+ private InCallPresenter mInCallPresenter;
+ /** The current call context, if applicable. */
+ private CallContext mPrimaryCallContext = null;
+ /**
+ * Tracks whether the application is in the background. {@code True} if the application is in the
+ * background, {@code false} otherwise.
+ */
+ private boolean mIsInBackground = false;
+
+ /**
+ * Singleton accessor for the {@link VideoPauseController}.
+ *
+ * @return Singleton instance of the {@link VideoPauseController}.
+ */
+ /*package*/
+ static synchronized VideoPauseController getInstance() {
+ if (sVideoPauseController == null) {
+ sVideoPauseController = new VideoPauseController();
+ }
+ return sVideoPauseController;
+ }
+
+ /**
+ * Determines if a given call is the same one stored in a {@link CallContext}.
+ *
+ * @param call The call.
+ * @param callContext The call context.
+ * @return {@code true} if the {@link DialerCall} is the same as the one referenced in the {@link
+ * CallContext}.
+ */
+ private static boolean areSame(DialerCall call, CallContext callContext) {
+ if (call == null && callContext == null) {
+ return true;
+ } else if (call == null || callContext == null) {
+ return false;
+ }
+ return call.equals(callContext.getCall());
+ }
+
+ /**
+ * Determines if a video call can be paused. Only a video call which is active can be paused.
+ *
+ * @param callContext The call context to check.
+ * @return {@code true} if the call is an active video call.
+ */
+ private static boolean canVideoPause(CallContext callContext) {
+ return isVideoCall(callContext) && callContext.getState() == DialerCall.State.ACTIVE;
+ }
+
+ /**
+ * Determines if a call referenced by a {@link CallContext} is a video call.
+ *
+ * @param callContext The call context.
+ * @return {@code true} if the call is a video call, {@code false} otherwise.
+ */
+ private static boolean isVideoCall(CallContext callContext) {
+ return callContext != null && VideoUtils.isVideoCall(callContext.getVideoState());
+ }
+
+ /**
+ * Determines if call is in incoming/waiting state.
+ *
+ * @param call The call context.
+ * @return {@code true} if the call is in incoming or waiting state, {@code false} otherwise.
+ */
+ private static boolean isIncomingCall(CallContext call) {
+ return call != null && isIncomingCall(call.getCall());
+ }
+
+ /**
+ * Determines if a call is in incoming/waiting state.
+ *
+ * @param call The call.
+ * @return {@code true} if the call is in incoming or waiting state, {@code false} otherwise.
+ */
+ private static boolean isIncomingCall(DialerCall call) {
+ return call != null
+ && (call.getState() == DialerCall.State.CALL_WAITING
+ || call.getState() == DialerCall.State.INCOMING);
+ }
+
+ /**
+ * Determines if a call is dialing.
+ *
+ * @param call The call context.
+ * @return {@code true} if the call is dialing, {@code false} otherwise.
+ */
+ private static boolean isDialing(CallContext call) {
+ return call != null && DialerCall.State.isDialing(call.getState());
+ }
+
+ /**
+ * Configures the {@link VideoPauseController} to listen to call events. Configured via the {@link
+ * com.android.incallui.InCallPresenter}.
+ *
+ * @param inCallPresenter The {@link com.android.incallui.InCallPresenter}.
+ */
+ public void setUp(@NonNull InCallPresenter inCallPresenter) {
+ log("setUp");
+ mInCallPresenter = Objects.requireNonNull(inCallPresenter);
+ mInCallPresenter.addListener(this);
+ mInCallPresenter.addIncomingCallListener(this);
+ }
+
+ /**
+ * Cleans up the {@link VideoPauseController} by removing all listeners and clearing its internal
+ * state. Called from {@link com.android.incallui.InCallPresenter}.
+ */
+ public void tearDown() {
+ log("tearDown...");
+ mInCallPresenter.removeListener(this);
+ mInCallPresenter.removeIncomingCallListener(this);
+ clear();
+ }
+
+ /** Clears the internal state for the {@link VideoPauseController}. */
+ private void clear() {
+ mInCallPresenter = null;
+ mPrimaryCallContext = null;
+ mIsInBackground = false;
+ }
+
+ /**
+ * Handles changes in the {@link InCallState}. Triggers pause and resumption of video for the
+ * current foreground call.
+ *
+ * @param oldState The previous {@link InCallState}.
+ * @param newState The current {@link InCallState}.
+ * @param callList List of current call.
+ */
+ @Override
+ public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
+ log("onStateChange, OldState=" + oldState + " NewState=" + newState);
+
+ DialerCall call;
+ if (newState == InCallState.INCOMING) {
+ call = callList.getIncomingCall();
+ } else if (newState == InCallState.WAITING_FOR_ACCOUNT) {
+ call = callList.getWaitingForAccountCall();
+ } else if (newState == InCallState.PENDING_OUTGOING) {
+ call = callList.getPendingOutgoingCall();
+ } else if (newState == InCallState.OUTGOING) {
+ call = callList.getOutgoingCall();
+ } else {
+ call = callList.getActiveCall();
+ }
+
+ boolean hasPrimaryCallChanged = !areSame(call, mPrimaryCallContext);
+ boolean canVideoPause = VideoUtils.canVideoPause(call);
+ log("onStateChange, hasPrimaryCallChanged=" + hasPrimaryCallChanged);
+ log("onStateChange, canVideoPause=" + canVideoPause);
+ log("onStateChange, IsInBackground=" + mIsInBackground);
+
+ if (hasPrimaryCallChanged) {
+ onPrimaryCallChanged(call);
+ return;
+ }
+
+ if (isDialing(mPrimaryCallContext) && canVideoPause && mIsInBackground) {
+ // Bring UI to foreground if outgoing request becomes active while UI is in
+ // background.
+ bringToForeground();
+ } else if (!isVideoCall(mPrimaryCallContext) && canVideoPause && mIsInBackground) {
+ // Bring UI to foreground if VoLTE call becomes active while UI is in
+ // background.
+ bringToForeground();
+ }
+
+ updatePrimaryCallContext(call);
+ }
+
+ /**
+ * Handles a change to the primary call.
+ *
+ * <p>Reject incoming or hangup dialing call: Where the previous call was an incoming call or a
+ * call in dialing state, resume the new primary call. DialerCall swap: Where the new primary call
+ * is incoming, pause video on the previous primary call.
+ *
+ * @param call The new primary call.
+ */
+ private void onPrimaryCallChanged(DialerCall call) {
+ log("onPrimaryCallChanged: New call = " + call);
+ log("onPrimaryCallChanged: Old call = " + mPrimaryCallContext);
+ log("onPrimaryCallChanged, IsInBackground=" + mIsInBackground);
+
+ if (areSame(call, mPrimaryCallContext)) {
+ throw new IllegalStateException();
+ }
+ final boolean canVideoPause = VideoUtils.canVideoPause(call);
+
+ if ((isIncomingCall(mPrimaryCallContext)
+ || isDialing(mPrimaryCallContext)
+ || (call != null && VideoProfile.isPaused(call.getVideoState())))
+ && canVideoPause
+ && !mIsInBackground) {
+ // Send resume request for the active call, if user rejects incoming call, ends dialing
+ // call, or the call was previously in a paused state and UI is in the foreground.
+ sendRequest(call, true);
+ } else if (isIncomingCall(call) && canVideoPause(mPrimaryCallContext)) {
+ // Send pause request if there is an active video call, and we just received a new
+ // incoming call.
+ sendRequest(mPrimaryCallContext.getCall(), false);
+ }
+
+ updatePrimaryCallContext(call);
+ }
+
+ /**
+ * Handles new incoming calls by triggering a change in the primary call.
+ *
+ * @param oldState the old {@link InCallState}.
+ * @param newState the new {@link InCallState}.
+ * @param call the incoming call.
+ */
+ @Override
+ public void onIncomingCall(InCallState oldState, InCallState newState, DialerCall call) {
+ log("onIncomingCall, OldState=" + oldState + " NewState=" + newState + " DialerCall=" + call);
+
+ if (areSame(call, mPrimaryCallContext)) {
+ return;
+ }
+
+ onPrimaryCallChanged(call);
+ }
+
+ /**
+ * Caches a reference to the primary call and stores its previous state.
+ *
+ * @param call The new primary call.
+ */
+ private void updatePrimaryCallContext(DialerCall call) {
+ if (call == null) {
+ mPrimaryCallContext = null;
+ } else if (mPrimaryCallContext != null) {
+ mPrimaryCallContext.update(call);
+ } else {
+ mPrimaryCallContext = new CallContext(call);
+ }
+ }
+
+ /**
+ * Called when UI goes in/out of the foreground.
+ *
+ * @param showing true if UI is in the foreground, false otherwise.
+ */
+ public void onUiShowing(boolean showing) {
+ if (mInCallPresenter == null) {
+ return;
+ }
+
+ final boolean isInCall = mInCallPresenter.getInCallState() == InCallState.INCALL;
+ if (showing) {
+ onResume(isInCall);
+ } else {
+ onPause(isInCall);
+ }
+ }
+
+ /**
+ * Called when UI is brought to the foreground. Sends a session modification request to resume the
+ * outgoing video.
+ *
+ * @param isInCall {@code true} if we are in an active call. A resume request is only sent to the
+ * video provider if we are in a call.
+ */
+ private void onResume(boolean isInCall) {
+ log("onResume");
+
+ mIsInBackground = false;
+ if (canVideoPause(mPrimaryCallContext) && isInCall) {
+ sendRequest(mPrimaryCallContext.getCall(), true);
+ } else {
+ log("onResume. Ignoring...");
+ }
+ }
+
+ /**
+ * Called when UI is sent to the background. Sends a session modification request to pause the
+ * outgoing video.
+ *
+ * @param isInCall {@code true} if we are in an active call. A pause request is only sent to the
+ * video provider if we are in a call.
+ */
+ private void onPause(boolean isInCall) {
+ log("onPause");
+
+ mIsInBackground = true;
+ if (canVideoPause(mPrimaryCallContext) && isInCall) {
+ sendRequest(mPrimaryCallContext.getCall(), false);
+ } else {
+ log("onPause, Ignoring...");
+ }
+ }
+
+ private void bringToForeground() {
+ if (mInCallPresenter != null) {
+ log("Bringing UI to foreground");
+ mInCallPresenter.bringToForeground(false);
+ } else {
+ loge("InCallPresenter is null. Cannot bring UI to foreground");
+ }
+ }
+
+ /**
+ * Sends Pause/Resume request.
+ *
+ * @param call DialerCall to be paused/resumed.
+ * @param resume If true resume request will be sent, otherwise pause request.
+ */
+ private void sendRequest(DialerCall call, boolean resume) {
+ // Check if this call supports pause/un-pause.
+ if (!call.can(android.telecom.Call.Details.CAPABILITY_CAN_PAUSE_VIDEO)) {
+ return;
+ }
+
+ if (resume) {
+ log("sending resume request, call=" + call);
+ call.getVideoCall().sendSessionModifyRequest(VideoUtils.makeVideoUnPauseProfile(call));
+ } else {
+ log("sending pause request, call=" + call);
+ call.getVideoCall().sendSessionModifyRequest(VideoUtils.makeVideoPauseProfile(call));
+ }
+ }
+
+ /**
+ * Logs a debug message.
+ *
+ * @param msg The message.
+ */
+ private void log(String msg) {
+ Log.d(this, TAG + msg);
+ }
+
+ /**
+ * Logs an error message.
+ *
+ * @param msg The message.
+ */
+ private void loge(String msg) {
+ Log.e(this, TAG + msg);
+ }
+
+ /** Keeps track of the current active/foreground call. */
+ private static class CallContext {
+
+ private int mState = State.INVALID;
+ private int mVideoState;
+ private DialerCall mCall;
+
+ public CallContext(@NonNull DialerCall call) {
+ Objects.requireNonNull(call);
+ update(call);
+ }
+
+ public void update(@NonNull DialerCall call) {
+ mCall = Objects.requireNonNull(call);
+ mState = call.getState();
+ mVideoState = call.getVideoState();
+ }
+
+ public int getState() {
+ return mState;
+ }
+
+ public int getVideoState() {
+ return mVideoState;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "CallContext {CallId=%s, State=%s, VideoState=%d}", mCall.getId(), mState, mVideoState);
+ }
+
+ public DialerCall getCall() {
+ return mCall;
+ }
+ }
+}