ใช้บริบทที่คาดการณ์ไว้เพื่อเข้าถึงฮาร์ดแวร์ในแว่นตาเสียงและแว่นตาแสดงผล

อุปกรณ์ XR ที่รองรับ
คำแนะนำนี้จะช่วยคุณสร้างประสบการณ์การใช้งานสำหรับอุปกรณ์ XR ประเภทนี้
แว่นตาที่มีจอแสดงผล
และเสียง

หลังจากที่คุณขอและได้รับสิทธิ์ที่จำเป็นแล้ว แอปจะเข้าถึงฮาร์ดแวร์ในแว่นตาที่มีระบบเสียงหรือแว่นตาที่มีจอแสดงผลได้ กุญแจสำคัญในการเข้าถึงฮาร์ดแวร์ของแว่นตา (แทนฮาร์ดแวร์ของโทรศัพท์) คือการใช้บริบทที่คาดการณ์ไว้

การรับบริบทที่คาดการณ์ไว้มี 2 วิธีหลักๆ ขึ้นอยู่กับตำแหน่งที่โค้ดทำงาน ดังนี้

รับบริบทที่คาดการณ์ไว้หากโค้ดของคุณทำงานในกิจกรรมที่คาดการณ์ไว้

หากโค้ดของแอปทำงานจากภายในกิจกรรมที่ฉายภาพ บริบทกิจกรรมของแอปเองจะเป็นบริบทที่ฉายภาพอยู่แล้ว ในกรณีนี้ การเรียกใช้ที่ทำภายในกิจกรรมนั้นจะเข้าถึงฮาร์ดแวร์ของแว่นตาได้อยู่แล้ว

รับบริบทที่คาดการณ์ไว้สำหรับโค้ดที่ทำงานในคอมโพเนนต์แอปโทรศัพท์

หากส่วนหนึ่งของแอปที่อยู่นอกกิจกรรมที่ฉายภาพ (เช่น กิจกรรมหรือบริการของโทรศัพท์) จำเป็นต้องเข้าถึงฮาร์ดแวร์ของแว่นตา ส่วนนั้นจะต้องได้รับบริบทที่ฉายภาพอย่างชัดแจ้ง โดยใช้วิธีการ createProjectedDeviceContext ดังนี้

@RequiresApi(Build.VERSION_CODES.BAKLAVA)
@OptIn(ExperimentalProjectedApi::class, ExperimentalCoroutinesApi::class)
private fun monitorProjectedConnectivity(activity: ComponentActivity) {
    activity.lifecycleScope.launch {
        // Before creating a projected context, check to see if the projected device is connected.
        // While this method returns true, the projected context remains valid.
        ProjectedContext.isProjectedDeviceConnected(activity, coroutineContext)
            .collectLatest { isConnected ->
                if (isConnected) {
                    // From a phone Activity or Service, get a context for the audio and display glasses.
                    // Re-initialize on reconnect: Obtain another context instance.
                    val projectedContext = try {
                        ProjectedContext.createProjectedDeviceContext(activity)
                    } catch (e: IllegalStateException) {
                        Log.e(TAG, "Failed to create projected context", e)
                        return@collectLatest
                    }

                    // Use the projectedContext to initialize system services (e.g., CameraManager).
                    Log.i(TAG, "Projected device connected. Initializing hardware...")
                } else {
                    // The projected context is destroyed when the device disconnects.
                    // Clean up on disconnect: Listen for 'false' and release resources.
                    Log.i(TAG, "Projected device disconnected. Cleaning up hardware resources...")
                }
            }
    }
}

ตรวจสอบความถูกต้อง

รวมการเรียก createProjectedDeviceContext ไว้ใน ProjectedContext.isProjectedDeviceConnected แม้ว่าเมธอดนี้จะแสดงผล true แต่บริบทที่คาดการณ์ไว้จะยังคงใช้ได้กับอุปกรณ์ที่เชื่อมต่อ และกิจกรรมหรือบริการของแอปโทรศัพท์ (เช่น CameraManager) จะเข้าถึงฮาร์ดแวร์ของแว่นตาได้

ล้างข้อมูลเมื่อยกเลิกการเชื่อมต่อ

