1 /* 2 * Copyright 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 package android.view.cts; 17 18 import static org.junit.Assert.assertTrue; 19 20 import android.app.Activity; 21 import android.graphics.Canvas; 22 import android.graphics.Paint; 23 import android.os.Bundle; 24 import android.os.Handler; 25 import android.os.Looper; 26 import android.util.Log; 27 import android.view.Choreographer; 28 import android.view.PointerIcon; 29 import android.view.Surface; 30 import android.view.SurfaceHolder; 31 import android.view.SurfaceView; 32 import android.view.View; 33 import android.view.WindowManager; 34 import android.widget.FrameLayout; 35 import android.widget.LinearLayout; 36 37 import androidx.annotation.NonNull; 38 39 import java.util.Objects; 40 import java.util.concurrent.CountDownLatch; 41 import java.util.concurrent.TimeUnit; 42 43 /** 44 * Test {@link Activity} that repeatedly creates tons of {@link Surface Surfaces} to put memory 45 * pressure on the GC. 46 */ 47 public class SurfaceOOMTestActivity extends Activity implements Choreographer.FrameCallback, 48 Choreographer.VsyncCallback { 49 private static final String TAG = SurfaceOOMTestActivity.class.getSimpleName(); 50 51 private Choreographer mChoreographer; 52 53 private FrameLayout mParent; 54 private SurfaceView mSurfaceView; 55 private Surface mSurface; 56 private Paint mPaint; 57 58 private CountDownLatch mReadyToStart = new CountDownLatch(1); 59 private Handler mHandler; 60 61 // Test State: 62 private boolean mHasRendered; 63 private CountDownLatch mFence; 64 private int mCurrentRunWidth; 65 private int mCurrentRunHeight; 66 private long mNumSurfacesToCreate; 67 private long mRemainingSurfacesToCreate; 68 69 @Override onEnterAnimationComplete()70 public void onEnterAnimationComplete() { 71 mReadyToStart.countDown(); 72 } 73 74 @Override onCreate(Bundle savedInstanceState)75 public void onCreate(Bundle savedInstanceState) { 76 super.onCreate(savedInstanceState); 77 78 final View decorView = getWindow().getDecorView(); 79 decorView.setSystemUiVisibility( 80 View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN); 81 decorView.setPointerIcon( 82 PointerIcon.getSystemIcon(this, PointerIcon.TYPE_NULL)); 83 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 84 85 mPaint = new Paint(); 86 mPaint.setColor(0xFF00FF); 87 88 mParent = findViewById(android.R.id.content); 89 mChoreographer = Choreographer.getInstance(); 90 mHandler = new Handler(Objects.requireNonNull(Looper.myLooper())); 91 } 92 93 /** 94 * Schedules a loop of surface creation / destruction on the activity's main thread. 95 * 96 * A fence is created and waited on in the calling thread, and control will return once that 97 * fence is triggered. 98 */ verifyCreatingManySurfaces(int width, int height, long numSurfacesToCreate)99 public void verifyCreatingManySurfaces(int width, int height, long numSurfacesToCreate) { 100 awaitReadyState(); 101 102 assertTrue("numSurfacesToCreate must be greater than 0", numSurfacesToCreate > 0); 103 104 Log.d(TAG, "Creating and rendering to " + numSurfacesToCreate + " surfaces."); 105 106 mFence = new CountDownLatch(1); 107 mCurrentRunWidth = width; 108 mCurrentRunHeight = height; 109 mNumSurfacesToCreate = numSurfacesToCreate; 110 mRemainingSurfacesToCreate = numSurfacesToCreate; 111 112 mHandler.post(() -> { 113 setupNextFrame(); 114 mChoreographer.postFrameCallback(this); 115 }); 116 117 try { 118 assertTrue( 119 "Unable to finish creating many surfaces with " + mRemainingSurfacesToCreate 120 + "/" + numSurfacesToCreate + " remaining", 121 mFence.await(5, TimeUnit.MINUTES)); 122 mFence = null; 123 } catch (InterruptedException e) { 124 throw new RuntimeException(e); 125 } 126 } 127 128 @Override doFrame(long frameTimeNanos)129 public void doFrame(long frameTimeNanos) { 130 mChoreographer.postVsyncCallback(this); 131 132 if (mSurface == null) { 133 // We don't have a surface yet, wait for the next frame. 134 return; 135 } 136 137 Canvas c = mSurface.lockCanvas(null); 138 c.drawRect(0, 0, c.getWidth(), c.getHeight(), mPaint); 139 mSurface.unlockCanvasAndPost(c); 140 141 mHasRendered = true; 142 } 143 144 @Override onVsync(@onNull Choreographer.FrameData data)145 public void onVsync(@NonNull Choreographer.FrameData data) { 146 if (!mHasRendered) { 147 mChoreographer.postFrameCallback(this); 148 return; 149 } 150 151 mRemainingSurfacesToCreate--; 152 if (mRemainingSurfacesToCreate == 0) { 153 mFence.countDown(); 154 Log.d(TAG, "Finished processing " + mNumSurfacesToCreate + " frames."); 155 return; 156 } 157 158 setupNextFrame(); 159 mChoreographer.postFrameCallback(this); 160 } 161 awaitReadyState()162 private void awaitReadyState() { 163 try { 164 assertTrue(mReadyToStart.await(5, TimeUnit.SECONDS)); 165 } catch (InterruptedException e) { 166 throw new RuntimeException(e); 167 } 168 } 169 setupNextFrame()170 private void setupNextFrame() { 171 Log.d(TAG, "Setting frame " + mRemainingSurfacesToCreate + "/" 172 + mNumSurfacesToCreate + " left"); 173 174 mHasRendered = false; 175 mSurface = null; 176 if (mSurfaceView != null) { 177 mParent.removeView(mSurfaceView); 178 } 179 mSurfaceView = new SurfaceView(this); 180 mSurfaceView.setLayoutParams( 181 new LinearLayout.LayoutParams(mCurrentRunWidth, mCurrentRunHeight)); 182 mParent.addView(mSurfaceView); 183 184 mSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() { 185 @Override 186 public void surfaceCreated(@NonNull SurfaceHolder holder) { 187 mSurface = holder.getSurface(); 188 } 189 190 @Override 191 public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, 192 int height) { 193 } 194 195 @Override 196 public void surfaceDestroyed(@NonNull SurfaceHolder holder) { 197 } 198 }); 199 } 200 } 201