إنشاء نشاطك الأول لنظارات الصوت والنظارات المزودة بشاشة عرض

أجهزة XR المشمولة
تساعدك هذه الإرشادات في إنشاء تجارب لهذه الأنواع من أجهزة XR.
نظارات الصوت
والعرض

تستند التجارب المعزّزة للنظارات الصوتية ونظارات بشاشة عرض إلى Activityواجهة برمجة التطبيقات الحالية لإطار عمل Android، وتتضمّن مفاهيم إضافية لدعم الجوانب الفريدة لهذه النظارات. على عكس سمّاعات الرأس التي تعمل بنظام الواقع الممتد والتي تشغّل حزمة APK كاملة على الجهاز، تستخدم نظارات الصوت والنظارات المزودة بشاشة نشاطًا مخصّصًا يتم تشغيله داخل التطبيق الحالي على هاتفك، ويتم عرض هذا النشاط من الجهاز المضيف إلى النظارات.

لإنشاء تجربة تطبيقك على النظارات الصوتية ونظارات بشاشة عرض، عليك توسيع نطاق تطبيقك الحالي على تطبيق الهاتف من خلال إنشاء Activity جديد معروض. يمثّل هذا النشاط نقطة الدخول الرئيسية لتشغيل تطبيقك على النظارات. ويبسّط هذا النهج عملية التطوير لأنّه يتيح لك مشاركة منطق النشاط التجاري وإعادة استخدامه بين تجارب الهاتف والنظارات.

التوافق مع الإصدارات

اطّلِع على متطلبات التوافق مع حزمة تطوير البرامج (SDK) لنظام التشغيل Android الخاصة بحزمة Jetpack XR SDK.

الطلبات التابعة

أضِف اعتماديات المكتبة التالية للنظارات الصوتية ونظارات العرض:

أنيق

dependencies {
    implementation "androidx.xr.runtime:runtime:1.0.0-alpha15"
    implementation "androidx.xr.glimmer:glimmer:1.0.0-alpha13"
    implementation "androidx.xr.glimmer:glimmer-google-fonts:1.0.0-alpha13"
    implementation "androidx.xr.projected:projected:1.0.0-alpha08"
    implementation "androidx.xr.arcore:arcore:1.0.0-alpha14"
}

Kotlin

dependencies {
    implementation("androidx.xr.runtime:runtime:1.0.0-alpha15")
    implementation("androidx.xr.glimmer:glimmer:1.0.0-alpha13")
    implementation("androidx.xr.glimmer:glimmer-google-fonts:1.0.0-alpha13")
    implementation("androidx.xr.projected:projected:1.0.0-alpha08")
    implementation("androidx.xr.arcore:arcore:1.0.0-alpha14")
}

تحديد النشاط في ملف البيان الخاص بتطبيقك

وكما هو الحال مع أنواع الأنشطة الأخرى، عليك تعريف نشاطك في ملف بيان التطبيق ليتمكّن النظام من رؤيته وتشغيله.

<application>
  <activity
      android:name="com.example.xr.projected.ProjectedMainActivity"
      android:exported="true"
      android:requiredDisplayCategory="android.hardware.display.category.XR_PROJECTED"
      android:label="Example activity for audio glasses and display glasses">
      <intent-filter>
          <action android:name="android.intent.action.MAIN" />
          <category android:name="android.intent.category.XR_PROJECTED_LAUNCHER"/>
      </intent-filter>
  </activity>
</application>

نقاط أساسية حول الرمز

  • تحدّد هذه السمة القيمة android.hardware.display.category.XR_PROJECTED للسمة android:requiredDisplayCategory لإعلام النظام بأنّ هذا النشاط معروض ويمكن عرضه على نظارات الصوت ونظارات بشاشة عرض.
  • تضبط android.intent.action.MAIN هذا النشاط كنشاط الإطلاق التلقائي.
  • android.intent.category.XR_PROJECTED_LAUNCHER هي فئة متخصّصة تتيح لـ "أوامر Gemini الصوتية" العثور على نشاطك المعروض.

    عندما يوجّه المستخدم طلبًا صوتيًا باستخدام اسم التطبيق (على سبيل المثال، "افتح عيّنة كتالوج الذكاء الاصطناعي" أو "شغِّل عيّنة كتالوج الذكاء الاصطناعي" أو "ابدأ عيّنة كتالوج الذكاء الاصطناعي")، يستخدم النظام هذه الفئة لتحديد موقع النشاط المحدّد وبدء تشغيله على النظارات الصوتية أو نظارات بشاشة عرض.

إنشاء نشاطك

بعد ذلك، ستنشئ نشاطًا صغيرًا يمكنه عرض محتوى على النظارات الذكية التي تعمل بالذكاء الاصطناعي كلما تم تشغيل الشاشة.

@OptIn(ExperimentalProjectedApi::class)
class ProjectedMainActivity : ComponentActivity() {

    private var displayController: ProjectedDisplayController? = null
    private var isVisualUiSupported by mutableStateOf(false)
    private var areVisualsOn by mutableStateOf(true)
    private var isPermissionDenied by mutableStateOf(false)

