• 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.app.Activity;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.net.NetworkPolicyManager;
23 import android.net.NetworkTemplate;
24 import android.telephony.SubscriptionInfo;
25 import android.telephony.SubscriptionManager;
26 import android.telephony.SubscriptionPlan;
27 import android.text.TextUtils;
28 import android.util.Log;
29 import android.util.RecurrenceRule;
30 
31 import androidx.annotation.VisibleForTesting;
32 import androidx.preference.Preference;
33 import androidx.preference.PreferenceFragmentCompat;
34 import androidx.recyclerview.widget.RecyclerView;
35 
36 import com.android.internal.util.CollectionUtils;
37 import com.android.settings.R;
38 import com.android.settings.core.BasePreferenceController;
39 import com.android.settings.core.PreferenceControllerMixin;
40 import com.android.settings.widget.EntityHeaderController;
41 import com.android.settingslib.NetworkPolicyEditor;
42 import com.android.settingslib.core.lifecycle.Lifecycle;
43 import com.android.settingslib.core.lifecycle.LifecycleObserver;
44 import com.android.settingslib.core.lifecycle.events.OnStart;
45 import com.android.settingslib.net.DataUsageController;
46 
47 import java.util.List;
48 
49 /**
50  * This is the controller for a data usage header that retrieves carrier data from the new
51  * subscriptions framework API if available. The controller reads subscription information from the
52  * framework and falls back to legacy usage data if none are available.
53  */
54 public class DataUsageSummaryPreferenceController extends BasePreferenceController
55         implements PreferenceControllerMixin, LifecycleObserver, OnStart {
56 
57     private static final String TAG = "DataUsageController";
58     private static final String KEY = "status_header";
59     private static final long PETA = 1000000000000000L;
60     private static final float RELATIVE_SIZE_LARGE = 1.25f * 1.25f;  // (1/0.8)^2
61     private static final float RELATIVE_SIZE_SMALL = 1.0f / RELATIVE_SIZE_LARGE;  // 0.8^2
62 
63     private final Activity mActivity;
64     private final EntityHeaderController mEntityHeaderController;
65     private final Lifecycle mLifecycle;
66     private final PreferenceFragmentCompat mFragment;
67     protected final DataUsageController mDataUsageController;
68     protected final DataUsageInfoController mDataInfoController;
69     private final NetworkTemplate mDefaultTemplate;
70     protected final NetworkPolicyEditor mPolicyEditor;
71     private final int mDataUsageTemplate;
72     private final boolean mHasMobileData;
73     private final SubscriptionManager mSubscriptionManager;
74 
75     /** Name of the carrier, or null if not available */
76     private CharSequence mCarrierName;
77 
78     /** The number of registered plans, [0,N] */
79     private int mDataplanCount;
80 
81     /** The time of the last update in milliseconds since the epoch, or -1 if unknown */
82     private long mSnapshotTime;
83 
84     /**
85      * The size of the first registered plan if one exists or the size of the warning if it is set.
86      * -1 if no information is available.
87      */
88     private long mDataplanSize;
89     /** The "size" of the data usage bar, i.e. the amount of data its rhs end represents */
90     private long mDataBarSize;
91     /** The number of bytes used since the start of the cycle. */
92     private long mDataplanUse;
93     /** The starting time of the billing cycle in ms since the epoch */
94     private long mCycleStart;
95     /** The ending time of the billing cycle in ms since the epoch */
96     private long mCycleEnd;
97     /** The subscription that we should show usage for. */
98     private int mSubscriptionId;
99 
100     private Intent mManageSubscriptionIntent;
101 
DataUsageSummaryPreferenceController(Activity activity, Lifecycle lifecycle, PreferenceFragmentCompat fragment, int subscriptionId)102     public DataUsageSummaryPreferenceController(Activity activity,
103             Lifecycle lifecycle, PreferenceFragmentCompat fragment, int subscriptionId) {
104         super(activity, KEY);
105 
106         mActivity = activity;
107         mEntityHeaderController = EntityHeaderController.newInstance(activity,
108                 fragment, null);
109         mLifecycle = lifecycle;
110         mFragment = fragment;
111         mSubscriptionId = subscriptionId;
112 
113         mDefaultTemplate = DataUsageUtils.getDefaultTemplate(activity, mSubscriptionId);
114         NetworkPolicyManager policyManager = activity.getSystemService(NetworkPolicyManager.class);
115         mPolicyEditor = new NetworkPolicyEditor(policyManager);
116 
117         mHasMobileData = DataUsageUtils.hasMobileData(activity)
118                 && mSubscriptionId != SubscriptionManager.INVALID_SUBSCRIPTION_ID;
119 
120         mDataUsageController = new DataUsageController(activity);
121         mDataUsageController.setSubscriptionId(mSubscriptionId);
122         mDataInfoController = new DataUsageInfoController();
123 
124         if (mHasMobileData) {
125             mDataUsageTemplate = R.string.cell_data_template;
126         } else if (DataUsageUtils.hasWifiRadio(activity)) {
127             mDataUsageTemplate = R.string.wifi_data_template;
128         } else {
129             mDataUsageTemplate = R.string.ethernet_data_template;
130         }
131 
132         mSubscriptionManager = (SubscriptionManager)
133                 mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
134     }
135 
136     @VisibleForTesting
DataUsageSummaryPreferenceController( DataUsageController dataUsageController, DataUsageInfoController dataInfoController, NetworkTemplate defaultTemplate, NetworkPolicyEditor policyEditor, int dataUsageTemplate, boolean hasMobileData, SubscriptionManager subscriptionManager, Activity activity, Lifecycle lifecycle, EntityHeaderController entityHeaderController, PreferenceFragmentCompat fragment, int subscriptionId)137     DataUsageSummaryPreferenceController(
138             DataUsageController dataUsageController,
139             DataUsageInfoController dataInfoController,
140             NetworkTemplate defaultTemplate,
141             NetworkPolicyEditor policyEditor,
142             int dataUsageTemplate,
143             boolean hasMobileData,
144             SubscriptionManager subscriptionManager,
145             Activity activity,
146             Lifecycle lifecycle,
147             EntityHeaderController entityHeaderController,
148             PreferenceFragmentCompat fragment,
149             int subscriptionId) {
150         super(activity, KEY);
151         mDataUsageController = dataUsageController;
152         mDataInfoController = dataInfoController;
153         mDefaultTemplate = defaultTemplate;
154         mPolicyEditor = policyEditor;
155         mDataUsageTemplate = dataUsageTemplate;
156         mHasMobileData = hasMobileData;
157         mSubscriptionManager = subscriptionManager;
158         mActivity = activity;
159         mLifecycle = lifecycle;
160         mEntityHeaderController = entityHeaderController;
161         mFragment = fragment;
162         mSubscriptionId = subscriptionId;
163     }
164 
165     @Override
onStart()166     public void onStart() {
167         RecyclerView view = mFragment.getListView();
168         mEntityHeaderController.setRecyclerView(view, mLifecycle);
169         mEntityHeaderController.styleActionBar(mActivity);
170     }
171 
172     @VisibleForTesting
setPlanValues(int dataPlanCount, long dataPlanSize, long dataPlanUse)173     void setPlanValues(int dataPlanCount, long dataPlanSize, long dataPlanUse) {
174         mDataplanCount = dataPlanCount;
175         mDataplanSize = dataPlanSize;
176         mDataBarSize = dataPlanSize;
177         mDataplanUse = dataPlanUse;
178     }
179 
180     @VisibleForTesting
setCarrierValues(String carrierName, long snapshotTime, long cycleEnd, Intent intent)181     void setCarrierValues(String carrierName, long snapshotTime, long cycleEnd, Intent intent) {
182         mCarrierName = carrierName;
183         mSnapshotTime = snapshotTime;
184         mCycleEnd = cycleEnd;
185         mManageSubscriptionIntent = intent;
186     }
187 
188     @Override
getAvailabilityStatus()189     public int getAvailabilityStatus() {
190         return DataUsageUtils.hasSim(mActivity)
191                 || DataUsageUtils.hasWifiRadio(mContext) ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
192     }
193 
194     @Override
updateState(Preference preference)195     public void updateState(Preference preference) {
196         DataUsageSummaryPreference summaryPreference = (DataUsageSummaryPreference) preference;
197 
198         final DataUsageController.DataUsageInfo info;
199         if (DataUsageUtils.hasSim(mActivity)) {
200             info = mDataUsageController.getDataUsageInfo(mDefaultTemplate);
201             mDataInfoController.updateDataLimit(info, mPolicyEditor.getPolicy(mDefaultTemplate));
202             summaryPreference.setWifiMode(/* isWifiMode */ false,
203                     /* usagePeriod */ null, /* isSingleWifi */ false);
204         } else {
205             info = mDataUsageController.getDataUsageInfo(
206                     NetworkTemplate.buildTemplateWifiWildcard());
207             summaryPreference.setWifiMode(/* isWifiMode */ true, /* usagePeriod */
208                     info.period, /* isSingleWifi */ false);
209             summaryPreference.setLimitInfo(null);
210             summaryPreference.setUsageNumbers(info.usageLevel,
211                     /* dataPlanSize */ -1L,
212                     /* hasMobileData */ true);
213             summaryPreference.setChartEnabled(false);
214             summaryPreference.setUsageInfo(info.cycleEnd,
215                     /* snapshotTime */ -1L,
216                     /* carrierName */ null,
217                     /* numPlans */ 0,
218                     /* launchIntent */ null);
219             return;
220         }
221 
222         if (mSubscriptionManager != null) {
223             refreshDataplanInfo(info);
224         }
225 
226         if (info.warningLevel > 0 && info.limitLevel > 0) {
227             summaryPreference.setLimitInfo(TextUtils.expandTemplate(
228                     mContext.getText(R.string.cell_data_warning_and_limit),
229                     DataUsageUtils.formatDataUsage(mContext, info.warningLevel),
230                     DataUsageUtils.formatDataUsage(mContext, info.limitLevel)));
231         } else if (info.warningLevel > 0) {
232             summaryPreference.setLimitInfo(TextUtils.expandTemplate(
233                     mContext.getText(R.string.cell_data_warning),
234                     DataUsageUtils.formatDataUsage(mContext, info.warningLevel)));
235         } else if (info.limitLevel > 0) {
236             summaryPreference.setLimitInfo(TextUtils.expandTemplate(
237                     mContext.getText(R.string.cell_data_limit),
238                     DataUsageUtils.formatDataUsage(mContext, info.limitLevel)));
239         } else {
240             summaryPreference.setLimitInfo(null);
241         }
242 
243         summaryPreference.setUsageNumbers(mDataplanUse, mDataplanSize, mHasMobileData);
244 
245         if (mDataBarSize <= 0) {
246             summaryPreference.setChartEnabled(false);
247         } else {
248             summaryPreference.setChartEnabled(true);
249             summaryPreference.setLabels(DataUsageUtils.formatDataUsage(mContext, 0 /* sizeBytes */),
250                     DataUsageUtils.formatDataUsage(mContext, mDataBarSize));
251             summaryPreference.setProgress(mDataplanUse / (float) mDataBarSize);
252         }
253         summaryPreference.setUsageInfo(mCycleEnd, mSnapshotTime, mCarrierName,
254                 mDataplanCount, mManageSubscriptionIntent);
255     }
256 
257     // TODO(b/70950124) add test for this method once the robolectric shadow run script is
258     // completed (b/3526807)
refreshDataplanInfo(DataUsageController.DataUsageInfo info)259     private void refreshDataplanInfo(DataUsageController.DataUsageInfo info) {
260         // reset data before overwriting
261         mCarrierName = null;
262         mDataplanCount = 0;
263         mDataplanSize = -1L;
264         mDataBarSize = mDataInfoController.getSummaryLimit(info);
265         mDataplanUse = info.usageLevel;
266         mCycleStart = info.cycleStart;
267         mCycleEnd = info.cycleEnd;
268         mSnapshotTime = -1L;
269 
270         SubscriptionInfo subInfo = mSubscriptionManager.getActiveSubscriptionInfo(mSubscriptionId);
271         if (subInfo == null) {
272             subInfo = mSubscriptionManager.getAllSubscriptionInfoList().stream().filter(
273                     i -> i.getSubscriptionId() == mSubscriptionId).findFirst().orElse(null);
274         }
275         if (subInfo != null && mHasMobileData) {
276             mCarrierName = subInfo.getCarrierName();
277             List<SubscriptionPlan> plans = mSubscriptionManager.getSubscriptionPlans(
278                     mSubscriptionId);
279             final SubscriptionPlan primaryPlan = getPrimaryPlan(mSubscriptionManager,
280                     mSubscriptionId);
281 
282             if (primaryPlan != null) {
283                 mDataplanCount = plans.size();
284                 mDataplanSize = primaryPlan.getDataLimitBytes();
285                 if (unlimited(mDataplanSize)) {
286                     mDataplanSize = -1L;
287                 }
288                 mDataBarSize = mDataplanSize;
289                 mDataplanUse = primaryPlan.getDataUsageBytes();
290 
291                 RecurrenceRule rule = primaryPlan.getCycleRule();
292                 if (rule != null && rule.start != null && rule.end != null) {
293                     mCycleStart = rule.start.toEpochSecond() * 1000L;
294                     mCycleEnd = rule.end.toEpochSecond() * 1000L;
295                 }
296                 mSnapshotTime = primaryPlan.getDataUsageTime();
297             }
298         }
299         mManageSubscriptionIntent =
300                 mSubscriptionManager.createManageSubscriptionIntent(mSubscriptionId);
301         Log.i(TAG, "Have " + mDataplanCount + " plans, dflt sub-id " + mSubscriptionId
302                 + ", intent " + mManageSubscriptionIntent);
303     }
304 
getPrimaryPlan(SubscriptionManager subManager, int primaryId)305     public static SubscriptionPlan getPrimaryPlan(SubscriptionManager subManager, int primaryId) {
306         List<SubscriptionPlan> plans = subManager.getSubscriptionPlans(primaryId);
307         if (CollectionUtils.isEmpty(plans)) {
308             return null;
309         }
310         // First plan in the list is the primary plan
311         SubscriptionPlan plan = plans.get(0);
312         return plan.getDataLimitBytes() > 0
313                 && saneSize(plan.getDataUsageBytes())
314                 && plan.getCycleRule() != null ? plan : null;
315     }
316 
saneSize(long value)317     private static boolean saneSize(long value) {
318         return value >= 0L && value < PETA;
319     }
320 
unlimited(long size)321     public static boolean unlimited(long size) {
322         return size == SubscriptionPlan.BYTES_UNLIMITED;
323     }
324 }
325