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);