1 /*
2  * Copyright (C) 2021 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.settingslib.widget;
18 
19 import android.content.Context;
20 import android.text.SpannableString;
21 import android.text.Spanned;
22 import android.text.TextUtils;
23 import android.text.style.AbsoluteSizeSpan;
24 import android.util.AttributeSet;
25 import android.view.View;
26 import android.widget.FrameLayout;
27 import android.widget.ImageView;
28 import android.widget.ProgressBar;
29 import android.widget.TextView;
30 
31 import androidx.preference.Preference;
32 import androidx.preference.PreferenceViewHolder;
33 
34 import java.util.regex.Matcher;
35 import java.util.regex.Pattern;
36 
37 /**
38  * Progres bar preference with a usage summary and a total summary.
39  * This preference shows number in usage summary with enlarged font size.
40  */
41 public class UsageProgressBarPreference extends Preference {
42 
43     private final Pattern mNumberPattern = Pattern.compile("[\\d]*[\\٫.,]?[\\d]+");
44 
45     private CharSequence mUsageSummary;
46     private CharSequence mTotalSummary;
47     private CharSequence mBottomSummary;
48     private ImageView mCustomImageView;
49     private int mPercent = -1;
50 
51     /**
52      * Perform inflation from XML and apply a class-specific base style.
53      *
54      * @param context  The {@link Context} this is associated with, through which it can
55      *                 access the current theme, resources, {@link SharedPreferences}, etc.
56      * @param attrs    The attributes of the XML tag that is inflating the preference
57      * @param defStyle An attribute in the current theme that contains a reference to a style
58      *                 resource that supplies default values for the view. Can be 0 to not
59      *                 look for defaults.
60      */
UsageProgressBarPreference(Context context, AttributeSet attrs, int defStyle)61     public UsageProgressBarPreference(Context context, AttributeSet attrs, int defStyle) {
62         super(context, attrs, defStyle);
63         setLayoutResource(R.layout.preference_usage_progress_bar);
64     }
65 
66     /**
67      * Perform inflation from XML and apply a class-specific base style.
68      *
69      * @param context The {@link Context} this is associated with, through which it can
70      *                access the current theme, resources, {@link SharedPreferences}, etc.
71      * @param attrs   The attributes of the XML tag that is inflating the preference
72      */
UsageProgressBarPreference(Context context, AttributeSet attrs)73     public UsageProgressBarPreference(Context context, AttributeSet attrs) {
74         super(context, attrs);
75         setLayoutResource(R.layout.preference_usage_progress_bar);
76     }
77 
78     /**
79      * Constructor to create a preference.
80      *
81      * @param context The Context this is associated with.
82      */
UsageProgressBarPreference(Context context)83     public UsageProgressBarPreference(Context context) {
84         this(context, null);
85     }
86 
87     /** Set usage summary, number in the summary will show with enlarged font size. */
setUsageSummary(CharSequence usageSummary)88     public void setUsageSummary(CharSequence usageSummary) {
89         if (TextUtils.equals(mUsageSummary, usageSummary)) {
90             return;
91         }
92         mUsageSummary = usageSummary;
93         notifyChanged();
94     }
95 
96     /** Set total summary. */
setTotalSummary(CharSequence totalSummary)97     public void setTotalSummary(CharSequence totalSummary) {
98         if (TextUtils.equals(mTotalSummary, totalSummary)) {
99             return;
100         }
101         mTotalSummary = totalSummary;
102         notifyChanged();
103     }
104 
105     /** Set bottom summary. */
setBottomSummary(CharSequence bottomSummary)106     public void setBottomSummary(CharSequence bottomSummary) {
107         if (TextUtils.equals(mBottomSummary, bottomSummary)) {
108             return;
109         }
110         mBottomSummary = bottomSummary;
111         notifyChanged();
112     }
113 
114     /** Set percentage of the progress bar. */
setPercent(long usage, long total)115     public void setPercent(long usage, long total) {
116         if (usage >  total) {
117             return;
118         }
119         if (total == 0L) {
120             if (mPercent != 0) {
121                 mPercent = 0;
122                 notifyChanged();
123             }
124             return;
125         }
126         final int percent = (int) (usage / (double) total * 100);
127         if (mPercent == percent) {
128             return;
129         }
130         mPercent = percent;
131         notifyChanged();
132     }
133 
134     /** Set custom ImageView to the right side of total summary. */
setCustomContent(T imageView)135     public <T extends ImageView> void setCustomContent(T imageView) {
136         if (imageView == mCustomImageView) {
137             return;
138         }
139         mCustomImageView = imageView;
140         notifyChanged();
141     }
142 
143     /**
144      * Binds the created View to the data for this preference.
145      *
146      * <p>This is a good place to grab references to custom Views in the layout and set
147      * properties on them.
148      *
149      * <p>Make sure to call through to the superclass's implementation.
150      *
151      * @param holder The ViewHolder that provides references to the views to fill in. These views
152      *               will be recycled, so you should not hold a reference to them after this method
153      *               returns.
154      */
155     @Override
onBindViewHolder(PreferenceViewHolder holder)156     public void onBindViewHolder(PreferenceViewHolder holder) {
157         super.onBindViewHolder(holder);
158 
159         holder.setDividerAllowedAbove(false);
160         holder.setDividerAllowedBelow(false);
161 
162         final TextView usageSummary = (TextView) holder.findViewById(R.id.usage_summary);
163         usageSummary.setText(enlargeFontOfNumber(mUsageSummary));
164 
165         final TextView totalSummary = (TextView) holder.findViewById(R.id.total_summary);
166         if (mTotalSummary != null) {
167             totalSummary.setText(mTotalSummary);
168         }
169 
170         final TextView bottomSummary = (TextView) holder.findViewById(R.id.bottom_summary);
171         if (TextUtils.isEmpty(mBottomSummary)) {
172             bottomSummary.setVisibility(View.GONE);
173         } else {
174             bottomSummary.setVisibility(View.VISIBLE);
175             bottomSummary.setText(mBottomSummary);
176         }
177 
178         final ProgressBar progressBar = (ProgressBar) holder.findViewById(android.R.id.progress);
179         if (mPercent < 0) {
180             progressBar.setIndeterminate(true);
181         } else {
182             progressBar.setIndeterminate(false);
183             progressBar.setProgress(mPercent);
184         }
185 
186         final FrameLayout customLayout = (FrameLayout) holder.findViewById(R.id.custom_content);
187         if (mCustomImageView == null) {
188             customLayout.removeAllViews();
189             customLayout.setVisibility(View.GONE);
190         } else {
191             customLayout.removeAllViews();
192             customLayout.addView(mCustomImageView);
193             customLayout.setVisibility(View.VISIBLE);
194         }
195     }
196 
enlargeFontOfNumber(CharSequence summary)197     private CharSequence enlargeFontOfNumber(CharSequence summary) {
198         if (TextUtils.isEmpty(summary)) {
199             return "";
200         }
201 
202         final Matcher matcher = mNumberPattern.matcher(summary);
203         if (matcher.find()) {
204             final SpannableString spannableSummary =  new SpannableString(summary);
205             spannableSummary.setSpan(new AbsoluteSizeSpan(64, true /* dip */), matcher.start(),
206                     matcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
207             return spannableSummary;
208         }
209         return summary;
210     }
211 }
212