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.wm.ContentRecorder.KEY_RECORD_TASK_FEATURE;
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.never;
45 
46 import android.app.WindowConfiguration;
47 import android.content.pm.ActivityInfo;
48 import android.content.res.Configuration;
49 import android.graphics.Point;
50 import android.graphics.Rect;
51 import android.os.IBinder;
52 import android.platform.test.annotations.Presubmit;
53 import android.provider.DeviceConfig;
54 import android.view.ContentRecordingSession;
55 import android.view.DisplayInfo;
56 import android.view.Gravity;
57 import android.view.SurfaceControl;
58 
59 import androidx.annotation.NonNull;
60 import androidx.test.filters.SmallTest;
61 
62 import com.android.server.wm.ContentRecorder.MediaProjectionManagerWrapper;
63 
64 import org.junit.After;
65 import org.junit.Before;
66 import org.junit.Test;
67 import org.junit.runner.RunWith;
68 import org.mockito.Mock;
69 import org.mockito.MockitoAnnotations;
70 
71 import java.util.concurrent.CountDownLatch;
72 
73 /**
74  * Tests for the {@link ContentRecorder} class.
75  *
76  * Build/Install/Run:
77  *  atest WmTests:ContentRecorderTests
78  */
79 @SmallTest
80 @Presubmit
81 @RunWith(WindowTestRunner.class)
82 public class ContentRecorderTests extends WindowTestsBase {
83     private static IBinder sTaskWindowContainerToken;
84     private DisplayContent mVirtualDisplayContent;
85     private Task mTask;
86     private final ContentRecordingSession mDisplaySession =
87             ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY);
88     private final ContentRecordingSession mWaitingDisplaySession =
89             ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY);
90     private ContentRecordingSession mTaskSession;
91     private static Point sSurfaceSize;
92     private ContentRecorder mContentRecorder;
93     @Mock private MediaProjectionManagerWrapper mMediaProjectionManagerWrapper;
94     private SurfaceControl mRecordedSurface;
95     // Handle feature flag.
96     private ConfigListener mConfigListener;
97     private CountDownLatch mLatch;
98 
setUp()99     @Before public void setUp() {
100         MockitoAnnotations.initMocks(this);
101 
102         // GIVEN SurfaceControl can successfully mirror the provided surface.
103         sSurfaceSize = new Point(
104                 mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
105                 mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
106         mRecordedSurface = surfaceControlMirrors(sSurfaceSize);
107 
108         doReturn(INVALID_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt());
109 
110         // GIVEN the VirtualDisplay associated with the session (so the display has state ON).
111         DisplayInfo displayInfo = mDisplayInfo;
112         displayInfo.logicalWidth = sSurfaceSize.x;
113         displayInfo.logicalHeight = sSurfaceSize.y;
114         displayInfo.state = STATE_ON;
115         mVirtualDisplayContent = createNewDisplay(displayInfo);
116         final int displayId = mVirtualDisplayContent.getDisplayId();
117         mContentRecorder = new ContentRecorder(mVirtualDisplayContent,
118                 mMediaProjectionManagerWrapper);
119         spyOn(mVirtualDisplayContent);
120 
121         // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
122         // record.
123         mDisplaySession.setVirtualDisplayId(displayId);
124         mDisplaySession.setDisplayToRecord(mDefaultDisplay.mDisplayId);
125 
126         // GIVEN there is a window token associated with a task to record.
127         sTaskWindowContainerToken = setUpTaskWindowContainerToken(mVirtualDisplayContent);
128         mTaskSession = ContentRecordingSession.createTaskSession(sTaskWindowContainerToken);
129         mTaskSession.setVirtualDisplayId(displayId);
130 
131         // GIVEN a session is waiting for the user to review consent.
132         mWaitingDisplaySession.setVirtualDisplayId(displayId);
133         mWaitingDisplaySession.setWaitingForConsent(true);
134 
135         mConfigListener = new ConfigListener();
136         DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
137                 mContext.getMainExecutor(), mConfigListener);
138         mLatch = new CountDownLatch(1);
139         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_RECORD_TASK_FEATURE,
140                 "true", true);
141 
142         // Skip unnecessary operations of relayout.
143         spyOn(mWm.mWindowPlacerLocked);
144         doNothing().when(mWm.mWindowPlacerLocked).performSurfacePlacement(anyBoolean());
145     }
146 
147     @After
teardown()148     public void teardown() {
149         DeviceConfig.removeOnPropertiesChangedListener(mConfigListener);
150     }
151 
152     @Test
testIsCurrentlyRecording()153     public void testIsCurrentlyRecording() {
154         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
155 
156         mContentRecorder.updateRecording();
157         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
158     }
159 
160     @Test
testUpdateRecording_display()161     public void testUpdateRecording_display() {
162         mContentRecorder.setContentRecordingSession(mDisplaySession);
163         mContentRecorder.updateRecording();
164         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
165     }
166 
167     @Test
testUpdateRecording_display_invalidDisplayIdToMirror()168     public void testUpdateRecording_display_invalidDisplayIdToMirror() {
169         ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
170                 INVALID_DISPLAY);
171         mContentRecorder.setContentRecordingSession(session);
172         mContentRecorder.updateRecording();
173         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
174     }
175 
176     @Test
testUpdateRecording_display_noDisplayContentToMirror()177     public void testUpdateRecording_display_noDisplayContentToMirror() {
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_featureDisabled()186     public void testUpdateRecording_task_featureDisabled() {
187         mLatch = new CountDownLatch(1);
188         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_RECORD_TASK_FEATURE,
189                 "false", false);
190         mContentRecorder.setContentRecordingSession(mTaskSession);
191         mContentRecorder.updateRecording();
192         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
193     }
194 
195     @Test
testUpdateRecording_task_featureEnabled()196     public void testUpdateRecording_task_featureEnabled() {
197         // Feature already enabled; don't need to again.
198         mContentRecorder.setContentRecordingSession(mTaskSession);
199         mContentRecorder.updateRecording();
200         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
201     }
202 
203     @Test
testUpdateRecording_task_nullToken()204     public void testUpdateRecording_task_nullToken() {
205         ContentRecordingSession session = mTaskSession;
206         session.setVirtualDisplayId(mDisplaySession.getVirtualDisplayId());
207         session.setTokenToRecord(null);
208         mContentRecorder.setContentRecordingSession(session);
209         mContentRecorder.updateRecording();
210         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
211         verify(mMediaProjectionManagerWrapper).stopActiveProjection();
212     }
213 
214     @Test
testUpdateRecording_task_noWindowContainer()215     public void testUpdateRecording_task_noWindowContainer() {
216         // Use the window container token of the DisplayContent, rather than task.
217         ContentRecordingSession invalidTaskSession = ContentRecordingSession.createTaskSession(
218                 new WindowContainer.RemoteToken(mDisplayContent));
219         mContentRecorder.setContentRecordingSession(invalidTaskSession);
220         mContentRecorder.updateRecording();
221         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
222         verify(mMediaProjectionManagerWrapper).stopActiveProjection();
223     }
224 
225     @Test
testUpdateRecording_wasPaused()226     public void testUpdateRecording_wasPaused() {
227         mContentRecorder.setContentRecordingSession(mDisplaySession);
228         mContentRecorder.updateRecording();
229 
230         mContentRecorder.pauseRecording();
231         mContentRecorder.updateRecording();
232         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
233     }
234 
235     @Test
testUpdateRecording_waitingForConsent()236     public void testUpdateRecording_waitingForConsent() {
237         mContentRecorder.setContentRecordingSession(mWaitingDisplaySession);
238         mContentRecorder.updateRecording();
239         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
240 
241 
242         mContentRecorder.setContentRecordingSession(mDisplaySession);
243         mContentRecorder.updateRecording();
244         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
245     }
246 
247     @Test
testOnConfigurationChanged_neverRecording()248     public void testOnConfigurationChanged_neverRecording() {
249         mContentRecorder.onConfigurationChanged(ORIENTATION_PORTRAIT);
250 
251         verify(mTransaction, never()).setPosition(eq(mRecordedSurface), anyFloat(), anyFloat());
252         verify(mTransaction, never()).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
253                 anyFloat(), anyFloat());
254     }
255 
256     @Test
testOnConfigurationChanged_resizesSurface()257     public void testOnConfigurationChanged_resizesSurface() {
258         mContentRecorder.setContentRecordingSession(mDisplaySession);
259         mContentRecorder.updateRecording();
260         // Ensure a different orientation when we check if something has changed.
261         @Configuration.Orientation final int lastOrientation =
262                 mDisplayContent.getConfiguration().orientation == ORIENTATION_PORTRAIT
263                         ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
264         mContentRecorder.onConfigurationChanged(lastOrientation);
265 
266         verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
267                 anyFloat());
268         verify(mTransaction, atLeast(2)).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
269                 anyFloat(), anyFloat());
270     }
271 
272     @Test
testOnConfigurationChanged_resizesVirtualDisplay()273     public void testOnConfigurationChanged_resizesVirtualDisplay() {
274         final int newWidth = 55;
275         mContentRecorder.setContentRecordingSession(mDisplaySession);
276         mContentRecorder.updateRecording();
277 
278         // The user rotates the device, so the host app resizes the virtual display for the capture.
279         resizeDisplay(mDisplayContent, newWidth, sSurfaceSize.y);
280         resizeDisplay(mVirtualDisplayContent, newWidth, sSurfaceSize.y);
281         mContentRecorder.onConfigurationChanged(mDisplayContent.getConfiguration().orientation);
282 
283         verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
284                 anyFloat());
285         verify(mTransaction, atLeast(2)).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
286                 anyFloat(), anyFloat());
287     }
288 
289     @Test
testOnConfigurationChanged_rotateVirtualDisplay()290     public void testOnConfigurationChanged_rotateVirtualDisplay() {
291         mContentRecorder.setContentRecordingSession(mDisplaySession);
292         mContentRecorder.updateRecording();
293 
294         // Change a value that we shouldn't rely upon; it has the wrong type.
295         mVirtualDisplayContent.setOverrideOrientation(SCREEN_ORIENTATION_FULL_SENSOR);
296         mContentRecorder.onConfigurationChanged(
297                 mVirtualDisplayContent.getConfiguration().orientation);
298 
299         // No resize is issued, only the initial transformations when we started recording.
300         verify(mTransaction).setPosition(eq(mRecordedSurface), anyFloat(),
301                 anyFloat());
302         verify(mTransaction).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
303                 anyFloat(), anyFloat());
304     }
305 
306     /**
307      * Test that resizing the output surface results in resizing the mirrored content to fit.
308      */
309     @Test
testOnConfigurationChanged_resizeSurface()310     public void testOnConfigurationChanged_resizeSurface() {
311         mContentRecorder.setContentRecordingSession(mDisplaySession);
312         mContentRecorder.updateRecording();
313 
314         // Resize the output surface.
315         final Point newSurfaceSize = new Point(Math.round(sSurfaceSize.x / 2f),
316                 Math.round(sSurfaceSize.y * 2));
317         doReturn(newSurfaceSize).when(mWm.mDisplayManagerInternal).getDisplaySurfaceDefaultSize(
318                 anyInt());
319         mContentRecorder.onConfigurationChanged(
320                 mVirtualDisplayContent.getConfiguration().orientation);
321 
322         // No resize is issued, only the initial transformations when we started recording.
323         verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
324                 anyFloat());
325         verify(mTransaction, atLeast(2)).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
326                 anyFloat(), anyFloat());
327 
328     }
329 
330     @Test
testOnTaskOrientationConfigurationChanged_resizesSurface()331     public void testOnTaskOrientationConfigurationChanged_resizesSurface() {
332         mContentRecorder.setContentRecordingSession(mTaskSession);
333         mContentRecorder.updateRecording();
334 
335         Configuration config = mTask.getConfiguration();
336         // Ensure a different orientation when we compare.
337         @Configuration.Orientation final int orientation =
338                 config.orientation == ORIENTATION_PORTRAIT ? ORIENTATION_LANDSCAPE
339                         : ORIENTATION_PORTRAIT;
340         final Rect lastBounds = config.windowConfiguration.getBounds();
341         config.orientation = orientation;
342         config.windowConfiguration.setBounds(
343                 new Rect(0, 0, lastBounds.height(), lastBounds.width()));
344         mTask.onConfigurationChanged(config);
345 
346         verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
347                 anyFloat());
348         verify(mTransaction, atLeast(2)).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
349                 anyFloat(), anyFloat());
350     }
351 
352     @Test
testOnTaskBoundsConfigurationChanged_notifiesCallback()353     public void testOnTaskBoundsConfigurationChanged_notifiesCallback() {
354         mTask.getRootTask().setWindowingMode(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
355 
356         final int minWidth = 222;
357         final int minHeight = 777;
358         final int recordedWidth = 333;
359         final int recordedHeight = 999;
360 
361         final ActivityInfo info = new ActivityInfo();
362         info.windowLayout = new ActivityInfo.WindowLayout(-1 /* width */,
363                 -1 /* widthFraction */, -1 /* height */, -1 /* heightFraction */,
364                 Gravity.NO_GRAVITY, minWidth, minHeight);
365         mTask.setMinDimensions(info);
366 
367         // WHEN a recording is ongoing.
368         mContentRecorder.setContentRecordingSession(mTaskSession);
369         mContentRecorder.updateRecording();
370         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
371 
372         // WHEN a configuration change arrives, and the recorded content is a different size.
373         Configuration configuration = mTask.getConfiguration();
374         configuration.windowConfiguration.setBounds(new Rect(0, 0, recordedWidth, recordedHeight));
375         configuration.windowConfiguration.setAppBounds(
376                 new Rect(0, 0, recordedWidth, recordedHeight));
377         mTask.onConfigurationChanged(configuration);
378         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
379 
380         // THEN content in the captured DisplayArea is scaled to fit the surface size.
381         verify(mTransaction, atLeastOnce()).setMatrix(eq(mRecordedSurface), anyFloat(), eq(0f),
382                 eq(0f),
383                 anyFloat());
384         // THEN the resize callback is notified.
385         verify(mMediaProjectionManagerWrapper).notifyActiveProjectionCapturedContentResized(
386                 recordedWidth, recordedHeight);
387     }
388 
389     @Test
testTaskWindowingModeChanged_pip_stopsRecording()390     public void testTaskWindowingModeChanged_pip_stopsRecording() {
391         // WHEN a recording is ongoing.
392         mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
393         mContentRecorder.setContentRecordingSession(mTaskSession);
394         mContentRecorder.updateRecording();
395         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
396 
397         // WHEN a configuration change arrives, and the task is now pinned.
398         mTask.setWindowingMode(WINDOWING_MODE_PINNED);
399         Configuration configuration = mTask.getConfiguration();
400         mTask.onConfigurationChanged(configuration);
401 
402         // THEN recording is paused.
403         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
404     }
405 
406     @Test
testTaskWindowingModeChanged_fullscreen_startsRecording()407     public void testTaskWindowingModeChanged_fullscreen_startsRecording() {
408         // WHEN a recording is ongoing.
409         mTask.setWindowingMode(WINDOWING_MODE_PINNED);
410         mContentRecorder.setContentRecordingSession(mTaskSession);
411         mContentRecorder.updateRecording();
412         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
413 
414         // WHEN the task is now fullscreen.
415         mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
416         mContentRecorder.updateRecording();
417 
418         // THEN recording is started.
419         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
420     }
421 
422     @Test
testStartRecording_notifiesCallback_taskSession()423     public void testStartRecording_notifiesCallback_taskSession() {
424         // WHEN a recording is ongoing.
425         mContentRecorder.setContentRecordingSession(mTaskSession);
426         mContentRecorder.updateRecording();
427         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
428 
429         // THEN the visibility change callback is notified.
430         verify(mMediaProjectionManagerWrapper)
431                 .notifyActiveProjectionCapturedContentVisibilityChanged(true);
432     }
433 
434     @Test
testStartRecording_notifiesCallback_displaySession()435     public void testStartRecording_notifiesCallback_displaySession() {
436         // WHEN a recording is ongoing.
437         mContentRecorder.setContentRecordingSession(mDisplaySession);
438         mContentRecorder.updateRecording();
439         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
440 
441         // THEN the visibility change callback is notified.
442         verify(mMediaProjectionManagerWrapper)
443                 .notifyActiveProjectionCapturedContentVisibilityChanged(true);
444     }
445 
446     @Test
testStartRecording_taskInPIP_recordingNotStarted()447     public void testStartRecording_taskInPIP_recordingNotStarted() {
448         // GIVEN a task is in PIP.
449         mContentRecorder.setContentRecordingSession(mTaskSession);
450         mTask.setWindowingMode(WINDOWING_MODE_PINNED);
451 
452         // WHEN a recording tries to start.
453         mContentRecorder.updateRecording();
454 
455         // THEN recording does not start.
456         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
457     }
458 
459     @Test
testStartRecording_taskInSplit_recordingStarted()460     public void testStartRecording_taskInSplit_recordingStarted() {
461         // GIVEN a task is in PIP.
462         mContentRecorder.setContentRecordingSession(mTaskSession);
463         mTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
464 
465         // WHEN a recording tries to start.
466         mContentRecorder.updateRecording();
467 
468         // THEN recording does not start.
469         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
470     }
471 
472     @Test
testStartRecording_taskInFullscreen_recordingStarted()473     public void testStartRecording_taskInFullscreen_recordingStarted() {
474         // GIVEN a task is in PIP.
475         mContentRecorder.setContentRecordingSession(mTaskSession);
476         mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
477 
478         // WHEN a recording tries to start.
479         mContentRecorder.updateRecording();
480 
481         // THEN recording does not start.
482         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
483     }
484 
485     @Test
testOnVisibleRequestedChanged_notifiesCallback()486     public void testOnVisibleRequestedChanged_notifiesCallback() {
487         // WHEN a recording is ongoing.
488         mContentRecorder.setContentRecordingSession(mTaskSession);
489         mContentRecorder.updateRecording();
490         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
491 
492         // WHEN the child requests a visibility change.
493         boolean isVisibleRequested = true;
494         mContentRecorder.onVisibleRequestedChanged(isVisibleRequested);
495 
496         // THEN the visibility change callback is notified.
497         verify(mMediaProjectionManagerWrapper, atLeastOnce())
498                 .notifyActiveProjectionCapturedContentVisibilityChanged(isVisibleRequested);
499 
500         // WHEN the child requests a visibility change.
501         isVisibleRequested = false;
502         mContentRecorder.onVisibleRequestedChanged(isVisibleRequested);
503 
504         // THEN the visibility change callback is notified.
505         verify(mMediaProjectionManagerWrapper)
506                 .notifyActiveProjectionCapturedContentVisibilityChanged(isVisibleRequested);
507     }
508 
509     @Test
testOnVisibleRequestedChanged_noRecording_doesNotNotifyCallback()510     public void testOnVisibleRequestedChanged_noRecording_doesNotNotifyCallback() {
511         // WHEN a recording is not ongoing.
512         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
513 
514         // WHEN the child requests a visibility change.
515         boolean isVisibleRequested = true;
516         mContentRecorder.onVisibleRequestedChanged(isVisibleRequested);
517 
518         // THEN the visibility change callback is not notified.
519         verify(mMediaProjectionManagerWrapper, never())
520                 .notifyActiveProjectionCapturedContentVisibilityChanged(isVisibleRequested);
521 
522         // WHEN the child requests a visibility change.
523         isVisibleRequested = false;
524         mContentRecorder.onVisibleRequestedChanged(isVisibleRequested);
525 
526         // THEN the visibility change callback is not notified.
527         verify(mMediaProjectionManagerWrapper, never())
528                 .notifyActiveProjectionCapturedContentVisibilityChanged(isVisibleRequested);
529     }
530 
531     @Test
testPauseRecording_pausesRecording()532     public void testPauseRecording_pausesRecording() {
533         mContentRecorder.setContentRecordingSession(mDisplaySession);
534         mContentRecorder.updateRecording();
535 
536         mContentRecorder.pauseRecording();
537         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
538     }
539 
540     @Test
testPauseRecording_neverRecording()541     public void testPauseRecording_neverRecording() {
542         mContentRecorder.pauseRecording();
543         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
544     }
545 
546     @Test
testStopRecording_stopsRecording()547     public void testStopRecording_stopsRecording() {
548         mContentRecorder.setContentRecordingSession(mDisplaySession);
549         mContentRecorder.updateRecording();
550 
551         mContentRecorder.stopRecording();
552         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
553     }
554 
555     @Test
testStopRecording_neverRecording()556     public void testStopRecording_neverRecording() {
557         mContentRecorder.stopRecording();
558         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
559     }
560 
561     @Test
testRemoveTask_stopsRecording()562     public void testRemoveTask_stopsRecording() {
563         mContentRecorder.setContentRecordingSession(mTaskSession);
564         mContentRecorder.updateRecording();
565 
566         mTask.removeImmediately();
567 
568         verify(mMediaProjectionManagerWrapper).stopActiveProjection();
569     }
570 
571     @Test
testRemoveTask_stopsRecording_nullSessionShouldNotThrowExceptions()572     public void testRemoveTask_stopsRecording_nullSessionShouldNotThrowExceptions() {
573         mContentRecorder.setContentRecordingSession(mTaskSession);
574         mContentRecorder.updateRecording();
575         mContentRecorder.setContentRecordingSession(null);
576         mTask.removeImmediately();
577     }
578 
579     @Test
testUpdateMirroredSurface_capturedAreaResized()580     public void testUpdateMirroredSurface_capturedAreaResized() {
581         mContentRecorder.setContentRecordingSession(mDisplaySession);
582         mContentRecorder.updateRecording();
583         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
584 
585         // WHEN attempting to mirror on the virtual display, and the captured content is resized.
586         float xScale = 0.7f;
587         float yScale = 2f;
588         Rect displayAreaBounds = new Rect(0, 0, Math.round(sSurfaceSize.x * xScale),
589                 Math.round(sSurfaceSize.y * yScale));
590         mContentRecorder.updateMirroredSurface(mTransaction, displayAreaBounds, sSurfaceSize);
591         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
592 
593         // THEN content in the captured DisplayArea is scaled to fit the surface size.
594         verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, 1.0f / yScale, 0, 0,
595                 1.0f / yScale);
596         // THEN captured content is positioned in the centre of the output surface.
597         int scaledWidth = Math.round((float) displayAreaBounds.width() / xScale);
598         int xInset = (sSurfaceSize.x - scaledWidth) / 2;
599         verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, xInset, 0);
600         // THEN the resize callback is notified.
601         verify(mMediaProjectionManagerWrapper).notifyActiveProjectionCapturedContentResized(
602                 displayAreaBounds.width(), displayAreaBounds.height());
603     }
604 
605     /**
606      * Creates a {@link android.window.WindowContainerToken} associated with a task, in order for
607      * that task to be recorded.
608      */
setUpTaskWindowContainerToken(DisplayContent displayContent)609     private IBinder setUpTaskWindowContainerToken(DisplayContent displayContent) {
610         final Task rootTask = createTask(displayContent);
611         mTask = createTaskInRootTask(rootTask, 0 /* userId */);
612         // Ensure the task is not empty.
613         createActivityRecord(displayContent, mTask);
614         return mTask.getTaskInfo().token.asBinder();
615     }
616 
617     /**
618      * SurfaceControl successfully creates a mirrored surface of the given size.
619      */
surfaceControlMirrors(Point surfaceSize)620     private SurfaceControl surfaceControlMirrors(Point surfaceSize) {
621         // Do not set the parent, since the mirrored surface is the root of a new surface hierarchy.
622         SurfaceControl mirroredSurface = new SurfaceControl.Builder()
623                 .setName("mirroredSurface")
624                 .setBufferSize(surfaceSize.x, surfaceSize.y)
625                 .setCallsite("mirrorSurface")
626                 .build();
627         doReturn(mirroredSurface).when(() -> SurfaceControl.mirrorSurface(any()));
628         doReturn(surfaceSize).when(mWm.mDisplayManagerInternal).getDisplaySurfaceDefaultSize(
629                 anyInt());
630         return mirroredSurface;
631     }
632 
633     private class ConfigListener implements DeviceConfig.OnPropertiesChangedListener {
634         @Override
onPropertiesChanged(@onNull DeviceConfig.Properties properties)635         public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
636             if (mLatch != null && properties.getKeyset().contains(KEY_RECORD_TASK_FEATURE)) {
637                 mLatch.countDown();
638             }
639         }
640     }
641 }
642