1 /*
2  * Copyright (C) 2019 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.settings.widget;
18 
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.graphics.drawable.Drawable;
22 import android.util.AttributeSet;
23 import android.view.View;
24 import android.view.ViewGroup;
25 import android.widget.ImageView;
26 import android.widget.SeekBar;
27 import android.widget.TextView;
28 
29 import androidx.core.content.res.TypedArrayUtils;
30 import androidx.preference.PreferenceViewHolder;
31 
32 import com.android.internal.util.Preconditions;
33 import com.android.settings.R;
34 import com.android.settings.Utils;
35 
36 /**
37  * A labeled {@link SeekBarPreference} with left and right text label, icon label, or both.
38  *
39  * <p>
40  * The component provides the attribute usage below.
41  * <attr name="textStart" format="reference" />
42  * <attr name="textEnd" format="reference" />
43  * <attr name="tickMark" format="reference" />
44  * <attr name="iconStart" format="reference" />
45  * <attr name="iconEnd" format="reference" />
46  * <attr name="iconStartContentDescription" format="reference" />
47  * <attr name="iconEndContentDescription" format="reference" />
48  * </p>
49  *
50  * <p> If you set the attribute values {@code iconStartContentDescription} or {@code
51  * iconEndContentDescription} from XML, you must also set the corresponding attributes {@code
52  * iconStart} or {@code iconEnd}, otherwise throws an {@link IllegalArgumentException}.</p>
53  */
54 public class LabeledSeekBarPreference extends SeekBarPreference {
55 
56     private final int mTextStartId;
57     private final int mTextEndId;
58     private final int mTickMarkId;
59     private final int mIconStartId;
60     private final int mIconEndId;
61     private final int mIconStartContentDescriptionId;
62     private final int mIconEndContentDescriptionId;
63     private OnPreferenceChangeListener mStopListener;
64     private SeekBar.OnSeekBarChangeListener mSeekBarChangeListener;
65 
66     private SeekBar mSeekBar;
67 
LabeledSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)68     public LabeledSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr,
69             int defStyleRes) {
70 
71         super(context, attrs, defStyleAttr, defStyleRes);
72         setLayoutResource(R.layout.preference_labeled_slider);
73 
74         final TypedArray styledAttrs = context.obtainStyledAttributes(attrs,
75                 R.styleable.LabeledSeekBarPreference);
76         mTextStartId = styledAttrs.getResourceId(
77                 R.styleable.LabeledSeekBarPreference_textStart, /* defValue= */ 0);
78         mTextEndId = styledAttrs.getResourceId(
79                 R.styleable.LabeledSeekBarPreference_textEnd, /* defValue= */ 0);
80         mTickMarkId = styledAttrs.getResourceId(
81                 R.styleable.LabeledSeekBarPreference_tickMark, /* defValue= */ 0);
82         mIconStartId = styledAttrs.getResourceId(
83                 R.styleable.LabeledSeekBarPreference_iconStart, /* defValue= */ 0);
84         mIconEndId = styledAttrs.getResourceId(
85                 R.styleable.LabeledSeekBarPreference_iconEnd, /* defValue= */ 0);
86 
87         mIconStartContentDescriptionId = styledAttrs.getResourceId(
88                 R.styleable.LabeledSeekBarPreference_iconStartContentDescription,
89                 /* defValue= */ 0);
90         Preconditions.checkArgument(!(mIconStartContentDescriptionId != 0 && mIconStartId == 0),
91                 "The resource of the iconStart attribute may be invalid or not set, "
92                         + "you should set the iconStart attribute and have the valid resource.");
93 
94         mIconEndContentDescriptionId = styledAttrs.getResourceId(
95                 R.styleable.LabeledSeekBarPreference_iconEndContentDescription,
96                 /* defValue= */ 0);
97         Preconditions.checkArgument(!(mIconEndContentDescriptionId != 0 && mIconEndId == 0),
98                 "The resource of the iconEnd attribute may be invalid or not set, "
99                         + "you should set the iconEnd attribute and have the valid resource.");
100         styledAttrs.recycle();
101     }
102 
LabeledSeekBarPreference(Context context, AttributeSet attrs)103     public LabeledSeekBarPreference(Context context, AttributeSet attrs) {
104         this(context, attrs, TypedArrayUtils.getAttr(context,
105                 androidx.preference.R.attr.seekBarPreferenceStyle,
106                 com.android.internal.R.attr.seekBarPreferenceStyle), 0);
107     }
108 
getSeekbar()109     public SeekBar getSeekbar() {
110         return mSeekBar;
111     }
112 
113     @Override
onBindViewHolder(PreferenceViewHolder holder)114     public void onBindViewHolder(PreferenceViewHolder holder) {
115         super.onBindViewHolder(holder);
116 
117         final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary);
118         boolean isSummaryVisible = false;
119         if (summaryView != null) {
120             isSummaryVisible = (summaryView.getVisibility() == View.VISIBLE);
121         }
122         final TextView titleView = (TextView) holder.findViewById(android.R.id.title);
123         if (titleView != null && !isSelectable() && isEnabled() && isSummaryVisible) {
124             titleView.setTextColor(
125                     Utils.getColorAttr(getContext(), android.R.attr.textColorPrimary));
126         }
127 
128         final TextView startText = (TextView) holder.findViewById(android.R.id.text1);
129         if (mTextStartId > 0) {
130             startText.setText(mTextStartId);
131         }
132 
133         final TextView endText = (TextView) holder.findViewById(android.R.id.text2);
134         if (mTextEndId > 0) {
135             endText.setText(mTextEndId);
136         }
137 
138         final View labelFrame = holder.findViewById(R.id.label_frame);
139         final boolean isValidTextResIdExist = mTextStartId > 0 || mTextEndId > 0;
140         labelFrame.setVisibility(isValidTextResIdExist ? View.VISIBLE : View.GONE);
141 
142         mSeekBar = (SeekBar) holder.findViewById(com.android.internal.R.id.seekbar);
143         if (mTickMarkId != 0) {
144             final Drawable tickMark = getContext().getDrawable(mTickMarkId);
145             mSeekBar.setTickMark(tickMark);
146         }
147 
148         final ViewGroup iconStartFrame = (ViewGroup) holder.findViewById(R.id.icon_start_frame);
149         final ImageView iconStartView = (ImageView) holder.findViewById(R.id.icon_start);
150         updateIconStartIfNeeded(iconStartFrame, iconStartView, mSeekBar);
151 
152         final ViewGroup iconEndFrame = (ViewGroup) holder.findViewById(R.id.icon_end_frame);
153         final ImageView iconEndView = (ImageView) holder.findViewById(R.id.icon_end);
154         updateIconEndIfNeeded(iconEndFrame, iconEndView, mSeekBar);
155     }
156 
setOnPreferenceChangeStopListener(OnPreferenceChangeListener listener)157     public void setOnPreferenceChangeStopListener(OnPreferenceChangeListener listener) {
158         mStopListener = listener;
159     }
160 
161     @Override
onStartTrackingTouch(SeekBar seekBar)162     public void onStartTrackingTouch(SeekBar seekBar) {
163         super.onStartTrackingTouch(seekBar);
164 
165         if (mSeekBarChangeListener != null) {
166             mSeekBarChangeListener.onStartTrackingTouch(seekBar);
167         }
168     }
169 
170     @Override
onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)171     public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
172         super.onProgressChanged(seekBar, progress, fromUser);
173 
174         if (mSeekBarChangeListener != null) {
175             mSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser);
176         }
177     }
178 
179     @Override
onStopTrackingTouch(SeekBar seekBar)180     public void onStopTrackingTouch(SeekBar seekBar) {
181         super.onStopTrackingTouch(seekBar);
182 
183         if (mSeekBarChangeListener != null) {
184             mSeekBarChangeListener.onStopTrackingTouch(seekBar);
185         }
186 
187         if (mStopListener != null) {
188             mStopListener.onPreferenceChange(this, seekBar.getProgress());
189         }
190 
191         // Need to update the icon enabled status
192         notifyChanged();
193     }
194 
setOnSeekBarChangeListener(SeekBar.OnSeekBarChangeListener seekBarChangeListener)195     public void setOnSeekBarChangeListener(SeekBar.OnSeekBarChangeListener seekBarChangeListener) {
196         mSeekBarChangeListener = seekBarChangeListener;
197     }
198 
updateIconStartIfNeeded(ViewGroup iconFrame, ImageView iconStart, SeekBar seekBar)199     private void updateIconStartIfNeeded(ViewGroup iconFrame, ImageView iconStart,
200             SeekBar seekBar) {
201         if (mIconStartId == 0) {
202             return;
203         }
204 
205         if (iconStart.getDrawable() == null) {
206             iconStart.setImageResource(mIconStartId);
207         }
208 
209         if (mIconStartContentDescriptionId != 0) {
210             final String contentDescription =
211                     iconFrame.getContext().getString(mIconStartContentDescriptionId);
212             iconFrame.setContentDescription(contentDescription);
213         }
214 
215         iconFrame.setOnClickListener((view) -> {
216             final int progress = getProgress();
217             if (progress > 0) {
218                 setProgress(progress - 1);
219             }
220         });
221 
222         iconFrame.setVisibility(View.VISIBLE);
223         setIconViewAndFrameEnabled(iconStart, seekBar.getProgress() > 0);
224     }
225 
updateIconEndIfNeeded(ViewGroup iconFrame, ImageView iconEnd, SeekBar seekBar)226     private void updateIconEndIfNeeded(ViewGroup iconFrame, ImageView iconEnd, SeekBar seekBar) {
227         if (mIconEndId == 0) {
228             return;
229         }
230 
231         if (iconEnd.getDrawable() == null) {
232             iconEnd.setImageResource(mIconEndId);
233         }
234 
235         if (mIconEndContentDescriptionId != 0) {
236             final String contentDescription =
237                     iconFrame.getContext().getString(mIconEndContentDescriptionId);
238             iconFrame.setContentDescription(contentDescription);
239         }
240 
241         iconFrame.setOnClickListener((view) -> {
242             final int progress = getProgress();
243             if (progress < getMax()) {
244                 setProgress(progress + 1);
245             }
246         });
247 
248         iconFrame.setVisibility(View.VISIBLE);
249         setIconViewAndFrameEnabled(iconEnd, seekBar.getProgress() < seekBar.getMax());
250     }
251 
setIconViewAndFrameEnabled(View iconView, boolean enabled)252     private static void setIconViewAndFrameEnabled(View iconView, boolean enabled) {
253         iconView.setEnabled(enabled);
254         final ViewGroup iconFrame = (ViewGroup) iconView.getParent();
255         iconFrame.setEnabled(enabled);
256     }
257 }
258 
259