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