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.content.Context.MEDIA_PROJECTION_SERVICE; 20 import static android.content.res.Configuration.ORIENTATION_UNDEFINED; 21 import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY; 22 import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK; 23 import static android.view.ViewProtoEnums.DISPLAY_STATE_OFF; 24 25 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONTENT_RECORDING; 26 27 import android.annotation.NonNull; 28 import android.annotation.Nullable; 29 import android.content.res.Configuration; 30 import android.graphics.Point; 31 import android.graphics.Rect; 32 import android.media.projection.IMediaProjectionManager; 33 import android.os.IBinder; 34 import android.os.RemoteException; 35 import android.os.ServiceManager; 36 import android.provider.DeviceConfig; 37 import android.view.ContentRecordingSession; 38 import android.view.ContentRecordingSession.RecordContent; 39 import android.view.Display; 40 import android.view.SurfaceControl; 41 42 import com.android.internal.annotations.VisibleForTesting; 43 import com.android.internal.protolog.common.ProtoLog; 44 45 /** 46 * Manages content recording for a particular {@link DisplayContent}. 47 */ 48 final class ContentRecorder implements WindowContainerListener { 49 50 /** 51 * The key for accessing the device config that controls if task recording is supported. 52 */ 53 @VisibleForTesting static final String KEY_RECORD_TASK_FEATURE = "record_task_content"; 54 55 /** 56 * The display content this class is handling recording for. 57 */ 58 @NonNull 59 private final DisplayContent mDisplayContent; 60 61 @Nullable private final MediaProjectionManagerWrapper mMediaProjectionManager; 62 63 /** 64 * The session for content recording, or null if this DisplayContent is not being used for 65 * recording. 66 */ 67 private ContentRecordingSession mContentRecordingSession = null; 68 69 /** 70 * The WindowContainer for the level of the hierarchy to record. 71 */ 72 @Nullable private WindowContainer mRecordedWindowContainer = null; 73 74 /** 75 * The surface for recording the contents of this hierarchy, or null if content recording is 76 * temporarily disabled. 77 */ 78 @Nullable private SurfaceControl mRecordedSurface = null; 79 80 /** 81 * The last bounds of the region to record. 82 */ 83 @Nullable private Rect mLastRecordedBounds = null; 84 85 /** 86 * The last size of the surface mirrored out to. 87 */ 88 @Nullable private Point mLastConsumingSurfaceSize = new Point(0, 0); 89 90 /** 91 * The last configuration orientation. 92 */ 93 @Configuration.Orientation 94 private int mLastOrientation = ORIENTATION_UNDEFINED; 95 ContentRecorder(@onNull DisplayContent displayContent)96 ContentRecorder(@NonNull DisplayContent displayContent) { 97 this(displayContent, new RemoteMediaProjectionManagerWrapper(displayContent.mDisplayId)); 98 } 99 100 @VisibleForTesting ContentRecorder(@onNull DisplayContent displayContent, @NonNull MediaProjectionManagerWrapper mediaProjectionManager)101 ContentRecorder(@NonNull DisplayContent displayContent, 102 @NonNull MediaProjectionManagerWrapper mediaProjectionManager) { 103 mDisplayContent = displayContent; 104 mMediaProjectionManager = mediaProjectionManager; 105 } 106 107 /** 108 * Sets the incoming recording session. Should only be used when starting to record on 109 * this display; stopping recording is handled separately when the display is destroyed. 110 * 111 * @param session the new session indicating recording will begin on this display. 112 */ setContentRecordingSession(@ullable ContentRecordingSession session)113 void setContentRecordingSession(@Nullable ContentRecordingSession session) { 114 mContentRecordingSession = session; 115 } 116 isContentRecordingSessionSet()117 boolean isContentRecordingSessionSet() { 118 return mContentRecordingSession != null; 119 } 120 121 /** 122 * Returns {@code true} if this DisplayContent is currently recording. 123 */ isCurrentlyRecording()124 boolean isCurrentlyRecording() { 125 return mContentRecordingSession != null && mRecordedSurface != null; 126 } 127 128 /** 129 * Start recording if this DisplayContent no longer has content. Pause recording if it now 130 * has content or the display is not on. 131 */ updateRecording()132 @VisibleForTesting void updateRecording() { 133 if (isCurrentlyRecording() && (mDisplayContent.getLastHasContent() 134 || mDisplayContent.getDisplayInfo().state == Display.STATE_OFF)) { 135 pauseRecording(); 136 } else { 137 // Display no longer has content, or now has a surface to write to, so try to start 138 // recording. 139 startRecordingIfNeeded(); 140 } 141 } 142 143 /** 144 * Handle a configuration change on the display content, and resize recording if needed. 145 * @param lastOrientation the prior orientation of the configuration 146 */ onConfigurationChanged(@onfiguration.Orientation int lastOrientation)147 void onConfigurationChanged(@Configuration.Orientation int lastOrientation) { 148 // Update surface for MediaProjection, if this DisplayContent is being used for recording. 149 if (!isCurrentlyRecording() || mLastRecordedBounds == null) { 150 return; 151 } 152 153 // Recording has already begun, but update recording since the display is now on. 154 if (mRecordedWindowContainer == null) { 155 ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, 156 "Content Recording: Unexpectedly null window container; unable to update " 157 + "recording for display %d", 158 mDisplayContent.getDisplayId()); 159 return; 160 } 161 162 // TODO(b/297514518) Do not start capture if the app is in PIP, the bounds are 163 // inaccurate. 164 if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) { 165 final Task capturedTask = mRecordedWindowContainer.asTask(); 166 if (capturedTask.inPinnedWindowingMode()) { 167 ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, 168 "Content Recording: Display %d was already recording, but " 169 + "pause capture since the task is in PIP", 170 mDisplayContent.getDisplayId()); 171 pauseRecording(); 172 return; 173 } 174 } 175 176 ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, 177 "Content Recording: Display %d was already recording, so apply " 178 + "transformations if necessary", 179 mDisplayContent.getDisplayId()); 180 // Retrieve the size of the region to record, and continue with the update 181 // if the bounds or orientation has changed. 182 final Rect recordedContentBounds = mRecordedWindowContainer.getBounds(); 183 @Configuration.Orientation int recordedContentOrientation = 184 mRecordedWindowContainer.getConfiguration().orientation; 185 final Point surfaceSize = fetchSurfaceSizeIfPresent(); 186 if (!mLastRecordedBounds.equals(recordedContentBounds) 187 || lastOrientation != recordedContentOrientation 188 || !mLastConsumingSurfaceSize.equals(surfaceSize)) { 189 if (surfaceSize != null) { 190 ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, 191 "Content Recording: Going ahead with updating recording for display " 192 + "%d to new bounds %s and/or orientation %d and/or surface " 193 + "size %s", 194 mDisplayContent.getDisplayId(), recordedContentBounds, 195 recordedContentOrientation, surfaceSize); 196 updateMirroredSurface(mRecordedWindowContainer.getSyncTransaction(), 197 recordedContentBounds, surfaceSize); 198 } else { 199 // If the surface removed, do nothing. We will handle this via onDisplayChanged 200 // (the display will be off if the surface is removed). 201 ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, 202 "Content Recording: Unable to update recording for display %d to new " 203 + "bounds %s and/or orientation %d and/or surface size %s, " 204 + "since the surface is not available.", 205 mDisplayContent.getDisplayId(), recordedContentBounds, 206 recordedContentOrientation, surfaceSize); 207 } 208 } 209 } 210 211 /** 212 * Pauses recording on this display content. Note the session does not need to be updated, 213 * since recording can be resumed still. 214 */ pauseRecording()215 void pauseRecording() { 216 if (mRecordedSurface == null) { 217 return; 218 } 219 ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, 220 "Content Recording: Display %d has content (%b) so pause recording", 221 mDisplayContent.getDisplayId(), mDisplayContent.getLastHasContent()); 222 // If the display is not on and it is a virtual display, then it no longer has an 223 // associated surface to write output to. 224 // If the display now has content, stop mirroring to it. 225 mDisplayContent.mWmService.mTransactionFactory.get() 226 // Remove the reference to mMirroredSurface, to clean up associated memory. 227 .remove(mRecordedSurface) 228 // Reparent the SurfaceControl of this DisplayContent back to mSurfaceControl, 229 // to allow content to be added to it. This allows this DisplayContent to stop 230 // mirroring and show content normally. 231 .reparent(mDisplayContent.getWindowingLayer(), mDisplayContent.getSurfaceControl()) 232 .reparent(mDisplayContent.getOverlayLayer(), mDisplayContent.getSurfaceControl()) 233 .apply(); 234 // Pause mirroring by destroying the reference to the mirrored layer. 235 mRecordedSurface = null; 236 // Do not un-set the token, in case content is removed and recording should begin again. 237 } 238 239 /** 240 * Stops recording on this DisplayContent, and updates the session details. 241 */ stopRecording()242 void stopRecording() { 243 unregisterListener(); 244 if (mRecordedSurface != null) { 245 // Do not wait for the mirrored surface to be garbage collected, but clean up 246 // immediately. 247 mDisplayContent.mWmService.mTransactionFactory.get().remove(mRecordedSurface).apply(); 248 mRecordedSurface = null; 249 clearContentRecordingSession(); 250 // Do not need to force remove the VirtualDisplay; this is handled by the media 251 // projection service when the display is removed. 252 } 253 } 254 255 256 /** 257 * Ensure recording does not fall back to the display stack; ensure the recording is stopped 258 * and the client notified by tearing down the virtual display. 259 */ stopMediaProjection()260 private void stopMediaProjection() { 261 ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, 262 "Content Recording: Stop MediaProjection on virtual display %d", 263 mDisplayContent.getDisplayId()); 264 if (mMediaProjectionManager != null) { 265 mMediaProjectionManager.stopActiveProjection(); 266 } 267 } 268 269 /** 270 * Removes both the local cache and WM Service view of the current session, to stop the session 271 * on this display. 272 */ clearContentRecordingSession()273 private void clearContentRecordingSession() { 274 // Update the cached session state first, since updating the service will result in always 275 // returning to this instance to update recording state. 276 mContentRecordingSession = null; 277 mDisplayContent.mWmService.mContentRecordingController.setContentRecordingSessionLocked( 278 null, mDisplayContent.mWmService); 279 } 280 unregisterListener()281 private void unregisterListener() { 282 Task recordedTask = mRecordedWindowContainer != null 283 ? mRecordedWindowContainer.asTask() : null; 284 if (recordedTask == null || !isRecordingContentTask()) { 285 return; 286 } 287 recordedTask.unregisterWindowContainerListener(this); 288 mRecordedWindowContainer = null; 289 } 290 291 /** 292 * Start recording to this DisplayContent if it does not have its own content. Captures the 293 * content of a WindowContainer indicated by a WindowToken. If unable to start recording, falls 294 * back to original MediaProjection approach. 295 */ startRecordingIfNeeded()296 private void startRecordingIfNeeded() { 297 // Only record if this display does not have its own content, is not recording already, 298 // and if this display is on (it has a surface to write output to). 299 if (mDisplayContent.getLastHasContent() || isCurrentlyRecording() 300 || mDisplayContent.getDisplayInfo().state == Display.STATE_OFF 301 || mContentRecordingSession == null) { 302 return; 303 } 304 305 if (mContentRecordingSession.isWaitingForConsent()) { 306 ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, "Content Recording: waiting to record, so do " 307 + "nothing"); 308 return; 309 } 310 311 mRecordedWindowContainer = retrieveRecordedWindowContainer(); 312 if (mRecordedWindowContainer == null) { 313 // Either the token is missing, or the window associated with the token is missing. 314 // Error has already been handled, so just leave. 315 return; 316 } 317 318 // TODO(b/297514518) Do not start capture if the app is in PIP, the bounds are inaccurate. 319 if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) { 320 if (mRecordedWindowContainer.asTask().inPinnedWindowingMode()) { 321 ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, 322 "Content Recording: Display %d should start recording, but " 323 + "don't yet since the task is in PIP", 324 mDisplayContent.getDisplayId()); 325 return; 326 } 327 } 328 329 final Point surfaceSize = fetchSurfaceSizeIfPresent(); 330 if (surfaceSize == null) { 331 ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, 332 "Content Recording: Unable to start recording for display %d since the " 333 + "surface is not available.", 334 mDisplayContent.getDisplayId()); 335 return; 336 } 337 ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, 338 "Content Recording: Display %d has no content and is on, so start recording for " 339 + "state %d", 340 mDisplayContent.getDisplayId(), mDisplayContent.getDisplayInfo().state); 341 342 // Create a mirrored hierarchy for the SurfaceControl of the DisplayArea to capture. 343 mRecordedSurface = SurfaceControl.mirrorSurface( 344 mRecordedWindowContainer.getSurfaceControl()); 345 SurfaceControl.Transaction transaction = 346 mDisplayContent.mWmService.mTransactionFactory.get() 347 // Set the mMirroredSurface's parent to the root SurfaceControl for this 348 // DisplayContent. This brings the new mirrored hierarchy under this 349 // DisplayContent, 350 // so SurfaceControl will write the layers of this hierarchy to the 351 // output surface 352 // provided by the app. 353 .reparent(mRecordedSurface, mDisplayContent.getSurfaceControl()) 354 // Reparent the SurfaceControl of this DisplayContent to null, to prevent 355 // content 356 // being added to it. This ensures that no app launched explicitly on the 357 // VirtualDisplay will show up as part of the mirrored content. 358 .reparent(mDisplayContent.getWindowingLayer(), null) 359 .reparent(mDisplayContent.getOverlayLayer(), null); 360 // Retrieve the size of the DisplayArea to mirror. 361 updateMirroredSurface(transaction, mRecordedWindowContainer.getBounds(), surfaceSize); 362 transaction.apply(); 363 364 // Notify the client about the visibility of the mirrored region, now that we have begun 365 // capture. 366 if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) { 367 mMediaProjectionManager.notifyActiveProjectionCapturedContentVisibilityChanged( 368 mRecordedWindowContainer.asTask().isVisibleRequested()); 369 } else { 370 int currentDisplayState = 371 mRecordedWindowContainer.asDisplayContent().getDisplayInfo().state; 372 mMediaProjectionManager.notifyActiveProjectionCapturedContentVisibilityChanged( 373 currentDisplayState != DISPLAY_STATE_OFF); 374 } 375 376 // No need to clean up. In SurfaceFlinger, parents hold references to their children. The 377 // mirrored SurfaceControl is alive since the parent DisplayContent SurfaceControl is 378 // holding a reference to it. Therefore, the mirrored SurfaceControl will be cleaned up 379 // when the VirtualDisplay is destroyed - which will clean up this DisplayContent. 380 } 381 382 /** 383 * Retrieves the {@link WindowContainer} for the level of the hierarchy to start recording, 384 * indicated by the {@link #mContentRecordingSession}. Performs any error handling and state 385 * updates necessary if the {@link WindowContainer} could not be retrieved. 386 * {@link #mContentRecordingSession} must be non-null. 387 * 388 * @return a {@link WindowContainer} to record, or {@code null} if an error was encountered. The 389 * error is logged and any cleanup is handled. 390 */ 391 @Nullable retrieveRecordedWindowContainer()392 private WindowContainer retrieveRecordedWindowContainer() { 393 @RecordContent final int contentToRecord = mContentRecordingSession.getContentToRecord(); 394 final IBinder tokenToRecord = mContentRecordingSession.getTokenToRecord(); 395 switch (contentToRecord) { 396 case RECORD_CONTENT_DISPLAY: 397 // Given the id of the display to record, retrieve the associated DisplayContent. 398 final DisplayContent dc = 399 mDisplayContent.mWmService.mRoot.getDisplayContent( 400 mContentRecordingSession.getDisplayToRecord()); 401 if (dc == null) { 402 // Fall back to screenrecording using the data sent to DisplayManager 403 mDisplayContent.mWmService.mDisplayManagerInternal.setWindowManagerMirroring( 404 mDisplayContent.getDisplayId(), false); 405 handleStartRecordingFailed(); 406 ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, 407 "Unable to retrieve window container to start recording for " 408 + "display %d", mDisplayContent.getDisplayId()); 409 return null; 410 } 411 // TODO(206461622) Migrate to using the RootDisplayArea 412 return dc; 413 case RECORD_CONTENT_TASK: 414 if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER, 415 KEY_RECORD_TASK_FEATURE, false)) { 416 handleStartRecordingFailed(); 417 ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, 418 "Content Recording: Unable to record task since feature is disabled %d", 419 mDisplayContent.getDisplayId()); 420 return null; 421 } 422 // Given the WindowToken of the region to record, retrieve the associated 423 // SurfaceControl. 424 if (tokenToRecord == null) { 425 handleStartRecordingFailed(); 426 ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, 427 "Content Recording: Unable to start recording due to null token for " 428 + "display %d", 429 mDisplayContent.getDisplayId()); 430 return null; 431 } 432 Task taskToRecord = WindowContainer.fromBinder(tokenToRecord).asTask(); 433 if (taskToRecord == null) { 434 handleStartRecordingFailed(); 435 ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, 436 "Content Recording: Unable to retrieve task to start recording for " 437 + "display %d", 438 mDisplayContent.getDisplayId()); 439 } else { 440 taskToRecord.registerWindowContainerListener(this); 441 } 442 return taskToRecord; 443 default: 444 // Not a valid region, or recording is disabled, so fall back to Display stack 445 // capture for the entire display. 446 handleStartRecordingFailed(); 447 ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, 448 "Content Recording: Unable to start recording due to invalid region for " 449 + "display %d", 450 mDisplayContent.getDisplayId()); 451 return null; 452 } 453 } 454 455 /** 456 * Exit this recording session. 457 * <p> 458 * If this is a task session, stop the recording entirely, including the MediaProjection. 459 * Do not fall back to recording the entire display on the display stack; this would surprise 460 * the user given they selected task capture. 461 * </p><p> 462 * If this is a display session, just stop recording by layer mirroring. Fall back to recording 463 * from the display stack. 464 * </p> 465 */ handleStartRecordingFailed()466 private void handleStartRecordingFailed() { 467 final boolean shouldExitTaskRecording = isRecordingContentTask(); 468 unregisterListener(); 469 clearContentRecordingSession(); 470 if (shouldExitTaskRecording) { 471 // Clean up the cached session first to ensure recording doesn't re-start, since 472 // tearing down the display will generate display events which will trickle back here. 473 stopMediaProjection(); 474 } 475 } 476 477 /** 478 * Apply transformations to the mirrored surface to ensure the captured contents are scaled to 479 * fit and centred in the output surface. 480 * 481 * @param transaction the transaction to include transformations of mMirroredSurface 482 * to. Transaction is not applied before returning. 483 * @param recordedContentBounds bounds of the content to record to the surface provided by 484 * the app. 485 * @param surfaceSize the default size of the surface to write the display area 486 * content to 487 */ updateMirroredSurface(SurfaceControl.Transaction transaction, Rect recordedContentBounds, Point surfaceSize)488 @VisibleForTesting void updateMirroredSurface(SurfaceControl.Transaction transaction, 489 Rect recordedContentBounds, Point surfaceSize) { 490 // Calculate the scale to apply to the root mirror SurfaceControl to fit the size of the 491 // output surface. 492 float scaleX = surfaceSize.x / (float) recordedContentBounds.width(); 493 float scaleY = surfaceSize.y / (float) recordedContentBounds.height(); 494 float scale = Math.min(scaleX, scaleY); 495 int scaledWidth = Math.round(scale * (float) recordedContentBounds.width()); 496 int scaledHeight = Math.round(scale * (float) recordedContentBounds.height()); 497 498 // Calculate the shift to apply to the root mirror SurfaceControl to centre the mirrored 499 // contents in the output surface. 500 int shiftedX = 0; 501 if (scaledWidth != surfaceSize.x) { 502 shiftedX = (surfaceSize.x - scaledWidth) / 2; 503 } 504 int shiftedY = 0; 505 if (scaledHeight != surfaceSize.y) { 506 shiftedY = (surfaceSize.y - scaledHeight) / 2; 507 } 508 509 ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, 510 "Content Recording: Apply transformations of shift %d x %d, scale %f, crop (aka " 511 + "recorded content size) %d x %d for display %d; display has size %d x " 512 + "%d; surface has size %d x %d", 513 shiftedX, shiftedY, scale, recordedContentBounds.width(), 514 recordedContentBounds.height(), mDisplayContent.getDisplayId(), 515 mDisplayContent.getConfiguration().screenWidthDp, 516 mDisplayContent.getConfiguration().screenHeightDp, surfaceSize.x, surfaceSize.y); 517 518 transaction 519 // Crop the area to capture to exclude the 'extra' wallpaper that is used 520 // for parallax (b/189930234). 521 .setWindowCrop(mRecordedSurface, recordedContentBounds.width(), 522 recordedContentBounds.height()) 523 // Scale the root mirror SurfaceControl, based upon the size difference between the 524 // source (DisplayArea to capture) and output (surface the app reads images from). 525 .setMatrix(mRecordedSurface, scale, 0 /* dtdx */, 0 /* dtdy */, scale) 526 // Position needs to be updated when the mirrored DisplayArea has changed, since 527 // the content will no longer be centered in the output surface. 528 .setPosition(mRecordedSurface, shiftedX /* x */, shiftedY /* y */); 529 mLastRecordedBounds = new Rect(recordedContentBounds); 530 mLastConsumingSurfaceSize.x = surfaceSize.x; 531 mLastConsumingSurfaceSize.y = surfaceSize.y; 532 // Request to notify the client about the resize. 533 mMediaProjectionManager.notifyActiveProjectionCapturedContentResized( 534 mLastRecordedBounds.width(), mLastRecordedBounds.height()); 535 } 536 537 /** 538 * Returns a non-null {@link Point} if the surface is present, or null otherwise 539 */ 540 @Nullable fetchSurfaceSizeIfPresent()541 private Point fetchSurfaceSizeIfPresent() { 542 // Retrieve the default size of the surface the app provided to 543 // MediaProjection#createVirtualDisplay. Note the app is the consumer of the surface, 544 // since it reads out buffers from the surface, and SurfaceFlinger is the producer since 545 // it writes the mirrored layers to the buffers. 546 Point surfaceSize = 547 mDisplayContent.mWmService.mDisplayManagerInternal.getDisplaySurfaceDefaultSize( 548 mDisplayContent.getDisplayId()); 549 if (surfaceSize == null) { 550 // Layer mirroring started with a null surface, so do not apply any transformations yet. 551 // State of virtual display will change to 'ON' when the surface is set. 552 // will get event DISPLAY_DEVICE_EVENT_CHANGED 553 ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, 554 "Content Recording: Provided surface for recording on display %d is not " 555 + "present, so do not update the surface", 556 mDisplayContent.getDisplayId()); 557 return null; 558 } 559 return surfaceSize; 560 } 561 562 // WindowContainerListener 563 @Override onRemoved()564 public void onRemoved() { 565 ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, 566 "Content Recording: Recorded task is removed, so stop recording on display %d", 567 mDisplayContent.getDisplayId()); 568 569 unregisterListener(); 570 // Stop mirroring and teardown. 571 clearContentRecordingSession(); 572 // Clean up the cached session first to ensure recording doesn't re-start, since 573 // tearing down the display will generate display events which will trickle back here. 574 stopMediaProjection(); 575 } 576 577 // WindowContainerListener 578 @Override onMergedOverrideConfigurationChanged( Configuration mergedOverrideConfiguration)579 public void onMergedOverrideConfigurationChanged( 580 Configuration mergedOverrideConfiguration) { 581 WindowContainerListener.super.onMergedOverrideConfigurationChanged( 582 mergedOverrideConfiguration); 583 onConfigurationChanged(mLastOrientation); 584 mLastOrientation = mergedOverrideConfiguration.orientation; 585 } 586 587 // WindowContainerListener 588 @Override onVisibleRequestedChanged(boolean isVisibleRequested)589 public void onVisibleRequestedChanged(boolean isVisibleRequested) { 590 // Check still recording just to be safe. 591 if (isCurrentlyRecording() && mLastRecordedBounds != null) { 592 mMediaProjectionManager.notifyActiveProjectionCapturedContentVisibilityChanged( 593 isVisibleRequested); 594 } 595 } 596 597 @VisibleForTesting interface MediaProjectionManagerWrapper { stopActiveProjection()598 void stopActiveProjection(); notifyActiveProjectionCapturedContentResized(int width, int height)599 void notifyActiveProjectionCapturedContentResized(int width, int height); notifyActiveProjectionCapturedContentVisibilityChanged(boolean isVisible)600 void notifyActiveProjectionCapturedContentVisibilityChanged(boolean isVisible); 601 } 602 603 private static final class RemoteMediaProjectionManagerWrapper implements 604 MediaProjectionManagerWrapper { 605 606 private final int mDisplayId; 607 @Nullable private IMediaProjectionManager mIMediaProjectionManager = null; 608 RemoteMediaProjectionManagerWrapper(int displayId)609 RemoteMediaProjectionManagerWrapper(int displayId) { 610 mDisplayId = displayId; 611 } 612 613 @Override stopActiveProjection()614 public void stopActiveProjection() { 615 fetchMediaProjectionManager(); 616 if (mIMediaProjectionManager == null) { 617 return; 618 } 619 try { 620 ProtoLog.e(WM_DEBUG_CONTENT_RECORDING, 621 "Content Recording: stopping active projection for display %d", 622 mDisplayId); 623 mIMediaProjectionManager.stopActiveProjection(); 624 } catch (RemoteException e) { 625 ProtoLog.e(WM_DEBUG_CONTENT_RECORDING, 626 "Content Recording: Unable to tell MediaProjectionManagerService to stop " 627 + "the active projection for display %d: %s", 628 mDisplayId, e); 629 } 630 } 631 632 @Override notifyActiveProjectionCapturedContentResized(int width, int height)633 public void notifyActiveProjectionCapturedContentResized(int width, int height) { 634 fetchMediaProjectionManager(); 635 if (mIMediaProjectionManager == null) { 636 return; 637 } 638 try { 639 mIMediaProjectionManager.notifyActiveProjectionCapturedContentResized(width, 640 height); 641 } catch (RemoteException e) { 642 ProtoLog.e(WM_DEBUG_CONTENT_RECORDING, 643 "Content Recording: Unable to tell MediaProjectionManagerService about " 644 + "resizing the active projection: %s", 645 e); 646 } 647 } 648 649 @Override notifyActiveProjectionCapturedContentVisibilityChanged(boolean isVisible)650 public void notifyActiveProjectionCapturedContentVisibilityChanged(boolean isVisible) { 651 fetchMediaProjectionManager(); 652 if (mIMediaProjectionManager == null) { 653 return; 654 } 655 try { 656 mIMediaProjectionManager.notifyActiveProjectionCapturedContentVisibilityChanged( 657 isVisible); 658 } catch (RemoteException e) { 659 ProtoLog.e(WM_DEBUG_CONTENT_RECORDING, 660 "Content Recording: Unable to tell MediaProjectionManagerService about " 661 + "visibility change on the active projection: %s", 662 e); 663 } 664 } 665 fetchMediaProjectionManager()666 private void fetchMediaProjectionManager() { 667 if (mIMediaProjectionManager != null) { 668 return; 669 } 670 IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE); 671 if (b == null) { 672 return; 673 } 674 mIMediaProjectionManager = IMediaProjectionManager.Stub.asInterface(b); 675 } 676 } 677 isRecordingContentTask()678 private boolean isRecordingContentTask() { 679 return mContentRecordingSession != null 680 && mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK; 681 } 682 } 683