Skip to content

Commit

Permalink
Clean up launch of Vision Glass
Browse files Browse the repository at this point in the history
Structure the code so it responds to changes in connected devices,
USB permissions, etc.
  • Loading branch information
felipeerias committed Jan 22, 2024
1 parent 70d9aad commit 77af9f8
Showing 1 changed file with 132 additions and 71 deletions.
203 changes: 132 additions & 71 deletions app/src/visionglass/java/com/igalia/wolvic/PlatformActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,21 @@

package com.igalia.wolvic;

import androidx.activity.ComponentActivity;

import androidx.annotation.Keep;

import android.annotation.SuppressLint;
import android.app.Presentation;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.hardware.display.DisplayManager;
import android.content.IntentFilter;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.usb.UsbDevice;
import android.hardware.display.DisplayManager;
import android.opengl.GLSurfaceView;
import android.os.Build;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.KeyEvent;
Expand All @@ -34,21 +30,28 @@
import android.view.WindowManager;
import android.widget.Button;
import android.widget.RelativeLayout;
import android.widget.TextView;

import androidx.activity.ComponentActivity;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.lifecycle.Lifecycle;

import com.huawei.usblib.DisplayMode;
import com.huawei.usblib.DisplayModeCallback;
import com.huawei.usblib.OnConnectionListener;
import com.huawei.usblib.VisionGlass;
import com.igalia.wolvic.utils.SystemUtils;

import java.util.ArrayList;
import java.util.HashMap;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class PlatformActivity extends ComponentActivity implements SensorEventListener {
public class PlatformActivity extends ComponentActivity implements SensorEventListener, OnConnectionListener {
static String LOGTAG = SystemUtils.createLogtag(PlatformActivity.class);

private boolean mWasImuStarted;
private boolean mIsAskingForPermission;
private DisplayManager mDisplayManager;
private Display mPresentationDisplay;
private VisionGlassPresentation mActivePresentation;
Expand Down Expand Up @@ -92,54 +95,103 @@ protected Intent getStoreIntent() {
};
private final Runnable activityResumedRunnable = this::activityResumed;

private final BroadcastReceiver mUsbPermissionReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.d(LOGTAG, "mUsbPermissionReceiver onReceive: " + intent.toString());

// debug
Log.d(LOGTAG, " Action: " + intent.getAction());
Log.d(LOGTAG, " Data: " + intent.getData());
if (intent.getExtras() != null) {
for (String key : intent.getExtras().keySet()) {
Log.d(LOGTAG, " Extra " + key + ": " + intent.getExtras().get(key));
}
}

Log.d(LOGTAG, "mUsbPermissionReceiver : mWasImuStarted = " + mWasImuStarted + " mIsAskingForPermission = " + mIsAskingForPermission);
initVisionGlass();
}
};

@SuppressLint("UnspecifiedRegisterReceiverFlag")
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.d(LOGTAG, "PlatformActivity onCreate");
super.onCreate(savedInstanceState);

mDisplayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);

IntentFilter usbPermissionFilter = new IntentFilter();
usbPermissionFilter.addAction("com.huawei.usblib.USB_PERMISSION");
// usbPermissionFilter.addAction("android.hardware.usb.action.USB_DEVICE_ATTACHED");
// usbPermissionFilter.addAction("android.hardware.usb.action.USB_DEVICE_DETACHED");
registerReceiver(mUsbPermissionReceiver, usbPermissionFilter);

VisionGlass.getInstance().init(getApplication());
VisionGlass.getInstance().setOnConnectionListener(this);
initVisionGlass();
}

mDisplayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
boolean wasImuStarted = false;
boolean isAskingForPermission = false;
do {
if (VisionGlass.getInstance().isConnected()) {
if (VisionGlass.getInstance().hasUsbPermission()) {
Log.d(LOGTAG, "Device has USB permission -> registering callback for startImu");
wasImuStarted = true;
VisionGlass.getInstance().startImu((w, x, y, z) -> queueRunnable(() -> setHead(x, y, z, w)));
} else {
if (isAskingForPermission) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
Log.w(LOGTAG, "Device does not have USB permission -> asking");
VisionGlass.getInstance().requestUsbPermission();
isAskingForPermission = true;
}
}
} else {
// TODO: show a dialog asking the user to put on the glasses
Log.e(LOGTAG, "Device not connected" + (VisionGlass.getInstance().hasUsbPermission() ? " and does NOT have USB permissions" : ""));

@Override
public void onConnectionChange(boolean b) {
if (VisionGlass.getInstance().isConnected()) {
Log.d(LOGTAG, "onConnectionChange: Device connected");
initVisionGlass();
} else {
Log.d(LOGTAG, "onConnectionChange: Device disconnected");
if (mWasImuStarted) {
Log.d(LOGTAG, "onConnectionChange: Finish activity");
finish();
}
}
}

private void initVisionGlass() {
Log.d(LOGTAG, "initVisionGlass");

if (mWasImuStarted) {
Log.d(LOGTAG, "Duplicated call to init the Vision Glass system");
updateDisplays();
return;
}

if (!VisionGlass.getInstance().isConnected()) {
Log.d(LOGTAG, "Glasses not connected yet");
return;
}

if (VisionGlass.getInstance().hasUsbPermission()) {
mIsAskingForPermission = false;
} else {
if (!mIsAskingForPermission) {
Log.d(LOGTAG, "Asking for USB permission");
mIsAskingForPermission = true;
VisionGlass.getInstance().requestUsbPermission();
}
} while (!wasImuStarted);
return;
}

Log.d(LOGTAG, "Start IMU");

mWasImuStarted = true;
VisionGlass.getInstance().startImu((w, x, y, z) -> queueRunnable(() -> setHead(x, y, z, w)));

VisionGlass.getInstance().setDisplayMode(DisplayMode.vr3d, new DisplayModeCallback() {
@Override
public void onSuccess(DisplayMode displayMode) { Log.d(LOGTAG, "Successfully switched to 3D mode"); }
public void onSuccess(DisplayMode displayMode) {
Log.d(LOGTAG, "Successfully switched to 3D mode");
updateDisplays();
}

@Override
public void onError(String s, int i) {
Log.d(LOGTAG, "Error " + i + " failed to switch to 3D mode " + s);
}
});

mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);

setContentView(R.layout.visionglass_layout);

