summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Stetson <alexstetson@google.com>2022-01-07 12:30:11 -0800
committerFelipe Leme <felipeal@google.com>2022-01-20 17:42:57 +0000
commit87fbd252e42fb3270bd6779379412fbf90dce197 (patch)
treee178e7d895b13f705cd06e9f332d9c865c0dcbab
parenta1bd7f7593f3f346a81ce2721dfe8d862a49106a (diff)
downloadsystemlibs-87fbd252e42fb3270bd6779379412fbf90dce197.tar.gz
Fixed QC SeekBar clickable while disabled behavior
Because SeekBars don't respect drawable states for it's disabled appearance, it needs to actually be disabled to appear correctly. Because of this (and the fact that the AbsSeekBar parent class overrides the View.java allowClickWhileDisabled behavior), it needs to be extended to special case the clickable while disabled state when touched. Bug: 205891123 Bug: 207175350 Test: manual, atest CarQCLibUnitTests (cherry picked from commit feda43803acd3a427ad0a52128bf0f081c372693) Merged-In: I3467334b0c3865dfd8872b6f1723da2eedca5316 Change-Id: I3467334b0c3865dfd8872b6f1723da2eedca5316
-rw-r--r--car-qc-lib/res/layout/qc_row_view.xml2
-rw-r--r--car-qc-lib/src/com/android/car/qc/view/QCRowView.java19
-rw-r--r--car-qc-lib/src/com/android/car/qc/view/QCSeekBarView.java78
-rw-r--r--car-qc-lib/tests/unit/src/com/android/car/qc/view/QCSeekBarViewTest.java97
4 files changed, 182 insertions, 14 deletions
diff --git a/car-qc-lib/res/layout/qc_row_view.xml b/car-qc-lib/res/layout/qc_row_view.xml
index 3005175..6656b29 100644
--- a/car-qc-lib/res/layout/qc_row_view.xml
+++ b/car-qc-lib/res/layout/qc_row_view.xml
@@ -120,7 +120,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/barrier2"
app:layout_constraintBottom_toBottomOf="parent">
- <com.android.car.ui.uxr.DrawableStateSeekBar
+ <com.android.car.qc.view.QCSeekBarView
android:id="@+id/seekbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/car-qc-lib/src/com/android/car/qc/view/QCRowView.java b/car-qc-lib/src/com/android/car/qc/view/QCRowView.java
index e4c3aa3..a11643a 100644
--- a/car-qc-lib/src/com/android/car/qc/view/QCRowView.java
+++ b/car-qc-lib/src/com/android/car/qc/view/QCRowView.java
@@ -76,7 +76,7 @@ public class QCRowView extends FrameLayout {
private LinearLayout mSeekBarContainer;
@Nullable
private QCSlider mQCSlider;
- private SeekBar mSeekBar;
+ private QCSeekBarView mSeekBar;
private QCActionListener mActionListener;
private boolean mInDirectManipulationMode;
@@ -84,7 +84,8 @@ public class QCRowView extends FrameLayout {
private final View.OnKeyListener mSeekBarKeyListener = new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (mSeekBar == null || !mSeekBar.isEnabled()) {
+ if (mSeekBar == null || (!mSeekBar.isEnabled()
+ && !mSeekBar.isClickableWhileDisabled())) {
return false;
}
// Consume nudge events in direct manipulation mode.
@@ -381,8 +382,9 @@ public class QCRowView extends FrameLayout {
mSeekBar.setMin(slider.getMin());
mSeekBar.setMax(slider.getMax());
mSeekBar.setProgress(slider.getValue());
- mSeekBar.setEnabled(slider.isEnabled() || slider.isClickableWhileDisabled());
- CarUiUtils.makeAllViewsEnabled(mSeekBar, slider.isEnabled());
+ mSeekBar.setEnabled(slider.isEnabled());
+ mSeekBar.setClickableWhileDisabled(slider.isClickableWhileDisabled());
+ mSeekBar.setDisabledClickListener(seekBar -> fireAction(slider, new Intent()));
if (!slider.isEnabled() && mInDirectManipulationMode) {
setInDirectManipulationMode(mSeekBarContainer, mSeekBar, false);
}
@@ -391,15 +393,6 @@ public class QCRowView extends FrameLayout {
}
mSeekbarChangeListener.setSlider(slider);
mSeekBar.setOnSeekBarChangeListener(mSeekbarChangeListener);
- mSeekBar.setOnTouchListener((v, event) -> {
- if (!slider.isEnabled()) {
- if (event.getActionMasked() == MotionEvent.ACTION_UP) {
- fireAction(slider, new Intent());
- }
- return true;
- }
- return false;
- });
// set up rotary support
mSeekBarContainer.setOnKeyListener(mSeekBarKeyListener);
mSeekBarContainer.setOnFocusChangeListener(mSeekBarFocusChangeListener);
diff --git a/car-qc-lib/src/com/android/car/qc/view/QCSeekBarView.java b/car-qc-lib/src/com/android/car/qc/view/QCSeekBarView.java
new file mode 100644
index 0000000..b13f784
--- /dev/null
+++ b/car-qc-lib/src/com/android/car/qc/view/QCSeekBarView.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2022 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.car.qc.view;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.SeekBar;
+
+import androidx.annotation.Nullable;
+
+import com.android.car.ui.uxr.DrawableStateSeekBar;
+
+import java.util.function.Consumer;
+
+/**
+ * A {@link SeekBar} specifically for Quick Controls that allows for a disabled click action
+ * to execute on {@link MotionEvent.ACTION_UP}.
+ */
+public class QCSeekBarView extends DrawableStateSeekBar {
+ private boolean mClickableWhileDisabled;
+ private Consumer<SeekBar> mDisabledClickListener;
+
+ public QCSeekBarView(Context context) {
+ super(context);
+ }
+
+ public QCSeekBarView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public QCSeekBarView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public QCSeekBarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ // AbsSeekBar will ignore all touch events if not enabled. If this SeekBar should be
+ // clickable while disabled, the touch event will be handled here.
+ if (!isEnabled() && mClickableWhileDisabled) {
+ if (event.getAction() == MotionEvent.ACTION_UP && mDisabledClickListener != null) {
+ mDisabledClickListener.accept(this);
+ }
+ return true;
+ }
+ return super.onTouchEvent(event);
+ }
+
+ public void setClickableWhileDisabled(boolean clickable) {
+ mClickableWhileDisabled = clickable;
+ }
+
+ public void setDisabledClickListener(@Nullable Consumer<SeekBar> disabledClickListener) {
+ mDisabledClickListener = disabledClickListener;
+ }
+
+ public boolean isClickableWhileDisabled() {
+ return mClickableWhileDisabled;
+ }
+}
diff --git a/car-qc-lib/tests/unit/src/com/android/car/qc/view/QCSeekBarViewTest.java b/car-qc-lib/tests/unit/src/com/android/car/qc/view/QCSeekBarViewTest.java
new file mode 100644
index 0000000..9adbd3b
--- /dev/null
+++ b/car-qc-lib/tests/unit/src/com/android/car/qc/view/QCSeekBarViewTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 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.car.qc.view;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.view.MotionEvent;
+import android.widget.SeekBar;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
+@RunWith(AndroidJUnit4.class)
+public class QCSeekBarViewTest {
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private final QCSeekBarView mView = new QCSeekBarView(mContext);
+
+ @Mock
+ private MotionEvent mMotionEvent;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_UP);
+ }
+
+ @Test
+ public void enabled_standardTouchEvent() {
+ assertThat(mView.onTouchEvent(mMotionEvent)).isTrue();
+ }
+
+ @Test
+ public void disabled_standardTouchEvent() {
+ mView.setEnabled(false);
+
+ assertThat(mView.onTouchEvent(mMotionEvent)).isFalse();
+ }
+
+ @Test
+ public void clickableWhileDisabled_customTouchEvent() {
+ mView.setEnabled(false);
+ mView.setClickableWhileDisabled(true);
+
+ assertThat(mView.onTouchEvent(mMotionEvent)).isTrue();
+ }
+
+ @Test
+ public void clickableWhileDisabled_actionDown_doesNotTriggerDisabledClickListener() {
+ AtomicBoolean called = new AtomicBoolean(false);
+ Consumer<SeekBar> disabledClickListener = seekBar -> called.set(true);
+ mView.setEnabled(false);
+ mView.setClickableWhileDisabled(true);
+ mView.setDisabledClickListener(disabledClickListener);
+ when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_DOWN);
+
+ assertThat(mView.onTouchEvent(mMotionEvent)).isTrue();
+ assertThat(called.get()).isFalse();
+ }
+
+ @Test
+ public void clickableWhileDisabled_actionUp_triggersDisabledClickListener() {
+ AtomicBoolean called = new AtomicBoolean(false);
+ Consumer<SeekBar> disabledClickListener = seekBar -> called.set(true);
+ mView.setEnabled(false);
+ mView.setClickableWhileDisabled(true);
+ mView.setDisabledClickListener(disabledClickListener);
+
+ assertThat(mView.onTouchEvent(mMotionEvent)).isTrue();
+ assertThat(called.get()).isTrue();
+ }
+}