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