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

Commit 2030f45f authored by Noah Wang's avatar Noah Wang
Browse files

Replace SeekBar with LabeledSeekBar for Accessibility-related settings

in order to adjust the nonsense read-out by talkback.

Change-Id: I5d2d333c045c3753784b01f731e68918d1175241
parent 815665a1
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -70,7 +70,7 @@
                android:focusable="true"
                android:contentDescription="@string/font_size_make_smaller_desc" />

            <SeekBar
            <com.android.settings.widget.LabeledSeekBar
                android:id="@+id/seek_bar"
                android:layout_width="0dp"
                android:layout_height="48dp"
+1 −1
Original line number Diff line number Diff line
@@ -69,7 +69,7 @@
                android:focusable="true"
                android:contentDescription="@string/screen_zoom_make_smaller_desc" />

            <SeekBar
            <com.android.settings.widget.LabeledSeekBar
                android:id="@+id/seek_bar"
                android:layout_width="0dp"
                android:layout_height="48dp"
+3 −1
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import com.android.settings.widget.DotsPageIndicator;
import com.android.settings.widget.LabeledSeekBar;


/**
@@ -97,7 +98,8 @@ public abstract class PreviewSeekBarPreferenceFragment extends SettingsPreferenc
        // seek bar.
        final int max = Math.max(1, mEntries.length - 1);

        final SeekBar seekBar = (SeekBar) content.findViewById(R.id.seek_bar);
        final LabeledSeekBar seekBar = (LabeledSeekBar) content.findViewById(R.id.seek_bar);
        seekBar.setLabels(mEntries);
        seekBar.setMax(max);
        seekBar.setProgress(mInitialIndex);
        seekBar.setOnSeekBarChangeListener(new onPreviewSeekBarChangeListener());
+188 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 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.widget;

import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.widget.ExploreByTouchHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.SeekBar;

import java.util.List;

/**
 * LabeledSeekBar represent a seek bar assigned with labeled, discrete values.
 * It pretends to be a group of radio button for AccessibilityServices, in order to adjust the
 * behavior of these services to keep the mental model of the visual discrete SeekBar.
 */
public class LabeledSeekBar extends SeekBar {

    private class LabeledSeekBarExploreByTouchHelper extends ExploreByTouchHelper {

        public LabeledSeekBarExploreByTouchHelper(View forView) {
            super(forView);
        }

        @Override
        protected int getVirtualViewAt(float x, float y) {
            return getVirtualViewIdIndexFromX(x);
        }

        @Override
        protected void getVisibleVirtualViews(List<Integer> list) {
            for (int i = 0, c = LabeledSeekBar.this.getMax(); i <= c; ++i) {
                list.add(i);
            }
        }

        @Override
        protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
                Bundle arguments) {
            if (virtualViewId == ExploreByTouchHelper.HOST_ID) {
                // Do nothing
                return false;
            }

            switch (action) {
                case AccessibilityNodeInfoCompat.ACTION_CLICK:
                    LabeledSeekBar.this.setProgress(virtualViewId);
                    sendEventForVirtualView(virtualViewId, AccessibilityEvent.TYPE_VIEW_CLICKED);
                    return true;
                default:
                    return false;
            }
        }

        @Override
        protected void onPopulateNodeForVirtualView(
                int virtualViewId, AccessibilityNodeInfoCompat node) {
            node.setClassName(RadioButton.class.getName());
            node.setBoundsInParent(getBoundsInParentFromVirtualViewId(virtualViewId));
            node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
            node.setContentDescription(mLabels[virtualViewId]);
            node.setClickable(true);
            node.setCheckable(true);
            node.setChecked(virtualViewId == LabeledSeekBar.this.getProgress());
        }

        @Override
        protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
            event.setClassName(RadioButton.class.getName());
            event.setContentDescription(mLabels[virtualViewId]);
            event.setChecked(virtualViewId == LabeledSeekBar.this.getProgress());
        }

        @Override
        protected void onPopulateNodeForHost(AccessibilityNodeInfoCompat node) {
            node.setClassName(RadioGroup.class.getName());
        }

        @Override
        protected void onPopulateEventForHost(AccessibilityEvent event) {
            event.setClassName(RadioGroup.class.getName());
        }

        private int getHalfVirtualViewWidth() {
            final int width = LabeledSeekBar.this.getWidth();
            final int barWidth = width - LabeledSeekBar.this.getPaddingStart()
                    - LabeledSeekBar.this.getPaddingEnd();
            return Math.max(0, barWidth / (LabeledSeekBar.this.getMax() * 2));
        }

        private int getVirtualViewIdIndexFromX(float x) {
            final int posBase = Math.max(0,
                    ((int) x - LabeledSeekBar.this.getPaddingStart()) / getHalfVirtualViewWidth());
            return (posBase + 1) / 2;
        }

        private Rect getBoundsInParentFromVirtualViewId(int virtualViewId) {
            int left = (virtualViewId * 2 - 1) * getHalfVirtualViewWidth()
                    + LabeledSeekBar.this.getPaddingStart();
            int right = (virtualViewId * 2 + 1) * getHalfVirtualViewWidth()
                    + LabeledSeekBar.this.getPaddingStart();

            // Edge case
            left = virtualViewId == 0 ? 0 : left;
            right = virtualViewId == LabeledSeekBar.this.getMax()
                    ? LabeledSeekBar.this.getWidth() : right;

            final Rect r = new Rect();
            r.set(left, 0, right, LabeledSeekBar.this.getHeight());
            return r;
        }
    }

    private String[] mLabels;

    private ExploreByTouchHelper mAccessHelper;

    public LabeledSeekBar(Context context, AttributeSet attrs) {
        this(context, attrs, com.android.internal.R.attr.seekBarStyle);
    }

    public LabeledSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public LabeledSeekBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public synchronized void setProgress(int progress) {
        if (mAccessHelper != null) {
            mAccessHelper.invalidateRoot();
        }

        super.setProgress(progress);
    }

    public void setLabels(String[] labels) {
        mLabels = labels;
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        mAccessHelper = new LabeledSeekBarExploreByTouchHelper(this);
        ViewCompat.setAccessibilityDelegate(this, mAccessHelper);
    }

    @Override
    protected void onDetachedFromWindow() {
        ViewCompat.setAccessibilityDelegate(this, null);
        mAccessHelper = null;
        super.onDetachedFromWindow();
    }

    @Override
    protected boolean dispatchHoverEvent(MotionEvent event) {
        if (mAccessHelper != null && mAccessHelper.dispatchHoverEvent(event)) {
            return true;
        }

        return super.dispatchHoverEvent(event);
    }
}