Merge "Replace SeekBar with LabeledSeekBar for Accessibility-related settings in order to adjust the nonsense read-out by talkback."
diff --git a/res/layout/font_size_activity.xml b/res/layout/font_size_activity.xml
index ec064fa..479a5f5 100644
--- a/res/layout/font_size_activity.xml
+++ b/res/layout/font_size_activity.xml
@@ -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"
diff --git a/res/layout/screen_zoom_activity.xml b/res/layout/screen_zoom_activity.xml
index 51fea69..47c6b19 100644
--- a/res/layout/screen_zoom_activity.xml
+++ b/res/layout/screen_zoom_activity.xml
@@ -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"
diff --git a/src/com/android/settings/PreviewSeekBarPreferenceFragment.java b/src/com/android/settings/PreviewSeekBarPreferenceFragment.java
index ea8b55a..ad32c2d 100644
--- a/src/com/android/settings/PreviewSeekBarPreferenceFragment.java
+++ b/src/com/android/settings/PreviewSeekBarPreferenceFragment.java
@@ -28,6 +28,7 @@
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 @@
// 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());
diff --git a/src/com/android/settings/widget/LabeledSeekBar.java b/src/com/android/settings/widget/LabeledSeekBar.java
new file mode 100644
index 0000000..9463bf7
--- /dev/null
+++ b/src/com/android/settings/widget/LabeledSeekBar.java
@@ -0,0 +1,188 @@
+/*
+ * 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);
+ }
+}