1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.server.wm.display;
18 
19 import static android.server.wm.CtsWindowInfoUtils.waitForStableWindowGeometry;
20 import static android.server.wm.CtsWindowInfoUtils.waitForWindowVisible;
21 import static android.server.wm.UiDeviceUtils.pressUnlockButton;
22 import static android.server.wm.UiDeviceUtils.pressWakeupButton;
23 import static android.server.wm.WindowManagerState.STATE_RESUMED;
24 import static android.view.Display.DEFAULT_DISPLAY;
25 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
26 import static android.view.cts.surfacevalidator.ASurfaceControlTestActivity.WAIT_TIMEOUT_S;
27 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS;
28 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM;
29 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN;
30 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_TOO_MANY_REQUESTS;
31 import static android.widget.LinearLayout.VERTICAL;
32 
33 import static org.junit.Assert.assertArrayEquals;
34 import static org.junit.Assert.assertEquals;
35 import static org.junit.Assert.assertNotEquals;
36 import static org.junit.Assert.assertNotNull;
37 import static org.junit.Assert.assertNull;
38 
39 import android.app.Activity;
40 import android.app.Instrumentation;
41 import android.app.KeyguardManager;
42 import android.content.ComponentName;
43 import android.content.Context;
44 import android.content.Intent;
45 import android.graphics.Color;
46 import android.graphics.Point;
47 import android.graphics.Rect;
48 import android.os.Bundle;
49 import android.os.PowerManager;
50 import android.os.SystemClock;
51 import android.platform.test.annotations.Presubmit;
52 import android.server.wm.WindowManagerState;
53 import android.server.wm.WindowManagerStateHelper;
54 import android.service.displayhash.DisplayHashParams;
55 import android.util.Size;
56 import android.view.Gravity;
57 import android.view.SurfaceControl;
58 import android.view.View;
59 import android.view.ViewTreeObserver;
60 import android.view.WindowManager;
61 import android.view.displayhash.DisplayHash;
62 import android.view.displayhash.DisplayHashManager;
63 import android.view.displayhash.DisplayHashResultCallback;
64 import android.view.displayhash.VerifiedDisplayHash;
65 import android.widget.LinearLayout;
66 import android.widget.RelativeLayout;
67 
68 import androidx.annotation.NonNull;
69 import androidx.test.filters.FlakyTest;
70 import androidx.test.platform.app.InstrumentationRegistry;
71 import androidx.test.rule.ActivityTestRule;
72 
73 import com.android.compatibility.common.util.SystemUtil;
74 
75 import org.junit.After;
76 import org.junit.Before;
77 import org.junit.Rule;
78 import org.junit.Test;
79 
80 import java.util.ArrayList;
81 import java.util.Objects;
82 import java.util.Set;
83 import java.util.concurrent.CountDownLatch;
84 import java.util.concurrent.Executor;
85 import java.util.concurrent.TimeUnit;
86 
87 @Presubmit
88 public class DisplayHashManagerTest {
89     private static final int WAIT_TIME_S = 5;
90     private static final int TIMEOUT_MS = 1000;
91     private static final int SLEEP_TIMEOUT_MS = 200;
92 
93     private final Point mCenter = new Point();
94     private final Point mTestViewSize = new Point(200, 300);
95 
96     private Instrumentation mInstrumentation;
97     private RelativeLayout mMainView;
98     private TestActivity mActivity;
99 
100     private View mTestView;
101 
102     private DisplayHashManager mDisplayHashManager;
103     private String mPhashAlgorithm;
104 
105     private Executor mExecutor;
106 
107     private SyncDisplayHashResultCallback mSyncDisplayHashResultCallback;
108 
109     protected WindowManagerStateHelper mWmState = new WindowManagerStateHelper();
110 
111     @Rule
112     public ActivityTestRule<TestActivity> mActivityRule =
113             new ActivityTestRule<>(TestActivity.class);
114 
115     @Before
setUp()116     public void setUp() throws Exception {
117         mInstrumentation = InstrumentationRegistry.getInstrumentation();
118         final Context context = mInstrumentation.getContext();
119         final KeyguardManager km = context.getSystemService(KeyguardManager.class);
120         Intent intent = new Intent(Intent.ACTION_MAIN);
121         intent.setClass(context, TestActivity.class);
122         mActivity = mActivityRule.getActivity();
123 
124         if (km != null && km.isKeyguardLocked() || !Objects.requireNonNull(
125                 context.getSystemService(PowerManager.class)).isInteractive()) {
126             pressWakeupButton();
127             pressUnlockButton();
128         }
129 
130         mActivity.runOnUiThread(() -> {
131             mMainView = new RelativeLayout(mActivity);
132             mActivity.setContentView(mMainView);
133         });
134         mInstrumentation.waitForIdleSync();
135         mActivity.runOnUiThread(() -> {
136             mCenter.set((mMainView.getWidth() - mTestViewSize.x) / 2,
137                     (mMainView.getHeight() - mTestViewSize.y) / 2);
138         });
139         mDisplayHashManager = context.getSystemService(DisplayHashManager.class);
140 
141         Set<String> algorithms = mDisplayHashManager.getSupportedHashAlgorithms();
142         assertNotNull(algorithms);
143         assertNotEquals(0, algorithms.size());
144         for (String algorithm : algorithms) {
145             if ("pHash".equalsIgnoreCase(algorithm)) {
146                 mPhashAlgorithm = algorithm;
147                 break;
148             }
149         }
150         assertNotNull(mPhashAlgorithm);
151 
152         mExecutor = context.getMainExecutor();
153         mSyncDisplayHashResultCallback = new SyncDisplayHashResultCallback();
154         SystemUtil.runWithShellPermissionIdentity(
155                 () -> mDisplayHashManager.setDisplayHashThrottlingEnabled(false));
156     }
157 
158     @After
tearDown()159     public void tearDown() {
160         SystemUtil.runWithShellPermissionIdentity(
161                 () -> mDisplayHashManager.setDisplayHashThrottlingEnabled(true));
162     }
163 
164     @FlakyTest(bugId = 292291447)
165     @Test
testGenerateAndVerifyDisplayHash()166     public void testGenerateAndVerifyDisplayHash() throws InterruptedException {
167         setupChildView();
168 
169         // A solid color image has expected hash of all 0s
170         byte[] expectedImageHash = new byte[8];
171 
172         DisplayHash displayHash = generateDisplayHash(null);
173         VerifiedDisplayHash verifiedDisplayHash = mDisplayHashManager.verifyDisplayHash(
174                 displayHash);
175         assertNotNull(verifiedDisplayHash);
176 
177         assertEquals(mTestViewSize.x, verifiedDisplayHash.getBoundsInWindow().width());
178         assertEquals(mTestViewSize.y, verifiedDisplayHash.getBoundsInWindow().height());
179         assertArrayEquals(expectedImageHash, verifiedDisplayHash.getImageHash());
180     }
181 
182     @Test
testGenerateAndVerifyDisplayHash_BoundsInView()183     public void testGenerateAndVerifyDisplayHash_BoundsInView() throws InterruptedException {
184         setupChildView();
185 
186         Rect bounds = new Rect(10, 20, mTestViewSize.x / 2, mTestViewSize.y / 2);
187         DisplayHash displayHash = generateDisplayHash(new Rect(bounds));
188 
189         VerifiedDisplayHash verifiedDisplayHash = mDisplayHashManager.verifyDisplayHash(
190                 displayHash);
191         assertNotNull(verifiedDisplayHash);
192         assertEquals(bounds.width(), verifiedDisplayHash.getBoundsInWindow().width());
193         assertEquals(bounds.height(), verifiedDisplayHash.getBoundsInWindow().height());
194     }
195 
196     @Test
testGenerateAndVerifyDisplayHash_EmptyBounds()197     public void testGenerateAndVerifyDisplayHash_EmptyBounds() throws InterruptedException {
198         setupChildView();
199 
200         mTestView.generateDisplayHash(mPhashAlgorithm, new Rect(), mExecutor,
201                 mSyncDisplayHashResultCallback);
202 
203         int errorCode = mSyncDisplayHashResultCallback.getError();
204         assertEquals(DISPLAY_HASH_ERROR_INVALID_BOUNDS, errorCode);
205     }
206 
207     @Test
testGenerateAndVerifyDisplayHash_BoundsBiggerThanView()208     public void testGenerateAndVerifyDisplayHash_BoundsBiggerThanView()
209             throws InterruptedException {
210         setupChildView();
211 
212         Rect bounds = new Rect(0, 0, mTestViewSize.x + 100, mTestViewSize.y + 100);
213 
214         DisplayHash displayHash = generateDisplayHash(new Rect(bounds));
215 
216         VerifiedDisplayHash verifiedDisplayHash = mDisplayHashManager.verifyDisplayHash(
217                 displayHash);
218         assertNotNull(verifiedDisplayHash);
219         assertEquals(mTestViewSize.x, verifiedDisplayHash.getBoundsInWindow().width());
220         assertEquals(mTestViewSize.y, verifiedDisplayHash.getBoundsInWindow().height());
221     }
222 
223     @Test
testGenerateDisplayHash_BoundsOutOfView()224     public void testGenerateDisplayHash_BoundsOutOfView() throws InterruptedException {
225         setupChildView();
226 
227         Rect bounds = new Rect(mTestViewSize.x + 1, mTestViewSize.y + 1, mTestViewSize.x + 100,
228                 mTestViewSize.y + 100);
229 
230         mTestView.generateDisplayHash(mPhashAlgorithm, new Rect(bounds),
231                 mExecutor, mSyncDisplayHashResultCallback);
232         int errorCode = mSyncDisplayHashResultCallback.getError();
233         assertEquals(DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN, errorCode);
234     }
235 
236     @Test
testGenerateDisplayHash_ViewOffscreen()237     public void testGenerateDisplayHash_ViewOffscreen() {
238         final CountDownLatch committedCallbackLatch = new CountDownLatch(1);
239         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
240         t.addTransactionCommittedListener(mExecutor, committedCallbackLatch::countDown);
241 
242         mInstrumentation.runOnMainSync(() -> {
243             final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
244                     mTestViewSize.y);
245             mTestView = new View(mActivity);
246             mTestView.setBackgroundColor(Color.BLUE);
247             mTestView.setX(-mTestViewSize.x);
248 
249             mMainView.addView(mTestView, p);
250             mMainView.getRootSurfaceControl().applyTransactionOnDraw(t);
251         });
252         mInstrumentation.waitForIdleSync();
253         try {
254             committedCallbackLatch.await(WAIT_TIME_S, TimeUnit.SECONDS);
255         } catch (InterruptedException e) {
256         }
257 
258         mTestView.generateDisplayHash(mPhashAlgorithm, null, mExecutor,
259                 mSyncDisplayHashResultCallback);
260 
261         int errorCode = mSyncDisplayHashResultCallback.getError();
262         assertEquals(DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN, errorCode);
263     }
264 
265     @Test
testGenerateDisplayHash_WindowOffscreen()266     public void testGenerateDisplayHash_WindowOffscreen() throws InterruptedException {
267         final WindowManager wm = mActivity.getWindowManager();
268         final WindowManager.LayoutParams windowParams = new WindowManager.LayoutParams();
269 
270         final CountDownLatch committedCallbackLatch = new CountDownLatch(1);
271         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
272         t.addTransactionCommittedListener(mExecutor, committedCallbackLatch::countDown);
273         mInstrumentation.runOnMainSync(() -> {
274             mMainView = new RelativeLayout(mActivity);
275             windowParams.width = mTestViewSize.x;
276             windowParams.height = mTestViewSize.y;
277             windowParams.gravity = Gravity.LEFT | Gravity.TOP;
278             windowParams.flags = FLAG_LAYOUT_NO_LIMITS;
279             mActivity.addWindow(mMainView, windowParams);
280 
281             mMainView.getViewTreeObserver().addOnWindowAttachListener(
282                     new ViewTreeObserver.OnWindowAttachListener() {
283                         @Override
284                         public void onWindowAttached() {
285                             final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(
286                                     mTestViewSize.x,
287                                     mTestViewSize.y);
288                             mTestView = new View(mActivity);
289                             mTestView.setBackgroundColor(Color.BLUE);
290                             mMainView.addView(mTestView, p);
291                             mMainView.getRootSurfaceControl().applyTransactionOnDraw(t);
292                         }
293 
294                         @Override
295                         public void onWindowDetached() {
296                         }
297                     });
298         });
299         mInstrumentation.waitForIdleSync();
300         try {
301             committedCallbackLatch.await(WAIT_TIME_S, TimeUnit.SECONDS);
302         } catch (InterruptedException e) {
303         }
304 
305         waitForAllActivitiesResumed();
306 
307         generateDisplayHash(null);
308 
309         mInstrumentation.runOnMainSync(() -> {
310             int[] mainViewLocationOnScreen = new int[2];
311             mMainView.getLocationOnScreen(mainViewLocationOnScreen);
312 
313             windowParams.x = -mTestViewSize.x - mainViewLocationOnScreen[0];
314             wm.updateViewLayout(mMainView, windowParams);
315         });
316         mInstrumentation.waitForIdleSync();
317         waitForStableWindowGeometry(WAIT_TIMEOUT_S, TimeUnit.SECONDS);
318 
319         mSyncDisplayHashResultCallback.reset();
320         mTestView.generateDisplayHash(mPhashAlgorithm, null, mExecutor,
321                 mSyncDisplayHashResultCallback);
322 
323         int errorCode = mSyncDisplayHashResultCallback.getError();
324         assertEquals(DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN, errorCode);
325     }
326 
327     @Test
testGenerateDisplayHash_InvalidHashAlgorithm()328     public void testGenerateDisplayHash_InvalidHashAlgorithm() throws InterruptedException {
329         setupChildView();
330 
331         mTestView.generateDisplayHash("fake hash", null, mExecutor,
332                 mSyncDisplayHashResultCallback);
333         int errorCode = mSyncDisplayHashResultCallback.getError();
334         assertEquals(DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM, errorCode);
335     }
336 
337     @Test
testVerifyDisplayHash_ValidDisplayHash()338     public void testVerifyDisplayHash_ValidDisplayHash() throws InterruptedException {
339         setupChildView();
340 
341         DisplayHash displayHash = generateDisplayHash(null);
342         VerifiedDisplayHash verifiedDisplayHash = mDisplayHashManager.verifyDisplayHash(
343                 displayHash);
344 
345         assertNotNull(verifiedDisplayHash);
346         assertEquals(displayHash.getTimeMillis(), verifiedDisplayHash.getTimeMillis());
347         assertEquals(displayHash.getBoundsInWindow(), verifiedDisplayHash.getBoundsInWindow());
348         assertEquals(displayHash.getHashAlgorithm(), verifiedDisplayHash.getHashAlgorithm());
349         assertArrayEquals(displayHash.getImageHash(), verifiedDisplayHash.getImageHash());
350     }
351 
352     @Test
testVerifyDisplayHash_InvalidDisplayHash()353     public void testVerifyDisplayHash_InvalidDisplayHash() throws InterruptedException {
354         setupChildView();
355 
356         DisplayHash displayHash = generateDisplayHash(null);
357         DisplayHash fakeDisplayHash = new DisplayHash(
358                 displayHash.getTimeMillis(), displayHash.getBoundsInWindow(),
359                 displayHash.getHashAlgorithm(), new byte[32], displayHash.getHmac());
360         VerifiedDisplayHash verifiedDisplayHash = mDisplayHashManager.verifyDisplayHash(
361                 fakeDisplayHash);
362 
363         assertNull(verifiedDisplayHash);
364     }
365 
366     @Test
testVerifiedDisplayHash()367     public void testVerifiedDisplayHash() {
368         long timeMillis = 1000;
369         Rect boundsInWindow = new Rect(0, 0, 50, 100);
370         String hashAlgorithm = "hashAlgorithm";
371         byte[] imageHash = new byte[]{2, 4, 1, 5, 6, 2};
372         VerifiedDisplayHash verifiedDisplayHash = new VerifiedDisplayHash(timeMillis,
373                 boundsInWindow, hashAlgorithm, imageHash);
374 
375         assertEquals(timeMillis, verifiedDisplayHash.getTimeMillis());
376         assertEquals(boundsInWindow, verifiedDisplayHash.getBoundsInWindow());
377         assertEquals(hashAlgorithm, verifiedDisplayHash.getHashAlgorithm());
378         assertArrayEquals(imageHash, verifiedDisplayHash.getImageHash());
379     }
380 
381     @Test
testGenerateDisplayHash_Throttle()382     public void testGenerateDisplayHash_Throttle() throws InterruptedException {
383         SystemUtil.runWithShellPermissionIdentity(
384                 () -> mDisplayHashManager.setDisplayHashThrottlingEnabled(true));
385 
386         setupChildView();
387 
388         mTestView.generateDisplayHash(mPhashAlgorithm, null, mExecutor,
389                 mSyncDisplayHashResultCallback);
390         mSyncDisplayHashResultCallback.getDisplayHash();
391         mSyncDisplayHashResultCallback.reset();
392         // Generate a second display hash right away.
393         mTestView.generateDisplayHash(mPhashAlgorithm, null, mExecutor,
394                 mSyncDisplayHashResultCallback);
395         int errorCode = mSyncDisplayHashResultCallback.getError();
396         assertEquals(DISPLAY_HASH_ERROR_TOO_MANY_REQUESTS, errorCode);
397     }
398 
399     @Test
400     @FlakyTest(bugId = 292291447)
testGenerateAndVerifyDisplayHash_MultiColor()401     public void testGenerateAndVerifyDisplayHash_MultiColor() throws InterruptedException {
402         final CountDownLatch committedCallbackLatch = new CountDownLatch(1);
403         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
404         t.addTransactionCommittedListener(mExecutor, committedCallbackLatch::countDown);
405         mInstrumentation.runOnMainSync(() -> {
406             final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
407                     mTestViewSize.y);
408             LinearLayout linearLayout = new LinearLayout(mActivity);
409             linearLayout.setOrientation(VERTICAL);
410             LinearLayout.LayoutParams blueParams = new LinearLayout.LayoutParams(mTestViewSize.x,
411                     mTestViewSize.y / 2);
412             View blueView = new View(mActivity);
413             blueView.setBackgroundColor(Color.BLUE);
414             LinearLayout.LayoutParams redParams = new LinearLayout.LayoutParams(mTestViewSize.x,
415                     mTestViewSize.y / 2);
416             View redView = new View(mActivity);
417             redView.setBackgroundColor(Color.RED);
418 
419             linearLayout.addView(blueView, blueParams);
420             linearLayout.addView(redView, redParams);
421             mTestView = linearLayout;
422 
423             mTestView.setX(mCenter.x);
424             mTestView.setY(mCenter.y);
425             mMainView.addView(mTestView, p);
426             mMainView.getRootSurfaceControl().applyTransactionOnDraw(t);
427         });
428         mInstrumentation.waitForIdleSync();
429         mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
430         try {
431             committedCallbackLatch.await(WAIT_TIME_S, TimeUnit.SECONDS);
432         } catch (InterruptedException e) {
433         }
434 
435         ComponentName componentName = ComponentName.unflattenFromString(
436                 "android.server.wm/android.server.wm.display.DisplayHashManagerTest$TestActivity");
437         waitForActivityResumed(TIMEOUT_MS, componentName);
438         waitForWindowVisible(mTestView);
439 
440         byte[] expectedImageHash = new byte[]{-1, -1, 127, -1, -1, -1, 127, 127};
441 
442         DisplayHash displayHash = generateDisplayHash(null);
443         VerifiedDisplayHash verifiedDisplayHash = mDisplayHashManager.verifyDisplayHash(
444                 displayHash);
445         assertNotNull(verifiedDisplayHash);
446 
447         assertEquals(mTestViewSize.x, verifiedDisplayHash.getBoundsInWindow().width());
448         assertEquals(mTestViewSize.y, verifiedDisplayHash.getBoundsInWindow().height());
449         assertArrayEquals(expectedImageHash, verifiedDisplayHash.getImageHash());
450     }
451 
waitForActivityResumed(int timeoutMs, ComponentName componentName)452     private void waitForActivityResumed(int timeoutMs, ComponentName componentName) {
453         long endTime = System.currentTimeMillis() + timeoutMs;
454         while (endTime > System.currentTimeMillis()) {
455             mWmState.computeState();
456             if (mWmState.hasActivityState(componentName, STATE_RESUMED)) {
457                 SystemClock.sleep(SLEEP_TIMEOUT_MS);
458                 mWmState.computeState();
459                 break;
460             }
461             SystemClock.sleep(SLEEP_TIMEOUT_MS);
462             mWmState.computeState();
463         }
464     }
465 
466     @Test
testDisplayHashParams()467     public void testDisplayHashParams() {
468         int width = 10;
469         int height = 20;
470         boolean isGrayscale = true;
471         DisplayHashParams displayHashParams = new DisplayHashParams.Builder()
472                 .setBufferSize(width, height)
473                 .setGrayscaleBuffer(isGrayscale)
474                 .build();
475 
476         Size bufferSize = displayHashParams.getBufferSize();
477         assertEquals(width, bufferSize.getWidth());
478         assertEquals(height, bufferSize.getHeight());
479         assertEquals(isGrayscale, displayHashParams.isGrayscaleBuffer());
480     }
481 
generateDisplayHash(Rect bounds)482     private DisplayHash generateDisplayHash(Rect bounds) {
483         mTestView.generateDisplayHash(mPhashAlgorithm, bounds, mExecutor,
484                 mSyncDisplayHashResultCallback);
485         DisplayHash displayHash = mSyncDisplayHashResultCallback.getDisplayHash();
486 
487         assertNotNull(displayHash);
488         return displayHash;
489     }
490 
setupChildView()491     private void setupChildView() throws InterruptedException {
492         final CountDownLatch committedCallbackLatch = new CountDownLatch(1);
493         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
494         t.addTransactionCommittedListener(mExecutor, committedCallbackLatch::countDown);
495 
496         mInstrumentation.runOnMainSync(() -> {
497             final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
498                     mTestViewSize.y);
499             mTestView = new View(mActivity);
500             mTestView.setX(mCenter.x);
501             mTestView.setY(mCenter.y);
502             mTestView.setBackgroundColor(Color.BLUE);
503             mMainView.addView(mTestView, p);
504             mMainView.getRootSurfaceControl().applyTransactionOnDraw(t);
505         });
506         mInstrumentation.waitForIdleSync();
507         try {
508             committedCallbackLatch.await(WAIT_TIME_S, TimeUnit.SECONDS);
509         } catch (InterruptedException e) {
510         }
511         waitForAllActivitiesResumed();
512         waitForWindowVisible(mTestView);
513     }
514 
515     public static class TestActivity extends Activity {
516         private final ArrayList<View> mViews = new ArrayList<>();
517 
518         @Override
onCreate(Bundle savedInstanceState)519         protected void onCreate(Bundle savedInstanceState) {
520             super.onCreate(savedInstanceState);
521         }
522 
addWindow(View view, WindowManager.LayoutParams attrs)523         void addWindow(View view, WindowManager.LayoutParams attrs) {
524             getWindowManager().addView(view, attrs);
525             mViews.add(view);
526         }
527 
removeAllWindows()528         void removeAllWindows() {
529             for (View view : mViews) {
530                 getWindowManager().removeViewImmediate(view);
531             }
532             mViews.clear();
533         }
534 
535         @Override
onPause()536         protected void onPause() {
537             super.onPause();
538             removeAllWindows();
539         }
540     }
541 
542     private static class SyncDisplayHashResultCallback implements DisplayHashResultCallback {
543         private static final int SCREENSHOT_WAIT_TIME_S = 1;
544         private DisplayHash mDisplayHash;
545         private int mError;
546         private CountDownLatch mCountDownLatch = new CountDownLatch(1);
547 
reset()548         public void reset() {
549             mCountDownLatch = new CountDownLatch(1);
550         }
551 
getDisplayHash()552         public DisplayHash getDisplayHash() {
553             try {
554                 mCountDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS);
555             } catch (Exception e) {
556             }
557             return mDisplayHash;
558         }
559 
getError()560         public int getError() {
561             try {
562                 mCountDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS);
563             } catch (Exception e) {
564             }
565             return mError;
566         }
567 
568         @Override
onDisplayHashResult(@onNull DisplayHash displayHash)569         public void onDisplayHashResult(@NonNull DisplayHash displayHash) {
570             mDisplayHash = displayHash;
571             mCountDownLatch.countDown();
572         }
573 
574         @Override
onDisplayHashError(int errorCode)575         public void onDisplayHashError(int errorCode) {
576             mError = errorCode;
577             mCountDownLatch.countDown();
578         }
579     }
580 
581     /**
582      * Waits for all activities to be resumed since image hash could be calculated before the
583      * activity is resumed.
584      */
waitForAllActivitiesResumed()585     private void waitForAllActivitiesResumed() {
586         mWmState.waitForWithAmState(WindowManagerState::allActivitiesResumed,
587                 "All activities should be resumed");
588     }
589 }
590