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 android.content.Context; 19 import android.graphics.Bitmap; 20 import android.graphics.Point; 21 import android.os.Handler; 22 import android.os.HandlerThread; 23 import android.os.Trace; 24 import android.renderscript.Allocation; 25 import android.renderscript.Element; 26 import android.renderscript.RenderScript; 27 import android.renderscript.Type; 28 import android.util.Log; 29 import android.util.SparseArray; 30 import android.view.Surface; 31 32 public class SurfacePixelValidator { 33 private static final String TAG = "SurfacePixelValidator"; 34 35 /** 36 * Observed that first few frames have errors with SurfaceView placement, so we skip for now. 37 * b/29603849 tracking that issue. 38 */ 39 private static final int NUM_FIRST_FRAMES_SKIPPED = 8; 40 41 // If no channel is greater than this value, pixel will be considered 'blackish'. 42 private static final short PIXEL_CHANNEL_THRESHOLD = 4; 43 44 private static final int MAX_CAPTURED_FAILURES = 5; 45 46 private final int mWidth; 47 private final int mHeight; 48 49 private final HandlerThread mWorkerThread; 50 private final Handler mWorkerHandler; 51 52 private final PixelChecker mPixelChecker; 53 54 private final RenderScript mRS; 55 56 private final Allocation mInPixelsAllocation; 57 private final ScriptC_PixelCounter mScript; 58 59 60 private final Object mResultLock = new Object(); 61 private int mResultSuccessFrames; 62 private int mResultFailureFrames; 63 private SparseArray<Bitmap> mFirstFailures = new SparseArray<>(MAX_CAPTURED_FAILURES); 64 65 private Runnable mConsumeRunnable = new Runnable() { 66 int numSkipped = 0; 67 @Override 68 public void run() { 69 Trace.beginSection("consume buffer"); 70 mInPixelsAllocation.ioReceive(); 71 Trace.endSection(); 72 73 Trace.beginSection("compare and sum"); 74 int blackishPixelCount = mScript.reduce_countBlackishPixels(mInPixelsAllocation).get(); 75 Trace.endSection(); 76 77 boolean success = mPixelChecker.checkPixels(blackishPixelCount, mWidth, mHeight); 78 synchronized (mResultLock) { 79 if (numSkipped < NUM_FIRST_FRAMES_SKIPPED) { 80 numSkipped++; 81 Log.d(TAG, "skipped frame nr " + numSkipped + ", success = " + success); 82 } else { 83 if (success) { 84 mResultSuccessFrames++; 85 } else { 86 mResultFailureFrames++; 87 int totalFramesSeen = mResultSuccessFrames + mResultFailureFrames; 88 Log.d(TAG, "Failure (pixel count = " + blackishPixelCount 89 + ") occurred on frame " + totalFramesSeen); 90 91 if (mFirstFailures.size() < MAX_CAPTURED_FAILURES) { 92 Log.d(TAG, "Capturing bitmap #" + mFirstFailures.size()); 93 // error, worth looking at... 94 Bitmap capture = Bitmap.createBitmap(mWidth, mHeight, 95 Bitmap.Config.ARGB_8888); 96 mInPixelsAllocation.copyTo(capture); 97 mFirstFailures.put(totalFramesSeen, capture); 98 } 99 } 100 } 101 } 102 } 103 }; 104 105 public SurfacePixelValidator(Context context, Point size, PixelChecker pixelChecker) { 106 mWidth = size.x; 107 mHeight = size.y; 108 109 mWorkerThread = new HandlerThread("SurfacePixelValidator"); 110 mWorkerThread.start(); 111 mWorkerHandler = new Handler(mWorkerThread.getLooper()); 112 113 mPixelChecker = pixelChecker; 114 115 mRS = RenderScript.create(context); 116 mScript = new ScriptC_PixelCounter(mRS); 117 118 mInPixelsAllocation = createBufferQueueAllocation(); 119 mScript.set_THRESHOLD(PIXEL_CHANNEL_THRESHOLD); 120 121 mInPixelsAllocation.setOnBufferAvailableListener( 122 allocation -> mWorkerHandler.post(mConsumeRunnable)); 123 } 124 125 public Surface getSurface() { 126 return mInPixelsAllocation.getSurface(); 127 } 128 129 private Allocation createBufferQueueAllocation() { 130 return Allocation.createAllocations(mRS, Type.createXY(mRS, 131 Element.RGBA_8888(mRS) 132 /*Element.U32(mRS)*/, mWidth, mHeight), 133 Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_INPUT, 134 1)[0]; 135 } 136 137 /** 138 * Shuts down processing pipeline, and returns current pass/fail counts. 139 * 140 * Wait for pipeline to flush before calling this method. If not, frames that are still in 141 * flight may be lost. 142 */ 143 public void finish(CapturedActivity.TestResult testResult) { 144 synchronized (mResultLock) { 145 // could in theory miss results still processing, but only if latency is extremely high. 146 // Caller should only call this 147 testResult.failFrames = mResultFailureFrames; 148 testResult.passFrames = mResultSuccessFrames; 149 150 for (int i = 0; i < mFirstFailures.size(); i++) { 151 testResult.failures.put(mFirstFailures.keyAt(i), mFirstFailures.valueAt(i)); 152 } 153 } 154 mWorkerThread.quitSafely(); 155 } 156 } 157