From 2e605fd66849febbbd05d96f785bb2800206cf04 Mon Sep 17 00:00:00 2001 From: chaviw Date: Mon, 29 Mar 2021 14:19:04 -0500 Subject: [PATCH] Add throttling to generateDisplayHash Calling generateDisplayHash can cause the system to slow down if there are too many requests. Add throttling logic to prevent apps from calling the API too frequently. The throttling time is based on a value sent from ExtServices so it can be adjusted later Test: DisplayHashManagerTest Bug: 155825630 Change-Id: Id49d1c4113fc65d7b73a1e7b9fc73f50a8d7041f --- core/api/current.txt | 1 + core/api/system-current.txt | 2 + core/api/test-current.txt | 8 + .../displayhash/DisplayHasherService.java | 10 ++ core/java/android/view/IWindowManager.aidl | 9 ++ .../view/displayhash/DisplayHashManager.java | 17 +++ .../DisplayHashResultCallback.java | 9 +- core/res/res/values/attrs.xml | 6 + core/res/res/values/public.xml | 2 + .../server/wm/DisplayHashController.java | 139 +++++++++++++++++- .../server/wm/WindowManagerService.java | 19 ++- 11 files changed, 218 insertions(+), 4 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index 103b7dc955a44..4b5041021da3d 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -51520,6 +51520,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 0f390218ee11d..e87c28177557d 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -320,6 +320,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 } @@ -9784,6 +9785,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 21771df3c6a3c..06a665cb1043a 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2863,6 +2863,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) {