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 com.android.settingslib.R; 36 37 import java.util.Date; 38 import java.util.Locale; 39 40 import static android.net.ConnectivityManager.TYPE_MOBILE; 41 import static android.net.NetworkStatsHistory.FIELD_RX_BYTES; 42 import static android.net.NetworkStatsHistory.FIELD_TX_BYTES; 43 import static android.telephony.TelephonyManager.SIM_STATE_READY; 44 import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH; 45 import static android.text.format.DateUtils.FORMAT_SHOW_DATE; 46 import static android.net.TrafficStats.MB_IN_BYTES; 47 48 public 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