บริบทที่ฉายจะเชื่อมโยงกับวงจรของอุปกรณ์ที่เชื่อมต่อ ดังนั้นระบบจะทำลายบริบทดังกล่าวเมื่ออุปกรณ์ยกเลิกการเชื่อมต่อ เมื่ออุปกรณ์ยกเลิกการเชื่อมต่อ ProjectedContext.isProjectedDeviceConnected จะแสดงผลเป็น false แอปของคุณควรคอยฟังการเปลี่ยนแปลงนี้และล้างบริการของระบบ (เช่น CameraManager) หรือทรัพยากรที่แอปสร้างขึ้นโดยใช้บริบทที่ฉายภาพนั้น

เริ่มต้นใหม่เมื่อเชื่อมต่ออีกครั้ง

เมื่อแว่นตาเชื่อมต่ออีกครั้ง แอปจะรับอินสแตนซ์บริบทที่ฉายอีกรายการได้โดยใช้ createProjectedDeviceContext จากนั้นเริ่มต้นบริการหรือทรัพยากรระบบอีกครั้งโดยใช้บริบทที่ฉายใหม่

บันทึกเสียงด้วยไมโครโฟนของแว่นตา

คุณสามารถบันทึกเสียงจากแว่นตาโดยใช้โปรไฟล์แฮนด์ฟรี (HFP) ของบลูทูธได้ ในโหมดนี้ แว่นตาจะเชื่อมต่อกับโทรศัพท์โดยใช้โปรไฟล์ Headset และ Advanced Audio Distribution Profile (A2DP) มาตรฐาน ซึ่งทำงานเหมือนกับอุปกรณ์ต่อพ่วงบลูทูธทั่วไป หากแอปของคุณออกแบบมาสำหรับการบันทึกเสียงผ่านบลูทูธมาตรฐานอยู่แล้ว คุณสามารถใช้วิธีนี้เพื่อบันทึกเสียงจากแว่นตาได้โดยไม่ต้องพัฒนาเพิ่มเติม

บันทึกเสียงโดยใช้บลูทูธ HFP

หากต้องการบันทึกเสียงโดยใช้ Bluetooth HFP ให้ขอสิทธิ์รันไทม์ที่จำเป็นก่อน แล้วจึงบันทึกเสียงโดยใช้ AudioManager API ตามที่อธิบายไว้ในส่วนต่อไปนี้

ขอสิทธิ์

เช่นเดียวกับอุปกรณ์เสียงบลูทูธมาตรฐาน สิทธิ์ RECORD_AUDIO, BLUETOOTH_CONNECT และสิทธิ์อื่นๆ ที่เกี่ยวข้องจะควบคุมโดยโทรศัพท์ ไม่ใช่อุปกรณ์ที่เชื่อมต่อ (เช่น แว่นตาที่มีระบบเสียง หรือแว่นตาที่มีจอแสดงผล)

ทำตามขั้นตอนต่อไปนี้เพื่อขอสิทธิ์

  1. ประกาศสิทธิ์ต่อไปนี้ในไฟล์ Manifest ของแอป

  2. ขอทั้งสิทธิ์ RECORD_AUDIO และ BLUETOOTH_CONNECT ขณะรันไทม์โดยใช้ขั้นตอนการขอสิทธิ์ Android มาตรฐาน

ใช้ AudioManager เพื่อกำหนดเส้นทางเสียง

หลังจากที่ผู้ใช้ให้สิทธิ์รันไทม์ที่จำเป็นแก่แอปแล้ว ให้ใช้ API AudioManager เพื่อตั้งค่าอุปกรณ์สื่อสารเป็น TYPE_BLUETOOTH_SCO เพื่อกำหนดเส้นทางเสียงผ่าน Bluetooth HFP ซึ่งจะสั่งให้ระบบดึงข้อมูลเสียงจากอุปกรณ์ต่อพ่วงบลูทูธ

val audioManager = context.getSystemService(AudioManager::class.java) ?: return
val hfpDevice = audioManager.availableCommunicationDevices
    .firstOrNull { it.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO } ?: return

// HFP input is limited to 16 kHz, with mono or stereo channel configurations.
val sampleRate = 16_000
val channelMask = AudioFormat.CHANNEL_IN_MONO
val encoding = AudioFormat.ENCODING_PCM_16BIT

// Use a small multiple of the minimum buffer size to keep chunks short for low latency.
val minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelMask, encoding)
if (minBufferSize <= 0) return

val audioRecord = AudioRecord.Builder()
    .setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION)
    .setAudioFormat(
        AudioFormat.Builder()
            .setSampleRate(sampleRate)
            .setChannelMask(channelMask)
            .setEncoding(encoding)
            .build()
    )
    .setBufferSizeInBytes(minBufferSize * 2)
    .build()

