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