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.buildTemplateWifi(
142                     NetworkTemplate.WIFI_NETWORKID_ALL, null /* subscriberId */);
143         } else {
144             mDataUsageTemplate = R.string.ethernet_data_template;
145             mDefaultTemplate = DataUsageUtils.getDefaultTemplate(context, subscriptionId);
146         }
147     }
148 
149     @VisibleForTesting
DataUsageSummaryPreferenceController( DataUsageController dataUsageController, DataUsageInfoController dataInfoController, NetworkTemplate defaultTemplate, NetworkPolicyEditor policyEditor, int dataUsageTemplate, Activity activity, Lifecycle lifecycle, EntityHeaderController entityHeaderController, PreferenceFragmentCompat fragment, int subscriptionId)150     DataUsageSummaryPreferenceController(
151             DataUsageController dataUsageController,
152             DataUsageInfoController dataInfoController,
153             NetworkTemplate defaultTemplate,
154             NetworkPolicyEditor policyEditor,
155             int dataUsageTemplate,
156             Activity activity,
157             Lifecycle lifecycle,
158             EntityHeaderController entityHeaderController,
159             PreferenceFragmentCompat fragment,
160             int subscriptionId) {
161         super(activity, KEY);
162         mDataUsageController = dataUsageController;
163         mDataInfoController = dataInfoController;
164         mDefaultTemplate = defaultTemplate;
165         mPolicyEditor = policyEditor;
166         mDataUsageTemplate = dataUsageTemplate;
167         mHasMobileData = true;
168         mLifecycle = lifecycle;
169         mEntityHeaderController = entityHeaderController;
170         mFragment = fragment;
171         mSubId = subscriptionId;
172     }
173 
174     @Override
onStart()175     public void onStart() {
176         if (mEntityHeaderController == null) {
177             mEntityHeaderController =
178                     EntityHeaderController.newInstance((Activity) mContext, mFragment, null);
179         }
180         RecyclerView view = mFragment.getListView();
181         mEntityHeaderController.setRecyclerView(view, mLifecycle);
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         // Temporarily return null, since no current users of SubscriptionPlan have this intent set.
329         // TODO (b/170330084): Remove after refactoring 5G SubscriptionPlan logic.
330         // mManageSubscriptionIntent = createManageSubscriptionIntent(mSubId);
331         mManageSubscriptionIntent = null;
332         Log.i(TAG, "Have " + mDataplanCount + " plans, dflt sub-id " + mSubId
333                 + ", intent " + mManageSubscriptionIntent);
334     }
335 
336     /**
337      * Create an {@link Intent} that can be launched towards the carrier app
338      * that is currently defining the billing relationship plan through
339      * {@link INetworkPolicyManager#setSubscriptionPlans(int, SubscriptionPlan [], String)}.
340      *
341      * @return ready to launch Intent targeted towards the carrier app, or
342      *         {@code null} if no carrier app is defined, or if the defined
343      *         carrier app provides no management activity.
344      */
345     @VisibleForTesting
createManageSubscriptionIntent(int subId)346     Intent createManageSubscriptionIntent(int subId) {
347         final INetworkPolicyManager iNetPolicyManager = INetworkPolicyManager.Stub.asInterface(
348                 ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
349         String owner = "";
350         try {
351             owner = iNetPolicyManager.getSubscriptionPlansOwner(subId);
352         } catch (Exception ex) {
353             Log.w(TAG, "Fail to get subscription plan owner for subId " + subId, ex);
354         }
355 
356         if (TextUtils.isEmpty(owner)) {
357             return null;
358         }
359 
360         final List<SubscriptionPlan> plans = getSubscriptionPlans(subId);
361         if (plans.isEmpty()) {
362             return null;
363         }
364 
365         final Intent intent = new Intent(SubscriptionManager.ACTION_MANAGE_SUBSCRIPTION_PLANS);
366         intent.setPackage(owner);
367         intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
368 
369         if (mContext.getPackageManager().queryIntentActivities(intent,
370                 PackageManager.MATCH_DEFAULT_ONLY).isEmpty()) {
371             return null;
372         }
373 
374         return intent;
375     }
376 
getPrimaryPlan(List<SubscriptionPlan> plans)377     private static SubscriptionPlan getPrimaryPlan(List<SubscriptionPlan> plans) {
378         if (CollectionUtils.isEmpty(plans)) {
379             return null;
380         }
381         // First plan in the list is the primary plan
382         SubscriptionPlan plan = plans.get(0);
383         return plan.getDataLimitBytes() > 0
384                 && validSize(plan.getDataUsageBytes())
385                 && plan.getCycleRule() != null ? plan : null;
386     }
387 
validSize(long value)388     private static boolean validSize(long value) {
389         return value >= 0L && value < PETA;
390     }
391 
unlimited(long size)392     public static boolean unlimited(long size) {
393         return size == SubscriptionPlan.BYTES_UNLIMITED;
394     }
395 }
396