• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.car.settings.datausage;
18 
19 import android.car.drivingstate.CarUxRestrictions;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.net.NetworkTemplate;
23 import android.telephony.SubscriptionInfo;
24 import android.telephony.SubscriptionManager;
25 import android.telephony.SubscriptionPlan;
26 import android.telephony.TelephonyManager;
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.RecurrenceRule;
33 
34 import androidx.annotation.Nullable;
35 import androidx.annotation.VisibleForTesting;
36 
37 import com.android.car.settings.R;
38 import com.android.car.settings.common.FragmentController;
39 import com.android.car.settings.common.PreferenceController;
40 import com.android.car.settings.network.NetworkUtils;
41 import com.android.settingslib.net.DataUsageController;
42 import com.android.settingslib.utils.StringUtil;
43 
44 import java.util.List;
45 import java.util.concurrent.TimeUnit;
46 
47 /**
48  * Business logic for setting the {@link DataUsageSummaryPreference} with the current data usage and
49  * the appropriate summary text.
50  */
51 public class DataUsageSummaryPreferenceController extends
52         PreferenceController<DataUsageSummaryPreference> {
53 
54     private static final long MILLIS_IN_A_DAY = TimeUnit.DAYS.toMillis(1);
55     private static final long MILLIS_IN_AN_HOUR = TimeUnit.HOURS.toMillis(1);
56     private static final long MILLIS_IN_A_MINUTE = TimeUnit.MINUTES.toMillis(1);
57     private static final long MILLIS_IN_A_SECOND = TimeUnit.SECONDS.toMillis(1);
58     private static final int MAX_PROGRESS_BAR_VALUE = 1000;
59 
60     @VisibleForTesting
61     static final long WARNING_AGE = TimeUnit.HOURS.toMillis(6L);
62 
63     private final SubscriptionManager mSubscriptionManager;
64     private final TelephonyManager mTelephonyManager;
65     private final DataUsageController mDataUsageController;
66     private final NetworkTemplate mDefaultTemplate;
67 
68     /** Name of the carrier, or null if not available */
69     @Nullable
70     private CharSequence mCarrierName;
71     /** The number of registered plans, [0,N] */
72     private int mDataplanCount;
73     /** The time of the last update in milliseconds since the epoch, or -1 if unknown */
74     private long mSnapshotTime;
75     /** The size of the first registered plan if one exists. -1 if no information is available. */
76     private long mDataplanSize = -1;
77     /**
78      * Limit to track. Size of the first registered plan if one exists. Otherwise size of data limit
79      * or warning.
80      */
81     private long mDataplanTrackingThreshold;
82     /** The number of bytes used since the start of the cycle. */
83     private long mDataplanUse;
84     /** The ending time of the billing cycle in ms since the epoch */
85     private long mCycleEnd;
86     private Intent mManageSubscriptionIntent;
87 
DataUsageSummaryPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)88     public DataUsageSummaryPreferenceController(Context context, String preferenceKey,
89             FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
90         super(context, preferenceKey, fragmentController, uxRestrictions);
91         mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
92         mTelephonyManager = context.getSystemService(TelephonyManager.class);
93         mDataUsageController = new DataUsageController(context);
94 
95         int defaultSubId = DataUsageUtils.getDefaultSubscriptionId(mSubscriptionManager);
96         mDefaultTemplate = DataUsageUtils.getMobileNetworkTemplate(mTelephonyManager, defaultSubId);
97     }
98 
99     @Override
getPreferenceType()100     protected Class<DataUsageSummaryPreference> getPreferenceType() {
101         return DataUsageSummaryPreference.class;
102     }
103 
104     @Override
getAvailabilityStatus()105     protected int getAvailabilityStatus() {
106         return NetworkUtils.hasSim(mTelephonyManager) ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
107     }
108 
109     @Override
onCreateInternal()110     protected void onCreateInternal() {
111         getPreference().setMin(0);
112         getPreference().setMax(MAX_PROGRESS_BAR_VALUE);
113     }
114 
115     @Override
updateState(DataUsageSummaryPreference preference)116     protected void updateState(DataUsageSummaryPreference preference) {
117         DataUsageController.DataUsageInfo info = mDataUsageController.getDataUsageInfo(
118                 mDefaultTemplate);
119 
120         if (mSubscriptionManager != null) {
121             refreshDataplanInfo(info);
122         }
123 
124         preference.setTitle(getUsageText());
125         preference.setManageSubscriptionIntent(mManageSubscriptionIntent);
126 
127         preference.setDataLimitText(getLimitText(info));
128         preference.setRemainingBillingCycleText(getRemainingBillingCycleTimeText());
129 
130         // Carrier Info has special styling based on when it was last updated.
131         preference.setCarrierInfoText(getCarrierInfoText());
132         long updateAgeMillis = calculateTruncatedUpdateAge(mSnapshotTime);
133         if (updateAgeMillis <= WARNING_AGE) {
134             preference.setCarrierInfoTextStyle(R.style.DataUsageSummaryCarrierInfoTextAppearance);
135         } else {
136             preference.setCarrierInfoTextStyle(
137                     R.style.DataUsageSummaryCarrierInfoWarningTextAppearance);
138         }
139 
140         // Set the progress bar values.
141         preference.setMinLabel(DataUsageUtils.bytesToIecUnits(getContext(), /* byteValue= */ 0));
142         preference.setMaxLabel(
143                 DataUsageUtils.bytesToIecUnits(getContext(), mDataplanTrackingThreshold));
144         preference.setProgress(scaleUsage(mDataplanUse, mDataplanTrackingThreshold));
145     }
146 
getUsageText()147     private CharSequence getUsageText() {
148         Formatter.BytesResult usedResult = Formatter.formatBytes(getContext().getResources(),
149                 mDataplanUse, Formatter.FLAG_CALCULATE_ROUNDED | Formatter.FLAG_IEC_UNITS);
150         SpannableString usageNumberText = new SpannableString(usedResult.value);
151         int textSize = getContext().getResources().getDimensionPixelSize(
152                 R.dimen.usage_number_text_size);
153 
154         // Set the usage text (only the number) to the size defined by usage_number_text_size.
155         usageNumberText.setSpan(new AbsoluteSizeSpan(textSize), /* start= */ 0,
156                 usageNumberText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
157 
158         CharSequence template = getContext().getText(R.string.data_used_formatted);
159         CharSequence usageText = TextUtils.expandTemplate(template, usageNumberText,
160                 usedResult.units);
161         return usageText;
162     }
163 
164 
getLimitText(DataUsageController.DataUsageInfo info)165     private CharSequence getLimitText(DataUsageController.DataUsageInfo info) {
166         if (info.warningLevel > 0 && info.limitLevel > 0) {
167             return TextUtils.expandTemplate(
168                     getContext().getText(R.string.cell_data_warning_and_limit),
169                     DataUsageUtils.bytesToIecUnits(getContext(), info.warningLevel),
170                     DataUsageUtils.bytesToIecUnits(getContext(), info.limitLevel));
171         } else if (info.warningLevel > 0) {
172             return TextUtils.expandTemplate(getContext().getText(R.string.cell_data_warning),
173                     DataUsageUtils.bytesToIecUnits(getContext(), info.warningLevel));
174         } else if (info.limitLevel > 0) {
175             return TextUtils.expandTemplate(getContext().getText(R.string.cell_data_limit),
176                     DataUsageUtils.bytesToIecUnits(getContext(), info.limitLevel));
177         }
178 
179         return null;
180     }
181 
getRemainingBillingCycleTimeText()182     private CharSequence getRemainingBillingCycleTimeText() {
183         long millisLeft = mCycleEnd - System.currentTimeMillis();
184         if (millisLeft <= 0) {
185             return getContext().getString(R.string.billing_cycle_none_left);
186         } else {
187             int daysLeft = (int) (millisLeft / MILLIS_IN_A_DAY);
188             return daysLeft < 1
189                     ? getContext().getString(R.string.billing_cycle_less_than_one_day_left)
190                     : getContext().getResources().getQuantityString(
191                             R.plurals.billing_cycle_days_left, daysLeft, daysLeft);
192         }
193     }
194 
getCarrierInfoText()195     private CharSequence getCarrierInfoText() {
196         if (mDataplanCount > 0 && mSnapshotTime >= 0L) {
197             long updateAgeMillis = calculateTruncatedUpdateAge(mSnapshotTime);
198 
199             int textResourceId;
200             CharSequence updateTime = null;
201             if (updateAgeMillis == 0) {
202                 if (mCarrierName != null) {
203                     textResourceId = R.string.carrier_and_update_now_text;
204                 } else {
205                     textResourceId = R.string.no_carrier_update_now_text;
206                 }
207             } else {
208                 if (mCarrierName != null) {
209                     textResourceId = R.string.carrier_and_update_text;
210                 } else {
211                     textResourceId = R.string.no_carrier_update_text;
212                 }
213                 updateTime = StringUtil.formatElapsedTime(getContext(),
214                         updateAgeMillis, /* withSeconds= */ false);
215             }
216             return TextUtils.expandTemplate(getContext().getText(textResourceId), mCarrierName,
217                     updateTime);
218         }
219 
220         return null;
221     }
222 
refreshDataplanInfo(DataUsageController.DataUsageInfo info)223     private void refreshDataplanInfo(DataUsageController.DataUsageInfo info) {
224         // Reset data before overwriting.
225         mCarrierName = null;
226         mDataplanCount = 0;
227         mSnapshotTime = -1L;
228         mDataplanSize = -1L;
229         mDataplanTrackingThreshold = getSummaryLimit(info);
230         mDataplanUse = info.usageLevel;
231         mCycleEnd = info.cycleEnd;
232 
233         int defaultSubId = SubscriptionManager.getDefaultSubscriptionId();
234         SubscriptionInfo subInfo = mSubscriptionManager.getDefaultDataSubscriptionInfo();
235         if (subInfo != null) {
236             mCarrierName = subInfo.getCarrierName();
237             List<SubscriptionPlan> plans = mSubscriptionManager.getSubscriptionPlans(defaultSubId);
238             SubscriptionPlan primaryPlan = DataUsageUtils.getPrimaryPlan(mSubscriptionManager,
239                     defaultSubId);
240             if (primaryPlan != null) {
241                 mDataplanCount = plans.size();
242                 mDataplanSize = primaryPlan.getDataLimitBytes();
243                 if (mDataplanSize == SubscriptionPlan.BYTES_UNLIMITED) {
244                     mDataplanSize = -1L;
245                 }
246                 mDataplanTrackingThreshold = mDataplanSize;
247                 mDataplanUse = primaryPlan.getDataUsageBytes();
248 
249                 RecurrenceRule rule = primaryPlan.getCycleRule();
250                 if (rule != null && rule.start != null && rule.end != null) {
251                     mCycleEnd = rule.end.toEpochSecond() * MILLIS_IN_A_SECOND;
252                 }
253                 mSnapshotTime = primaryPlan.getDataUsageTime();
254             }
255         }
256         mManageSubscriptionIntent = mSubscriptionManager.createManageSubscriptionIntent(
257                 defaultSubId);
258     }
259 
260     /** Scales the current usage to be an integer between 0 and {@link #MAX_PROGRESS_BAR_VALUE}. */
scaleUsage(long usage, long maxUsage)261     private int scaleUsage(long usage, long maxUsage) {
262         if (maxUsage == 0) {
263             return 0;
264         }
265         return (int) ((usage / (float) maxUsage) * MAX_PROGRESS_BAR_VALUE);
266     }
267 
268     /**
269      * Gets the max displayed limit based on {@link DataUsageController.DataUsageInfo}.
270      *
271      * @return the most appropriate limit for the data usage summary. Use the total usage when it
272      * is higher than the limit and warning level. Use the limit when it is set and less than usage.
273      * Otherwise use warning level.
274      */
getSummaryLimit(DataUsageController.DataUsageInfo info)275     private static long getSummaryLimit(DataUsageController.DataUsageInfo info) {
276         long limit = info.limitLevel;
277         if (limit <= 0) {
278             limit = info.warningLevel;
279         }
280         if (info.usageLevel > limit) {
281             limit = info.usageLevel;
282         }
283         return limit;
284     }
285 
286     /**
287      * Returns the time since the last carrier update, as defined by {@link #mSnapshotTime},
288      * truncated to the nearest day / hour / minute in milliseconds, or 0 if less than 1 min.
289      */
calculateTruncatedUpdateAge(long snapshotTime)290     private long calculateTruncatedUpdateAge(long snapshotTime) {
291         long updateAgeMillis = System.currentTimeMillis() - snapshotTime;
292 
293         // Round to nearest whole unit
294         if (updateAgeMillis >= MILLIS_IN_A_DAY) {
295             return (updateAgeMillis / MILLIS_IN_A_DAY) * MILLIS_IN_A_DAY;
296         } else if (updateAgeMillis >= MILLIS_IN_AN_HOUR) {
297             return (updateAgeMillis / MILLIS_IN_AN_HOUR) * MILLIS_IN_AN_HOUR;
298         } else if (updateAgeMillis >= MILLIS_IN_A_MINUTE) {
299             return (updateAgeMillis / MILLIS_IN_A_MINUTE) * MILLIS_IN_A_MINUTE;
300         } else {
301             return 0;
302         }
303     }
304 }
305