0

My application is supporting accessibility feature and application is having both portrait and landscape mode.

In my screen i have some views like button, textview, listview with custom row.

The issue what i am facing is when user focus any item in portrait mode and rotate screen, application is not focusing the same element in landscape mode. Can some one suggest how to set the focus to the item which was selected in portrait mode to landscape mode?

i even did some research on existed applications like native settings apps-wifi page and "ES file explore", in these applications also accessibility is not maintained when user change the orientation to landscape mode. System is selecting some random elements in landscape to portrait or vice versa.

Below is the code snippet

accessibility_sample.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<TextView
    android:id="@+id/name_text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:contentDescription="Name"
    android:focusableInTouchMode="true"
    android:text="Name" />

<TextView
    android:id="@+id/email_text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:contentDescription="Email"
    android:focusableInTouchMode="true"
    android:text="Email" />

<Button
    android:id="@+id/sample_button"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:contentDescription="Button description"
    android:text="ButtonText" />

<CheckBox
    android:id="@+id/checkbox"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:contentDescription="Checkbox description"
    android:text="Checkbox Text" />

<ListView
    android:id="@+id/sample_list"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:fadeScrollbars="false" >
</ListView>

</LinearLayout>

sample_list_row.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" 
>

<CheckBox
    android:id="@+id/list_row_cb"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" >
</CheckBox>

<TextView
    android:id="@+id/list_row_name"
    android:layout_width="match_parent"
    android:focusableInTouchMode="true"
    android:focusable="true"
    android:layout_height="wrap_content" />

  </LinearLayout>

AccessibilitySampleActivity.java

public class AccessibilitySampleActivity extends Activity {

private String TAG = AccessibilitySampleActivity.class.getSimpleName();
ListView sampleList;
Button myButton;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.accessibility_sample);
    sampleList = (ListView) findViewById(R.id.sample_list);
    myButton = (Button) findViewById(R.id.sample_button);
    ArrayList<String> countriesList = new ArrayList<String>();
    countriesList.add("India");
    countriesList.add("America");
    countriesList.add("China");
    countriesList.add("Swis");
    countriesList.add("Paries");
    countriesList.add("Pak");
    countriesList.add("Aus");
    countriesList.add("Afg");
    countriesList.add("Nedharnalds");
    countriesList.add("Bangladhesh");
    countriesList.add("Srilanka");
    countriesList.add("France");
    countriesList.add("Japan");
    countriesList.add("SouthAfrica");
    countriesList.add("Iran");
    countriesList.add("Malaysia");
    countriesList.add("Nepal");

    sampleList.setAdapter(new CustomArrayAdapter(this, R.layout.sample_list_row, countriesList));

}

class CustomArrayAdapter extends ArrayAdapter<String> {

    Context context;
    int resource;
    ArrayList<String> countriesList;

    class ViewHolder {
        TextView name;
    }

    public CustomArrayAdapter(Context context, int resource, ArrayList<String> countriesList) {
        super(context, resource, countriesList);
        this.context = context;
        this.resource = resource;
        this.countriesList = countriesList;

    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;

        if (convertView == null) {
            LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = vi.inflate(resource, null);

            holder = new ViewHolder();
            holder.name = (TextView) convertView.findViewById(R.id.list_row_name);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }
        holder.name.setText(getItem(position));

        return convertView;
    }

    @Override
    public int getCount() {
        return super.getCount();
    }

}

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static View findAccessibilityFocus(View view) {
    if (view == null)
        return view;

    if (view.isAccessibilityFocused())
        return view;

    if (view instanceof ViewGroup) {
        ViewGroup viewGroup = (ViewGroup) view;

        for (int i = 0; i < viewGroup.getChildCount(); i++) {
            View childView = viewGroup.getChildAt(i);

            View result = findAccessibilityFocus(childView);

            if (result != null)
                return result;
        }
    }

    return null;
}

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    Log.d(TAG, "onConfigurationChanged");
    AccessibilityManager am = (AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE);
    boolean isAccessibilityEnabled = am.isEnabled();
    boolean isExploreByTouchEnabled = am.isTouchExplorationEnabled();
    Log.d(TAG, "isAccessibilityEnabled:" + isAccessibilityEnabled + " isExploreByTouchEnabled:"
            + isExploreByTouchEnabled);

    if (isAccessibilityEnabled) {
        View activityView = this.findViewById(android.R.id.content);
        Log.d(TAG, "activityView:" + activityView);
        View selectedView = findAccessibilityFocus(activityView);
        if (selectedView != null) {
            Log.d(TAG, "selectedView:" + selectedView);
            selectedView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
        }
    }
}

}

