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

Commit bbedd457 authored by Patrick Williams's avatar Patrick Williams Committed by Android (Google) Code Review
Browse files

Merge "Add screen recording detection public APIs" into main

parents 2465038b c3aa8fee
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -90,6 +90,7 @@ package android {
    field public static final String DELETE_PACKAGES = "android.permission.DELETE_PACKAGES";
    field public static final String DELIVER_COMPANION_MESSAGES = "android.permission.DELIVER_COMPANION_MESSAGES";
    field public static final String DETECT_SCREEN_CAPTURE = "android.permission.DETECT_SCREEN_CAPTURE";
    field @FlaggedApi("com.android.window.flags.screen_recording_callbacks") public static final String DETECT_SCREEN_RECORDING = "android.permission.DETECT_SCREEN_RECORDING";
    field public static final String DIAGNOSTIC = "android.permission.DIAGNOSTIC";
    field public static final String DISABLE_KEYGUARD = "android.permission.DISABLE_KEYGUARD";
    field public static final String DUMP = "android.permission.DUMP";
@@ -53784,6 +53785,7 @@ package android.view {
    method public default void addCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
    method public default void addCrossWindowBlurEnabledListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
    method public default void addProposedRotationListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
    method @FlaggedApi("com.android.window.flags.screen_recording_callbacks") @RequiresPermission(android.Manifest.permission.DETECT_SCREEN_RECORDING) public default int addScreenRecordingCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
    method @NonNull public default android.view.WindowMetrics getCurrentWindowMetrics();
    method @Deprecated public android.view.Display getDefaultDisplay();
    method @NonNull public default android.view.WindowMetrics getMaximumWindowMetrics();
@@ -53793,6 +53795,7 @@ package android.view {
    method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void registerUnbatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.os.Looper, @NonNull android.view.SurfaceControlInputReceiver);
    method public default void removeCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
    method public default void removeProposedRotationListener(@NonNull java.util.function.IntConsumer);
    method @FlaggedApi("com.android.window.flags.screen_recording_callbacks") @RequiresPermission(android.Manifest.permission.DETECT_SCREEN_RECORDING) public default void removeScreenRecordingCallback(@NonNull java.util.function.Consumer<java.lang.Integer>);
    method public void removeViewImmediate(android.view.View);
    method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void unregisterSurfaceControlInputReceiver(@NonNull android.view.SurfaceControl);
    method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void unregisterTrustedPresentationListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
@@ -53812,6 +53815,8 @@ package android.view {
    field public static final String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
    field public static final String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION = "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
    field @FlaggedApi("com.android.window.flags.supports_multi_instance_system_ui") public static final String PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI = "android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI";
    field @FlaggedApi("com.android.window.flags.screen_recording_callbacks") public static final int SCREEN_RECORDING_STATE_NOT_VISIBLE = 0; // 0x0
    field @FlaggedApi("com.android.window.flags.screen_recording_callbacks") public static final int SCREEN_RECORDING_STATE_VISIBLE = 1; // 0x1
  }
  public static class WindowManager.BadTokenException extends java.lang.RuntimeException {
+2 −0
Original line number Diff line number Diff line
@@ -1085,7 +1085,9 @@ interface IWindowManager

    void unregisterTrustedPresentationListener(in ITrustedPresentationListener listener, int id);

    @EnforcePermission("DETECT_SCREEN_RECORDING")
    boolean registerScreenRecordingCallback(IScreenRecordingCallback callback);

    @EnforcePermission("DETECT_SCREEN_RECORDING")
    void unregisterScreenRecordingCallback(IScreenRecordingCallback callback);
}
+146 −0
Original line number Diff line number Diff line
/*
 * Copyright 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.view;

import static android.Manifest.permission.DETECT_SCREEN_RECORDING;
import static android.view.WindowManager.SCREEN_RECORDING_STATE_NOT_VISIBLE;
import static android.view.WindowManager.SCREEN_RECORDING_STATE_VISIBLE;

import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.os.Binder;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.view.WindowManager.ScreenRecordingState;
import android.window.IScreenRecordingCallback;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;

/**
 * This class is responsible for calling app-registered screen recording callbacks. This class
 * registers a single screen recording callback with WindowManagerService and calls the
 * app-registered callbacks whenever that WindowManagerService callback is called.
 *
 * @hide
 */
public final class ScreenRecordingCallbacks {

    private static ScreenRecordingCallbacks sInstance;
    private static final Object sLock = new Object();

    private final ArrayMap<Consumer<@ScreenRecordingState Integer>, Executor> mCallbacks =
            new ArrayMap<>();

    private IScreenRecordingCallback mCallbackNotifier;
    private @ScreenRecordingState int mState = SCREEN_RECORDING_STATE_NOT_VISIBLE;

    private ScreenRecordingCallbacks() {}

    private static @NonNull IWindowManager getWindowManagerService() {
        return Objects.requireNonNull(WindowManagerGlobal.getWindowManagerService());
    }

    static ScreenRecordingCallbacks getInstance() {
        synchronized (sLock) {
            if (sInstance == null) {
                sInstance = new ScreenRecordingCallbacks();
            }
            return sInstance;
        }
    }

    @RequiresPermission(DETECT_SCREEN_RECORDING)
    @ScreenRecordingState
    int addCallback(
            @NonNull @CallbackExecutor Executor executor,
            @NonNull Consumer<@ScreenRecordingState Integer> callback) {
        synchronized (sLock) {
            if (mCallbackNotifier == null) {
                mCallbackNotifier =
                        new IScreenRecordingCallback.Stub() {
                            @Override
                            public void onScreenRecordingStateChanged(
                                    boolean visibleInScreenRecording) {
                                int state =
                                        visibleInScreenRecording
                                                ? SCREEN_RECORDING_STATE_VISIBLE
                                                : SCREEN_RECORDING_STATE_NOT_VISIBLE;
                                notifyCallbacks(state);
                            }
                        };
                try {
                    boolean visibleInScreenRecording =
                            getWindowManagerService()
                                    .registerScreenRecordingCallback(mCallbackNotifier);
                    mState =
                            visibleInScreenRecording
                                    ? SCREEN_RECORDING_STATE_VISIBLE
                                    : SCREEN_RECORDING_STATE_NOT_VISIBLE;
                } catch (RemoteException e) {
                    e.rethrowFromSystemServer();
                }
            }
            mCallbacks.put(callback, executor);
            return mState;
        }
    }

    @RequiresPermission(DETECT_SCREEN_RECORDING)
    void removeCallback(@NonNull Consumer<@ScreenRecordingState Integer> callback) {
        synchronized (sLock) {
            mCallbacks.remove(callback);
            if (mCallbacks.isEmpty()) {
                try {
                    getWindowManagerService().unregisterScreenRecordingCallback(mCallbackNotifier);
                } catch (RemoteException e) {
                    e.rethrowFromSystemServer();
                }
                mCallbackNotifier = null;
            }
        }
    }

    private void notifyCallbacks(@ScreenRecordingState int state) {
        List<Runnable> callbacks;
        synchronized (sLock) {
            mState = state;
            if (mCallbacks.isEmpty()) {
                return;
            }

            callbacks = new ArrayList<>();
            for (int i = 0; i < mCallbacks.size(); i++) {
                Consumer<Integer> callback = mCallbacks.keyAt(i);
                Executor executor = mCallbacks.valueAt(i);
                callbacks.add(() -> executor.execute(() -> callback.accept(state)));
            }
        }
        final long token = Binder.clearCallingIdentity();
        try {
            for (int i = 0; i < callbacks.size(); i++) {
                callbacks.get(i).run();
            }
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }
}
+63 −0
Original line number Diff line number Diff line
@@ -128,8 +128,10 @@ import android.window.TrustedPresentationThresholds;

import com.android.window.flags.Flags;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -6117,4 +6119,65 @@ public interface WindowManager extends ViewManager {
        throw new UnsupportedOperationException(
                "getDefaultToken is not implemented");
    }

    /** @hide */
    @Target(ElementType.TYPE_USE)
    @IntDef(
            prefix = {"SCREEN_RECORDING_STATE"},
            value = {SCREEN_RECORDING_STATE_NOT_VISIBLE, SCREEN_RECORDING_STATE_VISIBLE})
    @Retention(RetentionPolicy.SOURCE)
    @interface ScreenRecordingState {}

    /** Indicates the app that registered the callback is not visible in screen recording. */
    @FlaggedApi(com.android.window.flags.Flags.FLAG_SCREEN_RECORDING_CALLBACKS)
    int SCREEN_RECORDING_STATE_NOT_VISIBLE = 0;

    /** Indicates the app that registered the callback is visible in screen recording. */
    @FlaggedApi(com.android.window.flags.Flags.FLAG_SCREEN_RECORDING_CALLBACKS)
    int SCREEN_RECORDING_STATE_VISIBLE = 1;

    /**
     * Adds a screen recording callback. The callback will be invoked whenever the app becomes
     * visible in screen recording or was visible in screen recording and becomes invisible in
     * screen recording.
     *
     * <p>An app is considered visible in screen recording if any activities owned by the
     * registering process's UID are being recorded.
     *
     * <p>Example:
     *
     * <pre>
     * windowManager.addScreenRecordingCallback(state -> {
     *     // handle change in screen recording state
     * });
     * </pre>
     *
     * @param executor The executor on which callback method will be invoked.
     * @param callback The callback that will be invoked when screen recording visibility changes.
     * @return the current screen recording state.
     * @see #SCREEN_RECORDING_STATE_NOT_VISIBLE
     * @see #SCREEN_RECORDING_STATE_VISIBLE
     */
    @SuppressLint("AndroidFrameworkRequiresPermission")
    @RequiresPermission(permission.DETECT_SCREEN_RECORDING)
    @FlaggedApi(com.android.window.flags.Flags.FLAG_SCREEN_RECORDING_CALLBACKS)
    default @ScreenRecordingState int addScreenRecordingCallback(
            @NonNull @CallbackExecutor Executor executor,
            @NonNull Consumer<@ScreenRecordingState Integer> callback) {
        throw new UnsupportedOperationException();
    }

    /**
     * Removes a screen recording callback.
     *
     * @param callback The callback to remove.
     * @see #addScreenRecordingCallback(Executor, Consumer)
     */
    @SuppressLint("AndroidFrameworkRequiresPermission")
    @RequiresPermission(permission.DETECT_SCREEN_RECORDING)
    @FlaggedApi(com.android.window.flags.Flags.FLAG_SCREEN_RECORDING_CALLBACKS)
    default void removeScreenRecordingCallback(
            @NonNull Consumer<@ScreenRecordingState Integer> callback) {
        throw new UnsupportedOperationException();
    }
}
+23 −0
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
import static android.window.WindowProviderService.isWindowProviderService;

import static com.android.window.flags.Flags.screenRecordingCallbacks;

import android.annotation.CallbackExecutor;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -551,4 +553,25 @@ public final class WindowManagerImpl implements WindowManager {
    public IBinder getSurfaceControlInputClientToken(@NonNull SurfaceControl surfaceControl) {
        return mGlobal.getSurfaceControlInputClientToken(surfaceControl);
    }

    @Override
    public @ScreenRecordingState int addScreenRecordingCallback(
            @NonNull @CallbackExecutor Executor executor,
            @NonNull Consumer<@ScreenRecordingState Integer> callback) {
        if (screenRecordingCallbacks()) {
            Objects.requireNonNull(executor, "executor must not be null");
            Objects.requireNonNull(callback, "callback must not be null");
            return ScreenRecordingCallbacks.getInstance().addCallback(executor, callback);
        }
        return SCREEN_RECORDING_STATE_NOT_VISIBLE;
    }

    @Override
    public void removeScreenRecordingCallback(
            @NonNull Consumer<@ScreenRecordingState Integer> callback) {
        if (screenRecordingCallbacks()) {
            Objects.requireNonNull(callback, "callback must not be null");
            ScreenRecordingCallbacks.getInstance().removeCallback(callback);
        }
    }
}
Loading