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     private 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 = new DataUsageController(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 = NetworkTemplate.buildTemplateWifiWildcard();
142         } else {
143             mDataUsageTemplate = R.string.ethernet_data_template;
144             mDefaultTemplate = DataUsageUtils.getDefaultTemplate(context, subscriptionId);
145         }
146     }
147 
148     @VisibleForTesting
DataUsageSummaryPreferenceController( DataUsageController dataUsageController, DataUsageInfoController dataInfoController, NetworkTemplate defaultTemplate, NetworkPolicyEditor policyEditor, int dataUsageTemplate, Activity activity, Lifecycle lifecycle, EntityHeaderController entityHeaderController, PreferenceFragmentCompat fragment, int subscriptionId)149     DataUsageSummaryPreferenceController(
150             DataUsageController dataUsageController,
151             DataUsageInfoController dataInfoController,
152             NetworkTemplate defaultTemplate,
153             NetworkPolicyEditor policyEditor,
154             int dataUsageTemplate,
155             Activity activity,
156             Lifecycle lifecycle,
157             EntityHeaderController entityHeaderController,
158             PreferenceFragmentCompat fragment,
159             int subscriptionId) {
160         super(activity, KEY);
161         mDataUsageController = dataUsageController;
162         mDataInfoController = dataInfoController;
163         mDefaultTemplate = defaultTemplate;
164         mPolicyEditor = policyEditor;
165         mDataUsageTemplate = dataUsageTemplate;
166         mHasMobileData = true;
167         mLifecycle = lifecycle;
168         mEntityHeaderController = entityHeaderController;
169         mFragment = fragment;
170         mSubId = subscriptionId;
171     }
172 
173     @Override
onStart()174     public void onStart() {
175         if (mEntityHeaderController == null) {
176             mEntityHeaderController =
177                     EntityHeaderController.newInstance((Activity) mContext, mFragment, null);
178         }
179         RecyclerView view = mFragment.getListView();
180         mEntityHeaderController.setRecyclerView(view, mLifecycle);
181         mEntityHeaderController.styleActionBar((Activity) mContext);
182     }
183 
184     @VisibleForTesting
getSubscriptionPlans(int subscriptionId)185     List<SubscriptionPlan> getSubscriptionPlans(int subscriptionId) {
186         return ProxySubscriptionManager.getInstance(mContext).get()
187                 .getSubscriptionPlans(subscriptionId);
188     }
189 
190     @VisibleForTesting
getSubscriptionInfo(int subscriptionId)191     SubscriptionInfo getSubscriptionInfo(int subscriptionId) {
192         if (!mHasMobileData) {
193             return null;
194         }
195         return ProxySubscriptionManager.getInstance(mContext)
196                 .getAccessibleSubscriptionInfo(subscriptionId);
197     }
198 
199     @Override
getAvailabilityStatus(int subId)200     public int getAvailabilityStatus(int subId) {
201         return (getSubscriptionInfo(subId) != null)
202                 || DataUsageUtils.hasWifiRadio(mContext) ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
203     }
204 
205     @Override
updateState(Preference preference)206     public void updateState(Preference preference) {
207         DataUsageSummaryPreference summaryPreference = (DataUsageSummaryPreference) preference;
208 
209         final SubscriptionInfo subInfo = getSubscriptionInfo(mSubId);
210         if (mDataUsageController == null) {
211             updateConfiguration(mContext, mSubId, subInfo);
212         }
213 
214         mHistoricalUsageLevel = ThreadUtils.postOnBackgroundThread(() ->
215                 mDataUsageController.getHistoricalUsageLevel(mDefaultTemplate));
216 
217         final DataUsageController.DataUsageInfo info =
218                 mDataUsageController.getDataUsageInfo(mDefaultTemplate);
219 
220         long usageLevel = info.usageLevel;
221 
222         if (subInfo != null) {
223             mDataInfoController.updateDataLimit(info, mPolicyEditor.getPolicy(mDefaultTemplate));
224             summaryPreference.setWifiMode(/* isWifiMode */ false,
225                     /* usagePeriod */ null, /* isSingleWifi */ false);
226         } else {
227             summaryPreference.setWifiMode(/* isWifiMode */ true, /* usagePeriod */
228                     info.period, /* isSingleWifi */ false);
229             summaryPreference.setLimitInfo(null);
230             summaryPreference.setUsageNumbers(displayUsageLevel(usageLevel),
231                     /* dataPlanSize */ -1L,
232                     /* hasMobileData */ true);
233             summaryPreference.setChartEnabled(false);
234             summaryPreference.setUsageInfo(info.cycleEnd,
235                     /* snapshotTime */ -1L,
236                     /* carrierName */ null,
237                     /* numPlans */ 0,
238                     /* launchIntent */ null);
239             return;
240         }
241 
242         refreshDataplanInfo(info, subInfo);
243 
244         if (info.warningLevel > 0 && info.limitLevel > 0) {
245             summaryPreference.setLimitInfo(TextUtils.expandTemplate(
246                     mContext.getText(R.string.cell_data_warning_and_limit),
247                     DataUsageUtils.formatDataUsage(mContext, info.warningLevel),
248                     DataUsageUtils.formatDataUsage(mContext, info.limitLevel)));
249         } else if (info.warningLevel > 0) {
250             summaryPreference.setLimitInfo(TextUtils.expandTemplate(
251                     mContext.getText(R.string.cell_data_warning),
252                     DataUsageUtils.formatDataUsage(mContext, info.warningLevel)));
253         } else if (info.limitLevel > 0) {
254             summaryPreference.setLimitInfo(TextUtils.expandTemplate(
255                     mContext.getText(R.string.cell_data_limit),
256                     DataUsageUtils.formatDataUsage(mContext, info.limitLevel)));
257         } else {
258             summaryPreference.setLimitInfo(null);
259         }
260 
261         if ((mDataplanUse <= 0L) && (mSnapshotTime < 0)) {
262             Log.d(TAG, "Display data usage from history");
263             mDataplanUse = displayUsageLevel(usageLevel);
264             mSnapshotTime = -1L;
265         }
266 
267         summaryPreference.setUsageNumbers(mDataplanUse, mDataplanSize, mHasMobileData);
268 
269         if (mDataBarSize <= 0) {
270             summaryPreference.setChartEnabled(false);
271         } else {
272             summaryPreference.setChartEnabled(true);
273             summaryPreference.setLabels(DataUsageUtils.formatDataUsage(mContext, 0 /* sizeBytes */),
274                     DataUsageUtils.formatDataUsage(mContext, mDataBarSize));
275             summaryPreference.setProgress(mDataplanUse / (float) mDataBarSize);
276         }
277         summaryPreference.setUsageInfo(mCycleEnd, mSnapshotTime, mCarrierName,
278                 mDataplanCount, mManageSubscriptionIntent);
279     }
280 
displayUsageLevel(long usageLevel)281     private long displayUsageLevel(long usageLevel) {
282         if (usageLevel > 0) {
283             return usageLevel;
284         }
285         try {
286             usageLevel = mHistoricalUsageLevel.get();
287         } catch (Exception ex) {
288         }
289         return usageLevel;
290     }
291 
292     // TODO(b/70950124) add test for this method once the robolectric shadow run script is
293     // completed (b/3526807)
refreshDataplanInfo(DataUsageController.DataUsageInfo info, SubscriptionInfo subInfo)294     private void refreshDataplanInfo(DataUsageController.DataUsageInfo info,
295             SubscriptionInfo subInfo) {
296         // reset data before overwriting
297         mCarrierName = null;
298         mDataplanCount = 0;
299         mDataplanSize = -1L;
300         mDataBarSize = mDataInfoController.getSummaryLimit(info);
301         mDataplanUse = info.usageLevel;
302         mCycleStart = info.cycleStart;
303         mCycleEnd = info.cycleEnd;
304         mSnapshotTime = -1L;
305 
306         if (subInfo != null && mHasMobileData) {
307             mCarrierName = subInfo.getCarrierName();
308             final List<SubscriptionPlan> plans = getSubscriptionPlans(mSubId);
309             final SubscriptionPlan primaryPlan = getPrimaryPlan(plans);
310 
311             if (primaryPlan != null) {
312                 mDataplanCount = plans.size();
313                 mDataplanSize = primaryPlan.getDataLimitBytes();
314                 if (unlimited(mDataplanSize)) {
315                     mDataplanSize = -1L;
316                 }
317                 mDataBarSize = mDataplanSize;
318                 mDataplanUse = primaryPlan.getDataUsageBytes();
319 
320                 RecurrenceRule rule = primaryPlan.getCycleRule();
321                 if (rule != null && rule.start != null && rule.end != null) {
322                     mCycleStart = rule.start.toEpochSecond() * 1000L;
323                     mCycleEnd = rule.end.toEpochSecond() * 1000L;
324                 }
325                 mSnapshotTime = primaryPlan.getDataUsageTime();
326             }
327         }
328         mManageSubscriptionIntent = createManageSubscriptionIntent(mSubId);
329         Log.i(TAG, "Have " + mDataplanCount + " plans, dflt sub-id " + mSubId
330                 + ", intent " + mManageSubscriptionIntent);
331     }
332 
333     /**
334      * Create an {@link Intent} that can be launched towards the carrier app
335      * that is currently defining the billing relationship plan through
336      * {@link INetworkPolicyManager#setSubscriptionPlans(int, SubscriptionPlan [], String)}.
337      *
338      * @return ready to launch Intent targeted towards the carrier app, or
339      *         {@code null} if no carrier app is defined, or if the defined
340      *         carrier app provides no management activity.
341      */
342     @VisibleForTesting
createManageSubscriptionIntent(int subId)343     Intent createManageSubscriptionIntent(int subId) {
344         final INetworkPolicyManager iNetPolicyManager = INetworkPolicyManager.Stub.asInterface(
345                 ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
346         String owner = "";
347         try {
348             owner = iNetPolicyManager.getSubscriptionPlansOwner(subId);
349         } catch (Exception ex) {
350             Log.w(TAG, "Fail to get subscription plan owner for subId " + subId, ex);
351         }
352 
353         if (TextUtils.isEmpty(owner)) {
354             return null;
355         }
356 
357         final List<SubscriptionPlan> plans = getSubscriptionPlans(subId);
358         if (plans.isEmpty()) {
359             return null;
360         }
361 
362         final Intent intent = new Intent(SubscriptionManager.ACTION_MANAGE_SUBSCRIPTION_PLANS);
363         intent.setPackage(owner);
364         intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
365 
366         if (mContext.getPackageManager().queryIntentActivities(intent,
367                 PackageManager.MATCH_DEFAULT_ONLY).isEmpty()) {
368             return null;
369         }
370 
371         return intent;
372     }
373 
getPrimaryPlan(List<SubscriptionPlan> plans)374     private static SubscriptionPlan getPrimaryPlan(List<SubscriptionPlan> plans) {
375         if (CollectionUtils.isEmpty(plans)) {
376             return null;
377         }
378         // First plan in the list is the primary plan
379         SubscriptionPlan plan = plans.get(0);
380         return plan.getDataLimitBytes() > 0
381                 && saneSize(plan.getDataUsageBytes())
382                 && plan.getCycleRule() != null ? plan : null;
383     }
384 
saneSize(long value)385     private static boolean saneSize(long value) {
386         return value >= 0L && value < PETA;
387     }
388 
unlimited(long size)389     public static boolean unlimited(long size) {
390         return size == SubscriptionPlan.BYTES_UNLIMITED;
391     }
392 }
393