For normal views like TextView, Button, Checkbox it is able to maintain state when user rotate screen.

If the user select list view below are the issues i am facing

  • If any view is selected in portrait and rotated screen, then focus is not maintained in landscape. Some times If the selected view is able to display without scrolling(like if user select India and rotate India will be in visible are without scrolling) it maintain the state
  • When user rotate screen from portrait to landscape, apply scrolling and select any view(like Japan) and change orientation form landscape to portrait mode, then we could see that it always select first row checkbox ie checkbox of india.
Ashok Reddy Narra
  • 577
  • 10
  • 27

1 Answers1

0

WCAG currently does not specify what should happen with focus after dynamic (while your app is running) orientation changes. It does discuss orientation changes, but not what to do with focus on such context changes, and for good reason. Focus management is not the core issue here! In fact, if anything, WCAG would discourage you from the solution you are attempting, as it could be seen as interfering with the default operation of the Assistive Technology. Think about it, in your question you pointed out that most apps "don't do" the thing that you're trying to do. If you were to accomplish this thing, AND EVERY OTHER APP IN THE WORLD DIDN'T, who is really inaccessible? Is not "expected behavior" beneficial?

The inaccessible part of this is the surprising orientation change NOT what happens with focus after a COMPLETELY SCREEN RE-DRAW! It seems to me, that the motivation for this is potentially a misguided interpretation of guidelines under WCAG 2.0 3.2.#. If users are unexpectedly causing orientation changes at times when they care about where focus is... this is indeed an issue! You just have latched onto the wrong part of the problem.

Focus control is very important for accessibility, but orientation changes are different from other contextual changes. In an orientation change, the screen is potentially completely different. Sure, there are going to be similar elements on screen, but no guarantee that the focused element is even still present. Rather than attempting to track focus and move it to the appropriate (potentially now off screen element) let the AT do it's thing, and be more selective about when orientation changes occur. Best practices here:

  • Never lock a user into a specific orientation. Remember users may have their device mounted in front of them, so requiring an orientation is very bad. Mobile Guidelines 4.1.
  • However, this being said, once your sure the user (when Assistive Technologies that use touch exploration are one) is in the orientation they are comfortable with, lock the orientation, and provide another mechanism for them to manually change orientation. This way the user can freely do things like hold their phone to their ear, without worrying about accidentally triggering an orientation change. WCAG 2.0 - 3.2.5
  • Should an orientation change occur, allow the default behavior. Even if it appears to you to be inaccessible, it's likely not that frustrating AS LONG AS users aren't accidentally triggering orientation changes. Which if you've addressed the issue above properly, will not be a problem.
  • Finally, remember, that when your developing for accessibility, TalkBack is just one Assistive Technology, and blind individuals are just one subset of the users of this technology. While, you may perhaps be improving the user experience for blind TalkBack users, you have to consider what you're doing for TalkBacks users who use talkback to support their physical disability, or BrailleBack users, or SwitchAccess users. In general, supporting multiple ATs means allowing the operating system, AT and user Agent to do the default/supported thing as often as possible. While adding in hack solutions may improve experience for blind TalkBack users, on TalkBack v#.#, it could easily make things worse for current SwitchAccess users, and even break it for blink TalkBack v#.#+1 users. This concept of hack solutions being bad is actually covered in WCAG as well, under WCAG 4.1.

In conclusion, what you're trying to do is really somewhat misguided from an accessibility perspective, and would actually be making things worse. That being said, the concept of tracking accessibility focus in an Android view is interesting, and I will leave the code for you below. But, I would recommend against this approach, in this instance.

ORIGINAL ANWER:

