The open source OpenXR runtime

t/openxr_android: add Google Cardboard QR code scanning

Co-authored-by: Rylie Pavlik <rylie.pavlik@collabora.com>
Part-of: <https://gitlab.freedesktop.org/monado/monado/-/merge_requests/2560>

authored by

Simon Zeni
Rylie Pavlik
and committed by
Korcan Hussein
fdbc220e b51bbed0

+204 -7
+1 -1
src/xrt/targets/android_common/build.gradle
··· 1 - // Copyright 2020-2022, Collabora, Ltd. 1 + // Copyright 2020-2025, Collabora, Ltd. 2 2 // SPDX-License-Identifier: BSL-1.0 3 3 4 4 plugins {
+3 -2
src/xrt/targets/android_common/src/main/AndroidManifest.xml
··· 1 1 <?xml version="1.0" encoding="utf-8"?> 2 2 <manifest xmlns:android="http://schemas.android.com/apk/res/android"> 3 3 <!-- 4 - Copyright 2020, Collabora, Ltd. 4 + Copyright 2020-2025, Collabora, Ltd. 5 5 SPDX-License-Identifier: BSL-1.0 6 6 --> 7 7 ··· 47 47 android:theme="@style/AppTheme"> 48 48 <!-- Main "about" activity --> 49 49 <activity android:name=".AboutActivity" 50 - android:exported="true"> 50 + android:exported="true" 51 + android:label="Monado"> 51 52 <intent-filter> 52 53 <action android:name="android.intent.action.MAIN" /> 53 54 <category android:name="android.intent.category.LAUNCHER" />
+33 -1
src/xrt/targets/android_common/src/main/java/org/freedesktop/monado/android_common/AboutActivity.java
··· 1 - // Copyright 2020, Collabora, Ltd. 1 + // Copyright 2020-2025, Collabora, Ltd. 2 2 // SPDX-License-Identifier: BSL-1.0 3 3 /*! 4 4 * @file 5 5 * @brief Simple main activity for Android. 6 6 * @author Rylie Pavlik <rylie.pavlik@collabora.com> 7 + * @author Simon Zeni <simon.zeni@collabora.com> 7 8 */ 8 9 9 10 package org.freedesktop.monado.android_common; 10 11 11 12 import android.os.Bundle; 12 13 import android.text.method.LinkMovementMethod; 14 + import android.view.Menu; 15 + import android.view.MenuItem; 13 16 import android.view.View; 14 17 import android.widget.ImageView; 15 18 import android.widget.TextView; 19 + import androidx.annotation.NonNull; 16 20 import androidx.appcompat.app.AppCompatActivity; 17 21 import androidx.appcompat.app.AppCompatDelegate; 18 22 import androidx.fragment.app.Fragment; 19 23 import androidx.fragment.app.FragmentTransaction; 20 24 import dagger.hilt.android.AndroidEntryPoint; 25 + import java.util.Optional; 21 26 import javax.inject.Inject; 22 27 import org.freedesktop.monado.auxiliary.NameAndLogoProvider; 23 28 import org.freedesktop.monado.auxiliary.UiProvider; ··· 31 36 32 37 @Inject NameAndLogoProvider nameAndLogoProvider; 33 38 39 + /** 40 + * @noinspection OptionalUsedAsFieldOrParameterType 41 + */ 42 + @Inject Optional<AboutMenuProvider> aboutMenuProvider; 43 + 34 44 private boolean isInProcessBuild() { 35 45 try { 36 46 getClassLoader().loadClass("org/freedesktop/monado/ipc/Client"); ··· 48 58 49 59 // Default to dark mode universally? 50 60 AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); 61 + 62 + setSupportActionBar(findViewById(R.id.toolbar)); 51 63 52 64 // Make our Monado link clickable 53 65 ((TextView) findViewById(R.id.textPowered)) ··· 88 100 } 89 101 90 102 fragmentTransaction.commit(); 103 + } 104 + 105 + @Override 106 + public boolean onCreateOptionsMenu(Menu menu) { 107 + aboutMenuProvider.ifPresent( 108 + menuProvider -> menuProvider.onCreateOptionsMenu(getMenuInflater(), menu)); 109 + return super.onCreateOptionsMenu(menu); 110 + } 111 + 112 + @Override 113 + public boolean onOptionsItemSelected(@NonNull MenuItem item) { 114 + Optional<AboutMenuProvider> aboutMenu = aboutMenuProvider; 115 + if (aboutMenu.isPresent()) { 116 + if (aboutMenu.get().onOptionsItemSelected(this, item)) { 117 + // handled 118 + return true; 119 + } 120 + } 121 + 122 + return super.onOptionsItemSelected(item); 91 123 } 92 124 }
+25
src/xrt/targets/android_common/src/main/java/org/freedesktop/monado/android_common/AboutMenuProvider.kt
··· 1 + // Copyright 2020-2025, Collabora, Ltd. 2 + // SPDX-License-Identifier: BSL-1.0 3 + /*! 4 + * @file 5 + * @brief Dependency injection interface for about activity menu. 6 + * @author Rylie Pavlik <rylie.pavlik@collabora.com> 7 + * @author Simon Zeni <simon.zeni@collabora.com> 8 + */ 9 + package org.freedesktop.monado.android_common 10 + 11 + import android.content.Context 12 + import android.view.Menu 13 + import android.view.MenuItem 14 + 15 + /** 16 + * Menu handler for the about activity. This interface may be provided by any Android final target, 17 + * optionally. 18 + * 19 + * Intended for use in dependency injection, so you can add your own menu items 20 + */ 21 + interface AboutMenuProvider { 22 + fun onCreateOptionsMenu(menuInflater: android.view.MenuInflater, menu: Menu?) 23 + 24 + fun onOptionsItemSelected(context: Context, item: MenuItem): Boolean 25 + }
+26
src/xrt/targets/android_common/src/main/java/org/freedesktop/monado/android_common/OptionalMenuModule.java
··· 1 + // Copyright 2020-2025, Collabora, Ltd. 2 + // SPDX-License-Identifier: BSL-1.0 3 + /*! 4 + * @file 5 + * @brief Dep injection module for when you do not want a menu in the about activity 6 + * @author Rylie Pavlik <rylie.pavlik@collabora.com> 7 + * @author Simon Zeni <simon.zeni@collabora.com> 8 + */ 9 + package org.freedesktop.monado.android_common; 10 + 11 + import dagger.BindsOptionalOf; 12 + import dagger.Module; 13 + import dagger.hilt.InstallIn; 14 + import dagger.hilt.android.components.ActivityComponent; 15 + 16 + /** 17 + * Lets you optionally **not** provide a menu. 18 + * 19 + * @noinspection unused 20 + */ 21 + @Module 22 + @InstallIn(ActivityComponent.class) 23 + public interface OptionalMenuModule { 24 + @BindsOptionalOf 25 + AboutMenuProvider bindOptionalAboutMenu(); 26 + }
+7 -1
src/xrt/targets/android_common/src/main/res/layout/activity_about.xml
··· 13 13 android:id="@+id/imageView" 14 14 android:layout_width="@android:dimen/thumbnail_width" 15 15 android:layout_height="@android:dimen/thumbnail_height" 16 - android:layout_marginTop="8dp" 16 + android:layout_marginTop="96dp" 17 17 android:contentDescription="@string/logo_text" 18 18 android:scaleType="fitCenter" 19 19 app:layout_constraintEnd_toEndOf="parent" ··· 121 121 android:text="Button" 122 122 android:visibility="gone" /> 123 123 </FrameLayout> 124 + 125 + <androidx.appcompat.widget.Toolbar 126 + android:id="@+id/toolbar" 127 + android:layout_width="match_parent" 128 + android:layout_height="?attr/actionBarSize" 129 + android:background="?attr/colorPrimary" /> 124 130 125 131 126 132 </androidx.constraintlayout.widget.ConstraintLayout>
+1
src/xrt/targets/openxr_android/build.gradle
··· 298 298 } 299 299 300 300 dependencies { 301 + implementation project(':src:xrt:auxiliary:cardboard') 301 302 outOfProcessImplementation project(':src:xrt:ipc') 302 303 implementation project(':src:xrt:auxiliary') 303 304 implementation project(':src:xrt:targets:android_common')
+39
src/xrt/targets/openxr_android/src/main/java/org/freedesktop/monado/openxr_runtime/AboutMenuWithCardboard.kt
··· 1 + // Copyright 2020-2025, Collabora, Ltd. 2 + // SPDX-License-Identifier: BSL-1.0 3 + /*! 4 + * @file 5 + * @brief Menu with QR code scanner for Cardboard. 6 + * @author Rylie Pavlik <rylie.pavlik@collabora.com> 7 + * @author Simon Zeni <simon.zeni@collabora.com> 8 + */ 9 + 10 + package org.freedesktop.monado.openxr_runtime 11 + 12 + import android.content.Context 13 + import android.content.Intent 14 + import android.view.Menu 15 + import android.view.MenuInflater 16 + import android.view.MenuItem 17 + import javax.inject.Inject 18 + import org.freedesktop.monado.android_common.AboutMenuProvider 19 + import org.freedesktop.monado.auxiliary.ScannerProvider 20 + 21 + /** An about menu that has a QR code scanner to set up Cardboard parameters. */ 22 + class AboutMenuWithCardboard @Inject constructor(private val scanner: ScannerProvider) : 23 + AboutMenuProvider { 24 + 25 + override fun onCreateOptionsMenu(menuInflater: MenuInflater, menu: Menu?) { 26 + menuInflater.inflate(R.menu.menu_runtime, menu) 27 + menu ?: return 28 + menu.findItem(R.id.qrscan).icon = scanner.getIcon() 29 + } 30 + 31 + override fun onOptionsItemSelected(context: Context, item: MenuItem): Boolean { 32 + if (item.itemId == R.id.qrscan) { 33 + val scannerIntent: Intent = scanner.makeScannerIntent() 34 + context.startActivity(scannerIntent) 35 + return true 36 + } 37 + return false 38 + } 39 + }
+27
src/xrt/targets/openxr_android/src/main/java/org/freedesktop/monado/openxr_runtime/CardboardModule.kt
··· 1 + // Copyright 2020-2025, Collabora, Ltd. 2 + // SPDX-License-Identifier: BSL-1.0 3 + /*! 4 + * @file 5 + * @brief Module that binds all the optional Cardboard-related dependencies we inject with Hilt. 6 + * @author Rylie Pavlik <rylie.pavlik@collabora.com> 7 + */ 8 + 9 + package org.freedesktop.monado.openxr_runtime 10 + 11 + import dagger.Binds 12 + import dagger.Module 13 + import dagger.hilt.InstallIn 14 + import dagger.hilt.android.components.ActivityComponent 15 + import org.freedesktop.monado.android_common.AboutMenuProvider 16 + import org.freedesktop.monado.auxiliary.ScannerProvider 17 + 18 + @Module 19 + @InstallIn(ActivityComponent::class) 20 + abstract class CardboardModule { 21 + // Provide the optional menu provider, to access our QR code scanner for Cardboard 22 + @Binds abstract fun bindAboutMenu(aboutMenuProvider: AboutMenuWithCardboard): AboutMenuProvider 23 + 24 + // which itself needs a scanner 25 + @Binds 26 + abstract fun bindScanner(scannerProvider: CardboardQRCodeScannerProvider): ScannerProvider 27 + }
+26
src/xrt/targets/openxr_android/src/main/java/org/freedesktop/monado/openxr_runtime/CardboardQRCodeScannerProvider.kt
··· 1 + // Copyright 2025, Collabora, Ltd. 2 + // SPDX-License-Identifier: BSL-1.0 3 + /*! 4 + * @file 5 + * @brief Simple implementation of ScannerProvider. 6 + * @author Simon Zeni <simon.zeni@collabora.com> 7 + */ 8 + package org.freedesktop.monado.openxr_runtime 9 + 10 + import android.content.Context 11 + import android.content.Intent 12 + import android.graphics.drawable.Drawable 13 + import androidx.core.content.ContextCompat 14 + import dagger.hilt.android.qualifiers.ApplicationContext 15 + import javax.inject.Inject 16 + import org.freedesktop.monado.auxiliary.ScannerProvider 17 + import org.freedesktop.monado.auxiliary.cardboard.QrScannerActivity 18 + 19 + class CardboardQRCodeScannerProvider @Inject constructor(@ApplicationContext val context: Context) : 20 + ScannerProvider { 21 + 22 + override fun getIcon(): Drawable = 23 + ContextCompat.getDrawable(context, R.drawable.cardboard_oss_qr)!! 24 + 25 + override fun makeScannerIntent(): Intent = Intent(context, QrScannerActivity::class.java) 26 + }
+2 -2
src/xrt/targets/openxr_android/src/main/java/org/freedesktop/monado/openxr_runtime/MonadoOpenXrAndroidModule.kt
··· 1 - // Copyright 2020-2021, Collabora, Ltd. 1 + // Copyright 2020-2025, Collabora, Ltd. 2 2 // SPDX-License-Identifier: BSL-1.0 3 3 /*! 4 4 * @file 5 - * @brief Module that binds all the dependencies we inject with Hilt. 5 + * @brief Module that binds all the required dependencies we inject with Hilt. 6 6 * @author Rylie Pavlik <rylie.pavlik@collabora.com> 7 7 */ 8 8
+14
src/xrt/targets/openxr_android/src/main/res/menu/menu_runtime.xml
··· 1 + <?xml version="1.0" encoding="utf-8"?> 2 + <!-- 3 + Copyright 2025, Collabora, Ltd. 4 + SPDX-License-Identifier: BSL-1.0 5 + --> 6 + <menu xmlns:android="http://schemas.android.com/apk/res/android" 7 + xmlns:app="http://schemas.android.com/apk/res-auto"> 8 + 9 + <item 10 + android:id="@+id/qrscan" 11 + android:title="@string/qr" 12 + app:showAsAction="always" /> 13 + 14 + </menu>