Accessibility function implementation problems in Android

2.8k views Asked by At

I'm developing application that views books. There is a screen (Activity) which shows a book. It has custom view, something similar to ViewSwitcher and every page is a bitmap that is rendered by a custom View.

Now I should implement accessibility function - book should be read by the phone (audio).

I've read Accessibility section here https://developer.android.com/guide/topics/ui/accessibility/index.html but it is not clear enough.

I use SupportLibrary for accessibility management and now I have this code in ViewGroup (which manages book pages). Code 1:

private class EditionPagesViewSwitcherAccessibilityDelegate extends AccessibilityDelegateCompat {

    private int mPageCount;
    private double[] mPageRange;

    @Override
    public void onInitializeAccessibilityEvent(final View host, final AccessibilityEvent event) {
        super.onInitializeAccessibilityEvent(host, event);
        event.setClassName(EditionPagesViewSwitcher.class.getName());
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            event.setScrollable(canScroll());
        }
        if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED && updatePageValues()) {
            event.setItemCount(mPageCount);
            // we use +1 because of user friendly numbers (from 1 not 0)
            event.setFromIndex((int) (mPageRange[0] + 1));
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                event.setToIndex((int) (mPageRange[1] + 1));
            }
        }
    }

    @Override
    public void onInitializeAccessibilityNodeInfo(final View host, final AccessibilityNodeInfoCompat info) {
        super.onInitializeAccessibilityNodeInfo(host, info);
        info.setClassName(EditionPagesViewSwitcher.class.getName());

        info.setScrollable(canScroll());
        info.setLongClickable(true);
        if (canScrollForward()) {
            info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
        }
        if (canScrollBackward()) {
            info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
        }
    }

    @Override
    public boolean performAccessibilityAction(final View host, final int action, final Bundle args) {
        if (super.performAccessibilityAction(host, action, args)) {
            return true;
        }
        switch (action) {
            case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: {
                if (canScrollForward()) {
                    showNext();
                    return true;
                }
            }
            return false;
            case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: {
                if (canScrollBackward()) {
                    showPrevious();
                    return true;
                }
            }
            return false;
        }
        return false;
    }

Here is code from page view Code 2:

    @Override
    public void onInitializeAccessibilityEvent(final View host, final AccessibilityEvent event) {
        super.onInitializeAccessibilityEvent(host, event);

        event.setClassName(EditionPageView.class.getName());
        if (hasText()) {
            event.getText().add(getPageRangeText());
            final String trimText = mSurfaceUpdateData.getPageText().trim();
            if (trimText.length() > MAX_TEXT_LENGTH) {
                event.getText().add(trimText.substring(0, MAX_TEXT_LENGTH));
//              event.getText().add(trimText.substring(MAX_TEXT_LENGTH, trimText.length()));
            }
            else {
                event.getText().add(trimText);
            }
        }
    }

    @Override
    public void onInitializeAccessibilityNodeInfo(final View host, final AccessibilityNodeInfoCompat info) {
        super.onInitializeAccessibilityNodeInfo(host, info);

        info.setClassName(EditionPageView.class.getName());
    }

Because page text data loads asynchronous first time accessibility don't have any text while executes onInitializeAccessibilityEvent code. And then when data have been loaded I fire AccessibilityEvent.TYPE_VIEW_SELECTED and AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED events. Then onInitializeAccessibilityEvent executes again and phone "read" book text.

So my questions:

  1. Is my Accessibility implementation right? May be it is design wrong? Because I didn't find any good tutorial about this feature.
  2. Why I need to use SDK versions checks in Support implementations in Code 1? Why support implementation doesn't handle it correctly?
  3. Is firing TYPE_VIEW_SELECTED and TYPE_VIEW_TEXT_CHANGED really needed? Or may be some other code should be implemented?
  4. The main question. In Code 2 there is commented code line. This code statement substring text to be less then MAX_TEXT_LENGTH (it's 3800) because if text is bigger nothing is played. Nothing. Is it accessibility restriction? Any other text that is less then this value is played well.
  5. Does anyone know where I can find any good tutorial? (yes I saw samples).
  6. Does anyone have any custom realizations to look through?

UPDATED

Well. Here is some answers:

  1. As I can see TYPE_VIEW_SELECTED and TYPE_VIEW_TEXT_CHANGED events are not needed if you don't want this text to be read as soon as you get it.
  2. On Nexus 7 all large text is played well (text up to 8000 symbols), so this issue doesn't reproduce on it, but on Samsung Galaxy Tab 10.1 (Android 4.0.4) and Genymotion emulator of Tab 10.1 with Android 4.3 does. And this is strange...
2

There are 2 answers

2
Koen Gabriels On

4.. According to the documentation of String.substring() The first argument you pass is the start index in the original string, the second argument is the end index in the original string.

Example:

String text = "Hello";
partOfText = text.substring(2,text.length() - 1);

partOfText equals to "llo" (the first char is index 0)

So by putting your constant MAX_TEXT_LENGTH as a first argument, it would start at index 3800 to take out the substring.

http://developer.android.com/reference/java/lang/String.html#substring(int)

1
edwindh On

You are right MAX_TEXT_LENGTH is 3800.

About your doubt,

this code:

 event.getText().add(trimText.substring(MAX_TEXT_LENGTH, trimText.length()));
        }

you are trying to substring "trimText" from MAX_TEXT_LENGTH to trimText.length() ! Supposing that trimText = "STACK", trimText.length() = 5, then trimText.substring(3800,5) is going to be ?

At first, this doesn't have sense, using correctly would be like this: trimText.substring(0,2) = "ST";