1 /*
2  * Copyright (C) 2014 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 package android.uirendering.cts.testinfrastructure;
17 
18 import static org.junit.Assert.fail;
19 
20 import android.app.Activity;
21 import android.content.res.Configuration;
22 import android.graphics.Point;
23 import android.os.Bundle;
24 import android.os.Handler;
25 import android.os.HandlerThread;
26 import android.os.Message;
27 import android.support.annotation.Nullable;
28 import android.uirendering.cts.R;
29 import android.util.Log;
30 import android.view.FrameMetrics;
31 import android.view.View;
32 import android.view.ViewGroup;
33 import android.view.ViewStub;
34 import android.view.ViewTreeObserver;
35 import android.view.Window;
36 
37 import java.util.concurrent.CountDownLatch;
38 import java.util.concurrent.TimeUnit;
39 
40 /**
41  * A generic activity that uses a view specified by the user.
42  */
43 public class DrawActivity extends Activity {
44     private final static long TIME_OUT_MS = 10000;
45     private final Point mLock = new Point();
46 
47     private Handler mHandler;
48     private View mView;
49     private View mViewWrapper;
50     private boolean mOnTv;
51 
onCreate(Bundle bundle)52     public void onCreate(Bundle bundle){
53         super.onCreate(bundle);
54         getWindow().getDecorView().setSystemUiVisibility(
55                 View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN);
56         mHandler = new RenderSpecHandler();
57         int uiMode = getResources().getConfiguration().uiMode;
58         mOnTv = (uiMode & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION;
59 
60         // log frame metrics
61         HandlerThread handlerThread = new HandlerThread("FrameMetrics");
62         handlerThread.start();
63         getWindow().addOnFrameMetricsAvailableListener(
64                 new Window.OnFrameMetricsAvailableListener() {
65                     int mRtFrameCount = 0;
66                     @Override
67                     public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics,
68                             int dropCountSinceLastInvocation) {
69                         Log.d("UiRendering", "Window frame count " + mRtFrameCount
70                                 + ", frame drops " + dropCountSinceLastInvocation);
71                         mRtFrameCount++;
72                     }
73                 }, new Handler(handlerThread.getLooper()));
74 
75         // log draw metrics
76         View view = new View(this);
77         setContentView(view);
78         view.getViewTreeObserver().addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
79             int mFrameCount;
80             @Override
81             public void onDraw() {
82                 Log.d("UiRendering", "View tree frame count " + mFrameCount);
83                 mFrameCount++;
84             }
85         });
86     }
87 
getOnTv()88     public boolean getOnTv() {
89         return mOnTv;
90     }
91 
enqueueRenderSpecAndWait(int layoutId, CanvasClient canvasClient, @Nullable ViewInitializer viewInitializer, boolean useHardware, boolean usePicture)92     public Point enqueueRenderSpecAndWait(int layoutId, CanvasClient canvasClient,
93             @Nullable ViewInitializer viewInitializer, boolean useHardware, boolean usePicture) {
94         ((RenderSpecHandler) mHandler).setViewInitializer(viewInitializer);
95         int arg2 = (useHardware ? View.LAYER_TYPE_NONE : View.LAYER_TYPE_SOFTWARE);
96         Point point = new Point();
97         synchronized (mLock) {
98             if (canvasClient != null) {
99                 mHandler.obtainMessage(RenderSpecHandler.CANVAS_MSG, usePicture ? 1 : 0,
100                         arg2, canvasClient).sendToTarget();
101             } else {
102                 mHandler.obtainMessage(RenderSpecHandler.LAYOUT_MSG, layoutId, arg2).sendToTarget();
103             }
104 
105             try {
106                 mLock.wait(TIME_OUT_MS);
107                 point.set(mLock.x, mLock.y);
108             } catch (InterruptedException e) {
109                 e.printStackTrace();
110             }
111         }
112         return point;
113     }
114 
reset()115     public void reset() {
116         CountDownLatch fence = new CountDownLatch(1);
117         mHandler.obtainMessage(RenderSpecHandler.RESET_MSG, fence).sendToTarget();
118         try {
119             if (!fence.await(10, TimeUnit.SECONDS)) {
120                 fail("Timeout exception");
121             }
122         } catch (InterruptedException ex) {
123             fail(ex.getMessage());
124         }
125     }
126 
127     private ViewInitializer mViewInitializer;
128 
notifyOnDrawCompleted()129     private void notifyOnDrawCompleted() {
130         DrawCounterListener onDrawListener = new DrawCounterListener();
131         mView.getViewTreeObserver().addOnDrawListener(onDrawListener);
132         mView.invalidate();
133     }
134 
135     private class RenderSpecHandler extends Handler {
136         public static final int RESET_MSG = 0;
137         public static final int LAYOUT_MSG = 1;
138         public static final int CANVAS_MSG = 2;
139 
140 
setViewInitializer(ViewInitializer viewInitializer)141         public void setViewInitializer(ViewInitializer viewInitializer) {
142             mViewInitializer = viewInitializer;
143         }
144 
handleMessage(Message message)145         public void handleMessage(Message message) {
146             Log.d("UiRendering", "message of type " + message.what);
147             if (message.what == RESET_MSG) {
148                 ((ViewGroup)findViewById(android.R.id.content)).removeAllViews();
149                 ((CountDownLatch)message.obj).countDown();
150                 return;
151             }
152             setContentView(R.layout.test_container);
153             ViewStub stub = (ViewStub) findViewById(R.id.test_content_stub);
154             mViewWrapper = findViewById(R.id.test_content_wrapper);
155             switch (message.what) {
156                 case LAYOUT_MSG: {
157                     stub.setLayoutResource(message.arg1);
158                     mView = stub.inflate();
159                 } break;
160 
161                 case CANVAS_MSG: {
162                     stub.setLayoutResource(R.layout.test_content_canvasclientview);
163                     mView = stub.inflate();
164                     ((CanvasClientView) mView).setCanvasClient((CanvasClient) (message.obj));
165                     if (message.arg1 != 0) {
166                         ((CanvasClientView) mView).setUsePicture(true);
167                     }
168                 } break;
169             }
170 
171             if (mView == null) {
172                 throw new IllegalStateException("failed to inflate test content");
173             }
174 
175             if (mViewInitializer != null) {
176                 mViewInitializer.initializeView(mView);
177             }
178 
179             // set layer on wrapper parent of view, so view initializer
180             // can control layer type of View under test.
181             mViewWrapper.setLayerType(message.arg2, null);
182 
183             notifyOnDrawCompleted();
184         }
185     }
186 
187     @Override
onPause()188     protected void onPause() {
189         super.onPause();
190         if (mViewInitializer != null) {
191             mViewInitializer.teardownView();
192         }
193     }
194 
195     @Override
finish()196     public void finish() {
197         // Ignore
198     }
199 
200     /** Call this when all the tests that use this activity have completed.
201      * This will then clean up any internal state and finish the activity. */
allTestsFinished()202     public void allTestsFinished() {
203         super.finish();
204     }
205 
206     private class DrawCounterListener implements ViewTreeObserver.OnDrawListener {
207         private final int[] mLocationOnScreen = new int[2];
208         private static final int DEBUG_REQUIRE_EXTRA_FRAMES = 1;
209         private int mDrawCount = 0;
210 
211         @Override
onDraw()212         public void onDraw() {
213             if (++mDrawCount <= DEBUG_REQUIRE_EXTRA_FRAMES) {
214                 mView.postInvalidate();
215                 return;
216             }
217             mView.post(() -> {
218                 Log.d("UiRendering", "notifying capture");
219                 mView.getViewTreeObserver().removeOnDrawListener(this);
220                 synchronized (mLock) {
221                     mViewWrapper.getLocationOnScreen(mLocationOnScreen);
222                     mLock.set(mLocationOnScreen[0], mLocationOnScreen[1]);
223                     mLock.notify();
224                 }
225             });
226         }
227     }
228 }
229