একটি অ্যাক্সেসিবিলিটি পরিষেবা তৈরি করুন

অ্যাক্সেসিবিলিটি সার্ভিস হলো এমন একটি অ্যাপ যা প্রতিবন্ধী ব্যবহারকারীদের বা যারা সাময়িকভাবে কোনো ডিভাইসের সাথে পুরোপুরি ইন্টারঅ্যাক্ট করতে অক্ষম, তাদের সহায়তা করার জন্য ইউজার ইন্টারফেসকে উন্নত করে। এই সার্ভিসগুলো ব্যাকগ্রাউন্ডে চলে এবং ব্যবহারকারীর পক্ষ থেকে স্ক্রিনের বিষয়বস্তু পরীক্ষা করতে ও অ্যাপগুলো ব্যবহার করতে সিস্টেমের সাথে যোগাযোগ করে। এর উদাহরণগুলোর মধ্যে রয়েছে স্ক্রিন রিডার (যেমন টকব্যাক), সুইচ অ্যাক্সেস টুল এবং ভয়েস কন্ট্রোল সিস্টেম।

এই নির্দেশিকায় একটি অ্যান্ড্রয়েড অ্যাক্সেসিবিলিটি সার্ভিস তৈরির প্রাথমিক বিষয়গুলো আলোচনা করা হয়েছে।

অ্যাক্সেসিবিলিটি পরিষেবা জীবনচক্র

একটি অ্যাক্সেসিবিলিটি সার্ভিস তৈরি করতে, আপনাকে অবশ্যই 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 : আপনার সার্ভিসের যদি UI হায়ারার্কি পরীক্ষা করার প্রয়োজন হয় (উদাহরণস্বরূপ, স্ক্রিন থেকে টেক্সট পড়ার জন্য), তবে এটি অবশ্যই true হতে হবে।
  • android:canPerformGestures : যদি আপনি প্রোগ্রাম্যাটিকভাবে জেসচার (যেমন সোয়াইপ বা ট্যাপ) সম্পাদন করতে চান, তবে এটি অবশ্যই true হতে হবে।
  • android:accessibilityFlags : ফিচারগুলো চালু করতে ফ্ল্যাগগুলো একত্রিত করুন। ফিঙ্গারপ্রিন্ট জেসচারের জন্য flagRequestFingerprintGestures আবশ্যক। সফটওয়্যার অ্যাক্সেসিবিলিটি বাটনের জন্য flagRequestAccessibilityButton আবশ্যক।

কনফিগারেশন বিকল্পগুলির সম্পূর্ণ তালিকার জন্য, AccessibilityServiceInfo দেখুন।

রানটাইম কনফিগারেশন

এক্সএমএল কনফিগারেশন স্থির হলেও, আপনি রানটাইমে গতিশীলভাবে আপনার পরিষেবা কনফিগারেশন পরিবর্তন করতে পারেন। ব্যবহারকারীর পছন্দ অনুযায়ী বৈশিষ্ট্য চালু বা বন্ধ করার জন্য এটি উপযোগী।

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

UI বিষয়বস্তু ব্যাখ্যা করুন

যখন onAccessibilityEvent() ট্রিগার হয়, তখন সিস্টেম একটি AccessibilityEvent প্রদান করে। এই ইভেন্টটি অ্যাক্সেসিবিলিটি ট্রি- এর প্রবেশদ্বার হিসেবে কাজ করে, যা স্ক্রিনের বিষয়বস্তুর একটি শ্রেণিবদ্ধ উপস্থাপনা।

আপনার পরিষেবাটি প্রাথমিকভাবে 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
}

ব্যবহারকারীদের পক্ষে কাজ করুন

অ্যাক্সেসিবিলিটি সার্ভিসগুলো ব্যবহারকারীর পক্ষ থেকে বোতামে ক্লিক করা বা তালিকা স্ক্রোল করার মতো কাজ সম্পাদন করতে পারে।

কোনো কাজ সম্পাদন করতে, একটি AccessibilityNodeInfo অবজেক্টের উপর performAction() কল করুন।

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" ডিক্লেয়ার করুন, যাতে আপনি 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
    )
}

আপনার কনফিগারেশনে, XML-এ অথবা রানটাইমে setServiceInfo মাধ্যমে, FLAG_ENABLE_ACCESSIBILITY_VOLUME ফ্ল্যাগটি অবশ্যই অন্তর্ভুক্ত করুন।

উন্নত বৈশিষ্ট্য

আঙুলের ছাপের অঙ্গভঙ্গি

অ্যান্ড্রয়েড ১০ (এপিআই লেভেল ২৯) বা তার চেয়ে উন্নত সংস্করণে চালিত ডিভাইসগুলোতে, আপনার সার্ভিস ফিঙ্গারপ্রিন্ট সেন্সরের দিকনির্দেশক সোয়াইপগুলো ক্যাপচার করতে পারে। এটি বিকল্প নেভিগেশন কন্ট্রোল প্রদানের জন্য উপযোগী।

আপনার 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 প্রসেস করে, তখন সঠিক টেক্সট-টু-স্পিচ ভাষা নির্ধারণ করতে LocaleSpan অবজেক্টের text প্রপার্টিটি পরীক্ষা করুন।

অতিরিক্ত সম্পদ

আরও জানতে, নিম্নলিখিত উৎসগুলো দেখুন:

গাইড

কোডল্যাবস

বিষয়বস্তু দেখুন