Home | History | Annotate | Download | only in testinfrastructure
      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.assertNotNull;
     19 import static org.junit.Assert.fail;
     20 
     21 import android.app.Activity;
     22 import android.content.pm.ActivityInfo;
     23 import android.content.res.Configuration;
     24 import android.graphics.Point;
     25 import android.os.Bundle;
     26 import android.os.Handler;
     27 import android.os.Message;
     28 import androidx.annotation.Nullable;
     29 import android.uirendering.cts.R;
     30 import android.util.Log;
     31 import android.util.Pair;
     32 import android.view.FrameMetrics;
     33 import android.view.View;
     34 import android.view.ViewGroup;
     35 import android.view.ViewStub;
     36 import android.view.ViewTreeObserver;
     37 import android.view.Window;
     38 
     39 import java.util.ArrayList;
     40 import java.util.Iterator;
     41 import java.util.concurrent.CountDownLatch;
     42 import java.util.concurrent.TimeUnit;
     43 
     44 /**
     45  * A generic activity that uses a view specified by the user.
     46  */
     47 public class DrawActivity extends Activity {
     48     static final String EXTRA_WIDE_COLOR_GAMUT = "DrawActivity.WIDE_COLOR_GAMUT";
     49 
     50     private final static long TIME_OUT_MS = 10000;
     51     private final Object mLock = new Object();
     52     private ActivityTestBase.TestPositionInfo mPositionInfo;
     53 
     54     private Handler mHandler;
     55     private View mView;
     56     private View mViewWrapper;
     57     private boolean mOnTv;
     58     private DrawMonitor mDrawMonitor;
     59 
     60     public void onCreate(Bundle bundle){
     61         super.onCreate(bundle);
     62         getWindow().getDecorView().setSystemUiVisibility(
     63                 View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN);
     64         if (getIntent().getBooleanExtra(EXTRA_WIDE_COLOR_GAMUT, false)) {
     65             getWindow().setColorMode(ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT);
     66         }
     67         mHandler = new RenderSpecHandler();
     68         int uiMode = getResources().getConfiguration().uiMode;
     69         mOnTv = (uiMode & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION;
     70         mDrawMonitor = new DrawMonitor(getWindow());
     71     }
     72 
     73     public boolean getOnTv() {
     74         return mOnTv;
     75     }
     76 
     77     public ActivityTestBase.TestPositionInfo enqueueRenderSpecAndWait(int layoutId,
     78             CanvasClient canvasClient, @Nullable ViewInitializer viewInitializer,
     79             boolean useHardware, boolean usePicture) {
     80         ((RenderSpecHandler) mHandler).setViewInitializer(viewInitializer);
     81         int arg2 = (useHardware ? View.LAYER_TYPE_NONE : View.LAYER_TYPE_SOFTWARE);
     82         synchronized (mLock) {
     83             if (canvasClient != null) {
     84                 mHandler.obtainMessage(RenderSpecHandler.CANVAS_MSG, usePicture ? 1 : 0,
     85                         arg2, canvasClient).sendToTarget();
     86             } else {
     87                 mHandler.obtainMessage(RenderSpecHandler.LAYOUT_MSG, layoutId, arg2).sendToTarget();
     88             }
     89 
     90             try {
     91                 mLock.wait(TIME_OUT_MS);
     92             } catch (InterruptedException e) {
     93                 e.printStackTrace();
     94             }
     95         }
     96         assertNotNull("Timeout waiting for draw", mPositionInfo);
     97         return mPositionInfo;
     98     }
     99 
    100     public void reset() {
    101         CountDownLatch fence = new CountDownLatch(1);
    102         mHandler.obtainMessage(RenderSpecHandler.RESET_MSG, fence).sendToTarget();
    103         try {
    104             if (!fence.await(10, TimeUnit.SECONDS)) {
    105                 fail("Timeout exception");
    106             }
    107         } catch (InterruptedException ex) {
    108             fail(ex.getMessage());
    109         }
    110     }
    111 
    112     private ViewInitializer mViewInitializer;
    113 
    114     private void notifyOnDrawCompleted() {
    115         DrawCounterListener onDrawListener = new DrawCounterListener();
    116         mView.getViewTreeObserver().addOnDrawListener(onDrawListener);
    117         mView.invalidate();
    118     }
    119 
    120     private class RenderSpecHandler extends Handler {
    121         public static final int RESET_MSG = 0;
    122         public static final int LAYOUT_MSG = 1;
    123         public static final int CANVAS_MSG = 2;
    124 
    125 
    126         public void setViewInitializer(ViewInitializer viewInitializer) {
    127             mViewInitializer = viewInitializer;
    128         }
    129 
    130         public void handleMessage(Message message) {
    131             Log.d("UiRendering", "message of type " + message.what);
    132             if (message.what == RESET_MSG) {
    133                 ((ViewGroup)findViewById(android.R.id.content)).removeAllViews();
    134                 ((CountDownLatch)message.obj).countDown();
    135                 return;
    136             }
    137             setContentView(R.layout.test_container);
    138             ViewStub stub = (ViewStub) findViewById(R.id.test_content_stub);
    139             mViewWrapper = findViewById(R.id.test_content_wrapper);
    140             switch (message.what) {
    141                 case LAYOUT_MSG: {
    142                     stub.setLayoutResource(message.arg1);
    143                     mView = stub.inflate();
    144                 } break;
    145 
    146                 case CANVAS_MSG: {
    147                     stub.setLayoutResource(R.layout.test_content_canvasclientview);
    148                     mView = stub.inflate();
    149                     ((CanvasClientView) mView).setCanvasClient((CanvasClient) (message.obj));
    150                     if (message.arg1 != 0) {
    151                         ((CanvasClientView) mView).setUsePicture(true);
    152                     }
    153                 } break;
    154             }
    155 
    156             if (mView == null) {
    157                 throw new IllegalStateException("failed to inflate test content");
    158             }
    159 
    160             if (mViewInitializer != null) {
    161                 mViewInitializer.initializeView(mView);
    162             }
    163 
    164             // set layer on wrapper parent of view, so view initializer
    165             // can control layer type of View under test.
    166             mViewWrapper.setLayerType(message.arg2, null);
    167 
    168             notifyOnDrawCompleted();
    169         }
    170     }
    171 
    172     @Override
    173     protected void onPause() {
    174         super.onPause();
    175         if (mViewInitializer != null) {
    176             mViewInitializer.teardownView();
    177         }
    178     }
    179 
    180     @Override
    181     public void finish() {
    182         // Ignore
    183     }
    184 
    185     /** Call this when all the tests that use this activity have completed.
    186      * This will then clean up any internal state and finish the activity. */
    187     public void allTestsFinished() {
    188         super.finish();
    189     }
    190 
    191     private class DrawCounterListener implements ViewTreeObserver.OnDrawListener {
    192         private static final int DEBUG_REQUIRE_EXTRA_FRAMES = 1;
    193         private int mDrawCount = 0;
    194 
    195         @Override
    196         public void onDraw() {
    197             if (++mDrawCount <= DEBUG_REQUIRE_EXTRA_FRAMES) {
    198                 mView.postInvalidate();
    199                 return;
    200             }
    201 
    202             long vsyncMillis = mView.getDrawingTime();
    203 
    204             mView.post(() -> mView.getViewTreeObserver().removeOnDrawListener(this));
    205 
    206             mDrawMonitor.notifyWhenDrawn(vsyncMillis, () -> {
    207                 final int[] location = new int[2];
    208                 mViewWrapper.getLocationInWindow(location);
    209                 Point surfaceOffset = new Point(location[0], location[1]);
    210                 mViewWrapper.getLocationOnScreen(location);
    211                 Point screenOffset = new Point(location[0], location[1]);
    212                 synchronized (mLock) {
    213                     mPositionInfo = new ActivityTestBase.TestPositionInfo(
    214                             surfaceOffset, screenOffset);
    215                     mLock.notify();
    216                 }
    217             });
    218         }
    219     }
    220 
    221     private static class DrawMonitor {
    222 
    223         private ArrayList<Pair<Long, Runnable>> mListeners = new ArrayList<>();
    224 
    225         private DrawMonitor(Window window) {
    226             window.addOnFrameMetricsAvailableListener(this::onFrameMetricsAvailable,
    227                     new Handler());
    228         }
    229 
    230         private void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics,
    231                 /* This isn't actually unused as it's necessary for the method handle */
    232                 @SuppressWarnings("unused") int dropCountSinceLastInvocation) {
    233             ArrayList<Runnable> toInvoke = null;
    234             synchronized (mListeners) {
    235                 if (mListeners.size() == 0) {
    236                     return;
    237                 }
    238 
    239                 long vsyncAtMillis = TimeUnit.NANOSECONDS.convert(frameMetrics
    240                         .getMetric(FrameMetrics.VSYNC_TIMESTAMP), TimeUnit.MILLISECONDS);
    241                 boolean isUiThreadDraw = frameMetrics.getMetric(FrameMetrics.DRAW_DURATION) > 0;
    242 
    243                 Iterator<Pair<Long, Runnable>> iter = mListeners.iterator();
    244                 while (iter.hasNext()) {
    245                     Pair<Long, Runnable> listener = iter.next();
    246                     if ((listener.first == vsyncAtMillis && isUiThreadDraw)
    247                             || (listener.first < vsyncAtMillis)) {
    248                         if (toInvoke == null) {
    249                             toInvoke = new ArrayList<>();
    250                         }
    251                         Log.d("UiRendering", "adding listener for vsync " + listener.first
    252                                 + "; got vsync timestamp: " + vsyncAtMillis
    253                                 + " with isUiThreadDraw " + isUiThreadDraw);
    254                         toInvoke.add(listener.second);
    255                         iter.remove();
    256                     } else if (listener.first == vsyncAtMillis && !isUiThreadDraw) {
    257                         Log.d("UiRendering",
    258                                 "Ignoring draw that's not from the UI thread at vsync: "
    259                                         + vsyncAtMillis);
    260                     }
    261                 }
    262             }
    263 
    264             if (toInvoke != null && toInvoke.size() > 0) {
    265                 for (Runnable run : toInvoke) {
    266                     run.run();
    267                 }
    268             }
    269 
    270         }
    271 
    272         public void notifyWhenDrawn(long contentVsyncMillis, Runnable runWhenDrawn) {
    273             synchronized (mListeners) {
    274                 mListeners.add(new Pair<>(contentVsyncMillis, runWhenDrawn));
    275             }
    276         }
    277     }
    278 }
    279