Commit 92565b7f authored by zhibinw's avatar zhibinw Committed by Jakob Bornecrantz
Browse files

ipc/android: support create surface from runtime.

parent df9ebf26
......@@ -33,28 +33,12 @@ import java.util.Calendar;
public class MonadoView extends SurfaceView implements SurfaceHolder.Callback, SurfaceHolder.Callback2 {
private static final String TAG = "MonadoView";
@SuppressWarnings("deprecation")
private static final int sysUiVisFlags = 0
// Give us a stable view of content insets
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
// Be able to do fullscreen and hide navigation
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
// we want sticky immersive
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
@NonNull
private final Context context;
/// The activity we've connected to.
@Nullable
private final Activity activity;
@Nullable
private final Method viewSetSysUiVis;
private final Object currentSurfaceHolderSync = new Object();
public int width = -1;
......@@ -77,15 +61,12 @@ public class MonadoView extends SurfaceView implements SurfaceHolder.Callback, S
activity = null;
}
this.activity = activity;
viewSetSysUiVis = getSystemUiVisMethod();
}
public MonadoView(Activity activity) {
super(activity);
this.context = activity;
this.activity = activity;
viewSetSysUiVis = getSystemUiVisMethod();
}
private MonadoView(Activity activity, long nativePointer) {
......@@ -93,17 +74,6 @@ public class MonadoView extends SurfaceView implements SurfaceHolder.Callback, S
nativeCounterpart = new NativeCounterpart(nativePointer);
}
private static Method getSystemUiVisMethod() {
Method method;
try {
method = android.view.View.class.getMethod("setSystemUiVisibility", int.class);
} catch (NoSuchMethodException e) {
// ok
method = null;
}
return method;
}
/**
* Construct and start attaching a MonadoView to a client application.
*
......@@ -228,43 +198,6 @@ public class MonadoView extends SurfaceView implements SurfaceHolder.Callback, S
nativeCounterpart.markAsDiscardedByNative(TAG);
}
private boolean makeFullscreen() {
if (activity == null) {
return false;
}
if (viewSetSysUiVis == null) {
return false;
}
View decorView = activity.getWindow().getDecorView();
//! @todo implement with WindowInsetsController to ward off the stink of deprecation
try {
viewSetSysUiVis.invoke(decorView, sysUiVisFlags);
} catch (IllegalAccessException e) {
return false;
} catch (InvocationTargetException e) {
return false;
}
return true;
}
/**
* 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() {
if (activity == null) {
return;
}
activity.getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(visibility -> {
// If not fullscreen, fix it.
if (0 == (visibility & View.SYSTEM_UI_FLAG_FULLSCREEN)) {
makeFullscreen();
}
});
}
@Override
public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
synchronized (currentSurfaceHolderSync) {
......@@ -272,11 +205,6 @@ public class MonadoView extends SurfaceView implements SurfaceHolder.Callback, S
currentSurfaceHolderSync.notifyAll();
}
Log.i(TAG, "surfaceCreated: Got a surface holder!");
if (makeFullscreen()) {
// If we could make it full screen, make it really stick.
setSystemUiVisChangeListener();
}
}
@Override
......
// Copyright 2021, Qualcomm Innovation Center, Inc.
// SPDX-License-Identifier: BSL-1.0
/*!
* @file
* @brief Class to handle system ui visibility
* @author Jarvis Huang
* @ingroup aux_android_java
*/
package org.freedesktop.monado.auxiliary
import android.app.Activity
import android.os.Build
import android.view.View
import android.view.WindowInsets
import android.view.WindowInsetsController
import androidx.annotation.RequiresApi
/**
* Helper class that handles system ui visibility.
*/
class SystemUiController(activity: Activity) {
private val impl: Impl = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
WindowInsetsControllerImpl(activity)
} else {
SystemUiVisibilityImpl(activity)
}
/**
* Hide system ui and make fullscreen.
*/
fun hide() {
impl.hide()
}
private abstract class Impl(var activity: Activity) {
abstract fun hide()
fun runOnUiThread(runnable: Runnable) {
activity.runOnUiThread(runnable)
}
}
@Suppress("DEPRECATION")
private class SystemUiVisibilityImpl(activity: Activity) : Impl(activity) {
override fun hide() {
activity.runOnUiThread {
activity.window.decorView.systemUiVisibility = FLAG_FULL_SCREEN_IMMERSIVE_STICKY
}
}
companion object {
private const val FLAG_FULL_SCREEN_IMMERSIVE_STICKY =
// Give us a stable view of content insets
(View.SYSTEM_UI_FLAG_LAYOUT_STABLE // Be able to do fullscreen and hide navigation
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_FULLSCREEN
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // we want sticky immersive
or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
}
init {
runOnUiThread {
activity.window.decorView.setOnSystemUiVisibilityChangeListener { visibility: Int ->
// If not fullscreen, fix it.
if (0 == visibility and View.SYSTEM_UI_FLAG_FULLSCREEN) {
hide()
}
}
}
}
}
@RequiresApi(api = Build.VERSION_CODES.R)
private class WindowInsetsControllerImpl(activity: Activity) : Impl(activity) {
override fun hide() {
activity.runOnUiThread {
val controller = activity.window.insetsController
controller!!.hide(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars())
controller.systemBarsBehavior =
WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
}
init {
runOnUiThread {
activity.window.insetsController!!.addOnControllableInsetsChangedListener { _: WindowInsetsController?, typeMask: Int ->
if (typeMask and WindowInsets.Type.statusBars() == 1 || typeMask and WindowInsets.Type.navigationBars() == 1) {
hide()
}
}
}
}
}
}
......@@ -22,4 +22,14 @@ interface IMonado {
* Provide the surface we inject into the activity, back to the service.
*/
void passAppSurface(in Surface surface);
/*!
* Asking service to create surface and attach it to the display matches given display id.
*/
boolean createSurface(int displayId, boolean focusable);
/*!
* Asking service whether it has the capbility to draw over other apps or not.
*/
boolean canDrawOverOtherApps();
}
......@@ -20,12 +20,16 @@ import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.WindowManager;
import androidx.annotation.Keep;
import androidx.annotation.Nullable;
import org.freedesktop.monado.auxiliary.MonadoView;
import org.freedesktop.monado.auxiliary.NativeCounterpart;
import org.freedesktop.monado.auxiliary.SystemUiController;
import java.io.IOException;
......@@ -38,9 +42,9 @@ import java.io.IOException;
public class Client implements ServiceConnection {
private static final String TAG = "monado-ipc-client";
/**
* Used to block native until we have our side of the socket pair.
* Used to block until binder is ready.
*/
private final Object connectSync = new Object();
private final Object binderSync = new Object();
/**
* Keep track of the ipc_client_android instance over on the native side.
*/
......@@ -76,8 +80,10 @@ public class Client implements ServiceConnection {
* Intent for connecting to service
*/
private Intent intent = null;
private SurfaceHolder surfaceHolder;
/**
* Controll system ui visibility
*/
private SystemUiController systemUiController = null;
/**
* Constructor
......@@ -124,6 +130,8 @@ public class Client implements ServiceConnection {
* <p>
* The IPC client code on Android should load this class (from the right package), instantiate
* this class (retaining a reference to it!), and call this method.
* <p>
* This method must not be called from the main (UI) thread.
*
* @param context_ Context to use to make the connection. (We get the application context
* from it.)
......@@ -140,26 +148,75 @@ public class Client implements ServiceConnection {
public int blockingConnect(Context context_, String packageName) {
Log.i(TAG, "blockingConnect");
Activity activity = (Activity) context_;
MonadoView monadoView = MonadoView.attachToActivity(activity);
surfaceHolder = monadoView.waitGetSurfaceHolder(2000);
synchronized (connectSync) {
synchronized (binderSync) {
if (!bind(context_, packageName)) {
Log.e(TAG, "Bind failed immediately");
// Bind failed immediately
return -1;
}
try {
while (fd == null) {
connectSync.wait();
}
binderSync.wait();
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted: " + e.toString());
return -1;
}
}
if (monado == null) {
Log.e(TAG, "Invalid binder object");
return -1;
}
boolean surfaceCreated = false;
Activity activity = (Activity) context_;
try {
// Determine whether runtime or client should create surface
if (monado.canDrawOverOtherApps()) {
WindowManager wm = (WindowManager) context_.getSystemService(Context.WINDOW_SERVICE);
surfaceCreated = monado.createSurface(wm.getDefaultDisplay().getDisplayId(), false);
} else {
Surface surface = attachViewAndGetSurface(activity);
surfaceCreated = (surface != null);
if (surfaceCreated) {
monado.passAppSurface(surface);
}
}
} catch (RemoteException e) {
e.printStackTrace();
}
if (!surfaceCreated) {
Log.e(TAG, "Failed to create surface");
handleFailure();
return -1;
}
systemUiController = new SystemUiController(activity);
systemUiController.hide();
// Create socket pair
ParcelFileDescriptor theirs;
ParcelFileDescriptor ours;
try {
ParcelFileDescriptor[] fds = ParcelFileDescriptor.createSocketPair();
ours = fds[0];
theirs = fds[1];
monado.connect(theirs);
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "could not create socket pair: " + e.toString());
handleFailure();
return -1;
} catch (RemoteException e) {
e.printStackTrace();
Log.e(TAG, "could not connect to service: " + e.toString());
handleFailure();
return -1;
}
fd = ours;
Log.i(TAG, "Socket fd " + fd.getFd());
return fd.getFd();
}
......@@ -221,12 +278,20 @@ public class Client implements ServiceConnection {
shutdown();
}
@Nullable
private Surface attachViewAndGetSurface(Activity activity) {
MonadoView monadoView = MonadoView.attachToActivity(activity);
SurfaceHolder holder = monadoView.waitGetSurfaceHolder(2000);
Surface surface = null;
if (holder != null) {
surface = holder.getSurface();
}
return surface;
}
/**
* Handle the asynchronous connection of the binder IPC.
* <p>
* This sets up the class member `monado`, as well as the member `fd`. It calls
* `IMonado.connect()` automatically. The client still needs to call `IMonado.passAppSurface()`
* on `monado`.
*
* @param name should match the intent above, but not used.
* @param service the associated service, which we cast in this function.
......@@ -234,40 +299,10 @@ public class Client implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected");
monado = IMonado.Stub.asInterface(service);
try {
monado.passAppSurface(surfaceHolder.getSurface());
} catch (RemoteException e) {
e.printStackTrace();
Log.e(TAG, "Could not pass app surface: " + e.toString());
}
ParcelFileDescriptor theirs;
ParcelFileDescriptor ours;
try {
ParcelFileDescriptor[] fds = ParcelFileDescriptor.createSocketPair();
ours = fds[0];
theirs = fds[1];
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "could not create socket pair: " + e.toString());
handleFailure();
return;
}
try {
monado.connect(theirs);
} catch (RemoteException e) {
e.printStackTrace();
Log.e(TAG, "could not connect to service: " + e.toString());
handleFailure();
return;
}
synchronized (connectSync) {
Log.e(TAG, String.format("Notifying connectSync with fd %d", ours.getFd()));
fd = ours;
connectSync.notify();
synchronized (binderSync) {
monado = IMonado.Stub.asInterface(service);
binderSync.notify();
}
}
......
......@@ -13,6 +13,7 @@ package org.freedesktop.monado.ipc;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
......@@ -46,6 +47,27 @@ public class MonadoImpl extends IMonado.Stub {
"CompositorThread");
private boolean started = false;
private SurfaceManager surfaceManager;
public MonadoImpl(@NonNull SurfaceManager surfaceManager) {
this.surfaceManager = surfaceManager;
this.surfaceManager.setCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
Log.i(TAG, "surfaceCreated");
nativeAppSurface(holder.getSurface());
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
}
});
}
private void launchThreadIfNeeded() {
synchronized (compositorThread) {
if (!started) {
......@@ -85,6 +107,18 @@ public class MonadoImpl extends IMonado.Stub {
nativeAppSurface(surface);
}
@Override
public boolean createSurface(int displayId, boolean focusable) {
Log.i(TAG, "createSurface");
return surfaceManager.createSurfaceOnDisplay(displayId, focusable);
}
@Override
public boolean canDrawOverOtherApps() {
Log.i(TAG, "canDrawOverOtherApps");
return surfaceManager.canDrawOverlays();
}
private void threadEntry() {
Log.i(TAG, "threadEntry");
nativeThreadEntry();
......
......@@ -26,11 +26,27 @@ import javax.inject.Inject
*/
@AndroidEntryPoint
class MonadoService : Service() {
private val binder = MonadoImpl()
private val binder: MonadoImpl by lazy {
MonadoImpl(surfaceManager)
}
@Inject
lateinit var serviceNotification: IServiceNotification
private lateinit var surfaceManager: SurfaceManager
override fun onCreate() {
super.onCreate()
surfaceManager = SurfaceManager(this)
}
override fun onDestroy() {
super.onDestroy()
surfaceManager.destroySurface()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(TAG, "onStartCommand")
// if this isn't a restart
......
// Copyright 2021, Qualcomm Innovation Center, Inc.
// SPDX-License-Identifier: BSL-1.0
/*!
* @file
* @brief Class that manages surface
* @author Jarvis Huang
* @ingroup ipc_android
*/
package org.freedesktop.monado.ipc
import android.content.Context
import android.hardware.display.DisplayManager
import android.os.Handler
import android.os.Looper
import android.provider.Settings
import android.util.Log
import android.view.Display
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.view.WindowManager
import androidx.annotation.UiThread
import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.Condition
import java.util.concurrent.locks.ReentrantLock
/**
* Class that creates/manages surface on display.
*/
class SurfaceManager(context: Context) : SurfaceHolder.Callback {
private val appContext: Context = context.applicationContext
private val surfaceLock: ReentrantLock = ReentrantLock()
private val surfaceCondition: Condition = surfaceLock.newCondition()
private var callback: SurfaceHolder.Callback? = null
private val uiHandler: Handler = Handler(Looper.getMainLooper())
private val viewHelper: ViewHelper = ViewHelper(this)
override fun surfaceCreated(holder: SurfaceHolder) {
Log.i(TAG, "surfaceCreated")
callback?.surfaceCreated(holder)
surfaceLock.lock()
surfaceCondition.signal()
surfaceLock.unlock()
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
Log.i(TAG, "surfaceChanged, size: " + width + "x" + height)
callback?.surfaceChanged(holder, format, width, height)
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
Log.i(TAG, "surfaceDestroyed")
callback?.surfaceDestroyed(holder)
}
/**
* Register a callback for surface status.