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