diff options
19 files changed, 251 insertions, 50 deletions
diff --git a/api/current.txt b/api/current.txt index e166c14b6cb6..bc0099af0eb6 100644 --- a/api/current.txt +++ b/api/current.txt @@ -386,6 +386,7 @@ package android { field public static final int canRequestFingerprintGestures = 16844109; // 0x101054d field public static final int canRequestTouchExplorationMode = 16843735; // 0x10103d7 field public static final int canRetrieveWindowContent = 16843653; // 0x1010385 + field public static final int canTakeScreenshot = 16844304; // 0x1010610 field public static final int candidatesTextStyleSpans = 16843312; // 0x1010230 field public static final int cantSaveState = 16844142; // 0x101056e field @Deprecated public static final int capitalize = 16843113; // 0x1010169 @@ -2864,6 +2865,7 @@ package android.accessibilityservice { method protected void onServiceConnected(); method public final boolean performGlobalAction(int); method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo); + method public boolean takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.graphics.Bitmap>); field public static final int GESTURE_SWIPE_DOWN = 2; // 0x2 field public static final int GESTURE_SWIPE_DOWN_AND_LEFT = 15; // 0xf field public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 16; // 0x10 @@ -2960,6 +2962,7 @@ package android.accessibilityservice { field public static final int CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES = 64; // 0x40 field public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2; // 0x2 field public static final int CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT = 1; // 0x1 + field public static final int CAPABILITY_CAN_TAKE_SCREENSHOT = 128; // 0x80 field @NonNull public static final android.os.Parcelable.Creator<android.accessibilityservice.AccessibilityServiceInfo> CREATOR; field public static final int DEFAULT = 1; // 0x1 field public static final int FEEDBACK_ALL_MASK = -1; // 0xffffffff diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index e46840c0f467..0bd8ce692884 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -17,6 +17,7 @@ package android.accessibilityservice; import android.accessibilityservice.GestureDescription.MotionEventGenerator; +import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -26,12 +27,15 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.Intent; import android.content.pm.ParceledListSlice; +import android.graphics.Bitmap; import android.graphics.Region; +import android.os.Binder; import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.RemoteCallback; import android.os.RemoteException; import android.util.ArrayMap; import android.util.Log; @@ -48,10 +52,13 @@ import android.view.accessibility.AccessibilityWindowInfo; import com.android.internal.os.HandlerCaller; import com.android.internal.os.SomeArgs; +import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; +import java.util.concurrent.Executor; +import java.util.function.Consumer; /** * Accessibility services should only be used to assist users with disabilities in using @@ -483,6 +490,9 @@ public abstract class AccessibilityService extends Service { private FingerprintGestureController mFingerprintGestureController; + /** @hide */ + public static final String KEY_ACCESSIBILITY_SCREENSHOT = "screenshot"; + /** * Callback for {@link android.view.accessibility.AccessibilityEvent}s. * @@ -1761,6 +1771,51 @@ public abstract class AccessibilityService extends Service { } /** + * Takes a screenshot of the specified display and returns it by {@link Bitmap.Config#HARDWARE} + * format. + * <p> + * <strong>Note:</strong> In order to take screenshot your service has + * to declare the capability to take screenshot by setting the + * {@link android.R.styleable#AccessibilityService_canTakeScreenshot} + * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}. + * Besides, This API is only supported for default display now + * {@link Display#DEFAULT_DISPLAY}. + * </p> + * + * @param displayId The logic display id, must be {@link Display#DEFAULT_DISPLAY} for + * default display. + * @param executor Executor on which to run the callback. + * @param callback The callback invoked when the taking screenshot is done. + * + * @return {@code true} if the taking screenshot accepted, {@code false} if not. + */ + public boolean takeScreenshot(int displayId, @NonNull @CallbackExecutor Executor executor, + @NonNull Consumer<Bitmap> callback) { + Preconditions.checkNotNull(executor, "executor cannot be null"); + Preconditions.checkNotNull(callback, "callback cannot be null"); + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().getConnection( + mConnectionId); + if (connection == null) { + return false; + } + try { + connection.takeScreenshotWithCallback(displayId, new RemoteCallback((result) -> { + final Bitmap screenshot = result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT); + final long identity = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.accept(screenshot)); + } finally { + Binder.restoreCallingIdentity(identity); + } + })); + } catch (RemoteException re) { + throw new RuntimeException(re); + } + return true; + } + + /** * Implement to return the implementation of the internal accessibility * service interface. */ diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index 5e2c1faac156..12f2c3b17c96 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -86,6 +86,7 @@ import java.util.List; * @attr ref android.R.styleable#AccessibilityService_settingsActivity * @attr ref android.R.styleable#AccessibilityService_nonInteractiveUiTimeout * @attr ref android.R.styleable#AccessibilityService_interactiveUiTimeout + * @attr ref android.R.styleable#AccessibilityService_canTakeScreenshot * @see AccessibilityService * @see android.view.accessibility.AccessibilityEvent * @see android.view.accessibility.AccessibilityManager @@ -136,6 +137,12 @@ public class AccessibilityServiceInfo implements Parcelable { */ public static final int CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES = 0x00000040; + /** + * Capability: This accessibility service can take screenshot. + * @see android.R.styleable#AccessibilityService_canTakeScreenshot + */ + public static final int CAPABILITY_CAN_TAKE_SCREENSHOT = 0x00000080; + private static SparseArray<CapabilityInfo> sAvailableCapabilityInfos; /** @@ -625,6 +632,10 @@ public class AccessibilityServiceInfo implements Parcelable { .AccessibilityService_canRequestFingerprintGestures, false)) { mCapabilities |= CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES; } + if (asAttributes.getBoolean(com.android.internal.R.styleable + .AccessibilityService_canTakeScreenshot, false)) { + mCapabilities |= CAPABILITY_CAN_TAKE_SCREENSHOT; + } TypedValue peekedValue = asAttributes.peekValue( com.android.internal.R.styleable.AccessibilityService_description); if (peekedValue != null) { @@ -794,6 +805,7 @@ public class AccessibilityServiceInfo implements Parcelable { * @see #CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS * @see #CAPABILITY_CAN_CONTROL_MAGNIFICATION * @see #CAPABILITY_CAN_PERFORM_GESTURES + * @see #CAPABILITY_CAN_TAKE_SCREENSHOT */ public int getCapabilities() { return mCapabilities; @@ -810,6 +822,7 @@ public class AccessibilityServiceInfo implements Parcelable { * @see #CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS * @see #CAPABILITY_CAN_CONTROL_MAGNIFICATION * @see #CAPABILITY_CAN_PERFORM_GESTURES + * @see #CAPABILITY_CAN_TAKE_SCREENSHOT * * @hide */ @@ -1253,6 +1266,8 @@ public class AccessibilityServiceInfo implements Parcelable { return "CAPABILITY_CAN_PERFORM_GESTURES"; case CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES: return "CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES"; + case CAPABILITY_CAN_TAKE_SCREENSHOT: + return "CAPABILITY_CAN_TAKE_SCREENSHOT"; default: return "UNKNOWN"; } @@ -1314,6 +1329,10 @@ public class AccessibilityServiceInfo implements Parcelable { new CapabilityInfo(CAPABILITY_CAN_PERFORM_GESTURES, R.string.capability_title_canPerformGestures, R.string.capability_desc_canPerformGestures)); + sAvailableCapabilityInfos.put(CAPABILITY_CAN_TAKE_SCREENSHOT, + new CapabilityInfo(CAPABILITY_CAN_TAKE_SCREENSHOT, + R.string.capability_title_canTakeScreenshot, + R.string.capability_desc_canTakeScreenshot)); if ((context == null) || fingerprintAvailable(context)) { sAvailableCapabilityInfos.put(CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES, new CapabilityInfo(CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES, diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 932d0a4d65bd..4ea5c62bf05b 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -18,8 +18,10 @@ package android.accessibilityservice; import android.accessibilityservice.AccessibilityServiceInfo; import android.content.pm.ParceledListSlice; +import android.graphics.Bitmap; import android.graphics.Region; import android.os.Bundle; +import android.os.RemoteCallback; import android.view.MagnificationSpec; import android.view.MotionEvent; import android.view.accessibility.AccessibilityNodeInfo; @@ -104,4 +106,8 @@ interface IAccessibilityServiceConnection { IBinder getOverlayWindowToken(int displayid); int getWindowIdForLeashToken(IBinder token); + + Bitmap takeScreenshot(int displayId); + + void takeScreenshotWithCallback(int displayId, in RemoteCallback callback); } diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl index 8c3180b400ef..80ba464851e0 100644 --- a/core/java/android/app/IUiAutomationConnection.aidl +++ b/core/java/android/app/IUiAutomationConnection.aidl @@ -39,7 +39,6 @@ interface IUiAutomationConnection { boolean injectInputEvent(in InputEvent event, boolean sync); void syncInputTransactions(); boolean setRotation(int rotation); - Bitmap takeScreenshot(in Rect crop, int rotation); boolean clearWindowContentFrameStats(int windowId); WindowContentFrameStats getWindowContentFrameStats(int windowId); void clearWindowAnimationFrameStats(); diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index 18a3e6e89552..2579bd1abbfd 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -27,10 +27,7 @@ import android.annotation.Nullable; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Bitmap; -import android.graphics.Point; -import android.graphics.Rect; import android.graphics.Region; -import android.hardware.display.DisplayManagerGlobal; import android.os.Build; import android.os.Handler; import android.os.HandlerThread; @@ -831,39 +828,20 @@ public final class UiAutomation { } /** - * Takes a screenshot. + * Takes a screenshot of the default display and returns it by {@link Bitmap.Config#HARDWARE} + * format. * * @return The screenshot bitmap on success, null otherwise. */ public Bitmap takeScreenshot() { + final int connectionId; synchronized (mLock) { throwIfNotConnectedLocked(); + connectionId = mConnectionId; } - Display display = DisplayManagerGlobal.getInstance() - .getRealDisplay(Display.DEFAULT_DISPLAY); - Point displaySize = new Point(); - display.getRealSize(displaySize); - - int rotation = display.getRotation(); - - // Take the screenshot - Bitmap screenShot = null; - try { - // Calling out without a lock held. - screenShot = mUiAutomationConnection.takeScreenshot( - new Rect(0, 0, displaySize.x, displaySize.y), rotation); - if (screenShot == null) { - return null; - } - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error while taking screnshot!", re); - return null; - } - - // Optimization - screenShot.setHasAlpha(false); - - return screenShot; + // Calling out without a lock held. + return AccessibilityInteractionClient.getInstance() + .takeScreenshot(connectionId, Display.DEFAULT_DISPLAY); } /** diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java index 82e988109db8..4fb974305e48 100644 --- a/core/java/android/app/UiAutomationConnection.java +++ b/core/java/android/app/UiAutomationConnection.java @@ -21,8 +21,6 @@ import android.accessibilityservice.IAccessibilityServiceClient; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Rect; import android.hardware.input.InputManager; import android.os.Binder; import android.os.IBinder; @@ -180,23 +178,6 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { } @Override - public Bitmap takeScreenshot(Rect crop, int rotation) { - synchronized (mLock) { - throwIfCalledByNotTrustedUidLocked(); - throwIfShutdownLocked(); - throwIfNotConnectedLocked(); - } - final long identity = Binder.clearCallingIdentity(); - try { - int width = crop.width(); - int height = crop.height(); - return SurfaceControl.screenshot(crop, width, height, rotation); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - @Override public boolean clearWindowContentFrameStats(int windowId) throws RemoteException { synchronized (mLock) { throwIfCalledByNotTrustedUidLocked(); @@ -449,7 +430,8 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY - | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS); + | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS + | AccessibilityServiceInfo.CAPABILITY_CAN_TAKE_SCREENSHOT); try { // Calling out with a lock held is fine since if the system // process is gone the client calling in will be killed. diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index b4c87953567b..b9f08ada3152 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -20,6 +20,7 @@ import android.accessibilityservice.IAccessibilityServiceConnection; import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; +import android.graphics.Bitmap; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -828,6 +829,31 @@ public final class AccessibilityInteractionClient } /** + * Takes the screenshot of the specified display and returns it by bitmap format. + * + * @param connectionId The id of a connection for interacting with the system. + * @param displayId The logic display id, use {@link Display#DEFAULT_DISPLAY} for + * default display. + * @return The screenshot bitmap on success, null otherwise. + */ + public Bitmap takeScreenshot(int connectionId, int displayId) { + Bitmap screenShot = null; + try { + IAccessibilityServiceConnection connection = getConnection(connectionId); + if (connection != null) { + screenShot = connection.takeScreenshot(displayId); + } else { + if (DEBUG) { + Log.w(LOG_TAG, "No connection for connection id: " + connectionId); + } + } + } catch (RemoteException re) { + Log.w(LOG_TAG, "Error while calling remote takeScreenshot", re); + } + return screenShot; + } + + /** * Clears the result state. */ private void clearResultLocked() { diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 9c08728b559e..44754157c5b5 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3752,6 +3752,8 @@ </p> --> <attr name="canRequestFingerprintGestures" format="boolean" /> + <!-- Attribute whether the accessibility service wants to be able to take screenshot. --> + <attr name="canTakeScreenshot" format="boolean" /> <!-- Animated image of the accessibility service purpose or behavior, to help users understand how the service can help them.--> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 6cf6a6828237..98608300e17f 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3007,6 +3007,7 @@ <public name="featureId" /> <public name="supportsInlineSuggestions" /> <public name="crossProfile" /> + <public name="canTakeScreenshot"/> </public-group> <public-group type="drawable" first-id="0x010800b5"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index a6ebc33ebd9e..f977ea857fc8 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -794,6 +794,11 @@ <string name="capability_desc_canCaptureFingerprintGestures">Can capture gestures performed on the device\'s fingerprint sensor.</string> + <!-- Title for the capability of an accessibility service to take screenshot. [CHAR LIMIT=32] --> + <string name="capability_title_canTakeScreenshot">Take screenshot</string> + <!-- Description for the capability of an accessibility service to take screenshot. [CHAR LIMIT=NONE] --> + <string name="capability_desc_canTakeScreenshot">Can take a screenshot of the display.</string> + <!-- Permissions --> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a268e20ccdf6..30dbfc711932 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3811,4 +3811,9 @@ <java-symbol type="dimen" name="waterfall_display_top_edge_size" /> <java-symbol type="dimen" name="waterfall_display_right_edge_size" /> <java-symbol type="dimen" name="waterfall_display_bottom_edge_size" /> + + <!-- Accessibility take screenshot --> + <java-symbol type="string" name="capability_desc_canTakeScreenshot" /> + <java-symbol type="string" name="capability_title_canTakeScreenshot" /> + </resources> diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java index 836cb95992ff..f151b810eea3 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java @@ -19,9 +19,11 @@ package android.view.accessibility; import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceConnection; import android.content.pm.ParceledListSlice; +import android.graphics.Bitmap; import android.graphics.Region; import android.os.Bundle; import android.os.IBinder; +import android.os.RemoteCallback; /** * Stub implementation of IAccessibilityServiceConnection so each test doesn't need to implement @@ -147,4 +149,10 @@ public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon public int getWindowIdForLeashToken(IBinder token) { return -1; } + + public Bitmap takeScreenshot(int displayId) { + return null; + } + + public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {} } diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 016028604902..c3965c44d4c0 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -36,8 +36,12 @@ import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; +import android.graphics.Bitmap; +import android.graphics.Point; +import android.graphics.Rect; import android.graphics.Region; import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManagerGlobal; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -54,6 +58,7 @@ import android.util.SparseArray; import android.view.Display; import android.view.KeyEvent; import android.view.MagnificationSpec; +import android.view.SurfaceControl; import android.view.View; import android.view.WindowInfo; import android.view.accessibility.AccessibilityCache; @@ -90,6 +95,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ private static final String LOG_TAG = "AbstractAccessibilityServiceConnection"; private static final int WAIT_WINDOWS_TIMEOUT_MILLIS = 5000; + protected static final String TAKE_SCREENSHOT = "takeScreenshot"; protected final Context mContext; protected final SystemSupport mSystemSupport; protected final WindowManagerInternal mWindowManagerService; @@ -934,6 +940,54 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ mInvocationHandler.setSoftKeyboardCallbackEnabled(enabled); } + @Nullable + @Override + public Bitmap takeScreenshot(int displayId) { + synchronized (mLock) { + if (!hasRightsToCurrentUserLocked()) { + return null; + } + + if (!mSecurityPolicy.canTakeScreenshotLocked(this)) { + return null; + } + } + + if (!mSecurityPolicy.checkAccessibilityAccess(this)) { + return null; + } + + final Display display = DisplayManagerGlobal.getInstance() + .getRealDisplay(displayId); + if (display == null) { + return null; + } + final Point displaySize = new Point(); + display.getRealSize(displaySize); + + final int rotation = display.getRotation(); + Bitmap screenShot = null; + + final long identity = Binder.clearCallingIdentity(); + try { + final Rect crop = new Rect(0, 0, displaySize.x, displaySize.y); + // TODO (b/145893483): calling new API with the display as a parameter + // when surface control supported. + screenShot = SurfaceControl.screenshot(crop, displaySize.x, displaySize.y, + rotation); + if (screenShot != null) { + // Optimization for telling the bitmap that all of the pixels are known to be + // opaque (false). This is meant as a drawing hint, as in some cases a bitmap + // that is known to be opaque can take a faster drawing case than one that may + // have non-opaque per-pixel alpha values. + screenShot.setHasAlpha(false); + } + } finally { + Binder.restoreCallingIdentity(identity); + } + return screenShot; + } + @Override public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return; diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java index 0151a52c8a6b..7dbec7c8c0c5 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java @@ -326,6 +326,19 @@ public class AccessibilitySecurityPolicy { } /** + * Checks if a service can take screenshot. + * + * @param service The service requesting access + * + * @return Whether ot not the service may take screenshot + */ + public boolean canTakeScreenshotLocked( + @NonNull AbstractAccessibilityServiceConnection service) { + return (service.getCapabilities() + & AccessibilityServiceInfo.CAPABILITY_CAN_TAKE_SCREENSHOT) != 0; + } + + /** * Returns the parent userId of the profile according to the specified userId. * * @param userId The userId to check diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index edb4445151d5..25911a7ed0ea 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -16,6 +16,8 @@ package com.android.server.accessibility; +import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT; + import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import android.Manifest; @@ -25,16 +27,20 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ParceledListSlice; +import android.graphics.Bitmap; import android.os.Binder; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Process; +import android.os.RemoteCallback; import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; import android.util.Slog; import android.view.Display; +import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; @@ -387,4 +393,15 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } } } + + @Override + public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) { + mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> { + final Bitmap screenshot = super.takeScreenshot(displayId); + // Send back the result. + final Bundle payload = new Bundle(); + payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT, screenshot); + callback.sendResult(payload); + }, null).recycleOnUse()); + } } diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java index 7a8a112fae40..5d9af26a8339 100644 --- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java @@ -25,6 +25,7 @@ import android.content.Context; import android.os.Handler; import android.os.IBinder; import android.os.IBinder.DeathRecipient; +import android.os.RemoteCallback; import android.os.RemoteException; import android.util.Slog; import android.view.Display; @@ -325,5 +326,8 @@ class UiAutomationManager { @Override public void onFingerprintGesture(int gesture) {} + + @Override + public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {} } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java index c223f135d3df..e1e9b7e7b7cb 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java @@ -75,6 +75,7 @@ import android.os.IBinder; import android.os.IPowerManager; import android.os.PowerManager; import android.os.Process; +import android.os.RemoteCallback; import android.os.RemoteException; import android.testing.DexmakerShareClassLoaderRule; import android.view.Display; @@ -693,6 +694,18 @@ public class AbstractAccessibilityServiceConnectionTest { assertThat(result, is(false)); } + @Test + public void takeScreenshot_returnNull() { + // no canTakeScreenshot, should return null. + when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(false); + assertThat(mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY), is(nullValue())); + + // no checkAccessibilityAccess, should return null. + when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(true); + when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(false); + assertThat(mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY), is(nullValue())); + } + private void updateServiceInfo(AccessibilityServiceInfo serviceInfo, int eventType, int feedbackType, int flags, String[] packageNames, int notificationTimeout) { serviceInfo.eventTypes = eventType; @@ -832,5 +845,8 @@ public class AbstractAccessibilityServiceConnectionTest { @Override public void onFingerprintGesture(int gesture) {} + + @Override + public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {} } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java index 04ac7fe91008..150409766f47 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java @@ -346,6 +346,14 @@ public class AccessibilitySecurityPolicyTest { } @Test + public void canTakeScreenshot_hasCapability_returnTrue() { + when(mMockA11yServiceConnection.getCapabilities()) + .thenReturn(AccessibilityServiceInfo.CAPABILITY_CAN_TAKE_SCREENSHOT); + + assertTrue(mA11ySecurityPolicy.canTakeScreenshotLocked(mMockA11yServiceConnection)); + } + + @Test public void resolveProfileParent_userIdIsCurrentUser_returnCurrentUser() { final int currentUserId = 10; final int userId = currentUserId; |