ایجاد سرویس دسترس‌پذیری

سرویس دسترس‌پذیری برنامه‌ای است که میانای کاربر را برای کمک به کاربران دارای ناتوانی یا کاربرانی که ممکن است به‌طور موقت نتوانند به‌طور کامل با دستگاه تعامل داشته باشند بهبود می‌بخشد. این سرویس‌ها در پس‌زمینه اجرا می‌شوند و با سیستم ارتباط برقرار می‌کنند تا محتوای صفحه را بررسی کنند و ازجانب کاربر با برنامه‌ها تعامل داشته باشند. نمونه‌ها شامل صفحه‌خوان‌ها (مانند TalkBack)، ابزارهای «دسترسی با کلید»، و سیستم‌های کنترل صوتی می‌شود.

این راهنما اصول اولیه ساختن سرویس دسترس‌پذیری Android را پوشش می‌دهد.

چرخه حیات سرویس دسترس‌پذیری

برای ایجاد سرویس دسترس‌پذیری، باید کلاس AccessibilityService را گسترش دهید و سرویس را در مانیفست برنامه‌تان اعلام کنید.

ایجاد کلاس خدمات

کلاسی ایجاد کنید که 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
    }
}

اعلام در مانیفست

سرویس خود را در فایل 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 که در مانیفست اعلام کرده‌اید ارجاع دهید:

<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 باشد.
  • 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)
}

تفسیر محتوای میانای کاربر

وقتی onAccessibilityEvent() راه‌اندازی می‌شود، سیستم AccessibilityEvent ارائه می‌دهد. این رویداد به‌عنوان نقطه ورودی به درخت دسترس‌پذیری عمل می‌کند که نمایشی سلسله‌مراتبی از محتوای صفحه است.

سرویس شما عمدتاً با اشیاء AccessibilityNodeInfo تعامل دارد که عناصر میانای کاربر مثل دکمه‌ها، فهرست‌ها، و نوشتار را نشان می‌دهند. داده‌های مربوط به این عناصر میانای کاربر در 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 دو نوع متمایز از تمرکز دارد: تمرکز ورودی (جایی که ورودی صفحه‌کلید می‌رود) و تمرکز دسترس‌پذیری (آنچه سرویس دسترس‌پذیری درحال بازرسی آن است).

قطعه کد زیر نشان می‌دهد که چگونه می‌توان عنصری را که درحال‌حاضر دارای تمرکز دسترس‌پذیری است پیدا کرد:

// 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() استفاده کنید.

اشاره‌های ساده

برای اجرای اشاره‌های ساده، ابتدا یک شیء 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 (سطح میانای برنامه‌سازی کاربردی ۲۹) یا بالاتر استفاده می‌کنند، سرویس شما می‌تواند تند کشیدن‌های جهت‌دار را روی حسگر اثر انگشت ضبط کند. این ویژگی برای ارائه کنترل‌های پیمایش جایگزین مفید است.

منطق زیر را به روش 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 بررسی کنید تا زبان نوشتار به گفتار صحیح را تعیین کنید.

منابع بیشتر

برای اطلاعات بیشتر، منابع زیر را ببینید:

راهنماها

Codelabs

محتوا را مشاهده می‌کند