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