Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions app/src/aosp/java/com/igalia/wolvic/PlatformActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import android.content.Intent;
import android.view.KeyEvent;

import android.bluetooth.BluetoothDevice;

import com.google.androidgamesdk.GameActivity;
import com.igalia.wolvic.ui.widgets.WidgetManagerDelegate;

Expand Down Expand Up @@ -49,6 +51,12 @@ public boolean onKeyUp(int keyCode, KeyEvent event) {
return super.onKeyUp(keyCode, event);
}

protected void handleBluetoothHidDeviceConnected(BluetoothDevice device) {
}

protected void handleBluetoothHidDeviceDisconnected(BluetoothDevice device) {
}

protected native void queueRunnable(Runnable aRunnable);
protected native boolean platformExit();
}
98 changes: 98 additions & 0 deletions app/src/common/shared/com/igalia/wolvic/VRBrowserActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@

import static com.igalia.wolvic.ui.widgets.UIWidget.REMOVE_WIDGET;

import android.Manifest;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Color;
Expand All @@ -30,15 +34,21 @@
import android.os.Process;
import android.util.Log;
import android.util.Pair;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentController;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
Expand Down Expand Up @@ -76,6 +86,7 @@
import com.igalia.wolvic.ui.widgets.AppServicesProvider;
import com.igalia.wolvic.ui.widgets.HorizontalTabsBar;
import com.igalia.wolvic.ui.widgets.KeyboardWidget;
import com.igalia.wolvic.ui.widgets.MousePointerWidget;
import com.igalia.wolvic.ui.widgets.NavigationBarWidget;
import com.igalia.wolvic.ui.widgets.OverlayContentWidget;
import com.igalia.wolvic.ui.widgets.RootWidget;
Expand Down Expand Up @@ -159,6 +170,48 @@ public void onReceive(Context context, Intent intent) {
}
};

private BroadcastReceiver mBluetoothReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.d(LOGTAG, "Bluetooth receiver triggered: " + intent.getAction());
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (device == null) {
Log.e(LOGTAG, "Bluetooth device is null");
return;
}
if (!isHidDevice(device)) {
Log.d(LOGTAG, "Bluetooth device is not a HID device: " + device.getName());
return;
}
if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(intent.getAction())) {
handleBluetoothHidDeviceDisconnected(device);
} else if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(intent.getAction())) {
handleBluetoothHidDeviceConnected(device);
} else {
Log.d(LOGTAG, "Bluetooth device action not recognized: " + intent.getAction());
}
}
};

// Helper method to check if a device is likely a HID device
private boolean isHidDevice(BluetoothDevice device) {
Log.d(LOGTAG, "Checking if device is HID: " + device.getName());

// This is a basic check. We might need more sophisticated logic
// based on the device's BluetoothClass or supported UUIDs.
if (device.getBluetoothClass() != null) {
BluetoothClass bluetoothClass = device.getBluetoothClass();
Log.d(LOGTAG, "Bluetooth class: " + bluetoothClass.toString());
int deviceClass = bluetoothClass.getDeviceClass();
Log.d(LOGTAG, "Bluetooth device class: " + deviceClass);
return bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_HID)
&& deviceClass == BluetoothClass.Device.PERIPHERAL_POINTING;
} else {
Log.d(LOGTAG, "Bluetooth device class is null");
}
return false;
}

private LifecycleRegistry mLifeCycle;

@NonNull
Expand Down Expand Up @@ -289,6 +342,47 @@ protected void attachBaseContext(Context base) {
super.attachBaseContext(newContext);
}

private void registerBluetoothReceiver() {
Log.d(LOGTAG, "Registering Bluetooth receiver");
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
registerReceiver(mBluetoothReceiver, intentFilter, null, null, Context.RECEIVER_NOT_EXPORTED);
} else {
registerReceiver(mBluetoothReceiver, intentFilter, null, null);
}
}

