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 * <com.android.settingslib.widget.BarChartPreference 40 * android:key="bar_chart"/> 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