Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dcc25309 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Populates collection info count for A11y toggle feature pages." into main

parents 7b6a49a3 1890c16f
Loading
Loading
Loading
Loading
+13 −3
Original line number Diff line number Diff line
@@ -37,6 +37,13 @@ flag {
  bug: "300302098"
}

flag {
  name: "enable_color_contrast_control"
  namespace: "accessibility"
  description: "Allows users to control color contrast in the Accessibility settings page."
  bug: "246577325"
}

flag {
  name: "enable_hearing_aid_preset_control"
  namespace: "accessibility"
@@ -89,8 +96,11 @@ flag {
}

flag {
  name: "enable_color_contrast_control"
  name: "toggle_feature_fragment_collection_info"
  namespace: "accessibility"
  description: "Allows users to control color contrast in the Accessibility settings page."
  bug: "246577325"
  description: "Provides custom CollectionInfo for ToggleFeaturePreferenceFragment"
  bug: "318607873"
  metadata {
    purpose: PURPOSE_BUGFIX
  }
}
+152 −0
Original line number Diff line number Diff line
/*
 * 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();
        }
    }
}
+9 −0
Original line number Diff line number Diff line
@@ -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);
    }
}
+2 −1
Original line number Diff line number Diff line
@@ -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);
    }
+2 −1
Original line number Diff line number Diff line
@@ -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);
    }
Loading