สร้างบริการการช่วยเหลือพิเศษ

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

คู่มือนี้ครอบคลุมพื้นฐานของการสร้างบริการการช่วยเหลือพิเศษของ Android

วงจรของบริการการช่วยเหลือพิเศษ

หากต้องการสร้างบริการการช่วยเหลือพิเศษ คุณต้องขยายคลาส AccessibilityService และประกาศบริการในไฟล์ Manifest ของแอป

สร้างคลาสบริการ

สร้างคลาสที่ขยาย AccessibilityService คุณต้องลบล้างเมธอดต่อไปนี้

  • onAccessibilityEvent: เรียกใช้เมื่อระบบตรวจพบเหตุการณ์ที่ตรงกับการกำหนดค่าของบริการ (เช่น การเปลี่ยนโฟกัสหรือการคลิกปุ่ม) ซึ่งเป็นตำแหน่งที่บริการของคุณตีความอินเทอร์เฟซผู้ใช้
  • onInterrupt: เรียกใช้เมื่อระบบขัดจังหวะความคิดเห็นของบริการ (เช่น เพื่อหยุดเอาต์พุตเสียงพูดเมื่อผู้ใช้ย้ายโฟกัสอย่างรวดเร็ว)
package com.example.android.apis.accessibility

import android.accessibilityservice.AccessibilityService
import android.accessibilityservice.AccessibilityServiceInfo
import android.accessibilityservice.FingerprintGestureController
import android.accessibilityservice.AccessibilityButtonController
import android.accessibilityservice.GestureDescription
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import android.graphics.Path
import android.os.Build
import android.media.AudioManager
import android.content.Context

class MyAccessibilityService : AccessibilityService() {

    override fun onAccessibilityEvent(event: AccessibilityEvent) {
        // Interpret the event and provide feedback to the user
    }

    override fun onInterrupt() {
        // Interrupt any ongoing feedback
    }

    override fun onServiceConnected() {
        // Perform initialization here
    }
}

ประกาศในไฟล์ Manifest

ลงทะเบียนบริการในไฟล์ AndroidManifest.xml คุณต้องบังคับใช้สิทธิ์ BIND_ACCESSIBILITY_SERVICE อย่างเคร่งครัดเพื่อให้มีเพียงระบบเท่านั้นที่เชื่อมโยงกับบริการของคุณได้

ประกาศ ServiceSettingsActivity เพื่อให้ปุ่มการตั้งค่าทำงานได้

<application>
  <service android:name=".accessibility.MyAccessibilityService"
      android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
      android:exported="true"
      android:label="@string/accessibility_service_label">
      <intent-filter>
          <action android:name="android.accessibilityservice.AccessibilityService" />
      </intent-filter>
      <meta-data
          android:name="android.accessibilityservice"
          android:resource="@xml/accessibility_service_config" />
  </service>

  <activity android:name=".accessibility.ServiceSettingsActivity"
      android:exported="true"
      android:label="@string/accessibility_service_settings_label" />
</application>

กำหนดค่าบริการ

สร้างไฟล์การกำหนดค่าใน res/xml/accessibility_service_config.xml ไฟล์นี้จะกำหนดเหตุการณ์ที่บริการของคุณจัดการและความคิดเห็นที่บริการแสดง อย่าลืมอ้างอิง ServiceSettingsActivity ที่คุณประกาศไว้ใน ไฟล์ Manifest

<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/accessibility_service_description"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFlags="flagDefault|flagRequestFingerprintGestures|flagRequestAccessibilityButton"
    android:accessibilityFeedbackType="feedbackSpoken"
    android:notificationTimeout="100"
    android:canRetrieveWindowContent="true"
    android:canPerformGestures="true"
    android:settingsActivity="com.example.android.apis.accessibility.ServiceSettingsActivity" />

