Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8ae8fe47 authored by Vadim Caen's avatar Vadim Caen
Browse files

Stop projection on display removal

Listen to display removal and stop the projection if the display
currently being recorded is removed.

Test: android.media.projection.cts.MediaProjectionStoppingTest#mediaProjection_connectedDisplayRemoved_stops
Bug: 439017127
Flag: com.android.media.projection.flags.stop_on_display_removal
Change-Id: I7147bcaed7a4ccfd956cf30ab80feaab354613bd
parent 54951b09
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -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
    }
}
+12 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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;
            }
@@ -337,6 +343,9 @@ public final class MediaProjectionManagerService extends SystemService
        }
        mProjectionToken = projection.asBinder();
        mProjectionGrant = projection;
        if (Flags.stopOnDisplayRemoval()) {
            mMediaProjectionStopController.setRecordedDisplay(projection.mDisplayId);
        }
        dispatchStart(projection);
    }

@@ -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);
+58 −11
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;
@@ -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)
@@ -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;
@@ -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);
        }
@@ -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