aux/android: Factor out a "native counterpart" class.

This commit is contained in:
Ryan Pavlik 2020-11-09 14:22:23 -06:00
parent 2082eddb65
commit 5115124bb3
3 changed files with 149 additions and 31 deletions

View file

@ -8,7 +8,7 @@ android {
buildToolsVersion '30.0.2'
defaultConfig {
minSdkVersion 20
minSdkVersion 24
targetSdkVersion project.sharedTargetSdk
}

View file

@ -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.");
}
}
}

View file

@ -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 <ryan.pavlik@collabora.com>
* @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.
* <p>
* 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)
* <p>
* <pre>
* private final NativeCounterpart nativeCounterpart;
*
* &#64;Keep
* public void markAsDiscardedByNative() {
* nativeCounterpart.markAsDiscardedByNative(TAG);
* }
* </pre>
* Then, initialize it in your constructor, call {@code markAsUsedByNativeCode()} where desired
* (often in your constructor), and call {@code getNativePointer()} and
* {@code blockUntilNativeDiscard()} as needed.
* <p>
* Your native code can use this to turn a void* into a jlong:
* {@code static_cast<long long>(reinterpret_cast<intptr_t>(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.
* <p>
* 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<long long>(reinterpret_cast<intptr_t>(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;
}
}
}