2 * Copyright (C) 2018 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.settings.datausage;
19 import android.content.Context;
20 import android.content.Intent;
21 import android.net.NetworkPolicyManager;
22 import android.net.NetworkTemplate;
23 import android.support.annotation.VisibleForTesting;
24 import android.support.v7.preference.Preference;
25 import android.telephony.SubscriptionInfo;
26 import android.telephony.SubscriptionManager;
27 import android.telephony.SubscriptionPlan;
28 import android.text.BidiFormatter;
29 import android.text.Spannable;
30 import android.text.SpannableString;
31 import android.text.TextUtils;
32 import android.text.format.Formatter;
33 import android.text.style.RelativeSizeSpan;
34 import android.util.Log;
35 import android.util.RecurrenceRule;
37 import com.android.internal.util.CollectionUtils;
38 import com.android.settings.R;
39 import com.android.settings.core.BasePreferenceController;
40 import com.android.settings.core.FeatureFlags;
41 import com.android.settingslib.NetworkPolicyEditor;
42 import com.android.settingslib.net.DataUsageController;
44 import java.util.List;
47 * This is the controller for the top of the data usage screen that retrieves carrier data from the
48 * new subscriptions framework API if available. The controller reads subscription information from
49 * the framework and falls back to legacy usage data if none are available.
51 public class DataUsageSummaryPreferenceController extends BasePreferenceController {
53 private static final String TAG = "DataUsageController";
54 private static final String KEY = "status_header";
55 private static final long PETA = 1000000000000000L;
56 private static final float RELATIVE_SIZE_LARGE = 1.25f * 1.25f; // (1/0.8)^2
57 private static final float RELATIVE_SIZE_SMALL = 1.0f / RELATIVE_SIZE_LARGE; // 0.8^2
59 private final DataUsageController mDataUsageController;
60 private final DataUsageInfoController mDataInfoController;
61 private final NetworkTemplate mDefaultTemplate;
62 private final NetworkPolicyEditor mPolicyEditor;
63 private final int mDataUsageTemplate;
64 private final boolean mHasMobileData;
65 private final SubscriptionManager mSubscriptionManager;
67 /** Name of the carrier, or null if not available */
68 private CharSequence mCarrierName;
70 /** The number of registered plans, [0,N] */
71 private int mDataplanCount;
73 /** The time of the last update in milliseconds since the epoch, or -1 if unknown */
74 private long mSnapshotTime;
77 * The size of the first registered plan if one exists or the size of the warning if it is set.
78 * -1 if no information is available.
80 private long mDataplanSize;
81 /** The number of bytes used since the start of the cycle. */
82 private long mDataplanUse;
83 /** The starting time of the billing cycle in ms since the epoch */
84 private long mCycleStart;
85 /** The ending time of the billing cycle in ms since the epoch */
86 private long mCycleEnd;
88 private Intent mManageSubscriptionIntent;
90 public DataUsageSummaryPreferenceController(Context context) {
93 final int defaultSubId = DataUsageUtils.getDefaultSubscriptionId(context);
94 mDefaultTemplate = DataUsageUtils.getDefaultTemplate(context, defaultSubId);
95 NetworkPolicyManager policyManager = NetworkPolicyManager.from(context);
96 mPolicyEditor = new NetworkPolicyEditor(policyManager);
98 mHasMobileData = DataUsageUtils.hasMobileData(context)
99 && defaultSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID;
101 mDataUsageController = new DataUsageController(context);
102 mDataInfoController = new DataUsageInfoController();
104 if (mHasMobileData) {
105 mDataUsageTemplate = R.string.cell_data_template;
106 } else if (DataUsageUtils.hasWifiRadio(context)) {
107 mDataUsageTemplate = R.string.wifi_data_template;
109 mDataUsageTemplate = R.string.ethernet_data_template;
112 mSubscriptionManager = (SubscriptionManager)
113 mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
117 DataUsageSummaryPreferenceController(
119 DataUsageController dataUsageController,
120 DataUsageInfoController dataInfoController,
121 NetworkTemplate defaultTemplate,
122 NetworkPolicyEditor policyEditor,
123 int dataUsageTemplate,
124 boolean hasMobileData,
125 SubscriptionManager subscriptionManager) {
127 mDataUsageController = dataUsageController;
128 mDataInfoController = dataInfoController;
129 mDefaultTemplate = defaultTemplate;
130 mPolicyEditor = policyEditor;
131 mDataUsageTemplate = dataUsageTemplate;
132 mHasMobileData = hasMobileData;
133 mSubscriptionManager = subscriptionManager;
137 void setPlanValues(int dataPlanCount, long dataPlanSize, long dataPlanUse) {
138 mDataplanCount = dataPlanCount;
139 mDataplanSize = dataPlanSize;
140 mDataplanUse = dataPlanUse;
144 void setCarrierValues(String carrierName, long snapshotTime, long cycleEnd, Intent intent) {
145 mCarrierName = carrierName;
146 mSnapshotTime = snapshotTime;
147 mCycleEnd = cycleEnd;
148 mManageSubscriptionIntent = intent;
152 public int getAvailabilityStatus() {
157 public void updateState(Preference preference) {
158 DataUsageSummaryPreference summaryPreference = (DataUsageSummaryPreference) preference;
159 DataUsageController.DataUsageInfo info = mDataUsageController.getDataUsageInfo(
162 mDataInfoController.updateDataLimit(info, mPolicyEditor.getPolicy(mDefaultTemplate));
164 if (mSubscriptionManager != null) {
165 refreshDataplanInfo(info);
168 if (info.warningLevel > 0 && info.limitLevel > 0) {
169 summaryPreference.setLimitInfo(TextUtils.expandTemplate(
170 mContext.getText(R.string.cell_data_warning_and_limit),
171 Formatter.formatFileSize(mContext, info.warningLevel),
172 Formatter.formatFileSize(mContext, info.limitLevel)).toString());
173 } else if (info.warningLevel > 0) {
174 summaryPreference.setLimitInfo(TextUtils.expandTemplate(
175 mContext.getText(R.string.cell_data_warning),
176 Formatter.formatFileSize(mContext, info.warningLevel)).toString());
177 } else if (info.limitLevel > 0) {
178 summaryPreference.setLimitInfo(TextUtils.expandTemplate(
179 mContext.getText(R.string.cell_data_limit),
180 Formatter.formatFileSize(mContext, info.limitLevel)).toString());
182 summaryPreference.setLimitInfo(null);
185 summaryPreference.setUsageNumbers(mDataplanUse, mDataplanSize, mHasMobileData);
187 if (mDataplanSize <= 0) {
188 summaryPreference.setChartEnabled(false);
190 summaryPreference.setChartEnabled(true);
191 summaryPreference.setLabels(Formatter.formatFileSize(mContext, 0 /* sizeBytes */),
192 Formatter.formatFileSize(mContext, mDataplanSize));
193 summaryPreference.setProgress(mDataplanUse / (float) mDataplanSize);
195 summaryPreference.setUsageInfo(mCycleEnd, mSnapshotTime, mCarrierName,
196 mDataplanCount, mManageSubscriptionIntent);
199 private String getLimitText(long limit, int textId) {
203 return mContext.getString(textId, Formatter.formatFileSize(mContext, limit));
206 // TODO(b/70950124) add test for this method once the robolectric shadow run script is
207 // completed (b/3526807)
208 private void refreshDataplanInfo(DataUsageController.DataUsageInfo info) {
209 // reset data before overwriting
212 mDataplanSize = mDataInfoController.getSummaryLimit(info);
213 mDataplanUse = info.usageLevel;
214 mCycleStart = info.cycleStart;
215 mCycleEnd = info.cycleEnd;
218 final int defaultSubId = SubscriptionManager.getDefaultSubscriptionId();
219 final SubscriptionInfo subInfo = mSubscriptionManager.getDefaultDataSubscriptionInfo();
220 if (subInfo != null && mHasMobileData) {
221 mCarrierName = subInfo.getCarrierName();
222 List<SubscriptionPlan> plans = mSubscriptionManager.getSubscriptionPlans(defaultSubId);
223 final SubscriptionPlan primaryPlan = getPrimaryPlan(mSubscriptionManager, defaultSubId);
224 if (primaryPlan != null) {
225 mDataplanCount = plans.size();
226 mDataplanSize = primaryPlan.getDataLimitBytes();
227 if (unlimited(mDataplanSize)) {
230 mDataplanUse = primaryPlan.getDataUsageBytes();
232 RecurrenceRule rule = primaryPlan.getCycleRule();
233 if (rule != null && rule.start != null && rule.end != null) {
234 mCycleStart = rule.start.toEpochSecond() * 1000L;
235 mCycleEnd = rule.end.toEpochSecond() * 1000L;
237 mSnapshotTime = primaryPlan.getDataUsageTime();
240 mManageSubscriptionIntent =
241 mSubscriptionManager.createManageSubscriptionIntent(defaultSubId);
242 Log.i(TAG, "Have " + mDataplanCount + " plans, dflt sub-id " + defaultSubId
243 + ", intent " + mManageSubscriptionIntent);
246 public static SubscriptionPlan getPrimaryPlan(SubscriptionManager subManager, int primaryId) {
247 List<SubscriptionPlan> plans = subManager.getSubscriptionPlans(primaryId);
248 if (CollectionUtils.isEmpty(plans)) {
251 // First plan in the list is the primary plan
252 SubscriptionPlan plan = plans.get(0);
253 return plan.getDataLimitBytes() > 0
254 && saneSize(plan.getDataUsageBytes())
255 && plan.getCycleRule() != null ? plan : null;
258 private static boolean saneSize(long value) {
259 return value >= 0L && value < PETA;
262 public static boolean unlimited(long size) {
263 return size == SubscriptionPlan.BYTES_UNLIMITED;
267 private static CharSequence formatUsage(Context context, String template, long usageLevel) {
268 final int FLAGS = Spannable.SPAN_INCLUSIVE_INCLUSIVE;
270 final Formatter.BytesResult usedResult = Formatter.formatBytes(context.getResources(),
271 usageLevel, Formatter.FLAG_CALCULATE_ROUNDED);
272 final SpannableString enlargedValue = new SpannableString(usedResult.value);
273 enlargedValue.setSpan(
274 new RelativeSizeSpan(RELATIVE_SIZE_LARGE), 0, enlargedValue.length(), FLAGS);
276 final SpannableString amountTemplate = new SpannableString(
277 context.getString(com.android.internal.R.string.fileSizeSuffix)
278 .replace("%1$s", "^1").replace("%2$s", "^2"));
279 final CharSequence formattedUsage = TextUtils.expandTemplate(amountTemplate,
280 enlargedValue, usedResult.units);
282 final SpannableString fullTemplate = new SpannableString(template);
283 fullTemplate.setSpan(
284 new RelativeSizeSpan(RELATIVE_SIZE_SMALL), 0, fullTemplate.length(), FLAGS);
285 return TextUtils.expandTemplate(fullTemplate,
286 BidiFormatter.getInstance().unicodeWrap(formattedUsage.toString()));