diff --git a/src/xrt/auxiliary/android/build.gradle b/src/xrt/auxiliary/android/build.gradle index 1e6d38d57..36903aca2 100644 --- a/src/xrt/auxiliary/android/build.gradle +++ b/src/xrt/auxiliary/android/build.gradle @@ -8,7 +8,7 @@ android { buildToolsVersion '30.0.2' defaultConfig { - minSdkVersion 20 + minSdkVersion 24 targetSdkVersion project.sharedTargetSdk } diff --git a/src/xrt/auxiliary/android/src/main/java/org/freedesktop/monado/auxiliary/MonadoView.java b/src/xrt/auxiliary/android/src/main/java/org/freedesktop/monado/auxiliary/MonadoView.java index 74c1e0166..8a6279634 100644 --- a/src/xrt/auxiliary/android/src/main/java/org/freedesktop/monado/auxiliary/MonadoView.java +++ b/src/xrt/auxiliary/android/src/main/java/org/freedesktop/monado/auxiliary/MonadoView.java @@ -10,6 +10,7 @@ package org.freedesktop.monado.auxiliary; import android.app.Activity; +import android.os.Build; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; @@ -19,6 +20,7 @@ import android.view.WindowManager; import androidx.annotation.Keep; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -43,9 +45,8 @@ public class MonadoView extends SurfaceView implements SurfaceHolder.Callback, S /// Guards currentSurfaceHolder private final Object currentSurfaceHolderSync = new Object(); - /// Guards the usedByNativeCode flag - private final Object usedByNativeCodeSync = new Object(); private final Method viewSetSysUiVis; + private final NativeCounterpart nativeCounterpart; public int width = -1; public int height = -1; @@ -53,15 +54,11 @@ public class MonadoView extends SurfaceView implements SurfaceHolder.Callback, S /// Guarded by currentSurfaceHolderSync private SurfaceHolder currentSurfaceHolder = null; - /// Guarded by usedByNativeCodeSync - private boolean usedByNativeCode = false; - /// Contains the pointer to the native android_custom_surface object. - private long nativePointer = 0; private MonadoView(Activity activity, long nativePointer) { super(activity); this.activity = activity; - this.nativePointer = nativePointer; + this.nativeCounterpart = new NativeCounterpart(nativePointer); Method method; try { method = activity.getWindow().getDecorView().getClass().getMethod("setSystemUiVisibility", int.class); @@ -133,10 +130,7 @@ public class MonadoView extends SurfaceView implements SurfaceHolder.Callback, S } } if (ret != null) { - synchronized (usedByNativeCodeSync) { - usedByNativeCode = true; - usedByNativeCodeSync.notifyAll(); - } + nativeCounterpart.markAsUsedByNativeCode(); } return ret; } @@ -149,16 +143,7 @@ public class MonadoView extends SurfaceView implements SurfaceHolder.Callback, S */ @Keep public void markAsDiscardedByNative() { - - synchronized (usedByNativeCodeSync) { - if (!usedByNativeCode) { - Log.w(TAG, "This should not have happened: Discarding by native code, but not marked as used!"); - } - usedByNativeCode = false; - nativePointer = 0; - usedByNativeCodeSync.notifyAll(); - } - + nativeCounterpart.markAsDiscardedByNative(TAG); } private boolean makeFullscreen() { @@ -184,6 +169,7 @@ public class MonadoView extends SurfaceView implements SurfaceHolder.Callback, S /** * Add a listener so that if our system UI display state doesn't include all we want, we re-apply. */ + @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB) @SuppressWarnings("deprecation") private void setSystemUiVisChangeListener() { activity.getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(visibility -> { @@ -234,16 +220,9 @@ public class MonadoView extends SurfaceView implements SurfaceHolder.Callback, S } if (lost) { //! @todo this function should notify native code that the surface is gone. - try { - synchronized (usedByNativeCodeSync) { - while (usedByNativeCode) { - usedByNativeCodeSync.wait(); - } - } - } catch (InterruptedException e) { - e.printStackTrace(); + if (!nativeCounterpart.blockUntilNativeDiscard(TAG)) { Log.i(TAG, - "Interrupted in surfaceDestroyed while waiting for native code to finish up: " + e.toString()); + "Interrupted in surfaceDestroyed while waiting for native code to finish up."); } } } diff --git a/src/xrt/auxiliary/android/src/main/java/org/freedesktop/monado/auxiliary/NativeCounterpart.java b/src/xrt/auxiliary/android/src/main/java/org/freedesktop/monado/auxiliary/NativeCounterpart.java new file mode 100644 index 000000000..ce15a92f2 --- /dev/null +++ b/src/xrt/auxiliary/android/src/main/java/org/freedesktop/monado/auxiliary/NativeCounterpart.java @@ -0,0 +1,139 @@ +// Copyright 2020, Collabora, Ltd. +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief Utility class to deal with having a native-code counterpart object + * @author Ryan Pavlik + * @ingroup aux_android_java + */ + +package org.freedesktop.monado.auxiliary; + +import android.util.Log; + +import androidx.annotation.CheckResult; +import androidx.annotation.NonNull; + +import java.security.InvalidParameterException; + +/** + * Object that tracks the native counterpart object for a type. Must be initialized on construction, + * and may have its native code destroyed/discarded, but may not "re-seat" it to new native code + * pointer. + *

+ * Use as a member of any type with a native counterpart (a native-allocated-and-owned object that + * holds a reference to the owning class). Include the following field and delegating method to use + * (note: assumes you have a tag for logging purposes as TAG) + *

+ *

+ * private final NativeCounterpart nativeCounterpart;
+ *
+ * @Keep
+ * public void markAsDiscardedByNative() {
+ *     nativeCounterpart.markAsDiscardedByNative(TAG);
+ * }
+ * 
+ * Then, initialize it in your constructor, call {@code markAsUsedByNativeCode()} where desired + * (often in your constructor), and call {@code getNativePointer()} and + * {@code blockUntilNativeDiscard()} as needed. + *

+ * Your native code can use this to turn a void* into a jlong: + * {@code static_cast(reinterpret_cast(nativePointer))} + */ +public final class NativeCounterpart { + /** + * Guards the usedByNativeCodeSync. + */ + private final Object usedByNativeCodeSync = new Object(); + + /** + * Indicates if the containing object is in use by native code. + *

+ * Guarded by usedByNativeCodeSync. + */ + private boolean usedByNativeCode = false; + + /** + * Contains the pointer to the native counterpart object. + */ + private long nativePointer = 0; + + /** + * Constructor + * + * @param nativePointer The native pointer, cast appropriately. Must be non-zero. Can cast like: + * {@code static_cast(reinterpret_cast(nativePointer))} + */ + public NativeCounterpart(long nativePointer) throws InvalidParameterException { + if (nativePointer == 0) { + throw new InvalidParameterException("nativePointer must not be 0"); + } + this.nativePointer = nativePointer; + } + + /** + * Set the flag to indicate that native code is using this. Only call this once, probably in + * your constructor unless you have a good reason to call it elsewhere. + */ + public void markAsUsedByNativeCode() { + synchronized (usedByNativeCodeSync) { + assert nativePointer != 0; + usedByNativeCode = true; + usedByNativeCodeSync.notifyAll(); + } + } + + /** + * Change the flag and notify those waiting on it, to indicate that native code is done with + * this object. + * + * @param TAG Your owning class's logging tag + */ + public void markAsDiscardedByNative(String TAG) { + synchronized (usedByNativeCodeSync) { + if (!usedByNativeCode) { + Log.w(TAG, + "This should not have happened: Discarding by native code, but not marked as used!"); + } + usedByNativeCode = false; + nativePointer = 0; + usedByNativeCodeSync.notifyAll(); + } + } + + /** + * Retrieve the native pointer value. Will be 0 if discarded by native code!. + * + * @return pointer (cast as a long) + */ + public long getNativePointer() { + synchronized (usedByNativeCodeSync) { + return nativePointer; + } + } + + /** + * Wait until {@code markAsDiscardedByNative} has been called indicating that the native code is + * done with this. Be sure to check the result! + * + * @param TAG Your owning class's logging tag + * @return true if this class has successfully been discarded by native. + */ + @CheckResult + public boolean blockUntilNativeDiscard(@NonNull String TAG) { + try { + synchronized (usedByNativeCodeSync) { + while (usedByNativeCode) { + usedByNativeCodeSync.wait(); + } + return true; + } + } catch (InterruptedException e) { + e.printStackTrace(); + Log.i(TAG, + "Interrupted while waiting for native code to finish up: " + e.toString()); + return false; + } + + } +}