1 /*
2  * Copyright (C) 2015 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.settingslib.net;
18 
19 import static android.telephony.TelephonyManager.SIM_STATE_READY;
20 import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
21 import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
22 
23 import android.app.usage.NetworkStats.Bucket;
24 import android.app.usage.NetworkStatsManager;
25 import android.content.Context;
26 import android.net.NetworkPolicy;
27 import android.net.NetworkPolicyManager;
28 import android.net.NetworkTemplate;
29 import android.telephony.SubscriptionManager;
30 import android.telephony.TelephonyManager;
31 import android.text.format.DateUtils;
32 import android.util.Log;
33 import android.util.Range;
34 
35 import com.android.internal.R;
36 import com.android.internal.annotations.VisibleForTesting;
37 import com.android.internal.util.ArrayUtils;
38 
39 import java.time.ZonedDateTime;
40 import java.util.Iterator;
41 import java.util.Locale;
42 
43 public class DataUsageController {
44 
45     private static final String TAG = "DataUsageController";
46     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
47     private static final StringBuilder PERIOD_BUILDER = new StringBuilder(50);
48     private static final java.util.Formatter PERIOD_FORMATTER = new java.util.Formatter(
49             PERIOD_BUILDER, Locale.getDefault());
50     private static final long MB_IN_BYTES = 1024 * 1024;
51 
52     private final Context mContext;
53     private final NetworkPolicyManager mPolicyManager;
54     private final NetworkStatsManager mNetworkStatsManager;
55 
56     private Callback mCallback;
57     private NetworkNameProvider mNetworkController;
58     private int mSubscriptionId;
59 
DataUsageController(Context context)60     public DataUsageController(Context context) {
61         mContext = context;
62         mPolicyManager = NetworkPolicyManager.from(mContext);
63         mNetworkStatsManager = context.getSystemService(NetworkStatsManager.class);
64         mSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
65     }
66 
setNetworkController(NetworkNameProvider networkController)67     public void setNetworkController(NetworkNameProvider networkController) {
68         mNetworkController = networkController;
69     }
70 
71     /**
72      * By default this class will just get data usage information for the default data subscription,
73      * but this method can be called to require it to use an explicit subscription id which may be
74      * different from the default one (this is useful for the case of multi-SIM devices).
75      */
setSubscriptionId(int subscriptionId)76     public void setSubscriptionId(int subscriptionId) {
77         mSubscriptionId = subscriptionId;
78     }
79 
80     /**
81      * Returns the default warning level in bytes.
82      */
getDefaultWarningLevel()83     public long getDefaultWarningLevel() {
84         return MB_IN_BYTES
85                 * mContext.getResources().getInteger(R.integer.default_data_warning_level_mb);
86     }
87 
setCallback(Callback callback)88     public void setCallback(Callback callback) {
89         mCallback = callback;
90     }
91 
warn(String msg)92     private DataUsageInfo warn(String msg) {
93         Log.w(TAG, "Failed to get data usage, " + msg);
94         return null;
95     }
96 
getDataUsageInfo(NetworkTemplate template)97     public DataUsageInfo getDataUsageInfo(NetworkTemplate template) {
98         final NetworkPolicy policy = findNetworkPolicy(template);
99         final long now = System.currentTimeMillis();
100         final long start, end;
101         final Iterator<Range<ZonedDateTime>> it = (policy != null) ? policy.cycleIterator() : null;
102         if (it != null && it.hasNext()) {
103             final Range<ZonedDateTime> cycle = it.next();
104             start = cycle.getLower().toInstant().toEpochMilli();
105             end = cycle.getUpper().toInstant().toEpochMilli();
106         } else {
107             // period = last 4 wks
108             end = now;
109             start = now - DateUtils.WEEK_IN_MILLIS * 4;
110         }
111         final long totalBytes = getUsageLevel(template, start, end);
112         if (totalBytes < 0L) {
113             return warn("no entry data");
114         }
115         final DataUsageInfo usage = new DataUsageInfo();
116         usage.startDate = start;
117         usage.usageLevel = totalBytes;
118         usage.period = formatDateRange(start, end);
119         usage.cycleStart = start;
120         usage.cycleEnd = end;
121 
122         if (policy != null) {
123             usage.limitLevel = policy.limitBytes > 0 ? policy.limitBytes : 0;
124             usage.warningLevel = policy.warningBytes > 0 ? policy.warningBytes : 0;
125         } else {
126             usage.warningLevel = getDefaultWarningLevel();
127         }
128         if (mNetworkController != null) {
129             usage.carrier = mNetworkController.getMobileDataNetworkName();
130         }
131         return usage;
132     }
133 
134     /**
135      * Get the total usage level recorded in the network history
136      * @param template the network template to retrieve the network history
137      * @return the total usage level recorded in the network history or -1L if there is error
138      * retrieving the data.
139      */
getHistoricalUsageLevel(NetworkTemplate template)140     public long getHistoricalUsageLevel(NetworkTemplate template) {
141         return getUsageLevel(template, 0L /* start */, System.currentTimeMillis() /* end */);
142     }
143 
getUsageLevel(NetworkTemplate template, long start, long end)144     private long getUsageLevel(NetworkTemplate template, long start, long end) {
145         try {
146             final Bucket bucket = mNetworkStatsManager.querySummaryForDevice(template, start, end);
147             if (bucket != null) {
148                 return bucket.getRxBytes() + bucket.getTxBytes();
149             }
150             Log.w(TAG, "Failed to get data usage, no entry data");
151         } catch (RuntimeException e) {
152             Log.w(TAG, "Failed to get data usage, remote call failed");
153         }
154         return -1L;
155     }
156 
findNetworkPolicy(NetworkTemplate template)157     private NetworkPolicy findNetworkPolicy(NetworkTemplate template) {
158         if (mPolicyManager == null || template == null) return null;
159         final NetworkPolicy[] policies = mPolicyManager.getNetworkPolicies();
160         if (policies == null) return null;
161         for (final NetworkPolicy policy : policies) {
162             if (policy != null && template.equals(policy.template)) {
163                 return policy;
164             }
165         }
166         return null;
167     }
168 
169     @VisibleForTesting
getTelephonyManager()170     public TelephonyManager getTelephonyManager() {
171         int subscriptionId = mSubscriptionId;
172 
173         // If mSubscriptionId is invalid, get default data sub.
174         if (!SubscriptionManager.isValidSubscriptionId(subscriptionId)) {
175             subscriptionId = SubscriptionManager.getDefaultDataSubscriptionId();
176         }
177 
178         // If data sub is also invalid, get any active sub.
179         if (!SubscriptionManager.isValidSubscriptionId(subscriptionId)) {
180             int[] activeSubIds = SubscriptionManager.from(mContext).getActiveSubscriptionIdList();
181             if (!ArrayUtils.isEmpty(activeSubIds)) {
182                 subscriptionId = activeSubIds[0];
183             }
184         }
185 
186         return mContext.getSystemService(TelephonyManager.class)
187                 .createForSubscriptionId(subscriptionId);
188     }
189 
setMobileDataEnabled(boolean enabled)190     public void setMobileDataEnabled(boolean enabled) {
191         Log.d(TAG, "setMobileDataEnabled: enabled=" + enabled);
192         getTelephonyManager().setDataEnabled(enabled);
193         if (mCallback != null) {
194             mCallback.onMobileDataEnabled(enabled);
195         }
196     }
197 
isMobileDataSupported()198     public boolean isMobileDataSupported() {
199         // require both supported network and ready SIM
200         return getTelephonyManager().isDataCapable()
201                 && getTelephonyManager().getSimState() == SIM_STATE_READY;
202     }
203 
isMobileDataEnabled()204     public boolean isMobileDataEnabled() {
205         return getTelephonyManager().isDataEnabled();
206     }
207 
formatDateRange(long start, long end)208     private String formatDateRange(long start, long end) {
209         final int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH;
210         synchronized (PERIOD_BUILDER) {
211             PERIOD_BUILDER.setLength(0);
212             return DateUtils.formatDateRange(mContext, PERIOD_FORMATTER, start, end, flags, null)
213                     .toString();
214         }
215     }
216 
217     public interface NetworkNameProvider {
getMobileDataNetworkName()218         String getMobileDataNetworkName();
219     }
220 
221     public static class DataUsageInfo {
222         public String carrier;
223         public String period;
224         public long startDate;
225         public long limitLevel;
226         public long warningLevel;
227         public long usageLevel;
228         public long cycleStart;
229         public long cycleEnd;
230     }
231 
232     public interface Callback {
onMobileDataEnabled(boolean enabled)233         void onMobileDataEnabled(boolean enabled);
234     }
235 }
236