פיתוח שירות נגישות ל-Android

1. מבוא

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

מהו שירות נגישות?

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

דוגמאות לשירותי נגישות נפוצים

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

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

Google מספקת שירותים כמו 'גישה באמצעות מתג', 'גישה קולית' ו-TalkBack למשתמשי Android, אבל לא בטוח שהשירותים האלה יכולים לשרת את כל המשתמשים עם מוגבלויות. למשתמשים רבים עם מוגבלויות יש צרכים ייחודיים, לכן ממשקי ה-API של Android ליצירת שירותי נגישות פתוחים, והמפתחים יכולים ליצור שירותי נגישות ולהפיץ אותם בחינם דרך חנות Play.

מה תפַתחו

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

ממשק API לנגישות חזק: הקוד של השירות שתפתחו נמצא בארבעה קבצים בלבד והוא משתמש בכ-200 שורות קוד!

משתמש הקצה

תיצור שירות למשתמש היפותטי עם המאפיינים הבאים:

  • המשתמש מתקשה להגיע ללחצנים הצדדיים במכשיר.
  • המשתמש מתקשה בגלילה או בהחלקה.

פרטי השירות

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

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

מה צריך להכין

ה-Codelab הזה מניח שתשתמשו בנתונים הבאים:

  1. מחשב עם Android Studio.
  2. טרמינל לביצוע פקודות מעטפת פשוטות.
  3. מכשיר עם Android 7.0 (Nougat) שמחובר למחשב שמשמש לפיתוח.

קדימה, מתחילים!

2. בתהליך ההגדרה

באמצעות הטרמינל, יוצרים את הספרייה שבה רוצים לעבוד. שינוי לספרייה הזו.

להורדת הקוד

אפשר לשכפל את המאגר שמכיל את הקוד של ה-Codelab הזה:

git clone https://github.com/android/codelab-android-accessibility.git

המאגר מכיל כמה פרויקטים של Android Studio. באמצעות Android Studio, פותחים את GlobalActionBarService.

כדי להפעיל את Android Studio, לוחצים על הסמל של Studio:

הלוגו המשמש להפעלת Android Studio.

