יצירת שירות נגישות משלך

שירות נגישות הוא אפליקציה שמשפרת את ממשק המשתמש כדי לסייע משתמשים עם מוגבלויות או אשר באופן זמני לא יכולים לבצע אינטראקציה מלאה באמצעות מכשיר. לדוגמה, משתמשים שנוהגים ומטפלים בילד או ילדה, ייתכן שיהיה צורך בממשק נוסף או חלופי, או השתתפות במסיבה רועשת מאוד משוב.

Android מספק שירותי נגישות רגילים, כולל TalkBack , והמפתחים יכולים ליצור ולהפיץ את השירותים שלהם. המסמך הזה הסבר על העקרונות הבסיסיים של שירות נגישות.

אפשר לקבץ שירות נגישות עם אפליקציה רגילה או ליצור אותו בתור לפרויקט Android נפרד. השלבים ליצירת השירות זהים ב- בכל מצב.

יצירת שירות נגישות

בתוך הפרויקט, יוצרים מחלקה שמתרחבת AccessibilityService:

Kotlin

package com.example.android.apis.accessibility

import android.accessibilityservice.AccessibilityService
import android.view.accessibility.AccessibilityEvent

class MyAccessibilityService : AccessibilityService() {
...
    override fun onInterrupt() {}

    override fun onAccessibilityEvent(event: AccessibilityEvent?) {}
...
}

Java

package com.example.android.apis.accessibility;

import android.accessibilityservice.AccessibilityService;
import android.view.accessibility.AccessibilityEvent;

public class MyAccessibilityService extends AccessibilityService {
...
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
    }

    @Override
    public void onInterrupt() {
    }

...
}

אם יוצרים פרויקט חדש בשביל הService הזה ואין לך כוונה להפעיל אפליקציה שמשויכים אליו, אפשר להסיר את הכיתה Activity עם הסימן לתחילת פעולה מקור.

הצהרות והרשאות במניפסט

אפליקציות שמספקות שירותי נגישות חייבות לכלול הצהרות ספציפיות את קובצי המניפסט של האפליקציה שלהם כך שיטופלו כשירות נגישות על ידי Android המערכת. בקטע הזה מוסבר על ההגדרות הנדרשות והאופציונליות של שירותי נגישות.

הצהרה על שירות נגישות

כדי שהאפליקציה שלך תיחשב כשירות נגישות, יש לכלול service רכיב - ולא רכיב activity - בתוך application במניפסט. בנוסף, בתוך הרכיב service, יש לכלול מסנן Intent של שירות נגישות. המניפסט צריך גם להגן על השירות על ידי הוספת BIND_ACCESSIBILITY_SERVICE כדי להבטיח שרק המערכת תוכל לקשר אליו. הנה דוגמה:

  <application>
    <service android:name=".MyAccessibilityService"
        android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
        android:label="@string/accessibility_service_label">
      <intent-filter>
        <action android:name="android.accessibilityservice.AccessibilityService" />
      </intent-filter>
    </service>
  </application>

הגדרה של שירותי נגישות

שירותי הנגישות חייבים לספק תצורה שמציינת את הסוגים של אירועי נגישות שבהם השירות מטפל ומידע נוסף על את השירות. התצורה של שירות נגישות כלולה AccessibilityServiceInfo בכיתה. השירות יכול ליצור ולקבוע הגדרות אישיות באמצעות מופע של כיתה setServiceInfo() בזמן ריצה. אבל לא כל אפשרויות ההגדרה זמינות כשמשתמשים .

אפשר לכלול רכיב <meta-data> במניפסט עם הפניה אל קובץ תצורה, שמאפשר להגדיר את כל טווח האפשרויות שירות נגישות, כמו בדוגמה הבאה:

<service android:name=".MyAccessibilityService">
  ...
  <meta-data
    android:name="android.accessibilityservice"
    android:resource="@xml/accessibility_service_config" />
</service>

רכיב <meta-data> הזה מתייחס לקובץ XML שיצרת ספריית המשאבים של האפליקציה: <project_dir>/res/xml/accessibility_service_config.xml>. את הקוד הבא מציג דוגמה לתוכן של קובץ תצורת השירות:

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

למידע נוסף על מאפייני XML שניתן להשתמש בהם ב קובץ התצורה של שירותי נגישות. אפשר לעיין בחומר העזר הבא: תיעוד:

מידע נוסף על הגדרות התצורה שניתן להגדיר באופן דינמי בזמן הריצה, AccessibilityServiceInfo מסמכי עזר.

הגדרת שירות הנגישות

כדאי להביא בחשבון את הנקודות הבאות כשמגדירים את משתני ההגדרה של שירות נגישות כדי לומר למערכת איך ומתי להפעיל:

  • לאילו סוגי אירועים היית רוצה שהאירוע יגיב?
  • האם השירות צריך להיות פעיל בכל האפליקציות או רק בחבילה ספציפית שמות?
  • באילו סוגי משובים הוא משתמש?

יש שתי אפשרויות להגדרת המשתנים האלה. אפשרות של תאימות לאחור הוא להגדיר אותם בקוד, setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo) כדי לעשות את זה, משנים את onServiceConnected() ולהגדיר שם את השירות, כפי שמוצג בדוגמה הבאה:

Kotlin

override fun onServiceConnected() {
    info.apply {
        // Set the type of events that this service wants to listen to. Others
        // aren't passed to this service.
        eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED or AccessibilityEvent.TYPE_VIEW_FOCUSED

        // If you only want this service to work with specific apps, set their
        // package names here. Otherwise, when the service is activated, it
        // listens to events from all apps.
        packageNames = arrayOf("com.example.android.myFirstApp", "com.example.android.mySecondApp")

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

        // Default services are invoked only if no package-specific services are
        // present for the type of AccessibilityEvent generated. This service is
        // app-specific, so the flag isn't necessary. For a general-purpose
        // service, consider setting the DEFAULT flag.

        // flags = AccessibilityServiceInfo.DEFAULT;

        notificationTimeout = 100
    }

    this.serviceInfo = info

}

