Merge "Populates collection info count for A11y toggle feature pages." into main
This commit is contained in:
committed by
Android (Google) Code Review
commit
dcc2530964
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.accessibility;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.core.view.AccessibilityDelegateCompat;
|
||||
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceGroupAdapter;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
|
||||
|
||||
import com.android.settingslib.widget.IllustrationPreference;
|
||||
|
||||
/** Utilities for {@code Settings > Accessibility} fragments. */
|
||||
public class AccessibilityFragmentUtils {
|
||||
// TODO: b/350782252 - Replace with an official library-provided solution when available.
|
||||
/**
|
||||
* Modifies the existing {@link RecyclerViewAccessibilityDelegate} of the provided
|
||||
* {@link RecyclerView} for this fragment to report the number of visible and important
|
||||
* items on this page via the RecyclerView's {@link AccessibilityNodeInfo}.
|
||||
*
|
||||
* <p><strong>Note:</strong> This is special-cased to the structure of these fragments:
|
||||
* one column, N rows (one per preference, including category titles and header+footer
|
||||
* preferences), <=N 'important' rows (image prefs without content descriptions). This
|
||||
* is not intended for use with generic {@link RecyclerView}s.
|
||||
*/
|
||||
public static RecyclerView addCollectionInfoToAccessibilityDelegate(RecyclerView recyclerView) {
|
||||
if (!Flags.toggleFeatureFragmentCollectionInfo()) {
|
||||
return recyclerView;
|
||||
}
|
||||
final RecyclerViewAccessibilityDelegate delegate =
|
||||
recyclerView.getCompatAccessibilityDelegate();
|
||||
if (delegate == null) {
|
||||
// No delegate, so do nothing. This should not occur for real RecyclerViews.
|
||||
return recyclerView;
|
||||
}
|
||||
recyclerView.setAccessibilityDelegateCompat(
|
||||
new RvAccessibilityDelegateWrapper(recyclerView, delegate) {
|
||||
@Override
|
||||
public void onInitializeAccessibilityNodeInfo(@NonNull View host,
|
||||
@NonNull AccessibilityNodeInfoCompat info) {
|
||||
super.onInitializeAccessibilityNodeInfo(host, info);
|
||||
if (!(recyclerView.getAdapter()
|
||||
instanceof final PreferenceGroupAdapter preferenceGroupAdapter)) {
|
||||
return;
|
||||
}
|
||||
final int visibleCount = preferenceGroupAdapter.getItemCount();
|
||||
int importantCount = 0;
|
||||
for (int i = 0; i < visibleCount; i++) {
|
||||
if (isPreferenceImportantToA11y(preferenceGroupAdapter.getItem(i))) {
|
||||
importantCount++;
|
||||
}
|
||||
}
|
||||
info.unwrap().setCollectionInfo(
|
||||
new AccessibilityNodeInfo.CollectionInfo(
|
||||
/*rowCount=*/visibleCount,
|
||||
/*columnCount=*/1,
|
||||
/*hierarchical=*/false,
|
||||
AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_SINGLE,
|
||||
/*itemCount=*/visibleCount,
|
||||
/*importantForAccessibilityItemCount=*/importantCount));
|
||||
}
|
||||
});
|
||||
return recyclerView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the preference will be marked as important to accessibility for the sake
|
||||
* of calculating {@link AccessibilityNodeInfo.CollectionInfo} counts.
|
||||
*
|
||||
* <p>The accessibility service itself knows this information for an individual preference
|
||||
* on the screen, but it expects the preference's {@link RecyclerView} to also provide the
|
||||
* same information for its entire set of adapter items.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static boolean isPreferenceImportantToA11y(Preference pref) {
|
||||
if ((pref instanceof IllustrationPreference illustrationPref
|
||||
&& TextUtils.isEmpty(illustrationPref.getContentDescription()))
|
||||
|| pref instanceof PaletteListPreference) {
|
||||
// Illustration preference that is visible but unannounced by accessibility services.
|
||||
return false;
|
||||
}
|
||||
// All other preferences from the PreferenceGroupAdapter are important.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around a {@link RecyclerViewAccessibilityDelegate} that allows customizing
|
||||
* a subset of methods and while also deferring to the original. All overridden methods
|
||||
* in instantiations of this class should call {@code super}.
|
||||
*/
|
||||
private static class RvAccessibilityDelegateWrapper extends RecyclerViewAccessibilityDelegate {
|
||||
private final RecyclerViewAccessibilityDelegate mOriginal;
|
||||
|
||||
RvAccessibilityDelegateWrapper(RecyclerView recyclerView,
|
||||
RecyclerViewAccessibilityDelegate original) {
|
||||
super(recyclerView);
|
||||
mOriginal = original;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performAccessibilityAction(@NonNull View host, int action, Bundle args) {
|
||||
return mOriginal.performAccessibilityAction(host, action, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitializeAccessibilityNodeInfo(@NonNull View host,
|
||||
@NonNull AccessibilityNodeInfoCompat info) {
|
||||
mOriginal.onInitializeAccessibilityNodeInfo(host, info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitializeAccessibilityEvent(@NonNull View host,
|
||||
@NonNull AccessibilityEvent event) {
|
||||
mOriginal.onInitializeAccessibilityEvent(host, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public AccessibilityDelegateCompat getItemDelegate() {
|
||||
if (mOriginal == null) {
|
||||
// Needed for super constructor which calls getItemDelegate before mOriginal is
|
||||
// defined, but unused by actual clients of this RecyclerViewAccessibilityDelegate
|
||||
// which invoke getItemDelegate() after the constructor finishes.
|
||||
return new ItemDelegate(this);
|
||||
}
|
||||
return mOriginal.getItemDelegate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,7 @@ import androidx.annotation.VisibleForTesting;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.internal.accessibility.common.ShortcutConstants;
|
||||
import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
|
||||
@@ -871,4 +872,12 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
|
||||
return PreferredShortcuts.retrieveUserShortcutType(
|
||||
getPrefContext(), mComponentName.flattenToString(), getDefaultShortcutTypes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent,
|
||||
Bundle savedInstanceState) {
|
||||
RecyclerView recyclerView =
|
||||
super.onCreateRecyclerView(inflater, parent, savedInstanceState);
|
||||
return AccessibilityFragmentUtils.addCollectionInfoToAccessibilityDelegate(recyclerView);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,8 @@ public class ToggleScreenMagnificationPreferenceFragmentForSetupWizard
|
||||
Bundle savedInstanceState) {
|
||||
if (parent instanceof GlifPreferenceLayout) {
|
||||
final GlifPreferenceLayout layout = (GlifPreferenceLayout) parent;
|
||||
return layout.onCreateRecyclerView(inflater, parent, savedInstanceState);
|
||||
return AccessibilityFragmentUtils.addCollectionInfoToAccessibilityDelegate(
|
||||
layout.onCreateRecyclerView(inflater, parent, savedInstanceState));
|
||||
}
|
||||
return super.onCreateRecyclerView(inflater, parent, savedInstanceState);
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ public class ToggleScreenReaderPreferenceFragmentForSetupWizard
|
||||
Bundle savedInstanceState) {
|
||||
if (parent instanceof GlifPreferenceLayout) {
|
||||
final GlifPreferenceLayout layout = (GlifPreferenceLayout) parent;
|
||||
return layout.onCreateRecyclerView(inflater, parent, savedInstanceState);
|
||||
return AccessibilityFragmentUtils.addCollectionInfoToAccessibilityDelegate(
|
||||
layout.onCreateRecyclerView(inflater, parent, savedInstanceState));
|
||||
}
|
||||
return super.onCreateRecyclerView(inflater, parent, savedInstanceState);
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ public class ToggleSelectToSpeakPreferenceFragmentForSetupWizard
|
||||
Bundle savedInstanceState) {
|
||||
if (parent instanceof GlifPreferenceLayout) {
|
||||
final GlifPreferenceLayout layout = (GlifPreferenceLayout) parent;
|
||||
return layout.onCreateRecyclerView(inflater, parent, savedInstanceState);
|
||||
return AccessibilityFragmentUtils.addCollectionInfoToAccessibilityDelegate(
|
||||
layout.onCreateRecyclerView(inflater, parent, savedInstanceState));
|
||||
}
|
||||
return super.onCreateRecyclerView(inflater, parent, savedInstanceState);
|
||||
}
|
||||
|
||||
@@ -23,9 +23,14 @@ import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.internal.accessibility.AccessibilityShortcutController;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.accessibility.AccessibilityFragmentUtils;
|
||||
import com.android.settings.accessibility.AccessibilityShortcutPreferenceFragment;
|
||||
import com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType;
|
||||
import com.android.settings.search.BaseSearchIndexProvider;
|
||||
@@ -176,4 +181,12 @@ public class OneHandedSettings extends AccessibilityShortcutPreferenceFragment {
|
||||
return OneHandedSettingsUtils.isSupportOneHandedMode();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent,
|
||||
Bundle savedInstanceState) {
|
||||
RecyclerView recyclerView =
|
||||
super.onCreateRecyclerView(inflater, parent, savedInstanceState);
|
||||
return AccessibilityFragmentUtils.addCollectionInfoToAccessibilityDelegate(recyclerView);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user