• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wm;
18 
19 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
20 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
21 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
22 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR;
23 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
24 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
25 import static android.view.Display.DEFAULT_DISPLAY;
26 import static android.view.Display.INVALID_DISPLAY;
27 import static android.view.Display.STATE_ON;
28 
29 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
30 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
31 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
32 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
33 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
34 import static com.android.server.display.feature.flags.Flags.FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT;
35 
36 import static com.google.common.truth.Truth.assertThat;
37 
38 import static org.mockito.ArgumentMatchers.anyBoolean;
39 import static org.mockito.ArgumentMatchers.anyFloat;
40 import static org.mockito.ArgumentMatchers.anyInt;
41 import static org.mockito.ArgumentMatchers.eq;
42 import static org.mockito.Mockito.atLeast;
43 import static org.mockito.Mockito.atLeastOnce;
44 import static org.mockito.Mockito.clearInvocations;
45 import static org.mockito.Mockito.never;
46 import static org.mockito.Mockito.times;
47 
48 import android.app.WindowConfiguration;
49 import android.content.pm.ActivityInfo;
50 import android.content.res.Configuration;
51 import android.graphics.Point;
52 import android.graphics.Rect;
53 import android.media.projection.StopReason;
54 import android.os.IBinder;
55 import android.platform.test.annotations.EnableFlags;
56 import android.platform.test.annotations.Presubmit;
57 import android.view.ContentRecordingSession;
58 import android.view.Display;
59 import android.view.DisplayInfo;
60 import android.view.Gravity;
61 import android.view.SurfaceControl;
62 
63 import androidx.test.filters.SmallTest;
64 
65 import com.android.server.wm.ContentRecorder.MediaProjectionManagerWrapper;
66 
67 import org.junit.Before;
68 import org.junit.Test;
69 import org.junit.runner.RunWith;
70 import org.mockito.Mock;
71 import org.mockito.MockitoAnnotations;
72 
73 
74 /**
75  * Tests for the {@link ContentRecorder} class.
76  *
77  * Build/Install/Run:
78  *  atest WmTests:ContentRecorderTests
79  */
80 @SmallTest
81 @Presubmit
82 @RunWith(WindowTestRunner.class)
83 public class ContentRecorderTests extends WindowTestsBase {
84     private static IBinder sTaskWindowContainerToken;
85     private DisplayContent mVirtualDisplayContent;
86     private Task mTask;
87     private final ContentRecordingSession mDisplaySession =
88             ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY);
89     private final ContentRecordingSession mWaitingDisplaySession =
90             ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY);
91     private ContentRecordingSession mTaskSession;
92     private Point mSurfaceSize;
93     private ContentRecorder mContentRecorder;
94     @Mock private MediaProjectionManagerWrapper mMediaProjectionManagerWrapper;
95     private SurfaceControl mRecordedSurface;
96 
97     private boolean mHandleAnisotropicDisplayMirroring = false;
98 
setUp()99     @Before public void setUp() {
100         mDisplayInfo.type = Display.TYPE_VIRTUAL;
101         MockitoAnnotations.initMocks(this);
102 
103         doReturn(INVALID_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt());
104         doReturn(false).when(mWm.mDisplayManagerInternal).isDisplayReadyForMirroring(anyInt());
105 
106         // Skip unnecessary operations of relayout.
107         spyOn(mWm.mWindowPlacerLocked);
108         doNothing().when(mWm.mWindowPlacerLocked).performSurfacePlacement(anyBoolean());
109     }
110 
defaultInit()111     private void defaultInit() {
112         createContentRecorder(createDefaultDisplayInfo());
113     }
114 
createDefaultDisplayInfo()115     private DisplayInfo createDefaultDisplayInfo() {
116         return createDisplayInfo(mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
117                 mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
118     }
119 
createDisplayInfo(int width, int height)120     private DisplayInfo createDisplayInfo(int width, int height) {
121         // GIVEN SurfaceControl can successfully mirror the provided surface.
122         mSurfaceSize = new Point(width, height);
123         mRecordedSurface = surfaceControlMirrors(mSurfaceSize);
124 
125         DisplayInfo displayInfo = mDisplayInfo;
126         displayInfo.logicalWidth = width;
127         displayInfo.logicalHeight = height;
128         displayInfo.state = STATE_ON;
129         return displayInfo;
130     }
131 
createContentRecorder(DisplayInfo displayInfo)132     private void createContentRecorder(DisplayInfo displayInfo) {
133         mVirtualDisplayContent = createNewDisplay(displayInfo);
134         final int displayId = mVirtualDisplayContent.getDisplayId();
135         mContentRecorder = new ContentRecorder(mVirtualDisplayContent,
136                 mMediaProjectionManagerWrapper, mHandleAnisotropicDisplayMirroring);
137         spyOn(mVirtualDisplayContent);
138 
139         // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
140         // record.
141         mDisplaySession.setVirtualDisplayId(displayId);
142         mDisplaySession.setDisplayToRecord(mDefaultDisplay.mDisplayId);
143 
144         // GIVEN there is a window token associated with a task to record.
145         sTaskWindowContainerToken = setUpTaskWindowContainerToken(mVirtualDisplayContent);
146         mTaskSession = ContentRecordingSession.createTaskSession(sTaskWindowContainerToken);
147         mTaskSession.setVirtualDisplayId(displayId);
148 
149         // GIVEN a session is waiting for the user to review consent.
150         mWaitingDisplaySession.setVirtualDisplayId(displayId);
151         mWaitingDisplaySession.setWaitingForConsent(true);
152     }
153 
154     @Test
testIsCurrentlyRecording()155     public void testIsCurrentlyRecording() {
156         defaultInit();
157         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
158 
159         mContentRecorder.updateRecording();
160         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
161     }
162 
163     @Test
testUpdateRecording_display()164     public void testUpdateRecording_display() {
165         defaultInit();
166         mContentRecorder.setContentRecordingSession(mDisplaySession);
167         mContentRecorder.updateRecording();
168         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
169     }
170 
171     @Test
testUpdateRecording_externalDisplayWithoutUserConfirmation()172     public void testUpdateRecording_externalDisplayWithoutUserConfirmation() {
173         mDisplayInfo.type = Display.TYPE_EXTERNAL;
174         defaultInit();
175         mContentRecorder.setContentRecordingSession(mDisplaySession);
176         mContentRecorder.updateRecording();
177         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
178     }
179 
180     @Test
testUpdateRecording_externalDisplayWithUserConfirmation()181     public void testUpdateRecording_externalDisplayWithUserConfirmation() {
182         doReturn(true).when(mWm.mDisplayManagerInternal).isDisplayReadyForMirroring(anyInt());
183         mDisplayInfo.type = Display.TYPE_EXTERNAL;
184         defaultInit();
185         mContentRecorder.setContentRecordingSession(mDisplaySession);
186         mContentRecorder.updateRecording();
187         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
188     }
189 
190     @Test
testUpdateRecording_display_invalidDisplayIdToMirror()191     public void testUpdateRecording_display_invalidDisplayIdToMirror() {
192         defaultInit();
193         ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
194                 INVALID_DISPLAY);
195         mContentRecorder.setContentRecordingSession(session);
196         mContentRecorder.updateRecording();
197         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
198     }
199 
200     @Test
testUpdateRecording_display_noDisplayContentToMirror()201     public void testUpdateRecording_display_noDisplayContentToMirror() {
202         defaultInit();
203         doReturn(null).when(
204                 mWm.mRoot).getDisplayContent(anyInt());
205         mContentRecorder.setContentRecordingSession(mDisplaySession);
206         mContentRecorder.updateRecording();
207         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
208     }
209 
210     @Test
testUpdateRecording_task_nullToken()211     public void testUpdateRecording_task_nullToken() {
212         defaultInit();
213         ContentRecordingSession session = mTaskSession;
214         session.setVirtualDisplayId(mDisplaySession.getVirtualDisplayId());
215         session.setTokenToRecord(null);
216         mContentRecorder.setContentRecordingSession(session);
217         mContentRecorder.updateRecording();
218         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
219         verify(mMediaProjectionManagerWrapper).stopActiveProjection(StopReason.STOP_ERROR);
220     }
221 
222     @Test
testUpdateRecording_task_noWindowContainer()223     public void testUpdateRecording_task_noWindowContainer() {
224         defaultInit();
225         // Use the window container token of the DisplayContent, rather than task.
226         ContentRecordingSession invalidTaskSession = ContentRecordingSession.createTaskSession(
227                 new WindowContainer.RemoteToken(mDisplayContent));
228         mContentRecorder.setContentRecordingSession(invalidTaskSession);
229         mContentRecorder.updateRecording();
230         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
231         verify(mMediaProjectionManagerWrapper).stopActiveProjection(StopReason.STOP_ERROR);
232     }
233 
234     @Test
testUpdateRecording_wasPaused()235     public void testUpdateRecording_wasPaused() {
236         defaultInit();
237         mContentRecorder.setContentRecordingSession(mDisplaySession);
238         mContentRecorder.updateRecording();
239 
240         mContentRecorder.pauseRecording();
241         mContentRecorder.updateRecording();
242         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
243     }
244 
245     @Test
testUpdateRecording_waitingForConsent()246     public void testUpdateRecording_waitingForConsent() {
247         defaultInit();
248         mContentRecorder.setContentRecordingSession(mWaitingDisplaySession);
249         mContentRecorder.updateRecording();
250         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
251 
252 
253         mContentRecorder.setContentRecordingSession(mDisplaySession);
254         mContentRecorder.updateRecording();
255         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
256     }
257 
258     @Test
testOnConfigurationChanged_neverRecording()259     public void testOnConfigurationChanged_neverRecording() {
260         defaultInit();
261         mContentRecorder.onConfigurationChanged(ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN);
262 
263         verify(mTransaction, never()).setPosition(eq(mRecordedSurface), anyFloat(), anyFloat());
264         verify(mTransaction, never()).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
265                 anyFloat(), anyFloat());
266     }
267 
268     @Test
testOnConfigurationChanged_resizesSurface()269     public void testOnConfigurationChanged_resizesSurface() {
270         defaultInit();
271         mContentRecorder.setContentRecordingSession(mDisplaySession);
272         mContentRecorder.updateRecording();
273         // Ensure a different orientation when we check if something has changed.
274         @Configuration.Orientation final int lastOrientation =
275                 mDisplayContent.getConfiguration().orientation == ORIENTATION_PORTRAIT
276                         ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
277         mContentRecorder.onConfigurationChanged(lastOrientation, WINDOWING_MODE_FULLSCREEN);
278 
279         verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
280                 anyFloat());
281         verify(mTransaction, atLeast(2)).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
282                 anyFloat(), anyFloat());
283     }
284 
285     @Test
testOnConfigurationChanged_resizesVirtualDisplay()286     public void testOnConfigurationChanged_resizesVirtualDisplay() {
287         defaultInit();
288         final int newWidth = 55;
289         mContentRecorder.setContentRecordingSession(mDisplaySession);
290         mContentRecorder.updateRecording();
291 
292         // The user rotates the device, so the host app resizes the virtual display for the capture.
293         resizeDisplay(mDisplayContent, newWidth, mSurfaceSize.y);
294         resizeDisplay(mVirtualDisplayContent, newWidth, mSurfaceSize.y);
295         mContentRecorder.onConfigurationChanged(
296                 mDisplayContent.getConfiguration().orientation, WINDOWING_MODE_FULLSCREEN);
297 
298         verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
299                 anyFloat());
300         verify(mTransaction, atLeast(2)).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
301                 anyFloat(), anyFloat());
302     }
303 
304     @Test
testOnConfigurationChanged_rotateVirtualDisplay()305     public void testOnConfigurationChanged_rotateVirtualDisplay() {
306         defaultInit();
307         mContentRecorder.setContentRecordingSession(mDisplaySession);
308         mContentRecorder.updateRecording();
309 
310         // Change a value that we shouldn't rely upon; it has the wrong type.
311         mVirtualDisplayContent.setOverrideOrientation(SCREEN_ORIENTATION_FULL_SENSOR);
312         mContentRecorder.onConfigurationChanged(
313                 mVirtualDisplayContent.getConfiguration().orientation, WINDOWING_MODE_FULLSCREEN);
314 
315         // No resize is issued, only the initial transformations when we started recording.
316         verify(mTransaction).setPosition(eq(mRecordedSurface), anyFloat(), anyFloat());
317         verify(mTransaction).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
318                 anyFloat(), anyFloat());
319     }
320 
321     /**
322      * Test that resizing the output surface results in resizing the mirrored content to fit.
323      */
324     @Test
testOnConfigurationChanged_resizeSurface()325     public void testOnConfigurationChanged_resizeSurface() {
326         defaultInit();
327         mContentRecorder.setContentRecordingSession(mDisplaySession);
328         mContentRecorder.updateRecording();
329 
330         // Resize the output surface.
331         final Point newSurfaceSize = new Point(Math.round(mSurfaceSize.x / 2f), mSurfaceSize.y * 2);
332         doReturn(newSurfaceSize).when(mWm.mDisplayManagerInternal).getDisplaySurfaceDefaultSize(
333                 anyInt());
334         mContentRecorder.onConfigurationChanged(
335                 mVirtualDisplayContent.getConfiguration().orientation, WINDOWING_MODE_FULLSCREEN);
336 
337         // No resize is issued, only the initial transformations when we started recording.
338         verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
339                 anyFloat());
340         verify(mTransaction, atLeast(2)).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
341                 anyFloat(), anyFloat());
342 
343     }
344 
345     @Test
testOnTaskOrientationConfigurationChanged_resizesSurface()346     public void testOnTaskOrientationConfigurationChanged_resizesSurface() {
347         defaultInit();
348         mContentRecorder.setContentRecordingSession(mTaskSession);
349         mContentRecorder.updateRecording();
350 
351         Configuration config = mTask.getConfiguration();
352         // Ensure a different orientation when we compare.
353         @Configuration.Orientation final int orientation =
354                 config.orientation == ORIENTATION_PORTRAIT ? ORIENTATION_LANDSCAPE
355                         : ORIENTATION_PORTRAIT;
356         final Rect lastBounds = config.windowConfiguration.getBounds();
357         config.orientation = orientation;
358         config.windowConfiguration.setBounds(
359                 new Rect(0, 0, lastBounds.height(), lastBounds.width()));
360         mTask.onConfigurationChanged(config);
361 
362         verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
363                 anyFloat());
364         verify(mTransaction, atLeast(2)).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
365                 anyFloat(), anyFloat());
366     }
367 
368     @Test
testOnTaskBoundsConfigurationChanged_notifiesCallback()369     public void testOnTaskBoundsConfigurationChanged_notifiesCallback() {
370         defaultInit();
371         mTask.getRootTask().setWindowingMode(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
372 
373         final int minWidth = 222;
374         final int minHeight = 777;
375         final int recordedWidth = 333;
376         final int recordedHeight = 999;
377 
378         final ActivityInfo info = new ActivityInfo();
379         info.windowLayout = new ActivityInfo.WindowLayout(-1 /* width */,
380                 -1 /* widthFraction */, -1 /* height */, -1 /* heightFraction */,
381                 Gravity.NO_GRAVITY, minWidth, minHeight);
382         mTask.setMinDimensions(info);
383 
384         // WHEN a recording is ongoing.
385         mContentRecorder.setContentRecordingSession(mTaskSession);
386         mContentRecorder.updateRecording();
387         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
388 
389         // WHEN a configuration change arrives, and the recorded content is a different size.
390         Configuration configuration = mTask.getConfiguration();
391         Rect newBounds = new Rect(0, 0, recordedWidth, recordedHeight);
392         configuration.windowConfiguration.setBounds(newBounds);
393         configuration.windowConfiguration.setAppBounds(newBounds);
394         mTask.onConfigurationChanged(configuration);
395         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
396 
397         // THEN content in the captured DisplayArea is scaled to fit the surface size.
398         verify(mTransaction, atLeastOnce()).setMatrix(
399                 eq(mRecordedSurface), anyFloat(), eq(0f), eq(0f), anyFloat());
400         // THEN the resize callback is notified.
401         verify(mMediaProjectionManagerWrapper).notifyCaptureBoundsChanged(
402                 mTaskSession.getContentToRecord(), mTaskSession.getTargetUid(), newBounds);
403     }
404 
405     @Test
testTaskWindowingModeChanged_changeWindowMode_notifyWindowModeChanged()406     public void testTaskWindowingModeChanged_changeWindowMode_notifyWindowModeChanged() {
407         defaultInit();
408         // WHEN a recording is ongoing.
409         mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
410         mContentRecorder.setContentRecordingSession(mTaskSession);
411         mContentRecorder.updateRecording();
412         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
413 
414         // THEN the windowing mode change callback is notified.
415         verify(mMediaProjectionManagerWrapper)
416                 .notifyWindowingModeChanged(mTaskSession.getContentToRecord(),
417                         mTaskSession.getTargetUid(), WINDOWING_MODE_FULLSCREEN);
418 
419         // WHEN a configuration change arrives, and the task is now multi-window mode.
420         mTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
421         Configuration configuration = mTask.getConfiguration();
422         mTask.onConfigurationChanged(configuration);
423 
424         // THEN windowing mode change callback is notified again.
425         verify(mMediaProjectionManagerWrapper)
426                 .notifyWindowingModeChanged(mTaskSession.getContentToRecord(),
427                 mTaskSession.getTargetUid(), WINDOWING_MODE_MULTI_WINDOW);
428     }
429 
430     @Test
testTaskWindowingModeChanged_sameWindowMode_notifyWindowModeChanged()431     public void testTaskWindowingModeChanged_sameWindowMode_notifyWindowModeChanged() {
432         defaultInit();
433         // WHEN a recording is ongoing.
434         mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
435         mContentRecorder.setContentRecordingSession(mTaskSession);
436         mContentRecorder.updateRecording();
437         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
438 
439         // THEN the windowing mode change callback is notified.
440         verify(mMediaProjectionManagerWrapper)
441                 .notifyWindowingModeChanged(mTaskSession.getContentToRecord(),
442                         mTaskSession.getTargetUid(), WINDOWING_MODE_FULLSCREEN);
443 
444         // WHEN a configuration change arrives, and the task is STILL fullscreen.
445         mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
446         Configuration configuration = mTask.getConfiguration();
447         mTask.onConfigurationChanged(configuration);
448 
449         // THEN the windowing mode change callback is NOT called notified again.
450         verify(mMediaProjectionManagerWrapper, times(1))
451                 .notifyWindowingModeChanged(anyInt(), anyInt(), anyInt());
452     }
453 
454     @Test
testTaskWindowingModeChanged_pip_stopsRecording()455     public void testTaskWindowingModeChanged_pip_stopsRecording() {
456         defaultInit();
457         // WHEN a recording is ongoing.
458         mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
459         mContentRecorder.setContentRecordingSession(mTaskSession);
460         mContentRecorder.updateRecording();
461         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
462 
463         // WHEN a configuration change arrives, and the task is now pinned.
464         mTask.setWindowingMode(WINDOWING_MODE_PINNED);
465         Configuration configuration = mTask.getConfiguration();
466         mTask.onConfigurationChanged(configuration);
467 
468         // THEN recording is paused.
469         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
470     }
471 
472     @Test
testTaskWindowingModeChanged_fullscreen_startsRecording()473     public void testTaskWindowingModeChanged_fullscreen_startsRecording() {
474         defaultInit();
475         // WHEN a recording is ongoing.
476         mTask.setWindowingMode(WINDOWING_MODE_PINNED);
477         mContentRecorder.setContentRecordingSession(mTaskSession);
478         mContentRecorder.updateRecording();
479         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
480 
481         // WHEN the task is now fullscreen.
482         mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
483         mContentRecorder.updateRecording();
484 
485         // THEN recording is started.
486         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
487     }
488 
489     @Test
testStartRecording_notifiesCallback_taskSession()490     public void testStartRecording_notifiesCallback_taskSession() {
491         defaultInit();
492         // WHEN a recording is ongoing.
493         mContentRecorder.setContentRecordingSession(mTaskSession);
494         mContentRecorder.updateRecording();
495         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
496 
497         // THEN the visibility change & windowing mode change callbacks are notified.
498         verify(mMediaProjectionManagerWrapper)
499                 .notifyActiveProjectionCapturedContentVisibilityChanged(true);
500         verify(mMediaProjectionManagerWrapper)
501                 .notifyWindowingModeChanged(mTaskSession.getContentToRecord(),
502                         mTaskSession.getTargetUid(), mRootWindowContainer.getWindowingMode());
503     }
504 
505     @Test
testStartRecording_notifiesCallback_displaySession()506     public void testStartRecording_notifiesCallback_displaySession() {
507         defaultInit();
508         // WHEN a recording is ongoing.
509         mContentRecorder.setContentRecordingSession(mDisplaySession);
510         mContentRecorder.updateRecording();
511         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
512 
513         // THEN the visibility change & windowing mode change callbacks are notified.
514         verify(mMediaProjectionManagerWrapper)
515                 .notifyActiveProjectionCapturedContentVisibilityChanged(true);
516         verify(mMediaProjectionManagerWrapper)
517                 .notifyWindowingModeChanged(mDisplaySession.getContentToRecord(),
518                         mDisplaySession.getTargetUid(), mRootWindowContainer.getWindowingMode());
519     }
520 
521     @Test
testStartRecording_taskInPIP_recordingNotStarted()522     public void testStartRecording_taskInPIP_recordingNotStarted() {
523         defaultInit();
524         // GIVEN a task is in PIP.
525         mContentRecorder.setContentRecordingSession(mTaskSession);
526         mTask.setWindowingMode(WINDOWING_MODE_PINNED);
527 
528         // WHEN a recording tries to start.
529         mContentRecorder.updateRecording();
530 
531         // THEN recording does not start.
532         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
533     }
534 
535     @Test
testStartRecording_taskInSplit_recordingStarted()536     public void testStartRecording_taskInSplit_recordingStarted() {
537         defaultInit();
538         // GIVEN a task is in PIP.
539         mContentRecorder.setContentRecordingSession(mTaskSession);
540         mTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
541 
542         // WHEN a recording tries to start.
543         mContentRecorder.updateRecording();
544 
545         // THEN recording does not start.
546         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
547     }
548 
549     @Test
testStartRecording_taskInFullscreen_recordingStarted()550     public void testStartRecording_taskInFullscreen_recordingStarted() {
551         defaultInit();
552         // GIVEN a task is in PIP.
553         mContentRecorder.setContentRecordingSession(mTaskSession);
554         mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
555 
556         // WHEN a recording tries to start.
557         mContentRecorder.updateRecording();
558 
559         // THEN recording does not start.
560         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
561     }
562 
563     @EnableFlags(FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT)
564     @Test
testStartRecording_shouldShowSystemDecorations_recordingNotStarted()565     public void testStartRecording_shouldShowSystemDecorations_recordingNotStarted() {
566         defaultInit();
567         mContentRecorder.setContentRecordingSession(mTaskSession);
568 
569         spyOn(mVirtualDisplayContent.mDisplay);
570         doReturn(true).when(mVirtualDisplayContent.mDisplay).canHostTasks();
571 
572         // WHEN a recording tries to start.
573         mContentRecorder.updateRecording();
574 
575         // THEN recording does not start.
576         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
577     }
578 
579     @Test
testOnVisibleRequestedChanged_notifiesCallback()580     public void testOnVisibleRequestedChanged_notifiesCallback() {
581         defaultInit();
582         // WHEN a recording is ongoing.
583         mContentRecorder.setContentRecordingSession(mTaskSession);
584         mContentRecorder.updateRecording();
585         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
586 
587         // WHEN the child requests a visibility change.
588         boolean isVisibleRequested = true;
589         mContentRecorder.onVisibleRequestedChanged(isVisibleRequested);
590 
591         // THEN the visibility change callback is notified.
592         verify(mMediaProjectionManagerWrapper, atLeastOnce())
593                 .notifyActiveProjectionCapturedContentVisibilityChanged(isVisibleRequested);
594 
595         // WHEN the child requests a visibility change.
596         isVisibleRequested = false;
597         mContentRecorder.onVisibleRequestedChanged(isVisibleRequested);
598 
599         // THEN the visibility change callback is notified.
600         verify(mMediaProjectionManagerWrapper)
601                 .notifyActiveProjectionCapturedContentVisibilityChanged(isVisibleRequested);
602     }
603 
604     @Test
testOnVisibleRequestedChanged_noRecording_doesNotNotifyCallback()605     public void testOnVisibleRequestedChanged_noRecording_doesNotNotifyCallback() {
606         defaultInit();
607         // WHEN a recording is not ongoing.
608         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
609 
610         // WHEN the child requests a visibility change.
611         boolean isVisibleRequested = true;
612         mContentRecorder.onVisibleRequestedChanged(isVisibleRequested);
613 
614         // THEN the visibility change callback is not notified.
615         verify(mMediaProjectionManagerWrapper, never())
616                 .notifyActiveProjectionCapturedContentVisibilityChanged(isVisibleRequested);
617 
618         // WHEN the child requests a visibility change.
619         isVisibleRequested = false;
620         mContentRecorder.onVisibleRequestedChanged(isVisibleRequested);
621 
622         // THEN the visibility change callback is not notified.
623         verify(mMediaProjectionManagerWrapper, never())
624                 .notifyActiveProjectionCapturedContentVisibilityChanged(isVisibleRequested);
625     }
626 
627     @Test
testPauseRecording_pausesRecording()628     public void testPauseRecording_pausesRecording() {
629         defaultInit();
630         mContentRecorder.setContentRecordingSession(mDisplaySession);
631         mContentRecorder.updateRecording();
632 
633         mContentRecorder.pauseRecording();
634         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
635     }
636 
637     @Test
testPauseRecording_neverRecording()638     public void testPauseRecording_neverRecording() {
639         defaultInit();
640         mContentRecorder.pauseRecording();
641         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
642     }
643 
644     @Test
testStopRecording_stopsRecording()645     public void testStopRecording_stopsRecording() {
646         defaultInit();
647         mContentRecorder.setContentRecordingSession(mDisplaySession);
648         mContentRecorder.updateRecording();
649 
650         mContentRecorder.stopRecording();
651         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
652     }
653 
654     @Test
testStopRecording_neverRecording()655     public void testStopRecording_neverRecording() {
656         defaultInit();
657         mContentRecorder.stopRecording();
658         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
659     }
660 
661     @Test
testRemoveTask_stopsRecording()662     public void testRemoveTask_stopsRecording() {
663         defaultInit();
664         mContentRecorder.setContentRecordingSession(mTaskSession);
665         mContentRecorder.updateRecording();
666 
667         mTask.removeImmediately();
668 
669         verify(mMediaProjectionManagerWrapper).stopActiveProjection(StopReason.STOP_TARGET_REMOVED);
670     }
671 
672     @Test
testRemoveTask_stopsRecording_nullSessionShouldNotThrowExceptions()673     public void testRemoveTask_stopsRecording_nullSessionShouldNotThrowExceptions() {
674         defaultInit();
675         mContentRecorder.setContentRecordingSession(mTaskSession);
676         mContentRecorder.updateRecording();
677         mContentRecorder.setContentRecordingSession(null);
678         mTask.removeImmediately();
679     }
680 
681     @Test
testUpdateMirroredSurface_capturedAreaResized()682     public void testUpdateMirroredSurface_capturedAreaResized() {
683         defaultInit();
684         mContentRecorder.setContentRecordingSession(mDisplaySession);
685         mContentRecorder.updateRecording();
686         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
687 
688         // WHEN attempting to mirror on the virtual display, and the captured content is resized.
689         float xScale = 0.7f;
690         float yScale = 2f;
691         Rect displayAreaBounds = new Rect(0, 0, Math.round(mSurfaceSize.x * xScale),
692                 Math.round(mSurfaceSize.y * yScale));
693         mContentRecorder.updateMirroredSurface(mTransaction, displayAreaBounds, mSurfaceSize);
694         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
695 
696         // THEN content in the captured DisplayArea is scaled to fit the surface size.
697         verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, 1.0f / yScale, 0, 0,
698                 1.0f / yScale);
699         // THEN captured content is positioned in the centre of the output surface.
700         int scaledWidth = Math.round((float) displayAreaBounds.width() / xScale);
701         int xInset = (mSurfaceSize.x - scaledWidth) / 2;
702         verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, xInset, 0);
703         // THEN the resize callback is notified.
704         verify(mMediaProjectionManagerWrapper).notifyCaptureBoundsChanged(
705                 mDisplaySession.getContentToRecord(), mDisplaySession.getTargetUid(),  displayAreaBounds);
706     }
707 
708     @Test
testUpdateMirroredSurface_isotropicPixel()709     public void testUpdateMirroredSurface_isotropicPixel() {
710         mHandleAnisotropicDisplayMirroring = false;
711         DisplayInfo displayInfo = createDefaultDisplayInfo();
712         createContentRecorder(displayInfo);
713         mContentRecorder.setContentRecordingSession(mDisplaySession);
714         mContentRecorder.updateRecording();
715         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
716 
717         verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, 1, 0, 0, 1);
718     }
719 
720     @Test
testUpdateMirroredSurface_anisotropicPixel_compressY()721     public void testUpdateMirroredSurface_anisotropicPixel_compressY() {
722         mHandleAnisotropicDisplayMirroring = true;
723         DisplayInfo displayInfo = createDefaultDisplayInfo();
724         DisplayInfo inputDisplayInfo =
725                 mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo();
726         displayInfo.physicalXDpi = 2.0f * inputDisplayInfo.physicalXDpi;
727         displayInfo.physicalYDpi = inputDisplayInfo.physicalYDpi;
728         createContentRecorder(displayInfo);
729         mContentRecorder.setContentRecordingSession(mDisplaySession);
730         mContentRecorder.updateRecording();
731         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
732 
733         float xScale = 1f;
734         float yScale = 0.5f;
735         verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0,
736                 yScale);
737         verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, 0,
738                 Math.round(0.25 * mSurfaceSize.y));
739     }
740 
741     @Test
testUpdateMirroredSurface_anisotropicPixel_compressX()742     public void testUpdateMirroredSurface_anisotropicPixel_compressX() {
743         mHandleAnisotropicDisplayMirroring = true;
744         DisplayInfo displayInfo = createDefaultDisplayInfo();
745         DisplayInfo inputDisplayInfo =
746                 mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo();
747         displayInfo.physicalXDpi = inputDisplayInfo.physicalXDpi;
748         displayInfo.physicalYDpi = 2.0f * inputDisplayInfo.physicalYDpi;
749         createContentRecorder(displayInfo);
750         mContentRecorder.setContentRecordingSession(mDisplaySession);
751         mContentRecorder.updateRecording();
752         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
753 
754         float xScale = 0.5f;
755         float yScale = 1f;
756         verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0,
757                 yScale);
758         verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface,
759                 Math.round(0.25 * mSurfaceSize.x), 0);
760     }
761 
762     @Test
testUpdateMirroredSurface_anisotropicPixel_scaleOnX()763     public void testUpdateMirroredSurface_anisotropicPixel_scaleOnX() {
764         mHandleAnisotropicDisplayMirroring = true;
765         int width = 2 * mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width();
766         int height = 6 * mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height();
767         DisplayInfo displayInfo = createDisplayInfo(width, height);
768         DisplayInfo inputDisplayInfo =
769                 mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo();
770         displayInfo.physicalXDpi = inputDisplayInfo.physicalXDpi;
771         displayInfo.physicalYDpi = 2.0f * inputDisplayInfo.physicalYDpi;
772         createContentRecorder(displayInfo);
773         mContentRecorder.setContentRecordingSession(mDisplaySession);
774         mContentRecorder.updateRecording();
775         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
776 
777         float xScale = 2f;
778         float yScale = 4f;
779         verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0,
780                 yScale);
781         verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, 0,
782                 inputDisplayInfo.logicalHeight);
783     }
784 
785     @Test
testUpdateMirroredSurface_anisotropicPixel_scaleOnY()786     public void testUpdateMirroredSurface_anisotropicPixel_scaleOnY() {
787         mHandleAnisotropicDisplayMirroring = true;
788         int width = 6 * mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width();
789         int height = 2 * mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height();
790         DisplayInfo displayInfo = createDisplayInfo(width, height);
791         DisplayInfo inputDisplayInfo =
792                 mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo();
793         displayInfo.physicalXDpi = 2.0f * inputDisplayInfo.physicalXDpi;
794         displayInfo.physicalYDpi = inputDisplayInfo.physicalYDpi;
795         createContentRecorder(displayInfo);
796         mContentRecorder.setContentRecordingSession(mDisplaySession);
797         mContentRecorder.updateRecording();
798         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
799 
800         float xScale = 4f;
801         float yScale = 2f;
802         verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0,
803                 yScale);
804         verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface,
805                 inputDisplayInfo.logicalWidth, 0);
806     }
807 
808     @Test
testUpdateMirroredSurface_anisotropicPixel_shrinkCanvas()809     public void testUpdateMirroredSurface_anisotropicPixel_shrinkCanvas() {
810         mHandleAnisotropicDisplayMirroring = true;
811         int width = mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width() / 2;
812         int height = mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height() / 2;
813         DisplayInfo displayInfo = createDisplayInfo(width, height);
814         DisplayInfo inputDisplayInfo =
815                 mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo();
816         displayInfo.physicalXDpi = 2f * inputDisplayInfo.physicalXDpi;
817         displayInfo.physicalYDpi = inputDisplayInfo.physicalYDpi;
818         createContentRecorder(displayInfo);
819         mContentRecorder.setContentRecordingSession(mDisplaySession);
820         mContentRecorder.updateRecording();
821         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
822 
823         float xScale = 0.5f;
824         float yScale = 0.25f;
825         verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0,
826                 yScale);
827         verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, 0,
828                 (mSurfaceSize.y - height / 2) / 2);
829     }
830 
831     @Test
testDisplayContentUpdatesRecording_withoutSurface()832     public void testDisplayContentUpdatesRecording_withoutSurface() {
833         defaultInit();
834         // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
835         // mirror.
836         setUpDefaultTaskDisplayAreaWindowToken();
837 
838         // WHEN getting the DisplayContent for the new virtual display without providing a valid
839         // size from getDisplaySurfaceDefaultSize, i.e. the case of null surface.
840         final DisplayContent virtualDisplay =
841                 mRootWindowContainer.getDisplayContent(mDisplaySession.getVirtualDisplayId());
842         doReturn(null).when(mWm.mDisplayManagerInternal).getDisplaySurfaceDefaultSize(anyInt());
843         mWm.mContentRecordingController.setContentRecordingSessionLocked(mDisplaySession, mWm);
844         virtualDisplay.updateRecording();
845 
846         // THEN mirroring is not started, since a null surface indicates the VirtualDisplay is off.
847         assertThat(virtualDisplay.isCurrentlyRecording()).isFalse();
848     }
849 
850     @Test
testDisplayContentUpdatesRecording_withSurface()851     public void testDisplayContentUpdatesRecording_withSurface() {
852         defaultInit();
853         // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
854         // mirror.
855         setUpDefaultTaskDisplayAreaWindowToken();
856 
857         // WHEN getting the DisplayContent for the virtual display with a valid size from
858         // getDisplaySurfaceDefaultSize (done by surfaceControlMirrors in setUp).
859         final DisplayContent virtualDisplay =
860                 mRootWindowContainer.getDisplayContent(mDisplaySession.getVirtualDisplayId());
861         mWm.mContentRecordingController.setContentRecordingSessionLocked(mDisplaySession, mWm);
862         virtualDisplay.updateRecording();
863 
864         // THEN mirroring is initiated for the default display's DisplayArea.
865         assertThat(virtualDisplay.isCurrentlyRecording()).isTrue();
866     }
867 
868     @Test
testDisplayContentUpdatesRecording_displayMirroring()869     public void testDisplayContentUpdatesRecording_displayMirroring() {
870         defaultInit();
871         // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
872         // mirror.
873         setUpDefaultTaskDisplayAreaWindowToken();
874 
875         // GIVEN SurfaceControl can successfully mirror the provided surface.
876         surfaceControlMirrors(mSurfaceSize);
877         // Initially disable getDisplayIdToMirror since the DMS may create the DC outside the direct
878         // call in the test. We need to spy on the DC before updateRecording is called or we can't
879         // verify setDisplayMirroring is called
880         doReturn(INVALID_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt());
881 
882         // WHEN getting the DisplayContent for the new virtual display.
883         final DisplayContent virtualDisplay =
884                 mRootWindowContainer.getDisplayContent(mDisplaySession.getVirtualDisplayId());
885         // Return the default display as the value to mirror to ensure the VD with flag mirroring
886         // creates a ContentRecordingSession automatically.
887         doReturn(DEFAULT_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt());
888         clearInvocations(virtualDisplay);
889         virtualDisplay.updateRecording();
890 
891         // THEN mirroring is initiated for the default display's DisplayArea.
892         verify(virtualDisplay).setDisplayMirroring();
893         assertThat(virtualDisplay.isCurrentlyRecording()).isTrue();
894     }
895 
896     /**
897      * Creates a WindowToken associated with the default task DisplayArea, in order for that
898      * DisplayArea to be mirrored.
899      */
setUpDefaultTaskDisplayAreaWindowToken()900     private void setUpDefaultTaskDisplayAreaWindowToken() {
901         // GIVEN the default task display area is represented by the WindowToken.
902         spyOn(mWm.mWindowContextListenerController);
903         doReturn(mDefaultDisplay.getDefaultTaskDisplayArea()).when(
904                 mWm.mWindowContextListenerController).getContainer(any());
905     }
906 
907     /**
908      * Creates a {@link android.window.WindowContainerToken} associated with a task, in order for
909      * that task to be recorded.
910      */
setUpTaskWindowContainerToken(DisplayContent displayContent)911     private IBinder setUpTaskWindowContainerToken(DisplayContent displayContent) {
912         final Task rootTask = createTask(displayContent);
913         mTask = createTaskInRootTask(rootTask, 0 /* userId */);
914         // Ensure the task is not empty.
915         createActivityRecord(displayContent, mTask);
916         return mTask.getTaskInfo().token.asBinder();
917     }
918 
919     /**
920      * SurfaceControl successfully creates a mirrored surface of the given size.
921      */
surfaceControlMirrors(Point surfaceSize)922     private SurfaceControl surfaceControlMirrors(Point surfaceSize) {
923         // Do not set the parent, since the mirrored surface is the root of a new surface hierarchy.
924         SurfaceControl mirroredSurface = new SurfaceControl.Builder()
925                 .setName("mirroredSurface")
926                 .setBufferSize(surfaceSize.x, surfaceSize.y)
927                 .setCallsite("mirrorSurface")
928                 .build();
929         doReturn(mirroredSurface).when(() -> SurfaceControl.mirrorSurface(any()));
930         doReturn(surfaceSize).when(mWm.mDisplayManagerInternal).getDisplaySurfaceDefaultSize(
931                 anyInt());
932         return mirroredSurface;
933     }
934 }
935