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