try {
    // Route voice communication audio through the Bluetooth device.
    if (!audioManager.setCommunicationDevice(hfpDevice)) return
    audioRecord.startRecording()
    // Read audio with audioRecord.read() on a background thread.
} finally {
    // Stop and release when done.
    if (audioRecord.recordingState == AudioRecord.RECORDSTATE_RECORDING) audioRecord.stop()
    audioRecord.release()
    audioManager.clearCommunicationDevice()
}

จับภาพด้วยกล้องของแว่นตา

หากต้องการจับภาพด้วยกล้องของแว่นตา ให้ตั้งค่าและเชื่อมโยง กรณีการใช้งาน ImageCapture ของ CameraX กับกล้องของแว่นตาโดยใช้บริบทที่ถูกต้องสำหรับแอปของคุณ ดังนี้

private fun setUpGlassesCamera(activity: ComponentActivity) {
    activity.lifecycleScope.launch {
        // Before creating a projected context, check to see if the projected device is connected.
        ProjectedContext.isProjectedDeviceConnected(activity, coroutineContext)
            .collectLatest { isConnected ->
                if (isConnected) {
                    // 1. Get the CameraProvider using the projected context.
                    // When using the projected context, DEFAULT_BACK_CAMERA maps to the audio and display glasses' camera.
                    val projectedContext = try {
                        ProjectedContext.createProjectedDeviceContext(activity)
                    } catch (e: IllegalStateException) {
                        Log.e(TAG, "Projected context could not be created", e)
                        return@collectLatest
                    }

                    val cameraProviderFuture = ProcessCameraProvider.getInstance(projectedContext)

                    cameraProviderFuture.addListener({
                        val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
                        val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

                        // 2. Check for the presence of a camera.
                        if (!cameraProvider.hasCamera(cameraSelector)) {
                            Log.w(TAG, "The selected camera is not available.")
                            return@addListener
                        }

                        // 3. Query supported streaming resolutions dynamically using Camera2 Interop.
                        val cameraInfo = cameraProvider.getCameraInfo(cameraSelector)
                        val map = Camera2CameraInfo.from(cameraInfo).getCameraCharacteristic(
                            CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP
                        )
                        val supportedResolutions = map?.getOutputSizes(ImageFormat.JPEG)?.toList() ?: emptyList()
                        Log.i(TAG, "Supported resolutions for JPEG: ${supportedResolutions.joinToString()}")

                        // 4. Dynamically select the best target resolution from the queried list.
                        val preferredResolution = Size(1920, 1080)
                        val dynamicTargetResolution = when {
                            // Case A: The preferred resolution is explicitly supported by the glasses.
                            supportedResolutions.contains(preferredResolution) -> preferredResolution

                            // Case B: Find the highest supported 16:9 resolution as a fallback.
                            else -> {
                                val targetAspectRatio = 16.0 / 9.0
                                val tolerance = 0.01
                                supportedResolutions
                                    .filter { abs((it.width.toDouble() / it.height) - targetAspectRatio) < tolerance }
                                    .maxByOrNull { it.width * it.height }
                                    // Case C: Absolute fallback if no 16:9 resolutions are found.
                                    ?: supportedResolutions.firstOrNull()
                                    ?: preferredResolution
                            }
                        }
                        Log.i(TAG, "Selected target resolution: $dynamicTargetResolution")

                        val resolutionStrategy = ResolutionStrategy(
                            dynamicTargetResolution,
                            ResolutionStrategy.FALLBACK_RULE_CLOSEST_LOWER
                        )
                        val resolutionSelector = ResolutionSelector.Builder()
                            .setResolutionStrategy(resolutionStrategy)
                            .build()

                        // 5. Initialize the ImageCapture use case with options.
                        val imageCapture = ImageCapture.Builder()
                            // Optional: Configure resolution, format, etc.
                            .setResolutionSelector(resolutionSelector)
                            .build()

                        try {
                            // Unbind use cases before rebinding.
                            cameraProvider.unbindAll()

                            // Bind use cases to camera using the Activity as the LifecycleOwner.
                            cameraProvider.bindToLifecycle(
                                activity,
                                cameraSelector,
                                imageCapture
                            )
                        } catch (exc: Exception) {
                            Log.e(TAG, "Use case binding failed", exc)
                        }
                    }, ContextCompat.getMainExecutor(activity))
                }
            }
    }
}

