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 android.content.Context;
20 import android.net.ConnectivityManager;
21 import android.net.INetworkStatsService;
22 import android.net.INetworkStatsSession;
23 import android.net.NetworkPolicy;
24 import android.net.NetworkPolicyManager;
25 import android.net.NetworkStatsHistory;
26 import android.net.NetworkTemplate;
27 import android.os.RemoteException;
28 import android.os.ServiceManager;
29 import android.telephony.SubscriptionManager;
30 import android.telephony.TelephonyManager;
31 import android.text.format.DateUtils;
32 import android.text.format.Time;
33 import android.util.Log;
34 
35 import java.util.Date;
36 import java.util.Locale;
37 
38 import static android.net.ConnectivityManager.TYPE_MOBILE;
39 import static android.net.NetworkStatsHistory.FIELD_RX_BYTES;
40 import static android.net.NetworkStatsHistory.FIELD_TX_BYTES;
41 import static android.telephony.TelephonyManager.SIM_STATE_READY;
42 import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
43 import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
44 
45 public class DataUsageController {
46     private static final String TAG = "DataUsageController";
47     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
48 
49     public static final long DEFAULT_WARNING_LEVEL = 2L * 1024 * 1024 * 1024;
50     private static final int FIELDS = FIELD_RX_BYTES | FIELD_TX_BYTES;
51     private static final StringBuilder PERIOD_BUILDER = new StringBuilder(50);
52     private static final java.util.Formatter PERIOD_FORMATTER = new java.util.Formatter(
53             PERIOD_BUILDER, Locale.getDefault());
54 
55     private final Context mContext;
56     private final TelephonyManager mTelephonyManager;
57     private final ConnectivityManager mConnectivityManager;
58     private final INetworkStatsService mStatsService;
59     private final NetworkPolicyManager mPolicyManager;
60 
61     private INetworkStatsSession mSession;
62     private Callback mCallback;
63     private NetworkNameProvider mNetworkController;
64 
DataUsageController(Context context)65     public DataUsageController(Context context) {
66         mContext = context;
67         mTelephonyManager = TelephonyManager.from(context);
68         mConnectivityManager = ConnectivityManager.from(context);
69         mStatsService = INetworkStatsService.Stub.asInterface(
70                 ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
71         mPolicyManager = NetworkPolicyManager.from(mContext);
72     }
73 
setNetworkController(NetworkNameProvider networkController)74     public void setNetworkController(NetworkNameProvider networkController) {
75         mNetworkController = networkController;
76     }
77 
getSession()78     private INetworkStatsSession getSession() {
79         if (mSession == null) {
80             try {
81                 mSession = mStatsService.openSession();
82             } catch (RemoteException e) {
83                 Log.w(TAG, "Failed to open stats session", e);
84             } catch (RuntimeException e) {
85                 Log.w(TAG, "Failed to open stats session", e);
86             }
87         }
88         return mSession;
89     }
90 
setCallback(Callback callback)91     public void setCallback(Callback callback) {
92         mCallback = callback;
93     }
94 
warn(String msg)95     private DataUsageInfo warn(String msg) {
96         Log.w(TAG, "Failed to get data usage, " + msg);
97         return null;
98     }
99 
addMonth(Time t, int months)100     private static Time addMonth(Time t, int months) {
101         final Time rt = new Time(t);
102         rt.set(t.monthDay, t.month + months, t.year);
103         rt.normalize(false);
104         return rt;
105     }
106 
getDataUsageInfo()107     public DataUsageInfo getDataUsageInfo() {
108         final String subscriberId = getActiveSubscriberId(mContext);
109         if (subscriberId == null) {
110             return warn("no subscriber id");
111         }
112         NetworkTemplate template = NetworkTemplate.buildTemplateMobileAll(subscriberId);
113         template = NetworkTemplate.normalize(template, mTelephonyManager.getMergedSubscriberIds());
114 
115         return getDataUsageInfo(template);
116     }
117 
getWifiDataUsageInfo()118     public DataUsageInfo getWifiDataUsageInfo() {
119         NetworkTemplate template = NetworkTemplate.buildTemplateWifiWildcard();
120         return getDataUsageInfo(template);
121     }
122 
getDataUsageInfo(NetworkTemplate template)123     public DataUsageInfo getDataUsageInfo(NetworkTemplate template) {
124         final INetworkStatsSession session = getSession();
125         if (session == null) {
126             return warn("no stats session");
127         }
128         final NetworkPolicy policy = findNetworkPolicy(template);
129         try {
130             final NetworkStatsHistory history = session.getHistoryForNetwork(template, FIELDS);
131             final long now = System.currentTimeMillis();
132             final long start, end;
133             if (policy != null && policy.cycleDay > 0) {
134                 // period = determined from cycleDay
135                 if (DEBUG) Log.d(TAG, "Cycle day=" + policy.cycleDay + " tz="
136                         + policy.cycleTimezone);
137                 final Time nowTime = new Time(policy.cycleTimezone);
138                 nowTime.setToNow();
139                 final Time policyTime = new Time(nowTime);
140                 policyTime.set(policy.cycleDay, policyTime.month, policyTime.year);
141                 policyTime.normalize(false);
142                 if (nowTime.after(policyTime)) {
143                     start = policyTime.toMillis(false);
144                     end = addMonth(policyTime, 1).toMillis(false);
145                 } else {
146                     start = addMonth(policyTime, -1).toMillis(false);
147                     end = policyTime.toMillis(false);
148                 }
149             } else {
150                 // period = last 4 wks
151                 end = now;
152                 start = now - DateUtils.WEEK_IN_MILLIS * 4;
153             }
154             final long callStart = System.currentTimeMillis();
155             final NetworkStatsHistory.Entry entry = history.getValues(start, end, now, null);
156             final long callEnd = System.currentTimeMillis();
157             if (DEBUG) Log.d(TAG, String.format("history call from %s to %s now=%s took %sms: %s",
158                     new Date(start), new Date(end), new Date(now), callEnd - callStart,
159                     historyEntryToString(entry)));
160             if (entry == null) {
161                 return warn("no entry data");
162             }
163             final long totalBytes = entry.rxBytes + entry.txBytes;
164             final DataUsageInfo usage = new DataUsageInfo();
165             usage.startDate = start;
166             usage.usageLevel = totalBytes;
167             usage.period = formatDateRange(start, end);
168             if (policy != null) {
169                 usage.limitLevel = policy.limitBytes > 0 ? policy.limitBytes : 0;
170                 usage.warningLevel = policy.warningBytes > 0 ? policy.warningBytes : 0;
171             } else {
172                 usage.warningLevel = DEFAULT_WARNING_LEVEL;
173             }
174             if (usage != null && mNetworkController != null) {
175                 usage.carrier = mNetworkController.getMobileDataNetworkName();
176             }
177             return usage;
178         } catch (RemoteException e) {
179             return warn("remote call failed");
180         }
181     }
182 
findNetworkPolicy(NetworkTemplate template)183     private NetworkPolicy findNetworkPolicy(NetworkTemplate template) {
184         if (mPolicyManager == null || template == null) return null;
185         final NetworkPolicy[] policies = mPolicyManager.getNetworkPolicies();
186         if (policies == null) return null;
187         final int N = policies.length;
188         for (int i = 0; i < N; i++) {
189             final NetworkPolicy policy = policies[i];
190             if (policy != null && template.equals(policy.template)) {
191                 return policy;
192             }
193         }
194         return null;
195     }
196 
historyEntryToString(NetworkStatsHistory.Entry entry)197     private static String historyEntryToString(NetworkStatsHistory.Entry entry) {
198         return entry == null ? null : new StringBuilder("Entry[")
199                 .append("bucketDuration=").append(entry.bucketDuration)
200                 .append(",bucketStart=").append(entry.bucketStart)
201                 .append(",activeTime=").append(entry.activeTime)
202                 .append(",rxBytes=").append(entry.rxBytes)
203                 .append(",rxPackets=").append(entry.rxPackets)
204                 .append(",txBytes=").append(entry.txBytes)
205                 .append(",txPackets=").append(entry.txPackets)
206                 .append(",operations=").append(entry.operations)
207                 .append(']').toString();
208     }
209 
setMobileDataEnabled(boolean enabled)210     public void setMobileDataEnabled(boolean enabled) {
211         Log.d(TAG, "setMobileDataEnabled: enabled=" + enabled);
212         mTelephonyManager.setDataEnabled(enabled);
213         if (mCallback != null) {
214             mCallback.onMobileDataEnabled(enabled);
215         }
216     }
217 
isMobileDataSupported()218     public boolean isMobileDataSupported() {
219         // require both supported network and ready SIM
220         return mConnectivityManager.isNetworkSupported(TYPE_MOBILE)
221                 && mTelephonyManager.getSimState() == SIM_STATE_READY;
222     }
223 
isMobileDataEnabled()224     public boolean isMobileDataEnabled() {
225         return mTelephonyManager.getDataEnabled();
226     }
227 
getActiveSubscriberId(Context context)228     private static String getActiveSubscriberId(Context context) {
229         final TelephonyManager tele = TelephonyManager.from(context);
230         final String actualSubscriberId = tele.getSubscriberId(
231                 SubscriptionManager.getDefaultDataSubscriptionId());
232         return actualSubscriberId;
233     }
234 
formatDateRange(long start, long end)235     private String formatDateRange(long start, long end) {
236         final int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH;
237         synchronized (PERIOD_BUILDER) {
238             PERIOD_BUILDER.setLength(0);
239             return DateUtils.formatDateRange(mContext, PERIOD_FORMATTER, start, end, flags, null)
240                     .toString();
241         }
242     }
243 
244     public interface NetworkNameProvider {
getMobileDataNetworkName()245         String getMobileDataNetworkName();
246     }
247 
248     public static class DataUsageInfo {
249         public String carrier;
250         public String period;
251         public long startDate;
252         public long limitLevel;
253         public long warningLevel;
254         public long usageLevel;
255     }
256 
257     public interface Callback {
onMobileDataEnabled(boolean enabled)258         void onMobileDataEnabled(boolean enabled);
259     }
260 }
261