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
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:
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.