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