1 /*
2  * Copyright (C) 2023 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 
17 package com.android.server.wm;
18 
19 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
20 
21 import android.content.Context;
22 import android.graphics.Canvas;
23 import android.graphics.Color;
24 import android.os.Handler;
25 import android.os.HandlerThread;
26 import android.os.Looper;
27 import android.util.Log;
28 import android.view.SurfaceHolder;
29 import android.view.SurfaceView;
30 import android.view.ViewGroup;
31 import android.view.cts.surfacevalidator.ISurfaceValidatorTestCase;
32 import android.view.cts.surfacevalidator.PixelChecker;
33 import android.widget.FrameLayout;
34 import android.window.SurfaceSyncGroup;
35 
36 import androidx.annotation.NonNull;
37 
38 /**
39  * A validator class that will create a SurfaceView and then update its size over and over. The code
40  * will request to sync the SurfaceView content with the main window and validate that there was
41  * never an empty area (black color). The test uses {@link SurfaceSyncGroup} class to gather the
42  * content it wants to synchronize.
43  */
44 public class SurfaceViewSyncValidatorTestCase implements ISurfaceValidatorTestCase {
45     private static final String TAG = "SurfaceSyncGroupValidatorTestCase";
46 
47     private final Runnable mRunnable = new Runnable() {
48         @Override
49         public void run() {
50             updateSurfaceViewSize();
51             mHandler.postDelayed(this, 100);
52         }
53     };
54 
55     private Handler mHandler;
56     private SurfaceView mSurfaceView;
57     private boolean mLastExpanded = true;
58 
59     private RenderingThread mRenderingThread;
60     private FrameLayout mParent;
61 
62     private SurfaceSyncGroup mSyncGroup;
63 
64     final SurfaceHolder.Callback mCallback = new SurfaceHolder.Callback() {
65         @Override
66         public void surfaceCreated(@NonNull SurfaceHolder holder) {
67             final Canvas canvas = holder.lockCanvas();
68             canvas.drawARGB(255, 100, 100, 100);
69             holder.unlockCanvasAndPost(canvas);
70             Log.d(TAG, "surfaceCreated");
71             mRenderingThread = new RenderingThread(holder);
72             mRenderingThread.start();
73         }
74 
75         @Override
76         public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
77                 int height) {
78             if (mSyncGroup != null) {
79                 mSyncGroup.add(mSurfaceView, frameCallback ->
80                         mRenderingThread.setFrameCallback(frameCallback));
81                 mSyncGroup.markSyncReady();
82                 mSyncGroup = null;
83             }
84         }
85 
86         @Override
87         public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
88             mRenderingThread.stopRendering();
89         }
90     };
91 
92     @Override
getChecker()93     public PixelChecker getChecker() {
94         return new PixelChecker(Color.BLACK) {
95             @Override
96             public boolean checkPixels(int matchingPixelCount, int width, int height) {
97                 return matchingPixelCount == 0;
98             }
99         };
100     }
101 
102     @Override
103     public void start(Context context, FrameLayout parent) {
104         mSurfaceView = new SurfaceView(context);
105         mSurfaceView.getHolder().addCallback(mCallback);
106         mParent = parent;
107 
108         FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(MATCH_PARENT, 600);
109         parent.addView(mSurfaceView, layoutParams);
110         mHandler = new Handler(Looper.getMainLooper());
111         mHandler.post(mRunnable);
112     }
113 
114     @Override
115     public void end() {
116         mHandler.removeCallbacks(mRunnable);
117     }
118 
119     public void updateSurfaceViewSize() {
120         if (mRenderingThread == null || mSyncGroup != null || !mRenderingThread.isReadyToSync()) {
121             return;
122         }
123 
124         Log.d(TAG, "updateSurfaceViewSize");
125 
126         final int height;
127         if (mLastExpanded) {
128             height = 300;
129         } else {
130             height = 600;
131         }
132         mLastExpanded = !mLastExpanded;
133 
134         mRenderingThread.pauseRendering();
135         mSyncGroup = new SurfaceSyncGroup(TAG);
136         mSyncGroup.add(mParent.getRootSurfaceControl(), null /* runanble */);
137 
138         ViewGroup.LayoutParams svParams = mSurfaceView.getLayoutParams();
139         svParams.height = height;
140         mSurfaceView.setLayoutParams(svParams);
141     }
142 
143     private static class RenderingThread extends HandlerThread {
144         private final SurfaceHolder mSurfaceHolder;
145         private SurfaceSyncGroup.SurfaceViewFrameCallback mFrameCallback;
146         private boolean mPauseRendering;
147         private boolean mComplete;
148 
149         int mColorValue = 0;
150         int mColorDelta = 10;
151 
152         @Override
153         public void run() {
154             try {
155                 while (true) {
156                     sleep(10);
157                     synchronized (this) {
158                         if (mComplete) {
159                             break;
160                         }
161                         if (mPauseRendering) {
162                             continue;
163                         }
164 
165                         if (mFrameCallback != null) {
166                             Log.d(TAG, "onFrameStarted");
167                             mFrameCallback.onFrameStarted();
168                         }
169 
170                         mColorValue += mColorDelta;
171                         if (mColorValue > 245 || mColorValue < 10) {
172                             mColorDelta *= -1;
173                         }
174 
175                         Canvas c = mSurfaceHolder.lockCanvas();
176                         if (c != null) {
177                             c.drawRGB(255, mColorValue, 255 - mColorValue);
178                             mSurfaceHolder.unlockCanvasAndPost(c);
179                         }
180 
181                         mFrameCallback = null;
182                     }
183                 }
184             } catch (InterruptedException e) {
185             }
186         }
187 
188         RenderingThread(SurfaceHolder holder) {
189             super("RenderingThread");
190             mSurfaceHolder = holder;
191         }
192 
193         public void pauseRendering() {
194             synchronized (this) {
195                 mPauseRendering = true;
196             }
197         }
198 
199         private boolean isReadyToSync() {
200             synchronized (this) {
201                 return mFrameCallback == null;
202             }
203         }
204         public void setFrameCallback(SurfaceSyncGroup.SurfaceViewFrameCallback frameCallback) {
205             synchronized (this) {
206                 mFrameCallback = frameCallback;
207                 mPauseRendering = false;
208             }
209         }
210 
211         public void stopRendering() {
212             synchronized (this) {
213                 mComplete = true;
214             }
215         }
216     }
217 }
218