View touchpad = findViewById(R.id.touchpad);
Expand All @@ -148,7 +200,7 @@ public void onError(String s, int i) {
layoutParams.height = getResources().getDisplayMetrics().widthPixels;
touchpad.setLayoutParams(layoutParams);

touchpad.setOnClickListener((View.OnClickListener) v -> {
touchpad.setOnClickListener(v -> {
// We don't really need the coordinates of the click because we use the position
// of the aim in the 3D environment.
queueRunnable(() -> touchEvent(false, 0, 0));
Expand Down Expand Up @@ -178,22 +230,19 @@ public void onError(String s, int i) {
Button homeButton = findViewById(R.id.home_button);

// Set click listeners for the buttons
backButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
}
});
backButton.setOnClickListener(v -> onBackPressed());

homeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Handle home button click
Log.d(LOGTAG, "Home button clicked");
}
homeButton.setOnClickListener(v -> {
// Handle home button click
Log.d(LOGTAG, "Home button clicked");
});

getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

// Show the app
if (getLifecycle().getCurrentState() == Lifecycle.State.RESUMED) {
updateDisplays();
}
}

// SensorEventListener overrides
Expand Down Expand Up @@ -236,12 +285,16 @@ public boolean onGenericMotionEvent(MotionEvent aEvent) {
protected void onPause() {
Log.d(LOGTAG, "PlatformActivity onPause");
super.onPause();
synchronized (mRenderLock) {
queueRunnable(activityPausedRunnable);
try {
mRenderLock.wait();
} catch(InterruptedException e) {
Log.e(LOGTAG, "activityPausedRunnable interrupted: " + e.toString());

// TODO This check is needed to prevent a crash when pausing before 3D mode has started.
if (mWasImuStarted) {
synchronized (mRenderLock) {
queueRunnable(activityPausedRunnable);
try {
mRenderLock.wait();
} catch(InterruptedException e) {
Log.e(LOGTAG, "activityPausedRunnable interrupted: " + e);
}
}
}
if (mActivePresentation != null)
Expand All @@ -258,7 +311,6 @@ protected void onResume() {
super.onResume();

updateDisplays();
showPresentation();

if (mActivePresentation != null && mActivePresentation.mGLView != null)
mActivePresentation.mGLView.onResume();
Expand All @@ -275,12 +327,14 @@ protected void onResume() {
protected void onDestroy() {
Log.d(LOGTAG, "PlatformActivity onDestroy");
super.onDestroy();
unregisterReceiver(mUsbPermissionReceiver);
synchronized (mRenderLock) {
queueRunnable(activityDestroyedRunnable);
try {
mRenderLock.wait();
} catch(InterruptedException e) {
Log.e(LOGTAG, "activityDestroyedRunnable interrupted: " + e.toString());
if (queueRunnable(activityDestroyedRunnable)) {
try {
mRenderLock.wait();
} catch (InterruptedException e) {
Log.e(LOGTAG, "activityDestroyedRunnable interrupted: " + e.toString());
}
}
}
}
Expand All @@ -306,17 +360,20 @@ void setImmersiveSticky() {
}
}

void queueRunnable(Runnable aRunnable) {
boolean queueRunnable(@NonNull Runnable aRunnable) {
if (mActivePresentation != null) {
mActivePresentation.mGLView.queueEvent(aRunnable);
return true;
} else {
synchronized (mPendingEvents) {
mPendingEvents.add(aRunnable);
}
if (mActivePresentation != null) {
notifyPendingEvents();
return true;
}
}
return false;
}

private void notifyPendingEvents() {
Expand All @@ -332,26 +389,33 @@ private void updateDisplays() {
Display[] displays = mDisplayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
if (displays.length == 0) {
mPresentationDisplay = null;
Log.d(LOGTAG, "updateDisplays: could not find a Presentation display");
return;
}

Log.d(LOGTAG, "updateDisplays: found Presentation display");
mPresentationDisplay = displays[0];

runOnUiThread(this::showPresentation);
}

private final DisplayManager.DisplayListener mDisplayListener =
new DisplayManager.DisplayListener() {
@Override
public void onDisplayAdded(int displayId) {
Log.d(LOGTAG, "display listener: onDisplayAdded");
updateDisplays();
}

@Override
public void onDisplayChanged(int displayId) {
Log.d(LOGTAG, "display listener: onDisplayChanged");
updateDisplays();
}

@Override
public void onDisplayRemoved(int displayId) {
Log.d(LOGTAG, "display listener: onDisplayRemoved");
updateDisplays();
}
};
Expand Down Expand Up @@ -392,6 +456,7 @@ public VisionGlassPresentation(Context context, Display display) {
* Sets the preferred display mode id for the presentation.
*/
public void setPreferredDisplayMode(int modeId) {
Log.d(LOGTAG, "VisionGlassPresentation setPreferredDisplayMode: " + modeId);
WindowManager.LayoutParams params = getWindow().getAttributes();
params.preferredDisplayModeId = modeId;
getWindow().setAttributes(params);
Expand All @@ -402,10 +467,6 @@ protected void onCreate(Bundle savedInstanceState) {
// Be sure to call the super class.
super.onCreate(savedInstanceState);

// Get the resources for the context of the presentation.
// Notice that we are getting the resources from the context of the presentation.
Resources r = getContext().getResources();

// Inflate the layout.
setContentView(R.layout.visionglass_presentation_layout);

Expand Down Expand Up @@ -437,7 +498,7 @@ public void onDrawFrame(GL10 gl) {
@Keep
@SuppressWarnings("unused")
private void setRenderMode(final int aMode) {
runOnUiThread(() -> setImmersiveSticky());
runOnUiThread(this::setImmersiveSticky);
}

private native void activityCreated(Object aAssetManager);
Expand Down

0 comments on commit 77af9f8

Please sign in to comment.