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

Commit 5cdb9091 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Stop projection on display removal" into main

parents deec2f8d 8ae8fe47
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