Home | History | Annotate | Download | only in surfacevalidator
      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 package android.view.cts.surfacevalidator;
     17 
     18 import static org.junit.Assert.assertTrue;
     19 import static org.junit.Assert.fail;
     20 
     21 import android.app.Activity;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.pm.PackageManager;
     25 import android.graphics.Bitmap;
     26 import android.graphics.Point;
     27 import android.hardware.display.DisplayManager;
     28 import android.hardware.display.VirtualDisplay;
     29 import android.media.MediaPlayer;
     30 import android.media.projection.MediaProjection;
     31 import android.media.projection.MediaProjectionManager;
     32 import android.os.Bundle;
     33 import android.os.Handler;
     34 import android.os.Looper;
     35 import android.support.test.InstrumentationRegistry;
     36 import android.support.test.uiautomator.By;
     37 import android.support.test.uiautomator.UiDevice;
     38 import android.support.test.uiautomator.UiObject2;
     39 import android.support.test.uiautomator.Until;
     40 import android.util.DisplayMetrics;
     41 import android.util.Log;
     42 import android.util.SparseArray;
     43 import android.view.Display;
     44 import android.view.PointerIcon;
     45 import android.view.View;
     46 import android.view.cts.R;
     47 import android.widget.FrameLayout;
     48 
     49 import java.util.concurrent.CountDownLatch;
     50 import java.util.concurrent.TimeUnit;
     51 
     52 
     53 public class CapturedActivity extends Activity {
     54     public static class TestResult {
     55         public int passFrames;
     56         public int failFrames;
     57         public final SparseArray<Bitmap> failures = new SparseArray<>();
     58     }
     59 
     60     private static final String TAG = "CapturedActivity";
     61     private static final int PERMISSION_CODE = 1;
     62     private MediaProjectionManager mProjectionManager;
     63     private MediaProjection mMediaProjection;
     64     private VirtualDisplay mVirtualDisplay;
     65 
     66     private SurfacePixelValidator mSurfacePixelValidator;
     67 
     68     private static final int PERMISSION_DIALOG_WAIT_MS = 1000;
     69     private static final int RETRY_COUNT = 2;
     70 
     71     private static final long START_CAPTURE_DELAY_MS = 4000;
     72 
     73     private static final String ACCEPT_RESOURCE_ID = "android:id/button1";
     74 
     75     private MediaPlayer mMediaPlayer;
     76 
     77     private final Handler mHandler = new Handler(Looper.getMainLooper());
     78     private volatile boolean mOnEmbedded;
     79     private volatile boolean mOnWatch;
     80     private CountDownLatch mCountDownLatch;
     81 
     82     @Override
     83     public void onCreate(Bundle savedInstanceState) {
     84         super.onCreate(savedInstanceState);
     85         final PackageManager packageManager = getPackageManager();
     86         mOnWatch = packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH);
     87         if (mOnWatch) {
     88             // Don't try and set up test/capture infrastructure - they're not supported
     89             return;
     90         }
     91         // Embedded devices are significantly slower, and are given
     92         // longer duration to capture the expected number of frames
     93         mOnEmbedded = packageManager.hasSystemFeature(PackageManager.FEATURE_EMBEDDED);
     94 
     95         getWindow().getDecorView().setSystemUiVisibility(
     96                 View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN);
     97         // Set the NULL pointer icon so that it won't obstruct the captured image.
     98         getWindow().getDecorView().setPointerIcon(
     99                 PointerIcon.getSystemIcon(this, PointerIcon.TYPE_NULL));
    100 
    101         mProjectionManager =
    102                 (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
    103 
    104         mCountDownLatch = new CountDownLatch(1);
    105         startActivityForResult(mProjectionManager.createScreenCaptureIntent(), PERMISSION_CODE);
    106 
    107         mMediaPlayer = MediaPlayer.create(this, R.raw.colors_video);
    108         mMediaPlayer.setLooping(true);
    109     }
    110 
    111     public void dismissPermissionDialog() {
    112         // The permission dialog will be auto-opened by the activity - find it and accept
    113         UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
    114         UiObject2 acceptButton = uiDevice.wait(Until.findObject(By.res(ACCEPT_RESOURCE_ID)),
    115                 PERMISSION_DIALOG_WAIT_MS);
    116         if (acceptButton != null) {
    117             Log.d(TAG, "found permission dialog after searching all windows, clicked");
    118             acceptButton.click();
    119         }
    120     }
    121 
    122     /**
    123      * MediaPlayer pre-loaded with a video with no black pixels. Be kind, rewind.
    124      */
    125     public MediaPlayer getMediaPlayer() {
    126         return mMediaPlayer;
    127     }
    128 
    129     @Override
    130     public void onDestroy() {
    131         super.onDestroy();
    132         Log.d(TAG, "onDestroy");
    133         if (mMediaProjection != null) {
    134             mMediaProjection.stop();
    135             mMediaProjection = null;
    136         }
    137     }
    138 
    139     @Override
    140     public void onActivityResult(int requestCode, int resultCode, Intent data) {
    141         if (mOnWatch) return;
    142         getWindow().getDecorView().setSystemUiVisibility(
    143                 View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN);
    144 
    145         if (requestCode != PERMISSION_CODE) {
    146             throw new IllegalStateException("Unknown request code: " + requestCode);
    147         }
    148         if (resultCode != RESULT_OK) {
    149             throw new IllegalStateException("User denied screen sharing permission");
    150         }
    151         Log.d(TAG, "onActivityResult");
    152         mMediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
    153         mMediaProjection.registerCallback(new MediaProjectionCallback(), null);
    154         mCountDownLatch.countDown();
    155     }
    156 
    157     public long getCaptureDurationMs() {
    158         return mOnEmbedded ? 100000 : 10000;
    159     }
    160 
    161     public TestResult runTest(AnimationTestCase animationTestCase) throws Throwable {
    162         TestResult testResult = new TestResult();
    163         if (mOnWatch) {
    164             /**
    165              * Watch devices not supported, since they may not support:
    166              *    1) displaying unmasked windows
    167              *    2) RenderScript
    168              *    3) Video playback
    169              */
    170             Log.d(TAG, "Skipping test on watch.");
    171             testResult.passFrames = 1000;
    172             testResult.failFrames = 0;
    173             return testResult;
    174         }
    175 
    176         final long timeOutMs = mOnEmbedded ? 125000 : 25000;
    177         final long endCaptureDelayMs = START_CAPTURE_DELAY_MS + getCaptureDurationMs();
    178         final long endDelayMs = endCaptureDelayMs + 1000;
    179 
    180         int count = 0;
    181         // Sometimes system decides to rotate the permission activity to another orientation
    182         // right after showing it. This results in: uiautomation thinks that accept button appears,
    183         // we successfully click it in terms of uiautomation, but nothing happens,
    184         // because permission activity is already recreated.
    185         // Thus, we try to click that button multiple times.
    186         do {
    187             assertTrue("Can't get the permission", count <= RETRY_COUNT);
    188             dismissPermissionDialog();
    189             count++;
    190         } while (!mCountDownLatch.await(timeOutMs, TimeUnit.MILLISECONDS));
    191 
    192         mHandler.post(() -> {
    193             Log.d(TAG, "Setting up test case");
    194 
    195             // shouldn't be necessary, since we've already done this in #create,
    196             // but ensure status/nav are hidden for test
    197             getWindow().getDecorView().setSystemUiVisibility(
    198                     View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN);
    199 
    200             animationTestCase.start(getApplicationContext(),
    201                     (FrameLayout) findViewById(android.R.id.content));
    202         });
    203 
    204         mHandler.postDelayed(() -> {
    205             Log.d(TAG, "Starting capture");
    206 
    207             Display display = getWindow().getDecorView().getDisplay();
    208             Point size = new Point();
    209             DisplayMetrics metrics = new DisplayMetrics();
    210             display.getRealSize(size);
    211             display.getMetrics(metrics);
    212 
    213 
    214             mSurfacePixelValidator = new SurfacePixelValidator(CapturedActivity.this,
    215                     size, animationTestCase.getChecker());
    216             Log.d("MediaProjection", "Size is " + size.toString());
    217             mVirtualDisplay = mMediaProjection.createVirtualDisplay("ScreenSharingDemo",
    218                     size.x, size.y,
    219                     metrics.densityDpi,
    220                     DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
    221                     mSurfacePixelValidator.getSurface(),
    222                     null /*Callbacks*/,
    223                     null /*Handler*/);
    224         }, START_CAPTURE_DELAY_MS);
    225 
    226         mHandler.postDelayed(() -> {
    227             Log.d(TAG, "Stopping capture");
    228             mVirtualDisplay.release();
    229             mVirtualDisplay = null;
    230         }, endCaptureDelayMs);
    231 
    232         final CountDownLatch latch = new CountDownLatch(1);
    233         mHandler.postDelayed(() -> {
    234             Log.d(TAG, "Ending test case");
    235             animationTestCase.end();
    236             mSurfacePixelValidator.finish(testResult);
    237             latch.countDown();
    238             mSurfacePixelValidator = null;
    239         }, endDelayMs);
    240 
    241         boolean latchResult = latch.await(timeOutMs, TimeUnit.MILLISECONDS);
    242         if (!latchResult) {
    243             testResult.passFrames = 0;
    244             testResult.failFrames = 1000;
    245         }
    246         Log.d(TAG, "Test finished, passFrames " + testResult.passFrames
    247                 + ", failFrames " + testResult.failFrames);
    248         return testResult;
    249     }
    250 
    251     private class MediaProjectionCallback extends MediaProjection.Callback {
    252         @Override
    253         public void onStop() {
    254             Log.d(TAG, "MediaProjectionCallback#onStop");
    255             if (mVirtualDisplay != null) {
    256                 mVirtualDisplay.release();
    257                 mVirtualDisplay = null;
    258             }
    259         }
    260     }
    261 }
    262