1 /*
2  * Copyright (C) 2016 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.fail;
20 
21 import android.app.Activity;
22 import android.content.Context;
23 import android.content.pm.ActivityInfo;
24 import android.graphics.Canvas;
25 import android.graphics.Color;
26 import android.graphics.Paint;
27 import android.graphics.Point;
28 import android.graphics.Rect;
29 import android.os.Bundle;
30 import android.util.Log;
31 import android.view.View;
32 import android.view.ViewTreeObserver.OnDrawListener;
33 import android.view.WindowInsets;
34 import android.view.cts.util.DisplayUtils;
35 import android.widget.FrameLayout;
36 
37 import java.util.concurrent.CountDownLatch;
38 import java.util.concurrent.TimeUnit;
39 
40 public class PixelCopyViewProducerActivity extends Activity implements OnDrawListener,
41         View.OnApplyWindowInsetsListener{
42     private static final int[] ORIENTATIONS = {
43             ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
44             ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
45             ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
46             ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE,
47     };
48     // TODO: Lower this (or remove it entirely) by leveraging things like
49     //  ViewTreeObserver#registerFrameCommitCallback (and possibly display orientation listeners?)
50     private static final int DRAW_FRAME_COUNT_BEFORE_CAPTURE = 10;
51     private int mCurrentOrientation = 0;
52     private View mContent;
53     private Rect mContentBounds = new Rect();
54     private Rect mOutsets = new Rect();
55     private CountDownLatch mFence = new CountDownLatch(DRAW_FRAME_COUNT_BEFORE_CAPTURE);
56     private boolean mSupportsRotation;
57 
58     @Override
onCreate(Bundle savedInstanceState)59     protected void onCreate(Bundle savedInstanceState) {
60         super.onCreate(savedInstanceState);
61 
62         // Check if the device supports both of portrait and landscape orientation screens.
63         mSupportsRotation = DisplayUtils.supportOrientationRequest(this);
64         if (mSupportsRotation) {
65             Log.d("PixelCopyTest", "Setting orientation index = " + mCurrentOrientation);
66             setRequestedOrientation(ORIENTATIONS[mCurrentOrientation]);
67         }
68 
69         mContent = new ColoredGrid(this);
70         setContentView(mContent);
71         View view = this.getWindow().getDecorView();
72         view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
73         mContent.getViewTreeObserver().addOnDrawListener(this);
74         mContent.setOnApplyWindowInsetsListener(this);
75     }
76 
77     @Override
onDraw()78     public void onDraw() {
79         final int requestedOrientation = ORIENTATIONS[mCurrentOrientation];
80         boolean isRequestingPortrait =
81                 requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
82                 || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
83         if (mSupportsRotation && (isRequestingPortrait != DisplayUtils.isDevicePortrait(this))) {
84             return;
85         }
86         mContent.post(() -> {
87             Point offset = new Point();
88             // We pass mContentBounds here just as a throwaway rect, we don't care about
89             // the visible rect just the global offset.
90             mContent.getGlobalVisibleRect(mContentBounds, offset);
91             mContentBounds.set(offset.x - mOutsets.left, offset.y - mOutsets.top,
92                     offset.x - mOutsets.left + mContent.getWidth(),
93                     offset.y - mOutsets.top + mContent.getHeight());
94             mFence.countDown();
95             if (mFence.getCount() > 0) {
96                 mContent.invalidate();
97             }
98         });
99     }
100 
101     @Override
onApplyWindowInsets(View v, WindowInsets in)102     public WindowInsets onApplyWindowInsets(View v, WindowInsets in) {
103         if (in.isRound()) {
104             FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mContent.getLayoutParams();
105             params.setMargins(in.getSystemWindowInsetLeft(), in.getSystemWindowInsetTop(),
106                     in.getSystemWindowInsetRight(), in.getSystemWindowInsetBottom());
107             mOutsets = new Rect(in.getSystemWindowInsetLeft(), in.getSystemWindowInsetTop(),
108                     in.getSystemWindowInsetRight(), in.getSystemWindowInsetBottom());
109             mContent.setLayoutParams(params);
110         }
111         return in;
112     }
113 
waitForFirstDrawCompleted(int timeout, TimeUnit unit)114     public void waitForFirstDrawCompleted(int timeout, TimeUnit unit) {
115         try {
116             if (!mFence.await(timeout, unit)) {
117                 fail("Timeout");
118             }
119         } catch (InterruptedException ex) {
120             fail(ex.getMessage());
121         }
122     }
123 
rotate()124     public boolean rotate() {
125         if (!mSupportsRotation) {
126             // Do not rotate the screen if it is not supported.
127             return false;
128         }
129         mFence = new CountDownLatch(DRAW_FRAME_COUNT_BEFORE_CAPTURE);
130         runOnUiThread(() -> {
131             mCurrentOrientation = (mCurrentOrientation + 1) % ORIENTATIONS.length;
132             Log.d("PixelCopyTest", "Setting orientation index = " + mCurrentOrientation);
133             setRequestedOrientation(ORIENTATIONS[mCurrentOrientation]);
134         });
135         waitForFirstDrawCompleted(10, TimeUnit.SECONDS);
136         return mCurrentOrientation != 0;
137     }
138 
139     // Convert a rect in normalized 0-100 dimensions to the bounds of the actual View.
normalizedToSurface(Rect inOut)140     public void normalizedToSurface(Rect inOut) {
141         float sx = mContentBounds.width() / 100.0f;
142         float sy = mContentBounds.height() / 100.0f;
143         inOut.left = (int) (inOut.left * sx);
144         inOut.top = (int) (inOut.top * sy);
145         inOut.right = (int) (inOut.right * sx + 0.5f);
146         inOut.bottom = (int) (inOut.bottom * sy + 0.5f);
147         inOut.offset(mContentBounds.left, mContentBounds.top);
148     }
149 
150     private static final class ColoredGrid extends View {
151         private Paint mPaint = new Paint();
152         private Rect mRect = new Rect();
153 
ColoredGrid(Context context)154         ColoredGrid(Context context) {
155             super(context);
156             setWillNotDraw(false);
157         }
158 
159         @Override
onDraw(Canvas canvas)160         protected void onDraw(Canvas canvas) {
161             int cx = getWidth() / 2;
162             int cy = getHeight() / 2;
163             final int BORDER_WIDTH = 2;
164 
165             canvas.drawColor(Color.YELLOW);
166 
167             mRect.set(BORDER_WIDTH, BORDER_WIDTH, cx, cy);
168             mPaint.setColor(Color.RED);
169             canvas.drawRect(mRect, mPaint);
170 
171             mRect.set(cx, BORDER_WIDTH, getWidth() - BORDER_WIDTH, cy);
172             mPaint.setColor(Color.GREEN);
173             canvas.drawRect(mRect, mPaint);
174 
175             mRect.set(BORDER_WIDTH, cy, cx, getHeight() - BORDER_WIDTH);
176             mPaint.setColor(Color.BLUE);
177             canvas.drawRect(mRect, mPaint);
178 
179             mRect.set(cx, cy, getWidth() - BORDER_WIDTH, getHeight() - BORDER_WIDTH);
180             mPaint.setColor(Color.BLACK);
181             canvas.drawRect(mRect, mPaint);
182         }
183     }
184 }
185