diff --git a/core/api/current.txt b/core/api/current.txt index 03c1c198873ad..0bdaa1da2e451 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -48490,6 +48490,7 @@ package android.view { public static class SurfaceControl.Transaction implements java.io.Closeable android.os.Parcelable { ctor public SurfaceControl.Transaction(); + method @NonNull public android.view.SurfaceControl.Transaction addTransactionCommittedListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.TransactionCommittedListener); method public void apply(); method public void close(); method public int describeContents(); @@ -48616,6 +48617,10 @@ package android.view { field public static final int TO_RIGHT = 8; // 0x8 } + public interface TransactionCommittedListener { + method public void onTransactionCommitted(); + } + public final class VelocityTracker { method public void addMovement(android.view.MotionEvent); method public void clear(); diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS index cdf1e46ac5dba..2b5e286e89e40 100644 --- a/core/java/android/view/OWNERS +++ b/core/java/android/view/OWNERS @@ -83,6 +83,7 @@ per-file SyncRtSurfaceTransactionApplier.java = file:/services/core/java/com/and per-file ViewRootInsetsControllerHost.java = file:/services/core/java/com/android/server/wm/OWNERS per-file Window*.java = file:/services/core/java/com/android/server/wm/OWNERS per-file Window*.aidl = file:/services/core/java/com/android/server/wm/OWNERS +per-file TransactionCommittedCallback.java = file:/services/core/java/com/android/server/wm/OWNERS # Scroll Capture per-file *ScrollCapture*.aidl = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index a563d091d8493..d7c456f00b8ab 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -26,6 +26,7 @@ import static android.view.SurfaceControlProto.HASH_CODE; import static android.view.SurfaceControlProto.LAYER_ID; import static android.view.SurfaceControlProto.NAME; +import android.annotation.CallbackExecutor; import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.IntRange; @@ -74,6 +75,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Objects; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; /** @@ -243,6 +245,8 @@ public final class SurfaceControl implements Parcelable { private static native void nativeSetTransformHint(long nativeObject, int transformHint); private static native int nativeGetTransformHint(long nativeObject); private static native int nativeGetLayerId(long nativeObject); + private static native void nativeAddTransactionCommittedListener(long nativeObject, + TransactionCommittedListener listener); @Nullable @GuardedBy("mLock") @@ -3508,6 +3512,31 @@ public final class SurfaceControl implements Parcelable { return this; } + /** + * Request to add a {@link TransactionCommittedListener}. + * + * The callback is invoked when transaction is applied and the updates are ready to be + * presented. This callback does not mean buffers have been released! It simply means that + * any new transactions applied will not overwrite the transaction for which we are + * receiving a callback and instead will be included in the next frame. If you are trying + * to avoid dropping frames (overwriting transactions), and unable to use timestamps (Which + * provide a more efficient solution), then this method provides a method to pace your + * transaction application. + * + * @param executor The executor that the callback should be invoked on. + * @param listener The callback that will be invoked when the transaction has been + * committed. + */ + @NonNull + public Transaction addTransactionCommittedListener( + @NonNull @CallbackExecutor Executor executor, + @NonNull TransactionCommittedListener listener) { + TransactionCommittedListener listenerInner = + () -> executor.execute(listener::onTransactionCommitted); + nativeAddTransactionCommittedListener(mNativeObject, listenerInner); + return this; + } + /** * Writes the transaction to parcel, clearing the transaction as if it had been applied so * it can be used to store future transactions. It's the responsibility of the parcel diff --git a/core/java/android/view/TransactionCommittedListener.java b/core/java/android/view/TransactionCommittedListener.java new file mode 100644 index 0000000000000..6abded23a2e7f --- /dev/null +++ b/core/java/android/view/TransactionCommittedListener.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 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; + +import java.util.concurrent.Executor; + +/** + * Interface to handle request to + * {@link SurfaceControl.Transaction#addTransactionCommittedListener(Executor, TransactionCommittedListener)} + */ +public interface TransactionCommittedListener { + /** + * Invoked when the transaction has been committed in SurfaceFlinger. + */ + void onTransactionCommitted(); +} diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 5c096ddc0c183..1adaf10a1ce2d 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -238,6 +238,11 @@ static struct { jmethodID ctor; } gJankDataClassInfo; +static struct { + jclass clazz; + jmethodID onTransactionCommitted; +} gTransactionCommittedListenerClassInfo; + class JNamedColorSpace { public: // ColorSpace.Named.SRGB.ordinal() = 0; @@ -329,6 +334,44 @@ private: } }; +class TransactionCommittedListenerWrapper { +public: + explicit TransactionCommittedListenerWrapper(JNIEnv* env, jobject object) { + env->GetJavaVM(&mVm); + mTransactionCommittedListenerObject = env->NewGlobalRef(object); + LOG_ALWAYS_FATAL_IF(!mTransactionCommittedListenerObject, "Failed to make global ref"); + } + + ~TransactionCommittedListenerWrapper() { + getenv()->DeleteGlobalRef(mTransactionCommittedListenerObject); + } + + void callback() { + JNIEnv* env = getenv(); + env->CallVoidMethod(mTransactionCommittedListenerObject, + gTransactionCommittedListenerClassInfo.onTransactionCommitted); + } + + static void transactionCallbackThunk(void* context, nsecs_t /*latchTime*/, + const sp& /*presentFence*/, + const std::vector& /*stats*/) { + TransactionCommittedListenerWrapper* listener = + reinterpret_cast(context); + listener->callback(); + delete listener; + } + +private: + jobject mTransactionCommittedListenerObject; + JavaVM* mVm; + + JNIEnv* getenv() { + JNIEnv* env; + mVm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); + return env; + } +}; + // ---------------------------------------------------------------------------- static jlong nativeCreateTransaction(JNIEnv* env, jclass clazz) { @@ -1730,6 +1773,17 @@ static void nativeSetFrameTimelineVsync(JNIEnv* env, jclass clazz, jlong transac {frameTimelineVsyncId, android::os::IInputConstants::INVALID_INPUT_EVENT_ID}); } +static void nativeAddTransactionCommittedListener(JNIEnv* env, jclass clazz, jlong transactionObj, + jobject transactionCommittedListenerObject) { + auto transaction = reinterpret_cast(transactionObj); + + void* context = + new TransactionCommittedListenerWrapper(env, transactionCommittedListenerObject); + transaction->addTransactionCommittedCallback(TransactionCommittedListenerWrapper:: + transactionCallbackThunk, + context); +} + class JankDataListenerWrapper : public JankDataListener { public: JankDataListenerWrapper(JNIEnv* env, jobject onJankDataListenerObject) { @@ -2027,6 +2081,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeGetLayerId }, {"nativeSetDropInputMode", "(JJI)V", (void*)nativeSetDropInputMode }, + {"nativeAddTransactionCommittedListener", "(JLandroid/view/TransactionCommittedListener;)V", + (void*) nativeAddTransactionCommittedListener }, // clang-format on }; @@ -2242,6 +2298,14 @@ int register_android_view_SurfaceControl(JNIEnv* env) gJankDataListenerClassInfo.onJankDataAvailable = GetMethodIDOrDie(env, onJankDataListenerClazz, "onJankDataAvailable", "([Landroid/view/SurfaceControl$JankData;)V"); + + jclass transactionCommittedListenerClazz = + FindClassOrDie(env, "android/view/TransactionCommittedListener"); + gTransactionCommittedListenerClassInfo.clazz = + MakeGlobalRefOrDie(env, transactionCommittedListenerClazz); + gTransactionCommittedListenerClassInfo.onTransactionCommitted = + GetMethodIDOrDie(env, transactionCommittedListenerClazz, "onTransactionCommitted", + "()V"); return err; }