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.settingslib.widget;
18 
19 import android.content.Context;
20 import android.util.AttributeSet;
21 import android.view.View;
22 import android.widget.Button;
23 import android.widget.TextView;
24 
25 import androidx.annotation.NonNull;
26 import androidx.annotation.Nullable;
27 import androidx.preference.Preference;
28 import androidx.preference.PreferenceViewHolder;
29 
30 import java.util.Arrays;
31 
32 /**
33  * This BarChartPreference shows up to four bar views in this preference at most.
34  *
35  * <p>The following code sample shows a typical use, with an XML layout and code to initialize the
36  * contents of the BarChartPreference:
37  *
38  * <pre>
39  * &lt;com.android.settingslib.widget.BarChartPreference
40  *        android:key="bar_chart"/&gt;
41  * </pre>
42  *
43  * <p>This code sample demonstrates how to initialize the contents of the BarChartPreference
44  * defined in the previous XML layout:
45  *
46  * <pre>
47  * BarChartPreference preference = ((BarChartPreference) findPreference("bar_chart"));
48  *
49  * BarChartInfo info = new BarChartInfo.Builder()
50  *     .setTitle(R.string.permission_bar_chart_title)
51  *     .setDetails(R.string.permission_bar_chart_details)
52  *     .setEmptyText(R.string.permission_bar_chart_empty_text)
53  *     .addBarViewInfo(new barViewInfo(...))
54  *     .addBarViewInfo(new barViewInfo(...))
55  *     .addBarViewInfo(new barViewInfo(...))
56  *     .addBarViewInfo(new barViewInfo(...))
57  *     .setDetailsOnClickListener(v -> doSomething())
58  *     .build();
59  *
60  * preference.initializeBarChart(info);
61  * </pre>
62  *
63  *
64  * <p>You also can update new information for bar views by
65  * {@link BarChartPreference#setBarViewInfos(BarViewInfo[])}
66  *
67  * <pre>
68  * BarViewInfo[] barViewsInfo = new BarViewInfo [] {
69  *     new BarViewInfo(...),
70  *     new BarViewInfo(...),
71  *     new BarViewInfo(...),
72  *     new BarViewInfo(...),
73  * };
74  *
75  * preference.setBarViewInfos(barViewsInfo);
76  * </pre>
77  */
78 public class BarChartPreference extends Preference {
79 
80     public static final int MAXIMUM_BAR_VIEWS = 4;
81     private static final String TAG = "BarChartPreference";
82     private static final int[] BAR_VIEWS = {
83             R.id.bar_view1,
84             R.id.bar_view2,
85             R.id.bar_view3,
86             R.id.bar_view4
87     };
88 
89     private int mMaxBarHeight;
90     private boolean mIsLoading;
91     private BarChartInfo mBarChartInfo;
92 
BarChartPreference(Context context)93     public BarChartPreference(Context context) {
94         super(context);
95         init();
96     }
97 
BarChartPreference(Context context, AttributeSet attrs)98     public BarChartPreference(Context context, AttributeSet attrs) {
99         super(context, attrs);
100         init();
101     }
102 
BarChartPreference(Context context, AttributeSet attrs, int defStyleAttr)103     public BarChartPreference(Context context, AttributeSet attrs, int defStyleAttr) {
104         super(context, attrs, defStyleAttr);
105         init();
106     }
107 
BarChartPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)108     public BarChartPreference(Context context, AttributeSet attrs, int defStyleAttr,
109             int defStyleRes) {
110         super(context, attrs, defStyleAttr, defStyleRes);
111         init();
112     }
113 
114     /**
115      * According to the information in {@link BarChartInfo} to initialize bar chart.
116      *
117      * @param barChartInfo The barChartInfo contains title, details, empty text, click listener
118      *                     attached on details view and four bar views.
119      */
initializeBarChart(@onNull BarChartInfo barChartInfo)120     public void initializeBarChart(@NonNull BarChartInfo barChartInfo) {
121         mBarChartInfo = barChartInfo;
122         notifyChanged();
123     }
124 
125     /**
126      * Sets all bar view information which you'd like to show in preference.
127      *
128      * @param barViewInfos the barViewInfos contain at least one {@link BarViewInfo}.
129      */
setBarViewInfos(@ullable BarViewInfo[] barViewInfos)130     public void setBarViewInfos(@Nullable BarViewInfo[] barViewInfos) {
131         if (barViewInfos != null && barViewInfos.length > MAXIMUM_BAR_VIEWS) {
132             throw new IllegalStateException("We only support up to four bar views");
133         }
134         mBarChartInfo.setBarViewInfos(barViewInfos);
135         notifyChanged();
136     }
137 
138     /**
139      * Set loading state for {@link BarChartPreference}.
140      *
141      * By default, {@link BarChartPreference} doesn't care about it.
142      *
143      * But if user sets loading state to true explicitly, it means {@link BarChartPreference}
144      * needs to take some time to load data. So we won't initialize any view now.
145      *
146      * Once the state is updated to false, we will start to initialize view again.
147      *
148      * @param isLoading whether or not {@link BarChartPreference} is in loading state.
149      */
updateLoadingState(boolean isLoading)150     public void updateLoadingState(boolean isLoading) {
151         mIsLoading = isLoading;
152         notifyChanged();
153     }
154 
155     @Override
onBindViewHolder(PreferenceViewHolder holder)156     public void onBindViewHolder(PreferenceViewHolder holder) {
157         super.onBindViewHolder(holder);
158         holder.setDividerAllowedAbove(true);
159         holder.setDividerAllowedBelow(true);
160 
161         // We bind title and details early so that we can preserve the correct height for chart
162         // view.
163         bindChartTitleView(holder);
164         bindChartDetailsView(holder);
165 
166         // If the state is loading, we just show a blank view.
167         if (mIsLoading) {
168             holder.itemView.setVisibility(View.INVISIBLE);
169             return;
170         }
171         holder.itemView.setVisibility(View.VISIBLE);
172 
173         final BarViewInfo[] barViewInfos = mBarChartInfo.getBarViewInfos();
174         // If there is no any bar view, we just show an empty text.
175         if (barViewInfos == null || barViewInfos.length == 0) {
176             setEmptyViewVisible(holder, true /* visible */);
177             return;
178         }
179         setEmptyViewVisible(holder, false /* visible */);
180         updateBarChart(holder);
181     }
182 
init()183     private void init() {
184         setSelectable(false);
185         setLayoutResource(R.layout.settings_bar_chart);
186         mMaxBarHeight = getContext().getResources().getDimensionPixelSize(
187                 R.dimen.settings_bar_view_max_height);
188     }
189 
bindChartTitleView(PreferenceViewHolder holder)190     private void bindChartTitleView(PreferenceViewHolder holder) {
191         final TextView titleView = (TextView) holder.findViewById(R.id.bar_chart_title);
192         titleView.setText(mBarChartInfo.getTitle());
193     }
194 
bindChartDetailsView(PreferenceViewHolder holder)195     private void bindChartDetailsView(PreferenceViewHolder holder) {
196         final Button detailsView = (Button) holder.findViewById(R.id.bar_chart_details);
197         final int details = mBarChartInfo.getDetails();
198         if (details == 0) {
199             detailsView.setVisibility(View.GONE);
200         } else {
201             detailsView.setVisibility(View.VISIBLE);
202             detailsView.setText(details);
203             detailsView.setOnClickListener(mBarChartInfo.getDetailsOnClickListener());
204         }
205     }
206 
updateBarChart(PreferenceViewHolder holder)207     private void updateBarChart(PreferenceViewHolder holder) {
208         normalizeBarViewHeights();
209 
210         final BarViewInfo[] barViewInfos = mBarChartInfo.getBarViewInfos();
211 
212         for (int index = 0; index < MAXIMUM_BAR_VIEWS; index++) {
213             final BarView barView = (BarView) holder.findViewById(BAR_VIEWS[index]);
214 
215             // If there is no bar view info can be shown.
216             if (barViewInfos == null || index >= barViewInfos.length) {
217                 barView.setVisibility(View.GONE);
218                 continue;
219             }
220             barView.setVisibility(View.VISIBLE);
221             barView.updateView(barViewInfos[index]);
222         }
223     }
224 
normalizeBarViewHeights()225     private void normalizeBarViewHeights() {
226         final BarViewInfo[] barViewInfos = mBarChartInfo.getBarViewInfos();
227         // If there is no any bar view info, we don't need to calculate the height of all bar views.
228         if (barViewInfos == null || barViewInfos.length == 0) {
229             return;
230         }
231         // Do a sort in descending order, the first element would have max {@link
232         // BarViewInfo#mHeight}
233         Arrays.sort(barViewInfos);
234         // Since we sorted this array in advance, the first element must have the max {@link
235         // BarViewInfo#mHeight}.
236         final int maxBarHeight = barViewInfos[0].getHeight();
237         // If the max number of bar view is zero, then we don't calculate the unit for bar height.
238         final int unit = maxBarHeight == 0 ? 0 : mMaxBarHeight / maxBarHeight;
239 
240         for (BarViewInfo barView : barViewInfos) {
241             barView.setNormalizedHeight(barView.getHeight() * unit);
242         }
243     }
244 
setEmptyViewVisible(PreferenceViewHolder holder, boolean visible)245     private void setEmptyViewVisible(PreferenceViewHolder holder, boolean visible) {
246         final View barViewsContainer = holder.findViewById(R.id.bar_views_container);
247         final TextView emptyView = (TextView) holder.findViewById(R.id.empty_view);
248         final int emptyTextRes = mBarChartInfo.getEmptyText();
249 
250         if (emptyTextRes != 0) {
251             emptyView.setText(emptyTextRes);
252         }
253         emptyView.setVisibility(visible ? View.VISIBLE : View.GONE);
254         barViewsContainer.setVisibility(visible ? View.GONE : View.VISIBLE);
255     }
256 }
257