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