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

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

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

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

برای ایجاد یک سرویس دسترسی، باید کلاس 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 ایستا است، اما می‌توانید پیکربندی سرویس خود را به صورت پویا در زمان اجرا تغییر دهید. این برای تغییر ویژگی‌ها بر اساس تنظیمات کاربر مفید است.

برای اعمال به‌روزرسانی‌های زمان اجرا با استفاده از setServiceInfo() تابع onServiceConnected() را بازنویسی کنید:

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)
}

مدیریت تمرکز

اندروید دو نوع تمرکز مجزا دارد: تمرکز ورودی (جایی که ورودی صفحه کلید می‌رود) و تمرکز دسترسی (آنچه سرویس دسترسی بررسی می‌کند).

قطعه کد زیر نحوه یافتن عنصری را نشان می‌دهد که در حال حاضر تمرکز دسترسی روی آن قرار دارد:

// 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" را در پیکربندی خود تعریف کنید تا بتوانید از API dispatchGesture() استفاده کنید.

حرکات ساده

برای انجام حرکات ساده، با ایجاد یک شیء Path برای نمایش حرکت مرتبط با یک حرکت مشخص شروع کنید. سپس، Path را در یک GestureDescription قرار دهید تا stroke را توصیف کند. در نهایت، 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 در زمان اجرا، لحاظ کنید.

ویژگی‌های پیشرفته

حرکات اثر انگشت

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

منابع اضافی

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

راهنماها

کدلبز

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