1 /*
2  * Copyright (C) 2019 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.view.cts;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertTrue;
21 
22 import android.graphics.Bitmap;
23 import android.graphics.Color;
24 import android.graphics.Matrix;
25 import android.graphics.Rect;
26 import android.os.Handler;
27 import android.os.Looper;
28 import android.view.PixelCopy;
29 import android.view.View;
30 import android.view.ViewGroup;
31 
32 import androidx.test.InstrumentationRegistry;
33 import androidx.test.annotation.UiThreadTest;
34 import androidx.test.filters.SmallTest;
35 import androidx.test.rule.ActivityTestRule;
36 import androidx.test.runner.AndroidJUnit4;
37 
38 import org.junit.Rule;
39 import org.junit.Test;
40 import org.junit.runner.RunWith;
41 
42 import java.util.concurrent.CountDownLatch;
43 import java.util.concurrent.TimeUnit;
44 
45 @SmallTest
46 @RunWith(AndroidJUnit4.class)
47 public class ViewAnimationMatrixTest {
48 
49     @Rule
50     public ActivityTestRule<ViewAnimationMatrixActivity> mRule =
51             new ActivityTestRule<>(ViewAnimationMatrixActivity.class, false, false);
52 
53     @UiThreadTest
54     @Test
testAnimationMatrixGetter()55     public void testAnimationMatrixGetter() {
56         View view = new View(InstrumentationRegistry.getTargetContext());
57         Matrix matrix = new Matrix();
58         matrix.setTranslate(34, 65);
59         matrix.setRotate(45);
60         view.setAnimationMatrix(matrix);
61 
62         assertEquals(matrix, view.getAnimationMatrix());
63     }
64 
65     @Test
testAnimationMatrixAppliedDuringDrawing()66     public void testAnimationMatrixAppliedDuringDrawing() throws Throwable {
67         ViewAnimationMatrixActivity activity = mRule.launchActivity(null);
68         final View view = activity.mView;
69         final View root = activity.mRoot;
70 
71         // view has some offset and rotation originally
72         mRule.runOnUiThread(() -> view.setAnimationMatrix(moveToTopLeftCorner(view)));
73         // now it should be in the top left corner of the parent
74 
75         waitForDraw(root);
76         Bitmap bitmap = captureView(root, view.getWidth(), view.getHeight());
77 
78         assertAllPixelsAre(Color.BLACK, bitmap);
79     }
80 
81     @Test
testAnimationMatrixClearedWithPassingNull()82     public void testAnimationMatrixClearedWithPassingNull() throws Throwable {
83         ViewAnimationMatrixActivity activity = mRule.launchActivity(null);
84         final View view = activity.mView;
85         final View root = activity.mRoot;
86 
87         mRule.runOnUiThread(() -> view.setAnimationMatrix(moveToTopLeftCorner(view)));
88         mRule.runOnUiThread(() -> view.setAnimationMatrix(null));
89 
90         waitForDraw(root);
91         Bitmap bitmap = captureView(root, view.getWidth(), view.getHeight());
92 
93         // view should again be drawn with the original offset, so our target rect is empty
94         assertAllPixelsAre(Color.WHITE, bitmap);
95     }
96 
moveToTopLeftCorner(View view)97     private Matrix moveToTopLeftCorner(View view) {
98         final ViewGroup.MarginLayoutParams lp =
99                 (ViewGroup.MarginLayoutParams) view.getLayoutParams();
100         Matrix matrix = new Matrix();
101         matrix.setRotate(-view.getRotation(), lp.width / 2f, lp.height / 2f);
102         matrix.postTranslate(-lp.leftMargin, 0);
103         return matrix;
104     }
105 
captureView(final View view, int width, int height)106     private Bitmap captureView(final View view, int width, int height) throws Throwable {
107         final Bitmap dest = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
108         final CountDownLatch latch = new CountDownLatch(1);
109         int[] offset = new int[2];
110         view.getLocationInWindow(offset);
111         Rect srcRect = new Rect(0, 0, width, height);
112         srcRect.offset(offset[0], offset[1]);
113         PixelCopy.OnPixelCopyFinishedListener onCopyFinished =
114                 copyResult -> {
115                     assertEquals(PixelCopy.SUCCESS, copyResult);
116                     latch.countDown();
117                 };
118         PixelCopy.request(mRule.getActivity().getWindow(), srcRect, dest, onCopyFinished,
119                 new Handler(Looper.getMainLooper()));
120         assertTrue(latch.await(1, TimeUnit.SECONDS));
121         return dest;
122     }
123 
waitForDraw(final View view)124     private void waitForDraw(final View view) throws Throwable {
125         final CountDownLatch latch = new CountDownLatch(1);
126         mRule.runOnUiThread(() -> {
127             view.getViewTreeObserver().registerFrameCommitCallback(latch::countDown);
128             view.invalidate();
129         });
130         assertTrue(latch.await(1, TimeUnit.SECONDS));
131     }
132 
assertAllPixelsAre(int color, Bitmap bitmap)133     private void assertAllPixelsAre(int color, Bitmap bitmap) {
134         // skipping a few border pixels in case of antialiazing
135         for (int i = 2; i < bitmap.getWidth() - 2; i++) {
136             for (int j = 2; j < bitmap.getHeight() - 2; j++) {
137                 assertEquals(color, bitmap.getPixel(i, j));
138             }
139         }
140     }
141 
142 }
143