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