• 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.app.settings.SettingsEnums;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.graphics.Typeface;
24 import android.net.ConnectivityManager;
25 import android.net.NetworkTemplate;
26 import android.os.Bundle;
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.LinearLayout;
36 import android.widget.ProgressBar;
37 import android.widget.TextView;
38 
39 import androidx.annotation.VisibleForTesting;
40 import androidx.preference.Preference;
41 import androidx.preference.PreferenceViewHolder;
42 
43 import com.android.settings.R;
44 import com.android.settings.core.SubSettingLauncher;
45 import com.android.settingslib.Utils;
46 import com.android.settingslib.net.DataUsageController;
47 import com.android.settingslib.utils.StringUtil;
48 
49 import java.util.Objects;
50 import java.util.concurrent.TimeUnit;
51 
52 /**
53  * Provides a summary of data usage.
54  */
55 public class DataUsageSummaryPreference extends Preference {
56     private static final long MILLIS_IN_A_DAY = TimeUnit.DAYS.toMillis(1);
57     private static final long WARNING_AGE = TimeUnit.HOURS.toMillis(6L);
58     @VisibleForTesting
59     static final Typeface SANS_SERIF_MEDIUM =
60             Typeface.create("sans-serif-medium", Typeface.NORMAL);
61 
62     private boolean mChartEnabled = true;
63     private CharSequence mStartLabel;
64     private CharSequence mEndLabel;
65 
66     /** large vs small size is 36/16 ~ 2.25 */
67     private static final float LARGER_FONT_RATIO = 2.25f;
68     private static final float SMALLER_FONT_RATIO = 1.0f;
69 
70     private boolean mDefaultTextColorSet;
71     private int mDefaultTextColor;
72     private int mNumPlans;
73     /** The specified un-initialized value for cycle time */
74     private final long CYCLE_TIME_UNINITIAL_VALUE = 0;
75     /** The ending time of the billing cycle in milliseconds since epoch. */
76     private long mCycleEndTimeMs;
77     /** The time of the last update in standard milliseconds since the epoch */
78     private long mSnapshotTimeMs;
79     /** Name of carrier, or null if not available */
80     private CharSequence mCarrierName;
81     private CharSequence mLimitInfoText;
82     private Intent mLaunchIntent;
83 
84     /** Progress to display on ProgressBar */
85     private float mProgress;
86     private boolean mHasMobileData;
87 
88     /**
89      * The size of the first registered plan if one exists or the size of the warning if it is set.
90      * -1 if no information is available.
91      */
92     private long mDataplanSize;
93 
94     /** The number of bytes used since the start of the cycle. */
95     private long mDataplanUse;
96 
97     /** WiFi only mode */
98     private boolean mWifiMode;
99     private String mUsagePeriod;
100     private boolean mSingleWifi;    // Shows only one specified WiFi network usage
101 
DataUsageSummaryPreference(Context context, AttributeSet attrs)102     public DataUsageSummaryPreference(Context context, AttributeSet attrs) {
103         super(context, attrs);
104         setLayoutResource(R.layout.data_usage_summary_preference);
105     }
106 
setLimitInfo(CharSequence text)107     public void setLimitInfo(CharSequence text) {
108         if (!Objects.equals(text, mLimitInfoText)) {
109             mLimitInfoText = text;
110             notifyChanged();
111         }
112     }
113 
setProgress(float progress)114     public void setProgress(float progress) {
115         mProgress = progress;
116         notifyChanged();
117     }
118 
setUsageInfo(long cycleEnd, long snapshotTime, CharSequence carrierName, int numPlans, Intent launchIntent)119     public void setUsageInfo(long cycleEnd, long snapshotTime, CharSequence carrierName,
120             int numPlans, Intent launchIntent) {
121         mCycleEndTimeMs = cycleEnd;
122         mSnapshotTimeMs = snapshotTime;
123         mCarrierName = carrierName;
124         mNumPlans = numPlans;
125         mLaunchIntent = launchIntent;
126         notifyChanged();
127     }
128 
setChartEnabled(boolean enabled)129     public void setChartEnabled(boolean enabled) {
130         if (mChartEnabled != enabled) {
131             mChartEnabled = enabled;
132             notifyChanged();
133         }
134     }
135 
setLabels(CharSequence start, CharSequence end)136     public void setLabels(CharSequence start, CharSequence end) {
137         mStartLabel = start;
138         mEndLabel = end;
139         notifyChanged();
140     }
141 
setUsageNumbers(long used, long dataPlanSize, boolean hasMobileData)142     void setUsageNumbers(long used, long dataPlanSize, boolean hasMobileData) {
143         mDataplanUse = used;
144         mDataplanSize = dataPlanSize;
145         mHasMobileData = hasMobileData;
146         notifyChanged();
147     }
148 
setWifiMode(boolean isWifiMode, String usagePeriod, boolean isSingleWifi)149     void setWifiMode(boolean isWifiMode, String usagePeriod, boolean isSingleWifi) {
150         mWifiMode = isWifiMode;
151         mUsagePeriod = usagePeriod;
152         mSingleWifi = isSingleWifi;
153         notifyChanged();
154     }
155 
156     @Override
onBindViewHolder(PreferenceViewHolder holder)157     public void onBindViewHolder(PreferenceViewHolder holder) {
158         super.onBindViewHolder(holder);
159 
160         ProgressBar bar = getProgressBar(holder);
161         if (mChartEnabled && (!TextUtils.isEmpty(mStartLabel) || !TextUtils.isEmpty(mEndLabel))) {
162             bar.setVisibility(View.VISIBLE);
163             getLabelBar(holder).setVisibility(View.VISIBLE);
164             bar.setProgress((int) (mProgress * 100));
165             (getLabel1(holder)).setText(mStartLabel);
166             (getLabel2(holder)).setText(mEndLabel);
167         } else {
168             bar.setVisibility(View.GONE);
169             getLabelBar(holder).setVisibility(View.GONE);
170         }
171 
172         updateDataUsageLabels(holder);
173 
174         TextView usageTitle = getUsageTitle(holder);
175         TextView carrierInfo = getCarrierInfo(holder);
176         Button launchButton = getLaunchButton(holder);
177         TextView limitInfo = getDataLimits(holder);
178 
179         if (mWifiMode && mSingleWifi) {
180             updateCycleTimeText(holder);
181 
182             usageTitle.setVisibility(View.GONE);
183             launchButton.setVisibility(View.GONE);
184             carrierInfo.setVisibility(View.GONE);
185 
186             limitInfo.setVisibility(TextUtils.isEmpty(mLimitInfoText) ? View.GONE : View.VISIBLE);
187             limitInfo.setText(mLimitInfoText);
188         } else if (mWifiMode) {
189             usageTitle.setText(R.string.data_usage_wifi_title);
190             usageTitle.setVisibility(View.VISIBLE);
191             TextView cycleTime = getCycleTime(holder);
192             cycleTime.setText(mUsagePeriod);
193             carrierInfo.setVisibility(View.GONE);
194             limitInfo.setVisibility(View.GONE);
195 
196             final long usageLevel = getHistoricalUsageLevel();
197             if (usageLevel > 0L) {
198                 launchButton.setOnClickListener((view) -> {
199                     launchWifiDataUsage(getContext());
200                 });
201             } else {
202                 launchButton.setEnabled(false);
203             }
204             launchButton.setText(R.string.launch_wifi_text);
205             launchButton.setVisibility(View.VISIBLE);
206         } else {
207             usageTitle.setVisibility(mNumPlans > 1 ? View.VISIBLE : View.GONE);
208             updateCycleTimeText(holder);
209             updateCarrierInfo(carrierInfo);
210             if (mLaunchIntent != null) {
211                 launchButton.setOnClickListener((view) -> {
212                     getContext().startActivity(mLaunchIntent);
213                 });
214                 launchButton.setVisibility(View.VISIBLE);
215             } else {
216                 launchButton.setVisibility(View.GONE);
217             }
218             limitInfo.setVisibility(
219                     TextUtils.isEmpty(mLimitInfoText) ? View.GONE : View.VISIBLE);
220             limitInfo.setText(mLimitInfoText);
221         }
222     }
223 
224     @VisibleForTesting
launchWifiDataUsage(Context context)225     static void launchWifiDataUsage(Context context) {
226         final Bundle args = new Bundle(1);
227         args.putParcelable(DataUsageList.EXTRA_NETWORK_TEMPLATE,
228                 new NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI).build());
229         args.putInt(DataUsageList.EXTRA_NETWORK_TYPE, ConnectivityManager.TYPE_WIFI);
230         final SubSettingLauncher launcher = new SubSettingLauncher(context)
231                 .setArguments(args)
232                 .setDestination(DataUsageList.class.getName())
233                 .setSourceMetricsCategory(SettingsEnums.PAGE_UNKNOWN);
234         launcher.setTitleRes(R.string.wifi_data_usage);
235         launcher.launch();
236     }
237 
updateDataUsageLabels(PreferenceViewHolder holder)238     private void updateDataUsageLabels(PreferenceViewHolder holder) {
239         TextView usageNumberField = getDataUsed(holder);
240 
241         final Formatter.BytesResult usedResult = Formatter.formatBytes(getContext().getResources(),
242                 mDataplanUse, Formatter.FLAG_CALCULATE_ROUNDED | Formatter.FLAG_IEC_UNITS);
243         final SpannableString usageNumberText = new SpannableString(usedResult.value);
244         final int textSize =
245                 getContext().getResources().getDimensionPixelSize(R.dimen.usage_number_text_size);
246         usageNumberText.setSpan(new AbsoluteSizeSpan(textSize), 0, usageNumberText.length(),
247                 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
248         CharSequence template = getContext().getText(R.string.data_used_formatted);
249 
250         CharSequence usageText =
251                 TextUtils.expandTemplate(template, usageNumberText, usedResult.units);
252         usageNumberField.setText(usageText);
253 
254         final MeasurableLinearLayout layout = getLayout(holder);
255 
256         if (mHasMobileData && mNumPlans >= 0 && mDataplanSize > 0L) {
257             TextView usageRemainingField = getDataRemaining(holder);
258             long dataRemaining = mDataplanSize - mDataplanUse;
259             if (dataRemaining >= 0) {
260                 usageRemainingField.setText(
261                         TextUtils.expandTemplate(getContext().getText(R.string.data_remaining),
262                                 DataUsageUtils.formatDataUsage(getContext(), dataRemaining)));
263                 usageRemainingField.setTextColor(
264                         Utils.getColorAttr(getContext(), android.R.attr.colorAccent));
265             } else {
266                 usageRemainingField.setText(
267                         TextUtils.expandTemplate(getContext().getText(R.string.data_overusage),
268                                 DataUsageUtils.formatDataUsage(getContext(), -dataRemaining)));
269                 usageRemainingField.setTextColor(
270                         Utils.getColorAttr(getContext(), android.R.attr.colorError));
271             }
272             layout.setChildren(usageNumberField, usageRemainingField);
273         } else {
274             layout.setChildren(usageNumberField, null);
275         }
276     }
277 
updateCycleTimeText(PreferenceViewHolder holder)278     private void updateCycleTimeText(PreferenceViewHolder holder) {
279         TextView cycleTime = getCycleTime(holder);
280 
281         // Takes zero as a special case which value is never set.
282         if (mCycleEndTimeMs == CYCLE_TIME_UNINITIAL_VALUE) {
283             cycleTime.setVisibility(View.GONE);
284             return;
285         }
286 
287         cycleTime.setVisibility(View.VISIBLE);
288         long millisLeft = mCycleEndTimeMs - System.currentTimeMillis();
289         if (millisLeft <= 0) {
290             cycleTime.setText(getContext().getString(R.string.billing_cycle_none_left));
291         } else {
292             int daysLeft = (int) (millisLeft / MILLIS_IN_A_DAY);
293             cycleTime.setText(daysLeft < 1
294                     ? getContext().getString(R.string.billing_cycle_less_than_one_day_left)
295                     : getContext().getResources().getQuantityString(
296                             R.plurals.billing_cycle_days_left, daysLeft, daysLeft));
297         }
298     }
299 
300 
301     private void updateCarrierInfo(TextView carrierInfo) {
302         if (mNumPlans > 0 && mSnapshotTimeMs >= 0L) {
303             carrierInfo.setVisibility(View.VISIBLE);
304             long updateAgeMillis = calculateTruncatedUpdateAge();
305 
306             int textResourceId;
307             CharSequence updateTime = null;
308             if (updateAgeMillis == 0) {
309                 if (mCarrierName != null) {
310                     textResourceId = R.string.carrier_and_update_now_text;
311                 } else {
312                     textResourceId = R.string.no_carrier_update_now_text;
313                 }
314             } else {
315                 if (mCarrierName != null) {
316                     textResourceId = R.string.carrier_and_update_text;
317                 } else {
318                     textResourceId = R.string.no_carrier_update_text;
319                 }
320                 updateTime = StringUtil.formatElapsedTime(
321                         getContext(),
322                         updateAgeMillis,
323                         false /* withSeconds */,
324                         false /* collapseTimeUnit */);
325             }
326             carrierInfo.setText(TextUtils.expandTemplate(
327                     getContext().getText(textResourceId),
328                     mCarrierName,
329                     updateTime));
330 
331             if (updateAgeMillis <= WARNING_AGE) {
332                 setCarrierInfoTextStyle(
333                         carrierInfo, android.R.attr.textColorSecondary, Typeface.SANS_SERIF);
334             } else {
335                 setCarrierInfoTextStyle(carrierInfo, android.R.attr.colorError, SANS_SERIF_MEDIUM);
336             }
337         } else {
338             carrierInfo.setVisibility(View.GONE);
339         }
340     }
341 
342     /**
343      * Returns the time since the last carrier update, as defined by {@link #mSnapshotTimeMs},
344      * truncated to the nearest day / hour / minute in milliseconds, or 0 if less than 1 min.
345      */
calculateTruncatedUpdateAge()346     private long calculateTruncatedUpdateAge() {
347         long updateAgeMillis = System.currentTimeMillis() - mSnapshotTimeMs;
348 
349         // Round to nearest whole unit
350         if (updateAgeMillis >= TimeUnit.DAYS.toMillis(1)) {
351             return (updateAgeMillis / TimeUnit.DAYS.toMillis(1)) * TimeUnit.DAYS.toMillis(1);
352         } else if (updateAgeMillis >= TimeUnit.HOURS.toMillis(1)) {
353             return (updateAgeMillis / TimeUnit.HOURS.toMillis(1)) * TimeUnit.HOURS.toMillis(1);
354         } else if (updateAgeMillis >= TimeUnit.MINUTES.toMillis(1)) {
355             return (updateAgeMillis / TimeUnit.MINUTES.toMillis(1)) * TimeUnit.MINUTES.toMillis(1);
356         } else {
357             return 0;
358         }
359     }
360 
setCarrierInfoTextStyle( TextView carrierInfo, @AttrRes int colorId, Typeface typeface)361     private void setCarrierInfoTextStyle(
362             TextView carrierInfo, @AttrRes int colorId, Typeface typeface) {
363         carrierInfo.setTextColor(Utils.getColorAttr(getContext(), colorId));
364         carrierInfo.setTypeface(typeface);
365     }
366 
367     @VisibleForTesting
getHistoricalUsageLevel()368     protected long getHistoricalUsageLevel() {
369         final DataUsageController controller = new DataUsageController(getContext());
370         return controller.getHistoricalUsageLevel(
371                 new NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI).build());
372     }
373 
374     @VisibleForTesting
getUsageTitle(PreferenceViewHolder holder)375     protected TextView getUsageTitle(PreferenceViewHolder holder) {
376         return (TextView) holder.findViewById(R.id.usage_title);
377     }
378 
379     @VisibleForTesting
getCycleTime(PreferenceViewHolder holder)380     protected TextView getCycleTime(PreferenceViewHolder holder) {
381         return (TextView) holder.findViewById(R.id.cycle_left_time);
382     }
383 
384     @VisibleForTesting
getCarrierInfo(PreferenceViewHolder holder)385     protected TextView getCarrierInfo(PreferenceViewHolder holder) {
386         return (TextView) holder.findViewById(R.id.carrier_and_update);
387     }
388 
389     @VisibleForTesting
getDataLimits(PreferenceViewHolder holder)390     protected TextView getDataLimits(PreferenceViewHolder holder) {
391         return (TextView) holder.findViewById(R.id.data_limits);
392     }
393 
394     @VisibleForTesting
getDataUsed(PreferenceViewHolder holder)395     protected TextView getDataUsed(PreferenceViewHolder holder) {
396         return (TextView) holder.findViewById(R.id.data_usage_view);
397     }
398 
399     @VisibleForTesting
getDataRemaining(PreferenceViewHolder holder)400     protected TextView getDataRemaining(PreferenceViewHolder holder) {
401         return (TextView) holder.findViewById(R.id.data_remaining_view);
402     }
403 
404     @VisibleForTesting
getLaunchButton(PreferenceViewHolder holder)405     protected Button getLaunchButton(PreferenceViewHolder holder) {
406         return (Button) holder.findViewById(R.id.launch_mdp_app_button);
407     }
408 
409     @VisibleForTesting
getLabelBar(PreferenceViewHolder holder)410     protected LinearLayout getLabelBar(PreferenceViewHolder holder) {
411         return (LinearLayout) holder.findViewById(R.id.label_bar);
412     }
413 
414     @VisibleForTesting
getLabel1(PreferenceViewHolder holder)415     protected TextView getLabel1(PreferenceViewHolder holder) {
416         return (TextView) holder.findViewById(android.R.id.text1);
417     }
418 
419     @VisibleForTesting
getLabel2(PreferenceViewHolder holder)420     protected TextView getLabel2(PreferenceViewHolder holder) {
421         return (TextView) holder.findViewById(android.R.id.text2);
422     }
423 
424     @VisibleForTesting
getProgressBar(PreferenceViewHolder holder)425     protected ProgressBar getProgressBar(PreferenceViewHolder holder) {
426         return (ProgressBar) holder.findViewById(R.id.determinateBar);
427     }
428 
429     @VisibleForTesting
getLayout(PreferenceViewHolder holder)430     protected MeasurableLinearLayout getLayout(PreferenceViewHolder holder) {
431         return (MeasurableLinearLayout) holder.findViewById(R.id.usage_layout);
432     }
433 }
434