ประเด็นสำคัญเกี่ยวกับโค้ด

  • รับอินสแตนซ์ของ ProcessCameraProvider โดยใช้บริบทอุปกรณ์ที่ฉายภาพ
  • ภายในขอบเขตของบริบทที่ฉาย กล้องหลักที่หันออกด้านนอกของแว่นตาจะแมปกับ DEFAULT_BACK_CAMERA เมื่อเลือกกล้อง
  • การตรวจสอบก่อนการเชื่อมโยงจะใช้ cameraProvider.hasCamera(cameraSelector) เพื่อยืนยันว่ากล้องที่เลือกพร้อมใช้งานในอุปกรณ์ก่อนดำเนินการต่อ
  • ใช้ Camera2 Interop กับ Camera2CameraInfo เพื่ออ่าน CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP ที่สำคัญ ซึ่งอาจเป็นประโยชน์สำหรับการตรวจสอบขั้นสูงเกี่ยวกับความละเอียดที่รองรับ
  • ResolutionSelector ที่กำหนดเองสร้างขึ้นเพื่อควบคุมความละเอียดของรูปภาพเอาต์พุตสำหรับ ImageCapture อย่างแม่นยำ
  • สร้างกรณีการใช้งาน ImageCapture ที่กำหนดค่าด้วย ResolutionSelector ที่กำหนดเอง
  • เชื่อมโยงกรณีการใช้งาน ImageCapture กับวงจรของกิจกรรม ซึ่งจะจัดการการเปิดและปิดกล้องโดยอัตโนมัติตามสถานะของกิจกรรม (เช่น การหยุดกล้องเมื่อกิจกรรมหยุดชั่วคราว)
  • การกำหนดค่าสำหรับฮาร์ดแวร์สตรีมเดียว: ไปป์ไลน์กล้องของแว่นตาถูกจำกัดให้สตรีมที่ใช้งานอยู่ได้ครั้งละ 1 รายการเท่านั้น เมื่อเชื่อมโยง ImageCapture เพียงอย่างเดียว CameraX จะแนบสตรีมที่ทำซ้ำภายใน (MeteringRepeating) สำหรับการโฟกัสและการวัดแสงโดยค่าเริ่มต้น ซึ่งทำให้ไปป์ไลน์สตรีมเดียวหยุดชะงัก หากต้องการเชื่อมโยง ImageCapture ด้วยตัวเองโดยไม่มีสตรีมตัวอย่าง ให้ปิดใช้สตรีมที่บังคับให้ทำซ้ำโดยตั้งค่า setRepeatingStreamForced(false) ใน CameraXConfig.Builder (เช่น โดยการใช้ CameraXConfig.Provider ในคลาส Application หรือกำหนดค่า ProcessCameraProvider ก่อนการเริ่มต้น)

หลังจากตั้งค่ากล้องของแว่นตาแล้ว คุณจะจับภาพได้ด้วยคลาส ImageCapture ของ CameraX โปรดดูเอกสารประกอบของ CameraX เพื่อดูข้อมูลเกี่ยวกับการใช้ takePicture เพื่อจับภาพ

จับภาพสตรีมด้วยกล้องของแว่นตา

หากต้องการจับภาพสตรีมแทนรูปภาพ ให้แทนที่คอมโพเนนต์ ImageCapture จากตัวอย่างการจับภาพด้วยคอมโพเนนต์ VideoCapture หรือ ImageAnalysis แล้วเปลี่ยนตรรกะการจับภาพ

ดูข้อมูลเพิ่มเติมเกี่ยวกับ API เหล่านี้และวิธีใช้ได้ในเอกสารประกอบเกี่ยวกับการบันทึกวิดีโอด้วย CameraX และการวิเคราะห์รูปภาพด้วย CameraX

เลือกความละเอียดและอัตราเฟรม

คุณควรค้นหาอุปกรณ์เพื่อดูความละเอียดที่รองรับและอัตราเฟรมสูงสุดที่สอดคล้องกัน ตารางต่อไปนี้แสดงตัวอย่างค่าที่คุณอาจเห็น โดยขึ้นอยู่กับกรณีการใช้งานของแอป หากต้องการประหยัดพลังงานแบตเตอรี่และลดความร้อนของแว่นตา ให้เลือกความละเอียดและอัตราเฟรมต่ำสุดที่เหมาะกับกรณีการใช้งานของคุณ

