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 com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONTENT_RECORDING;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.view.ContentRecordingSession;
24 
25 import com.android.internal.annotations.VisibleForTesting;
26 import com.android.internal.protolog.common.ProtoLog;
27 
28 /**
29  * Orchestrates the handoff between displays if the recording session changes, and keeps track of
30  * the current recording session state. Only supports one content recording session on the device at
31  * once.
32  */
33 final class ContentRecordingController {
34 
35     /**
36      * The current recording session.
37      */
38     @Nullable
39     private ContentRecordingSession mSession = null;
40 
41     @Nullable
42     private DisplayContent mDisplayContent = null;
43 
44     /**
45      * Returns the current recording session. If returns {@code null}, then recording is not taking
46      * place.
47      */
48     @Nullable
49     @VisibleForTesting
getContentRecordingSessionLocked()50     ContentRecordingSession getContentRecordingSessionLocked() {
51         // Copy out the session, to allow it to be modified without updating this reference.
52         return mSession;
53     }
54 
55     /**
56      * Updates the current recording session.
57      * <p>Handles the following scenarios:
58      * <ul>
59      *         <li>Invalid scenarios: The incoming session is malformed.</li>
60      *         <li>Ignored scenario: the incoming session is identical to the current session.</li>
61      *         <li>Start Scenario: Starting a new session. Recording begins immediately.</li>
62      *         <li>Takeover Scenario: Occurs during a Start Scenario, if a pre-existing session was
63      *         in-progress. For example, recording on VirtualDisplay "app_foo" was ongoing. A
64      *         session for VirtualDisplay "app_bar" arrives. The controller stops the session on
65      *         VirtualDisplay "app_foo" and allows the session for VirtualDisplay "app_bar" to
66      *         begin.</li>
67      *         <li>Stopping scenario: The incoming session is null and there is currently an ongoing
68      *         session. The controller stops recording.</li>
69      *         <li>Updating scenario: There is an update for the same display, where recording
70      *         was previously not taking place but is now permitted to go ahead.</li>
71      * </ul>
72      *
73      * @param incomingSession The incoming recording session (either an update to a current session
74      *                        or a new session), or null to stop the current session.
75      * @param wmService       The window manager service.
76      */
setContentRecordingSessionLocked(@ullable ContentRecordingSession incomingSession, @NonNull WindowManagerService wmService)77     void setContentRecordingSessionLocked(@Nullable ContentRecordingSession incomingSession,
78             @NonNull WindowManagerService wmService) {
79         // Invalid scenario: ignore invalid incoming session.
80         if (incomingSession != null && !ContentRecordingSession.isValid(incomingSession)) {
81             return;
82         }
83         final boolean hasSessionUpdatedWithConsent =
84                 mSession != null && incomingSession != null && mSession.isWaitingForConsent()
85                         && !incomingSession.isWaitingForConsent();
86         if (ContentRecordingSession.isProjectionOnSameDisplay(mSession, incomingSession)) {
87             if (hasSessionUpdatedWithConsent) {
88                 // Updating scenario: accept an incoming session updating the current display.
89                 ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
90                         "Content Recording: Accept session updating same display %d with granted "
91                                 + "consent, with an existing session %s",
92                         incomingSession.getVirtualDisplayId(), mSession.getVirtualDisplayId());
93             } else {
94                 // Ignored scenario: ignore identical incoming session.
95                 ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
96                         "Content Recording: Ignoring session on same display %d, with an existing "
97                                 + "session %s",
98                         incomingSession.getVirtualDisplayId(), mSession.getVirtualDisplayId());
99                 return;
100             }
101         }
102         DisplayContent incomingDisplayContent = null;
103         if (incomingSession != null) {
104             // Start scenario: recording begins immediately.
105             ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
106                     "Content Recording: Handle incoming session on display %d, with a "
107                             + "pre-existing session %s", incomingSession.getVirtualDisplayId(),
108                     mSession == null ? null : mSession.getVirtualDisplayId());
109             incomingDisplayContent = wmService.mRoot.getDisplayContentOrCreate(
110                     incomingSession.getVirtualDisplayId());
111             if (incomingDisplayContent == null) {
112                 ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
113                         "Content Recording: Incoming session on display %d can't be set since it "
114                                 + "is already null; the corresponding VirtualDisplay must have "
115                                 + "already been removed.", incomingSession.getVirtualDisplayId());
116                 return;
117             }
118             incomingDisplayContent.setContentRecordingSession(incomingSession);
119             // Updating scenario: Explicitly ask ContentRecorder to update, since no config or
120             // display change will trigger an update from the DisplayContent. There exists a
121             // scenario where a DisplayContent is created, but it's ContentRecordingSession hasn't
122             // been set yet due to a race condition. On creation, updateRecording fails to start
123             // recording, so now this call guarantees recording will be started from somewhere.
124             incomingDisplayContent.updateRecording();
125         }
126         // Takeover and stopping scenario: stop recording on the pre-existing session.
127         if (mSession != null && !hasSessionUpdatedWithConsent) {
128             ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
129                     "Content Recording: Pause the recording session on display %s",
130                     mDisplayContent.getDisplayId());
131             mDisplayContent.pauseRecording();
132             mDisplayContent.setContentRecordingSession(null);
133         }
134         // Update the cached states.
135         mDisplayContent = incomingDisplayContent;
136         mSession = incomingSession;
137     }
138 }
139