ไฟล์การกำหนดค่ามีแอตทริบิวต์หลักต่อไปนี้

  • android:accessibilityEventTypes: เหตุการณ์ที่ต้องการรับ ใช้ typeAllMask สำหรับบริการอเนกประสงค์
  • android:canRetrieveWindowContent: ต้องเป็น true หากบริการของคุณต้อง ตรวจสอบลําดับชั้น UI (เช่น เพื่ออ่านข้อความจากหน้าจอ)
  • android:canPerformGestures: ต้องเป็น true หากคุณต้องการส่ง ท่าทางสัมผัส (เช่น การปัดหรือแตะ) โดยอัตโนมัติ
  • android:accessibilityFlags: รวมแฟล็กเพื่อเปิดใช้ฟีเจอร์ flagRequestFingerprintGestures ต้องใช้สำหรับท่าทางสัมผัสด้วยลายนิ้วมือ flagRequestAccessibilityButton จำเป็นสำหรับปุ่มการช่วยเหลือพิเศษของซอฟต์แวร์

ดูรายการตัวเลือกการกำหนดค่าทั้งหมดได้ที่ AccessibilityServiceInfo

การกำหนดค่ารันไทม์

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

ลบล้าง onServiceConnected() เพื่อใช้การอัปเดตรันไทม์โดยใช้ setServiceInfo() ดังนี้

override fun onServiceConnected() {
    val info = AccessibilityServiceInfo()

    // Set the type of events that this service wants to listen to.
    info.eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED or AccessibilityEvent.TYPE_VIEW_FOCUSED

    // Set the type of feedback your service provides.
    info.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN

    // Set flags at runtime.
    info.flags = AccessibilityServiceInfo.FLAG_DEFAULT or
            AccessibilityServiceInfo.FLAG_REQUEST_FINGERPRINT_GESTURES

    this.setServiceInfo(info)
}

ตีความเนื้อหา UI

เมื่อ onAccessibilityEvent() ทริกเกอร์ ระบบจะแสดง AccessibilityEvent เหตุการณ์นี้ทำหน้าที่เป็นจุดแรกเข้าของAccessibility Tree ซึ่งเป็นการแสดงเนื้อหาบนหน้าจอแบบลำดับชั้น

บริการของคุณจะโต้ตอบกับออบเจ็กต์ AccessibilityNodeInfo เป็นหลัก ซึ่งแสดงถึงองค์ประกอบ UI เช่น ปุ่ม รายการ และข้อความ ข้อมูลเกี่ยวกับ องค์ประกอบ UI เหล่านี้จะได้รับการปรับให้เป็นมาตรฐานใน AccessibilityNodeInfo

ตัวอย่างต่อไปนี้แสดงวิธีดึงข้อมูลแหล่งที่มาของเหตุการณ์และไปยังส่วนต่างๆ ของ ทรีการช่วยเหลือพิเศษเพื่อค้นหาข้อมูล

override fun onAccessibilityEvent(event: AccessibilityEvent) {
    // Get the source node of the event
    val sourceNode: AccessibilityNodeInfo? = event.source

    if (sourceNode == null) return

    // Inspect properties
    if (sourceNode.isCheckable) {
        val state = if (sourceNode.isChecked) "Checked" else "Unchecked"
        val label = sourceNode.text ?: sourceNode.contentDescription
        
        // Provide feedback (for example, speak to the user)
        speakToUser("$label is $state")
    }

    // Always recycle nodes to prevent memory leaks
    sourceNode.recycle()
}

private fun speakToUser(text: String) {
    // Your text-to-speech implementation goes here
}

ดำเนินการในนามของผู้ใช้

บริการการช่วยเหลือพิเศษสามารถดำเนินการต่างๆ เช่น คลิกปุ่มหรือ เลื่อนรายการ ในนามของผู้ใช้

หากต้องการดำเนินการ ให้เรียกใช้ performAction() ในออบเจ็กต์ AccessibilityNodeInfo

fun performClick(node: AccessibilityNodeInfo) {
    if (node.isClickable) {
        node.performAction(AccessibilityNodeInfo.ACTION_CLICK)
    }
}

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