กรณีการใช้งาน ความละเอียด อัตราเฟรม
การจดจำข้อความ (OCR) หรือการตรวจจับสภาพแวดล้อม 1920 x 1080 1 FPS
สตรีมมิงวิดีโอ AI (ผู้ช่วยแบบหลายรูปแบบ) 1024 x 768 1 FPS
การแปลและการระบุตำแหน่งด้วยภาพ (VPS) 640 x 360 หรือ 640 x 480 10 FPS
วิดีโอคอล 640 x 360 หรือ 640 x 480 24 FPS

ค้นหาอัตราเฟรมสูงสุดสำหรับความละเอียดแต่ละรายการ

CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES จะรายงานขอบเขตอัตราเฟรมโดยรวมของอุปกรณ์ ซึ่งอาจสูงกว่าอัตราการสตรีมที่ความละเอียดหนึ่งๆ รองรับ หากต้องการดูค่าสูงสุดจริงสำหรับความละเอียดแต่ละรายการในแว่นตาที่มีจอแสดงผลและเสียง ให้อ่านระยะเวลาเฟรมขั้นต่ำและแปลงเป็น FPS ด้วย StreamConfigurationMap.getOutputMinFrameDuration(format, size):

@androidx.annotation.OptIn(ExperimentalCamera2Interop::class)
fun getMaxFpsBySize(cameraInfo: CameraInfo, format: Int): Map<Size, Int> {
    val map = Camera2CameraInfo.from(cameraInfo).getCameraCharacteristic(
        CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP
    ) ?: return emptyMap()

    return map.getOutputSizes(format).associateWith { size ->
        val minFrameDurationNs = map.getOutputMinFrameDuration(format, size)
        if (minFrameDurationNs > 0) (1_000_000_000L / minFrameDurationNs).toInt() else 0
    }
}

จากนั้นใช้ค่านั้นเพื่อคำนวณขอบเขต minFps และ maxFps สำหรับช่วงอัตราเฟรมเป้าหมาย จำกัดขอบเขตล่าง (minFps) ที่อัตราเฟรมสตรีมมิงสูงสุดของความละเอียดที่คำนวณจาก getOutputMinFrameDuration และตั้งค่าขอบเขตบนที่สูงขึ้น (maxFps) เป็นกรอบเวลาการไล่ตาม:

val maxFpsBySize = getMaxFpsBySize(cameraInfo, ImageFormat.YUV_420_888)
val maxSupportedFps = maxFpsBySize[Size(640, 480)] ?: return ImageAnalysis.Builder().build()

// minFps is the target streaming frame rate and should be at most maxSupportedFps.
val minFps = minOf(desiredFps, maxSupportedFps)
// Set the upper bound to 2x minFps so the stream can catch up if frames are delayed.
val maxFps = minFps * 2

ประเด็นสำคัญเกี่ยวกับโค้ด

  • ใช้รูปแบบที่ตรงกับกรณีการใช้งานของคุณ สำหรับ ImageAnalysis คือ YUV_420_888
  • สำหรับ VideoCapture ให้ใช้โอเวอร์โหลด Class แทน ดังนี้ getOutputSizes(MediaRecorder::class.java) และ getOutputMinFrameDuration(MediaRecorder::class.java, size)
  • ระบุช่วงที่ maxFps มากกว่า minFps (เช่น minFps*2) แทนที่จะตั้งค่าขอบเขตทั้ง 2 เป็นค่าเดียวกัน เช่น Range(minFps, minFps) ขอบเขตล่าง (minFps) จะกำหนดอัตราเฟรมสตรีมมิงเป้าหมายและควรมีค่าไม่เกินค่าที่คำนวณจาก getOutputMinFrameDuration ส่วนขอบเขตบน (maxFps) จะทำหน้าที่เป็นกรอบเวลาไล่ตามสำหรับเฟรมที่ล่าช้าเพื่อป้องกันเวลาในการตอบสนองที่ต่อเนื่อง เช่น [1,2] หรือ [1,3] สำหรับสตรีม 1 FPS และ [24,30] หรือ [24,48] สำหรับสตรีม 24 FPS

กำหนดค่าอัตราเฟรม

วิธีกำหนดค่าช่วงอัตราเฟรมตามกรณีการใช้งาน CameraX มีดังนี้

