diff --git a/core/api/current.txt b/core/api/current.txt
index a77040c2d95fb..e94b3e1c4a96e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -25552,6 +25552,7 @@ package android.media.projection {
public abstract static class MediaProjection.Callback {
ctor public MediaProjection.Callback();
method public void onCapturedContentResize(int, int);
+ method public void onCapturedContentVisibilityChanged(boolean);
method public void onStop();
}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index bddf4524b63da..7425c95e93370 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1633,12 +1633,6 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "-576580969": {
- "message": "viewServerWindowCommand: bootFinished() failed.",
- "level": "WARN",
- "group": "WM_ERROR",
- "at": "com\/android\/server\/wm\/WindowManagerService.java"
- },
"-576070986": {
"message": "Performing post-rotate rotation after seamless rotation",
"level": "INFO",
@@ -1981,6 +1975,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-254406860": {
+ "message": "Unable to tell MediaProjectionManagerService about visibility change on the active projection: %s",
+ "level": "ERROR",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"-251259736": {
"message": "No longer freezing: %s",
"level": "VERBOSE",
@@ -4177,12 +4177,6 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
- "1903353011": {
- "message": "notifyAppStopped: %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_ADD_REMOVE",
- "at": "com\/android\/server\/wm\/ActivityRecord.java"
- },
"1912291550": {
"message": "Sleep still waiting to pause %s",
"level": "VERBOSE",
diff --git a/media/java/android/media/projection/IMediaProjectionCallback.aidl b/media/java/android/media/projection/IMediaProjectionCallback.aidl
index 2c8de2e4eec1d..147d74cb58f28 100644
--- a/media/java/android/media/projection/IMediaProjectionCallback.aidl
+++ b/media/java/android/media/projection/IMediaProjectionCallback.aidl
@@ -20,4 +20,5 @@ package android.media.projection;
oneway interface IMediaProjectionCallback {
void onStop();
void onCapturedContentResize(int width, int height);
+ void onCapturedContentVisibilityChanged(boolean isVisible);
}
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index a63d02bb11103..99d1f8dccb671 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -48,7 +48,11 @@ interface IMediaProjectionManager {
void notifyActiveProjectionCapturedContentResized(int width, int height);
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
- + ".permission.MANAGE_MEDIA_PROJECTION)")
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
+ void notifyActiveProjectionCapturedContentVisibilityChanged(boolean isVisible);
+
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
void addCallback(IMediaProjectionWatcherCallback callback);
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 3dfff1fbfc1b7..985ac3ca7d6ec 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -66,13 +66,20 @@ public final class MediaProjection {
}
}
- /** Register a listener to receive notifications about when the {@link
- * MediaProjection} changes state.
+ /**
+ * Register a listener to receive notifications about when the {@link MediaProjection} or
+ * captured content changes state.
+ *
+ * The callback should be registered before invoking
+ * {@link #createVirtualDisplay(String, int, int, int, int, Surface, VirtualDisplay.Callback,
+ * Handler)}
+ * to ensure that any notifications on the callback are not missed.
+ *
*
* @param callback The callback to call.
- * @param handler The handler on which the callback should be invoked, or
- * null if the callback should be invoked on the calling thread's looper.
- *
+ * @param handler The handler on which the callback should be invoked, or
+ * null if the callback should be invoked on the calling thread's looper.
+ * @throws IllegalArgumentException If the given callback is null.
* @see #unregisterCallback
*/
public void registerCallback(Callback callback, Handler handler) {
@@ -85,10 +92,11 @@ public final class MediaProjection {
mCallbacks.put(callback, new CallbackRecord(callback, handler));
}
- /** Unregister a MediaProjection listener.
+ /**
+ * Unregister a {@link MediaProjection} listener.
*
* @param callback The callback to unregister.
- *
+ * @throws IllegalArgumentException If the given callback is null.
* @see #registerCallback
*/
public void unregisterCallback(Callback callback) {
@@ -283,6 +291,34 @@ public final class MediaProjection {
* }
*/
public void onCapturedContentResize(int width, int height) { }
+
+ /**
+ * Indicates the visibility of the captured region has changed. Called immediately after
+ * capture begins with the initial visibility state, and when visibility changes. Provides
+ * the app with accurate state for presenting its own UI. The application can take advantage
+ * of this by showing or hiding the captured content, based on if the captured region is
+ * currently visible to the user.
+ *
+ * For example, if the user elected to capture a single app (from the activity shown from
+ * {@link MediaProjectionManager#createScreenCaptureIntent()}), the callback may be
+ * triggered for the following reasons:
+ *
+ * -
+ * The captured region may become visible ({@code isVisible} with value
+ * {@code true}), because the captured app is at least partially visible. This may
+ * happen if the captured app was previously covered by another app. The other app
+ * moves to show at least some portion of the captured app.
+ *
+ * -
+ * The captured region may become invisible ({@code isVisible} with value
+ * {@code false}) if it is entirely hidden. This may happen if the captured app is
+ * entirely covered by another app, or the user navigates away from the captured
+ * app.
+ *
+ *
+ *
+ */
+ public void onCapturedContentVisibilityChanged(boolean isVisible) { }
}
private final class MediaProjectionCallback extends IMediaProjectionCallback.Stub {
@@ -299,6 +335,13 @@ public final class MediaProjection {
cbr.onCapturedContentResize(width, height);
}
}
+
+ @Override
+ public void onCapturedContentVisibilityChanged(boolean isVisible) {
+ for (CallbackRecord cbr : mCallbacks.values()) {
+ cbr.onCapturedContentVisibilityChanged(isVisible);
+ }
+ }
}
private final static class CallbackRecord {
@@ -322,5 +365,9 @@ public final class MediaProjection {
public void onCapturedContentResize(int width, int height) {
mHandler.post(() -> mCallback.onCapturedContentResize(width, height));
}
+
+ public void onCapturedContentVisibilityChanged(boolean isVisible) {
+ mHandler.post(() -> mCallback.onCapturedContentVisibilityChanged(isVisible));
+ }
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index b505396000ddf..43c8032db5467 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -11510,6 +11510,11 @@ public class AudioService extends IAudioService.Stub
public void onCapturedContentResize(int width, int height) {
// Ignore resize of the captured content.
}
+
+ @Override
+ public void onCapturedContentVisibilityChanged(boolean isVisible) {
+ // Ignore visibility changes of the captured content.
+ }
};
UnregisterOnStopCallback mProjectionCallback;
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 5a5be4eeb796d..ddeaa1b8e125a 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -621,6 +621,13 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
// expect), and there will still be letterboxing on the output content since the
// Surface and VirtualDisplay would then have different aspect ratios.
}
+
+ @Override
+ public void onCapturedContentVisibilityChanged(boolean isVisible) {
+ // Do nothing when we tell the client that the content has a visibility change - it is
+ // up to them to decide to pause recording, and update their own UI, depending on their
+ // use case.
+ }
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 50e1fca138776..e9ee75076b860 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -370,6 +370,26 @@ public final class MediaProjectionManagerService extends SystemService
}
}
+ @Override
+ public void notifyActiveProjectionCapturedContentVisibilityChanged(boolean isVisible) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to notify "
+ + "on captured content resize");
+ }
+ if (!isValidMediaProjection(mProjectionGrant)) {
+ return;
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (mProjectionGrant != null && mCallbackDelegate != null) {
+ mCallbackDelegate.dispatchVisibilityChanged(mProjectionGrant, isVisible);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
@Override //Binder call
public void addCallback(final IMediaProjectionWatcherCallback callback) {
if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
@@ -750,8 +770,9 @@ public final class MediaProjectionManagerService extends SystemService
public void dispatchResize(MediaProjection projection, int width, int height) {
if (projection == null) {
- Slog.e(TAG, "Tried to dispatch stop notification for a null media projection."
- + " Ignoring!");
+ Slog.e(TAG,
+ "Tried to dispatch resize notification for a null media projection. "
+ + "Ignoring!");
return;
}
synchronized (mLock) {
@@ -774,6 +795,36 @@ public final class MediaProjectionManagerService extends SystemService
// is for passing along if recording is still ongoing or not.
}
}
+
+ public void dispatchVisibilityChanged(MediaProjection projection, boolean isVisible) {
+ if (projection == null) {
+ Slog.e(TAG,
+ "Tried to dispatch visibility changed notification for a null media "
+ + "projection. Ignoring!");
+ return;
+ }
+ synchronized (mLock) {
+ // TODO(b/249827847) Currently the service assumes there is only one projection
+ // at once - need to find the callback for the given projection, when there are
+ // multiple sessions.
+ for (IMediaProjectionCallback callback : mClientCallbacks.values()) {
+ mHandler.post(() -> {
+ try {
+ // Notify every callback the client has registered for a particular
+ // MediaProjection instance.
+ callback.onCapturedContentVisibilityChanged(isVisible);
+ } catch (RemoteException e) {
+ Slog.w(TAG,
+ "Failed to notify media projection has captured content "
+ + "visibility change to "
+ + isVisible, e);
+ }
+ });
+ }
+ // Do not need to notify watcher callback about visibility changes, since watcher
+ // callback is for passing along if recording is still ongoing or not.
+ }
+ }
}
private static final class WatcherStartCallback implements Runnable {
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 8d5d0d5c1ce25..af135b7d7710a 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -211,6 +211,7 @@ final class ContentRecorder implements WindowContainerListener {
* Stops recording on this DisplayContent, and updates the session details.
*/
void stopRecording() {
+ unregisterListener();
if (mRecordedSurface != null) {
// Do not wait for the mirrored surface to be garbage collected, but clean up
// immediately.
@@ -227,7 +228,7 @@ final class ContentRecorder implements WindowContainerListener {
* Ensure recording does not fall back to the display stack; ensure the recording is stopped
* and the client notified by tearing down the virtual display.
*/
- void stopMediaProjection() {
+ private void stopMediaProjection() {
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
"Stop MediaProjection on virtual display %d", mDisplayContent.getDisplayId());
if (mMediaProjectionManager != null) {
@@ -247,6 +248,16 @@ final class ContentRecorder implements WindowContainerListener {
null, mDisplayContent.mWmService);
}
+ private void unregisterListener() {
+ Task recordedTask = mRecordedWindowContainer != null
+ ? mRecordedWindowContainer.asTask() : null;
+ if (recordedTask == null || !isRecordingContentTask()) {
+ return;
+ }
+ recordedTask.unregisterWindowContainerListener(this);
+ mRecordedWindowContainer = null;
+ }
+
/**
* Start recording to this DisplayContent if it does not have its own content. Captures the
* content of a WindowContainer indicated by a WindowToken. If unable to start recording, falls
@@ -301,6 +312,13 @@ final class ContentRecorder implements WindowContainerListener {
// Retrieve the size of the DisplayArea to mirror.
updateMirroredSurface(transaction, mRecordedWindowContainer.getBounds(), surfaceSize);
+ // Notify the client about the visibility of the mirrored region, now that we have begun
+ // capture.
+ if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) {
+ mMediaProjectionManager.notifyActiveProjectionCapturedContentVisibilityChanged(
+ mRecordedWindowContainer.asTask().isVisibleRequested());
+ }
+
// No need to clean up. In SurfaceFlinger, parents hold references to their children. The
// mirrored SurfaceControl is alive since the parent DisplayContent SurfaceControl is
// holding a reference to it. Therefore, the mirrored SurfaceControl will be cleaned up
@@ -389,6 +407,7 @@ final class ContentRecorder implements WindowContainerListener {
*/
private void handleStartRecordingFailed() {
final boolean shouldExitTaskRecording = isRecordingContentTask();
+ unregisterListener();
clearContentRecordingSession();
if (shouldExitTaskRecording) {
// Clean up the cached session first to ensure recording doesn't re-start, since
@@ -478,12 +497,7 @@ final class ContentRecorder implements WindowContainerListener {
"Recorded task is removed, so stop recording on display %d",
mDisplayContent.getDisplayId());
- Task recordedTask = mRecordedWindowContainer != null
- ? mRecordedWindowContainer.asTask() : null;
- if (recordedTask == null || !isRecordingContentTask()) {
- return;
- }
- recordedTask.unregisterWindowContainerListener(this);
+ unregisterListener();
// Stop mirroring and teardown.
clearContentRecordingSession();
// Clean up the cached session first to ensure recording doesn't re-start, since
@@ -501,9 +515,20 @@ final class ContentRecorder implements WindowContainerListener {
mLastOrientation = mergedOverrideConfiguration.orientation;
}
+ // WindowContainerListener
+ @Override
+ public void onVisibleRequestedChanged(boolean isVisibleRequested) {
+ // Check still recording just to be safe.
+ if (isCurrentlyRecording() && mLastRecordedBounds != null) {
+ mMediaProjectionManager.notifyActiveProjectionCapturedContentVisibilityChanged(
+ isVisibleRequested);
+ }
+ }
+
@VisibleForTesting interface MediaProjectionManagerWrapper {
void stopActiveProjection();
void notifyActiveProjectionCapturedContentResized(int width, int height);
+ void notifyActiveProjectionCapturedContentVisibilityChanged(boolean isVisible);
}
private static final class RemoteMediaProjectionManagerWrapper implements
@@ -543,6 +568,23 @@ final class ContentRecorder implements WindowContainerListener {
}
}
+ @Override
+ public void notifyActiveProjectionCapturedContentVisibilityChanged(boolean isVisible) {
+ fetchMediaProjectionManager();
+ if (mIMediaProjectionManager == null) {
+ return;
+ }
+ try {
+ mIMediaProjectionManager.notifyActiveProjectionCapturedContentVisibilityChanged(
+ isVisible);
+ } catch (RemoteException e) {
+ ProtoLog.e(WM_DEBUG_CONTENT_RECORDING,
+ "Unable to tell MediaProjectionManagerService about visibility change on "
+ + "the active projection: %s",
+ e);
+ }
+ }
+
private void fetchMediaProjectionManager() {
if (mIMediaProjectionManager != null) {
return;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 044357b978961..a8d26d6a60458 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -6504,15 +6504,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
}
- /**
- * The MediaProjection instance is torn down.
- */
- @VisibleForTesting void stopMediaProjection() {
- if (mContentRecorder != null) {
- mContentRecorder.stopMediaProjection();
- }
- }
-
/**
* Sets the incoming recording session. Should only be used when starting to record on
* this display; stopping recording is handled separately when the display is destroyed.
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index ce41ae74c7eab..67370524dc5e1 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1305,6 +1305,11 @@ class WindowContainer extends ConfigurationContainer<
if (parent != null) {
parent.onChildVisibleRequestedChanged(this);
}
+
+ // Notify listeners about visibility change.
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ mListeners.get(i).onVisibleRequestedChanged(mVisibleRequested);
+ }
return true;
}
diff --git a/services/core/java/com/android/server/wm/WindowContainerListener.java b/services/core/java/com/android/server/wm/WindowContainerListener.java
index ac1fe173dd091..c1ee254c20f59 100644
--- a/services/core/java/com/android/server/wm/WindowContainerListener.java
+++ b/services/core/java/com/android/server/wm/WindowContainerListener.java
@@ -27,4 +27,13 @@ interface WindowContainerListener extends ConfigurationContainerListener {
/** Called when {@link WindowContainer#removeImmediately()} is invoked. */
default void onRemoved() {}
+
+ /**
+ * Only invoked if the child successfully requested a visibility change.
+ *
+ * @param isVisibleRequested The current {@link WindowContainer#isVisibleRequested()} of this
+ * {@link WindowContainer} (not of the child).
+ * @see WindowContainer#onChildVisibleRequestedChanged(WindowContainer)
+ */
+ default void onVisibleRequestedChanged(boolean isVisibleRequested) { }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index 92dd047b55379..4ad851669c8d4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
import static android.view.Display.INVALID_DISPLAY;
@@ -280,6 +279,64 @@ public class ContentRecorderTests extends WindowTestsBase {
recordedWidth, recordedHeight);
}
+ @Test
+ public void testStartRecording_notifiesCallback() {
+ // WHEN a recording is ongoing.
+ mContentRecorder.setContentRecordingSession(mTaskSession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+ // THEN the visibility change callback is notified.
+ verify(mMediaProjectionManagerWrapper)
+ .notifyActiveProjectionCapturedContentVisibilityChanged(true);
+ }
+
+ @Test
+ public void testOnVisibleRequestedChanged_notifiesCallback() {
+ // WHEN a recording is ongoing.
+ mContentRecorder.setContentRecordingSession(mTaskSession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+ // WHEN the child requests a visibility change.
+ boolean isVisibleRequested = true;
+ mContentRecorder.onVisibleRequestedChanged(isVisibleRequested);
+
+ // THEN the visibility change callback is notified.
+ verify(mMediaProjectionManagerWrapper, atLeastOnce())
+ .notifyActiveProjectionCapturedContentVisibilityChanged(isVisibleRequested);
+
+ // WHEN the child requests a visibility change.
+ isVisibleRequested = false;
+ mContentRecorder.onVisibleRequestedChanged(isVisibleRequested);
+
+ // THEN the visibility change callback is notified.
+ verify(mMediaProjectionManagerWrapper)
+ .notifyActiveProjectionCapturedContentVisibilityChanged(isVisibleRequested);
+ }
+
+ @Test
+ public void testOnVisibleRequestedChanged_noRecording_doesNotNotifyCallback() {
+ // WHEN a recording is not ongoing.
+ assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
+
+ // WHEN the child requests a visibility change.
+ boolean isVisibleRequested = true;
+ mContentRecorder.onVisibleRequestedChanged(isVisibleRequested);
+
+ // THEN the visibility change callback is not notified.
+ verify(mMediaProjectionManagerWrapper, never())
+ .notifyActiveProjectionCapturedContentVisibilityChanged(isVisibleRequested);
+
+ // WHEN the child requests a visibility change.
+ isVisibleRequested = false;
+ mContentRecorder.onVisibleRequestedChanged(isVisibleRequested);
+
+ // THEN the visibility change callback is not notified.
+ verify(mMediaProjectionManagerWrapper, never())
+ .notifyActiveProjectionCapturedContentVisibilityChanged(isVisibleRequested);
+ }
+
@Test
public void testPauseRecording_pausesRecording() {
mContentRecorder.setContentRecordingSession(mDisplaySession);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index d583e898b1a80..1a1ca543f9d28 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -56,6 +56,8 @@ import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -659,6 +661,111 @@ public class WindowContainerTests extends WindowTestsBase {
assertEquals(SCREEN_ORIENTATION_PORTRAIT, root.getOrientation());
}
+ @Test
+ public void testSetVisibleRequested() {
+ final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).setLayer(
+ 0).build());
+ assertThat(root.isVisibleRequested()).isFalse();
+ final TestWindowContainerListener listener = new TestWindowContainerListener();
+ root.registerWindowContainerListener(listener);
+
+ assertThat(root.setVisibleRequested(/* isVisible= */ false)).isFalse();
+ assertThat(root.isVisibleRequested()).isFalse();
+
+ assertThat(root.setVisibleRequested(/* isVisible= */ true)).isTrue();
+ assertThat(root.isVisibleRequested()).isTrue();
+ assertThat(listener.mIsVisibleRequested).isTrue();
+ }
+
+ @Test
+ public void testSetVisibleRequested_childRequestsVisible() {
+ final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).setLayer(
+ 0).build());
+ final TestWindowContainer child1 = root.addChildWindow();
+ assertThat(child1.isVisibleRequested()).isFalse();
+ final TestWindowContainerListener listener = new TestWindowContainerListener();
+ root.registerWindowContainerListener(listener);
+
+ // Hidden root and child request hidden.
+ assertThat(root.setVisibleRequested(/* isVisible= */ false)).isFalse();
+ assertThat(listener.mIsVisibleRequested).isFalse();
+ assertThat(child1.isVisibleRequested()).isFalse();
+
+ // Child requests to be visible, so child and root request visible.
+ assertThat(child1.setVisibleRequested(/* isVisible= */ true)).isTrue();
+ assertThat(root.isVisibleRequested()).isTrue();
+ assertThat(listener.mIsVisibleRequested).isTrue();
+ assertThat(child1.isVisibleRequested()).isTrue();
+ // Visible request didn't change.
+ assertThat(child1.setVisibleRequested(/* isVisible= */ true)).isFalse();
+ verify(root, times(2)).onChildVisibleRequestedChanged(child1);
+ }
+
+ @Test
+ public void testSetVisibleRequested_childRequestsHidden() {
+ final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).setLayer(
+ 0).build());
+ final TestWindowContainer child1 = root.addChildWindow();
+ assertThat(child1.isVisibleRequested()).isFalse();
+ final TestWindowContainerListener listener = new TestWindowContainerListener();
+ root.registerWindowContainerListener(listener);
+
+ // Root and child requests visible.
+ assertThat(root.setVisibleRequested(/* isVisible= */ true)).isTrue();
+ assertThat(listener.mIsVisibleRequested).isTrue();
+ assertThat(child1.setVisibleRequested(/* isVisible= */ true)).isTrue();
+ assertThat(child1.isVisibleRequested()).isTrue();
+
+ // Child requests hidden, so child and root request hidden.
+ assertThat(child1.setVisibleRequested(/* isVisible= */ false)).isTrue();
+ assertThat(root.isVisibleRequested()).isFalse();
+ assertThat(listener.mIsVisibleRequested).isFalse();
+ assertThat(child1.isVisibleRequested()).isFalse();
+ // Visible request didn't change.
+ assertThat(child1.setVisibleRequested(/* isVisible= */ false)).isFalse();
+ verify(root, times(3)).onChildVisibleRequestedChanged(child1);
+ }
+
+ @Test
+ public void testOnChildVisibleRequestedChanged_bothVisible() {
+ final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).setLayer(
+ 0).build());
+ final TestWindowContainer child1 = root.addChildWindow();
+
+ // Child and root request visible.
+ assertThat(root.setVisibleRequested(/* isVisible= */ true)).isTrue();
+ assertThat(child1.setVisibleRequested(/* isVisible= */ true)).isTrue();
+
+ // Visible request already updated on root when child requested.
+ assertThat(root.onChildVisibleRequestedChanged(child1)).isFalse();
+ }
+
+ @Test
+ public void testOnChildVisibleRequestedChanged_childVisible() {
+ final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).setLayer(
+ 0).build());
+ final TestWindowContainer child1 = root.addChildWindow();
+
+ assertThat(root.setVisibleRequested(/* isVisible= */ false)).isFalse();
+ assertThat(child1.setVisibleRequested(/* isVisible= */ true)).isTrue();
+
+ // Visible request already updated on root when child requested.
+ assertThat(root.onChildVisibleRequestedChanged(child1)).isFalse();
+ }
+
+ @Test
+ public void testOnChildVisibleRequestedChanged_childHidden() {
+ final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).setLayer(
+ 0).build());
+ final TestWindowContainer child1 = root.addChildWindow();
+
+ assertThat(root.setVisibleRequested(/* isVisible= */ false)).isFalse();
+ assertThat(child1.setVisibleRequested(/* isVisible= */ false)).isFalse();
+
+ // Visible request did not change.
+ assertThat(root.onChildVisibleRequestedChanged(child1)).isFalse();
+ }
+
@Test
public void testSetOrientation() {
final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).build());
@@ -1656,6 +1763,7 @@ public class WindowContainerTests extends WindowTestsBase {
private static class TestWindowContainerListener implements WindowContainerListener {
private Configuration mConfiguration = new Configuration();
private DisplayContent mDisplayContent;
+ private boolean mIsVisibleRequested;
@Override
public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
@@ -1666,5 +1774,10 @@ public class WindowContainerTests extends WindowTestsBase {
public void onDisplayChanged(DisplayContent dc) {
mDisplayContent = dc;
}
+
+ @Override
+ public void onVisibleRequestedChanged(boolean isVisibleRequested) {
+ mIsVisibleRequested = isVisibleRequested;
+ }
}
}