// Navigate back
fun navigateBack() {
    performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK)
}

จัดการโฟกัส

Android มีโฟกัส 2 ประเภทที่แตกต่างกัน ได้แก่ โฟกัสอินพุต (ตำแหน่งที่อินพุตจากคีย์บอร์ด ไป) และโฟกัสการช่วยเหลือพิเศษ (สิ่งที่บริการการช่วยเหลือพิเศษตรวจสอบ)

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

// Find the node that currently has accessibility focus
// Note: rootInActiveWindow can be null if the window is not available
val root = rootInActiveWindow
if (root != null) {
    val focusedNode = root.findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY)

    // Do something with focusedNode

    // Always recycle nodes
    focusedNode?.recycle()
    // rootInActiveWindow doesn't need to be recycled, but obtained nodes do.
}

ข้อมูลโค้ดต่อไปนี้แสดงวิธีย้ายโฟกัสการช่วยเหลือพิเศษไปยังองค์ประกอบที่เฉพาะเจาะจง

// Request that the system give focus to a given node
fun focusNode(node: AccessibilityNodeInfo) {
    node.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS)
}

เมื่อสร้างบริการการช่วยเหลือพิเศษ ให้คำนึงถึงสถานะโฟกัสของผู้ใช้และหลีกเลี่ยงการขโมยโฟกัส เว้นแต่จะเกิดจากการดำเนินการของผู้ใช้โดยชัดแจ้ง

ทำท่าทางสัมผัส

บริการของคุณสามารถส่งท่าทางสัมผัสที่กำหนดเองไปยังหน้าจอได้ เช่น การปัด การแตะ หรือการโต้ตอบแบบมัลติทัช โดยให้ประกาศ android:canPerformGestures="true" ในการกำหนดค่าเพื่อให้คุณใช้ dispatchGesture() API ได้

ท่าทางสัมผัสง่ายๆ

หากต้องการใช้ท่าทางสัมผัสอย่างง่าย ให้เริ่มต้นด้วยการสร้างออบเจ็กต์ Path เพื่อแสดงถึง การเคลื่อนไหวที่เชื่อมโยงกับท่าทางสัมผัสที่กำหนด จากนั้นให้ใส่ Path ใน GestureDescription เพื่ออธิบายเส้น สุดท้าย ให้เรียกใช้ dispatchGesture เพื่อส่งท่าทางสัมผัส

fun swipeRight() {
    // Create a path for the swipe (from x=100 to x=500)
    val swipePath = Path()
    swipePath.moveTo(100f, 500f)
    swipePath.lineTo(500f, 500f)

    // Build the stroke description (0ms delay, 500ms duration)
    val stroke = GestureDescription.StrokeDescription(swipePath, 0, 500)

    // Build the gesture description
    val gestureBuilder = GestureDescription.Builder()
    gestureBuilder.addStroke(stroke)

    // Dispatch the gesture
    dispatchGesture(gestureBuilder.build(), object : AccessibilityService.GestureResultCallback() {
        override fun onCompleted(gestureDescription: GestureDescription?) {
            super.onCompleted(gestureDescription)
            // Gesture finished successfully
        }
    }, null)
}

ท่าทางสัมผัสต่อเนื่อง

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

fun performLShapedGesture() {
    val path1 = Path().apply {
        moveTo(200f, 200f)
        lineTo(400f, 200f)
    }

    val path2 = Path().apply {
        moveTo(400f, 200f)
        lineTo(400f, 400f)
    }

    // First stroke: willContinue = true
    val stroke1 = GestureDescription.StrokeDescription(path1, 0, 500, true)

    // Second stroke: continues immediately after stroke1
    val stroke2 = stroke1.continueStroke(path2, 0, 500, false)

    val builder = GestureDescription.Builder()
    builder.addStroke(stroke1)
    builder.addStroke(stroke2)

    dispatchGesture(builder.build(), null, null)
}

