diff --git a/core/api/current.txt b/core/api/current.txt index 48fa5dc8031fb..f86544784dc4d 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -51550,6 +51550,7 @@ package android.view.displayhash { field public static final int DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM = -5; // 0xfffffffb field public static final int DISPLAY_HASH_ERROR_MISSING_WINDOW = -3; // 0xfffffffd field public static final int DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN = -4; // 0xfffffffc + field public static final int DISPLAY_HASH_ERROR_TOO_MANY_REQUESTS = -6; // 0xfffffffa field public static final int DISPLAY_HASH_ERROR_UNKNOWN = -1; // 0xffffffff } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index a0efbb8065854..d0f90949dfd32 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -321,6 +321,7 @@ package android { field public static final int requiredSystemPropertyValue = 16844134; // 0x1010566 field public static final int sdkVersion = 16844304; // 0x1010610 field public static final int supportsAmbientMode = 16844173; // 0x101058d + field public static final int throttleDurationMillis = 16844360; // 0x1010648 field public static final int userRestriction = 16844164; // 0x1010584 } @@ -9794,6 +9795,7 @@ package android.service.displayhash { method @NonNull public abstract java.util.Map onGetDisplayHashAlgorithms(); method @Nullable public abstract android.view.displayhash.VerifiedDisplayHash onVerifyDisplayHash(@NonNull byte[], @NonNull android.view.displayhash.DisplayHash); field public static final String SERVICE_INTERFACE = "android.service.displayhash.DisplayHasherService"; + field public static final String SERVICE_META_DATA = "android.displayhash.display_hasher_service"; } } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 04541be6ffbc5..56b0a9d5a4d04 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2877,6 +2877,14 @@ package android.view.contentcapture { } +package android.view.displayhash { + + public final class DisplayHashManager { + method @RequiresPermission("android.permission.READ_FRAME_BUFFER") public void setDisplayHashThrottlingEnabled(boolean); + } + +} + package android.view.inputmethod { public final class InlineSuggestion implements android.os.Parcelable { diff --git a/core/java/android/service/displayhash/DisplayHasherService.java b/core/java/android/service/displayhash/DisplayHasherService.java index 2105d848057e6..d300cf10e47a1 100644 --- a/core/java/android/service/displayhash/DisplayHasherService.java +++ b/core/java/android/service/displayhash/DisplayHasherService.java @@ -51,6 +51,16 @@ public abstract class DisplayHasherService extends Service { public static final String EXTRA_VERIFIED_DISPLAY_HASH = "android.service.displayhash.extra.VERIFIED_DISPLAY_HASH"; + /** + * Name under which a DisplayHasherService component publishes information + * about itself. This meta-data must reference an XML resource containing a + * {@link com.android.internal.R.styleable#DisplayHasherService} tag. + * + * @hide + */ + @SystemApi + public static final String SERVICE_META_DATA = "android.displayhash.display_hasher_service"; + /** * The {@link Intent} action that must be declared as handled by a service in its manifest * for the system to recognize it as a DisplayHash providing service. diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index e1f13f2a93c7f..d0a3e4b86e2d5 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -776,6 +776,15 @@ interface IWindowManager */ VerifiedDisplayHash verifyDisplayHash(in DisplayHash displayHash); + /** + * Call to enable or disable the throttling when generating a display hash. This should only be + * used for testing. Throttling is enabled by default. + * + * Must be called from a process that has {@link android.Manifest.permission#READ_FRAME_BUFFER} + * permission. + */ + void setDisplayHashThrottlingEnabled(boolean enable); + /** * Registers a listener for a {@link android.window.WindowContext} to handle configuration * changes from the server side. diff --git a/core/java/android/view/displayhash/DisplayHashManager.java b/core/java/android/view/displayhash/DisplayHashManager.java index 6b0c1a6d76337..69dfc3869b7d3 100644 --- a/core/java/android/view/displayhash/DisplayHashManager.java +++ b/core/java/android/view/displayhash/DisplayHashManager.java @@ -21,7 +21,9 @@ import static android.content.Context.DISPLAY_HASH_SERVICE; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.SystemService; +import android.annotation.TestApi; import android.os.RemoteException; import android.util.ArraySet; import android.util.Log; @@ -94,4 +96,19 @@ public final class DisplayHashManager { return null; } } + + /** + * Call to enable or disable the throttling when generating a display hash. This should only be + * used for testing. Throttling is enabled by default. + * + * @hide + */ + @TestApi + @RequiresPermission(android.Manifest.permission.READ_FRAME_BUFFER) + public void setDisplayHashThrottlingEnabled(boolean enable) { + try { + WindowManagerGlobal.getWindowManagerService().setDisplayHashThrottlingEnabled(enable); + } catch (RemoteException e) { + } + } } diff --git a/core/java/android/view/displayhash/DisplayHashResultCallback.java b/core/java/android/view/displayhash/DisplayHashResultCallback.java index 04d29ee3d48a9..6e3d9a8786af0 100644 --- a/core/java/android/view/displayhash/DisplayHashResultCallback.java +++ b/core/java/android/view/displayhash/DisplayHashResultCallback.java @@ -72,13 +72,20 @@ public interface DisplayHashResultCallback { */ int DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM = -5; + /** + * The caller requested to generate the hash too frequently. The caller should try again at a + * after some time has passed to ensure the system isn't overloaded. + */ + int DISPLAY_HASH_ERROR_TOO_MANY_REQUESTS = -6; + /** @hide */ @IntDef(prefix = {"DISPLAY_HASH_ERROR_"}, value = { DISPLAY_HASH_ERROR_UNKNOWN, DISPLAY_HASH_ERROR_INVALID_BOUNDS, DISPLAY_HASH_ERROR_MISSING_WINDOW, DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN, - DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM + DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM, + DISPLAY_HASH_ERROR_TOO_MANY_REQUESTS }) @Retention(RetentionPolicy.SOURCE) @interface DisplayHashErrorCode { diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 74f8ff6551658..eea379944a217 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -9539,4 +9539,10 @@ + + + + + diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 49039f9370ddc..aea9d6031818f 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3096,6 +3096,8 @@ + + diff --git a/services/core/java/com/android/server/wm/DisplayHashController.java b/services/core/java/com/android/server/wm/DisplayHashController.java index 1262dee68b74f..5a8af457c8edd 100644 --- a/services/core/java/com/android/server/wm/DisplayHashController.java +++ b/services/core/java/com/android/server/wm/DisplayHashController.java @@ -17,7 +17,9 @@ package com.android.server.wm; import static android.service.displayhash.DisplayHasherService.EXTRA_VERIFIED_DISPLAY_HASH; +import static android.service.displayhash.DisplayHasherService.SERVICE_META_DATA; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM; +import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_TOO_MANY_REQUESTS; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_UNKNOWN; import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH_ERROR_CODE; @@ -34,6 +36,9 @@ import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; @@ -49,15 +54,22 @@ import android.os.UserHandle; import android.service.displayhash.DisplayHashParams; import android.service.displayhash.DisplayHasherService; import android.service.displayhash.IDisplayHasherService; +import android.util.AttributeSet; import android.util.Size; import android.util.Slog; +import android.util.Xml; import android.view.MagnificationSpec; import android.view.SurfaceControl; import android.view.displayhash.DisplayHash; import android.view.displayhash.VerifiedDisplayHash; +import com.android.internal.R; import com.android.internal.annotations.GuardedBy; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -102,6 +114,41 @@ public class DisplayHashController { private final Matrix mTmpMatrix = new Matrix(); private final RectF mTmpRectF = new RectF(); + /** + * Lock used when retrieving xml metadata. Lock when retrieving the xml data the first time + * since it will be cached after that. Check if {@link #mParsedXml} is set to determine if the + * metadata needs to retrieved. + */ + private final Object mParseXmlLock = new Object(); + + /** + * Flag whether the xml metadata has been retrieved and parsed. Once this is set to true, + * there's no need to request metadata again. + */ + @GuardedBy("mParseXmlLock") + private boolean mParsedXml; + + /** + * Specified throttle time in milliseconds. Don't allow an app to generate a display hash more + * than once per throttleTime + */ + private int mThrottleDurationMillis = 0; + + /** + * The last time an app requested to generate a display hash in System time. + */ + private long mLastRequestTimeMs; + + /** + * The last uid that requested to generate a hash. + */ + private int mLastRequestUid; + + /** + * Only used for testing. Throttling should always be enabled unless running tests + */ + private boolean mDisplayHashThrottlingEnabled = true; + private interface Command { void run(IDisplayHasherService service) throws RemoteException; } @@ -131,6 +178,10 @@ public class DisplayHashController { return results.getParcelable(EXTRA_VERIFIED_DISPLAY_HASH); } + void setDisplayHashThrottlingEnabled(boolean enable) { + mDisplayHashThrottlingEnabled = enable; + } + private void generateDisplayHash(HardwareBuffer buffer, Rect bounds, String hashAlgorithm, RemoteCallback callback) { connectAndRun( @@ -138,8 +189,36 @@ public class DisplayHashController { callback)); } + private boolean allowedToGenerateHash(int uid) { + if (!mDisplayHashThrottlingEnabled) { + // Always allow to generate the hash. This is used to allow tests to run without + // waiting on the designated threshold. + return true; + } + + long currentTime = System.currentTimeMillis(); + if (mLastRequestUid != uid) { + mLastRequestUid = uid; + mLastRequestTimeMs = currentTime; + return true; + } + + int throttleDurationMs = getThrottleDurationMillis(); + if (currentTime - mLastRequestTimeMs < throttleDurationMs) { + return false; + } + + mLastRequestTimeMs = currentTime; + return true; + } + void generateDisplayHash(SurfaceControl.LayerCaptureArgs.Builder args, - Rect boundsInWindow, String hashAlgorithm, RemoteCallback callback) { + Rect boundsInWindow, String hashAlgorithm, int uid, RemoteCallback callback) { + if (!allowedToGenerateHash(uid)) { + sendDisplayHashError(callback, DISPLAY_HASH_ERROR_TOO_MANY_REQUESTS); + return; + } + final Map displayHashAlgorithmsMap = getDisplayHashAlgorithms(); DisplayHashParams displayHashParams = displayHashAlgorithmsMap.get(hashAlgorithm); if (displayHashParams == null) { @@ -277,6 +356,64 @@ public class DisplayHashController { } } + private int getThrottleDurationMillis() { + if (!parseXmlProperties()) { + return 0; + } + return mThrottleDurationMillis; + } + + private boolean parseXmlProperties() { + // We have a separate lock for the xml parsing since it doesn't need to make the + // request through the service connection. Instead, we have a lock to ensure we can + // properly cache the xml metadata so we don't need to call into the ExtServices + // process for each request. + synchronized (mParseXmlLock) { + if (mParsedXml) { + return true; + } + + final ServiceInfo serviceInfo = getServiceInfo(); + if (serviceInfo == null) return false; + + final PackageManager pm = mContext.getPackageManager(); + + XmlResourceParser parser; + parser = serviceInfo.loadXmlMetaData(pm, SERVICE_META_DATA); + if (parser == null) { + return false; + } + + Resources res; + try { + res = pm.getResourcesForApplication(serviceInfo.applicationInfo); + } catch (PackageManager.NameNotFoundException e) { + return false; + } + + AttributeSet attrs = Xml.asAttributeSet(parser); + + int type; + while (true) { + try { + if (!((type = parser.next()) != XmlPullParser.END_DOCUMENT + && type != XmlPullParser.START_TAG)) { + break; + } + } catch (XmlPullParserException | IOException e) { + return false; + } + } + + TypedArray sa = res.obtainAttributes(attrs, R.styleable.DisplayHasherService); + mThrottleDurationMillis = sa.getInt( + R.styleable.DisplayHasherService_throttleDurationMillis, 0); + sa.recycle(); + mParsedXml = true; + return true; + } + } + /** * Run a command, starting the service connection if necessary. */ diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index d494b75733590..a670b390cb947 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -8626,6 +8626,14 @@ public class WindowManagerService extends IWindowManager.Stub return mDisplayHashController.verifyDisplayHash(displayHash); } + @Override + public void setDisplayHashThrottlingEnabled(boolean enable) { + if (!checkCallingPermission(READ_FRAME_BUFFER, "setDisplayHashThrottle()")) { + throw new SecurityException("Requires READ_FRAME_BUFFER permission"); + } + mDisplayHashController.setDisplayHashThrottlingEnabled(enable); + } + void generateDisplayHash(Session session, IWindow window, Rect boundsInWindow, String hashAlgorithm, RemoteCallback callback) { final SurfaceControl displaySurfaceControl; @@ -8639,6 +8647,13 @@ public class WindowManagerService extends IWindowManager.Stub return; } + if (win.mActivityRecord == null || !win.mActivityRecord.isState( + Task.ActivityState.RESUMED)) { + mDisplayHashController.sendDisplayHashError(callback, + DISPLAY_HASH_ERROR_MISSING_WINDOW); + return; + } + DisplayContent displayContent = win.getDisplayContent(); if (displayContent == null) { Slog.w(TAG, "Failed to generate DisplayHash. Window is not on a display"); @@ -8669,8 +8684,8 @@ public class WindowManagerService extends IWindowManager.Stub .setUid(uid) .setSourceCrop(boundsInDisplay); - mDisplayHashController.generateDisplayHash(args, boundsInWindow, - hashAlgorithm, callback); + mDisplayHashController.generateDisplayHash(args, boundsInWindow, hashAlgorithm, uid, + callback); } boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken) {