Add DisplayHashParams to specify how to generate display hash

When generating a DisplayHash, each algorithm can have different
requirements about how to capture the content. Instead of
trying to adjust the buffer after the content is rendered, we can
let SurfaceFlinger use the specified arguments to render the content.

This can be done by getting the display hash params from ExtServices so
they can be specified per algorithm. Then, system server will render the
content using the specified values, ensuring a more efficient
way to scale and grayscale the final buffer.

Test: DisplayHash is grayscale and scaled.
Bug: 155825630
Change-Id: I657e2f94219baaac9318500134a6cd82cd07af56
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index b0b2c6c..cdd9f9b 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -9587,10 +9587,28 @@
 
 package android.service.displayhash {
 
+  public final class DisplayHashParams implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.util.Size getBufferSize();
+    method public boolean isBufferScaleWithFiltering();
+    method public boolean isGrayscaleBuffer();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.displayhash.DisplayHashParams> CREATOR;
+  }
+
+  public static final class DisplayHashParams.Builder {
+    ctor public DisplayHashParams.Builder();
+    method @NonNull public android.service.displayhash.DisplayHashParams build();
+    method @NonNull public android.service.displayhash.DisplayHashParams.Builder setBufferScaleWithFiltering(boolean);
+    method @NonNull public android.service.displayhash.DisplayHashParams.Builder setBufferSize(int, int);
+    method @NonNull public android.service.displayhash.DisplayHashParams.Builder setGrayscaleBuffer(boolean);
+  }
+
   public abstract class DisplayHasherService extends android.app.Service {
     ctor public DisplayHasherService();
     method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
     method @Nullable public abstract void onGenerateDisplayHash(@NonNull byte[], @NonNull android.hardware.HardwareBuffer, @NonNull android.graphics.Rect, @NonNull String, @NonNull android.view.displayhash.DisplayHashResultCallback);
+    method @NonNull public abstract java.util.Map<java.lang.String,android.service.displayhash.DisplayHashParams> 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";
   }