בוחרים באפשרות Import project (Eclipse ADT, Gradle וכו'):

מסך הפתיחה של Android Studio.

עוברים למיקום שבו שכפול המקור ובוחרים GlobalActionBarService.

לאחר מכן, עוברים לספריית הבסיס (root) באמצעות טרמינל.

3. הסבר על קוד ההתחלה

חוקרים את הפרויקט שפתחתם.

כבר נוצר עבורך השלד הבסיסי של שירות הנגישות. כל הקוד שתכתבו ב-Codelab הזה מוגבל לארבעת הקבצים הבאים:

  1. app/src/main/AndroidManifest.xml
  2. app/src/main/res/layout/action_bar.xml
  3. app/src/main/res/xml/global_action_bar_service.xml
  4. app/src/main/java/com/example/android/globalactionbarservice/GlobalActionBarService.java

בהמשך מופיעה הדרכה מפורטת לגבי התוכן של כל קובץ.

AndroidManifest.xml

המידע על שירות הנגישות מוצהר במניפסט:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.example.android.globalactionbarservice">

   <application>
       <service
           android:name=".GlobalActionBarService"
           android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
           android:exported="true">
           <intent-filter>
               <action android:name="android.accessibilityservice.AccessibilityService" />
           </intent-filter>
           <meta-data
               android:name="android.accessibilityservice"
               android:resource="@xml/global_action_bar_service" />
       </service>
   </application>
</manifest>

שלושת הפריטים הנדרשים הבאים מוצהרים בקובץ AndroidManifest.xml:

  1. הרשאה לקשר לשירות נגישות:
<service
    ...
    android:permission = "android.permission.BIND_ACCESSIBILITY_SERVICE">
    ...             
</service>
  1. ה-Intent של AccessibilityService הוא:
<intent-filter>
   <action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
  1. מיקום הקובץ המכיל את המטא-נתונים של השירות שאתם יוצרים:
<meta-data
       ...
       android:resource="@xml/global_action_bar_service" />
</service>

global_action_bar_service.xml

הקובץ הזה מכיל את המטא-נתונים של השירות.

<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
   android:accessibilityFeedbackType="feedbackGeneric"
   android:accessibilityFlags="flagDefault"
   android:canPerformGestures="true"
   android:canRetrieveWindowContent="true" />

באמצעות רכיב &lt;accessibility-service&gt;, הוגדרו המטא-נתונים הבאים:

  1. סוג המשוב לשירות הזה (ב-Codelab הזה נעשה שימוש ב-feedbackGeneral, שזו ברירת מחדל טובה).
  2. דגלי הנגישות של השירות (ב-Codelab הזה נעשה שימוש בדגלי ברירת מחדל).
  3. היכולות הנדרשות לשירות:
  4. כדי לבצע החלקה, הפונקציה android:canPerformGestures מוגדרת כ-true.
  5. כדי לאחזר תוכן של חלון, android:canRetrieveWindowContent מוגדר כ-true.

GlobalActionBarService.java

רוב הקוד של שירות הנגישות נמצא ב-GlobalActionBarService.java. בהתחלה הקובץ מכיל את קוד המינימום המוחלט של שירות נגישות:

  1. מחלקה שמרחיבה את AccessibilityService.
  2. שתי שיטות נדרשות מבוטלות (השארת ריקה ב-Codelab הזה).
public class GlobalActionBarService extends AccessibilityService {

   @Override
   public void onAccessibilityEvent(AccessibilityEvent event) {

   }

   @Override
   public void onInterrupt() {

   }
}

צריך להוסיף קוד לקובץ הזה במהלך ה-Codelab.

action_bar.xml

השירות חושף ממשק משתמש עם ארבעה לחצנים, וקובץ הפריסה action_bar.xml מכיל את תגי העיצוב להצגת הלחצנים האלה:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:orientation="horizontal"
   android:layout_width="match_parent"
   android:layout_height="wrap_content">
</LinearLayout>

בשלב הזה, הקובץ הזה מכיל LinearLayout ריקה. במהלך ה-Codelab צריך להוסיף תגי עיצוב ללחצנים.

הפעלת האפליקציה

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

עוברים אל הגדרות > נגישות. שירות סרגל הפעולות הגלובלי מותקן במכשיר.

מסך הגדרות הנגישות

לוחצים על Global Action Bar Service ומפעילים אותו. אתם אמורים לראות את תיבת הדו-שיח הבאה של ההרשאה:

תיבת דו-שיח להרשאה של שירות נגישות.

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

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

4. יצירת הלחצנים

פותחים את action_bar.xml ב-res/layout. מוסיפים את תגי העיצוב בתוך LinearLayout הריקה כרגע:

<LinearLayout ...>
    <Button
        android:id="@+id/power"
        android:text="@string/power"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <Button
        android:id="@+id/volume_up"
        android:text="@string/volume"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <Button
        android:id="@+id/scroll"
        android:text="@string/scroll"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <Button
        android:id="@+id/swipe"
        android:text="@string/swipe"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</LinearLayout>

הפעולה הזו יוצרת לחצנים שהמשתמש צריך ללחוץ עליהם כדי להפעיל פעולות במכשיר.

פותחים את GlobalActionBarService.java ומוסיפים משתנה כדי לשמור את הפריסה של סרגל הפעולות:

public class GlobalActionBarService extends AccessibilityService {
    FrameLayout mLayout;
    ...
}

עכשיו מוסיפים שיטת onServiceStarted():

public class GlobalActionBarService extends AccessibilityService {
   FrameLayout mLayout;

   @Override
   protected void onServiceConnected() {
       // Create an overlay and display the action bar
       WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
       mLayout = new FrameLayout(this);
       WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
       lp.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
       lp.format = PixelFormat.TRANSLUCENT;
       lp.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
       lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
       lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
       lp.gravity = Gravity.TOP;
       LayoutInflater inflater = LayoutInflater.from(this);
       inflater.inflate(R.layout.action_bar, mLayout);
       wm.addView(mLayout, lp);
   }
}

הקוד מנפח את הפריסה ומוסיף את סרגל הפעולות לכיוון החלק העליון של המסך.

ה-method onServiceConnected() פועלת כשהשירות מחובר. בשלב זה, לשירות הנגישות יש את כל ההרשאות הנחוצות להפעלתו. הרשאת המפתח שבה משתמשים כאן היא ההרשאה WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY. ההרשאה הזו מאפשרת לכם לצייר ישירות על המסך מעל התוכן הקיים, בלי שתצטרכו לבצע תהליך הרשאות מורכב.

מחזור החיים של שירות נגישות

מחזור החיים של שירות נגישות מנוהל על ידי המערכת באופן בלעדי ותואם למחזור החיים של השירות שהוגדר.

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

הפעלת השירות

לפני שתוכל להפעיל את השירות באמצעות Android Studio, עליך לוודא שהגדרות ההרצה מוגדרות כראוי.

עורכים את ההגדרות האישיות של Run (משתמשים באפשרות 'הרצה' בתפריט העליון ועוברים אל 'עריכת הגדרות אישיות'). לאחר מכן, באמצעות התפריט הנפתח, משנים את אפשרות ההפעלה מ'פעילות ברירת מחדל' ל"שום דבר".

תפריט נפתח לקביעת הגדרות ההרצה להפעלת שירות באמצעות Android Studio.

עכשיו אמורה להיות לך אפשרות להפעיל את השירות באמצעות Android Studio.

לוחצים על סמל ההפעלה הירוק לחצן Play ב-Android Studio ששימש להפעלת השירות בסרגל התפריטים לכיוון החלק העליון של המסך. לאחר מכן עוברים אל הגדרות > נגישות ומפעילים את האפשרות Global Action Bar Service.

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

overlay.png

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

5. הגדרת לחצן ההפעלה

מוסיפים את השיטה configurePowerButton() ל-configurePowerButton():

private void configurePowerButton() {
   Button powerButton = (Button) mLayout.findViewById(R.id.power);
   powerButton.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View view) {
           performGlobalAction(GLOBAL_ACTION_POWER_DIALOG);
       }
   });
}