Java

@Override
public void onServiceConnected() {
    // Set the type of events that this service wants to listen to. Others
    // aren't passed to this service.
    info.eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED |
            AccessibilityEvent.TYPE_VIEW_FOCUSED;

    // If you only want this service to work with specific apps, set their
    // package names here. Otherwise, when the service is activated, it listens
    // to events from all apps.
    info.packageNames = new String[]
            {"com.example.android.myFirstApp", "com.example.android.mySecondApp"};

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

    // Default services are invoked only if no package-specific services are
    // present for the type of AccessibilityEvent generated. This service is
    // app-specific, so the flag isn't necessary. For a general-purpose service,
    // consider setting the DEFAULT flag.

    // info.flags = AccessibilityServiceInfo.DEFAULT;

    info.notificationTimeout = 100;

    this.setServiceInfo(info);

}

האפשרות השנייה היא להגדיר את השירות באמצעות קובץ XML. מסוימת להגדרות אישיות, כמו canRetrieveWindowContent זמינים רק אם הגדרת את השירות באמצעות XML. ההגדרות האישיות מהדוגמה הקודמת נראות כך כאשר הן מוגדרות באמצעות XML:

<accessibility-service
     android:accessibilityEventTypes="typeViewClicked|typeViewFocused"
     android:packageNames="com.example.android.myFirstApp, com.example.android.mySecondApp"
     android:accessibilityFeedbackType="feedbackSpoken"
     android:notificationTimeout="100"
     android:settingsActivity="com.example.android.apis.accessibility.TestBackActivity"
     android:canRetrieveWindowContent="true"
/>

אם אתם משתמשים ב-XML, יש להפנות אליו במניפסט על ידי הוספת <meta-data> הצהרת שירות שמצביעה על קובץ ה-XML. אם אתם מאחסנים את קובץ ה-XML ב: res/xml/serviceconfig.xml, התג החדש נראה כך:

<service android:name=".MyAccessibilityService">
     <intent-filter>
         <action android:name="android.accessibilityservice.AccessibilityService" />
     </intent-filter>
     <meta-data android:name="android.accessibilityservice"
     android:resource="@xml/serviceconfig" />
</service>

שיטות שירות הנגישות

שירות נגישות צריך להרחיב את המחלקה AccessibilityService לשנות את השיטות הבאות מאותה המחלקה. השיטות האלה מוצגות הסדר שבו מערכת Android מתקשרת אליהם: מרגע הפעלת השירות (onServiceConnected()), עד שהאפליקציה פועלת (onAccessibilityEvent(), onInterrupt()), עד למועד כיבוי (onUnbind()).

  • onServiceConnected(): (אופציונלי) המערכת קוראת לשיטה הזו כשהיא מתחבר לשירות הנגישות שלך. ניתן להשתמש בשיטה הזו לביצוע הגדרה חד-פעמית שלבים לשירות שלך, כולל התחברות למערכת המשוב של המשתמשים שירותים, כגון מנהל האודיו או הרטט של המכשיר. אם רוצים להגדיר את תצורת השירות בזמן הריצה או לבצע שינויים חד-פעמיים, זהו מיקום נוח להתקשר אל setServiceInfo().

  • onAccessibilityEvent(): (חובה) המערכת קוראת חזרה לשיטה הזו כאשר הוא מזהה AccessibilityEvent שתואם לפרמטרים לסינון אירועים שצוינו על ידי הנגישות שלך השירות, למשל כשהמשתמש מקיש על לחצן או מתמקד בממשק משתמש באפליקציה ששירות הנגישות שלך מספק משוב עליה. מתי המערכת קוראת לשיטה הזו, היא מעבירה את AccessibilityEvent המשויך, שהשירות יכול לפרש ולהשתמש בו כדי לספק משוב משתמש. אפשר לקרוא לשיטה הזו פעמים רבות במהלך מחזור החיים של לאחר השיפור.

  • onInterrupt(): (חובה) המערכת קוראת לשיטה הזו כשהמערכת רוצה להפסיק את המשוב שהשירות מספק, בדרך כלל תגובה לפעולת משתמש, כמו העברת המיקוד לפקד אחר. הזה ניתן לקרוא ל-method מספר פעמים במהלך מחזור החיים של השירות.

  • onUnbind(): (אופציונלי) המערכת קוראת לשיטה הזו כשהמערכת עומד לכבות את שירות הנגישות. בשיטה הזו אפשר הליכי השבתה חד-פעמיים, כולל ביטול הקצאת משוב למשתמשים שירותים, כגון מנהל האודיו או הרטט של המכשיר.

השיטות האלה להתקשרות חזרה מספקות את המבנה הבסיסי של הנגישות לאחר השיפור. באפשרותך להחליט איך לעבד את הנתונים שמסופקים על ידי מערכת Android ב בצורה של אובייקטים מסוג AccessibilityEvent ונותנים משוב למשתמש. עבור מידע נוסף על קבלת מידע מאירוע נגישות, ראו קבלת פרטי האירוע.

הרשמה לאירועי נגישות

אחת הפונקציות החשובות ביותר בהגדרת שירות הנגישות היא מאפשרת לציין אילו סוגים של אירועי נגישות השירות לטפל בתופעה. ציון המידע הזה מאפשר לשירותי הנגישות לשתף פעולה שתי רשתות נוירונים זו מול זו, ונותן לכם את הגמישות לטפל רק באירוע ספציפי מאפליקציות ספציפיות. סינון האירועים יכול לכלול את הפרטים הבאים: קריטריונים:

  • שמות חבילות: מציינים את שמות החבילות של אפליקציות שהנגישות שלהן האירועים שבהם אתם רוצים שהשירות יטפל. אם לא תשמיט את הפרמטר הזה, שירות נגישות נחשב כזמין לנגישות של שירות אירועים באפליקציה כלשהי. אפשר להגדיר את הפרמטר הזה בשירות הנגישות קובצי תצורה עם המאפיין android:packageNames רשימה מופרדת בפסיקים או להשתמש בפונקציה AccessibilityServiceInfo.packageNames חבר.

  • סוגי אירועים: מציינים את הסוגים של אירועי הנגישות שאתם רוצים שירות שצריך לטפל בו. אפשר להגדיר את הפרמטר הזה בשירות הנגישות קובצי תצורה עם המאפיין android:accessibilityEventTypes: רשימה שמופרדת באמצעות התו |, לדוגמה accessibilityEventTypes="typeViewClicked|typeViewFocused". לחלופין אפשר להגדיר באמצעות התכונה AccessibilityServiceInfo.eventTypes חבר.

