• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 
15 package com.android.settings.datausage;
16 
17 import android.content.Context;
18 import android.content.res.Resources;
19 import android.net.NetworkPolicy;
20 import android.text.SpannableStringBuilder;
21 import android.text.TextUtils;
22 import android.text.format.DateUtils;
23 import android.text.style.ForegroundColorSpan;
24 import android.util.AttributeSet;
25 import android.util.DataUnit;
26 import android.util.SparseIntArray;
27 
28 import androidx.annotation.NonNull;
29 import androidx.annotation.Nullable;
30 import androidx.annotation.VisibleForTesting;
31 import androidx.preference.Preference;
32 import androidx.preference.PreferenceViewHolder;
33 
34 import com.android.settings.R;
35 import com.android.settings.Utils;
36 import com.android.settings.datausage.lib.DataUsageFormatter;
37 import com.android.settings.datausage.lib.NetworkCycleChartData;
38 import com.android.settings.datausage.lib.NetworkUsageData;
39 import com.android.settings.widget.UsageView;
40 import com.android.settingslib.spaprivileged.framework.common.BytesFormatter;
41 
42 import java.util.ArrayList;
43 import java.util.Comparator;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.stream.Collectors;
47 
48 public class ChartDataUsagePreference extends Preference {
49 
50     // The resolution we show on the graph so that we can squash things down to ints.
51     // Set to half a meg for now.
52     private static final long RESOLUTION = DataUnit.MEBIBYTES.toBytes(1) / 2;
53 
54     private final int mWarningColor;
55     private final int mLimitColor;
56 
57     private final Resources mResources;
58     @Nullable private NetworkPolicy mPolicy;
59     private long mStart;
60     private long mEnd;
61     private NetworkCycleChartData mNetworkCycleChartData;
62 
ChartDataUsagePreference(Context context, AttributeSet attrs)63     public ChartDataUsagePreference(Context context, AttributeSet attrs) {
64         super(context, attrs);
65         mResources = context.getResources();
66         setSelectable(false);
67         mLimitColor = Utils.getColorAttrDefaultColor(context, android.R.attr.colorError);
68         mWarningColor = Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondary);
69         setLayoutResource(R.layout.data_usage_graph);
70     }
71 
72     @Override
onBindViewHolder(@onNull PreferenceViewHolder holder)73     public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
74         super.onBindViewHolder(holder);
75         final UsageView chart = holder.itemView.requireViewById(R.id.data_usage);
76         final int top = getTop();
77         chart.clearPaths();
78         chart.configureGraph(toInt(mEnd - mStart), top);
79         if (mNetworkCycleChartData != null) {
80             calcPoints(chart, mNetworkCycleChartData.getDailyUsage());
81             setupContentDescription(chart, mNetworkCycleChartData.getDailyUsage());
82         }
83         chart.setBottomLabels(new CharSequence[] {
84                 Utils.formatDateRange(getContext(), mStart, mStart),
85                 Utils.formatDateRange(getContext(), mEnd, mEnd),
86         });
87 
88         bindNetworkPolicy(chart, mPolicy, top);
89     }
90 
getTop()91     public int getTop() {
92         final long totalData =
93                 mNetworkCycleChartData != null ? mNetworkCycleChartData.getTotal().getUsage() : 0;
94         final long policyMax =
95             mPolicy != null ? Math.max(mPolicy.limitBytes, mPolicy.warningBytes) : 0;
96         return (int) (Math.max(totalData, policyMax) / RESOLUTION);
97     }
98 
99     @VisibleForTesting
calcPoints(UsageView chart, @NonNull List<NetworkUsageData> usageSummary)100     void calcPoints(UsageView chart, @NonNull List<NetworkUsageData> usageSummary) {
101         final SparseIntArray points = new SparseIntArray();
102         points.put(0, 0);
103 
104         final long now = System.currentTimeMillis();
105         long totalData = 0;
106         for (NetworkUsageData data : usageSummary) {
107             final long startTime = data.getStartTime();
108             if (startTime > now) {
109                 break;
110             }
111             final long endTime = data.getEndTime();
112 
113             // increment by current bucket total
114             totalData += data.getUsage();
115 
116             points.put(toInt(startTime - mStart + 1), (int) (totalData / RESOLUTION));
117             points.put(toInt(endTime - mStart), (int) (totalData / RESOLUTION));
118         }
119         if (points.size() > 1) {
120             chart.addPath(points);
121         }
122     }
123 
setupContentDescription( UsageView chart, @NonNull List<NetworkUsageData> usageSummary)124     private void setupContentDescription(
125             UsageView chart, @NonNull List<NetworkUsageData> usageSummary) {
126         final Context context = getContext();
127         final StringBuilder contentDescription = new StringBuilder();
128         final int flags = DateUtils.FORMAT_SHOW_DATE;
129 
130         // Setup a brief content description.
131         final String startDate = DateUtils.formatDateTime(context, mStart, flags);
132         final String endDate = DateUtils.formatDateTime(context, mEnd, flags);
133         final String briefContentDescription = mResources
134                 .getString(R.string.data_usage_chart_brief_content_description, startDate, endDate);
135         contentDescription.append(briefContentDescription);
136 
137         if (usageSummary.isEmpty()) {
138             final String noDataContentDescription = mResources
139                     .getString(R.string.data_usage_chart_no_data_content_description);
140             contentDescription.append(noDataContentDescription);
141             chart.setContentDescription(contentDescription);
142             return;
143         }
144 
145         // Append more detailed stats.
146         String nodeDate;
147         String nodeContentDescription;
148         final List<DataUsageSummaryNode> densedStatsData = getDensedStatsData(usageSummary);
149         for (DataUsageSummaryNode data : densedStatsData) {
150             final int dataUsagePercentage = data.getDataUsagePercentage();
151             if (!data.isFromMultiNode() || dataUsagePercentage == 100) {
152                 nodeDate = DateUtils.formatDateTime(context, data.getStartTime(), flags);
153             } else {
154                 nodeDate = DateUtils.formatDateRange(context, data.getStartTime(),
155                         data.getEndTime(), flags);
156             }
157             nodeContentDescription = String.format("; %s, %d%%", nodeDate, dataUsagePercentage);
158 
159             contentDescription.append(nodeContentDescription);
160         }
161 
162         chart.setContentDescription(contentDescription);
163     }
164 
165     /**
166      * To avoid wordy data, e.g., Aug 2: 0%; Aug 3: 0%;...Aug 22: 0%; Aug 23: 2%.
167      * Collect the date of the same percentage, e.g., Aug 2 to Aug 22: 0%; Aug 23: 2%.
168      */
169     @VisibleForTesting
getDensedStatsData(@onNull List<NetworkUsageData> usageSummary)170     List<DataUsageSummaryNode> getDensedStatsData(@NonNull List<NetworkUsageData> usageSummary) {
171         final List<DataUsageSummaryNode> dataUsageSummaryNodes = new ArrayList<>();
172         final long overallDataUsage = Math.max(1L, usageSummary.stream()
173                 .mapToLong(NetworkUsageData::getUsage).sum());
174         long cumulatedDataUsage = 0L;
175 
176         // Collect List of DataUsageSummaryNode for data usage percentage information.
177         for (NetworkUsageData data : usageSummary) {
178             cumulatedDataUsage += data.getUsage();
179             int cumulatedDataUsagePercentage =
180                     (int) ((cumulatedDataUsage * 100) / overallDataUsage);
181 
182             final DataUsageSummaryNode node = new DataUsageSummaryNode(data.getStartTime(),
183                     data.getEndTime(), cumulatedDataUsagePercentage);
184             dataUsageSummaryNodes.add(node);
185         }
186 
187         // Group nodes of the same data usage percentage.
188         final Map<Integer, List<DataUsageSummaryNode>> nodesByDataUsagePercentage
189                 = dataUsageSummaryNodes.stream().collect(
190                         Collectors.groupingBy(DataUsageSummaryNode::getDataUsagePercentage));
191 
192         // Collect densed nodes from collection of the same  data usage percentage
193         final List<DataUsageSummaryNode> densedNodes = new ArrayList<>();
194         nodesByDataUsagePercentage.forEach((percentage, nodes) -> {
195             final long startTime = nodes.stream().mapToLong(DataUsageSummaryNode::getStartTime)
196                     .min().getAsLong();
197             final long endTime = nodes.stream().mapToLong(DataUsageSummaryNode::getEndTime)
198                     .max().getAsLong();
199 
200             final DataUsageSummaryNode densedNode = new DataUsageSummaryNode(
201                     startTime, endTime, percentage);
202             if (nodes.size() > 1) {
203                 densedNode.setFromMultiNode(true /* isFromMultiNode */);
204             }
205 
206             densedNodes.add(densedNode);
207         });
208 
209         return densedNodes.stream()
210                 .sorted(Comparator.comparingInt(DataUsageSummaryNode::getDataUsagePercentage))
211                 .collect(Collectors.toList());
212     }
213 
214     @VisibleForTesting
215     class DataUsageSummaryNode {
216         private long mStartTime;
217         private long mEndTime;
218         private int mDataUsagePercentage;
219         private boolean mIsFromMultiNode;
220 
DataUsageSummaryNode(long startTime, long endTime, int dataUsagePercentage)221         public DataUsageSummaryNode(long startTime, long endTime, int dataUsagePercentage) {
222             mStartTime = startTime;
223             mEndTime = endTime;
224             mDataUsagePercentage = dataUsagePercentage;
225             mIsFromMultiNode = false;
226         }
227 
getStartTime()228         public long getStartTime() {
229             return mStartTime;
230         }
231 
getEndTime()232         public long getEndTime() {
233             return mEndTime;
234         }
235 
getDataUsagePercentage()236         public int getDataUsagePercentage() {
237             return mDataUsagePercentage;
238         }
239 
setFromMultiNode(boolean isFromMultiNode)240         public void setFromMultiNode(boolean isFromMultiNode) {
241             mIsFromMultiNode = isFromMultiNode;
242         }
243 
isFromMultiNode()244         public boolean isFromMultiNode() {
245             return mIsFromMultiNode;
246         }
247     }
248 
toInt(long l)249     private int toInt(long l) {
250         // Don't need that much resolution on these times.
251         return (int) (l / (1000 * 60));
252     }
253 
bindNetworkPolicy(UsageView chart, NetworkPolicy policy, int top)254     private void bindNetworkPolicy(UsageView chart, NetworkPolicy policy, int top) {
255         CharSequence[] labels = new CharSequence[3];
256         int middleVisibility = 0;
257         int topVisibility = 0;
258         if (policy == null) {
259             return;
260         }
261 
262         if (policy.limitBytes != NetworkPolicy.LIMIT_DISABLED) {
263             topVisibility = mLimitColor;
264             labels[2] = getLabel(policy.limitBytes, R.string.data_usage_sweep_limit, mLimitColor);
265         }
266 
267         if (policy.warningBytes != NetworkPolicy.WARNING_DISABLED) {
268             int dividerLoc = (int) (policy.warningBytes / RESOLUTION);
269             chart.setDividerLoc(dividerLoc);
270             float weight = dividerLoc / (float) top;
271             float above = 1 - weight;
272             chart.setSideLabelWeights(above, weight);
273             middleVisibility = mWarningColor;
274             labels[1] = getLabel(policy.warningBytes, R.string.data_usage_sweep_warning,
275                     mWarningColor);
276         }
277 
278         chart.setSideLabels(labels);
279         chart.setDividerColors(middleVisibility, topVisibility);
280     }
281 
getLabel(long bytes, int str, int mLimitColor)282     private CharSequence getLabel(long bytes, int str, int mLimitColor) {
283         DataUsageFormatter dataUsageFormatter = new DataUsageFormatter(getContext());
284         BytesFormatter.Result result = dataUsageFormatter.formatDataUsageWithUnits(bytes);
285         CharSequence label = TextUtils.expandTemplate(getContext().getText(str),
286                 result.getNumber(), result.getUnits());
287         return new SpannableStringBuilder().append(label, new ForegroundColorSpan(mLimitColor), 0);
288     }
289 
290     /** Sets network policy. */
setNetworkPolicy(@ullable NetworkPolicy policy)291     public void setNetworkPolicy(@Nullable NetworkPolicy policy) {
292         mPolicy = policy;
293         notifyChanged();
294     }
295 
296     /** Sets time. */
setTime(long start, long end)297     public void setTime(long start, long end) {
298         mStart = start;
299         mEnd = end;
300         notifyChanged();
301     }
302 
setNetworkCycleData(NetworkCycleChartData data)303     public void setNetworkCycleData(NetworkCycleChartData data) {
304         mNetworkCycleChartData = data;
305         notifyChanged();
306     }
307 }
308