    // Register the permissions launcher using the ProjectedPermissionsResultContract.
    private val requestPermissionLauncher: ActivityResultLauncher<List<ProjectedPermissionsRequestParams>> =
        registerForActivityResult(ProjectedPermissionsResultContract()) { results ->
            if (results[Manifest.permission.CAMERA] == true) {
                isPermissionDenied = false
                initializeGlassesFeatures()
            } else {
                // Handle permission denial.
                isPermissionDenied = true
            }
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        lifecycle.addObserver(object : DefaultLifecycleObserver {
            override fun onDestroy(owner: LifecycleOwner) {
                displayController?.close()
                displayController = null
            }
        })

        if (hasCameraPermission()) {
            initializeGlassesFeatures()
        } else {
            requestHardwarePermissions()
        }

        setContent {
            GlimmerTheme {
                HomeScreen(
                    areVisualsOn = areVisualsOn,
                    isVisualUiSupported = isVisualUiSupported,
                    isPermissionDenied = isPermissionDenied,
                    onRetryPermission = { requestHardwarePermissions() },
                    onClose = { finish() }
                )
            }
        }
    }

    private fun initializeGlassesFeatures() {
        lifecycleScope.launch {
            // Check device capabilities
            val projectedDeviceController = ProjectedDeviceController.create(this@ProjectedMainActivity)
            isVisualUiSupported = projectedDeviceController.capabilities.contains(CAPABILITY_VISUAL_UI)

            val controller = ProjectedDisplayController.create(this@ProjectedMainActivity)
            displayController = controller
            val observer = GlassesLifecycleObserver(
                context = this@ProjectedMainActivity,
                controller = controller,
                onVisualsChanged = { visualsOn -> areVisualsOn = visualsOn }
            )
            lifecycle.addObserver(observer)
        }
    }

    private fun hasCameraPermission(): Boolean {
        return ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) ==
                PackageManager.PERMISSION_GRANTED
    }

    private fun requestHardwarePermissions() {
        val params = ProjectedPermissionsRequestParams(
            permissions = listOf(Manifest.permission.CAMERA),
            rationale = "Camera access is required to overlay digital content on your physical environment."
        )
        requestPermissionLauncher.launch(listOf(params))
    }
}

نقاط أساسية حول الرمز

تنفيذ الدالة المركّبة

يشير النشاط الذي أنشأته إلى دالة مركّبة HomeScreen يجب تنفيذها. يستخدم الرمز البرمجي التالي Jetpack Compose Glimmer لتحديد دالة مركّبة يمكنها عرض بعض النصوص على شاشة النظّارة الذكية:

@Composable
fun HomeScreen(
    areVisualsOn: Boolean,
    isVisualUiSupported: Boolean,
    isPermissionDenied: Boolean,
    onRetryPermission: () -> Unit,
    onClose: () -> Unit,
    modifier: Modifier = Modifier
) {
    Box(
        modifier = modifier
            .surface()
            .focusable(false)
            .fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        if (isPermissionDenied) {
            Card(
                title = { Text("Permission Required") },
                action = { Button(onClick = onClose) { Text("Exit") } }
            ) {
                Text("Camera access is needed to use AI glasses features.")
                Button(onClick = onRetryPermission) { Text("Retry") }
            }
        } else if (isVisualUiSupported) {
            Card(
                title = { Text("Android XR") },
                action = {
                    Button(onClick = onClose) {
                        Text("Close")
                    }
                }
            ) {
                if (areVisualsOn) {
                    Text("Hello, AI Glasses!")
                } else {
                    Text("Display is off. Audio guidance active.")
                }
            }
        } else {
            Text("Audio Guidance Mode Active")
        }
    }
}

نقاط أساسية حول الرمز

  • كما حدّدت في نشاطك السابق، تتضمّن الدالة HomeScreen المحتوى القابل للإنشاء الذي يراه المستخدم عندما تكون شاشة النظارات مفعّلة.
  • يعرض مكوّن Glimmer Text في Jetpack Compose النص "مرحبًا، نظارات الذكاء الاصطناعي" على شاشة النظارات.
  • يغلق Jetpack Compose Glimmer Button النشاط من خلال استدعاء finish() عبر onClose في النشاط المعروض.

التحقّق مما إذا كانت النظارات الصوتية أو نظارات بشاشة عرض متصلة

لتحديد ما إذا كانت نظارات الصوت أو نظارات بشاشة عرض متصلة بهاتف المستخدم قبل إطلاق نشاطك، استخدِم طريقة ProjectedContext.isProjectedDeviceConnected. تعرض هذه الطريقة Flow<Boolean> التي يمكن لتطبيقك مراقبتها للحصول على آخر الأخبار حول حالة الاتصال في الوقت الفعلي.

بدء نشاطك

بعد إنشاء نشاط أساسي، يمكنك تشغيله على نظارتك. للوصول إلى أجهزة النظّارة الذكية، يجب أن يبدأ تطبيقك النشاط بخيارات معيّنة تطلب من النظام استخدام سياق معروض، كما هو موضّح في الرمز التالي:

val options = ProjectedContext.createProjectedActivityOptions(context)
val intent = Intent(context, ProjectedMainActivity::class.java)
context.startActivity(intent, options.toBundle())

تنشئ طريقة createProjectedActivityOptions في ProjectedContext الخيارات اللازمة لبدء نشاطك في سياق متوقّع. يمكن أن تكون المَعلمة context سياقًا من الهاتف أو نظارات الواقع الافتراضي.

الخطوات التالية

بعد إنشاء نشاطك الأول لنظارات الصوت والنظارات المزودة بشاشة، يمكنك استكشاف طرق أخرى لتوسيع نطاق وظائفه: