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