IMS-VT: Handle power button interactions in a VT call
1. Introduce InCallUiStateNotifier class that listens to below events
and notify whether UI is visible to the user or not using
onUiShowing API
a. InCallActivity's lifecycle events (onStop/onStart)
b. Display state change events (DISPLAY_ON/DISPLAY_OFF)
2. Introduce InCallUiStateNotifierListener Interface that exposes
onUiShowing API
3. Handle onUiShowing API to
a. Turn ON/OFF camera
b. Send Pause/Resume requests when multitasking is enabled
4. Ignore duplicate Pause/Resume requests
IMS_VT: Enable camera only when InCall UI is in foreground
- We don't need to enable camera when InCall UI is in
background.
Change-Id: Ic95b4ab8d2e8e3a8ba0ea856a53bb320aa713e37
IMS-VT: Always update "mIsInBackground" flag
update "mIsInBackground" flag whenever there are indications
that UE moved to either background or foreground and never
ignore these indications
Change-Id: I9f127fa640b779175dda95ff44aa5e77dc645a42
Change-Id: If4da4fa9fa3a007d23d1bf9e9896e6f40d859900
CRs-Fixed: 881797
diff --git a/InCallUI/src/com/android/incallui/InCallPresenter.java b/InCallUI/src/com/android/incallui/InCallPresenter.java
index b8f3715..630bb94 100644
--- a/InCallUI/src/com/android/incallui/InCallPresenter.java
+++ b/InCallUI/src/com/android/incallui/InCallPresenter.java
@@ -339,6 +339,7 @@
mCallList.addListener(this);
InCallCsRedialHandler.getInstance().setUp(mContext);
+ InCallUiStateNotifier.getInstance().setUp(mContext);
VideoPauseController.getInstance().setUp(this);
InCallVideoCallCallbackNotifier.getInstance().addSessionModificationListener(this);
@@ -371,8 +372,8 @@
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
VideoPauseController.getInstance().tearDown();
+ InCallUiStateNotifier.getInstance().tearDown();
InCallVideoCallCallbackNotifier.getInstance().removeSessionModificationListener(this);
-
InCallMessageController.getInstance().tearDown();
removeDetailsListener(CallSubstateNotifier.getInstance());
@@ -1089,7 +1090,7 @@
/*package*/
void onActivityStarted() {
Log.d(this, "onActivityStarted");
- notifyVideoPauseController(true);
+ notifyInCallUiStateNotifier(true);
if (mStatusBarNotifier != null) {
mStatusBarNotifier.updateCallStatusBar(mCallList);
}
@@ -1098,17 +1099,17 @@
/*package*/
void onActivityStopped() {
Log.d(this, "onActivityStopped");
- notifyVideoPauseController(false);
+ notifyInCallUiStateNotifier(false);
if (mStatusBarNotifier != null ) {
mStatusBarNotifier.updateCallStatusBar(mCallList);
}
}
- private void notifyVideoPauseController(boolean showing) {
- Log.d(this, "notifyVideoPauseController: mIsChangingConfigurations=" +
+ private void notifyInCallUiStateNotifier(boolean showing) {
+ Log.d(this, "notifyInCallUiStateNotifier: mIsChangingConfigurations=" +
mIsChangingConfigurations);
if (!mIsChangingConfigurations) {
- VideoPauseController.getInstance().onUiShowing(showing);
+ InCallUiStateNotifier.getInstance().onUiShowing(showing);
}
}
diff --git a/InCallUI/src/com/android/incallui/InCallUiStateNotifier.java b/InCallUI/src/com/android/incallui/InCallUiStateNotifier.java
new file mode 100644
index 0000000..cd9dd4a
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/InCallUiStateNotifier.java
@@ -0,0 +1,229 @@
+/* Copyright (c) 2015, 2016, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.incallui;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.view.Display;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.List;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * This class listens to below events and notifes whether InCallUI is visible to the user or not.
+ * a. InCallActivity's lifecycle events (onStop/onStart)
+ * b. Display state change events (DISPLAY_ON/DISPLAY_OFF)
+ */
+public class InCallUiStateNotifier implements DisplayManager.DisplayListener {
+
+ private List<InCallUiStateNotifierListener> mInCallUiStateNotifierListeners =
+ new CopyOnWriteArrayList<>();
+ private static InCallUiStateNotifier sInCallUiStateNotifier;
+ private DisplayManager mDisplayManager;
+ private Context mContext;
+
+ /**
+ * Tracks whether the application is in the background. {@code True} if the application is in
+ * the background, {@code false} otherwise.
+ */
+ private boolean mIsInBackground;
+
+ /**
+ * Tracks whether display is ON/OFF. {@code True} if display is ON, {@code false} otherwise.
+ */
+ private boolean mIsDisplayOn;
+
+ /**
+ * Handles set up of the {@class InCallUiStateNotifier}. Instantiates the context needed by
+ * the class and adds a listener to listen to display state changes.
+ */
+ public void setUp(Context context) {
+ mContext = context;
+ mDisplayManager = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
+ mDisplayManager.registerDisplayListener(this, null);
+ mIsDisplayOn = isDisplayOn(
+ mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY).getState());
+ Log.d(this, "setUp mIsDisplayOn: " + mIsDisplayOn);
+ }
+
+ /**
+ * Private constructor. Must use getInstance() to get this singleton.
+ */
+ private InCallUiStateNotifier() {
+ }
+
+ /**
+ * This method returns a singleton instance of {@class InCallUiStateNotifier}
+ */
+ public static synchronized InCallUiStateNotifier getInstance() {
+ if (sInCallUiStateNotifier == null) {
+ sInCallUiStateNotifier = new InCallUiStateNotifier();
+ }
+ return sInCallUiStateNotifier;
+ }
+
+ /**
+ * Adds a new {@link InCallUiStateNotifierListener}.
+ *
+ * @param listener The listener.
+ */
+ public void addListener(InCallUiStateNotifierListener listener) {
+ Preconditions.checkNotNull(listener);
+ mInCallUiStateNotifierListeners.add(listener);
+ }
+
+ /**
+ * Remove a {@link InCallUiStateNotifierListener}.
+ *
+ * @param listener The listener.
+ */
+ public void removeListener(InCallUiStateNotifierListener listener) {
+ if (listener != null) {
+ mInCallUiStateNotifierListeners.remove(listener);
+ } else {
+ Log.e(this, "Can't remove null listener");
+ }
+ }
+
+ /**
+ * Notfies when visibility of InCallUI is changed. For eg.
+ * when UE moves in/out of the foreground, display either turns ON/OFF
+ * @param showing true if InCallUI is visible, false otherwise.
+ */
+ private void notifyOnUiShowing(boolean showing) {
+ Preconditions.checkNotNull(mInCallUiStateNotifierListeners);
+ for (InCallUiStateNotifierListener listener : mInCallUiStateNotifierListeners) {
+ listener.onUiShowing(showing);
+ }
+ }
+
+ /**
+ * Handles tear down of the {@class InCallUiStateNotifier}. Sets the context to null and
+ * unregisters it's display listener.
+ */
+ public void tearDown() {
+ mDisplayManager.unregisterDisplayListener(this);
+ mDisplayManager = null;
+ mContext = null;
+ mInCallUiStateNotifierListeners.clear();
+ }
+
+ /**
+ * checks to see whether InCallUI experience is visible to the user or not.
+ * returns true if InCallUI experience is visible to the user else false.
+ */
+ private boolean isUiShowing() {
+ /* Not in background and display is ON does mean that InCallUI is visible/showing.
+ Return true in such cases else false */
+ return !mIsInBackground && mIsDisplayOn;
+ }
+
+ /**
+ * Checks whether the display is ON.
+ *
+ * @param displayState The display's current state.
+ */
+ public static boolean isDisplayOn(int displayState) {
+ return displayState == Display.STATE_ON ||
+ displayState == Display.STATE_DOZE ||
+ displayState == Display.STATE_DOZE_SUSPEND;
+ }
+
+ /**
+ * Called when UE goes in/out of the foreground.
+ * @param showing true if UE is in the foreground, false otherwise.
+ */
+ public void onUiShowing(boolean showing) {
+
+ //Check UI's old state before updating corresponding state variable(s)
+ final boolean wasShowing = isUiShowing();
+
+ mIsInBackground = !showing;
+
+ //Check UI's new state after updating corresponding state variable(s)
+ final boolean isShowing = isUiShowing();
+
+ Log.d(this, "onUiShowing wasShowing: " + wasShowing + " isShowing: " + isShowing);
+ //notify if there is a change in UI state
+ if (wasShowing != isShowing) {
+ notifyOnUiShowing(showing);
+ }
+ }
+
+ /**
+ * This method overrides onDisplayRemoved method of {@interface DisplayManager.DisplayListener}
+ * Added for completeness. No implementation yet.
+ */
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ }
+
+ /**
+ * This method overrides onDisplayAdded method of {@interface DisplayManager.DisplayListener}
+ * Added for completeness. No implementation yet.
+ */
+ @Override
+ public void onDisplayAdded(int displayId) {
+ }
+
+ /**
+ * This method overrides onDisplayAdded method of {@interface DisplayManager.DisplayListener}
+ * The method gets invoked whenever the properties of a logical display have changed.
+ */
+ @Override
+ public void onDisplayChanged(int displayId) {
+ final int displayState = mDisplayManager.getDisplay(displayId).getState();
+ Log.d(this, "onDisplayChanged displayState: " + displayState +
+ " displayId: " + displayId);
+
+ /* Ignore display changed indications if they are received for displays
+ * other than default display
+ */
+ if (displayId != Display.DEFAULT_DISPLAY) {
+ Log.w(this, "onDisplayChanged Ignoring...");
+ return;
+ }
+
+ //Check UI's old state before updating corresponding state variable(s)
+ final boolean wasShowing = isUiShowing();
+
+ mIsDisplayOn = isDisplayOn(displayState);
+
+ //Check UI's new state after updating corresponding state variable(s)
+ final boolean isShowing = isUiShowing();
+
+ Log.d(this, "onDisplayChanged wasShowing: " + wasShowing + " isShowing: " + isShowing);
+ //notify if there is a change in UI state
+ if (wasShowing != isShowing) {
+ notifyOnUiShowing(mIsDisplayOn);
+ }
+ }
+}
diff --git a/InCallUI/src/com/android/incallui/InCallUiStateNotifierListener.java b/InCallUI/src/com/android/incallui/InCallUiStateNotifierListener.java
new file mode 100644
index 0000000..772f2da
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/InCallUiStateNotifierListener.java
@@ -0,0 +1,38 @@
+/* Copyright (c) 2015, 2016, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.incallui;
+
+/**
+ * Listener interface for any class that wants to be notified when
+ * visibilitiy of InCallUI is changed. For eg. when UE moves in/out of the foreground,
+ * display either turns ON/OFF
+ */
+public interface InCallUiStateNotifierListener {
+ public void onUiShowing(boolean showing);
+}
diff --git a/InCallUI/src/com/android/incallui/VideoCallPresenter.java b/InCallUI/src/com/android/incallui/VideoCallPresenter.java
index 32ad4e8..df4060e 100644
--- a/InCallUI/src/com/android/incallui/VideoCallPresenter.java
+++ b/InCallUI/src/com/android/incallui/VideoCallPresenter.java
@@ -68,7 +68,7 @@
public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi> implements
IncomingCallListener, InCallOrientationListener, InCallStateListener,
InCallDetailsListener, SurfaceChangeListener, VideoEventListener,
- InCallPresenter.InCallEventListener {
+ InCallPresenter.InCallEventListener, InCallUiStateNotifierListener {
public static final String TAG = "VideoCallPresenter";
public static final boolean DEBUG = false;
@@ -191,6 +191,10 @@
private int mAutoFullscreenTimeoutMillis = 0;
/**
+ *Caches information about whether InCall UI is in the background or foreground
+ */
+ private boolean mIsInBackground;
+ /**
* Determines if the countdown is currently running to automatically enter full screen video
* mode.
*/
@@ -240,6 +244,7 @@
// Register for surface and video events from {@link InCallVideoCallListener}s.
InCallVideoCallCallbackNotifier.getInstance().addSurfaceChangeListener(this);
InCallVideoCallCallbackNotifier.getInstance().addVideoEventListener(this);
+ InCallUiStateNotifier.getInstance().addListener(this);
mCurrentVideoState = VideoProfile.STATE_AUDIO_ONLY;
mCurrentCallState = Call.State.INVALID;
@@ -270,6 +275,7 @@
InCallVideoCallCallbackNotifier.getInstance().removeSurfaceChangeListener(this);
InCallVideoCallCallbackNotifier.getInstance().removeVideoEventListener(this);
+ InCallUiStateNotifier.getInstance().removeListener(this);
}
/**
@@ -295,8 +301,8 @@
if (mPreviewSurfaceState == PreviewSurfaceState.CAPABILITIES_RECEIVED) {
mPreviewSurfaceState = PreviewSurfaceState.SURFACE_SET;
mVideoCall.setPreviewSurface(ui.getPreviewVideoSurface());
- } else if (mPreviewSurfaceState == PreviewSurfaceState.NONE && isCameraRequired()){
- enableCamera(mVideoCall, true);
+ } else {
+ maybeEnableCamera();
}
} else if (surface == VideoCallFragment.SURFACE_DISPLAY) {
mVideoCall.setDisplaySurface(ui.getDisplayVideoSurface());
@@ -670,9 +676,9 @@
}
}
- private static boolean isCameraRequired(int videoState) {
- return VideoProfile.isBidirectional(videoState) ||
- VideoProfile.isTransmissionEnabled(videoState);
+ private boolean isCameraRequired(int videoState) {
+ return ((VideoProfile.isBidirectional(videoState) ||
+ VideoProfile.isTransmissionEnabled(videoState)) && !mIsInBackground);
}
private boolean isCameraRequired() {
@@ -827,6 +833,40 @@
}
/**
+ * Opens camera if the camera has not yet been set on the {@link VideoCall}; negotiation has
+ * not yet started and if camera is required
+ */
+ private void maybeEnableCamera() {
+ if (mPreviewSurfaceState == PreviewSurfaceState.NONE && isCameraRequired()) {
+ enableCamera(mVideoCall, true);
+ }
+ }
+
+ /**
+ * This method gets invoked when visibility of InCallUI is changed. For eg.
+ * when UE moves in/out of the foreground, display either turns ON/OFF
+ * @param showing true if InCallUI is visible, false otherwise.
+ */
+ @Override
+ public void onUiShowing(boolean showing) {
+ Log.d(this, "onUiShowing, showing = " + showing + " mPrimaryCall = " + mPrimaryCall +
+ " mPreviewSurfaceState = " + mPreviewSurfaceState);
+
+ mIsInBackground = !showing;
+
+ if (mPrimaryCall == null || !VideoUtils.isActiveVideoCall(mPrimaryCall)) {
+ Log.w(this, "onUiShowing, received for non-active video call");
+ return;
+ }
+
+ if (showing) {
+ maybeEnableCamera();
+ } else if (mPreviewSurfaceState != PreviewSurfaceState.NONE) {
+ enableCamera(mVideoCall, false);
+ }
+ }
+
+ /**
* Handles peer video pause state changes.
*
* @param call The call which paused or un-pausedvideo transmission.
diff --git a/InCallUI/src/com/android/incallui/VideoPauseController.java b/InCallUI/src/com/android/incallui/VideoPauseController.java
index afbb3ee..823b5a6 100644
--- a/InCallUI/src/com/android/incallui/VideoPauseController.java
+++ b/InCallUI/src/com/android/incallui/VideoPauseController.java
@@ -29,8 +29,9 @@
* 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";
+class VideoPauseController implements InCallStateListener, IncomingCallListener,
+ InCallUiStateNotifierListener {
+ private static final String TAG = "VideoPauseController:";
/**
* Keeps track of the current active/foreground call.
@@ -106,6 +107,7 @@
mInCallPresenter = Preconditions.checkNotNull(inCallPresenter);
mInCallPresenter.addListener(this);
mInCallPresenter.addIncomingCallListener(this);
+ InCallUiStateNotifier.getInstance().addListener(this);
}
/**
@@ -114,6 +116,7 @@
*/
public void tearDown() {
log("tearDown...");
+ InCallUiStateNotifier.getInstance().removeListener(this);
mInCallPresenter.removeListener(this);
mInCallPresenter.removeIncomingCallListener(this);
clear();
@@ -242,10 +245,13 @@
}
/**
- * Called when UI goes in/out of the foreground.
- * @param showing true if UI is in the foreground, false otherwise.
+ * This method gets invoked when visibility of InCallUI is changed. For eg.
+ * when UE moves in/out of the foreground, display either turns ON/OFF
+ * @param showing true if InCallUI is visible, false otherwise.
*/
+ @Override
public void onUiShowing(boolean showing) {
+ log("onUiShowing, showing = " + showing);
// Only send pause/unpause requests if we are in the INCALL state.
if (mInCallPresenter == null) {
return;
@@ -266,6 +272,11 @@
private void onResume(boolean isInCall) {
log("onResume");
+ if (!mIsInBackground) {
+ log("onResume, Ignoring... already resumed");
+ return;
+ }
+
mIsInBackground = false;
if (canVideoPause(mPrimaryCallContext) && isInCall) {
sendRequest(mPrimaryCallContext.getCall(), true);
@@ -282,6 +293,11 @@
private void onPause(boolean isInCall) {
log("onPause");
+ if (mIsInBackground) {
+ log("onPause, Ignoring... already paused");
+ return;
+ }
+
mIsInBackground = true;
if (canVideoPause(mPrimaryCallContext) && isInCall) {
sendRequest(mPrimaryCallContext.getCall(), false);