diff --git a/core/java/android/view/TunnelModeEnabledListener.java b/core/java/android/view/TunnelModeEnabledListener.java new file mode 100644 index 0000000000000..c158da9dfa23f --- /dev/null +++ b/core/java/android/view/TunnelModeEnabledListener.java @@ -0,0 +1,98 @@ +/* + * Copyright 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 com.android.internal.annotations.VisibleForTesting; + +import java.util.concurrent.Executor; + +/** + * Listens for tunnel mode enabled/disabled events from SurfaceFlinger. + * {@hide} + */ +public abstract class TunnelModeEnabledListener { + + private long mNativeListener; + private final Executor mExecutor; + + public TunnelModeEnabledListener(Executor executor) { + mExecutor = executor; + mNativeListener = nativeCreate(this); + } + + /** + * Destroys the listener. + */ + public void destroy() { + if (mNativeListener == 0) { + return; + } + unregister(this); + nativeDestroy(mNativeListener); + mNativeListener = 0; + } + + @Override + protected void finalize() throws Throwable { + try { + destroy(); + } finally { + super.finalize(); + } + } + + /** + * Reports when tunnel mode has been enabled/disabled. + */ + public abstract void onTunnelModeEnabledChanged(boolean tunnelModeEnabled); + + /** + * Registers a listener. + */ + public static void register(TunnelModeEnabledListener listener) { + if (listener.mNativeListener == 0) { + return; + } + nativeRegister(listener.mNativeListener); + } + + /** + * Unregisters a listener. + */ + public static void unregister(TunnelModeEnabledListener listener) { + if (listener.mNativeListener == 0) { + return; + } + nativeUnregister(listener.mNativeListener); + } + + /** + * Dispatch tunnel mode enabled. + * + * Called from native code on a binder thread. + */ + @VisibleForTesting + public static void dispatchOnTunnelModeEnabledChanged(TunnelModeEnabledListener listener, + boolean tunnelModeEnabled) { + listener.mExecutor.execute(() -> listener.onTunnelModeEnabledChanged(tunnelModeEnabled)); + } + + private static native long nativeCreate(TunnelModeEnabledListener thiz); + private static native void nativeDestroy(long ptr); + private static native void nativeRegister(long ptr); + private static native void nativeUnregister(long ptr); +} diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 1f805c94d51ce..146863342f471 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -128,6 +128,7 @@ cc_library_shared { "android_graphics_BLASTBufferQueue.cpp", "android_view_SurfaceSession.cpp", "android_view_TextureView.cpp", + "android_view_TunnelModeEnabledListener.cpp", "android_view_VelocityTracker.cpp", "android_view_VerifiedKeyEvent.cpp", "android_view_VerifiedMotionEvent.cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index f76cccb659731..7e8fc7e6ba4fb 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -127,6 +127,7 @@ extern int register_android_view_SurfaceControlHdrLayerInfoListener(JNIEnv* env) extern int register_android_view_SurfaceSession(JNIEnv* env); extern int register_android_view_CompositionSamplingListener(JNIEnv* env); extern int register_android_view_TextureView(JNIEnv* env); +extern int register_android_view_TunnelModeEnabledListener(JNIEnv* env); extern int register_android_database_CursorWindow(JNIEnv* env); extern int register_android_database_SQLiteConnection(JNIEnv* env); extern int register_android_database_SQLiteGlobal(JNIEnv* env); @@ -1521,6 +1522,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_view_SurfaceSession), REG_JNI(register_android_view_CompositionSamplingListener), REG_JNI(register_android_view_TextureView), + REG_JNI(register_android_view_TunnelModeEnabledListener), REG_JNI(register_com_google_android_gles_jni_EGLImpl), REG_JNI(register_com_google_android_gles_jni_GLImpl), REG_JNI(register_android_opengl_jni_EGL14), diff --git a/core/jni/android_view_TunnelModeEnabledListener.cpp b/core/jni/android_view_TunnelModeEnabledListener.cpp new file mode 100644 index 0000000000000..af7bae8c89ddb --- /dev/null +++ b/core/jni/android_view_TunnelModeEnabledListener.cpp @@ -0,0 +1,130 @@ +/* + * Copyright 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. + */ + +#define LOG_TAG "TunnelModeEnabledListener" + +#include "android_util_Binder.h" +#include "core_jni_helpers.h" + +#include + +#include +#include +#include +#include + +#include +#include + +namespace android { + +namespace { + +struct { + jclass mClass; + jmethodID mDispatchOnTunnelModeEnabledChanged; +} gListenerClassInfo; + +struct TunnelModeEnabledListener : public gui::BnTunnelModeEnabledListener { + TunnelModeEnabledListener(JNIEnv* env, jobject listener) + : mListener(env->NewWeakGlobalRef(listener)) {} + + binder::Status onTunnelModeEnabledChanged(bool tunnelModeEnabled) override { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + LOG_ALWAYS_FATAL_IF(env == nullptr, + "Unable to retrieve JNIEnv in onTunnelModeEnabledChanged."); + + jobject listener = env->NewGlobalRef(mListener); + if (listener == NULL) { + // Weak reference went out of scope + return binder::Status::ok(); + } + env->CallStaticVoidMethod(gListenerClassInfo.mClass, + gListenerClassInfo.mDispatchOnTunnelModeEnabledChanged, listener, + static_cast(tunnelModeEnabled)); + env->DeleteGlobalRef(listener); + + if (env->ExceptionCheck()) { + ALOGE("TunnelModeEnabledListener.onTunnelModeEnabledChanged() failed."); + LOGE_EX(env); + env->ExceptionClear(); + } + return binder::Status::ok(); + } + +protected: + virtual ~TunnelModeEnabledListener() { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->DeleteWeakGlobalRef(mListener); + } + +private: + jweak mListener; +}; + +jlong nativeCreate(JNIEnv* env, jclass clazz, jobject obj) { + TunnelModeEnabledListener* listener = new TunnelModeEnabledListener(env, obj); + listener->incStrong((void*)nativeCreate); + return reinterpret_cast(listener); +} + +void nativeDestroy(JNIEnv* env, jclass clazz, jlong ptr) { + TunnelModeEnabledListener* listener = reinterpret_cast(ptr); + listener->decStrong((void*)nativeCreate); +} + +void nativeRegister(JNIEnv* env, jclass clazz, jlong ptr) { + sp listener = reinterpret_cast(ptr); + if (SurfaceComposerClient::addTunnelModeEnabledListener(listener) != OK) { + constexpr auto error_msg = "Couldn't addTunnelModeEnabledListener"; + ALOGE(error_msg); + jniThrowRuntimeException(env, error_msg); + } +} + +void nativeUnregister(JNIEnv* env, jclass clazz, jlong ptr) { + sp listener = reinterpret_cast(ptr); + + if (SurfaceComposerClient::removeTunnelModeEnabledListener(listener) != OK) { + constexpr auto error_msg = "Couldn't removeTunnelModeEnabledListener"; + ALOGE(error_msg); + jniThrowRuntimeException(env, error_msg); + } +} + +const JNINativeMethod gMethods[] = { + /* name, signature, funcPtr */ + {"nativeCreate", "(Landroid/view/TunnelModeEnabledListener;)J", (void*)nativeCreate}, + {"nativeDestroy", "(J)V", (void*)nativeDestroy}, + {"nativeRegister", "(J)V", (void*)nativeRegister}, + {"nativeUnregister", "(J)V", (void*)nativeUnregister}}; + +} // namespace + +int register_android_view_TunnelModeEnabledListener(JNIEnv* env) { + int res = jniRegisterNativeMethods(env, "android/view/TunnelModeEnabledListener", gMethods, + NELEM(gMethods)); + LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods."); + + jclass clazz = env->FindClass("android/view/TunnelModeEnabledListener"); + gListenerClassInfo.mClass = MakeGlobalRefOrDie(env, clazz); + gListenerClassInfo.mDispatchOnTunnelModeEnabledChanged = + env->GetStaticMethodID(clazz, "dispatchOnTunnelModeEnabledChanged", + "(Landroid/view/TunnelModeEnabledListener;Z)V"); + return 0; +} + +} // namespace android diff --git a/core/tests/coretests/src/android/view/TunnelModeEnabledListenerTest.java b/core/tests/coretests/src/android/view/TunnelModeEnabledListenerTest.java new file mode 100644 index 0000000000000..65dd34f723c37 --- /dev/null +++ b/core/tests/coretests/src/android/view/TunnelModeEnabledListenerTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.MediumTest; +import androidx.test.runner.AndroidJUnit4; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@MediumTest +@Presubmit +public class TunnelModeEnabledListenerTest { + private TestableTunnelModeEnabledListener mListener; + + @Before + public void init() { + mListener = new TestableTunnelModeEnabledListener(); + } + + @Test + public void testRegisterUnregister() { + TunnelModeEnabledListener.register(mListener); + TunnelModeEnabledListener.unregister(mListener); + } + + @Test + public void testDispatchUpdatesListener() { + TunnelModeEnabledListener.dispatchOnTunnelModeEnabledChanged(mListener, true); + assertEquals(true, mListener.mTunnelModeEnabled.get()); + TunnelModeEnabledListener.dispatchOnTunnelModeEnabledChanged(mListener, false); + assertEquals(false, mListener.mTunnelModeEnabled.get()); + } + + @Test + public void testRegisterUpdatesListener() throws Exception { + TunnelModeEnabledListener.register(mListener); + TimeUnit.SECONDS.sleep(1); + assertTrue(mListener.mTunnelModeEnabledUpdated.get()); + TunnelModeEnabledListener.unregister(mListener); + } + + private static class TestableTunnelModeEnabledListener extends TunnelModeEnabledListener { + AtomicBoolean mTunnelModeEnabled; + AtomicBoolean mTunnelModeEnabledUpdated; + + TestableTunnelModeEnabledListener() { + super(Runnable::run); + mTunnelModeEnabled = new AtomicBoolean(false); + mTunnelModeEnabledUpdated = new AtomicBoolean(); + } + + @Override + public void onTunnelModeEnabledChanged(boolean tunnelModeEnabled) { + mTunnelModeEnabled.set(tunnelModeEnabled); + mTunnelModeEnabledUpdated.set(true); + } + } + +} diff --git a/services/core/java/com/android/server/wm/BlurController.java b/services/core/java/com/android/server/wm/BlurController.java index ff1016855287a..03639449c3df1 100644 --- a/services/core/java/com/android/server/wm/BlurController.java +++ b/services/core/java/com/android/server/wm/BlurController.java @@ -29,6 +29,7 @@ import android.os.RemoteCallbackList; import android.os.RemoteException; import android.provider.Settings; import android.view.ICrossWindowBlurEnabledListener; +import android.view.TunnelModeEnabledListener; /** * Keeps track of the different factors that determine whether cross-window blur is enabled @@ -45,6 +46,16 @@ final class BlurController { private volatile boolean mBlurEnabled; private boolean mInPowerSaveMode; private boolean mBlurDisabledSetting; + private boolean mTunnelModeEnabled = false; + + private TunnelModeEnabledListener mTunnelModeListener = + new TunnelModeEnabledListener(Runnable::run) { + @Override + public void onTunnelModeEnabledChanged(boolean tunnelModeEnabled) { + mTunnelModeEnabled = tunnelModeEnabled; + updateBlurEnabled(); + } + }; BlurController(Context context, PowerManager powerManager) { mContext = context; @@ -78,6 +89,8 @@ final class BlurController { }); mBlurDisabledSetting = getBlurDisabledSetting(); + TunnelModeEnabledListener.register(mTunnelModeListener); + updateBlurEnabled(); } @@ -99,7 +112,7 @@ final class BlurController { private void updateBlurEnabled() { synchronized (mLock) { final boolean newEnabled = CROSS_WINDOW_BLUR_SUPPORTED && !mBlurDisabledSetting - && !mInPowerSaveMode; + && !mInPowerSaveMode && !mTunnelModeEnabled; if (mBlurEnabled == newEnabled) { return; }