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.googlecode.android_scripting.facade;
18 
19 import static android.net.ConnectivityManager.TYPE_MOBILE;
20 import static android.net.NetworkStats.SET_ALL;
21 import static android.net.NetworkStatsHistory.FIELD_RX_BYTES;
22 import static android.net.NetworkStatsHistory.FIELD_TX_BYTES;
23 import static android.telephony.TelephonyManager.SIM_STATE_READY;
24 import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
25 import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
26 import static android.text.format.DateUtils.FORMAT_SHOW_TIME;
27 import static android.text.format.DateUtils.FORMAT_SHOW_YEAR;
28 
29 import android.content.Context;
30 import android.net.ConnectivityManager;
31 import android.net.INetworkStatsService;
32 import android.net.INetworkStatsSession;
33 import android.net.NetworkPolicy;
34 import android.net.NetworkPolicyManager;
35 import android.net.NetworkStatsHistory;
36 import android.net.NetworkTemplate;
37 import android.os.RemoteException;
38 import android.os.ServiceManager;
39 import android.telephony.TelephonyManager;
40 import android.text.format.DateUtils;
41 import android.util.Log;
42 import android.util.Pair;
43 
44 import java.time.ZonedDateTime;
45 import java.util.Locale;
46 
47 /**
48  * DataUsageController.
49  */
50 public class DataUsageController {
51 
52     private static final String TAG = "DataUsageController";
53     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
54     private static final int FIELDS = FIELD_RX_BYTES | FIELD_TX_BYTES;
55     private static final StringBuilder PERIOD_BUILDER = new StringBuilder(50);
56     private static final java.util.Formatter PERIOD_FORMATTER = new java.util.Formatter(
57             PERIOD_BUILDER, Locale.getDefault());
58 
59     private final Context mContext;
60     private final TelephonyManager mTelephonyManager;
61     private final ConnectivityManager mConnectivityManager;
62     private final INetworkStatsService mStatsService;
63     private final NetworkPolicyManager mPolicyManager;
64 
65     private INetworkStatsSession mSession;
66     private Callback mCallback;
67     private NetworkNameProvider mNetworkController;
68 
DataUsageController(Context context)69     public DataUsageController(Context context) {
70         mContext = context;
71         mTelephonyManager = TelephonyManager.from(context);
72         mConnectivityManager = ConnectivityManager.from(context);
73         mStatsService = INetworkStatsService.Stub.asInterface(
74                 ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
75         mPolicyManager = NetworkPolicyManager.from(mContext);
76     }
77 
setNetworkController(NetworkNameProvider networkController)78     public void setNetworkController(NetworkNameProvider networkController) {
79         mNetworkController = networkController;
80     }
81 
getSession()82     private INetworkStatsSession getSession() {
83         if (mSession == null) {
84             try {
85                 mSession = mStatsService.openSession();
86             } catch (RemoteException e) {
87                 Log.w(TAG, "Failed to open stats session", e);
88             } catch (RuntimeException e) {
89                 Log.w(TAG, "Failed to open stats session", e);
90             }
91         }
92         return mSession;
93     }
94 
setCallback(Callback callback)95     public void setCallback(Callback callback) {
96         mCallback = callback;
97     }
98 
warn(String msg)99     private DataUsageInfo warn(String msg) {
100         Log.w(TAG, "Failed to get data usage, " + msg);
101         return null;
102     }
103 
104     /**
105      * Get mobile data usage info.
106      * @return DataUsageInfo: The Mobile data usage information.
107      */
getMobileDataUsageInfoForSubscriber(String subscriberId)108     public DataUsageInfo getMobileDataUsageInfoForSubscriber(String subscriberId) {
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 
118     /**
119      * Get mobile data usage info.
120      * @return DataUsageInfo: The Mobile data usage information.
121      */
getMobileDataUsageInfoForUid(Integer uId, String subscriberId)122     public DataUsageInfo getMobileDataUsageInfoForUid(Integer uId, String subscriberId) {
123         if (subscriberId == null) {
124             return warn("no subscriber id");
125         }
126         NetworkTemplate template = NetworkTemplate.buildTemplateMobileAll(subscriberId);
127         template = NetworkTemplate.normalize(template, mTelephonyManager.getMergedSubscriberIds());
128 
129         return getDataUsageInfo(template, uId);
130     }
131 
132     /**
133      * Get wifi data usage info.
134      * @return DataUsageInfo: The Wifi data usage information.
135      */
getWifiDataUsageInfo()136     public DataUsageInfo getWifiDataUsageInfo() {
137         NetworkTemplate template = NetworkTemplate.buildTemplateWifiWildcard();
138         return getDataUsageInfo(template);
139     }
140 
141     /**
142      * Get data usage info for a given template.
143      * @param template A given template.
144      * @return DataUsageInfo: The data usage information.
145      */
getDataUsageInfo(NetworkTemplate template)146     public DataUsageInfo getDataUsageInfo(NetworkTemplate template) {
147         return getDataUsageInfo(template, -1);
148     }
149 
150     /**
151      * Get data usage info for a given template.
152      * @param template A given template.
153      * @return DataUsageInfo: The data usage information.
154      */
getDataUsageInfo(NetworkTemplate template, int uId)155     public DataUsageInfo getDataUsageInfo(NetworkTemplate template, int uId) {
156         final INetworkStatsSession session = getSession();
157         if (session == null) {
158             return warn("no stats session");
159         }
160         final NetworkPolicy policy = findNetworkPolicy(template);
161         try {
162             final NetworkStatsHistory mHistory;
163             if (uId == -1) {
164                 mHistory = session.getHistoryForNetwork(template, FIELDS);
165             } else {
166                 mHistory = session.getHistoryForUid(template, uId, SET_ALL, 0, FIELDS);
167             }
168             final long now = System.currentTimeMillis();
169             final long start, end;
170             if (policy != null) {
171                 final Pair<ZonedDateTime, ZonedDateTime> cycle = NetworkPolicyManager
172                         .cycleIterator(policy).next();
173                 start = cycle.first.toInstant().toEpochMilli();
174                 end = cycle.second.toInstant().toEpochMilli();
175             } else {
176                 // period = last 4 wks
177                 end = now;
178                 start = now - DateUtils.WEEK_IN_MILLIS * 4;
179             }
180             //final long callStart = System.currentTimeMillis();
181             final NetworkStatsHistory.Entry entry = mHistory.getValues(start, end, now, null);
182             if (entry == null) {
183                 return warn("no entry data");
184             }
185             final DataUsageInfo usage = new DataUsageInfo();
186             usage.subscriberId = template.getSubscriberId();
187             usage.startEpochMilli = start;
188             usage.endEpochMilli = end;
189             usage.usageLevel = mHistory.getTotalBytes();
190             usage.period = formatDateRange(start, end);
191             usage.cycleStart = DateUtils.formatDateTime(mContext, start,
192                     FORMAT_SHOW_DATE + FORMAT_SHOW_YEAR + FORMAT_SHOW_TIME);
193             usage.cycleEnd = DateUtils.formatDateTime(mContext, end,
194                     FORMAT_SHOW_DATE + FORMAT_SHOW_YEAR + FORMAT_SHOW_TIME);
195 
196             if (policy != null) {
197                 usage.limitLevel = policy.limitBytes > 0 ? policy.limitBytes : -1;
198                 usage.warningLevel = policy.warningBytes > 0 ? policy.warningBytes : -1;
199             }
200             if (uId != -1) {
201                 usage.uId = uId;
202             }
203             return usage;
204         } catch (RemoteException e) {
205             return warn("remote call failed");
206         }
207     }
208 
findNetworkPolicy(NetworkTemplate template)209     private NetworkPolicy findNetworkPolicy(NetworkTemplate template) {
210         if (mPolicyManager == null || template == null) return null;
211         final NetworkPolicy[] policies = mPolicyManager.getNetworkPolicies();
212         if (policies == null) return null;
213         final int mLength = policies.length;
214         for (int i = 0; i < mLength; i++) {
215             final NetworkPolicy policy = policies[i];
216             if (policy != null && template.equals(policy.template)) {
217                 return policy;
218             }
219         }
220         return null;
221     }
222 
historyEntryToString(NetworkStatsHistory.Entry entry)223     private static String historyEntryToString(NetworkStatsHistory.Entry entry) {
224         return entry == null ? null : new StringBuilder("Entry[")
225                 .append("bucketDuration=").append(entry.bucketDuration)
226                 .append(",bucketStart=").append(entry.bucketStart)
227                 .append(",activeTime=").append(entry.activeTime)
228                 .append(",rxBytes=").append(entry.rxBytes)
229                 .append(",rxPackets=").append(entry.rxPackets)
230                 .append(",txBytes=").append(entry.txBytes)
231                 .append(",txPackets=").append(entry.txPackets)
232                 .append(",operations=").append(entry.operations)
233                 .append(']').toString();
234     }
235 
236     /**
237      * Enable or disable mobile data.
238      * @param enabled Enable data: True: Disable data: False.
239      */
setMobileDataEnabled(boolean enabled)240     public void setMobileDataEnabled(boolean enabled) {
241         Log.d(TAG, "setMobileDataEnabled: enabled=" + enabled);
242         mTelephonyManager.setDataEnabled(enabled);
243         if (mCallback != null) {
244             mCallback.onMobileDataEnabled(enabled);
245         }
246     }
247 
248     /**
249      * Check if mobile data is supported.
250      * @return True supported, False: Not supported.
251      */
isMobileDataSupported()252     public boolean isMobileDataSupported() {
253         // require both supported network and ready SIM
254         return mConnectivityManager.isNetworkSupported(TYPE_MOBILE)
255                 && mTelephonyManager.getSimState() == SIM_STATE_READY;
256     }
257 
258     /**
259      * Check if mobile data is enabled.
260      * @return True: enabled, False: Not enabled.
261      */
isMobileDataEnabled()262     public boolean isMobileDataEnabled() {
263         return mTelephonyManager.getDataEnabled();
264     }
265 
formatDateRange(long start, long end)266     private String formatDateRange(long start, long end) {
267         final int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH;
268         synchronized (PERIOD_BUILDER) {
269             PERIOD_BUILDER.setLength(0);
270             return DateUtils.formatDateRange(mContext, PERIOD_FORMATTER, start, end, flags, null)
271                     .toString();
272         }
273     }
274 
275     public interface NetworkNameProvider {
getMobileDataNetworkName()276         String getMobileDataNetworkName();
277     }
278 
279     public static class DataUsageInfo {
280         public String subscriberId;
281         public String period;
282         public Integer uId;
283         public long startEpochMilli;
284         public long endEpochMilli;
285         public long limitLevel;
286         public long warningLevel;
287         public long usageLevel;
288         public String cycleStart;
289         public String cycleEnd;
290     }
291 
292     public interface Callback {
onMobileDataEnabled(boolean enabled)293         void onMobileDataEnabled(boolean enabled);
294     }
295 }
296