• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.tv.media.settings;
18 
19 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_PROGRESS;
20 
21 import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_BACKWARD;
22 import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_FORWARD;
23 
24 import android.content.Context;
25 import android.graphics.Outline;
26 import android.os.Bundle;
27 import android.util.AttributeSet;
28 import android.view.View;
29 import android.view.ViewOutlineProvider;
30 import android.view.accessibility.AccessibilityNodeInfo;
31 import android.widget.SeekBar;
32 
33 import androidx.annotation.NonNull;
34 import androidx.annotation.Nullable;
35 import androidx.preference.PreferenceViewHolder;
36 
37 import com.android.systemui.tv.res.R;
38 import com.android.tv.twopanelsettings.slices.SliceSeekbarPreference;
39 import com.android.tv.twopanelsettings.slices.compat.core.SliceActionImpl;
40 
41 /**
42  * Slice preference for one panel settings a small icon, title, seekbar and the current seekbar
43  * value. Large non-themed icons/images are not supported.
44  */
45 public class SeekbarSlicePreference extends SliceSeekbarPreference implements TooltipPreference {
46     private ControlWidget.TooltipConfig mTooltipConfig = new ControlWidget.TooltipConfig();
47     private SeekBar mSeekbar;
48 
49     private SeekBar.OnSeekBarChangeListener mChangeListener;
50 
SeekbarSlicePreference(Context context, SliceActionImpl action, int min, int max, int value)51     public SeekbarSlicePreference(Context context, SliceActionImpl action, int min, int max,
52             int value) {
53         this(context, null, action, min, max, value);
54     }
55 
SeekbarSlicePreference(Context context, AttributeSet attrs, SliceActionImpl action, int min, int max, int value)56     public SeekbarSlicePreference(Context context, AttributeSet attrs, SliceActionImpl action,
57             int min, int max, int value) {
58         super(context, attrs, action, min, max, value);
59         setLayoutResource(R.layout.seekbar_slice_pref);
60         setShowSeekBarValue(true);
61     }
62 
63     @Override
onBindViewHolder(@onNull PreferenceViewHolder holder)64     public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
65         super.onBindViewHolder(holder);
66 
67         SeekbarControlWidget seekbarControlWidget = (SeekbarControlWidget) holder.itemView;
68         seekbarControlWidget.setEnabled(this.isEnabled());
69         seekbarControlWidget.setTooltipConfig(mTooltipConfig);
70 
71         mSeekbar = holder.itemView.requireViewById(R.id.seekbar);
72 
73         // Set outline of the seekbar to clip the thumb when getting closer to 0.
74         mSeekbar.setOutlineProvider(
75                 new ViewOutlineProvider() {
76                     @Override
77                     public void getOutline(View view, Outline outline) {
78                         outline.setRoundRect(
79                                 /* left= */ 0,
80                                 /* top= */ 0,
81                                 view.getWidth(),
82                                 view.getHeight(),
83                                 getContext().getResources().getDimensionPixelOffset(
84                                         R.dimen.seekbar_widget_track_corner_radius));
85                     }
86                 });
87         mSeekbar.setClipToOutline(true);
88 
89         holder.itemView.setAccessibilityDelegate(
90                 new View.AccessibilityDelegate() {
91                     @Override
92                     public void onInitializeAccessibilityNodeInfo(@NonNull View host,
93                             @NonNull AccessibilityNodeInfo info) {
94                         super.onInitializeAccessibilityNodeInfo(host, info);
95                         info.addAction(ACTION_SET_PROGRESS);
96                         info.setRangeInfo(getCurrentRange());
97                     }
98 
99                     @Override
100                     public boolean performAccessibilityAction(@NonNull View host, int action,
101                             Bundle args) {
102                         if (action == ACTION_SCROLL_FORWARD.getId()) {
103                             return increaseValue();
104                         }
105                         if (action == ACTION_SCROLL_BACKWARD.getId()) {
106                             return decreaseValue();
107                         }
108                         return super.performAccessibilityAction(host, action, args);
109                     }
110                 });
111 
112         mSeekbar.setOnSeekBarChangeListener(mChangeListener);
113     }
114 
setOnSeekbarChangedListener(SeekBar.OnSeekBarChangeListener listener)115     public void setOnSeekbarChangedListener(SeekBar.OnSeekBarChangeListener listener) {
116         mChangeListener = listener;
117         if (mSeekbar != null) {
118             mSeekbar.setOnSeekBarChangeListener(listener);
119         }
120     }
121 
getCurrentRange()122     public AccessibilityNodeInfo.RangeInfo getCurrentRange() {
123         return new AccessibilityNodeInfo.RangeInfo(
124                 AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_INT, getMin(), getMax(), getValue());
125     }
126 
increaseValue()127     private boolean increaseValue() {
128         int newProgress = getValue() + getSeekBarIncrement();
129 
130         if (newProgress <= getMax()) {
131             setValue(newProgress);
132             callChangeListener(newProgress);
133             return true;
134         }
135         return false;
136     }
137 
decreaseValue()138     private boolean decreaseValue() {
139         int newProgress = getValue() - getSeekBarIncrement();
140         if (newProgress >= getMin()) {
141             setValue(newProgress);
142             callChangeListener(newProgress);
143             return true;
144         }
145         return false;
146     }
147 
148     /** Set tool tip related attributes. */
149     @Override
setTooltipConfig(ControlWidget.TooltipConfig tooltipConfig)150     public void setTooltipConfig(ControlWidget.TooltipConfig tooltipConfig) {
151         if (!this.mTooltipConfig.equals(tooltipConfig)) {
152             this.mTooltipConfig = tooltipConfig;
153             notifyChanged();
154         }
155     }
156 
157     static class SeekbarControlWidget extends ControlWidget {
SeekbarControlWidget(Context context)158         public SeekbarControlWidget(Context context) {
159             this(context, /* attrs= */ null);
160         }
161 
SeekbarControlWidget(Context context, @Nullable AttributeSet attrs)162         public SeekbarControlWidget(Context context, @Nullable AttributeSet attrs) {
163             this(context, attrs, /* defStyleAttr= */ 0);
164         }
165 
166         @SuppressWarnings("nullness")
SeekbarControlWidget(Context context, @Nullable AttributeSet attrs, int defStyleAttr)167         public SeekbarControlWidget(Context context, @Nullable AttributeSet attrs,
168                 int defStyleAttr) {
169             super(context, attrs, defStyleAttr);
170             View.inflate(context, R.layout.seekbar_control_widget, /* root= */ this);
171         }
172     }
173 }
174