Loading media/java/android/media/flags/projection.aconfig +10 −0 Original line number Diff line number Diff line Loading @@ -74,3 +74,13 @@ flag { purpose: PURPOSE_BUGFIX } } flag { namespace: "media_projection" name: "stop_on_display_removal" description: "Stops the current media projection session when the recorded display is removed" bug: "439017127" metadata { purpose: PURPOSE_BUGFIX } } services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +12 −0 Original line number Diff line number Diff line Loading @@ -30,6 +30,8 @@ import static android.media.projection.ReviewGrantedConsentResult.UNKNOWN; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static com.android.server.media.projection.MediaProjectionStopController.STOP_REASON_DISPLAY_REMOVED; import android.Manifest; import android.annotation.EnforcePermission; import android.annotation.NonNull; Loading Loading @@ -179,6 +181,10 @@ public final class MediaProjectionManagerService extends SystemService private void maybeStopMediaProjection(int reason) { synchronized (mLock) { if (Flags.stopOnDisplayRemoval() && reason == STOP_REASON_DISPLAY_REMOVED) { mProjectionGrant.stop(StopReason.STOP_TARGET_REMOVED); return; } if (mMediaProjectionStopController.isExemptFromStopping(mProjectionGrant, reason)) { return; } Loading Loading @@ -337,6 +343,9 @@ public final class MediaProjectionManagerService extends SystemService } mProjectionToken = projection.asBinder(); mProjectionGrant = projection; if (Flags.stopOnDisplayRemoval()) { mMediaProjectionStopController.setRecordedDisplay(projection.mDisplayId); } dispatchStart(projection); } Loading @@ -352,6 +361,9 @@ public final class MediaProjectionManagerService extends SystemService ? session.getTargetUid() : ContentRecordingSession.TARGET_UID_UNKNOWN; mMediaProjectionMetricsLogger.logStopped(projection.uid, targetUid, stopReason); if (Flags.stopOnDisplayRemoval()) { mMediaProjectionStopController.clearRecordedDisplay(); } mProjectionToken = null; mProjectionGrant = null; dispatchStop(projection); Loading services/core/java/com/android/server/media/projection/MediaProjectionStopController.java +58 −11 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import android.companion.AssociationRequest; import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager; import android.hardware.display.DisplayManager; import android.os.Binder; import android.os.SystemClock; import android.os.UserHandle; Loading @@ -37,6 +38,7 @@ import android.util.Slog; import android.view.Display; import com.android.internal.annotations.VisibleForTesting; import com.android.media.projection.flags.Flags; import com.android.server.SystemConfig; import java.util.List; Loading @@ -55,7 +57,11 @@ public class MediaProjectionStopController { @VisibleForTesting static final int STOP_REASON_CALL_END = 2; static final int STOP_REASON_DISPLAY_REMOVED = 3; private final TelephonyCallback mTelephonyCallback = new ProjectionTelephonyCallback(); private final ProjectionDisplayListener mProjectionDisplayListener = new ProjectionDisplayListener(); private final Consumer<Integer> mStopReasonConsumer; private final KeyguardManager mKeyguardManager; private final TelecomManager mTelecomManager; Loading @@ -64,16 +70,19 @@ public class MediaProjectionStopController { private final PackageManager mPackageManager; private final RoleHolderProvider mRoleHolderProvider; private final ContentResolver mContentResolver; private final DisplayManager mDisplayManager; private int mCurrentRecordedDisplay = Display.INVALID_DISPLAY; private boolean mIsInCall; private long mLastCallStartTimeMillis; private final KeyguardManager.KeyguardLockedStateListener mOnKeyguardLockedStateChanged = this::onKeyguardLockedStateChanged; @VisibleForTesting interface RoleHolderProvider { List<String> getRoleHoldersAsUser(String roleName, UserHandle user); } public MediaProjectionStopController(Context context, Consumer<Integer> stopReasonConsumer) { this(context, stopReasonConsumer, (roleName, user) -> context.getSystemService(RoleManager.class) Loading @@ -88,6 +97,7 @@ public class MediaProjectionStopController { mTelecomManager = context.getSystemService(TelecomManager.class); mTelephonyManager = context.getSystemService(TelephonyManager.class); mAppOpsManager = context.getSystemService(AppOpsManager.class); mDisplayManager = context.getSystemService(DisplayManager.class); mPackageManager = context.getPackageManager(); mContentResolver = context.getContentResolver(); mRoleHolderProvider = roleHolderProvider; Loading @@ -100,12 +110,17 @@ public class MediaProjectionStopController { final long token = Binder.clearCallingIdentity(); try { mKeyguardManager.addKeyguardLockedStateListener(context.getMainExecutor(), this::onKeyguardLockedStateChanged); mOnKeyguardLockedStateChanged); if (com.android.media.projection.flags.Flags.stopMediaProjectionOnCallEnd()) { callStateChanged(); mTelephonyManager.registerTelephonyCallback(context.getMainExecutor(), mTelephonyCallback); } if (Flags.stopOnDisplayRemoval()) { mDisplayManager.registerDisplayListener(context.getMainExecutor(), DisplayManager.EVENT_TYPE_DISPLAY_REMOVED, mProjectionDisplayListener); } } finally { Binder.restoreCallingIdentity(token); } Loading Loading @@ -229,21 +244,53 @@ public class MediaProjectionStopController { mIsInCall = isInCall; } /** * Sets the recorded display ID. */ public void setRecordedDisplay(int displayId) { mCurrentRecordedDisplay = displayId; } /** * Clears the recorded display ID. */ public void clearRecordedDisplay() { setRecordedDisplay(Display.INVALID_DISPLAY); } void onDisplayRemoved() { mStopReasonConsumer.accept(STOP_REASON_DISPLAY_REMOVED); } /** * @return a String representation of the stop reason interrupting MediaProjection. */ public static String stopReasonToString(int stopReason) { switch (stopReason) { case STOP_REASON_KEYGUARD -> { return "STOP_REASON_KEYGUARD"; } case STOP_REASON_CALL_END -> { return "STOP_REASON_CALL_END"; return switch (stopReason) { case STOP_REASON_KEYGUARD -> "STOP_REASON_KEYGUARD"; case STOP_REASON_CALL_END -> "STOP_REASON_CALL_END"; case STOP_REASON_DISPLAY_REMOVED -> "STOP_REASON_DISPLAY_REMOVED"; default -> ""; }; } private final class ProjectionDisplayListener implements DisplayManager.DisplayListener { @Override public void onDisplayAdded(int displayId) { } @Override public void onDisplayRemoved(int displayId) { if (displayId == mCurrentRecordedDisplay) { clearRecordedDisplay(); MediaProjectionStopController.this.onDisplayRemoved(); } return ""; } @Override public void onDisplayChanged(int displayId) { } } private final class ProjectionTelephonyCallback extends TelephonyCallback implements TelephonyCallback.CallStateListener { @Override Loading Loading
media/java/android/media/flags/projection.aconfig +10 −0 Original line number Diff line number Diff line Loading @@ -74,3 +74,13 @@ flag { purpose: PURPOSE_BUGFIX } } flag { namespace: "media_projection" name: "stop_on_display_removal" description: "Stops the current media projection session when the recorded display is removed" bug: "439017127" metadata { purpose: PURPOSE_BUGFIX } }
services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +12 −0 Original line number Diff line number Diff line Loading @@ -30,6 +30,8 @@ import static android.media.projection.ReviewGrantedConsentResult.UNKNOWN; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static com.android.server.media.projection.MediaProjectionStopController.STOP_REASON_DISPLAY_REMOVED; import android.Manifest; import android.annotation.EnforcePermission; import android.annotation.NonNull; Loading Loading @@ -179,6 +181,10 @@ public final class MediaProjectionManagerService extends SystemService private void maybeStopMediaProjection(int reason) { synchronized (mLock) { if (Flags.stopOnDisplayRemoval() && reason == STOP_REASON_DISPLAY_REMOVED) { mProjectionGrant.stop(StopReason.STOP_TARGET_REMOVED); return; } if (mMediaProjectionStopController.isExemptFromStopping(mProjectionGrant, reason)) { return; } Loading Loading @@ -337,6 +343,9 @@ public final class MediaProjectionManagerService extends SystemService } mProjectionToken = projection.asBinder(); mProjectionGrant = projection; if (Flags.stopOnDisplayRemoval()) { mMediaProjectionStopController.setRecordedDisplay(projection.mDisplayId); } dispatchStart(projection); } Loading @@ -352,6 +361,9 @@ public final class MediaProjectionManagerService extends SystemService ? session.getTargetUid() : ContentRecordingSession.TARGET_UID_UNKNOWN; mMediaProjectionMetricsLogger.logStopped(projection.uid, targetUid, stopReason); if (Flags.stopOnDisplayRemoval()) { mMediaProjectionStopController.clearRecordedDisplay(); } mProjectionToken = null; mProjectionGrant = null; dispatchStop(projection); Loading
services/core/java/com/android/server/media/projection/MediaProjectionStopController.java +58 −11 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import android.companion.AssociationRequest; import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager; import android.hardware.display.DisplayManager; import android.os.Binder; import android.os.SystemClock; import android.os.UserHandle; Loading @@ -37,6 +38,7 @@ import android.util.Slog; import android.view.Display; import com.android.internal.annotations.VisibleForTesting; import com.android.media.projection.flags.Flags; import com.android.server.SystemConfig; import java.util.List; Loading @@ -55,7 +57,11 @@ public class MediaProjectionStopController { @VisibleForTesting static final int STOP_REASON_CALL_END = 2; static final int STOP_REASON_DISPLAY_REMOVED = 3; private final TelephonyCallback mTelephonyCallback = new ProjectionTelephonyCallback(); private final ProjectionDisplayListener mProjectionDisplayListener = new ProjectionDisplayListener(); private final Consumer<Integer> mStopReasonConsumer; private final KeyguardManager mKeyguardManager; private final TelecomManager mTelecomManager; Loading @@ -64,16 +70,19 @@ public class MediaProjectionStopController { private final PackageManager mPackageManager; private final RoleHolderProvider mRoleHolderProvider; private final ContentResolver mContentResolver; private final DisplayManager mDisplayManager; private int mCurrentRecordedDisplay = Display.INVALID_DISPLAY; private boolean mIsInCall; private long mLastCallStartTimeMillis; private final KeyguardManager.KeyguardLockedStateListener mOnKeyguardLockedStateChanged = this::onKeyguardLockedStateChanged; @VisibleForTesting interface RoleHolderProvider { List<String> getRoleHoldersAsUser(String roleName, UserHandle user); } public MediaProjectionStopController(Context context, Consumer<Integer> stopReasonConsumer) { this(context, stopReasonConsumer, (roleName, user) -> context.getSystemService(RoleManager.class) Loading @@ -88,6 +97,7 @@ public class MediaProjectionStopController { mTelecomManager = context.getSystemService(TelecomManager.class); mTelephonyManager = context.getSystemService(TelephonyManager.class); mAppOpsManager = context.getSystemService(AppOpsManager.class); mDisplayManager = context.getSystemService(DisplayManager.class); mPackageManager = context.getPackageManager(); mContentResolver = context.getContentResolver(); mRoleHolderProvider = roleHolderProvider; Loading @@ -100,12 +110,17 @@ public class MediaProjectionStopController { final long token = Binder.clearCallingIdentity(); try { mKeyguardManager.addKeyguardLockedStateListener(context.getMainExecutor(), this::onKeyguardLockedStateChanged); mOnKeyguardLockedStateChanged); if (com.android.media.projection.flags.Flags.stopMediaProjectionOnCallEnd()) { callStateChanged(); mTelephonyManager.registerTelephonyCallback(context.getMainExecutor(), mTelephonyCallback); } if (Flags.stopOnDisplayRemoval()) { mDisplayManager.registerDisplayListener(context.getMainExecutor(), DisplayManager.EVENT_TYPE_DISPLAY_REMOVED, mProjectionDisplayListener); } } finally { Binder.restoreCallingIdentity(token); } Loading Loading @@ -229,21 +244,53 @@ public class MediaProjectionStopController { mIsInCall = isInCall; } /** * Sets the recorded display ID. */ public void setRecordedDisplay(int displayId) { mCurrentRecordedDisplay = displayId; } /** * Clears the recorded display ID. */ public void clearRecordedDisplay() { setRecordedDisplay(Display.INVALID_DISPLAY); } void onDisplayRemoved() { mStopReasonConsumer.accept(STOP_REASON_DISPLAY_REMOVED); } /** * @return a String representation of the stop reason interrupting MediaProjection. */ public static String stopReasonToString(int stopReason) { switch (stopReason) { case STOP_REASON_KEYGUARD -> { return "STOP_REASON_KEYGUARD"; } case STOP_REASON_CALL_END -> { return "STOP_REASON_CALL_END"; return switch (stopReason) { case STOP_REASON_KEYGUARD -> "STOP_REASON_KEYGUARD"; case STOP_REASON_CALL_END -> "STOP_REASON_CALL_END"; case STOP_REASON_DISPLAY_REMOVED -> "STOP_REASON_DISPLAY_REMOVED"; default -> ""; }; } private final class ProjectionDisplayListener implements DisplayManager.DisplayListener { @Override public void onDisplayAdded(int displayId) { } @Override public void onDisplayRemoved(int displayId) { if (displayId == mCurrentRecordedDisplay) { clearRecordedDisplay(); MediaProjectionStopController.this.onDisplayRemoved(); } return ""; } @Override public void onDisplayChanged(int displayId) { } } private final class ProjectionTelephonyCallback extends TelephonyCallback implements TelephonyCallback.CallStateListener { @Override Loading