private void checkAndRequestBluetoothConnectPermission() {
Log.d(LOGTAG, "Checking for BLUETOOTH_CONNECT permission");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // Check for Android 12+
if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED) {
// Permission is already granted
Log.d(LOGTAG, "BLUETOOTH_CONNECT permission already granted.");
registerBluetoothReceiver();
} else {
// Permission is not granted, request it
Log.d(LOGTAG, "Requesting BLUETOOTH_CONNECT permission.");
requestPermission(null, Manifest.permission.BLUETOOTH_CONNECT, OriginatorType.APPLICATION, new WSession.PermissionDelegate.Callback() {
@Override
public void grant() {
registerBluetoothReceiver();
}

@Override
public void reject() {
Log.w(LOGTAG, "BLUETOOTH_CONNECT permission denied.");
}
});
}
} else {
// On older Android versions, BLUETOOTH and BLUETOOTH_ADMIN are sufficient
Log.d(LOGTAG, "Running on Android version < 12. No runtime BLUETOOTH_CONNECT permission needed.");
registerBluetoothReceiver(); // Register the receiver directly
}
}

@Override
protected void onCreate(Bundle savedInstanceState) {
mFragmentController = FragmentController.createController(new FragmentControllerCallbacks(this, new Handler(Looper.getMainLooper()), 0));
Expand Down Expand Up @@ -405,6 +499,8 @@ protected void onCreate(Bundle savedInstanceState) {
else
setPointerMode(mSettings.getPointerMode());

checkAndRequestBluetoothConnectPermission();

// Show the launch dialogs, if needed.
if (!showTermsServiceDialogIfNeeded()) {
if (!showPrivacyDialogIfNeeded()) {
Expand Down Expand Up @@ -500,6 +596,8 @@ public void onIsWindowFullscreenChanged(boolean isFullscreen) {

addWidgets(Arrays.asList(mRootWidget, mNavigationBar, mKeyboard, mTray, mTabsBar, mWebXRInterstitial));



// Create the platform plugin after widgets are created to be extra safe.
mPlatformPlugin = createPlatformPlugin(this);
if (mPlatformPlugin != null)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package com.igalia.wolvic.ui.widgets;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;

import androidx.annotation.NonNull;
import androidx.databinding.DataBindingUtil;
import androidx.databinding.ObservableBoolean;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;

import com.igalia.wolvic.R;
import com.igalia.wolvic.VRBrowserActivity;
import com.igalia.wolvic.browser.engine.SessionStore;
import com.igalia.wolvic.ui.viewmodel.WindowViewModel;
import com.igalia.wolvic.utils.SystemUtils;

public class MousePointerWidget extends UIWidget {

private static final String LOGTAG = SystemUtils.createLogtag(MousePointerWidget.class);

private WindowWidget mAttachedWindow;
private boolean mIsWindowAttached;

private Paint mPaint;
private Path mPointerPath;

private float mCurrentX = 0;
private float mCurrentY = 0;

public MousePointerWidget(Context aContext) {
super(aContext);
initialize(aContext);
}

public MousePointerWidget(Context aContext, AttributeSet aAttrs) {
super(aContext, aAttrs);
initialize(aContext);
}

public MousePointerWidget(Context aContext, AttributeSet aAttrs, int aDefStyle) {
super(aContext, aAttrs, aDefStyle);
initialize(aContext);
}

private void initialize(@NonNull Context aContext) {
updateUI();
}

private void updateUI() {
removeAllViews();

// Initialize paint for drawing the pointer
mPaint = new Paint();
mPaint.setColor(Color.BLACK); // Or any color you prefer
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);

// Initialize path for the pointer shape (e.g., a triangle)
mPointerPath = new Path();
// Define the shape of your mouse pointer here
// Example: a simple triangle pointing right-down
mPointerPath.moveTo(0, 0);
mPointerPath.lineTo(20, 10);
mPointerPath.lineTo(10, 20);
mPointerPath.close();

// Set initial visibility (you might want to hide it initially)
mWidgetPlacement.visible = false;
}

@Override
protected void initializeWidgetPlacement(WidgetPlacement aPlacement) {
aPlacement.width = 8;
aPlacement.height = 8;
aPlacement.cylinder = false;
}

@Override
protected void onDraw(Canvas canvas) {
Log.d(LOGTAG, "onDraw: " + mCurrentX + ", " + mCurrentY);
super.onDraw(canvas);

// Draw the mouse pointer shape on the canvas
canvas.drawPath(mPointerPath, mPaint);
}

// Method to update the pointer's position based on mouse events
// Method to update the pointer's position based on mouse events
public void updatePointerPosition(float x, float y) {
Log.d(LOGTAG, "updatePointerPosition: " + x + ", " + y);
mCurrentX = x;
mCurrentY = y;

// Update the widget's placement directly
// You'll need to translate screen coordinates (from MotionEvent)
// to your VR scene coordinates. This will depend on your VR rendering setup.
// For a simple overlay, you might just set the translation directly.

// Example (conceptual):
mWidgetPlacement.translationX = mCurrentX;
mWidgetPlacement.translationY = mCurrentY;
// mWidgetPlacement.translationZ = ... // Adjust Z if needed

// Notify the WidgetManager that the placement has changed
if (mWidgetManager != null) {
mWidgetManager.updateWidget(this);
}
}

// You might need methods to show and hide the pointer
public void showPointer() {
Log.d(LOGTAG, "showPointer");
mWidgetPlacement.visible = true;
mWidgetManager.updateWidget(this);
}

public void hidePointer() {
Log.d(LOGTAG, "hidePointer");
mWidgetPlacement.visible = false;
mWidgetManager.updateWidget(this);
}

@Override
public void show(@ShowFlags int aShowFlags) {
Log.d(LOGTAG, "show: " + aShowFlags);
if (!mWidgetPlacement.visible) {
mWidgetPlacement.visible = true;
mWidgetManager.addWidget(this);
}
}

@Override
public void hide(@HideFlags int aHideFlags) {
Log.d(LOGTAG, "hide: " + aHideFlags);
if (mWidgetPlacement.visible) {
mWidgetPlacement.visible = false;
if (aHideFlags == REMOVE_WIDGET) {
mWidgetManager.removeWidget(this);
} else {
mWidgetManager.updateWidget(this);
}
}
}

@Override
public void detachFromWindow() {
Log.d(LOGTAG, "detachFromWindow");
mWidgetPlacement.parentHandle = -1;

mIsWindowAttached = false;
}

@Override
public void attachToWindow(@NonNull WindowWidget aWindow) {
Log.d(LOGTAG, "attachToWindow: " + aWindow);
if (mAttachedWindow == aWindow) {
return;
}
detachFromWindow();

mAttachedWindow = aWindow;
mWidgetPlacement.parentHandle = aWindow.getHandle();


mIsWindowAttached = true;
}

Observer<ObservableBoolean> mIsVisibleObserver = aVisible -> {
if (aVisible.get()) {
this.show(REQUEST_FOCUS);

} else {
this.hide(UIWidget.KEEP_WIDGET);
}

mWidgetManager.updateWidget(MousePointerWidget.this);
};
}
9 changes: 9 additions & 0 deletions app/src/hvr/java/com/igalia/wolvic/PlatformActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package com.igalia.wolvic;

import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
Expand Down Expand Up @@ -382,6 +383,14 @@ public void surfaceDestroyed(SurfaceHolder holder)
queueRunnable(this::nativeOnSurfaceDestroyed);
}

protected void handleBluetoothHidDeviceConnected(BluetoothDevice device) {
Log.d(TAG, "Bluetooth HID device connected: " + device.getName());
}

protected void handleBluetoothHidDeviceDisconnected(BluetoothDevice device) {
Log.d(TAG, "Bluetooth HID device disconnected: " + device.getName());
}

public final PlatformActivityPlugin createPlatformPlugin(WidgetManagerDelegate delegate) { return null; }

protected boolean platformExit() {
Expand Down
Loading