การจัดการเสียง

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

fun increaseAccessibilityVolume() {
    val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
    audioManager.adjustStreamVolume(
        AudioManager.STREAM_ACCESSIBILITY,
        AudioManager.ADJUST_RAISE,
        0
    )
}

อย่าลืมใส่แฟล็ก FLAG_ENABLE_ACCESSIBILITY_VOLUME ในการกำหนดค่า ไม่ว่าจะเป็นใน XML หรือผ่าน setServiceInfo ที่รันไทม์

ฟีเจอร์ขั้นสูง

ท่าทางสัมผัสลายนิ้วมือ

ในอุปกรณ์ที่ใช้ Android 10 (ระดับ API 29) ขึ้นไป บริการของคุณจะบันทึกการปัดตามทิศทางบนเซ็นเซอร์ลายนิ้วมือได้ ซึ่งมีประโยชน์ในการจัดหา ตัวควบคุมการนำทางสำรอง

เพิ่มตรรกะต่อไปนี้ลงในเมธอด onServiceConnected()

// Import: android.os.Build
// Import: android.accessibilityservice.FingerprintGestureController

private var gestureController: FingerprintGestureController? = null

override fun onServiceConnected() {
    // Check if the device is running Android 10 (Q) or higher
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        gestureController = fingerprintGestureController

        val callback = object : FingerprintGestureController.FingerprintGestureCallback() {
            override fun onGestureDetected(gesture: Int) {
                when (gesture) {
                    FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_DOWN -> {
                        // Handle swipe down
                    }
                    FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_UP -> {
                        // Handle swipe up
                    }
                }
            }
        }

        gestureController?.registerFingerprintGestureCallback(callback, null)
    }
}

ปุ่มการช่วยเหลือพิเศษ

ในอุปกรณ์ที่ใช้ปุ่มนำทางของซอฟต์แวร์ ผู้ใช้จะเรียกใช้บริการของคุณได้ผ่าน ปุ่มการช่วยเหลือพิเศษในแถบนำทาง

หากต้องการใช้ฟีเจอร์นี้ ให้เพิ่มแฟล็ก FLAG_REQUEST_ACCESSIBILITY_BUTTON ลงในการกำหนดค่าบริการ จากนั้นเพิ่มตรรกะการลงทะเบียนลงในเมธอด onServiceConnected()

// Import: android.accessibilityservice.AccessibilityButtonController

override fun onServiceConnected() {
    // ... existing initialization code ...

    val controller = accessibilityButtonController

    controller.registerAccessibilityButtonCallback(
        object : AccessibilityButtonController.AccessibilityButtonCallback() {
            override fun onClicked(controller: AccessibilityButtonController) {
                // Respond to button tap
            }
        }
    )
}

การอ่านออกเสียงข้อความหลายภาษา

บริการที่อ่านข้อความออกเสียงจะเปลี่ยนภาษาโดยอัตโนมัติได้หากข้อความต้นฉบับ มีแท็ก LocaleSpan ซึ่งจะช่วยให้บริการออกเสียงเนื้อหาที่มีหลายภาษาได้อย่างถูกต้องโดยไม่ต้องสลับภาษาด้วยตนเอง

import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.style.LocaleSpan
import java.util.Locale

// Wrap text in LocaleSpan to indicate language
val spannable = SpannableStringBuilder("Bonjour")
spannable.setSpan(
    LocaleSpan(Locale.FRANCE),
    0,
    spannable.length,
    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)

เมื่อบริการของคุณประมวลผล AccessibilityNodeInfo ให้ตรวจสอบพร็อพเพอร์ตี้ text สำหรับออบเจ็กต์ LocaleSpan เพื่อกำหนดภาษา Text-to-Speech ที่ถูกต้อง

แหล่งข้อมูลเพิ่มเติม

ดูข้อมูลเพิ่มเติมได้ที่แหล่งข้อมูลต่อไปนี้

เส้นนำ

Codelabs

ดูเนื้อหา