כשמגדירים את שירות הנגישות, חשוב לשקול אילו אירועים יכול לטפל באירועים האלה ולהירשם אליהם בלבד. מאחר שמשתמשים יכולים להפעיל מספר שירותי נגישות בו-זמנית, אין לצרוך את השירות אירועים שהוא לא יכול לטפל בהם. חשוב לזכור ששירותים אחרים עשויים לטפל בהן אירועים כדי לשפר את חוויית המשתמש.

עוצמת הקול לנגישות

מכשירים שפועלת בהם גרסת Android 8.0 (רמת API 26) ואילך כוללים את STREAM_ACCESSIBILITY קטגוריית עוצמת קול, שמאפשרת לשלוט בעוצמת הקול של הנגישות פלט האודיו של השירות בנפרד מצלילים אחרים במכשיר.

שירותי הנגישות יכולים להשתמש בסוג השידור הזה על ידי הגדרת FLAG_ENABLE_ACCESSIBILITY_VOLUME כאפשרות. לאחר מכן אפשר לשנות את עוצמת הקול של האודיו לנגישות במכשיר באמצעות התקשרות ה adjustStreamVolume() ב-method batch במופע של המכשיר AudioManager

קטע הקוד הבא מדגים איך שירות נגישות יכול להשתמש קטגוריית כרך STREAM_ACCESSIBILITY:

Kotlin

import android.media.AudioManager.*

class MyAccessibilityService : AccessibilityService() {

    private val audioManager = getSystemService(AUDIO_SERVICE) as AudioManager

    override fun onAccessibilityEvent(accessibilityEvent: AccessibilityEvent) {
        if (accessibilityEvent.source.text == "Increase volume") {
            audioManager.adjustStreamVolume(AudioManager.STREAM_ACCESSIBILITY, ADJUST_RAISE, 0)
        }
    }
}

Java

import static android.media.AudioManager.*;

public class MyAccessibilityService extends AccessibilityService {
    private AudioManager audioManager =
            (AudioManager) getSystemService(AUDIO_SERVICE);

    @Override
    public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
        AccessibilityNodeInfo interactedNodeInfo =
                accessibilityEvent.getSource();
        if (interactedNodeInfo.getText().equals("Increase volume")) {
            audioManager.adjustStreamVolume(AudioManager.STREAM_ACCESSIBILITY,
                ADJUST_RAISE, 0);
        }
    }
}

למידע נוסף, ראו את הסרטון מה חדש בנגישות ב-Android מ-Google I/O 2017. החל מ- 6:35.

קיצור הדרך לנגישות

במכשירים עם Android מגרסה 8.0 (רמת API 26) ואילך, המשתמשים יכולים להפעיל ולהשבית את שירות הנגישות המועדף בכל מסך על ידי לחיצה על לחיצה על שני מקשי עוצמת הקול בו-זמנית. למרות שקיצור דרך זה מאפשר משבית את Talkback כברירת מחדל, המשתמשים יכולים להגדיר את הלחצן כדי להפעיל להשבית כל שירות שמותקן במכשיר שלהם.

כדי שמשתמשים יוכלו לגשת לשירות נגישות מסוים מקש קיצור, השירות צריך לבקש את התכונה בזמן הריצה.

למידע נוסף, ראו את הסרטון מה חדש בנגישות ב-Android מ-Google I/O 2017. החל מ- 13:25.

לחצן הנגישות

במכשירים עם אזור ניווט שעבר עיבוד באמצעות תוכנה שפועלת בהם מערכת Android 8.0 (רמת API 26) ומעלה, הצד השמאלי של סרגל הניווט כולל לחצן הנגישות. כשמשתמשים ילחצו על הלחצן הזה, הם יוכלו להפעיל אחת מספר תכונות ושירותים מופעלים של נגישות, בהתאם לתוכן שמוצגת עכשיו במסך.

כדי לאפשר למשתמשים להפעיל שירות נגישות נתון באמצעות הנגישות לחצן, השירות צריך להוסיף את FLAG_REQUEST_ACCESSIBILITY_BUTTON דגל ב-android:accessibilityFlags של אובייקט AccessibilityServiceInfo . לאחר מכן השירות יכול לרשום התקשרות חזרה באמצעות registerAccessibilityButtonCallback()

קטע הקוד הבא מדגים איך אפשר להגדיר נגישות שירות לתגובה למשתמש בלחיצה על לחצן הנגישות:

Kotlin

private var mAccessibilityButtonController: AccessibilityButtonController? = null
private var accessibilityButtonCallback:
        AccessibilityButtonController.AccessibilityButtonCallback? = null
private var mIsAccessibilityButtonAvailable: Boolean = false

override fun onServiceConnected() {
    mAccessibilityButtonController = accessibilityButtonController
    mIsAccessibilityButtonAvailable =
            mAccessibilityButtonController?.isAccessibilityButtonAvailable ?: false

    if (!mIsAccessibilityButtonAvailable) return

    serviceInfo = serviceInfo.apply {
        flags = flags or AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON
    }

    accessibilityButtonCallback =
        object : AccessibilityButtonController.AccessibilityButtonCallback() {
            override fun onClicked(controller: AccessibilityButtonController) {
                Log.d("MY_APP_TAG", "Accessibility button pressed!")

                // Add custom logic for a service to react to the
                // accessibility button being pressed.
            }

            override fun onAvailabilityChanged(
                    controller: AccessibilityButtonController,
                    available: Boolean
            ) {
                if (controller == mAccessibilityButtonController) {
                    mIsAccessibilityButtonAvailable = available
                }
            }
    }

    accessibilityButtonCallback?.also {
        mAccessibilityButtonController?.registerAccessibilityButtonCallback(it, null)
    }
}