כדי לגשת לתפריט ההפעלה, configurePowerButton() משתמש במתודה performGlobalAction(), שמסופקת על ידי AccessibilityService. הקוד שהוספת הוא פשוט: לחיצה על הלחצן מפעילה onClickListener(). הפעולה הזו מפעילה את performGlobalAction(GLOBAL_ACTION_POWER_DIALOG) ומציגה את תיבת הדו-שיח של ההפעלה למשתמש.

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

עכשיו מוסיפים את הפונקציה configurePowerButton() לסיום של ה-method configurePowerButton():

@Override
protected void onServiceConnected() {
   ...
   configurePowerButton();
}

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

לוחצים על לחצן ההפעלה כדי להציג את תיבת הדו-שיח של ההפעלה.

6. הגדרת לחצן עוצמת הקול

מוסיפים את השיטה configureVolumeButton() ל-configureVolumeButton():

private void configureVolumeButton() {
   Button volumeUpButton = (Button) mLayout.findViewById(R.id.volume_up);
   volumeUpButton.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View view) {
           AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
           audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
                   AudioManager.ADJUST_RAISE, AudioManager.FLAG_SHOW_UI);
       }
   });
}

שיטת configureVolumeButton() מוסיפה configureVolumeButton() שמופעל כאשר המשתמש לוחץ על לחצן עוצמת הקול. בתוך ה-listener הזה, הפונקציה configureVolumeButton() משתמשת ב-AudioManager כדי לכוונן את עוצמת הקול של השידור.

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

עכשיו מוסיפים את הפונקציה configureVolumeButton() לסוף של המתודה configureVolumeButton():

@Override
protected void onServiceConnected() {
   ...

   configureVolumeButton();
}

לוחצים על סמל ההפעלה הירוק לחצן Play ב-Android Studio ששימש להפעלת השירות בסרגל התפריטים לכיוון החלק העליון של המסך. לאחר מכן עוברים אל 'הגדרות' > נגישות והפעלה של שירות סרגל הפעולות הגלובלי.

לוחצים על לחצן עוצמת הקול כדי לשנות את עוצמת הקול.

המשתמש ההיפותטי שלא יכול להגיע אל פקדי עוצמת הקול בצד המכשיר יכול עכשיו להשתמש בשירות סרגל פעולות גלובלי כדי לשנות (להגדיל) את עוצמת הקול.

7. הגדרת לחצן הגלילה

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

מוסיפים את השיטה findScrollableNode ל-findScrollableNode:

private AccessibilityNodeInfo findScrollableNode(AccessibilityNodeInfo root) {
   Deque<AccessibilityNodeInfo> deque = new ArrayDeque<>();
   deque.add(root);
   while (!deque.isEmpty()) {
       AccessibilityNodeInfo node = deque.removeFirst();
       if (node.getActionList().contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD)) {
           return node;
       }
       for (int i = 0; i < node.getChildCount(); i++) {
           deque.addLast(node.getChild(i));
       }
   }
   return null;
}