// Configure Resolution Strategy for the stream
val resolutionStrategy = ResolutionStrategy(
    size,
    ResolutionStrategy.FALLBACK_RULE_CLOSEST_LOWER
)
val resolutionSelector = ResolutionSelector.Builder()
    .setResolutionStrategy(resolutionStrategy)
    .build()

// Create and return the ImageAnalysis builder with proper frame rate ranges
// ImageAnalysis: OCR, AI streaming, and VPS
val analysisBuilder = ImageAnalysis.Builder()
    .setResolutionSelector(resolutionSelector)

Camera2Interop.Extender(analysisBuilder)
    .setCaptureRequestOption(
        CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,
        Range(minFps, maxFps)
    )
return analysisBuilder.build()

ตัวอย่างนี้แสดงการกำหนดค่าสำหรับทั้ง VideoCapture และ ImageAnalysis แต่คุณสามารถใช้ Use Case ใดก็ได้ที่แอปรองรับ

ทำความเข้าใจลักษณะการทำงานของสตรีม

โปรดคำนึงถึงลักษณะการทำงานของระบบต่อไปนี้ขณะทำงานกับสตรีมการจับภาพ

  • ระบบจะปฏิเสธความละเอียดที่ไม่รองรับ: หากคุณขอขนาดที่ไม่อยู่ในรายการ Camera2 จะปฏิเสธการกำหนดค่าก่อนที่สตรีมจะเปิดขึ้น
  • การขออัตราเฟรมที่สูงกว่าค่าสูงสุด: หากคุณขออัตราเฟรมขั้นต่ำที่สูงกว่าค่าสูงสุดที่รองรับสำหรับความละเอียดของคุณ เซสชันจะยังคงเปิดอยู่ แต่อัตราเฟรมเป้าหมายจากแว่นตาจะถูกจำกัดไว้ที่ค่าสูงสุดที่รองรับ ในขณะที่ขอบเขตบนจะยังคงทำหน้าที่เป็นขีดจำกัดอัตราการไล่ตาม เช่น การขอ [30,60] ที่ 640 x 480 จะจำกัดอัตราเฟรมเป้าหมายไว้ที่ 24 FPS (ทำงานเหมือน [24,60])
  • การควบคุมปริมาณความร้อน: เมื่อแว่นตาเริ่มร้อน อัตราเฟรมจะลดลงแต่ความละเอียดยังคงเท่าเดิม สร้างแอปให้จัดการอัตราเฟรมที่ต่ำหรือไม่สม่ำเสมอได้ เช่น โดยการตรวจสอบการประทับเวลาของเฟรมแทนที่จะถือว่าอัตราเฟรมคงที่

เข้าถึงฮาร์ดแวร์ของโทรศัพท์จากกิจกรรมที่ฉายภาพ

กิจกรรมที่ฉายยังเข้าถึงฮาร์ดแวร์ของโทรศัพท์ (เช่น กล้องหรือไมโครโฟน) ได้โดยใช้ createHostDeviceContext(context) เพื่อรับบริบทของอุปกรณ์โฮสต์ (โทรศัพท์) ดังนี้

@OptIn(ExperimentalProjectedApi::class)
private fun getPhoneContext(activity: ComponentActivity): Context? {
    return try {
        // From a projected Activity, get a context for the phone.
        ProjectedContext.createHostDeviceContext(activity)
    } catch (e: IllegalStateException) {
        Log.e(TAG, "Failed to create host device context", e)
        null
    }
}

เมื่อเข้าถึงฮาร์ดแวร์หรือทรัพยากรที่เฉพาะเจาะจงสำหรับอุปกรณ์โฮสต์ (โทรศัพท์) ในแอปแบบไฮบริด (แอปที่มีทั้งประสบการณ์การใช้งานบนอุปกรณ์เคลื่อนที่และแว่นตา) คุณต้องเลือกบริบทที่ถูกต้องอย่างชัดเจนเพื่อให้แอปเข้าถึงฮาร์ดแวร์ที่ถูกต้องได้ โดยทำดังนี้

  • ใช้บริบท Activity จากโทรศัพท์ Activity หรือ ProjectedContext.createHostDeviceContext เพื่อรับบริบทของโทรศัพท์
  • อย่าใช้ getApplicationContext เนื่องจากบริบทของแอปพลิเคชันอาจแสดงผลบริบทของแว่นตาอย่างไม่ถูกต้องหากกิจกรรมที่ฉายภาพเป็นคอมโพเนนต์ที่เปิดล่าสุด