Java

private AccessibilityButtonController accessibilityButtonController;
private AccessibilityButtonController
        .AccessibilityButtonCallback accessibilityButtonCallback;
private boolean mIsAccessibilityButtonAvailable;

@Override
protected void onServiceConnected() {
    accessibilityButtonController = getAccessibilityButtonController();
    mIsAccessibilityButtonAvailable =
            accessibilityButtonController.isAccessibilityButtonAvailable();

    if (!mIsAccessibilityButtonAvailable) {
        return;
    }

    AccessibilityServiceInfo serviceInfo = getServiceInfo();
    serviceInfo.flags
            |= AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
    setServiceInfo(serviceInfo);

    accessibilityButtonCallback =
        new AccessibilityButtonController.AccessibilityButtonCallback() {
            @Override
            public void onClicked(AccessibilityButtonController controller) {
                Log.d("MY_APP_TAG", "Accessibility button pressed!");

                // Add custom logic for a service to react to the
                // accessibility button being pressed.
            }

            @Override
            public void onAvailabilityChanged(
              AccessibilityButtonController controller, boolean available) {
                if (controller.equals(accessibilityButtonController)) {
                    mIsAccessibilityButtonAvailable = available;
                }
            }
        };

    if (accessibilityButtonCallback != null) {
        accessibilityButtonController.registerAccessibilityButtonCallback(
                accessibilityButtonCallback, null);
    }
}

למידע נוסף, ראו את הסרטון מה חדש בנגישות ב-Android מ-Google I/O 2017. החל מ- 16:28.

תנועות של טביעות אצבעות

שירותי נגישות במכשירים שמותקנת בהם גרסת Android 8.0 (רמת API 26) ומעלה יכול להגיב להחלקות בכיוון מסוים (למעלה, למטה, שמאלה וימינה) לאורך מחיישן טביעות אצבע. כדי להגדיר שירות שיקבל שיחות חוזרות לגביו ויוצרות את הרצף הבא:

  1. להצהיר על USE_BIOMETRIC ואת ההרשאה CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES ליכולות של בינה מלאכותית גנרטיבית.
  2. מגדירים את FLAG_REQUEST_FINGERPRINT_GESTURES בתוך המאפיין android:accessibilityFlags.
  3. רישום להתקשרות חזרה באמצעות registerFingerprintGestureCallback().

חשוב לזכור שלא בכל המכשירים יש חיישני טביעות אצבע. כדי לזהות אם המכשיר תומך בחיישן, isHardwareDetected() . גם במכשיר עם חיישן טביעות אצבע, השירות לא יוכל להשתמש בחיישן כשהוא בשימוש למטרות אימות. כדי לזהות מתי שהחיישן זמין, isGestureDetectionAvailable() וליישם את onGestureDetectionAvailabilityChanged() קריאה חוזרת.

בקטע הקוד הבא מוצגת דוגמה לשימוש בתנועות של טביעות אצבע לנווט בלוח משחק וירטואלי:

// AndroidManifest.xml
<manifest ... >
    <uses-permission android:name="android.permission.USE_FINGERPRINT" />
    ...
    <application>
        <service android:name="com.example.MyFingerprintGestureService" ... >
            <meta-data
                android:name="android.accessibilityservice"
                android:resource="@xml/myfingerprintgestureservice" />
        </service>
    </application>
</manifest>
// myfingerprintgestureservice.xml
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    ...
    android:accessibilityFlags=" ... |flagRequestFingerprintGestures"
    android:canRequestFingerprintGestures="true"
    ... />

Kotlin

// MyFingerprintGestureService.kt
import android.accessibilityservice.FingerprintGestureController.*

class MyFingerprintGestureService : AccessibilityService() {

    private var gestureController: FingerprintGestureController? = null
    private var fingerprintGestureCallback:
            FingerprintGestureController.FingerprintGestureCallback? = null
    private var mIsGestureDetectionAvailable: Boolean = false

    override fun onCreate() {
        gestureController = fingerprintGestureController
        mIsGestureDetectionAvailable = gestureController?.isGestureDetectionAvailable ?: false
    }

    override fun onServiceConnected() {
        if (mFingerprintGestureCallback != null || !mIsGestureDetectionAvailable) return

        fingerprintGestureCallback =
                object : FingerprintGestureController.FingerprintGestureCallback() {
                    override fun onGestureDetected(gesture: Int) {
                        when (gesture) {
                            FINGERPRINT_GESTURE_SWIPE_DOWN -> moveGameCursorDown()
                            FINGERPRINT_GESTURE_SWIPE_LEFT -> moveGameCursorLeft()
                            FINGERPRINT_GESTURE_SWIPE_RIGHT -> moveGameCursorRight()
                            FINGERPRINT_GESTURE_SWIPE_UP -> moveGameCursorUp()
                            else -> Log.e(MY_APP_TAG, "Error: Unknown gesture type detected!")
                        }
                    }

                    override fun onGestureDetectionAvailabilityChanged(available: Boolean) {
                        mIsGestureDetectionAvailable = available
                    }
                }

        fingerprintGestureCallback?.also {
            gestureController?.registerFingerprintGestureCallback(it, null)
        }
    }
}

Java

// MyFingerprintGestureService.java
import static android.accessibilityservice.FingerprintGestureController.*;

public class MyFingerprintGestureService extends AccessibilityService {
    private FingerprintGestureController gestureController;
    private FingerprintGestureController
            .FingerprintGestureCallback fingerprintGestureCallback;
    private boolean mIsGestureDetectionAvailable;

    @Override
    public void onCreate() {
        gestureController = getFingerprintGestureController();
        mIsGestureDetectionAvailable =
                gestureController.isGestureDetectionAvailable();
    }

