blob: d65b80ae0700c621527eac6ec87b96ec7a297a58 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.wm;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.ViewGroup;
import android.view.cts.surfacevalidator.ISurfaceValidatorTestCase;
import android.view.cts.surfacevalidator.PixelChecker;
import android.widget.FrameLayout;
import android.window.SurfaceSyncer;
import androidx.annotation.NonNull;
/**
* A validator class that will create a SurfaceView and then update its size over and over. The code
* will request to sync the SurfaceView content with the main window and validate that there was
* never an empty area (black color). The test uses {@link SurfaceSyncer} class to gather the
* content it wants to synchronize.
*/
public class SurfaceSyncerValidatorTestCase implements ISurfaceValidatorTestCase {
private static final String TAG = "SurfaceSyncerValidatorTestCase";
private final Runnable mRunnable = new Runnable() {
@Override
public void run() {
updateSurfaceViewSize();
mHandler.postDelayed(this, 100);
}
};
private Handler mHandler;
private SurfaceView mSurfaceView;
private boolean mLastExpanded = true;
private final SurfaceSyncer mSurfaceSyncer = new SurfaceSyncer();
private RenderingThread mRenderingThread;
private FrameLayout mParent;
private int mLastSyncId = -1;
final SurfaceHolder.Callback mCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
final Canvas canvas = holder.lockCanvas();
canvas.drawARGB(255, 100, 100, 100);
holder.unlockCanvasAndPost(canvas);
Log.d(TAG, "surfaceCreated");
mRenderingThread = new RenderingThread(holder);
mRenderingThread.start();
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
int height) {
if (mLastSyncId >= 0) {
mSurfaceSyncer.addToSync(mLastSyncId, mSurfaceView, frameCallback ->
mRenderingThread.setFrameCallback(frameCallback));
mSurfaceSyncer.markSyncReady(mLastSyncId);
mLastSyncId = -1;
}
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
mRenderingThread.stopRendering();
}
};
@Override
public PixelChecker getChecker() {
return new PixelChecker(Color.BLACK) {
@Override
public boolean checkPixels(int matchingPixelCount, int width, int height) {
return matchingPixelCount == 0;
}
};
}
@Override
public void start(Context context, FrameLayout parent) {
mSurfaceView = new SurfaceView(context);
mSurfaceView.getHolder().addCallback(mCallback);
mParent = parent;
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(MATCH_PARENT, 600);
parent.addView(mSurfaceView, layoutParams);
mHandler = new Handler(Looper.getMainLooper());
mHandler.post(mRunnable);
}
@Override
public void end() {
mHandler.removeCallbacks(mRunnable);
}
public void updateSurfaceViewSize() {
if (mRenderingThread == null || mLastSyncId >= 0 || !mRenderingThread.isReadyToSync()) {
return;
}
Log.d(TAG, "updateSurfaceViewSize");
final int height;
if (mLastExpanded) {
height = 300;
} else {
height = 600;
}
mLastExpanded = !mLastExpanded;
mRenderingThread.pauseRendering();
mLastSyncId = mSurfaceSyncer.setupSync(() -> { });
mSurfaceSyncer.addToSync(mLastSyncId, mParent);
ViewGroup.LayoutParams svParams = mSurfaceView.getLayoutParams();
svParams.height = height;
mSurfaceView.setLayoutParams(svParams);
}
private static class RenderingThread extends HandlerThread {
private final SurfaceHolder mSurfaceHolder;
private SurfaceSyncer.SurfaceViewFrameCallback mFrameCallback;
private boolean mPauseRendering;
private boolean mComplete;
int mColorValue = 0;
int mColorDelta = 10;
@Override
public void run() {
try {
while (true) {
sleep(10);
synchronized (this) {
if (mComplete) {
break;
}
if (mPauseRendering) {
continue;
}
if (mFrameCallback != null) {
Log.d(TAG, "onFrameStarted");
mFrameCallback.onFrameStarted();
}
mColorValue += mColorDelta;
if (mColorValue > 245 || mColorValue < 10) {
mColorDelta *= -1;
}
Canvas c = mSurfaceHolder.lockCanvas();
if (c != null) {
c.drawRGB(255, mColorValue, 255 - mColorValue);
mSurfaceHolder.unlockCanvasAndPost(c);
}
mFrameCallback = null;
}
}
} catch (InterruptedException e) {
}
}
RenderingThread(SurfaceHolder holder) {
super("RenderingThread");
mSurfaceHolder = holder;
}
public void pauseRendering() {
synchronized (this) {
mPauseRendering = true;
}
}
private boolean isReadyToSync() {
synchronized (this) {
return mFrameCallback == null;
}
}
public void setFrameCallback(SurfaceSyncer.SurfaceViewFrameCallback frameCallback) {
synchronized (this) {
mFrameCallback = frameCallback;
mPauseRendering = false;
}
}
public void stopRendering() {
synchronized (this) {
mComplete = true;
}
}
}
}