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 android.view.choreographertests; 18 19 import static org.junit.Assert.assertTrue; 20 import static org.junit.Assert.fail; 21 22 import android.Manifest; 23 import android.hardware.display.DisplayManager; 24 import android.support.test.uiautomator.UiDevice; 25 import android.util.Log; 26 import android.view.Surface; 27 import android.view.SurfaceControl; 28 import android.view.SurfaceHolder; 29 import android.view.SurfaceView; 30 31 import androidx.lifecycle.Lifecycle; 32 import androidx.test.core.app.ActivityScenario; 33 import androidx.test.ext.junit.runners.AndroidJUnit4; 34 import androidx.test.platform.app.InstrumentationRegistry; 35 36 import org.junit.After; 37 import org.junit.Before; 38 import org.junit.Test; 39 import org.junit.runner.RunWith; 40 41 import java.util.concurrent.CountDownLatch; 42 import java.util.concurrent.TimeUnit; 43 44 @RunWith(AndroidJUnit4.class) 45 public class AttachedChoreographerNativeTest { 46 private static final String TAG = "AttachedChoreographerNativeTest"; 47 48 static { 49 System.loadLibrary("choreographertests_jni"); 50 } 51 52 private final CountDownLatch mSurfaceCreationCountDown = new CountDownLatch(1); 53 private CountDownLatch mTestCompleteSignal; 54 private long mChoreographerPtr; 55 private SurfaceView mSurfaceView; 56 private SurfaceHolder mSurfaceHolder; 57 private ActivityScenario<GraphicsActivity> mScenario; 58 private int mInitialMatchContentFrameRate; 59 private DisplayManager mDisplayManager; 60 nativeSurfaceControl_getChoreographer(SurfaceControl surfaceControl)61 private static native long nativeSurfaceControl_getChoreographer(SurfaceControl surfaceControl); nativeTestPostVsyncCallbackAtFrameRate( long choreographerPtr, float expectedFrameRate)62 private native void nativeTestPostVsyncCallbackAtFrameRate( 63 long choreographerPtr, float expectedFrameRate); 64 65 @Before setup()66 public void setup() throws Exception { 67 mScenario = ActivityScenario.launch(GraphicsActivity.class); 68 mScenario.moveToState(Lifecycle.State.CREATED); 69 mScenario.onActivity(activity -> { 70 mSurfaceView = activity.findViewById(R.id.surface); 71 mSurfaceHolder = mSurfaceView.getHolder(); 72 mSurfaceHolder.addCallback(new SurfaceHolder.Callback() { 73 @Override 74 public void surfaceChanged( 75 SurfaceHolder holder, int format, int width, int height) {} 76 77 @Override 78 public void surfaceCreated(SurfaceHolder holder) { 79 mSurfaceCreationCountDown.countDown(); 80 } 81 82 @Override 83 public void surfaceDestroyed(SurfaceHolder holder) {} 84 }); 85 }); 86 87 mScenario.moveToState(Lifecycle.State.RESUMED); 88 UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); 89 uiDevice.wakeUp(); 90 uiDevice.executeShellCommand("wm dismiss-keyguard"); 91 92 InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( 93 android.Manifest.permission.LOG_COMPAT_CHANGE, 94 android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG, 95 android.Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE, 96 android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS, 97 Manifest.permission.MANAGE_GAME_MODE); 98 mScenario.onActivity(activity -> { 99 mDisplayManager = activity.getSystemService(DisplayManager.class); 100 mInitialMatchContentFrameRate = 101 toSwitchingType(mDisplayManager.getMatchContentFrameRateUserPreference()); 102 mDisplayManager.setRefreshRateSwitchingType( 103 DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY); 104 mDisplayManager.setShouldAlwaysRespectAppRequestedMode(true); 105 }); 106 } 107 108 @After tearDown()109 public void tearDown() { 110 mDisplayManager.setRefreshRateSwitchingType(mInitialMatchContentFrameRate); 111 mDisplayManager.setShouldAlwaysRespectAppRequestedMode(false); 112 InstrumentationRegistry.getInstrumentation() 113 .getUiAutomation() 114 .dropShellPermissionIdentity(); 115 } 116 117 @Test test_choreographer_callbacksForVariousFrameRates()118 public void test_choreographer_callbacksForVariousFrameRates() { 119 for (int divisor : new int[] {2, 3}) { 120 mTestCompleteSignal = new CountDownLatch(1); 121 mScenario.onActivity(activity -> { 122 if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds= */ 1L)) { 123 fail("Unable to create surface within 1 Second"); 124 } 125 126 SurfaceControl surfaceControl = mSurfaceView.getSurfaceControl(); 127 mChoreographerPtr = nativeSurfaceControl_getChoreographer(surfaceControl); 128 Log.i(TAG, "mChoreographerPtr value " + mChoreographerPtr); 129 130 float displayRefreshRate = activity.getDisplay().getMode().getRefreshRate(); 131 float expectedFrameRate = displayRefreshRate / divisor; 132 133 SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); 134 transaction 135 .setFrameRate(surfaceControl, expectedFrameRate, 136 Surface.FRAME_RATE_COMPATIBILITY_DEFAULT) 137 .addTransactionCommittedListener(Runnable::run, 138 () -> { 139 assertTrue(mChoreographerPtr != 0L); 140 Log.i(TAG, "Testing frame rate of " + expectedFrameRate); 141 nativeTestPostVsyncCallbackAtFrameRate( 142 mChoreographerPtr, expectedFrameRate); 143 }) 144 .apply(); 145 }); 146 // wait for the previous callbacks to finish before moving to the next divisor 147 if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds= */ 5L)) { 148 fail("Test for divisor " + divisor + " not finished in 5 seconds"); 149 } 150 } 151 } 152 153 /** Call from native to trigger test completion. */ endTest()154 private void endTest() { 155 Log.i(TAG, "Signal test completion!"); 156 mTestCompleteSignal.countDown(); 157 } 158 waitForCountDown(CountDownLatch countDownLatch, long timeoutInSeconds)159 private boolean waitForCountDown(CountDownLatch countDownLatch, long timeoutInSeconds) { 160 try { 161 return !countDownLatch.await(timeoutInSeconds, TimeUnit.SECONDS); 162 } catch (InterruptedException ex) { 163 throw new AssertionError("Test interrupted", ex); 164 } 165 } 166 toSwitchingType(int matchContentFrameRateUserPreference)167 private int toSwitchingType(int matchContentFrameRateUserPreference) { 168 switch (matchContentFrameRateUserPreference) { 169 case DisplayManager.MATCH_CONTENT_FRAMERATE_NEVER: 170 return DisplayManager.SWITCHING_TYPE_NONE; 171 case DisplayManager.MATCH_CONTENT_FRAMERATE_SEAMLESSS_ONLY: 172 return DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS; 173 case DisplayManager.MATCH_CONTENT_FRAMERATE_ALWAYS: 174 return DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS; 175 default: 176 return -1; 177 } 178 } 179 } 180