    @Override
    protected void onServiceConnected() {
        if (fingerprintGestureCallback != null
                || !mIsGestureDetectionAvailable) {
            return;
        }

        fingerprintGestureCallback =
               new FingerprintGestureController.FingerprintGestureCallback() {
            @Override
            public void onGestureDetected(int gesture) {
                switch (gesture) {
                    case FINGERPRINT_GESTURE_SWIPE_DOWN:
                        moveGameCursorDown();
                        break;
                    case FINGERPRINT_GESTURE_SWIPE_LEFT:
                        moveGameCursorLeft();
                        break;
                    case FINGERPRINT_GESTURE_SWIPE_RIGHT:
                        moveGameCursorRight();
                        break;
                    case FINGERPRINT_GESTURE_SWIPE_UP:
                        moveGameCursorUp();
                        break;
                    default:
                        Log.e(MY_APP_TAG,
                                  "Error: Unknown gesture type detected!");
                        break;
                }
            }

            @Override
            public void onGestureDetectionAvailabilityChanged(boolean available) {
                mIsGestureDetectionAvailable = available;
            }
        };

        if (fingerprintGestureCallback != null) {
            gestureController.registerFingerprintGestureCallback(
                    fingerprintGestureCallback, null);
        }
    }
}

למידע נוסף, ראו את הסרטון מה חדש בנגישות ב-Android מ-Google I/O 2017. החל מ- 9:03.

המרת טקסט לדיבור בכמה שפות

החל מ-Android 8.0 (רמת API 26), שירות המרת טקסט לדיבור (TTS) של Android יכולים לזהות ולדבר ביטויים במספר שפות בתוך בלוק אחד של טקסט. כדי להפעיל את היכולת הזו למעבר אוטומטי בין שפות בנגישות שירות, לאחסן את כל המחרוזות LocaleSpan אובייקטים, כפי שמוצג בקטע הקוד הבא:

Kotlin

val localeWrappedTextView = findViewById<TextView>(R.id.my_french_greeting_text).apply {
    text = wrapTextInLocaleSpan("Bonjour!", Locale.FRANCE)
}

private fun wrapTextInLocaleSpan(originalText: CharSequence, loc: Locale): SpannableStringBuilder {
    return SpannableStringBuilder(originalText).apply {
        setSpan(LocaleSpan(loc), 0, originalText.length - 1, 0)
    }
}

Java

TextView localeWrappedTextView = findViewById(R.id.my_french_greeting_text);
localeWrappedTextView.setText(wrapTextInLocaleSpan("Bonjour!", Locale.FRANCE));

private SpannableStringBuilder wrapTextInLocaleSpan(
        CharSequence originalText, Locale loc) {
    SpannableStringBuilder myLocaleBuilder =
            new SpannableStringBuilder(originalText);
    myLocaleBuilder.setSpan(new LocaleSpan(loc), 0,
            originalText.length() - 1, 0);
    return myLocaleBuilder;
}

למידע נוסף, ראו את הסרטון מה חדש בנגישות ב-Android מ-Google I/O 2017. החל מ- 10:59.

פעולה בשם משתמשים

החל משנת 2011, שירותי נגישות יכולים לפעול בשם משתמשים, כולל. שינוי מיקוד הקלט ובחירה (הפעלה) של רכיבים בממשק המשתמש. לחשבון בשנת 2012, מגוון הפעולות הורחב וכולל רשימות גלילה ואינטראקציות עם שדות טקסט. שירותי הנגישות יכולים גם לבצע פעולות גלובליות, כמו לנווט למסך הבית, ללחוץ על הלחצן 'הקודם', ולפתוח את מסך ההתראות ורשימת האפליקציות האחרונות. מאז 2012, Android כולל התמקדות בנגישות, שמאפשרת לבחור את כל הרכיבים הגלויים שירות נגישות.

היכולות האלה מאפשרות למפתחים של שירותי נגישות ליצור מצבי ניווט, כמו ניווט באמצעות תנועות, ומתן אפשרויות למשתמשים עם מוגבלויות שליטה משופרת במכשירים מבוססי Android שלהם.

האזנה לתנועות

שירותי הנגישות יכולים להקשיב לתנועות ספציפיות ולהגיב על ידי הפעלה מטעמו של משתמש. כדי להשתמש בתכונה הזו צריך לבקש את שירות הנגישות שלך הפעלת התכונה 'גילוי באמצעות מגע'. השירות שלך יכול לבקש זאת הפעלה באמצעות הגדרה של flags במכונה AccessibilityServiceInfo של השירות, FLAG_REQUEST_TOUCH_EXPLORATION_MODE, כפי שאפשר לראות בדוגמה הבאה.

Kotlin

class MyAccessibilityService : AccessibilityService() {

    override fun onCreate() {
        serviceInfo.flags = AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE
    }
    ...
}

Java

public class MyAccessibilityService extends AccessibilityService {
    @Override
    public void onCreate() {
        getServiceInfo().flags = AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
    }
    ...
}

לאחר קבלת בקשה לשירות להפעלת 'גילוי באמצעות מגע', המשתמש צריך לאפשר להפעיל את התכונה, אם היא עדיין לא פעילה. כשהתכונה הזו פעיל, השירות שלך מקבל התראה על תנועות נגישות באמצעות של השירות שלך onGesture() שיטת הקריאה החוזרת (callback) ויכולה להגיב על ידי פעולה בשם המשתמש.

תנועות מתמשכות

מכשירים שפועלת בהם גרסת Android 8.0 (רמת API 26) תומכים בתנועות המשך, או תנועות פרוגרמטיות שמכילות יותר אובייקט Path.

כשמציינים רצף של תנועות, אפשר לציין שהן שייכות אותה תנועה פרוגרמטית באמצעות הארגומנט הסופי willContinue GestureDescription.StrokeDescription constructor, כפי שמוצג בקטע הקוד הבא:

Kotlin

