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