Create Screenshot Detection APIs
This is a followup on ag/19854404
The APIs are used on a per Activity granularity, a client can register
their callback via: `registerScreenCaptureCallback(mExecutor, mCallback);` on Activity
The activity token is used to communiate from the client side to the
ActivityTaskManager and ActivityRecord on the server side. The WMS will receieve a notification when the hardware chord for screenshots is triggered by the user, it would then find all activites that have visible windows and trigger the callbacks registered on all of them.
for more details, see go/screenshot-detection-dd
Bug: 245115438
Test: CTS tests && Manual
Change-Id: I27bf708ac86187f066971b1b01c5b308f78a01b3
diff --git a/core/api/current.txt b/core/api/current.txt
index 3d76b66..8d9a539 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4319,6 +4319,7 @@
method public void recreate();
method public void registerActivityLifecycleCallbacks(@NonNull android.app.Application.ActivityLifecycleCallbacks);
method public void registerForContextMenu(android.view.View);
+ method @RequiresPermission(android.Manifest.permission.DETECT_SCREEN_CAPTURE) public void registerScreenCaptureCallback(@NonNull java.util.concurrent.Executor, @NonNull android.app.Activity.ScreenCaptureCallback);
method public boolean releaseInstance();
method @Deprecated public final void removeDialog(int);
method public void reportFullyDrawn();
@@ -4404,6 +4405,7 @@
method public void triggerSearch(String, @Nullable android.os.Bundle);
method public void unregisterActivityLifecycleCallbacks(@NonNull android.app.Application.ActivityLifecycleCallbacks);
method public void unregisterForContextMenu(android.view.View);
+ method @RequiresPermission(android.Manifest.permission.DETECT_SCREEN_CAPTURE) public void unregisterScreenCaptureCallback(@NonNull android.app.Activity.ScreenCaptureCallback);
field public static final int DEFAULT_KEYS_DIALER = 1; // 0x1
field public static final int DEFAULT_KEYS_DISABLE = 0; // 0x0
field public static final int DEFAULT_KEYS_SEARCH_GLOBAL = 4; // 0x4
@@ -4417,6 +4419,10 @@
field public static final int RESULT_OK = -1; // 0xffffffff
}
+ public static interface Activity.ScreenCaptureCallback {
+ method public void onScreenCaptured();
+ }
+
@Deprecated public class ActivityGroup extends android.app.Activity {
ctor @Deprecated public ActivityGroup();
ctor @Deprecated public ActivityGroup(boolean);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 50cafe8..6ca4f0f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -16640,6 +16640,7 @@
public interface WindowManager extends android.view.ViewManager {
method @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS) public android.graphics.Region getCurrentImeTouchRegion();
+ method @NonNull public default java.util.List<android.content.ComponentName> notifyScreenshotListeners(int);
method public default void registerTaskFpsCallback(@IntRange(from=0) int, @NonNull java.util.concurrent.Executor, @NonNull android.window.TaskFpsCallback);
method public default void unregisterTaskFpsCallback(@NonNull android.window.TaskFpsCallback);
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index c0239e8..3c17a33 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -17,6 +17,7 @@
package android.app;
import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
+import static android.Manifest.permission.DETECT_SCREEN_CAPTURE;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
@@ -26,6 +27,7 @@
import static java.lang.Character.MIN_VALUE;
import android.annotation.CallSuper;
+import android.annotation.CallbackExecutor;
import android.annotation.DrawableRes;
import android.annotation.IdRes;
import android.annotation.IntDef;
@@ -1016,6 +1018,7 @@
private ComponentCallbacksController mCallbacksController;
@Nullable private IVoiceInteractionManagerService mVoiceInteractionManagerService;
+ private ScreenCaptureCallbackHandler mScreenCaptureCallbackHandler;
private final WindowControllerCallback mWindowControllerCallback =
new WindowControllerCallback() {
@@ -9222,4 +9225,43 @@
}
return mWindow.getOnBackInvokedDispatcher();
}
+
+ /**
+ * Interface for observing screen captures of an {@link Activity}.
+ */
+ public interface ScreenCaptureCallback {
+ /**
+ * Called when one of the monitored activities is captured.
+ * This is not invoked if the activity window
+ * has {@link WindowManager.LayoutParams#FLAG_SECURE} set.
+ */
+ void onScreenCaptured();
+ }
+
+ /**
+ * Registers a screen capture callback for this activity.
+ * The callback will be triggered when a screen capture of this activity is attempted.
+ * This callback will be executed on the thread of the passed {@code executor}.
+ * For details, see {@link ScreenCaptureCallback#onScreenCaptured}.
+ */
+ @RequiresPermission(DETECT_SCREEN_CAPTURE)
+ public void registerScreenCaptureCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull ScreenCaptureCallback callback) {
+ if (mScreenCaptureCallbackHandler == null) {
+ mScreenCaptureCallbackHandler = new ScreenCaptureCallbackHandler(mToken);
+ }
+ mScreenCaptureCallbackHandler.registerScreenCaptureCallback(executor, callback);
+ }
+
+
+ /**
+ * Unregisters a screen capture callback for this surface.
+ */
+ @RequiresPermission(DETECT_SCREEN_CAPTURE)
+ public void unregisterScreenCaptureCallback(@NonNull ScreenCaptureCallback callback) {
+ if (mScreenCaptureCallbackHandler != null) {
+ mScreenCaptureCallbackHandler.unregisterScreenCaptureCallback(callback);
+ }
+ }
}
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 461aa3c..e97e711 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -28,6 +28,7 @@
import android.app.IAssistDataReceiver;
import android.app.IInstrumentationWatcher;
import android.app.IProcessObserver;
+import android.app.IScreenCaptureObserver;
import android.app.IServiceConnection;
import android.app.IStopUserCallback;
import android.app.ITaskStackListener;
@@ -354,4 +355,23 @@
*/
android.window.BackNavigationInfo startBackNavigation(
in IWindowFocusObserver focusObserver, in BackAnimationAdapter adaptor);
+
+ /**
+ * registers a callback to be invoked when the screen is captured.
+ *
+ * @param observer callback to be registered.
+ * @param activityToken The token for the activity to set the callback to.
+ * @hide
+ */
+ void registerScreenCaptureObserver(IBinder activityToken, IScreenCaptureObserver observer);
+
+ /**
+ * unregisters the screen capture callback which was registered with
+ * {@link #registerScreenCaptureObserver(ScreenCaptureObserver)}.
+ *
+ * @param observer callback to be unregistered.
+ * @param activityToken The token for the activity to unset the callback from.
+ * @hide
+ */
+ void unregisterScreenCaptureObserver(IBinder activityToken, IScreenCaptureObserver observer);
}
diff --git a/core/java/android/app/IScreenCaptureObserver.aidl b/core/java/android/app/IScreenCaptureObserver.aidl
new file mode 100644
index 0000000..1668e5d10
--- /dev/null
+++ b/core/java/android/app/IScreenCaptureObserver.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 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.app;
+
+/** {@hide} */
+interface IScreenCaptureObserver {
+ oneway void onScreenCaptured();
+}
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 20869e0..6206f31 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -88,6 +88,7 @@
per-file *Task* = file:/services/core/java/com/android/server/wm/OWNERS
per-file Window* = file:/services/core/java/com/android/server/wm/OWNERS
per-file ConfigurationController.java = file:/services/core/java/com/android/server/wm/OWNERS
+per-file *ScreenCapture* = file:/services/core/java/com/android/server/wm/OWNERS
# TODO(b/174932174): determine the ownership of KeyguardManager.java
diff --git a/core/java/android/app/ScreenCaptureCallbackHandler.java b/core/java/android/app/ScreenCaptureCallbackHandler.java
new file mode 100644
index 0000000..997cf51
--- /dev/null
+++ b/core/java/android/app/ScreenCaptureCallbackHandler.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2022 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.app;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+
+import java.util.concurrent.Executor;
+
+/** Handles screen capture callbacks.
+ * @hide
+ **/
+public class ScreenCaptureCallbackHandler {
+
+ private final IBinder mActivityToken;
+ private final ScreenCaptureObserver mObserver;
+ private final ArrayMap<Activity.ScreenCaptureCallback, ScreenCaptureRegistration>
+ mScreenCaptureRegistrations = new ArrayMap<>();
+
+ public ScreenCaptureCallbackHandler(@NonNull IBinder activityToken) {
+ mActivityToken = activityToken;
+ mObserver = new ScreenCaptureObserver(mScreenCaptureRegistrations);
+ }
+
+ private static class ScreenCaptureRegistration {
+ Executor mExecutor;
+ Activity.ScreenCaptureCallback mCallback;
+
+ ScreenCaptureRegistration(Executor executor, Activity.ScreenCaptureCallback callback) {
+ this.mExecutor = executor;
+ this.mCallback = callback;
+ }
+ }
+
+ private static class ScreenCaptureObserver extends IScreenCaptureObserver.Stub {
+ ArrayMap<Activity.ScreenCaptureCallback, ScreenCaptureRegistration> mRegistrations;
+
+ ScreenCaptureObserver(
+ ArrayMap<Activity.ScreenCaptureCallback, ScreenCaptureRegistration>
+ registrations) {
+ this.mRegistrations = registrations;
+ }
+
+ @Override
+ public void onScreenCaptured() {
+ for (ScreenCaptureRegistration registration : mRegistrations.values()) {
+ registration.mExecutor.execute(
+ () -> {
+ registration.mCallback.onScreenCaptured();
+ });
+ }
+ }
+ }
+
+ /**
+ * Start monitoring for screen captures of the activity, the callback will be triggered whenever
+ * a screen capture is attempted. This callback will be executed on the thread of the passed
+ * {@code executor}. If the window is FLAG_SECURE, the callback will not be triggered.
+ */
+ public void registerScreenCaptureCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Activity.ScreenCaptureCallback callback) {
+ ScreenCaptureRegistration registration =
+ new ScreenCaptureRegistration(executor, callback);
+ synchronized (mScreenCaptureRegistrations) {
+ if (mScreenCaptureRegistrations.containsKey(callback)) {
+ throw new IllegalStateException(
+ "Capture observer already registered with the activity");
+ }
+ mScreenCaptureRegistrations.put(callback, registration);
+ // register with system server only once.
+ if (mScreenCaptureRegistrations.size() == 1) {
+ try {
+ ActivityTaskManager.getService()
+ .registerScreenCaptureObserver(mActivityToken, mObserver);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+ /** Stop monitoring for screen captures of the activity */
+ public void unregisterScreenCaptureCallback(@NonNull Activity.ScreenCaptureCallback callback) {
+ synchronized (mScreenCaptureRegistrations) {
+ if (!mScreenCaptureRegistrations.containsKey(callback)) {
+ throw new IllegalStateException(
+ "Capture observer not registered with the activity");
+ }
+ mScreenCaptureRegistrations.remove(callback);
+ // unregister only if no more registrations are left
+ if (mScreenCaptureRegistrations.size() == 0) {
+ try {
+ ActivityTaskManager.getService().unregisterScreenCaptureObserver(mActivityToken,
+ mObserver);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 9fdda29..4f3c5fe 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -22,6 +22,7 @@
import com.android.internal.policy.IShortcutService;
import android.app.IAssistDataReceiver;
+import android.content.ComponentName;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
@@ -1000,4 +1001,14 @@
* Mark a SurfaceSyncGroup stored in WindowManager as ready.
*/
oneway void markSurfaceSyncGroupReady(in IBinder syncGroupToken);
+
+ /**
+ * Invoked when a screenshot is taken of the default display to notify registered listeners.
+ *
+ * Should be invoked only by SysUI.
+ *
+ * @param displayId id of the display screenshot.
+ * @return List of ComponentNames corresponding to the activities that were notified.
+ */
+ List<ComponentName> notifyScreenshotListeners(int displayId);
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index e437c1f..5f6f55a 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -94,6 +94,7 @@
import android.app.Presentation;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
@@ -5269,4 +5270,18 @@
default Bitmap snapshotTaskForRecents(@IntRange(from = 0) int taskId) {
return null;
}
+
+ /**
+ * Invoked when a screenshot is taken of the default display to notify registered listeners.
+ *
+ * Should be invoked only by SysUI.
+ *
+ * @param displayId id of the display screenshot.
+ * @return List of ComponentNames corresponding to the activities that were notified.
+ * @hide
+ */
+ @SystemApi
+ default @NonNull List<ComponentName> notifyScreenshotListeners(int displayId) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 19d49d9..7ea8c0c 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -26,6 +26,7 @@
import android.annotation.Nullable;
import android.annotation.UiContext;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Region;
@@ -412,4 +413,15 @@
IBinder getDefaultToken() {
return mDefaultToken;
}
+
+ @Override
+ @NonNull
+ public List<ComponentName> notifyScreenshotListeners(int displayId) {
+ try {
+ return List.copyOf(WindowManagerGlobal.getWindowManagerService()
+ .notifyScreenshotListeners(displayId));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b4598bf..04c6ff9 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -802,6 +802,9 @@
<!-- Permissions required for CTS test - CtsBroadcastRadioTestCases -->
<uses-permission android:name="android.permission.ACCESS_BROADCAST_RADIO" />
+ <!-- Permission required for CTS test - ActivityCaptureCallbackTests -->
+ <uses-permission android:name="android.permission.DETECT_SCREEN_CAPTURE" />
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 94fb840..ca5fa5f 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -67,6 +67,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.ComponentName;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -91,6 +92,7 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.List;
/**
* This interface supplies all UI-specific behavior of the window manager. An
@@ -357,6 +359,11 @@
* windows even if the rotation hasn't changed.
*/
void updateRotation(boolean alwaysSendConfiguration, boolean forceRelayout);
+
+ /**
+ * Invoked when a screenshot is taken of the given display to notify registered listeners.
+ */
+ List<ComponentName> notifyScreenshotListeners(int displayId);
}
/**
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 90ac1aa..fd3f32d 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -249,6 +249,7 @@
import android.app.ActivityManager.TaskDescription;
import android.app.ActivityOptions;
import android.app.ICompatCameraControlCallback;
+import android.app.IScreenCaptureObserver;
import android.app.PendingIntent;
import android.app.PictureInPictureParams;
import android.app.ResultInfo;
@@ -301,6 +302,7 @@
import android.os.LocaleList;
import android.os.PersistableBundle;
import android.os.Process;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
@@ -886,6 +888,8 @@
private AppSaturationInfo mLastAppSaturationInfo;
+ private RemoteCallbackList<IScreenCaptureObserver> mCaptureCallbacks;
+
private final ColorDisplayService.ColorTransformController mColorTransformController =
(matrix, translation) -> mWmService.mH.post(() -> {
synchronized (mWmService.mGlobalLock) {
@@ -7020,6 +7024,41 @@
return mLocusId;
}
+ public void reportScreenCaptured() {
+ if (mCaptureCallbacks != null) {
+ final int n = mCaptureCallbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ IScreenCaptureObserver obs = mCaptureCallbacks.getBroadcastItem(i);
+ try {
+ obs.onScreenCaptured();
+ } catch (RemoteException e) {
+ }
+ }
+ mCaptureCallbacks.finishBroadcast();
+ }
+ }
+
+ public void registerCaptureObserver(IScreenCaptureObserver observer) {
+ synchronized (mWmService.mGlobalLock) {
+ if (mCaptureCallbacks == null) {
+ mCaptureCallbacks = new RemoteCallbackList<IScreenCaptureObserver>();
+ }
+ mCaptureCallbacks.register(observer);
+ }
+ }
+
+ public void unregisterCaptureObserver(IScreenCaptureObserver observer) {
+ synchronized (mWmService.mGlobalLock) {
+ if (mCaptureCallbacks != null) {
+ mCaptureCallbacks.unregister(observer);
+ }
+ }
+ }
+
+ boolean isRegisteredForScreenCaptureCallback() {
+ return mCaptureCallbacks != null && mCaptureCallbacks.getRegisteredCallbackCount() > 0;
+ }
+
void setVoiceSessionLocked(IVoiceInteractionSession session) {
voiceSession = session;
pendingVoiceInteractionStart = false;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a927ed3..2f82167 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.BIND_VOICE_INTERACTION;
import static android.Manifest.permission.CHANGE_CONFIGURATION;
import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
+import static android.Manifest.permission.DETECT_SCREEN_CAPTURE;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
@@ -145,6 +146,7 @@
import android.app.IApplicationThread;
import android.app.IAssistDataReceiver;
import android.app.INotificationManager;
+import android.app.IScreenCaptureObserver;
import android.app.ITaskStackListener;
import android.app.Notification;
import android.app.NotificationManager;
@@ -5437,6 +5439,32 @@
}
}
+ @Override
+ public void registerScreenCaptureObserver(IBinder activityToken,
+ IScreenCaptureObserver observer) {
+ mAmInternal.enforceCallingPermission(DETECT_SCREEN_CAPTURE,
+ "registerScreenCaptureObserver");
+ synchronized (mGlobalLock) {
+ ActivityRecord activityRecord = ActivityRecord.forTokenLocked(activityToken);
+ if (activityRecord != null) {
+ activityRecord.registerCaptureObserver(observer);
+ }
+ }
+ }
+
+ @Override
+ public void unregisterScreenCaptureObserver(IBinder activityToken,
+ IScreenCaptureObserver observer) {
+ mAmInternal.enforceCallingPermission(DETECT_SCREEN_CAPTURE,
+ "unregisterScreenCaptureObserver");
+ synchronized (mGlobalLock) {
+ ActivityRecord activityRecord = ActivityRecord.forTokenLocked(activityToken);
+ if (activityRecord != null) {
+ activityRecord.unregisterCaptureObserver(observer);
+ }
+ }
+ }
+
/**
* Returns {@code true} if the process represented by the pid passed as argument is
* instrumented and the instrumentation source was granted with the permission also
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index bded45e..cb1b3e4 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -26,6 +26,7 @@
import static android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS;
import static android.Manifest.permission.RESTRICTED_VR_ACCESS;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
+import static android.Manifest.permission.STATUS_BAR_SERVICE;
import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
@@ -168,6 +169,7 @@
import android.app.IAssistDataReceiver;
import android.app.WindowConfiguration;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -9447,4 +9449,55 @@
public void markSurfaceSyncGroupReady(IBinder syncGroupToken) {
mSurfaceSyncGroupController.markSyncGroupReady(syncGroupToken);
}
+
+ private ArraySet<ActivityRecord> getVisibleActivityRecords(int displayId) {
+ ArraySet<ActivityRecord> result = new ArraySet<>();
+ synchronized (mGlobalLock) {
+ ArraySet<ComponentName> addedActivities = new ArraySet<>();
+ DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+ if (displayContent != null) {
+ displayContent.forAllWindows(
+ (w) -> {
+ if (w.isVisible()
+ && w.isDisplayed()
+ && w.mActivityRecord != null
+ && !addedActivities.contains(
+ w.mActivityRecord.mActivityComponent)
+ && w.mActivityRecord.isVisible()
+ && w.isVisibleNow()) {
+ addedActivities.add(w.mActivityRecord.mActivityComponent);
+ result.add(w.mActivityRecord);
+ }
+ },
+ true /* traverseTopToBottom */);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Must be called when a screenshot is taken via hardware chord.
+ *
+ * Notifies all registered visible activities that have registered for screencapture callback,
+ * Returns a list of visible apps component names.
+ */
+ @Override
+ public List<ComponentName> notifyScreenshotListeners(int displayId) {
+ // make sure caller is SysUI.
+ if (!checkCallingPermission(STATUS_BAR_SERVICE,
+ "notifyScreenshotListeners()")) {
+ throw new SecurityException("Requires STATUS_BAR_SERVICE permission");
+ }
+ synchronized (mGlobalLock) {
+ ArraySet<ComponentName> notifiedApps = new ArraySet<>();
+ ArraySet<ActivityRecord> visibleApps = getVisibleActivityRecords(displayId);
+ for (ActivityRecord ar : visibleApps) {
+ if (ar.isRegisteredForScreenCaptureCallback()) {
+ ar.reportScreenCaptured();
+ notifiedApps.add(ar.mActivityComponent);
+ }
+ }
+ return List.copyOf(notifiedApps);
+ }
+ }
}