// Simulates an L-shaped drag path: 200 pixels right, then 200 pixels down.
private fun doRightThenDownDrag() {
    val dragRightPath = Path().apply {
        moveTo(200f, 200f)
        lineTo(400f, 200f)
    }
    val dragRightDuration = 500L // 0.5 second

    // The starting point of the second path must match
    // the ending point of the first path.
    val dragDownPath = Path().apply {
        moveTo(400f, 200f)
        lineTo(400f, 400f)
    }
    val dragDownDuration = 500L
    val rightThenDownDrag = GestureDescription.StrokeDescription(
            dragRightPath,
            0L,
            dragRightDuration,
            true
    ).apply {
        continueStroke(dragDownPath, dragRightDuration, dragDownDuration, false)
    }
}

Java

// Simulates an L-shaped drag path: 200 pixels right, then 200 pixels down.
private void doRightThenDownDrag() {
    Path dragRightPath = new Path();
    dragRightPath.moveTo(200, 200);
    dragRightPath.lineTo(400, 200);
    long dragRightDuration = 500L; // 0.5 second

    // The starting point of the second path must match
    // the ending point of the first path.
    Path dragDownPath = new Path();
    dragDownPath.moveTo(400, 200);
    dragDownPath.lineTo(400, 400);
    long dragDownDuration = 500L;
    GestureDescription.StrokeDescription rightThenDownDrag =
            new GestureDescription.StrokeDescription(dragRightPath, 0L,
            dragRightDuration, true);
    rightThenDownDrag.continueStroke(dragDownPath, dragRightDuration,
            dragDownDuration, false);
}

למידע נוסף, ראו את הסרטון מה חדש בנגישות ב-Android מ-Google I/O 2017. החל מ- 15:47.

שימוש בפעולות בנושא נגישות

שירותי הנגישות יכולים לפעול בשם המשתמשים כדי לפשט את האינטראקציה עם אפליקציות ופרודוקטיביות יותר. היכולת של שירותי הנגישות נוספו ב-2011 והורחבו משמעותית ב-2012.

כדי לפעול בשם משתמשים, שירות הנגישות צריך לרשום כדי לקבל אירועים מאפליקציות ולבקש הרשאה לצפות בתוכן מאפליקציות על ידי הגדרה של android:canRetrieveWindowContent כ-true קובץ תצורת שירות. כאשר אירועים מתקבלים השירות, הוא יכול לאחזר את AccessibilityNodeInfo מהאירוע באמצעות getSource() באמצעות האובייקט AccessibilityNodeInfo, השירות יכול לחקור את התצוגה ההיררכיה כדי לקבוע איזו פעולה לבצע ואז לפעול עבור המשתמש באמצעות performAction().

Kotlin

class MyAccessibilityService : AccessibilityService() {

    override fun onAccessibilityEvent(event: AccessibilityEvent) {
        // Get the source node of the event.
        event.source?.apply {

            // Use the event and node information to determine what action to
            // take.

            // Act on behalf of the user.
            performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD)

            // Recycle the nodeInfo object.
            recycle()
        }
    }
    ...
}

Java

public class MyAccessibilityService extends AccessibilityService {

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        // Get the source node of the event.
        AccessibilityNodeInfo nodeInfo = event.getSource();

        // Use the event and node information to determine what action to take.

        // Act on behalf of the user.
        nodeInfo.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);

        // Recycle the nodeInfo object.
        nodeInfo.recycle();
    }
    ...
}

השיטה performAction() מאפשרת לשירות לבצע פעולה בתוך אפליקציה. אם השירות צריך לבצע פעולה גלובלית, כמו לנווט למסך הבית, להקיש על הלחצן 'הקודם', או לפתוח את במסך ההתראות או ברשימת האפליקציות האחרונות, ואז להשתמש performGlobalAction() .

שימוש בסוגי התמקדות

בשנת 2012, הושקו ב-Android התמקדות בממשק המשתמש שנקראת התמקדות בנגישות. שירותי הנגישות יכולים להשתמש במיקוד הזה כדי לבחור כל ממשק משתמש גלוי ולפעול לפיו. סוג המיקוד הזה שונה ממיקוד בקלט, קובע איזה רכיב בממשק המשתמש שמופיע במסך מקבל קלט מקיש על תווים, מקיש על Enter במקלדת או לוחץ על הלחצן המרכזי בלחצני החיצים (D-pad).

ייתכן מצב שבו רכיב אחד בממשק המשתמש יתמקד בקלט בזמן לרכיב אחר יש מיקוד בנגישות. המטרה של התמקדות בנגישות היא כדי לספק שירותי נגישות שיטה ליצירת אינטראקציה עם רכיבים במסך, בין אם ניתן להתמקד בקלט על ידי נקודת מבט של המערכת. כדי לעזור להבטיח ששירות הנגישות שלך מקיים אינטראקציה נכון עם האפליקציות רכיבי קלט, צריך לפעול לפי ההנחיות לבדיקת הפורמטים של האפליקציות נגישות כדי לבדוק את השירות בזמן השימוש באפליקציה רגילה.

שירות נגישות יכול לקבוע איזה רכיב בממשק המשתמש מכיל קלט מיקוד או נגישות באמצעות AccessibilityNodeInfo.findFocus() . תוכלו גם לחפש אלמנטים שניתן לבחור עם התמקדות בקלט באמצעות focusSearch() . לבסוף, שירות הנגישות יכול להגדיר את מיקוד הנגישות באמצעות ה performAction(AccessibilityNodeInfo.ACTION_SET_ACCESSIBILITY_FOCUS) .

איסוף מידע

בשירותי נגישות יש שיטות סטנדרטיות לאיסוף וייצוג של מפתחות יחידות של פרטים שהמשתמשים סיפקו, כמו פרטי אירועים, טקסט ומספרים.

קבלת פרטים על השינויים בחלון

מערכת Android 9 (רמת API 28) ואילך מאפשרת לאפליקציות לעקוב אחרי עדכוני חלונות כאשר אפליקציה משחזרת חלונות מרובים בו-זמנית. כאשר TYPE_WINDOWS_CHANGED של האירוע, השתמשו getWindowChanges() API לקביעת האופן שבו החלונות משתנים. במהלך עדכון של מספר חלונות, כל אחד המערכת מפיקה סדרת אירועים משלו. ה-method getSource() מחזירה את הרמה הבסיסית (root) של החלון המשויך לכל אירוע.