This is pretty simple.

Telling talkback to focus a view:

View theView = findViewById(R.id.theViewId);
theView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);

As far as finding the currently focused node, you want to search your view heirarchy for the view that is focused.

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static View findAccessibilityFocus(View view) {
    if (view == null) return view;

    if (view.isAccessibilityFocused()) return view;

    if (view instanceof ViewGroup) {
        ViewGroup viewGroup = (ViewGroup)view;

        for (int i = 0; i < viewGroup.getChildCount(); i++) {
            View childView = viewGroup.getChildAt(i);

            View result = findAccessibilityFocus(childView);

            if (result != null) return result;
        }
    }

    return null;
}

You have to be careful when using this function! Accessibility focus can be a difficult thing to find when race conditions are involved. A view DOES NOT have to have a view that is accessibility focused. It may be easier to attach an accessibility delegate to your root view and keep track of the last element that had accessibility focus, tracking the corresponding accessibility events.

MobA11y
  • 18,425
  • 3
  • 49
  • 76
  • isAccessibilityFocused() is undefined. Could you please suggest on this? – Ashok Reddy Narra May 10 '17 at 14:45
  • Could you please suggest some sample ? – Ashok Reddy Narra May 10 '17 at 15:08
  • Some sample of what? This function is working fine in my app, so suggesting samples is silly, you should provide more detail if the above is not working... This function is clearly defined for android.widget.View instances as of Lollipop, per Android documentation. If that's not enough information, your question is lacking significant detail. Namely OS version/device restrictions. – MobA11y May 10 '17 at 17:12
  • Thank you for replay.. I have edited the question with my example by making use of your reply. Now only the issue i am facing is related to list view. Could you please suggest what could be the solution for my issue? – Ashok Reddy Narra May 12 '17 at 14:57
  • The additional detail is great, but none of it answers the real question: "What does 'isAccessibilityFocused() is undefined mean". Are you saying the function I wrote is returning null, or the function I wrote is not building for you? The addtional information provided doesn't really help me answer your question. Or rather, if I this had been the question I read initially, I would have provided the exact same answer... – MobA11y May 12 '17 at 17:52
  • It does seem to me though, that you're calling the function in the wrong place. "onConfigurationChanged" is called after the views configuration has changed. NOTHING will be focused at this point... or perhaps there would be a race condition for focus, BUT CERTAINLY, the thing that was focused has already been unfocused. This is clearly not what you want. – MobA11y May 12 '17 at 17:58
  • I mean to say that your function " findAccessibilityFocus" helped me a lot, and isAccessibilityFocused() also available, that time i was checking with lower sdk version. Now in my screen the top views like Name,Email,2 buttons, checkbox is able to maintain selection when user move from potrait to landscape or vice-versa. The issue what i am facing is for the listview row. If any item of the row (either checkbox or textview ) is selected and user rotate screen, selection is not maintained.Could you please suggest how can we do this?.. – Ashok Reddy Narra May 12 '17 at 18:15
  • Also instead of wring in "onConfigurationChanged" which is best method to write that code? – Ashok Reddy Narra May 12 '17 at 18:15
  • I believe you'll have to track focus throughout, the callback that will allow you to catch the currently focus on orientation changes, without a race condition, is not available. Tracking accessibility focus may be better done through tracking accessibility events, using an instance of AccessibilityDelegate attached to each focusable view, but before implementing that, please read my answer edits, for as to why I may believe this is a misguided approach to this problem. – MobA11y May 12 '17 at 18:19
  • Just saw your answer edited. Good to know the inner details. So if i understand correctly In general we don't need to maintain the accessibility focus when user rotate the screen. Is that correct?. If YES could you please suggest any referral link, so that i can give that link to customer and close the issue in my application. – Ashok Reddy Narra May 12 '17 at 18:31
  • That is correct, screen changes are not the same as other contextual changes. If your screen is changing as a result of things within the application, I would consider this a separate issue. But, maintaining focus on orientation changes is is a misinterpretation of a WCAG success criteria. I'll add a link to WCAG and the relevant success criteria as references for my answer. – MobA11y May 13 '17 at 00:34