Merge changes from topic "psc1"
* changes: Attached choreographer tests Attach the Choreographer instance with SurfaceControl
This commit is contained in:
@@ -3148,6 +3148,9 @@ package android.view {
|
||||
|
||||
public final class SurfaceControl implements android.os.Parcelable {
|
||||
ctor public SurfaceControl(@NonNull android.view.SurfaceControl, @NonNull String);
|
||||
method @NonNull public android.view.Choreographer getChoreographer();
|
||||
method @NonNull public android.view.Choreographer getChoreographer(@NonNull android.os.Looper);
|
||||
method public boolean hasChoreographer();
|
||||
method public boolean isSameSurface(@NonNull android.view.SurfaceControl);
|
||||
}
|
||||
|
||||
|
||||
@@ -270,10 +270,14 @@ public final class Choreographer {
|
||||
private static final int CALLBACK_LAST = CALLBACK_COMMIT;
|
||||
|
||||
private Choreographer(Looper looper, int vsyncSource) {
|
||||
this(looper, vsyncSource, /* layerHandle */ 0L);
|
||||
}
|
||||
|
||||
private Choreographer(Looper looper, int vsyncSource, long layerHandle) {
|
||||
mLooper = looper;
|
||||
mHandler = new FrameHandler(looper);
|
||||
mDisplayEventReceiver = USE_VSYNC
|
||||
? new FrameDisplayEventReceiver(looper, vsyncSource)
|
||||
? new FrameDisplayEventReceiver(looper, vsyncSource, layerHandle)
|
||||
: null;
|
||||
mLastFrameTimeNanos = Long.MIN_VALUE;
|
||||
|
||||
@@ -312,6 +316,26 @@ public final class Choreographer {
|
||||
return sSfThreadInstance.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the choreographer associated with the SurfaceControl.
|
||||
*
|
||||
* @param layerHandle to which the choreographer will be attached.
|
||||
* @param looper the choreographer is attached on this looper.
|
||||
*
|
||||
* @return The choreographer for the looper which is attached
|
||||
* to the sourced SurfaceControl::mNativeHandle.
|
||||
* @throws IllegalStateException if the looper sourced is null.
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
static Choreographer getInstanceForSurfaceControl(long layerHandle,
|
||||
@NonNull Looper looper) {
|
||||
if (looper == null) {
|
||||
throw new IllegalStateException("The current thread must have a looper!");
|
||||
}
|
||||
return new Choreographer(looper, VSYNC_SOURCE_APP, layerHandle);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The Choreographer of the main thread, if it exists, or {@code null} otherwise.
|
||||
* @hide
|
||||
@@ -333,6 +357,15 @@ public final class Choreographer {
|
||||
mDisplayEventReceiver.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose the DisplayEventReceiver on the Choreographer.
|
||||
* @hide
|
||||
*/
|
||||
@UnsupportedAppUsage
|
||||
void invalidate() {
|
||||
dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* The amount of time, in milliseconds, between each frame of the animation.
|
||||
* <p>
|
||||
@@ -1166,8 +1199,8 @@ public final class Choreographer {
|
||||
private int mFrame;
|
||||
private VsyncEventData mLastVsyncEventData = new VsyncEventData();
|
||||
|
||||
public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
|
||||
super(looper, vsyncSource, 0);
|
||||
FrameDisplayEventReceiver(Looper looper, int vsyncSource, long layerHandle) {
|
||||
super(looper, vsyncSource, /* eventRegistration */ 0, layerHandle);
|
||||
}
|
||||
|
||||
// TODO(b/116025192): physicalDisplayId is ignored because SF only emits VSYNC events for
|
||||
|
||||
@@ -80,7 +80,7 @@ public abstract class DisplayEventReceiver {
|
||||
private MessageQueue mMessageQueue;
|
||||
|
||||
private static native long nativeInit(WeakReference<DisplayEventReceiver> receiver,
|
||||
MessageQueue messageQueue, int vsyncSource, int eventRegistration);
|
||||
MessageQueue messageQueue, int vsyncSource, int eventRegistration, long layerHandle);
|
||||
private static native void nativeDispose(long receiverPtr);
|
||||
@FastNative
|
||||
private static native void nativeScheduleVsync(long receiverPtr);
|
||||
@@ -93,7 +93,11 @@ public abstract class DisplayEventReceiver {
|
||||
*/
|
||||
@UnsupportedAppUsage
|
||||
public DisplayEventReceiver(Looper looper) {
|
||||
this(looper, VSYNC_SOURCE_APP, 0);
|
||||
this(looper, VSYNC_SOURCE_APP, /* eventRegistration */ 0, /* layerHandle */ 0L);
|
||||
}
|
||||
|
||||
public DisplayEventReceiver(Looper looper, int vsyncSource, int eventRegistration) {
|
||||
this(looper, vsyncSource, eventRegistration, /* layerHandle */ 0L);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,15 +107,17 @@ public abstract class DisplayEventReceiver {
|
||||
* @param vsyncSource The source of the vsync tick. Must be on of the VSYNC_SOURCE_* values.
|
||||
* @param eventRegistration Which events to dispatch. Must be a bitfield consist of the
|
||||
* EVENT_REGISTRATION_*_FLAG values.
|
||||
* @param layerHandle Layer to which the current instance is attached to
|
||||
*/
|
||||
public DisplayEventReceiver(Looper looper, int vsyncSource, int eventRegistration) {
|
||||
public DisplayEventReceiver(Looper looper, int vsyncSource, int eventRegistration,
|
||||
long layerHandle) {
|
||||
if (looper == null) {
|
||||
throw new IllegalArgumentException("looper must not be null");
|
||||
}
|
||||
|
||||
mMessageQueue = looper.getQueue();
|
||||
mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue,
|
||||
vsyncSource, eventRegistration);
|
||||
vsyncSource, eventRegistration, layerHandle);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -64,6 +64,7 @@ import android.opengl.EGLDisplay;
|
||||
import android.opengl.EGLSync;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.os.RemoteException;
|
||||
@@ -463,6 +464,10 @@ public final class SurfaceControl implements Parcelable {
|
||||
public long mNativeObject;
|
||||
private long mNativeHandle;
|
||||
|
||||
private final Object mChoreographerLock = new Object();
|
||||
@GuardedBy("mChoreographerLock")
|
||||
private Choreographer mChoreographer;
|
||||
|
||||
// TODO: Move width/height to native and fix locking through out.
|
||||
private final Object mLock = new Object();
|
||||
@GuardedBy("mLock")
|
||||
@@ -1268,6 +1273,59 @@ public final class SurfaceControl implements Parcelable {
|
||||
return other.mNativeHandle == mNativeHandle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the associated {@link Choreographer} instance with the
|
||||
* current instance of the SurfaceControl.
|
||||
* Must be called from a thread that already has a {@link android.os.Looper}
|
||||
* associated with it.
|
||||
* If there is no {@link Choreographer} associated with the SurfaceControl then a new instance
|
||||
* of the {@link Choreographer} is created.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@TestApi
|
||||
public @NonNull Choreographer getChoreographer() {
|
||||
return getChoreographer(Looper.myLooper());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the associated {@link Choreographer} instance with the
|
||||
* current instance of the SurfaceControl.
|
||||
* If there is no {@link Choreographer} associated with the SurfaceControl then a new instance
|
||||
* of the {@link Choreographer} is created.
|
||||
*
|
||||
* @param looper the choreographer is attached on this looper
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@TestApi
|
||||
public @NonNull Choreographer getChoreographer(@NonNull Looper looper) {
|
||||
checkNotReleased();
|
||||
synchronized (mChoreographerLock) {
|
||||
if (mChoreographer != null) {
|
||||
return mChoreographer;
|
||||
}
|
||||
|
||||
mChoreographer = Choreographer.getInstanceForSurfaceControl(mNativeHandle, looper);
|
||||
return mChoreographer;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if {@link Choreographer} is present otherwise false.
|
||||
* To check the validity use {@link #isValid} on the SurfaceControl, a valid SurfaceControl with
|
||||
* choreographer will have the valid Choreographer.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@TestApi
|
||||
@UnsupportedAppUsage
|
||||
public boolean hasChoreographer() {
|
||||
synchronized (mChoreographerLock) {
|
||||
return mChoreographer != null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to a protocol buffer output stream. Protocol buffer message definition is at {@link
|
||||
* android.view.SurfaceControlProto}.
|
||||
@@ -1325,6 +1383,13 @@ public final class SurfaceControl implements Parcelable {
|
||||
mNativeObject = 0;
|
||||
mNativeHandle = 0;
|
||||
mCloseGuard.close();
|
||||
synchronized (mChoreographerLock) {
|
||||
if (mChoreographer != null) {
|
||||
mChoreographer.invalidate();
|
||||
// TODO(b/266121235): Use NativeAllocationRegistry to clean up Choreographer.
|
||||
mChoreographer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ class NativeDisplayEventReceiver : public DisplayEventDispatcher {
|
||||
public:
|
||||
NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak,
|
||||
const sp<MessageQueue>& messageQueue, jint vsyncSource,
|
||||
jint eventRegistration);
|
||||
jint eventRegistration, jlong layerHandle);
|
||||
|
||||
void dispose();
|
||||
|
||||
@@ -88,11 +88,15 @@ private:
|
||||
|
||||
NativeDisplayEventReceiver::NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak,
|
||||
const sp<MessageQueue>& messageQueue,
|
||||
jint vsyncSource, jint eventRegistration)
|
||||
jint vsyncSource, jint eventRegistration,
|
||||
jlong layerHandle)
|
||||
: DisplayEventDispatcher(messageQueue->getLooper(),
|
||||
static_cast<gui::ISurfaceComposer::VsyncSource>(vsyncSource),
|
||||
static_cast<gui::ISurfaceComposer::EventRegistration>(
|
||||
eventRegistration)),
|
||||
eventRegistration),
|
||||
layerHandle != 0 ? sp<IBinder>::fromExisting(
|
||||
reinterpret_cast<IBinder*>(layerHandle))
|
||||
: nullptr),
|
||||
mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)),
|
||||
mMessageQueue(messageQueue) {
|
||||
ALOGV("receiver %p ~ Initializing display event receiver.", this);
|
||||
@@ -214,7 +218,7 @@ void NativeDisplayEventReceiver::dispatchFrameRateOverrides(
|
||||
}
|
||||
|
||||
static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject messageQueueObj,
|
||||
jint vsyncSource, jint eventRegistration) {
|
||||
jint vsyncSource, jint eventRegistration, jlong layerHandle) {
|
||||
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
|
||||
if (messageQueue == NULL) {
|
||||
jniThrowRuntimeException(env, "MessageQueue is not initialized.");
|
||||
@@ -223,7 +227,7 @@ static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject
|
||||
|
||||
sp<NativeDisplayEventReceiver> receiver =
|
||||
new NativeDisplayEventReceiver(env, receiverWeak, messageQueue, vsyncSource,
|
||||
eventRegistration);
|
||||
eventRegistration, layerHandle);
|
||||
status_t status = receiver->initialize();
|
||||
if (status) {
|
||||
String8 message;
|
||||
@@ -268,7 +272,7 @@ static jobject nativeGetLatestVsyncEventData(JNIEnv* env, jclass clazz, jlong re
|
||||
|
||||
static const JNINativeMethod gMethods[] = {
|
||||
/* name, signature, funcPtr */
|
||||
{"nativeInit", "(Ljava/lang/ref/WeakReference;Landroid/os/MessageQueue;II)J",
|
||||
{"nativeInit", "(Ljava/lang/ref/WeakReference;Landroid/os/MessageQueue;IIJ)J",
|
||||
(void*)nativeInit},
|
||||
{"nativeDispose", "(J)V", (void*)nativeDispose},
|
||||
// @FastNative
|
||||
|
||||
46
tests/ChoreographerTests/Android.bp
Normal file
46
tests/ChoreographerTests/Android.bp
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2023 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 {
|
||||
// See: http://go/android-license-faq
|
||||
// A large-scale-change added 'default_applicable_licenses' to import
|
||||
// all of the 'license_kinds' from "frameworks_base_license"
|
||||
// to get the below license kinds:
|
||||
// SPDX-license-identifier-Apache-2.0
|
||||
default_applicable_licenses: ["frameworks_base_license"],
|
||||
}
|
||||
|
||||
android_test {
|
||||
name: "ChoreographerTests",
|
||||
srcs: ["src/**/*.java"],
|
||||
libs: [
|
||||
"android.test.runner",
|
||||
"android.test.base",
|
||||
],
|
||||
static_libs: [
|
||||
"androidx.test.core",
|
||||
"androidx.test.ext.junit",
|
||||
"androidx.test.rules",
|
||||
"compatibility-device-util-axt",
|
||||
"com.google.android.material_material",
|
||||
"truth-prebuilt",
|
||||
],
|
||||
resource_dirs: ["src/main/res"],
|
||||
certificate: "platform",
|
||||
platform_apis: true,
|
||||
test_suites: ["device-tests"],
|
||||
optimize: {
|
||||
enabled: false,
|
||||
},
|
||||
}
|
||||
33
tests/ChoreographerTests/AndroidManifest.xml
Normal file
33
tests/ChoreographerTests/AndroidManifest.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2023 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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android.view.choreographertests">
|
||||
|
||||
<application android:debuggable="true" android:testOnly="true">
|
||||
<uses-library android:name="android.test.runner"/>
|
||||
<activity
|
||||
android:name=".GraphicsActivity"
|
||||
android:exported="false">
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
|
||||
android:targetPackage="android.view.choreographertests"
|
||||
android:label="Tests of android.view.ChoreographerTests">
|
||||
</instrumentation>
|
||||
</manifest>
|
||||
30
tests/ChoreographerTests/AndroidTest.xml
Normal file
30
tests/ChoreographerTests/AndroidTest.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2023 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.
|
||||
-->
|
||||
<configuration description="Config for ChoreographerTests cases">
|
||||
<option name="test-suite-tag" value="cts" />
|
||||
<option name="test-tag" value="ChoreographerTests"/>
|
||||
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
|
||||
<option name="cleanup-apks" value="true" />
|
||||
<option name="install-arg" value="-t" />
|
||||
<option name="test-file-name" value="ChoreographerTests.apk" />
|
||||
</target_preparer>
|
||||
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
|
||||
<option name="package" value="android.view.choreographertests" />
|
||||
<option name="hidden-api-checks" value="false" />
|
||||
<option name="isolated-storage" value="false" />
|
||||
</test>
|
||||
</configuration>
|
||||
2
tests/ChoreographerTests/OWNERS
Normal file
2
tests/ChoreographerTests/OWNERS
Normal file
@@ -0,0 +1,2 @@
|
||||
include platform/frameworks/base:/graphics/java/android/graphics/OWNERS
|
||||
include platform/frameworks/native:/services/surfaceflinger/OWNERS
|
||||
7
tests/ChoreographerTests/TEST_MAPPING
Normal file
7
tests/ChoreographerTests/TEST_MAPPING
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"presubmit": [
|
||||
{
|
||||
"name": "ChoreographerTests"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,474 @@
|
||||
/*
|
||||
* Copyright 2023 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 android.view.choreographertests;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.compat.CompatChanges;
|
||||
import android.hardware.display.DisplayManager;
|
||||
import android.os.Looper;
|
||||
import android.support.test.uiautomator.UiDevice;
|
||||
import android.util.Log;
|
||||
import android.view.Choreographer;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.test.core.app.ActivityScenario;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class AttachedChoreographerTest {
|
||||
private static final String TAG = "AttachedChoreographerTest";
|
||||
private static final long DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID = 170503758;
|
||||
private static final int THRESHOLD_MS = 10;
|
||||
private static final int CALLBACK_TIME_10_FPS = 100;
|
||||
private static final int CALLBACK_TIME_30_FPS = 33;
|
||||
private static final int FRAME_ITERATIONS = 21;
|
||||
private static final int CALLBACK_MISSED_THRESHOLD = 2;
|
||||
|
||||
private final CountDownLatch mTestCompleteSignal = new CountDownLatch(2);
|
||||
private final CountDownLatch mSurfaceCreationCountDown = new CountDownLatch(1);
|
||||
private final CountDownLatch mNoCallbackSignal = new CountDownLatch(1);
|
||||
private final CountDownLatch mFramesSignal = new CountDownLatch(FRAME_ITERATIONS);
|
||||
|
||||
private ActivityScenario<GraphicsActivity> mScenario;
|
||||
private int mInitialMatchContentFrameRate;
|
||||
private DisplayManager mDisplayManager;
|
||||
private SurfaceView mSurfaceView;
|
||||
private SurfaceHolder mSurfaceHolder;
|
||||
private boolean mIsFirstCallback = true;
|
||||
private int mCallbackMissedCounter = 0;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mScenario = ActivityScenario.launch(GraphicsActivity.class);
|
||||
mScenario.moveToState(Lifecycle.State.CREATED);
|
||||
mCallbackMissedCounter = 0;
|
||||
mScenario.onActivity(activity -> {
|
||||
mSurfaceView = activity.findViewById(R.id.surface);
|
||||
mSurfaceHolder = mSurfaceView.getHolder();
|
||||
mSurfaceHolder.addCallback(new SurfaceHolder.Callback() {
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width,
|
||||
int height) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
mSurfaceCreationCountDown.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
|
||||
uiDevice.wakeUp();
|
||||
uiDevice.executeShellCommand("wm dismiss-keyguard");
|
||||
mScenario.moveToState(Lifecycle.State.RESUMED);
|
||||
|
||||
InstrumentationRegistry.getInstrumentation().getUiAutomation()
|
||||
.adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE,
|
||||
Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
|
||||
Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE,
|
||||
Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS,
|
||||
Manifest.permission.MANAGE_GAME_MODE);
|
||||
mScenario.onActivity(activity -> {
|
||||
mDisplayManager = activity.getSystemService(DisplayManager.class);
|
||||
mInitialMatchContentFrameRate = toSwitchingType(
|
||||
mDisplayManager.getMatchContentFrameRateUserPreference());
|
||||
mDisplayManager.setRefreshRateSwitchingType(
|
||||
DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
|
||||
mDisplayManager.setShouldAlwaysRespectAppRequestedMode(true);
|
||||
boolean changeIsEnabled =
|
||||
CompatChanges.isChangeEnabled(
|
||||
DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID);
|
||||
Log.i(TAG, "DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGE_ID is "
|
||||
+ (changeIsEnabled ? "enabled" : "disabled"));
|
||||
});
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
mDisplayManager.setRefreshRateSwitchingType(mInitialMatchContentFrameRate);
|
||||
mDisplayManager.setShouldAlwaysRespectAppRequestedMode(false);
|
||||
InstrumentationRegistry.getInstrumentation().getUiAutomation()
|
||||
.dropShellPermissionIdentity();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_create_choreographer() {
|
||||
mScenario.onActivity(activity -> {
|
||||
if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
|
||||
fail("Unable to create surface within 1 Second");
|
||||
}
|
||||
SurfaceControl sc = mSurfaceView.getSurfaceControl();
|
||||
mTestCompleteSignal.countDown();
|
||||
SurfaceControl sc1 = new SurfaceControl(sc, "AttachedChoreographerTests");
|
||||
// Create attached choreographer with getChoreographer
|
||||
sc1.getChoreographer();
|
||||
assertTrue(sc1.hasChoreographer());
|
||||
assertTrue(sc1.isValid());
|
||||
sc1.release();
|
||||
|
||||
SurfaceControl sc2 = new SurfaceControl(sc, "AttachedChoreographerTests");
|
||||
// Create attached choreographer with Looper.myLooper
|
||||
sc2.getChoreographer(Looper.myLooper());
|
||||
assertTrue(sc2.hasChoreographer());
|
||||
assertTrue(sc2.isValid());
|
||||
sc2.release();
|
||||
|
||||
SurfaceControl sc3 = new SurfaceControl(sc, "AttachedChoreographerTests");
|
||||
// Create attached choreographer with Looper.myLooper
|
||||
sc3.getChoreographer(Looper.getMainLooper());
|
||||
assertTrue(sc3.hasChoreographer());
|
||||
assertTrue(sc3.isValid());
|
||||
sc3.release();
|
||||
mTestCompleteSignal.countDown();
|
||||
});
|
||||
if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) {
|
||||
fail("Test not finished in 2 Seconds");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_copy_surface_control() {
|
||||
mScenario.onActivity(activity -> {
|
||||
if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
|
||||
fail("Unable to create surface within 1 Second");
|
||||
}
|
||||
SurfaceControl sc = mSurfaceView.getSurfaceControl();
|
||||
// Create attached choreographer
|
||||
sc.getChoreographer();
|
||||
assertTrue(sc.hasChoreographer());
|
||||
|
||||
// Use copy constructor
|
||||
SurfaceControl copyConstructorSc = new SurfaceControl(sc, "AttachedChoreographerTests");
|
||||
//Choreographer isn't copied over.
|
||||
assertFalse(copyConstructorSc.hasChoreographer());
|
||||
copyConstructorSc.getChoreographer();
|
||||
assertTrue(copyConstructorSc.hasChoreographer());
|
||||
mTestCompleteSignal.countDown();
|
||||
|
||||
// Use copyFrom
|
||||
SurfaceControl copyFromSc = new SurfaceControl();
|
||||
copyFromSc.copyFrom(sc, "AttachedChoreographerTests");
|
||||
//Choreographer isn't copied over.
|
||||
assertFalse(copyFromSc.hasChoreographer());
|
||||
copyFromSc.getChoreographer();
|
||||
assertTrue(copyFromSc.hasChoreographer());
|
||||
mTestCompleteSignal.countDown();
|
||||
});
|
||||
if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) {
|
||||
fail("Test not finished in 2 Seconds");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_mirror_surface_control() {
|
||||
mScenario.onActivity(activity -> {
|
||||
if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
|
||||
fail("Unable to create surface within 1 Second");
|
||||
}
|
||||
SurfaceControl sc = mSurfaceView.getSurfaceControl();
|
||||
// Create attached choreographer
|
||||
sc.getChoreographer();
|
||||
assertTrue(sc.hasChoreographer());
|
||||
mTestCompleteSignal.countDown();
|
||||
|
||||
// Use mirrorSurface
|
||||
SurfaceControl mirrorSc = SurfaceControl.mirrorSurface(sc);
|
||||
//Choreographer isn't copied over.
|
||||
assertFalse(mirrorSc.hasChoreographer());
|
||||
mirrorSc.getChoreographer();
|
||||
assertTrue(mirrorSc.hasChoreographer());
|
||||
// make SurfaceControl invalid by releasing it.
|
||||
mirrorSc.release();
|
||||
|
||||
assertTrue(sc.isValid());
|
||||
assertFalse(mirrorSc.isValid());
|
||||
assertFalse(mirrorSc.hasChoreographer());
|
||||
assertThrows(NullPointerException.class, mirrorSc::getChoreographer);
|
||||
mTestCompleteSignal.countDown();
|
||||
});
|
||||
if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) {
|
||||
fail("Test not finished in 2 Seconds");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_postFrameCallback() {
|
||||
mScenario.onActivity(activity -> {
|
||||
if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
|
||||
fail("Unable to create surface within 1 Second");
|
||||
}
|
||||
SurfaceControl sc = mSurfaceView.getSurfaceControl();
|
||||
sc.getChoreographer().postFrameCallback(
|
||||
frameTimeNanos -> mTestCompleteSignal.countDown());
|
||||
|
||||
SurfaceControl copySc = new SurfaceControl(sc, "AttachedChoreographerTests");
|
||||
Choreographer copyChoreographer = copySc.getChoreographer();
|
||||
// make SurfaceControl invalid by releasing it.
|
||||
copySc.release();
|
||||
|
||||
assertTrue(sc.isValid());
|
||||
assertFalse(copySc.isValid());
|
||||
copyChoreographer.postFrameCallback(frameTimeNanos -> mNoCallbackSignal.countDown());
|
||||
assertDoesReceiveCallback();
|
||||
});
|
||||
if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) {
|
||||
fail("Test not finished in 2 Seconds");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_postFrameCallbackDelayed() {
|
||||
mScenario.onActivity(activity -> {
|
||||
if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
|
||||
fail("Unable to create surface within 1 Second");
|
||||
}
|
||||
SurfaceControl sc = mSurfaceView.getSurfaceControl();
|
||||
sc.getChoreographer(Looper.getMainLooper()).postFrameCallbackDelayed(
|
||||
callback -> mTestCompleteSignal.countDown(),
|
||||
/* delayMillis */ 5);
|
||||
|
||||
SurfaceControl copySc = new SurfaceControl(sc, "AttachedChoreographerTests");
|
||||
Choreographer copyChoreographer = copySc.getChoreographer();
|
||||
// make SurfaceControl invalid by releasing it.
|
||||
copySc.release();
|
||||
|
||||
assertTrue(sc.isValid());
|
||||
assertFalse(copySc.isValid());
|
||||
copyChoreographer.postFrameCallbackDelayed(
|
||||
frameTimeNanos -> mNoCallbackSignal.countDown(), /* delayMillis */5);
|
||||
assertDoesReceiveCallback();
|
||||
});
|
||||
if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) {
|
||||
fail("Test not finished in 2 Seconds");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_postCallback() {
|
||||
mScenario.onActivity(activity -> {
|
||||
if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
|
||||
fail("Unable to create surface within 1 Second");
|
||||
}
|
||||
SurfaceControl sc = mSurfaceView.getSurfaceControl();
|
||||
sc.getChoreographer().postCallback(Choreographer.CALLBACK_COMMIT,
|
||||
mTestCompleteSignal::countDown, /* token */ this);
|
||||
|
||||
SurfaceControl copySc = new SurfaceControl(sc, "AttachedChoreographerTests");
|
||||
Choreographer copyChoreographer = copySc.getChoreographer();
|
||||
// make SurfaceControl invalid by releasing it.
|
||||
copySc.release();
|
||||
|
||||
assertTrue(sc.isValid());
|
||||
assertFalse(copySc.isValid());
|
||||
copyChoreographer.postCallback(Choreographer.CALLBACK_COMMIT,
|
||||
mNoCallbackSignal::countDown, /* token */ this);
|
||||
assertDoesReceiveCallback();
|
||||
});
|
||||
if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) {
|
||||
fail("Test not finished in 2 Seconds");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_postCallbackDelayed() {
|
||||
mScenario.onActivity(activity -> {
|
||||
if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
|
||||
fail("Unable to create surface within 1 Second");
|
||||
}
|
||||
SurfaceControl sc = mSurfaceView.getSurfaceControl();
|
||||
sc.getChoreographer().postCallbackDelayed(Choreographer.CALLBACK_COMMIT,
|
||||
mTestCompleteSignal::countDown, /* token */ this, /* delayMillis */ 5);
|
||||
|
||||
SurfaceControl copySc = new SurfaceControl(sc, "AttachedChoreographerTests");
|
||||
Choreographer copyChoreographer = copySc.getChoreographer();
|
||||
// make SurfaceControl invalid by releasing it.
|
||||
copySc.release();
|
||||
|
||||
assertTrue(sc.isValid());
|
||||
assertFalse(copySc.isValid());
|
||||
copyChoreographer.postCallbackDelayed(Choreographer.CALLBACK_COMMIT,
|
||||
mNoCallbackSignal::countDown, /* token */ this, /* delayMillis */ 5);
|
||||
assertDoesReceiveCallback();
|
||||
});
|
||||
if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) {
|
||||
fail("Test not finished in 2 Seconds");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_postVsyncCallback() {
|
||||
mScenario.onActivity(activity -> {
|
||||
if (waitForCountDown(mSurfaceCreationCountDown, /* timeout */ 1L)) {
|
||||
fail("Unable to create surface within 1 Second");
|
||||
}
|
||||
SurfaceControl sc = mSurfaceView.getSurfaceControl();
|
||||
sc.getChoreographer().postVsyncCallback(data -> mTestCompleteSignal.countDown());
|
||||
|
||||
SurfaceControl copySc = new SurfaceControl(sc, "AttachedChoreographerTests");
|
||||
Choreographer copyChoreographer = copySc.getChoreographer();
|
||||
// make SurfaceControl invalid by releasing it.
|
||||
copySc.release();
|
||||
|
||||
assertTrue(sc.isValid());
|
||||
assertFalse(copySc.isValid());
|
||||
copyChoreographer.postVsyncCallback(data -> mNoCallbackSignal.countDown());
|
||||
assertDoesReceiveCallback();
|
||||
});
|
||||
if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) {
|
||||
fail("Test not finished in 2 Seconds");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_choreographer_10Hz_refreshRate() {
|
||||
mScenario.onActivity(activity -> {
|
||||
if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
|
||||
fail("Unable to create surface within 1 Second");
|
||||
}
|
||||
SurfaceControl sc = mSurfaceView.getSurfaceControl();
|
||||
Choreographer choreographer = sc.getChoreographer();
|
||||
SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
|
||||
transaction.setFrameRate(sc, 10.0f, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE)
|
||||
.addTransactionCommittedListener(Runnable::run,
|
||||
() -> verifyVsyncCallbacks(choreographer,
|
||||
CALLBACK_TIME_10_FPS))
|
||||
.apply();
|
||||
mTestCompleteSignal.countDown();
|
||||
});
|
||||
if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 5L)) {
|
||||
fail("Test not finished in 5 Seconds");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_choreographer_30Hz_refreshRate() {
|
||||
mScenario.onActivity(activity -> {
|
||||
if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
|
||||
fail("Unable to create surface within 1 Second");
|
||||
}
|
||||
SurfaceControl sc = mSurfaceView.getSurfaceControl();
|
||||
Choreographer choreographer = sc.getChoreographer();
|
||||
SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
|
||||
transaction.setFrameRate(sc, 30.0f, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE)
|
||||
.addTransactionCommittedListener(Runnable::run,
|
||||
() -> verifyVsyncCallbacks(choreographer,
|
||||
CALLBACK_TIME_30_FPS))
|
||||
.apply();
|
||||
mTestCompleteSignal.countDown();
|
||||
});
|
||||
if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 5L)) {
|
||||
fail("Test not finished in 5 Seconds");
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyVsyncCallbacks(Choreographer choreographer, int callbackDurationMs) {
|
||||
long callbackRequestedTimeNs = System.nanoTime();
|
||||
choreographer.postVsyncCallback(frameData -> {
|
||||
mFramesSignal.countDown();
|
||||
final long frameCount = mFramesSignal.getCount();
|
||||
if (frameCount > 0) {
|
||||
if (!mIsFirstCallback) {
|
||||
// Skip the first callback as it takes 1 frame
|
||||
// to reflect the new refresh rate
|
||||
long callbackDurationDiffMs = getCallbackDurationDiffInMs(
|
||||
frameData.getFrameTimeNanos(),
|
||||
callbackRequestedTimeNs, callbackDurationMs);
|
||||
if (callbackDurationDiffMs < 0 || callbackDurationDiffMs > THRESHOLD_MS) {
|
||||
mCallbackMissedCounter++;
|
||||
Log.e(TAG, "Frame #" + Math.abs(frameCount - FRAME_ITERATIONS)
|
||||
+ " vsync callback failed, expected callback in "
|
||||
+ callbackDurationMs
|
||||
+ " With threshold of " + THRESHOLD_MS
|
||||
+ " but actual duration difference is " + callbackDurationDiffMs);
|
||||
}
|
||||
}
|
||||
mIsFirstCallback = false;
|
||||
verifyVsyncCallbacks(choreographer, callbackDurationMs);
|
||||
} else {
|
||||
assertTrue("Missed timeline for " + mCallbackMissedCounter + " callbacks, while "
|
||||
+ CALLBACK_MISSED_THRESHOLD + " missed callbacks are allowed",
|
||||
mCallbackMissedCounter <= CALLBACK_MISSED_THRESHOLD);
|
||||
mTestCompleteSignal.countDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private long getCallbackDurationDiffInMs(long callbackTimeNs, long requestedTimeNs,
|
||||
int expectedCallbackMs) {
|
||||
long actualTimeMs = TimeUnit.NANOSECONDS.toMillis(callbackTimeNs)
|
||||
- TimeUnit.NANOSECONDS.toMillis(requestedTimeNs);
|
||||
return Math.abs(expectedCallbackMs - actualTimeMs);
|
||||
}
|
||||
|
||||
private boolean waitForCountDown(CountDownLatch countDownLatch, long timeoutInSeconds) {
|
||||
try {
|
||||
return !countDownLatch.await(timeoutInSeconds, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException ex) {
|
||||
throw new AssertionError("Test interrupted", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private int toSwitchingType(int matchContentFrameRateUserPreference) {
|
||||
switch (matchContentFrameRateUserPreference) {
|
||||
case DisplayManager.MATCH_CONTENT_FRAMERATE_NEVER:
|
||||
return DisplayManager.SWITCHING_TYPE_NONE;
|
||||
case DisplayManager.MATCH_CONTENT_FRAMERATE_SEAMLESSS_ONLY:
|
||||
return DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS;
|
||||
case DisplayManager.MATCH_CONTENT_FRAMERATE_ALWAYS:
|
||||
return DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private void assertDoesReceiveCallback() {
|
||||
try {
|
||||
if (mNoCallbackSignal.await(/* timeout */ 50L, TimeUnit.MILLISECONDS)) {
|
||||
fail("Callback not supposed to be generated");
|
||||
} else {
|
||||
mTestCompleteSignal.countDown();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
fail("Callback wait is interrupted " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2023 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 android.view.choreographertests;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
public class GraphicsActivity extends Activity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_attached_choreographer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2023 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.
|
||||
-->
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<SurfaceView
|
||||
android:id="@+id/surface"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
18
tests/ChoreographerTests/src/main/res/values/strings.xml
Normal file
18
tests/ChoreographerTests/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2023 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.
|
||||
-->
|
||||
<resources>
|
||||
<string name="app_name">ChoreographerTests</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user