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