• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.settings.datausage;
18 
19 import android.annotation.AttrRes;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.graphics.Typeface;
23 import android.net.NetworkTemplate;
24 import android.os.Bundle;
25 import android.support.v7.preference.Preference;
26 import android.support.v7.preference.PreferenceViewHolder;
27 import android.text.Spannable;
28 import android.text.SpannableString;
29 import android.text.TextUtils;
30 import android.text.format.Formatter;
31 import android.text.style.AbsoluteSizeSpan;
32 import android.util.AttributeSet;
33 import android.view.View;
34 import android.widget.Button;
35 import android.widget.ProgressBar;
36 import android.widget.TextView;
37 
38 import com.android.internal.annotations.VisibleForTesting;
39 import com.android.internal.logging.nano.MetricsProto;
40 import com.android.settings.R;
41 import com.android.settings.core.SubSettingLauncher;
42 import com.android.settingslib.Utils;
43 import com.android.settingslib.utils.StringUtil;
44 
45 import java.util.Objects;
46 import java.util.concurrent.TimeUnit;
47 
48 /**
49  * Provides a summary of data usage.
50  */
51 public class DataUsageSummaryPreference extends Preference {
52     private static final long MILLIS_IN_A_DAY = TimeUnit.DAYS.toMillis(1);
53     private static final long WARNING_AGE = TimeUnit.HOURS.toMillis(6L);
54     @VisibleForTesting
55     static final Typeface SANS_SERIF_MEDIUM =
56             Typeface.create("sans-serif-medium", Typeface.NORMAL);
57 
58     private boolean mChartEnabled = true;
59     private CharSequence mStartLabel;
60     private CharSequence mEndLabel;
61 
62     /** large vs small size is 36/16 ~ 2.25 */
63     private static final float LARGER_FONT_RATIO = 2.25f;
64     private static final float SMALLER_FONT_RATIO = 1.0f;
65 
66     private boolean mDefaultTextColorSet;
67     private int mDefaultTextColor;
68     private int mNumPlans;
69     /** The ending time of the billing cycle in milliseconds since epoch. */
70     private long mCycleEndTimeMs;
71     /** The time of the last update in standard milliseconds since the epoch */
72     private long mSnapshotTimeMs;
73     /** Name of carrier, or null if not available */
74     private CharSequence mCarrierName;
75     private String mLimitInfoText;
76     private Intent mLaunchIntent;
77 
78     /** Progress to display on ProgressBar */
79     private float mProgress;
80     private boolean mHasMobileData;
81 
82     /**
83      * The size of the first registered plan if one exists or the size of the warning if it is set.
84      * -1 if no information is available.
85      */
86     private long mDataplanSize;
87 
88     /** The number of bytes used since the start of the cycle. */
89     private long mDataplanUse;
90 
91     /** WiFi only mode */
92     private boolean mWifiMode;
93     private String mUsagePeriod;
94 
DataUsageSummaryPreference(Context context, AttributeSet attrs)95     public DataUsageSummaryPreference(Context context, AttributeSet attrs) {
96         super(context, attrs);
97         setLayoutResource(R.layout.data_usage_summary_preference);
98     }
99 
setLimitInfo(String text)100     public void setLimitInfo(String text) {
101         if (!Objects.equals(text, mLimitInfoText)) {
102             mLimitInfoText = text;
103             notifyChanged();
104         }
105     }
106 
setProgress(float progress)107     public void setProgress(float progress) {
108         mProgress = progress;
109         notifyChanged();
110     }
111 
setUsageInfo(long cycleEnd, long snapshotTime, CharSequence carrierName, int numPlans, Intent launchIntent)112     public void setUsageInfo(long cycleEnd, long snapshotTime, CharSequence carrierName,
113             int numPlans, Intent launchIntent) {
114         mCycleEndTimeMs = cycleEnd;
115         mSnapshotTimeMs = snapshotTime;
116         mCarrierName = carrierName;
117         mNumPlans = numPlans;
118         mLaunchIntent = launchIntent;
119         notifyChanged();
120     }
121 
setChartEnabled(boolean enabled)122     public void setChartEnabled(boolean enabled) {
123         if (mChartEnabled != enabled) {
124             mChartEnabled = enabled;
125             notifyChanged();
126         }
127     }
128 
setLabels(CharSequence start, CharSequence end)129     public void setLabels(CharSequence start, CharSequence end) {
130         mStartLabel = start;
131         mEndLabel = end;
132         notifyChanged();
133     }
134 
setUsageNumbers(long used, long dataPlanSize, boolean hasMobileData)135     void setUsageNumbers(long used, long dataPlanSize, boolean hasMobileData) {
136         mDataplanUse = used;
137         mDataplanSize = dataPlanSize;
138         mHasMobileData = hasMobileData;
139         notifyChanged();
140     }
141 
setWifiMode(boolean isWifiMode, String usagePeriod)142     void setWifiMode(boolean isWifiMode, String usagePeriod) {
143         mWifiMode = isWifiMode;
144         mUsagePeriod = usagePeriod;
145         notifyChanged();
146     }
147 
148     @Override
onBindViewHolder(PreferenceViewHolder holder)149     public void onBindViewHolder(PreferenceViewHolder holder) {
150         super.onBindViewHolder(holder);
151 
152         ProgressBar bar = (ProgressBar) holder.findViewById(R.id.determinateBar);
153         if (mChartEnabled && (!TextUtils.isEmpty(mStartLabel) || !TextUtils.isEmpty(mEndLabel))) {
154             bar.setVisibility(View.VISIBLE);
155             holder.findViewById(R.id.label_bar).setVisibility(View.VISIBLE);
156             bar.setProgress((int) (mProgress * 100));
157             ((TextView) holder.findViewById(android.R.id.text1)).setText(mStartLabel);
158             ((TextView) holder.findViewById(android.R.id.text2)).setText(mEndLabel);
159         } else {
160             bar.setVisibility(View.GONE);
161             holder.findViewById(R.id.label_bar).setVisibility(View.GONE);
162         }
163 
164         updateDataUsageLabels(holder);
165 
166         TextView usageTitle = (TextView) holder.findViewById(R.id.usage_title);
167         TextView carrierInfo = (TextView) holder.findViewById(R.id.carrier_and_update);
168         Button launchButton = (Button) holder.findViewById(R.id.launch_mdp_app_button);
169         TextView limitInfo = (TextView) holder.findViewById(R.id.data_limits);
170 
171         if (mWifiMode) {
172             usageTitle.setText(R.string.data_usage_wifi_title);
173             usageTitle.setVisibility(View.VISIBLE);
174             TextView cycleTime = (TextView) holder.findViewById(R.id.cycle_left_time);
175             cycleTime.setText(mUsagePeriod);
176             carrierInfo.setVisibility(View.GONE);
177             limitInfo.setVisibility(View.GONE);
178 
179             launchButton.setOnClickListener((view) -> {
180                 launchWifiDataUsage(getContext());
181             });
182             launchButton.setText(R.string.launch_wifi_text);
183             launchButton.setVisibility(View.VISIBLE);
184         } else {
185             usageTitle.setVisibility(mNumPlans > 1 ? View.VISIBLE : View.GONE);
186             updateCycleTimeText(holder);
187             updateCarrierInfo(carrierInfo);
188             if (mLaunchIntent != null) {
189                 launchButton.setOnClickListener((view) -> {
190                     getContext().startActivity(mLaunchIntent);
191                 });
192                 launchButton.setVisibility(View.VISIBLE);
193             } else {
194                 launchButton.setVisibility(View.GONE);
195             }
196             limitInfo.setVisibility(
197                     TextUtils.isEmpty(mLimitInfoText) ? View.GONE : View.VISIBLE);
198             limitInfo.setText(mLimitInfoText);
199         }
200     }
201 
launchWifiDataUsage(Context context)202     private static void launchWifiDataUsage(Context context) {
203         final Bundle args = new Bundle(1);
204         args.putParcelable(DataUsageList.EXTRA_NETWORK_TEMPLATE,
205                 NetworkTemplate.buildTemplateWifiWildcard());
206         final SubSettingLauncher launcher = new SubSettingLauncher(context)
207                 .setArguments(args)
208                 .setDestination(DataUsageList.class.getName())
209                 .setSourceMetricsCategory(MetricsProto.MetricsEvent.VIEW_UNKNOWN);
210         launcher.setTitle(context.getString(R.string.wifi_data_usage));
211         launcher.launch();
212     }
213 
updateDataUsageLabels(PreferenceViewHolder holder)214     private void updateDataUsageLabels(PreferenceViewHolder holder) {
215         TextView usageNumberField = (TextView) holder.findViewById(R.id.data_usage_view);
216 
217         final Formatter.BytesResult usedResult = Formatter.formatBytes(getContext().getResources(),
218                 mDataplanUse, Formatter.FLAG_CALCULATE_ROUNDED | Formatter.FLAG_IEC_UNITS);
219         final SpannableString usageNumberText = new SpannableString(usedResult.value);
220         final int textSize =
221                 getContext().getResources().getDimensionPixelSize(R.dimen.usage_number_text_size);
222         usageNumberText.setSpan(new AbsoluteSizeSpan(textSize), 0, usageNumberText.length(),
223                 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
224         CharSequence template = getContext().getText(R.string.data_used_formatted);
225 
226         CharSequence usageText =
227                 TextUtils.expandTemplate(template, usageNumberText, usedResult.units);
228         usageNumberField.setText(usageText);
229 
230         final MeasurableLinearLayout layout =
231                 (MeasurableLinearLayout) holder.findViewById(R.id.usage_layout);
232 
233         if (mHasMobileData && mNumPlans >= 0 && mDataplanSize > 0L) {
234             TextView usageRemainingField = (TextView) holder.findViewById(R.id.data_remaining_view);
235             long dataRemaining = mDataplanSize - mDataplanUse;
236             if (dataRemaining >= 0) {
237                 usageRemainingField.setText(
238                         TextUtils.expandTemplate(getContext().getText(R.string.data_remaining),
239                                 DataUsageUtils.formatDataUsage(getContext(), dataRemaining)));
240                 usageRemainingField.setTextColor(
241                         Utils.getColorAttr(getContext(), android.R.attr.colorAccent));
242             } else {
243                 usageRemainingField.setText(
244                         TextUtils.expandTemplate(getContext().getText(R.string.data_overusage),
245                                 DataUsageUtils.formatDataUsage(getContext(), -dataRemaining)));
246                 usageRemainingField.setTextColor(
247                         Utils.getColorAttr(getContext(), android.R.attr.colorError));
248             }
249             layout.setChildren(usageNumberField, usageRemainingField);
250         } else {
251             layout.setChildren(usageNumberField, null);
252         }
253     }
254 
updateCycleTimeText(PreferenceViewHolder holder)255     private void updateCycleTimeText(PreferenceViewHolder holder) {
256         TextView cycleTime = (TextView) holder.findViewById(R.id.cycle_left_time);
257 
258         long millisLeft = mCycleEndTimeMs - System.currentTimeMillis();
259         if (millisLeft <= 0) {
260             cycleTime.setText(getContext().getString(R.string.billing_cycle_none_left));
261         } else {
262             int daysLeft = (int) (millisLeft / MILLIS_IN_A_DAY);
263             cycleTime.setText(daysLeft < 1
264                     ? getContext().getString(R.string.billing_cycle_less_than_one_day_left)
265                     : getContext().getResources().getQuantityString(
266                             R.plurals.billing_cycle_days_left, daysLeft, daysLeft));
267         }
268     }
269 
270 
271     private void updateCarrierInfo(TextView carrierInfo) {
272         if (mNumPlans > 0 && mSnapshotTimeMs >= 0L) {
273             carrierInfo.setVisibility(View.VISIBLE);
274             long updateAgeMillis = calculateTruncatedUpdateAge();
275 
276             int textResourceId;
277             CharSequence updateTime = null;
278             if (updateAgeMillis == 0) {
279                 if (mCarrierName != null) {
280                     textResourceId = R.string.carrier_and_update_now_text;
281                 } else {
282                     textResourceId = R.string.no_carrier_update_now_text;
283                 }
284             } else {
285                 if (mCarrierName != null) {
286                     textResourceId = R.string.carrier_and_update_text;
287                 } else {
288                     textResourceId = R.string.no_carrier_update_text;
289                 }
290                 updateTime = StringUtil.formatElapsedTime(
291                         getContext(), updateAgeMillis, false /* withSeconds */);
292             }
293             carrierInfo.setText(TextUtils.expandTemplate(
294                     getContext().getText(textResourceId),
295                     mCarrierName,
296                     updateTime));
297 
298             if (updateAgeMillis <= WARNING_AGE) {
299                 setCarrierInfoTextStyle(
300                         carrierInfo, android.R.attr.textColorSecondary, Typeface.SANS_SERIF);
301             } else {
302                 setCarrierInfoTextStyle(carrierInfo, android.R.attr.colorError, SANS_SERIF_MEDIUM);
303             }
304         } else {
305             carrierInfo.setVisibility(View.GONE);
306         }
307     }
308 
309     /**
310      * Returns the time since the last carrier update, as defined by {@link #mSnapshotTimeMs},
311      * truncated to the nearest day / hour / minute in milliseconds, or 0 if less than 1 min.
312      */
calculateTruncatedUpdateAge()313     private long calculateTruncatedUpdateAge() {
314         long updateAgeMillis = System.currentTimeMillis() - mSnapshotTimeMs;
315 
316         // Round to nearest whole unit
317         if (updateAgeMillis >= TimeUnit.DAYS.toMillis(1)) {
318             return (updateAgeMillis / TimeUnit.DAYS.toMillis(1)) * TimeUnit.DAYS.toMillis(1);
319         } else if (updateAgeMillis >= TimeUnit.HOURS.toMillis(1)) {
320             return (updateAgeMillis / TimeUnit.HOURS.toMillis(1)) * TimeUnit.HOURS.toMillis(1);
321         } else if (updateAgeMillis >= TimeUnit.MINUTES.toMillis(1)) {
322             return (updateAgeMillis / TimeUnit.MINUTES.toMillis(1)) * TimeUnit.MINUTES.toMillis(1);
323         } else {
324             return 0;
325         }
326     }
327 
setCarrierInfoTextStyle( TextView carrierInfo, @AttrRes int colorId, Typeface typeface)328     private void setCarrierInfoTextStyle(
329             TextView carrierInfo, @AttrRes int colorId, Typeface typeface) {
330         carrierInfo.setTextColor(Utils.getColorAttr(getContext(), colorId));
331         carrierInfo.setTypeface(typeface);
332     }
333 }
334