• 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.content.pm.PackageManager;
23 import android.net.INetworkPolicyManager;
24 import android.net.NetworkPolicyManager;
25 import android.net.NetworkTemplate;
26 import android.os.ServiceManager;
27 import android.telephony.SubscriptionInfo;
28 import android.telephony.SubscriptionManager;
29 import android.telephony.SubscriptionPlan;
30 import android.text.TextUtils;
31 import android.util.Log;
32 import android.util.RecurrenceRule;
33 
34 import androidx.annotation.VisibleForTesting;
35 import androidx.preference.Preference;
36 import androidx.preference.PreferenceFragmentCompat;
37 import androidx.recyclerview.widget.RecyclerView;
38 
39 import com.android.internal.util.CollectionUtils;
40 import com.android.settings.R;
41 import com.android.settings.core.PreferenceControllerMixin;
42 import com.android.settings.datausage.lib.DataUsageLib;
43 import com.android.settings.network.ProxySubscriptionManager;
44 import com.android.settings.network.telephony.TelephonyBasePreferenceController;
45 import com.android.settings.widget.EntityHeaderController;
46 import com.android.settingslib.NetworkPolicyEditor;
47 import com.android.settingslib.core.lifecycle.Lifecycle;
48 import com.android.settingslib.core.lifecycle.LifecycleObserver;
49 import com.android.settingslib.core.lifecycle.events.OnStart;
50 import com.android.settingslib.net.DataUsageController;
51 import com.android.settingslib.utils.ThreadUtils;
52 
53 import java.util.List;
54 import java.util.concurrent.Future;
55 
56 /**
57  * This is the controller for a data usage header that retrieves carrier data from the new
58  * subscriptions framework API if available. The controller reads subscription information from the
59  * framework and falls back to legacy usage data if none are available.
60  */
61 public class DataUsageSummaryPreferenceController extends TelephonyBasePreferenceController
62         implements PreferenceControllerMixin, LifecycleObserver, OnStart {
63 
64     private static final String TAG = "DataUsageController";
65     private static final String KEY = "status_header";
66     private static final long PETA = 1000000000000000L;
67     private static final float RELATIVE_SIZE_LARGE = 1.25f * 1.25f;  // (1/0.8)^2
68     private static final float RELATIVE_SIZE_SMALL = 1.0f / RELATIVE_SIZE_LARGE;  // 0.8^2
69 
70     private EntityHeaderController mEntityHeaderController;
71     private final Lifecycle mLifecycle;
72     private final PreferenceFragmentCompat mFragment;
73     protected DataUsageController mDataUsageController;
74     protected DataUsageInfoController mDataInfoController;
75     private NetworkTemplate mDefaultTemplate;
76     protected NetworkPolicyEditor mPolicyEditor;
77     private int mDataUsageTemplate;
78     private boolean mHasMobileData;
79 
80     /** Name of the carrier, or null if not available */
81     private CharSequence mCarrierName;
82 
83     /** The number of registered plans, [0,N] */
84     private int mDataplanCount;
85 
86     /** The time of the last update in milliseconds since the epoch, or -1 if unknown */
87     private long mSnapshotTime;
88 
89     /**
90      * The size of the first registered plan if one exists or the size of the warning if it is set.
91      * -1 if no information is available.
92      */
93     private long mDataplanSize;
94     /** The "size" of the data usage bar, i.e. the amount of data its rhs end represents */
95     private long mDataBarSize;
96     /** The number of bytes used since the start of the cycle. */
97     private long mDataplanUse;
98     /** The starting time of the billing cycle in ms since the epoch */
99     private long mCycleStart;
100     /** The ending time of the billing cycle in ms since the epoch */
101     private long mCycleEnd;
102 
103     private Intent mManageSubscriptionIntent;
104 
105     private Future<Long> mHistoricalUsageLevel;
106 
DataUsageSummaryPreferenceController(Activity activity, Lifecycle lifecycle, PreferenceFragmentCompat fragment, int subscriptionId)107     public DataUsageSummaryPreferenceController(Activity activity,
108             Lifecycle lifecycle, PreferenceFragmentCompat fragment, int subscriptionId) {
109         super(activity, KEY);
110 
111         mLifecycle = lifecycle;
112         mFragment = fragment;
113         init(subscriptionId);
114     }
115 
116     /**
117      * Initialize based on subscription ID provided
118      * @param subscriptionId is the target subscriptionId
119      */
init(int subscriptionId)120     public void init(int subscriptionId) {
121         mSubId = subscriptionId;
122         mHasMobileData = DataUsageUtils.hasMobileData(mContext);
123         mDataUsageController = null;
124     }
125 
updateConfiguration(Context context, int subscriptionId, SubscriptionInfo subInfo)126     protected void updateConfiguration(Context context,
127             int subscriptionId, SubscriptionInfo subInfo) {
128         final NetworkPolicyManager policyManager =
129                 context.getSystemService(NetworkPolicyManager.class);
130         mPolicyEditor = new NetworkPolicyEditor(policyManager);
131 
132         mDataUsageController = createDataUsageController(context);
133         mDataUsageController.setSubscriptionId(subscriptionId);
134         mDataInfoController = new DataUsageInfoController();
135 
136         if (subInfo != null) {
137             mDataUsageTemplate = R.string.cell_data_template;
138             mDefaultTemplate = DataUsageLib.getMobileTemplate(context, subscriptionId);
139         } else if (DataUsageUtils.hasWifiRadio(context)) {
140             mDataUsageTemplate = R.string.wifi_data_template;
141             mDefaultTemplate = new NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI).build();
142         } else {
143             mDataUsageTemplate = R.string.ethernet_data_template;
144             mDefaultTemplate = DataUsageUtils.getDefaultTemplate(context, subscriptionId);
145         }
146     }
147 
148     @VisibleForTesting
createDataUsageController(Context context)149     DataUsageController createDataUsageController(Context context) {
150         return new DataUsageController(context);
151     }
152 
153     @VisibleForTesting
DataUsageSummaryPreferenceController( DataUsageController dataUsageController, DataUsageInfoController dataInfoController, NetworkTemplate defaultTemplate, NetworkPolicyEditor policyEditor, int dataUsageTemplate, Activity activity, Lifecycle lifecycle, EntityHeaderController entityHeaderController, PreferenceFragmentCompat fragment, int subscriptionId)154     DataUsageSummaryPreferenceController(
155             DataUsageController dataUsageController,
156             DataUsageInfoController dataInfoController,
157             NetworkTemplate defaultTemplate,
158             NetworkPolicyEditor policyEditor,
159             int dataUsageTemplate,
160             Activity activity,
161             Lifecycle lifecycle,
162             EntityHeaderController entityHeaderController,
163             PreferenceFragmentCompat fragment,
164             int subscriptionId) {
165         super(activity, KEY);
166         mDataUsageController = dataUsageController;
167         mDataInfoController = dataInfoController;
168         mDefaultTemplate = defaultTemplate;
169         mPolicyEditor = policyEditor;
170         mDataUsageTemplate = dataUsageTemplate;
171         mHasMobileData = true;
172         mLifecycle = lifecycle;
173         mEntityHeaderController = entityHeaderController;
174         mFragment = fragment;
175         mSubId = subscriptionId;
176     }
177 
178     @Override
onStart()179     public void onStart() {
180         if (mEntityHeaderController == null) {
181             mEntityHeaderController =
182                     EntityHeaderController.newInstance((Activity) mContext, mFragment, null);
183         }
184         RecyclerView view = mFragment.getListView();
185         mEntityHeaderController.setRecyclerView(view, mLifecycle);
186     }
187 
188     @VisibleForTesting
getSubscriptionPlans(int subscriptionId)189     List<SubscriptionPlan> getSubscriptionPlans(int subscriptionId) {
190         return ProxySubscriptionManager.getInstance(mContext).get()
191                 .getSubscriptionPlans(subscriptionId);
192     }
193 
getSubscriptionInfo(int subscriptionId)194     protected SubscriptionInfo getSubscriptionInfo(int subscriptionId) {
195         if (!mHasMobileData) {
196             return null;
197         }
198         return ProxySubscriptionManager.getInstance(mContext)
199                 .getAccessibleSubscriptionInfo(subscriptionId);
200     }
201 
202     @Override
getAvailabilityStatus(int subId)203     public int getAvailabilityStatus(int subId) {
204         return (getSubscriptionInfo(subId) != null)
205                 || DataUsageUtils.hasWifiRadio(mContext) ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
206     }
207 
208     @Override
updateState(Preference preference)209     public void updateState(Preference preference) {
210         DataUsageSummaryPreference summaryPreference = (DataUsageSummaryPreference) preference;
211 
212         final SubscriptionInfo subInfo = getSubscriptionInfo(mSubId);
213         if (mDataUsageController == null) {
214             updateConfiguration(mContext, mSubId, subInfo);
215         }
216 
217         mHistoricalUsageLevel = ThreadUtils.postOnBackgroundThread(() ->
218                 mDataUsageController.getHistoricalUsageLevel(mDefaultTemplate));
219 
220         final DataUsageController.DataUsageInfo info =
221                 mDataUsageController.getDataUsageInfo(mDefaultTemplate);
222 
223         long usageLevel = info.usageLevel;
224 
225         if (subInfo != null) {
226             mDataInfoController.updateDataLimit(info, mPolicyEditor.getPolicy(mDefaultTemplate));
227             summaryPreference.setWifiMode(/* isWifiMode */ false,
228                     /* usagePeriod */ null, /* isSingleWifi */ false);
229         } else {
230             summaryPreference.setWifiMode(/* isWifiMode */ true, /* usagePeriod */
231                     info.period, /* isSingleWifi */ false);
232             summaryPreference.setLimitInfo(null);
233             summaryPreference.setUsageNumbers(displayUsageLevel(usageLevel),
234                     /* dataPlanSize */ -1L,
235                     /* hasMobileData */ true);
236             summaryPreference.setChartEnabled(false);
237             summaryPreference.setUsageInfo(info.cycleEnd,
238                     /* snapshotTime */ -1L,
239                     /* carrierName */ null,
240                     /* numPlans */ 0,
241                     /* launchIntent */ null);
242             return;
243         }
244 
245         refreshDataplanInfo(info, subInfo);
246 
247         if (info.warningLevel > 0 && info.limitLevel > 0) {
248             summaryPreference.setLimitInfo(TextUtils.expandTemplate(
249                     mContext.getText(R.string.cell_data_warning_and_limit),
250                     DataUsageUtils.formatDataUsage(mContext, info.warningLevel),
251                     DataUsageUtils.formatDataUsage(mContext, info.limitLevel)));
252         } else if (info.warningLevel > 0) {
253             summaryPreference.setLimitInfo(TextUtils.expandTemplate(
254                     mContext.getText(R.string.cell_data_warning),
255                     DataUsageUtils.formatDataUsage(mContext, info.warningLevel)));
256         } else if (info.limitLevel > 0) {
257             summaryPreference.setLimitInfo(TextUtils.expandTemplate(
258                     mContext.getText(R.string.cell_data_limit),
259                     DataUsageUtils.formatDataUsage(mContext, info.limitLevel)));
260         } else {
261             summaryPreference.setLimitInfo(null);
262         }
263 
264         if ((mDataplanUse <= 0L) && (mSnapshotTime < 0)) {
265             Log.d(TAG, "Display data usage from history");
266             mDataplanUse = displayUsageLevel(usageLevel);
267             mSnapshotTime = -1L;
268         }
269 
270         summaryPreference.setUsageNumbers(mDataplanUse, mDataplanSize, mHasMobileData);
271 
272         if (mDataBarSize <= 0) {
273             summaryPreference.setChartEnabled(false);
274         } else {
275             summaryPreference.setChartEnabled(true);
276             summaryPreference.setLabels(DataUsageUtils.formatDataUsage(mContext, 0 /* sizeBytes */),
277                     DataUsageUtils.formatDataUsage(mContext, mDataBarSize));
278             summaryPreference.setProgress(mDataplanUse / (float) mDataBarSize);
279         }
280         summaryPreference.setUsageInfo(mCycleEnd, mSnapshotTime, mCarrierName,
281                 mDataplanCount, mManageSubscriptionIntent);
282     }
283 
displayUsageLevel(long usageLevel)284     private long displayUsageLevel(long usageLevel) {
285         if (usageLevel > 0) {
286             return usageLevel;
287         }
288         try {
289             usageLevel = mHistoricalUsageLevel.get();
290         } catch (Exception ex) {
291         }
292         return usageLevel;
293     }
294 
295     // TODO(b/70950124) add test for this method once the robolectric shadow run script is
296     // completed (b/3526807)
refreshDataplanInfo(DataUsageController.DataUsageInfo info, SubscriptionInfo subInfo)297     private void refreshDataplanInfo(DataUsageController.DataUsageInfo info,
298             SubscriptionInfo subInfo) {
299         // reset data before overwriting
300         mCarrierName = null;
301         mDataplanCount = 0;
302         mDataplanSize = -1L;
303         mDataBarSize = mDataInfoController.getSummaryLimit(info);
304         mDataplanUse = info.usageLevel;
305         mCycleStart = info.cycleStart;
306         mCycleEnd = info.cycleEnd;
307         mSnapshotTime = -1L;
308 
309         if (subInfo != null && mHasMobileData) {
310             mCarrierName = subInfo.getCarrierName();
311             final List<SubscriptionPlan> plans = getSubscriptionPlans(mSubId);
312             final SubscriptionPlan primaryPlan = getPrimaryPlan(plans);
313 
314             if (primaryPlan != null) {
315                 mDataplanCount = plans.size();
316                 mDataplanSize = primaryPlan.getDataLimitBytes();
317                 if (unlimited(mDataplanSize)) {
318                     mDataplanSize = -1L;
319                 }
320                 mDataBarSize = mDataplanSize;
321                 mDataplanUse = primaryPlan.getDataUsageBytes();
322 
323                 RecurrenceRule rule = primaryPlan.getCycleRule();
324                 if (rule != null && rule.start != null && rule.end != null) {
325                     mCycleStart = rule.start.toEpochSecond() * 1000L;
326                     mCycleEnd = rule.end.toEpochSecond() * 1000L;
327                 }
328                 mSnapshotTime = primaryPlan.getDataUsageTime();
329             }
330         }
331         // Temporarily return null, since no current users of SubscriptionPlan have this intent set.
332         // TODO (b/170330084): Remove after refactoring 5G SubscriptionPlan logic.
333         // mManageSubscriptionIntent = createManageSubscriptionIntent(mSubId);
334         mManageSubscriptionIntent = null;
335         Log.i(TAG, "Have " + mDataplanCount + " plans, dflt sub-id " + mSubId
336                 + ", intent " + mManageSubscriptionIntent);
337     }
338 
339     /**
340      * Create an {@link Intent} that can be launched towards the carrier app
341      * that is currently defining the billing relationship plan through
342      * {@link INetworkPolicyManager#setSubscriptionPlans(int, SubscriptionPlan [], String)}.
343      *
344      * @return ready to launch Intent targeted towards the carrier app, or
345      *         {@code null} if no carrier app is defined, or if the defined
346      *         carrier app provides no management activity.
347      */
348     @VisibleForTesting
createManageSubscriptionIntent(int subId)349     Intent createManageSubscriptionIntent(int subId) {
350         final INetworkPolicyManager iNetPolicyManager = INetworkPolicyManager.Stub.asInterface(
351                 ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
352         String owner = "";
353         try {
354             owner = iNetPolicyManager.getSubscriptionPlansOwner(subId);
355         } catch (Exception ex) {
356             Log.w(TAG, "Fail to get subscription plan owner for subId " + subId, ex);
357         }
358 
359         if (TextUtils.isEmpty(owner)) {
360             return null;
361         }
362 
363         final List<SubscriptionPlan> plans = getSubscriptionPlans(subId);
364         if (plans.isEmpty()) {
365             return null;
366         }
367 
368         final Intent intent = new Intent(SubscriptionManager.ACTION_MANAGE_SUBSCRIPTION_PLANS);
369         intent.setPackage(owner);
370         intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
371 
372         if (mContext.getPackageManager().queryIntentActivities(intent,
373                 PackageManager.MATCH_DEFAULT_ONLY).isEmpty()) {
374             return null;
375         }
376 
377         return intent;
378     }
379 
getPrimaryPlan(List<SubscriptionPlan> plans)380     private static SubscriptionPlan getPrimaryPlan(List<SubscriptionPlan> plans) {
381         if (CollectionUtils.isEmpty(plans)) {
382             return null;
383         }
384         // First plan in the list is the primary plan
385         SubscriptionPlan plan = plans.get(0);
386         return plan.getDataLimitBytes() > 0
387                 && validSize(plan.getDataUsageBytes())
388                 && plan.getCycleRule() != null ? plan : null;
389     }
390 
validSize(long value)391     private static boolean validSize(long value) {
392         return value >= 0L && value < PETA;
393     }
394 
unlimited(long size)395     public static boolean unlimited(long size) {
396         return size == SubscriptionPlan.BYTES_UNLIMITED;
397     }
398 }
399