diff --git a/core/java/android/service/displayhash/DisplayHashParams.aidl b/core/java/android/service/displayhash/DisplayHashParams.aidl
new file mode 100644
index 0000000..90f9bf1
--- /dev/null
+++ b/core/java/android/service/displayhash/DisplayHashParams.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 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.service.displayhash;
+
+parcelable DisplayHashParams;
diff --git a/core/java/android/service/displayhash/DisplayHashParams.java b/core/java/android/service/displayhash/DisplayHashParams.java
new file mode 100644
index 0000000..6a176a33
--- /dev/null
+++ b/core/java/android/service/displayhash/DisplayHashParams.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2021 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.service.displayhash;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
+import android.os.Parcelable;
+import android.util.Size;
+import android.view.displayhash.DisplayHashResultCallback;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Information passed from the {@link DisplayHasherService} to system server about how to get the
+ * display data that will be used to generate the {@link android.view.displayhash.DisplayHash}
+ *
+ * @hide
+ */
+@SystemApi
+@DataClass(genAidl = true, genToString = true, genParcelable = true, genHiddenConstructor = true)
+public final class DisplayHashParams implements Parcelable {
+    /**
+     * The size to scale the buffer to so the hash algorithm can properly generate the hash. The
+     * buffer given to the {@link DisplayHasherService#onGenerateDisplayHash(byte[], HardwareBuffer,
+     * Rect, String, DisplayHashResultCallback)} will be stretched based on the value set here.
+     * If {@code null}, the buffer size will not be changed.
+     */
+    @Nullable
+    private final Size mBufferSize;
+
+    /**
+     * Whether the content captured will use filtering when scaling.
+     */
+    private final boolean mBufferScaleWithFiltering;
+
+    /**
+     * Whether the content will be captured in grayscale or color.
+     */
+    private final boolean mGrayscaleBuffer;
+
+    /**
+     * A builder for {@link DisplayHashParams}
+     */
+    public static final class Builder {
+        @Nullable
+        private Size mBufferSize;
+        private boolean mBufferScaleWithFiltering;
+        private boolean mGrayscaleBuffer;
+
+        /**
+         * Creates a new Builder.
+         */
+        public Builder() {
+        }
+
+        /**
+         * The size to scale the buffer to so the hash algorithm can properly generate the hash.
+         */
+        @NonNull
+        public Builder setBufferSize(int w, int h) {
+            mBufferSize = new Size(w, h);
+            return this;
+        }
+
+        /**
+         * Whether the content captured will use filtering when scaling.
+         */
+        @NonNull
+        public Builder setBufferScaleWithFiltering(boolean value) {
+            mBufferScaleWithFiltering = value;
+            return this;
+        }
+
+        /**
+         * Whether the content will be captured in grayscale or color.
+         */
+        @NonNull
+        public Builder setGrayscaleBuffer(boolean value) {
+            mGrayscaleBuffer = value;
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        @NonNull
+        public DisplayHashParams build() {
+            return new DisplayHashParams(mBufferSize, mBufferScaleWithFiltering, mGrayscaleBuffer);
+        }
+    }
+
+
+
+    // Code below generated by codegen v1.0.22.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/displayhash/DisplayHashParams.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new DisplayHashParams.
+     *
+     * @param bufferSize
+     *   The size to scale the buffer to so the hash algorithm can properly generate the hash. The
+     *   buffer given to the {@link DisplayHasherService#onGenerateDisplayHash(byte[], HardwareBuffer,
+     *   Rect, String, DisplayHashResultCallback)} will be stretched based on the value set here.
+     *   If {@code null}, the buffer size will not be changed.
+     * @param bufferScaleWithFiltering
+     *   Whether the content captured will use filtering when scaling.
+     * @param grayscaleBuffer
+     *   Whether the content will be captured in grayscale or color.
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public DisplayHashParams(
+            @Nullable Size bufferSize,
+            boolean bufferScaleWithFiltering,
+            boolean grayscaleBuffer) {
+        this.mBufferSize = bufferSize;
+        this.mBufferScaleWithFiltering = bufferScaleWithFiltering;
+        this.mGrayscaleBuffer = grayscaleBuffer;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * The size to scale the buffer to so the hash algorithm can properly generate the hash. The
+     * buffer given to the {@link DisplayHasherService#onGenerateDisplayHash(byte[], HardwareBuffer,
+     * Rect, String, DisplayHashResultCallback)} will be stretched based on the value set here.
+     * If {@code null}, the buffer size will not be changed.
+     */
+    @DataClass.Generated.Member
+    public @Nullable Size getBufferSize() {
+        return mBufferSize;
+    }
+
+    /**
+     * Whether the content captured will use filtering when scaling.
+     */
+    @DataClass.Generated.Member
+    public boolean isBufferScaleWithFiltering() {
+        return mBufferScaleWithFiltering;
+    }
+
+    /**
+     * Whether the content will be captured in grayscale or color.
+     */
+    @DataClass.Generated.Member
+    public boolean isGrayscaleBuffer() {
+        return mGrayscaleBuffer;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "DisplayHashParams { " +
+                "bufferSize = " + mBufferSize + ", " +
+                "bufferScaleWithFiltering = " + mBufferScaleWithFiltering + ", " +
+                "grayscaleBuffer = " + mGrayscaleBuffer +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mBufferScaleWithFiltering) flg |= 0x2;
+        if (mGrayscaleBuffer) flg |= 0x4;
+        if (mBufferSize != null) flg |= 0x1;
+        dest.writeByte(flg);
+        if (mBufferSize != null) dest.writeSize(mBufferSize);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ DisplayHashParams(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        boolean bufferScaleWithFiltering = (flg & 0x2) != 0;
+        boolean grayscaleBuffer = (flg & 0x4) != 0;
+        Size bufferSize = (flg & 0x1) == 0 ? null : (Size) in.readSize();
+
+        this.mBufferSize = bufferSize;
+        this.mBufferScaleWithFiltering = bufferScaleWithFiltering;
+        this.mGrayscaleBuffer = grayscaleBuffer;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<DisplayHashParams> CREATOR
+            = new Parcelable.Creator<DisplayHashParams>() {
+        @Override
+        public DisplayHashParams[] newArray(int size) {
+            return new DisplayHashParams[size];
+        }
+
+        @Override
+        public DisplayHashParams createFromParcel(@NonNull android.os.Parcel in) {
+            return new DisplayHashParams(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1615565493989L,
+            codegenVersion = "1.0.22",
+            sourceFile = "frameworks/base/core/java/android/service/displayhash/DisplayHashParams.java",
+            inputSignatures = "private final @android.annotation.Nullable android.util.Size mBufferSize\nprivate final  boolean mBufferScaleWithFiltering\nprivate final  boolean mGrayscaleBuffer\nclass DisplayHashParams extends java.lang.Object implements [android.os.Parcelable]\nprivate @android.annotation.Nullable android.util.Size mBufferSize\nprivate  boolean mBufferScaleWithFiltering\nprivate  boolean mGrayscaleBuffer\npublic @android.annotation.NonNull android.service.displayhash.DisplayHashParams.Builder setBufferSize(int,int)\npublic @android.annotation.NonNull android.service.displayhash.DisplayHashParams.Builder setBufferScaleWithFiltering(boolean)\npublic @android.annotation.NonNull android.service.displayhash.DisplayHashParams.Builder setGrayscaleBuffer(boolean)\npublic @android.annotation.NonNull android.service.displayhash.DisplayHashParams build()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genAidl=true, genToString=true, genParcelable=true, genHiddenConstructor=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/service/displayhash/DisplayHasherService.java b/core/java/android/service/displayhash/DisplayHasherService.java
index 331dbe9..2105d84 100644
--- a/core/java/android/service/displayhash/DisplayHasherService.java
+++ b/core/java/android/service/displayhash/DisplayHasherService.java
@@ -34,6 +34,8 @@
 import android.view.displayhash.DisplayHashResultCallback;
 import android.view.displayhash.VerifiedDisplayHash;
 
+import java.util.Map;
+
 /**
  * A service that handles generating and verify {@link DisplayHash}.
  *
@@ -50,15 +52,6 @@
             "android.service.displayhash.extra.VERIFIED_DISPLAY_HASH";
 
     /**
-     * Manifest metadata key for the resource string array containing the names of all hashing
-     * algorithms provided by the service.
-     *
-     * @hide
-     */
-    public static final String SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS =
-            "android.displayhash.available_algorithms";
-
-    /**
      * 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.
      *
@@ -96,7 +89,7 @@
      * @param buffer        The buffer for the content to generate the hash for.
      * @param bounds        The size and position of the content in window space.
      * @param hashAlgorithm The String for the hashing algorithm to use based values in
-     *                      {@link #SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS)}.
+     *                      {@link #getDisplayHashAlgorithms(RemoteCallback)}.
      * @param callback      The callback to invoke
      *                      {@link DisplayHashResultCallback#onDisplayHashResult(DisplayHash)}
      *                      if successfully generated a DisplayHash or {@link
@@ -108,6 +101,12 @@
             @NonNull String hashAlgorithm, @NonNull DisplayHashResultCallback callback);
 
     /**
+     * Returns a map of supported algorithms and their {@link DisplayHashParams}
+     */
+    @NonNull
+    public abstract Map<String, DisplayHashParams> onGetDisplayHashAlgorithms();
+
+    /**
      * Call to verify that the DisplayHash passed in was generated by the system.
      *
      * @param salt        The salt value to use when verifying the hmac. This should be the
@@ -132,6 +131,15 @@
         callback.sendResult(data);
     }
 
+    private void getDisplayHashAlgorithms(RemoteCallback callback) {
+        Map<String, DisplayHashParams> displayHashParams = onGetDisplayHashAlgorithms();
+        final Bundle data = new Bundle();
+        for (Map.Entry<String, DisplayHashParams> entry : displayHashParams.entrySet()) {
+            data.putParcelable(entry.getKey(), entry.getValue());
+        }
+        callback.sendResult(data);
+    }
+
     private final class DisplayHasherServiceWrapper extends IDisplayHasherService.Stub {
         @Override
         public void generateDisplayHash(byte[] salt, HardwareBuffer buffer, Rect bounds,
@@ -164,5 +172,11 @@
                     obtainMessage(DisplayHasherService::verifyDisplayHash,
                             DisplayHasherService.this, salt, displayHash, callback));
         }
+
+        @Override
+        public void getDisplayHashAlgorithms(RemoteCallback callback) {
+            mHandler.sendMessage(obtainMessage(DisplayHasherService::getDisplayHashAlgorithms,
+                    DisplayHasherService.this, callback));
+        }
     }
 }
diff --git a/core/java/android/service/displayhash/IDisplayHasherService.aidl b/core/java/android/service/displayhash/IDisplayHasherService.aidl
index 236bc28..d9dcdca 100644
--- a/core/java/android/service/displayhash/IDisplayHasherService.aidl
+++ b/core/java/android/service/displayhash/IDisplayHasherService.aidl
@@ -51,4 +51,11 @@
      * @param callback The callback invoked to send back the VerifiedDisplayHash.
      */
     void verifyDisplayHash(in byte[] salt, in DisplayHash displayHash, in RemoteCallback callback);
+
+    /**
+     * Call to get a map of supported algorithms and their {@link DisplayHashParams}
+     *
+     * @param callback The callback invoked to send back the map of algorithms to DisplayHashParams.
+     */
+    void getDisplayHashAlgorithms(in RemoteCallback callback);
 }
diff --git a/services/core/java/com/android/server/wm/DisplayHashController.java b/services/core/java/com/android/server/wm/DisplayHashController.java
index 7e16c22..1262dee 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_KEY_AVAILABLE_ALGORITHMS;
+import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM;
+import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_UNKNOWN;
+import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH_ERROR_CODE;
 
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -32,7 +34,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
-import android.content.res.Resources;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -45,16 +46,21 @@
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.service.displayhash.DisplayHashParams;
 import android.service.displayhash.DisplayHasherService;
 import android.service.displayhash.IDisplayHasherService;
+import android.util.Size;
 import android.util.Slog;
 import android.view.MagnificationSpec;
+import android.view.SurfaceControl;
 import android.view.displayhash.DisplayHash;
 import android.view.displayhash.VerifiedDisplayHash;
 
 import com.android.internal.annotations.GuardedBy;
 
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -78,12 +84,15 @@
     private final Context mContext;
 
     /**
-     * Lock used for the cached {@link #mHashAlgorithms} array
+     * Lock used for the cached {@link #mDisplayHashAlgorithms} map
      */
-    private final Object mHashAlgorithmsLock = new Object();
+    private final Object mDisplayHashAlgorithmsLock = new Object();
 
-    @GuardedBy("mHashingAlgorithmsLock")
-    private String[] mHashAlgorithms;
+    /**
+     * The cached map of display hash algorithms to the {@link DisplayHashParams}
+     */
+    @GuardedBy("mDisplayHashAlgorithmsLock")
+    private Map<String, DisplayHashParams> mDisplayHashAlgorithms;
 
     private final Handler mHandler;
 
@@ -104,34 +113,8 @@
     }
 
     String[] getSupportedHashAlgorithms() {
-        // We have a separate lock for the hashing algorithm array 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 hashing algorithms array so we don't need to call into the
-        // ExtServices process for each request.
-        synchronized (mHashAlgorithmsLock) {
-            // Already have cached values
-            if (mHashAlgorithms != null) {
-                return mHashAlgorithms;
-            }
-
-            final ServiceInfo serviceInfo = getServiceInfo();
-            if (serviceInfo == null) return null;
-
-            final PackageManager pm = mContext.getPackageManager();
-            final Resources res;
-            try {
-                res = pm.getResourcesForApplication(serviceInfo.applicationInfo);
-            } catch (PackageManager.NameNotFoundException e) {
-                Slog.e(TAG, "Error getting application resources for " + serviceInfo, e);
-                return null;
-            }
-
-            final int resourceId = serviceInfo.metaData.getInt(
-                    SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS);
-            mHashAlgorithms = res.getStringArray(resourceId);
-
-            return mHashAlgorithms;
-        }
+        Map<String, DisplayHashParams> displayHashAlgorithms = getDisplayHashAlgorithms();
+        return displayHashAlgorithms.keySet().toArray(new String[0]);
     }
 
     @Nullable
@@ -148,13 +131,76 @@
         return results.getParcelable(EXTRA_VERIFIED_DISPLAY_HASH);
     }
 
-    void generateDisplayHash(HardwareBuffer buffer, Rect bounds,
+    private void generateDisplayHash(HardwareBuffer buffer, Rect bounds,
             String hashAlgorithm, RemoteCallback callback) {
         connectAndRun(
                 service -> service.generateDisplayHash(mSalt, buffer, bounds, hashAlgorithm,
                         callback));
     }
 
+    void generateDisplayHash(SurfaceControl.LayerCaptureArgs.Builder args,
+            Rect boundsInWindow, String hashAlgorithm, RemoteCallback callback) {
+        final Map<String, DisplayHashParams> displayHashAlgorithmsMap = getDisplayHashAlgorithms();
+        DisplayHashParams displayHashParams = displayHashAlgorithmsMap.get(hashAlgorithm);
+        if (displayHashParams == null) {
+            Slog.w(TAG, "Failed to generateDisplayHash. Invalid hashAlgorithm");
+            sendDisplayHashError(callback, DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM);
+            return;
+        }
+
+        Size size = displayHashParams.getBufferSize();
+        if (size != null && (size.getWidth() > 0 || size.getHeight() > 0)) {
+            args.setFrameScale((float) size.getWidth() / boundsInWindow.width(),
+                    (float) size.getHeight() / boundsInWindow.height());
+        }
+
+        args.setGrayscale(displayHashParams.isGrayscaleBuffer());
+
+        SurfaceControl.ScreenshotHardwareBuffer screenshotHardwareBuffer =
+                SurfaceControl.captureLayers(args.build());
+        if (screenshotHardwareBuffer == null
+                || screenshotHardwareBuffer.getHardwareBuffer() == null) {
+            Slog.w(TAG, "Failed to generate DisplayHash. Couldn't capture content");
+            sendDisplayHashError(callback, DISPLAY_HASH_ERROR_UNKNOWN);
+            return;
+        }
+
+        generateDisplayHash(screenshotHardwareBuffer.getHardwareBuffer(), boundsInWindow,
+                hashAlgorithm, callback);
+    }
+
+    private Map<String, DisplayHashParams> getDisplayHashAlgorithms() {
+        // We have a separate lock for the hashing params to ensure we can properly cache the
+        // hashing params so we don't need to call into the ExtServices process for each request.
+        synchronized (mDisplayHashAlgorithmsLock) {
+            if (mDisplayHashAlgorithms != null) {
+                return mDisplayHashAlgorithms;
+            }
+
+            final SyncCommand syncCommand = new SyncCommand();
+            Bundle results = syncCommand.run((service, remoteCallback) -> {
+                try {
+                    service.getDisplayHashAlgorithms(remoteCallback);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Failed to invoke getDisplayHashAlgorithms command", e);
+                }
+            });
+
+            mDisplayHashAlgorithms = new HashMap<>(results.size());
+            for (String key : results.keySet()) {
+                mDisplayHashAlgorithms.put(key, results.getParcelable(key));
+            }
+
+            return mDisplayHashAlgorithms;
+        }
+    }
+
+    void sendDisplayHashError(RemoteCallback callback, int errorCode) {
+        Bundle bundle = new Bundle();
+        bundle.putInt(EXTRA_DISPLAY_HASH_ERROR_CODE, errorCode);
+        callback.sendResult(bundle);
+    }
+
     /**
      * Calculate the bounds to generate the hash for. This takes into account window transform,
      * magnification, and display bounds.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 6f853c7..47d4832 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -84,8 +84,6 @@
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID;
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW;
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN;
-import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_UNKNOWN;
-import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH_ERROR_CODE;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BOOT;
@@ -8655,14 +8653,16 @@
             final WindowState win = windowForClientLocked(session, window, false);
             if (win == null) {
                 Slog.w(TAG, "Failed to generate DisplayHash. Invalid window");
-                sendDisplayHashError(callback, DISPLAY_HASH_ERROR_MISSING_WINDOW);
+                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");
-                sendDisplayHashError(callback, DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN);
+                mDisplayHashController.sendDisplayHashError(callback,
+                        DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN);
                 return;
             }
 
@@ -8672,7 +8672,8 @@
 
             if (boundsInDisplay.isEmpty()) {
                 Slog.w(TAG, "Failed to generate DisplayHash. Bounds are not on screen");
-                sendDisplayHashError(callback, DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN);
+                mDisplayHashController.sendDisplayHashError(callback,
+                        DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN);
                 return;
             }
         }
@@ -8682,23 +8683,13 @@
         // be covering it with the same uid. We want to make sure we include content that's
         // covering to ensure we get as close as possible to what the user sees
         final int uid = session.mUid;
-        SurfaceControl.LayerCaptureArgs args =
+        SurfaceControl.LayerCaptureArgs.Builder args =
                 new SurfaceControl.LayerCaptureArgs.Builder(displaySurfaceControl)
                         .setUid(uid)
-                        .setSourceCrop(boundsInDisplay)
-                        .build();
+                        .setSourceCrop(boundsInDisplay);
 
-        SurfaceControl.ScreenshotHardwareBuffer screenshotHardwareBuffer =
-                SurfaceControl.captureLayers(args);
-        if (screenshotHardwareBuffer == null
-                || screenshotHardwareBuffer.getHardwareBuffer() == null) {
-            Slog.w(TAG, "Failed to generate DisplayHash. Couldn't capture content");
-            sendDisplayHashError(callback, DISPLAY_HASH_ERROR_UNKNOWN);
-            return;
-        }
-
-        mDisplayHashController.generateDisplayHash(screenshotHardwareBuffer.getHardwareBuffer(),
-                boundsInWindow, hashAlgorithm, callback);
+        mDisplayHashController.generateDisplayHash(args, boundsInWindow,
+                hashAlgorithm, callback);
     }
 
     boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken) {
@@ -8716,10 +8707,4 @@
             return snapshot != null && snapshot.hasImeSurface();
         }
     }
-
-    private void sendDisplayHashError(RemoteCallback callback, int errorCode) {
-        Bundle bundle = new Bundle();
-        bundle.putInt(EXTRA_DISPLAY_HASH_ERROR_CODE, errorCode);
-        callback.sendResult(bundle);
-    }
 }