אם באפליקציה מוגדרת חלונית נגישות כותרים View אובייקטים, השירות שלכם יכול לזהות מתי ממשק המשתמש של האפליקציה מעודכן. כאשר TYPE_WINDOW_STATE_CHANGED התרחש אירוע, יש להשתמש בסוגים שהוחזרו על ידי getContentChangeTypes() כדי לקבוע איך החלון משתנה. לדוגמה, ה-framework יכול לזהות מתי לחלונית יש כותרת חדשה או כאשר חלונית נעלמת.

קבלת פרטי האירוע

מערכת Android מספקת מידע לשירותי נגישות על ממשק המשתמש אינטראקציה באמצעות AccessibilityEvent אובייקטים. בגרסאות קודמות של Android, המידע הזמין עבור אירוע נגישות, תוך מתן חוויה פרטים על שליטה בממשק המשתמש שנבחרה על ידי משתמשים, מוצעת באופן מוגבל מידע לפי הקשר. במקרים רבים, המידע שחסר בהקשר עשוי להיות חיוניות להבנת המשמעות של אמצעי הבקרה שנבחר.

דוגמה לממשק שבו ההקשר הוא קריטי היא יומן או יום תכנון. אם המשתמש בוחר משבצת זמן של 16:00 ברשימה של ימים שני עד שישי ושירות הנגישות מכריז "16:00", אבל לא מודיע על יום השבוע. השם, היום בחודש או שם החודש. המשוב שמתקבל מבלבל. במקרה זה, ההקשר של שליטה בממשק המשתמש הוא קריטי משתמש שרוצה לקבוע פגישה.

מאז 2011, Android מרחיב באופן משמעותי את כמות המידע שירות נגישות יכול לקבל מידע על אינטראקציה עם ממשק המשתמש באמצעות כתיבת אירועי נגישות על סמך היררכיית התצוגות. היררכיית תצוגות מפורטות היא קבוצה של רכיבי ממשק משתמש שמכילים את הרכיב (ההורים שלו) ואת המשתמש רכיבים בממשק שעשויים להיות נכללים על ידי הרכיב הזה (הצאצאים שלו). לחשבון כך, מערכת Android יכולה לספק פרטים עשירים יותר על אירועי נגישות, שירותי נגישות מספקים משוב שימושי יותר למשתמשים.

שירות נגישות מקבל מידע על אירוע בממשק משתמש דרך AccessibilityEvent שהועברה על-ידי המערכת אל השירות שיטת קריאה חוזרת onAccessibilityEvent(). האובייקט הזה מספק פרטים על אירוע, כולל סוג האובייקט שעליו מתבצעת הפעולה, הטקסט התיאורי שלו פרטים נוספים.

  • AccessibilityEvent.getRecordCount() וגם getRecord(int): השיטות האלה מאפשרות לאחזר את AccessibilityRecord אובייקטים שתורמים ל-AccessibilityEvent שהועברו אליכם על ידי המערכת. רמת הפירוט הזו מספקת הקשר נוסף לגבי האירוע מפעיל את שירות הנגישות.

  • AccessibilityRecord.getSource(): השיטה הזו מחזירה אובייקט AccessibilityNodeInfo. האובייקט הזה מאפשר לבקש את היררכיית הפריסה (הורים וצאצאים) של הרכיב מקור אירוע הנגישות. התכונה הזו מאפשרת לנגישות לבדוק את ההקשר המלא של האירוע, כולל התוכן מצב של כל תצוגה כוללת או צפיות צאצא.

פלטפורמת Android מספקת ל-AccessibilityService את היכולת לבצע שאילתה היררכיית התצוגות, תוך איסוף מידע על רכיב ממשק המשתמש שיוצר וגם ההורה והצאצאים שלו. כדי לעשות את זה, צריך להגדיר את השורה הבאה. בתצורת ה-XML:

android:canRetrieveWindowContent="true"

בסיום, מקבלים אובייקט AccessibilityNodeInfo באמצעות getSource(). הקריאה הזו מחזירה אובייקט רק אם החלון שבו נוצר האירוע עדיין החלון הפעיל. אחרת, היא מחזירה null, ולכן צריך להתנהג בהתאם.

בדוגמה הבאה, הקוד מבצע את הפעולות הבאות כשאירוע מתקבל:

  1. פעולה זו תופסת מיד את ההורה של התצוגה המפורטת שממנה הגיע האירוע.
  2. בתצוגה הזו, הוא מחפש תווית ותיבת סימון בצפיות של צאצא.
  3. אם הוא מוצא אותם, הוא יוצר מחרוזת לדיווח למשתמש, שמציינת את ואם היא סומנה.

אם בשלב כלשהו מוחזר ערך null כשעוברים את היררכיית התצוגות, השיטה מוותרת בשקט.

Kotlin

// Alternative onAccessibilityEvent that uses AccessibilityNodeInfo.

override fun onAccessibilityEvent(event: AccessibilityEvent) {

    val source: AccessibilityNodeInfo = event.source ?: return

    // Grab the parent of the view that fires the event.
    val rowNode: AccessibilityNodeInfo = getListItemNodeInfo(source) ?: return

    // Using this parent, get references to both child nodes, the label, and the
    // checkbox.
    val taskLabel: CharSequence = rowNode.getChild(0)?.text ?: run {
        rowNode.recycle()
        return
    }

    val isComplete: Boolean = rowNode.getChild(1)?.isChecked ?: run {
        rowNode.recycle()
        return
    }

    // Determine what the task is and whether it's complete based on the text
    // inside the label, and the state of the checkbox.
    if (rowNode.childCount < 2 || !rowNode.getChild(1).isCheckable) {
        rowNode.recycle()
        return
    }

    val completeStr: String = if (isComplete) {
        getString(R.string.checked)
    } else {
        getString(R.string.not_checked)
    }
    val reportStr = "$taskLabel$completeStr"
    speakToUser(reportStr)
}

