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.net.NetworkPolicy;
19 import android.net.TrafficStats;
20 import android.text.SpannableStringBuilder;
21 import android.text.TextUtils;
22 import android.text.format.Formatter;
23 import android.text.style.ForegroundColorSpan;
24 import android.util.AttributeSet;
25 import android.util.SparseIntArray;
26 
27 import androidx.annotation.VisibleForTesting;
28 import androidx.preference.Preference;
29 import androidx.preference.PreferenceViewHolder;
30 
31 import com.android.settings.R;
32 import com.android.settings.Utils;
33 import com.android.settings.widget.UsageView;
34 import com.android.settingslib.net.NetworkCycleChartData;
35 import com.android.settingslib.net.NetworkCycleData;
36 
37 import java.util.List;
38 
39 public class ChartDataUsagePreference extends Preference {
40 
41     // The resolution we show on the graph so that we can squash things down to ints.
42     // Set to half a meg for now.
43     private static final long RESOLUTION = TrafficStats.MB_IN_BYTES / 2;
44 
45     private final int mWarningColor;
46     private final int mLimitColor;
47 
48     private NetworkPolicy mPolicy;
49     private long mStart;
50     private long mEnd;
51     private NetworkCycleChartData mNetworkCycleChartData;
52     private int mSecondaryColor;
53     private int mSeriesColor;
54 
ChartDataUsagePreference(Context context, AttributeSet attrs)55     public ChartDataUsagePreference(Context context, AttributeSet attrs) {
56         super(context, attrs);
57         setSelectable(false);
58         mLimitColor = Utils.getColorAttrDefaultColor(context, android.R.attr.colorError);
59         mWarningColor = Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondary);
60         setLayoutResource(R.layout.data_usage_graph);
61     }
62 
63     @Override
onBindViewHolder(PreferenceViewHolder holder)64     public void onBindViewHolder(PreferenceViewHolder holder) {
65         super.onBindViewHolder(holder);
66         final UsageView chart = (UsageView) holder.findViewById(R.id.data_usage);
67         if (mNetworkCycleChartData == null) {
68             return;
69         }
70 
71         final int top = getTop();
72         chart.clearPaths();
73         chart.configureGraph(toInt(mEnd - mStart), top);
74         calcPoints(chart, mNetworkCycleChartData.getUsageBuckets());
75         chart.setBottomLabels(new CharSequence[] {
76                 Utils.formatDateRange(getContext(), mStart, mStart),
77                 Utils.formatDateRange(getContext(), mEnd, mEnd),
78         });
79 
80         bindNetworkPolicy(chart, mPolicy, top);
81     }
82 
getTop()83     public int getTop() {
84         final long totalData = mNetworkCycleChartData.getTotalUsage();
85         final long policyMax =
86             mPolicy != null ? Math.max(mPolicy.limitBytes, mPolicy.warningBytes) : 0;
87         return (int) (Math.max(totalData, policyMax) / RESOLUTION);
88     }
89 
90     @VisibleForTesting
calcPoints(UsageView chart, List<NetworkCycleData> usageSummary)91     void calcPoints(UsageView chart, List<NetworkCycleData> usageSummary) {
92         if (usageSummary == null) {
93             return;
94         }
95         final SparseIntArray points = new SparseIntArray();
96         points.put(0, 0);
97 
98         final long now = System.currentTimeMillis();
99         long totalData = 0;
100         for (NetworkCycleData data : usageSummary) {
101             final long startTime = data.getStartTime();
102             if (startTime > now) {
103                 break;
104             }
105             final long endTime = data.getEndTime();
106 
107             // increment by current bucket total
108             totalData += data.getTotalUsage();
109 
110             if (points.size() == 1) {
111                 points.put(toInt(startTime - mStart) - 1, -1);
112             }
113             points.put(toInt(startTime - mStart + 1), (int) (totalData / RESOLUTION));
114             points.put(toInt(endTime - mStart), (int) (totalData / RESOLUTION));
115         }
116         if (points.size() > 1) {
117             chart.addPath(points);
118         }
119     }
120 
toInt(long l)121     private int toInt(long l) {
122         // Don't need that much resolution on these times.
123         return (int) (l / (1000 * 60));
124     }
125 
bindNetworkPolicy(UsageView chart, NetworkPolicy policy, int top)126     private void bindNetworkPolicy(UsageView chart, NetworkPolicy policy, int top) {
127         CharSequence[] labels = new CharSequence[3];
128         int middleVisibility = 0;
129         int topVisibility = 0;
130         if (policy == null) {
131             return;
132         }
133 
134         if (policy.limitBytes != NetworkPolicy.LIMIT_DISABLED) {
135             topVisibility = mLimitColor;
136             labels[2] = getLabel(policy.limitBytes, R.string.data_usage_sweep_limit, mLimitColor);
137         }
138 
139         if (policy.warningBytes != NetworkPolicy.WARNING_DISABLED) {
140             chart.setDividerLoc((int) (policy.warningBytes / RESOLUTION));
141             float weight = policy.warningBytes / RESOLUTION / (float) top;
142             float above = 1 - weight;
143             chart.setSideLabelWeights(above, weight);
144             middleVisibility = mWarningColor;
145             labels[1] = getLabel(policy.warningBytes, R.string.data_usage_sweep_warning,
146                     mWarningColor);
147         }
148 
149         chart.setSideLabels(labels);
150         chart.setDividerColors(middleVisibility, topVisibility);
151     }
152 
getLabel(long bytes, int str, int mLimitColor)153     private CharSequence getLabel(long bytes, int str, int mLimitColor) {
154         Formatter.BytesResult result = Formatter.formatBytes(getContext().getResources(),
155                 bytes, Formatter.FLAG_SHORTER | Formatter.FLAG_IEC_UNITS);
156         CharSequence label = TextUtils.expandTemplate(getContext().getText(str),
157                 result.value, result.units);
158         return new SpannableStringBuilder().append(label, new ForegroundColorSpan(mLimitColor), 0);
159     }
160 
setNetworkPolicy(NetworkPolicy policy)161     public void setNetworkPolicy(NetworkPolicy policy) {
162         mPolicy = policy;
163         notifyChanged();
164     }
165 
getInspectStart()166     public long getInspectStart() {
167         return mStart;
168     }
169 
getInspectEnd()170     public long getInspectEnd() {
171         return mEnd;
172     }
173 
setNetworkCycleData(NetworkCycleChartData data)174     public void setNetworkCycleData(NetworkCycleChartData data) {
175         mNetworkCycleChartData = data;
176         mStart = data.getStartTime();
177         mEnd = data.getEndTime();
178         notifyChanged();
179     }
180 
setColors(int seriesColor, int secondaryColor)181     public void setColors(int seriesColor, int secondaryColor) {
182         mSeriesColor = seriesColor;
183         mSecondaryColor = secondaryColor;
184         notifyChanged();
185     }
186 }
187