From a582dde3b59440a20245b5e259f66d2c118dda57 Mon Sep 17 00:00:00 2001 From: dakinola <dakinola@google.com> Date: Wed, 25 Oct 2023 10:59:08 +0000 Subject: [PATCH] Report MediaProjectionTargetChanged Atom Update ContentRecorder & MediaProjectionManagerService to log a MediaProjectionTargetChanged atom upon recording starting and further updates to windowing mode. Bug: 304728422 Test: atest WmTests:ContentRecorderTests Test: atest FrameworksServicesTests:MediaProjectionManagerServiceTest Test: atest FrameworksServicesTests:MediaProjectionMetricsLoggerTest Change-Id: I5120ba2571fb2e6e084e72c4fd079767530ccdeb --- data/etc/services.core.protolog.json | 6 + .../projection/IMediaProjectionManager.aidl | 5 + .../projection/FrameworkStatsLogWrapper.java | 21 +- .../MediaProjectionManagerService.java | 26 +++ .../MediaProjectionMetricsLogger.java | 114 +++++++++- .../android/server/wm/ContentRecorder.java | 47 +++- .../com/android/server/wm/DisplayContent.java | 3 +- .../MediaProjectionManagerServiceTest.java | 44 +++- .../MediaProjectionMetricsLoggerTest.java | 203 ++++++++++++++++-- .../server/wm/ContentRecorderTests.java | 71 +++++- 10 files changed, 486 insertions(+), 54 deletions(-) diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 87be13ab4a93..dc2b0561957d 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -2653,6 +2653,12 @@ "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/TransitionController.java" }, + "261227010": { + "message": "Content Recording: Unable to tell log windowing mode change: %s", + "level": "ERROR", + "group": "WM_DEBUG_CONTENT_RECORDING", + "at": "com\/android\/server\/wm\/ContentRecorder.java" + }, "269576220": { "message": "Resuming rotation after drag", "level": "DEBUG", diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl index 24efbd14bc02..a7ec6c692416 100644 --- a/media/java/android/media/projection/IMediaProjectionManager.aidl +++ b/media/java/android/media/projection/IMediaProjectionManager.aidl @@ -212,4 +212,9 @@ interface IMediaProjectionManager { @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + ".permission.MANAGE_MEDIA_PROJECTION)") oneway void notifyAppSelectorDisplayed(int hostUid); + + @EnforcePermission("MANAGE_MEDIA_PROJECTION") + @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + + ".permission.MANAGE_MEDIA_PROJECTION)") + void notifyWindowingModeChanged(int contentToRecord, int targetUid, int windowingMode); } diff --git a/services/core/java/com/android/server/media/projection/FrameworkStatsLogWrapper.java b/services/core/java/com/android/server/media/projection/FrameworkStatsLogWrapper.java index 5bad06777328..6c74cba99bcb 100644 --- a/services/core/java/com/android/server/media/projection/FrameworkStatsLogWrapper.java +++ b/services/core/java/com/android/server/media/projection/FrameworkStatsLogWrapper.java @@ -21,8 +21,8 @@ import com.android.internal.util.FrameworkStatsLog; /** Wrapper around {@link FrameworkStatsLog} */ public class FrameworkStatsLogWrapper { - /** Wrapper around {@link FrameworkStatsLog#write}. */ - public void write( + /** Wrapper around {@link FrameworkStatsLog#write} for MediaProjectionStateChanged atom. */ + public void writeStateChanged( int code, int sessionId, int state, @@ -41,4 +41,21 @@ public class FrameworkStatsLogWrapper { timeSinceLastActive, creationSource); } + + /** Wrapper around {@link FrameworkStatsLog#write} for MediaProjectionTargetChanged atom. */ + public void writeTargetChanged( + int code, + int sessionId, + int targetType, + int hostUid, + int targetUid, + int windowingMode) { + FrameworkStatsLog.write( + code, + sessionId, + targetType, + hostUid, + targetUid, + windowingMode); + } } 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 893ed6119f9f..6deda468f9a2 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -479,6 +479,18 @@ public final class MediaProjectionManagerService extends SystemService mMediaProjectionMetricsLogger.logAppSelectorDisplayed(hostUid); } + @VisibleForTesting + void notifyWindowingModeChanged(int contentToRecord, int targetUid, int windowingMode) { + synchronized (mLock) { + if (mProjectionGrant == null) { + Slog.i(TAG, "Cannot log MediaProjectionTargetChanged atom due to null projection"); + } else { + mMediaProjectionMetricsLogger.logChangedWindowingMode( + contentToRecord, mProjectionGrant.uid, targetUid, windowingMode); + } + } + } + /** * Handles result of dialog shown from * {@link BinderService#buildReviewGrantedConsentIntentLocked()}. @@ -904,6 +916,20 @@ public final class MediaProjectionManagerService extends SystemService } } + @Override // Binder call + @EnforcePermission(MANAGE_MEDIA_PROJECTION) + public void notifyWindowingModeChanged( + int contentToRecord, int targetUid, int windowingMode) { + notifyWindowingModeChanged_enforcePermission(); + final long token = Binder.clearCallingIdentity(); + try { + MediaProjectionManagerService.this.notifyWindowingModeChanged( + contentToRecord, targetUid, windowingMode); + } finally { + Binder.restoreCallingIdentity(token); + } + } + @Override // Binder call public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java index d7fefeb0b1fe..be2a25a755a5 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java @@ -16,16 +16,32 @@ package com.android.server.media.projection; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY; +import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK; + import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN; import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED; import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS; import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED; import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED; import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED; +import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED__TARGET_TYPE__TARGET_TYPE_APP_TASK; +import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED__TARGET_TYPE__TARGET_TYPE_DISPLAY; +import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED__TARGET_TYPE__TARGET_TYPE_UNKNOWN; +import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED__TARGET_WINDOWING_MODE__WINDOWING_MODE_FREEFORM; +import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED__TARGET_WINDOWING_MODE__WINDOWING_MODE_FULLSCREEN; +import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED__TARGET_WINDOWING_MODE__WINDOWING_MODE_SPLIT_SCREEN; +import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED__TARGET_WINDOWING_MODE__WINDOWING_MODE_UNKNOWN; +import android.app.WindowConfiguration.WindowingMode; import android.content.Context; import android.util.Log; +import android.view.ContentRecordingSession.RecordContent; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; import java.time.Duration; @@ -91,7 +107,7 @@ public class MediaProjectionMetricsLogger { durationSinceLastActiveSession == null ? TIME_SINCE_LAST_ACTIVE_UNKNOWN : (int) durationSinceLastActiveSession.toSeconds(); - write( + writeStateChanged( mSessionIdGenerator.createAndGetNewSessionId(), MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED, hostUid, @@ -102,13 +118,13 @@ public class MediaProjectionMetricsLogger { /** * Logs that the user entered the setup flow and permission dialog is displayed. This state is - * not sent when the permission is already granted and we skipped showing the permission dialog. + * not sent when the permission is already granted, and we skipped showing the permission dialog. * * @param hostUid UID of the package that initiates MediaProjection. */ public void logPermissionRequestDisplayed(int hostUid) { Log.d(TAG, "logPermissionRequestDisplayed"); - write( + writeStateChanged( mSessionIdGenerator.getCurrentSessionId(), MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED, hostUid, @@ -123,7 +139,7 @@ public class MediaProjectionMetricsLogger { * @param hostUid UID of the package that initiates MediaProjection. */ public void logProjectionPermissionRequestCancelled(int hostUid) { - write( + writeStateChanged( mSessionIdGenerator.getCurrentSessionId(), FrameworkStatsLog .MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CANCELLED, @@ -141,7 +157,7 @@ public class MediaProjectionMetricsLogger { */ public void logAppSelectorDisplayed(int hostUid) { Log.d(TAG, "logAppSelectorDisplayed"); - write( + writeStateChanged( mSessionIdGenerator.getCurrentSessionId(), MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED, hostUid, @@ -158,7 +174,7 @@ public class MediaProjectionMetricsLogger { */ public void logInProgress(int hostUid, int targetUid) { Log.d(TAG, "logInProgress"); - write( + writeStateChanged( mSessionIdGenerator.getCurrentSessionId(), MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS, hostUid, @@ -167,6 +183,54 @@ public class MediaProjectionMetricsLogger { MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN); } + /** + * Logs that the windowing mode of a projection has changed. + * + * @param contentToRecord ContentRecordingSession.RecordContent indicating whether it is a + * task capture or display capture - gets converted to the corresponding + * TargetType before being logged. + * @param hostUid UID of the package that initiates MediaProjection. + * @param targetUid UID of the package that is captured if selected. + * @param windowingMode Updated WindowConfiguration.WindowingMode of the captured region - gets + * converted to the corresponding TargetWindowingMode before being logged. + */ + public void logChangedWindowingMode( + int contentToRecord, int hostUid, int targetUid, int windowingMode) { + Log.d(TAG, "logChangedWindowingMode"); + writeTargetChanged( + mSessionIdGenerator.getCurrentSessionId(), + contentToRecordToTargetType(contentToRecord), + hostUid, + targetUid, + windowingModeToTargetWindowingMode(windowingMode)); + + } + + @VisibleForTesting + public int contentToRecordToTargetType(@RecordContent int recordContentType) { + return switch (recordContentType) { + case RECORD_CONTENT_DISPLAY -> + MEDIA_PROJECTION_TARGET_CHANGED__TARGET_TYPE__TARGET_TYPE_DISPLAY; + case RECORD_CONTENT_TASK -> + MEDIA_PROJECTION_TARGET_CHANGED__TARGET_TYPE__TARGET_TYPE_APP_TASK; + default -> MEDIA_PROJECTION_TARGET_CHANGED__TARGET_TYPE__TARGET_TYPE_UNKNOWN; + }; + } + + @VisibleForTesting + public int windowingModeToTargetWindowingMode(@WindowingMode int windowingMode) { + return switch (windowingMode) { + case WINDOWING_MODE_FULLSCREEN -> + MEDIA_PROJECTION_TARGET_CHANGED__TARGET_WINDOWING_MODE__WINDOWING_MODE_FULLSCREEN; + case WINDOWING_MODE_FREEFORM -> + MEDIA_PROJECTION_TARGET_CHANGED__TARGET_WINDOWING_MODE__WINDOWING_MODE_FREEFORM; + case WINDOWING_MODE_MULTI_WINDOW -> + MEDIA_PROJECTION_TARGET_CHANGED__TARGET_WINDOWING_MODE__WINDOWING_MODE_SPLIT_SCREEN; + default -> + MEDIA_PROJECTION_TARGET_CHANGED__TARGET_WINDOWING_MODE__WINDOWING_MODE_UNKNOWN; + }; + } + /** * Logs that the capturing stopped, either normally or because of error. * @@ -178,7 +242,7 @@ public class MediaProjectionMetricsLogger { mPreviousState == MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS; Log.d(TAG, "logStopped: wasCaptureInProgress=" + wasCaptureInProgress); - write( + writeStateChanged( mSessionIdGenerator.getCurrentSessionId(), MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED, hostUid, @@ -191,14 +255,31 @@ public class MediaProjectionMetricsLogger { } } - private void write( + public void notifyProjectionStateChange(int hostUid, int state, int sessionCreationSource) { + writeStateChanged(hostUid, state, sessionCreationSource); + } + + private void writeStateChanged(int hostUid, int state, int sessionCreationSource) { + mFrameworkStatsLogWrapper.writeStateChanged( + /* code */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED, + /* session_id */ 123, + /* state */ state, + /* previous_state */ FrameworkStatsLog + .MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN, + /* host_uid */ hostUid, + /* target_uid */ -1, + /* time_since_last_active */ 0, + /* creation_source */ sessionCreationSource); + } + + private void writeStateChanged( int sessionId, int state, int hostUid, int targetUid, int timeSinceLastActive, int creationSource) { - mFrameworkStatsLogWrapper.write( + mFrameworkStatsLogWrapper.writeStateChanged( /* code */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED, sessionId, state, @@ -209,4 +290,19 @@ public class MediaProjectionMetricsLogger { creationSource); mPreviousState = state; } + + private void writeTargetChanged( + int sessionId, + int targetType, + int hostUid, + int targetUid, + int targetWindowingMode) { + mFrameworkStatsLogWrapper.writeTargetChanged( + /* code */ FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED, + sessionId, + targetType, + hostUid, + targetUid, + targetWindowingMode); + } } diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java index 022ef6152929..8717098ff8e8 100644 --- a/services/core/java/com/android/server/wm/ContentRecorder.java +++ b/services/core/java/com/android/server/wm/ContentRecorder.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Context.MEDIA_PROJECTION_SERVICE; import static android.content.res.Configuration.ORIENTATION_UNDEFINED; import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY; @@ -100,6 +101,8 @@ final class ContentRecorder implements WindowContainerListener { @Configuration.Orientation private int mLastOrientation = ORIENTATION_UNDEFINED; + private int mLastWindowingMode = WINDOWING_MODE_UNDEFINED; + private final boolean mCorrectForAnisotropicPixels; ContentRecorder(@NonNull DisplayContent displayContent) { @@ -156,7 +159,8 @@ final class ContentRecorder implements WindowContainerListener { * Handle a configuration change on the display content, and resize recording if needed. * @param lastOrientation the prior orientation of the configuration */ - void onConfigurationChanged(@Configuration.Orientation int lastOrientation) { + void onConfigurationChanged( + @Configuration.Orientation int lastOrientation, int lastWindowingMode) { // Update surface for MediaProjection, if this DisplayContent is being used for recording. if (!isCurrentlyRecording() || mLastRecordedBounds == null) { return; @@ -185,6 +189,16 @@ final class ContentRecorder implements WindowContainerListener { } } + // Record updated windowing mode, if necessary. + int recordedContentWindowingMode = mRecordedWindowContainer.getWindowingMode(); + if (lastWindowingMode != recordedContentWindowingMode) { + mMediaProjectionManager.notifyWindowingModeChanged( + mContentRecordingSession.getContentToRecord(), + mContentRecordingSession.getTargetUid(), + recordedContentWindowingMode + ); + } + ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, "Content Recording: Display %d was already recording, so apply " + "transformations if necessary", @@ -327,8 +341,10 @@ final class ContentRecorder implements WindowContainerListener { return; } + final int contentToRecord = mContentRecordingSession.getContentToRecord(); + // TODO(b/297514518) Do not start capture if the app is in PIP, the bounds are inaccurate. - if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) { + if (contentToRecord == RECORD_CONTENT_TASK) { if (mRecordedWindowContainer.asTask().inPinnedWindowingMode()) { ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, "Content Recording: Display %d should start recording, but " @@ -375,7 +391,7 @@ final class ContentRecorder implements WindowContainerListener { // Notify the client about the visibility of the mirrored region, now that we have begun // capture. - if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) { + if (contentToRecord == RECORD_CONTENT_TASK) { mMediaProjectionManager.notifyActiveProjectionCapturedContentVisibilityChanged( mRecordedWindowContainer.asTask().isVisibleRequested()); } else { @@ -385,6 +401,11 @@ final class ContentRecorder implements WindowContainerListener { currentDisplayState != DISPLAY_STATE_OFF); } + // Record initial windowing mode after recording starts. + mMediaProjectionManager.notifyWindowingModeChanged( + contentToRecord, mContentRecordingSession.getTargetUid(), + mRecordedWindowContainer.getWindowConfiguration().getWindowingMode()); + // 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 @@ -617,8 +638,9 @@ final class ContentRecorder implements WindowContainerListener { Configuration mergedOverrideConfiguration) { WindowContainerListener.super.onMergedOverrideConfigurationChanged( mergedOverrideConfiguration); - onConfigurationChanged(mLastOrientation); + onConfigurationChanged(mLastOrientation, mLastWindowingMode); mLastOrientation = mergedOverrideConfiguration.orientation; + mLastWindowingMode = mergedOverrideConfiguration.windowConfiguration.getWindowingMode(); } // WindowContainerListener @@ -635,6 +657,7 @@ final class ContentRecorder implements WindowContainerListener { void stopActiveProjection(); void notifyActiveProjectionCapturedContentResized(int width, int height); void notifyActiveProjectionCapturedContentVisibilityChanged(boolean isVisible); + void notifyWindowingModeChanged(int contentToRecord, int targetUid, int windowingMode); } private static final class RemoteMediaProjectionManagerWrapper implements @@ -700,6 +723,22 @@ final class ContentRecorder implements WindowContainerListener { } } + @Override + public void notifyWindowingModeChanged(int contentToRecord, int targetUid, + int windowingMode) { + fetchMediaProjectionManager(); + if (mIMediaProjectionManager == null) { + return; + } + try { + mIMediaProjectionManager.notifyWindowingModeChanged( + contentToRecord, targetUid, windowingMode); + } catch (RemoteException e) { + ProtoLog.e(WM_DEBUG_CONTENT_RECORDING, + "Content Recording: Unable to tell log windowing mode change: %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 576e8a48ab31..c716879a5097 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -2757,6 +2757,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp @Override public void onConfigurationChanged(Configuration newParentConfig) { final int lastOrientation = getConfiguration().orientation; + final int lastWindowingMode = getWindowingMode(); super.onConfigurationChanged(newParentConfig); if (mDisplayPolicy != null) { mDisplayPolicy.onConfigurationChanged(); @@ -2768,7 +2769,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // Update surface for MediaProjection, if this DisplayContent is being used for recording. if (mContentRecorder != null) { - mContentRecorder.onConfigurationChanged(lastOrientation); + mContentRecorder.onConfigurationChanged(lastOrientation, lastWindowingMode); } if (lastOrientation != getConfiguration().orientation) { diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java index ece3dfeabafa..097cc5177a83 100644 --- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java @@ -17,12 +17,17 @@ package com.android.server.media.projection; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; import static android.media.projection.MediaProjectionManager.TYPE_MIRRORING; import static android.media.projection.ReviewGrantedConsentResult.RECORD_CANCEL; import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_DISPLAY; import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_TASK; import static android.media.projection.ReviewGrantedConsentResult.UNKNOWN; +import static android.view.ContentRecordingSession.TARGET_UID_FULL_SCREEN; +import static android.view.ContentRecordingSession.TARGET_UID_UNKNOWN; +import static android.view.ContentRecordingSession.createDisplaySession; +import static android.view.ContentRecordingSession.createTaskSession; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; @@ -62,6 +67,7 @@ import android.os.UserHandle; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; import android.view.ContentRecordingSession; +import android.view.ContentRecordingSession.RecordContent; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.FlakyTest; @@ -99,7 +105,7 @@ public class MediaProjectionManagerServiceTest { private final ApplicationInfo mAppInfo = new ApplicationInfo(); private final TestLooper mTestLooper = new TestLooper(); private static final ContentRecordingSession DISPLAY_SESSION = - ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY); + createDisplaySession(DEFAULT_DISPLAY); // Callback registered by an app on a MediaProjection instance. private final FakeIMediaProjectionCallback mIMediaProjectionCallback = new FakeIMediaProjectionCallback(); @@ -142,7 +148,7 @@ public class MediaProjectionManagerServiceTest { private MediaProjectionManagerService mService; private OffsettableClock mClock; private ContentRecordingSession mWaitingDisplaySession = - ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY); + createDisplaySession(DEFAULT_DISPLAY); @Mock private ActivityManagerInternal mAmInternal; @@ -333,7 +339,7 @@ public class MediaProjectionManagerServiceTest { projection.stop(); verify(mMediaProjectionMetricsLogger) - .logStopped(UID, ContentRecordingSession.TARGET_UID_UNKNOWN); + .logStopped(UID, TARGET_UID_UNKNOWN); } @Test @@ -351,7 +357,7 @@ public class MediaProjectionManagerServiceTest { projection.stop(); verify(mMediaProjectionMetricsLogger) - .logStopped(UID, ContentRecordingSession.TARGET_UID_FULL_SCREEN); + .logStopped(UID, TARGET_UID_FULL_SCREEN); } @Test @@ -366,7 +372,7 @@ public class MediaProjectionManagerServiceTest { .when(mWindowManagerInternal) .setContentRecordingSession(any(ContentRecordingSession.class)); ContentRecordingSession taskSession = - ContentRecordingSession.createTaskSession(mock(IBinder.class), targetUid); + createTaskSession(mock(IBinder.class), targetUid); service.setContentRecordingSession(taskSession); projection.stop(); @@ -695,6 +701,26 @@ public class MediaProjectionManagerServiceTest { verify(mMediaProjectionMetricsLogger).logAppSelectorDisplayed(hostUid); } + @Test + public void notifyWindowingModeChanged_forwardsToLogger() throws Exception { + int targetUid = 123; + mService = + new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector); + + ContentRecordingSession taskSession = + createTaskSession(mock(IBinder.class), targetUid); + mService.setContentRecordingSession(taskSession); + + MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); + projection.start(mIMediaProjectionCallback); + + mService.notifyWindowingModeChanged( + RECORD_CONTENT_TASK, targetUid, WINDOWING_MODE_MULTI_WINDOW); + + verify(mMediaProjectionMetricsLogger).logChangedWindowingMode(RECORD_CONTENT_TASK, + projection.uid, targetUid, WINDOWING_MODE_MULTI_WINDOW); + } + /** * Executes and validates scenario where the consent result indicates the projection ends. */ @@ -755,7 +781,7 @@ public class MediaProjectionManagerServiceTest { */ private void testSetUserReviewGrantedConsentResult_startedSession( @ReviewGrantedConsentResult int consentResult, - @ContentRecordingSession.RecordContent int recordedContent) + @RecordContent int recordedContent) throws NameNotFoundException { MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); projection.setLaunchCookie(mock(IBinder.class)); @@ -777,7 +803,7 @@ public class MediaProjectionManagerServiceTest { */ private void testSetUserReviewGrantedConsentResult_failedToStartSession( @ReviewGrantedConsentResult int consentResult, - @ContentRecordingSession.RecordContent int recordedContent) + @RecordContent int recordedContent) throws NameNotFoundException { MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); projection.start(mIMediaProjectionCallback); @@ -889,7 +915,7 @@ public class MediaProjectionManagerServiceTest { int targetUid = 123455; ContentRecordingSession taskSession = - ContentRecordingSession.createTaskSession(mock(IBinder.class), targetUid); + createTaskSession(mock(IBinder.class), targetUid); service.setContentRecordingSession(taskSession); verify(mMediaProjectionMetricsLogger).logInProgress(projection.uid, targetUid); @@ -970,7 +996,7 @@ public class MediaProjectionManagerServiceTest { verify(mWatcherCallback, never()).onRecordingSessionSet(any(), any()); } - private void verifySetSessionWithContent(@ContentRecordingSession.RecordContent int content) { + private void verifySetSessionWithContent(@RecordContent int content) { verify(mWindowManagerInternal, atLeastOnce()).setContentRecordingSession( mSessionCaptor.capture()); assertThat(mSessionCaptor.getValue()).isNotNull(); diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java index ad1cd6eca5ac..72ce9fe9a23f 100644 --- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java @@ -16,6 +16,14 @@ package com.android.server.media.projection; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY; +import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK; + import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN; import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED; import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CANCELLED; @@ -24,6 +32,13 @@ import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED; import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED; import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN; +import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED__TARGET_TYPE__TARGET_TYPE_APP_TASK; +import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED__TARGET_TYPE__TARGET_TYPE_DISPLAY; +import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED__TARGET_TYPE__TARGET_TYPE_UNKNOWN; +import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED__TARGET_WINDOWING_MODE__WINDOWING_MODE_FREEFORM; +import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED__TARGET_WINDOWING_MODE__WINDOWING_MODE_FULLSCREEN; +import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED__TARGET_WINDOWING_MODE__WINDOWING_MODE_SPLIT_SCREEN; +import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED__TARGET_WINDOWING_MODE__WINDOWING_MODE_UNKNOWN; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -38,10 +53,14 @@ import androidx.test.filters.SmallTest; import com.android.internal.util.FrameworkStatsLog; +import com.google.common.truth.Expect; + import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.time.Duration; @@ -60,12 +79,18 @@ public class MediaProjectionMetricsLoggerTest { private static final int TEST_TARGET_UID = 456; private static final int TEST_CREATION_SOURCE = 789; + private static final int TEST_WINDOWING_MODE = 987; + private static final int TEST_CONTENT_TO_RECORD = 654; + @Mock private FrameworkStatsLogWrapper mFrameworkStatsLogWrapper; @Mock private MediaProjectionSessionIdGenerator mSessionIdGenerator; @Mock private MediaProjectionTimestampStore mTimestampStore; private MediaProjectionMetricsLogger mLogger; + @Rule + public Expect mExpect = Expect.create(); + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -93,7 +118,7 @@ public class MediaProjectionMetricsLoggerTest { public void logInitiated_logsHostUid() { mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE); - verifyHostUidLogged(TEST_HOST_UID); + verifyStateChangedHostUidLogged(TEST_HOST_UID); } @Test @@ -107,7 +132,7 @@ public class MediaProjectionMetricsLoggerTest { public void logInitiated_logsUnknownTargetUid() { mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE); - verifyTargetUidLogged(-2); + verifyStageChangedTargetUidLogged(-2); } @Test @@ -178,14 +203,14 @@ public class MediaProjectionMetricsLoggerTest { public void logStopped_logsHostUid() { mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID); - verifyHostUidLogged(TEST_HOST_UID); + verifyStateChangedHostUidLogged(TEST_HOST_UID); } @Test public void logStopped_logsTargetUid() { mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID); - verifyTargetUidLogged(TEST_TARGET_UID); + verifyStageChangedTargetUidLogged(TEST_TARGET_UID); } @Test @@ -263,14 +288,14 @@ public class MediaProjectionMetricsLoggerTest { public void logInProgress_logsHostUid() { mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID); - verifyHostUidLogged(TEST_HOST_UID); + verifyStateChangedHostUidLogged(TEST_HOST_UID); } @Test public void logInProgress_logsTargetUid() { mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID); - verifyTargetUidLogged(TEST_TARGET_UID); + verifyStageChangedTargetUidLogged(TEST_TARGET_UID); } @Test @@ -336,14 +361,14 @@ public class MediaProjectionMetricsLoggerTest { public void logPermissionRequestDisplayed_logsHostUid() { mLogger.logPermissionRequestDisplayed(TEST_HOST_UID); - verifyHostUidLogged(TEST_HOST_UID); + verifyStateChangedHostUidLogged(TEST_HOST_UID); } @Test public void logPermissionRequestDisplayed_logsUnknownTargetUid() { mLogger.logPermissionRequestDisplayed(TEST_HOST_UID); - verifyTargetUidLogged(-2); + verifyStageChangedTargetUidLogged(-2); } @Test @@ -409,14 +434,14 @@ public class MediaProjectionMetricsLoggerTest { public void logAppSelectorDisplayed_logsHostUid() { mLogger.logAppSelectorDisplayed(TEST_HOST_UID); - verifyHostUidLogged(TEST_HOST_UID); + verifyStateChangedHostUidLogged(TEST_HOST_UID); } @Test public void logAppSelectorDisplayed_logsUnknownTargetUid() { mLogger.logAppSelectorDisplayed(TEST_HOST_UID); - verifyTargetUidLogged(-2); + verifyStageChangedTargetUidLogged(-2); } @Test @@ -492,14 +517,14 @@ public class MediaProjectionMetricsLoggerTest { public void logProjectionPermissionRequestCancelled_logsHostUid() { mLogger.logProjectionPermissionRequestCancelled(TEST_HOST_UID); - verifyHostUidLogged(TEST_HOST_UID); + verifyStateChangedHostUidLogged(TEST_HOST_UID); } @Test public void logProjectionPermissionRequestCancelled_logsUnknownTargetUid() { mLogger.logProjectionPermissionRequestCancelled(TEST_HOST_UID); - verifyTargetUidLogged(-2); + verifyStageChangedTargetUidLogged(-2); } @Test @@ -510,9 +535,88 @@ public class MediaProjectionMetricsLoggerTest { MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN); } + @Test + public void logWindowingModeChanged_logsTargetChangedAtomId() { + mLogger.logChangedWindowingMode( + TEST_CONTENT_TO_RECORD, TEST_HOST_UID, TEST_TARGET_UID, TEST_WINDOWING_MODE); + + verifyTargetChangedAtomIdLogged(); + } + + @Test + public void logWindowingModeChanged_logsTargetType() { + MediaProjectionMetricsLogger logger = Mockito.spy(mLogger); + final int testTargetType = 111; + when(logger.contentToRecordToTargetType(TEST_CONTENT_TO_RECORD)).thenReturn(testTargetType); + logger.logChangedWindowingMode( + TEST_CONTENT_TO_RECORD, TEST_HOST_UID, TEST_TARGET_UID, TEST_WINDOWING_MODE); + verifyTargetTypeLogged(testTargetType); + } + + @Test + public void logWindowingModeChanged_logsHostUid() { + mLogger.logChangedWindowingMode( + TEST_CONTENT_TO_RECORD, TEST_HOST_UID, TEST_TARGET_UID, TEST_WINDOWING_MODE); + verifyTargetChangedHostUidLogged(TEST_HOST_UID); + } + + @Test + public void logWindowingModeChanged_logsTargetUid() { + mLogger.logChangedWindowingMode( + TEST_CONTENT_TO_RECORD, TEST_HOST_UID, TEST_TARGET_UID, TEST_WINDOWING_MODE); + verifyTargetChangedTargetUidLogged(TEST_TARGET_UID); + } + + @Test + public void logWindowingModeChanged_logsTargetWindowingMode() { + MediaProjectionMetricsLogger logger = Mockito.spy(mLogger); + final int testTargetWindowingMode = 222; + when(logger.windowingModeToTargetWindowingMode(TEST_WINDOWING_MODE)) + .thenReturn(testTargetWindowingMode); + logger.logChangedWindowingMode( + TEST_CONTENT_TO_RECORD, TEST_HOST_UID, TEST_TARGET_UID, TEST_WINDOWING_MODE); + verifyWindowingModeLogged(testTargetWindowingMode); + } + + @Test + public void testContentToRecordToTargetType() { + mExpect.that(mLogger.contentToRecordToTargetType(RECORD_CONTENT_DISPLAY)) + .isEqualTo(MEDIA_PROJECTION_TARGET_CHANGED__TARGET_TYPE__TARGET_TYPE_DISPLAY); + + mExpect.that(mLogger.contentToRecordToTargetType(RECORD_CONTENT_TASK)) + .isEqualTo(MEDIA_PROJECTION_TARGET_CHANGED__TARGET_TYPE__TARGET_TYPE_APP_TASK); + + mExpect.that(mLogger.contentToRecordToTargetType(2)) + .isEqualTo(MEDIA_PROJECTION_TARGET_CHANGED__TARGET_TYPE__TARGET_TYPE_UNKNOWN); + + mExpect.that(mLogger.contentToRecordToTargetType(-1)) + .isEqualTo(MEDIA_PROJECTION_TARGET_CHANGED__TARGET_TYPE__TARGET_TYPE_UNKNOWN); + + mExpect.that(mLogger.contentToRecordToTargetType(100)) + .isEqualTo(MEDIA_PROJECTION_TARGET_CHANGED__TARGET_TYPE__TARGET_TYPE_UNKNOWN); + } + + @Test + public void testWindowingModeToTargetWindowingMode() { + mExpect.that(mLogger.windowingModeToTargetWindowingMode(WINDOWING_MODE_FULLSCREEN)) + .isEqualTo(MEDIA_PROJECTION_TARGET_CHANGED__TARGET_WINDOWING_MODE__WINDOWING_MODE_FULLSCREEN); + + mExpect.that(mLogger.windowingModeToTargetWindowingMode(WINDOWING_MODE_MULTI_WINDOW)) + .isEqualTo(MEDIA_PROJECTION_TARGET_CHANGED__TARGET_WINDOWING_MODE__WINDOWING_MODE_SPLIT_SCREEN); + + mExpect.that(mLogger.windowingModeToTargetWindowingMode(WINDOWING_MODE_FREEFORM)) + .isEqualTo(MEDIA_PROJECTION_TARGET_CHANGED__TARGET_WINDOWING_MODE__WINDOWING_MODE_FREEFORM); + + mExpect.that(mLogger.windowingModeToTargetWindowingMode(WINDOWING_MODE_PINNED)) + .isEqualTo(MEDIA_PROJECTION_TARGET_CHANGED__TARGET_WINDOWING_MODE__WINDOWING_MODE_UNKNOWN); + + mExpect.that(mLogger.windowingModeToTargetWindowingMode(WINDOWING_MODE_UNDEFINED)) + .isEqualTo(MEDIA_PROJECTION_TARGET_CHANGED__TARGET_WINDOWING_MODE__WINDOWING_MODE_UNKNOWN); + } + private void verifyStateChangedAtomIdLogged() { verify(mFrameworkStatsLogWrapper) - .write( + .writeStateChanged( /* code= */ eq(FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED), /* sessionId= */ anyInt(), /* state= */ anyInt(), @@ -525,7 +629,7 @@ public class MediaProjectionMetricsLoggerTest { private void verifyStateLogged(int state) { verify(mFrameworkStatsLogWrapper) - .write( + .writeStateChanged( /* code= */ anyInt(), /* sessionId= */ anyInt(), eq(state), @@ -536,9 +640,9 @@ public class MediaProjectionMetricsLoggerTest { /* creationSource= */ anyInt()); } - private void verifyHostUidLogged(int hostUid) { + private void verifyStateChangedHostUidLogged(int hostUid) { verify(mFrameworkStatsLogWrapper) - .write( + .writeStateChanged( /* code= */ anyInt(), /* sessionId= */ anyInt(), /* state= */ anyInt(), @@ -551,7 +655,7 @@ public class MediaProjectionMetricsLoggerTest { private void verifyCreationSourceLogged(int creationSource) { verify(mFrameworkStatsLogWrapper) - .write( + .writeStateChanged( /* code= */ anyInt(), /* sessionId= */ anyInt(), /* state= */ anyInt(), @@ -562,9 +666,9 @@ public class MediaProjectionMetricsLoggerTest { eq(creationSource)); } - private void verifyTargetUidLogged(int targetUid) { + private void verifyStageChangedTargetUidLogged(int targetUid) { verify(mFrameworkStatsLogWrapper) - .write( + .writeStateChanged( /* code= */ anyInt(), /* sessionId= */ anyInt(), /* state= */ anyInt(), @@ -577,7 +681,7 @@ public class MediaProjectionMetricsLoggerTest { private void verifyTimeSinceLastActiveSessionLogged(int timeSinceLastActiveSession) { verify(mFrameworkStatsLogWrapper) - .write( + .writeStateChanged( /* code= */ anyInt(), /* sessionId= */ anyInt(), /* state= */ anyInt(), @@ -590,7 +694,7 @@ public class MediaProjectionMetricsLoggerTest { private void verifySessionIdLogged(int newSessionId) { verify(mFrameworkStatsLogWrapper) - .write( + .writeStateChanged( /* code= */ anyInt(), /* sessionId= */ eq(newSessionId), /* state= */ anyInt(), @@ -603,7 +707,7 @@ public class MediaProjectionMetricsLoggerTest { private void verifyPreviousStateLogged(int previousState) { verify(mFrameworkStatsLogWrapper) - .write( + .writeStateChanged( /* code= */ anyInt(), /* sessionId= */ anyInt(), /* state= */ anyInt(), @@ -613,4 +717,59 @@ public class MediaProjectionMetricsLoggerTest { /* timeSinceLastActive= */ anyInt(), /* creationSource= */ anyInt()); } + + private void verifyTargetChangedAtomIdLogged() { + verify(mFrameworkStatsLogWrapper) + .writeTargetChanged( + eq(FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED), + /* sessionId= */ anyInt(), + /* targetType= */ anyInt(), + /* hostUid= */ anyInt(), + /* targetUid= */ anyInt(), + /* targetWindowingMode= */ anyInt()); + } + + private void verifyTargetTypeLogged(int targetType) { + verify(mFrameworkStatsLogWrapper) + .writeTargetChanged( + /* code= */ anyInt(), + /* sessionId= */ anyInt(), + eq(targetType), + /* hostUid= */ anyInt(), + /* targetUid= */ anyInt(), + /* targetWindowingMode= */ anyInt()); + } + + private void verifyTargetChangedHostUidLogged(int hostUid) { + verify(mFrameworkStatsLogWrapper) + .writeTargetChanged( + /* code= */ anyInt(), + /* sessionId= */ anyInt(), + /* targetType= */ anyInt(), + eq(hostUid), + /* targetUid= */ anyInt(), + /* targetWindowingMode= */ anyInt()); + } + + private void verifyTargetChangedTargetUidLogged(int targetUid) { + verify(mFrameworkStatsLogWrapper) + .writeTargetChanged( + /* code= */ anyInt(), + /* sessionId= */ anyInt(), + /* targetType= */ anyInt(), + /* hostUid= */ anyInt(), + eq(targetUid), + /* targetWindowingMode= */ anyInt()); + } + + private void verifyWindowingModeLogged(int targetWindowingMode) { + verify(mFrameworkStatsLogWrapper) + .writeTargetChanged( + /* code= */ anyInt(), + /* sessionId= */ anyInt(), + /* targetType= */ anyInt(), + /* hostUid= */ anyInt(), + /* targetUid= */ anyInt(), + eq(targetWindowingMode)); + } } 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 78566fb06179..887e5ee0c58a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java @@ -42,6 +42,7 @@ import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import android.app.WindowConfiguration; import android.content.pm.ActivityInfo; @@ -232,7 +233,7 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testOnConfigurationChanged_neverRecording() { defaultInit(); - mContentRecorder.onConfigurationChanged(ORIENTATION_PORTRAIT); + mContentRecorder.onConfigurationChanged(ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN); verify(mTransaction, never()).setPosition(eq(mRecordedSurface), anyFloat(), anyFloat()); verify(mTransaction, never()).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(), @@ -248,7 +249,7 @@ public class ContentRecorderTests extends WindowTestsBase { @Configuration.Orientation final int lastOrientation = mDisplayContent.getConfiguration().orientation == ORIENTATION_PORTRAIT ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT; - mContentRecorder.onConfigurationChanged(lastOrientation); + mContentRecorder.onConfigurationChanged(lastOrientation, WINDOWING_MODE_FULLSCREEN); verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(), anyFloat()); @@ -266,7 +267,8 @@ public class ContentRecorderTests extends WindowTestsBase { // The user rotates the device, so the host app resizes the virtual display for the capture. resizeDisplay(mDisplayContent, newWidth, mSurfaceSize.y); resizeDisplay(mVirtualDisplayContent, newWidth, mSurfaceSize.y); - mContentRecorder.onConfigurationChanged(mDisplayContent.getConfiguration().orientation); + mContentRecorder.onConfigurationChanged( + mDisplayContent.getConfiguration().orientation, WINDOWING_MODE_FULLSCREEN); verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(), anyFloat()); @@ -283,7 +285,7 @@ public class ContentRecorderTests extends WindowTestsBase { // Change a value that we shouldn't rely upon; it has the wrong type. mVirtualDisplayContent.setOverrideOrientation(SCREEN_ORIENTATION_FULL_SENSOR); mContentRecorder.onConfigurationChanged( - mVirtualDisplayContent.getConfiguration().orientation); + mVirtualDisplayContent.getConfiguration().orientation, WINDOWING_MODE_FULLSCREEN); // No resize is issued, only the initial transformations when we started recording. verify(mTransaction).setPosition(eq(mRecordedSurface), anyFloat(), @@ -307,7 +309,7 @@ public class ContentRecorderTests extends WindowTestsBase { doReturn(newSurfaceSize).when(mWm.mDisplayManagerInternal).getDisplaySurfaceDefaultSize( anyInt()); mContentRecorder.onConfigurationChanged( - mVirtualDisplayContent.getConfiguration().orientation); + mVirtualDisplayContent.getConfiguration().orientation, WINDOWING_MODE_FULLSCREEN); // No resize is issued, only the initial transformations when we started recording. verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(), @@ -378,6 +380,55 @@ public class ContentRecorderTests extends WindowTestsBase { recordedWidth, recordedHeight); } + @Test + public void testTaskWindowingModeChanged_changeWindowMode_notifyWindowModeChanged() { + defaultInit(); + // WHEN a recording is ongoing. + mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + mContentRecorder.setContentRecordingSession(mTaskSession); + mContentRecorder.updateRecording(); + assertThat(mContentRecorder.isCurrentlyRecording()).isTrue(); + + // THEN the windowing mode change callback is notified. + verify(mMediaProjectionManagerWrapper) + .notifyWindowingModeChanged(mTaskSession.getContentToRecord(), + mTaskSession.getTargetUid(), WINDOWING_MODE_FULLSCREEN); + + // WHEN a configuration change arrives, and the task is now multi-window mode. + mTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + Configuration configuration = mTask.getConfiguration(); + mTask.onConfigurationChanged(configuration); + + // THEN windowing mode change callback is notified again. + verify(mMediaProjectionManagerWrapper) + .notifyWindowingModeChanged(mTaskSession.getContentToRecord(), + mTaskSession.getTargetUid(), WINDOWING_MODE_MULTI_WINDOW); + } + + @Test + public void testTaskWindowingModeChanged_sameWindowMode_notifyWindowModeChanged() { + defaultInit(); + // WHEN a recording is ongoing. + mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + mContentRecorder.setContentRecordingSession(mTaskSession); + mContentRecorder.updateRecording(); + assertThat(mContentRecorder.isCurrentlyRecording()).isTrue(); + + // THEN the windowing mode change callback is notified. + verify(mMediaProjectionManagerWrapper) + .notifyWindowingModeChanged(mTaskSession.getContentToRecord(), + mTaskSession.getTargetUid(), WINDOWING_MODE_FULLSCREEN); + + // WHEN a configuration change arrives, and the task is STILL fullscreen. + mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + Configuration configuration = mTask.getConfiguration(); + mTask.onConfigurationChanged(configuration); + + // THEN the windowing mode change callback is NOT called notified again. + verify(mMediaProjectionManagerWrapper, times(1)) + .notifyWindowingModeChanged(anyInt(), anyInt(), anyInt()); + } + @Test public void testTaskWindowingModeChanged_pip_stopsRecording() { defaultInit(); @@ -421,9 +472,12 @@ public class ContentRecorderTests extends WindowTestsBase { mContentRecorder.updateRecording(); assertThat(mContentRecorder.isCurrentlyRecording()).isTrue(); - // THEN the visibility change callback is notified. + // THEN the visibility change & windowing mode change callbacks are notified. verify(mMediaProjectionManagerWrapper) .notifyActiveProjectionCapturedContentVisibilityChanged(true); + verify(mMediaProjectionManagerWrapper) + .notifyWindowingModeChanged(mTaskSession.getContentToRecord(), + mTaskSession.getTargetUid(), mRootWindowContainer.getWindowingMode()); } @Test @@ -434,9 +488,12 @@ public class ContentRecorderTests extends WindowTestsBase { mContentRecorder.updateRecording(); assertThat(mContentRecorder.isCurrentlyRecording()).isTrue(); - // THEN the visibility change callback is notified. + // THEN the visibility change & windowing mode change callbacks are notified. verify(mMediaProjectionManagerWrapper) .notifyActiveProjectionCapturedContentVisibilityChanged(true); + verify(mMediaProjectionManagerWrapper) + .notifyWindowingModeChanged(mDisplaySession.getContentToRecord(), + mDisplaySession.getTargetUid(), mRootWindowContainer.getWindowingMode()); } @Test -- GitLab