לשירות נגישות אין גישה לתצוגות שמופיעות בפועל במסך. במקום זאת, הוא רואה השתקפות של מה שמופיע במסך בצורת עץ שמורכב מאובייקטים של AccessibilityNodeInfo. האובייקטים האלה מכילים מידע על התצוגה שהם מייצגים (מיקום התצוגה, טקסט שמשויך לתצוגה, מטא-נתונים שנוספו לצורך נגישות, פעולות שנתמכות על ידי התצוגה וכו'). השיטה findScrollableNode() מבצעת מעבר רוחב-ראשון של העץ הזה, החל מהצומת של הרמה הבסיסית (root). אם היא מוצאת צומת שניתן לגלול (כלומר, צומת שתומך בפעולה AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD), הוא מחזיר אותו, אחרת הוא יחזיר null.

עכשיו מוסיפים את השיטה configureScrollButton() ל-configureScrollButton():

private void configureScrollButton() {
   Button scrollButton = (Button) mLayout.findViewById(R.id.scroll);
   scrollButton.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View view) {
           AccessibilityNodeInfo scrollable = findScrollableNode(getRootInActiveWindow());
           if (scrollable != null) {
               scrollable.performAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId());
           }
       }
   });
}

השיטה הזו יוצרת onClickListener() שמופעל כשמשתמש לוחץ על לחצן הגלילה. המערכת מנסה למצוא צומת שניתן לגלול, ואם הפעולה מצליחה, היא מבצעת את פעולת הגלילה.

עכשיו מוסיפים את configureScrollButton() ל-configureScrollButton():

@Override
protected void onServiceConnected() {
   ...

   configureScrollButton();
}

לוחצים על סמל ההפעלה הירוק לחצן Play ב-Android Studio ששימש להפעלת השירות בסרגל התפריטים לכיוון החלק העליון של המסך. לאחר מכן עוברים אל 'הגדרות' > נגישות והפעלה של שירות סרגל הפעולות הגלובלי.

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

8. הגדרה של לחצן ההחלקה

מוסיפים את השיטה configureSwipeButton() ל-configureSwipeButton():

private void configureSwipeButton() {
   Button swipeButton = (Button) mLayout.findViewById(R.id.swipe);
   swipeButton.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View view) {
           Path swipePath = new Path();
           swipePath.moveTo(1000, 1000);
           swipePath.lineTo(100, 1000);
           GestureDescription.Builder gestureBuilder = new GestureDescription.Builder();
           gestureBuilder.addStroke(new GestureDescription.StrokeDescription(swipePath, 0, 500));
           dispatchGesture(gestureBuilder.build(), null, null);
       }
   });
}

השיטה configureSwipeButton() משתמשת ב-API חדש שהתווסף ב-N שמבצע תנועות בשם המשתמש. הקוד משתמש באובייקט GestureDescription כדי לציין את הנתיב לביצוע התנועה (ב-codelab הזה נעשה שימוש בערכים בתוך הקוד), ולאחר מכן הוא שולח את תנועת ההחלקה בשם המשתמש באמצעות המתודה AccessibilityService dispatchGesture().

עכשיו מוסיפים את ההגדרה configureSwipeButton() ל-configureSwipeButton():

@Override
protected void onServiceConnected() {
   ...
   configureSwipeButton();
}

לוחצים על סמל ההפעלה הירוק לחצן Play ב-Android Studio ששימש להפעלת השירות בסרגל התפריטים לכיוון החלק העליון של המסך. לאחר מכן עוברים אל 'הגדרות' > נגישות והפעלה של שירות סרגל הפעולות הגלובלי.

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

9. סיכום

מעולה! פיתחתם שירות נגישות פונקציונלי פשוט ופונקציונלי.

ניתן להרחיב את השירות הזה במגוון דרכים. לדוגמה:

  1. אפשר להזיז את סרגל הפעולות (בשלב הזה הוא פשוט מונח על המסך).
  2. מאפשרים למשתמש להגביר וגם להנמיך את עוצמת הקול.
  3. המשתמש יכול להחליק גם שמאלה וגם ימינה.
  4. הוספת תמיכה בתנועות נוספות שסרגל הפעולות יכול להגיב להן.

ה-Codelab הזה מכסה רק חלק קטן מהפונקציונליות של ממשקי ה-API לנגישות. ה-API כולל גם את הפרטים הבאים (רשימה חלקית):

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

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