Java

// Alternative onAccessibilityEvent that uses AccessibilityNodeInfo.

@Override
public void onAccessibilityEvent(AccessibilityEvent event) {

    AccessibilityNodeInfo source = event.getSource();
    if (source == null) {
        return;
    }

    // Grab the parent of the view that fires the event.
    AccessibilityNodeInfo rowNode = getListItemNodeInfo(source);
    if (rowNode == null) {
        return;
    }

    // Using this parent, get references to both child nodes, the label, and the
    // checkbox.
    AccessibilityNodeInfo labelNode = rowNode.getChild(0);
    if (labelNode == null) {
        rowNode.recycle();
        return;
    }

    AccessibilityNodeInfo completeNode = rowNode.getChild(1);
    if (completeNode == null) {
        rowNode.recycle();
        return;
    }

    // Determine what the task is and whether it's complete based on the text
    // inside the label, and the state of the checkbox.
    if (rowNode.getChildCount() < 2 || !rowNode.getChild(1).isCheckable()) {
        rowNode.recycle();
        return;
    }

    CharSequence taskLabel = labelNode.getText();
    final boolean isComplete = completeNode.isChecked();
    String completeStr = null;

    if (isComplete) {
        completeStr = getString(R.string.checked);
    } else {
        completeStr = getString(R.string.not_checked);
    }
    String reportStr = taskLabel + completeStr;
    speakToUser(reportStr);
}

עכשיו יש לך שירות נגישות מלא ופעיל. אפשר לנסות להגדיר איך הוא מקיים אינטראקציה עם המשתמש על ידי הוספת המרת טקסט לדיבור של Android מנוע או להשתמש ב-Vibrator כדי לספק משוב פיזי משוב.

עיבוד טקסט

מכשירים שפועלת בהם גרסת Android 8.0 (רמת API 26) ואילך כוללים תכונות עיבוד טקסט שמקלות על שירותי הנגישות לזהות יחידות טקסט ספציפיות שמופיעות על המסך ולפעול לפיהן.

הסברים קצרים

Android 9 (רמת API 28) כוללת כמה יכולות שמעניקות לכם גישה הסברים קצרים בממשק המשתמש של האפליקציה. כדאי להשתמש getTooltipText() לקרוא את הטקסט של הסבר קצר, ולהשתמש ACTION_SHOW_TOOLTIP וגם ACTION_HIDE_TOOLTIP כדי להורות למופעים של View להציג או להסתיר את ההסברים הקצרים שלהם.

טקסט של רמז

החל משנת 2017, ב-Android יש כמה שיטות לביצוע אינטראקציה עם טקסט רמז לאובייקט מבוסס-טקסט:

  • isShowingHintText() וגם setShowingHintText() methods מציינות ומגדירות, בהתאמה, אם הטקסט הנוכחי של הצומת מייצג את טקסט הרמז של הצומת.
  • getHintText() מספקת גישה לטקסט הרמז עצמו. גם אם האובייקט לא מוצג טקסט רמז, הקריאה אל getHintText() מצליחה.

המיקומים של תווי הטקסט במסך

במכשירים בגרסת Android 8.0 (רמת API 26) ואילך, שירותי נגישות יכול לקבוע את הקואורדינטות של המסך עבור כל תיבה תוחמת של תו גלוי בווידג'ט TextView. Services (שירותים) למצוא את הקואורדינטות האלה באמצעות refreshWithExtraData() עובר EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY כארגומנט הראשון ואובייקט Bundle כארגומנט השני. בזמן שה-method מתבצעת, המערכת מאכלסת את Bundle ארגומנט עם מערך שניתן לחלקים של Rect אובייקטים. כל אובייקט Rect שמייצג את התיבה התוחמת בתו מסוים.

ערכים סטנדרטיים של טווח חד-צדדי

חלק מהאובייקטים מסוג AccessibilityNodeInfo משתמשים במופע של AccessibilityNodeInfo.RangeInfo כדי לציין שרכיב בממשק המשתמש יכול לקבל טווח ערכים. כשיוצרים טווח באמצעות RangeInfo.obtain() או בעת אחזור הערכים הקיצוניים של הטווח באמצעות getMin() וגם getMax(), חשוב לזכור שמכשירים עם Android בגרסה 8.0 (רמת API 26) ואילך מייצגים טווחים חד-צדדיים בצורה סטנדרטית:

תגובה לאירועי נגישות

עכשיו כשהשירות שלכם מוגדר להרצה ולהאזנה לאירועים, אתם צריכים לכתוב קוד כדי יודע מה לעשות כשAccessibilityEvent מגיע. בתור התחלה, אפשר לשנות את onAccessibilityEvent(AccessibilityEvent) . בשיטה הזאת, משתמשים getEventType() כדי לקבוע את סוג האירוע getContentDescription() כדי לחלץ טקסט של תווית המשויך לתצוגה המפורטת שמפעילה את האירוע:

Kotlin

override fun onAccessibilityEvent(event: AccessibilityEvent) {
    var eventText: String = when (event.eventType) {
        AccessibilityEvent.TYPE_VIEW_CLICKED -> "Clicked: "
        AccessibilityEvent.TYPE_VIEW_FOCUSED -> "Focused: "
        else -> ""
    }

    eventText += event.contentDescription

    // Do something nifty with this text, like speak the composed string back to
    // the user.
    speakToUser(eventText)
    ...
}

Java

@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
    final int eventType = event.getEventType();
    String eventText = null;
    switch(eventType) {
        case AccessibilityEvent.TYPE_VIEW_CLICKED:
            eventText = "Clicked: ";
            break;
        case AccessibilityEvent.TYPE_VIEW_FOCUSED:
            eventText = "Focused: ";
            break;
    }

    eventText = eventText + event.getContentDescription();

    // Do something nifty with this text, like speak the composed string back to
    // the user.
    speakToUser(eventText);
    ...
}

מקורות מידע נוספים

מידע נוסף זמין במקורות המידע הבאים:

מדריכים

שיעורי Lab