| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.settingslib.net; |
| |
| import static android.net.ConnectivityManager.TYPE_MOBILE; |
| import static android.net.NetworkStatsHistory.FIELD_RX_BYTES; |
| import static android.net.NetworkStatsHistory.FIELD_TX_BYTES; |
| import static android.net.TrafficStats.MB_IN_BYTES; |
| import static android.telephony.TelephonyManager.SIM_STATE_READY; |
| import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH; |
| import static android.text.format.DateUtils.FORMAT_SHOW_DATE; |
| |
| import android.app.usage.NetworkStats.Bucket; |
| import android.app.usage.NetworkStatsManager; |
| import android.content.Context; |
| import android.net.ConnectivityManager; |
| import android.net.INetworkStatsService; |
| import android.net.INetworkStatsSession; |
| import android.net.NetworkPolicy; |
| import android.net.NetworkPolicyManager; |
| import android.net.NetworkTemplate; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.telephony.SubscriptionManager; |
| import android.telephony.TelephonyManager; |
| import android.text.format.DateUtils; |
| import android.util.Log; |
| import android.util.Range; |
| |
| import com.android.internal.R; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.util.ArrayUtils; |
| |
| import java.time.ZonedDateTime; |
| import java.util.Iterator; |
| import java.util.Locale; |
| |
| public class DataUsageController { |
| |
| private static final String TAG = "DataUsageController"; |
| private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); |
| private static final int FIELDS = FIELD_RX_BYTES | FIELD_TX_BYTES; |
| private static final StringBuilder PERIOD_BUILDER = new StringBuilder(50); |
| private static final java.util.Formatter PERIOD_FORMATTER = new java.util.Formatter( |
| PERIOD_BUILDER, Locale.getDefault()); |
| |
| private final Context mContext; |
| private final ConnectivityManager mConnectivityManager; |
| private final INetworkStatsService mStatsService; |
| private final NetworkPolicyManager mPolicyManager; |
| private final NetworkStatsManager mNetworkStatsManager; |
| |
| private INetworkStatsSession mSession; |
| private Callback mCallback; |
| private NetworkNameProvider mNetworkController; |
| private int mSubscriptionId; |
| |
| public DataUsageController(Context context) { |
| mContext = context; |
| mConnectivityManager = ConnectivityManager.from(context); |
| mStatsService = INetworkStatsService.Stub.asInterface( |
| ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); |
| mPolicyManager = NetworkPolicyManager.from(mContext); |
| mNetworkStatsManager = context.getSystemService(NetworkStatsManager.class); |
| mSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; |
| } |
| |
| public void setNetworkController(NetworkNameProvider networkController) { |
| mNetworkController = networkController; |
| } |
| |
| /** |
| * By default this class will just get data usage information for the default data subscription, |
| * but this method can be called to require it to use an explicit subscription id which may be |
| * different from the default one (this is useful for the case of multi-SIM devices). |
| */ |
| public void setSubscriptionId(int subscriptionId) { |
| mSubscriptionId = subscriptionId; |
| } |
| |
| /** |
| * Returns the default warning level in bytes. |
| */ |
| public long getDefaultWarningLevel() { |
| return MB_IN_BYTES |
| * mContext.getResources().getInteger(R.integer.default_data_warning_level_mb); |
| } |
| |
| public void setCallback(Callback callback) { |
| mCallback = callback; |
| } |
| |
| private DataUsageInfo warn(String msg) { |
| Log.w(TAG, "Failed to get data usage, " + msg); |
| return null; |
| } |
| |
| public DataUsageInfo getDataUsageInfo() { |
| NetworkTemplate template = DataUsageUtils.getMobileTemplate(mContext, mSubscriptionId); |
| |
| return getDataUsageInfo(template); |
| } |
| |
| public DataUsageInfo getWifiDataUsageInfo() { |
| NetworkTemplate template = NetworkTemplate.buildTemplateWifiWildcard(); |
| return getDataUsageInfo(template); |
| } |
| |
| public DataUsageInfo getDataUsageInfo(NetworkTemplate template) { |
| final NetworkPolicy policy = findNetworkPolicy(template); |
| final long now = System.currentTimeMillis(); |
| final long start, end; |
| final Iterator<Range<ZonedDateTime>> it = (policy != null) ? policy.cycleIterator() : null; |
| if (it != null && it.hasNext()) { |
| final Range<ZonedDateTime> cycle = it.next(); |
| start = cycle.getLower().toInstant().toEpochMilli(); |
| end = cycle.getUpper().toInstant().toEpochMilli(); |
| } else { |
| // period = last 4 wks |
| end = now; |
| start = now - DateUtils.WEEK_IN_MILLIS * 4; |
| } |
| final long totalBytes = getUsageLevel(template, start, end); |
| if (totalBytes < 0L) { |
| return warn("no entry data"); |
| } |
| final DataUsageInfo usage = new DataUsageInfo(); |
| usage.startDate = start; |
| usage.usageLevel = totalBytes; |
| usage.period = formatDateRange(start, end); |
| usage.cycleStart = start; |
| usage.cycleEnd = end; |
| |
| if (policy != null) { |
| usage.limitLevel = policy.limitBytes > 0 ? policy.limitBytes : 0; |
| usage.warningLevel = policy.warningBytes > 0 ? policy.warningBytes : 0; |
| } else { |
| usage.warningLevel = getDefaultWarningLevel(); |
| } |
| if (usage != null && mNetworkController != null) { |
| usage.carrier = mNetworkController.getMobileDataNetworkName(); |
| } |
| return usage; |
| } |
| |
| /** |
| * Get the total usage level recorded in the network history |
| * @param template the network template to retrieve the network history |
| * @return the total usage level recorded in the network history or -1L if there is error |
| * retrieving the data. |
| */ |
| public long getHistoricalUsageLevel(NetworkTemplate template) { |
| return getUsageLevel(template, 0L /* start */, System.currentTimeMillis() /* end */); |
| } |
| |
| private long getUsageLevel(NetworkTemplate template, long start, long end) { |
| try { |
| final Bucket bucket = mNetworkStatsManager.querySummaryForDevice(template, start, end); |
| if (bucket != null) { |
| return bucket.getRxBytes() + bucket.getTxBytes(); |
| } |
| Log.w(TAG, "Failed to get data usage, no entry data"); |
| } catch (RemoteException e) { |
| Log.w(TAG, "Failed to get data usage, remote call failed"); |
| } |
| return -1L; |
| } |
| |
| private NetworkPolicy findNetworkPolicy(NetworkTemplate template) { |
| if (mPolicyManager == null || template == null) return null; |
| final NetworkPolicy[] policies = mPolicyManager.getNetworkPolicies(); |
| if (policies == null) return null; |
| final int N = policies.length; |
| for (int i = 0; i < N; i++) { |
| final NetworkPolicy policy = policies[i]; |
| if (policy != null && template.equals(policy.template)) { |
| return policy; |
| } |
| } |
| return null; |
| } |
| |
| private static String statsBucketToString(Bucket bucket) { |
| return bucket == null ? null : new StringBuilder("Entry[") |
| .append("bucketDuration=").append(bucket.getEndTimeStamp() - bucket.getStartTimeStamp()) |
| .append(",bucketStart=").append(bucket.getStartTimeStamp()) |
| .append(",rxBytes=").append(bucket.getRxBytes()) |
| .append(",rxPackets=").append(bucket.getRxPackets()) |
| .append(",txBytes=").append(bucket.getTxBytes()) |
| .append(",txPackets=").append(bucket.getTxPackets()) |
| .append(']').toString(); |
| } |
| |
| @VisibleForTesting |
| public TelephonyManager getTelephonyManager() { |
| int subscriptionId = mSubscriptionId; |
| |
| // If mSubscriptionId is invalid, get default data sub. |
| if (!SubscriptionManager.isValidSubscriptionId(subscriptionId)) { |
| subscriptionId = SubscriptionManager.getDefaultDataSubscriptionId(); |
| } |
| |
| // If data sub is also invalid, get any active sub. |
| if (!SubscriptionManager.isValidSubscriptionId(subscriptionId)) { |
| int[] activeSubIds = SubscriptionManager.from(mContext).getActiveSubscriptionIdList(); |
| if (!ArrayUtils.isEmpty(activeSubIds)) { |
| subscriptionId = activeSubIds[0]; |
| } |
| } |
| |
| return mContext.getSystemService( |
| TelephonyManager.class).createForSubscriptionId(subscriptionId); |
| } |
| |
| public void setMobileDataEnabled(boolean enabled) { |
| Log.d(TAG, "setMobileDataEnabled: enabled=" + enabled); |
| getTelephonyManager().setDataEnabled(enabled); |
| if (mCallback != null) { |
| mCallback.onMobileDataEnabled(enabled); |
| } |
| } |
| |
| public boolean isMobileDataSupported() { |
| // require both supported network and ready SIM |
| return mConnectivityManager.isNetworkSupported(TYPE_MOBILE) |
| && getTelephonyManager().getSimState() == SIM_STATE_READY; |
| } |
| |
| public boolean isMobileDataEnabled() { |
| return getTelephonyManager().isDataEnabled(); |
| } |
| |
| static int getNetworkType(NetworkTemplate networkTemplate) { |
| if (networkTemplate == null) { |
| return ConnectivityManager.TYPE_NONE; |
| } |
| final int matchRule = networkTemplate.getMatchRule(); |
| switch (matchRule) { |
| case NetworkTemplate.MATCH_MOBILE: |
| case NetworkTemplate.MATCH_MOBILE_WILDCARD: |
| return ConnectivityManager.TYPE_MOBILE; |
| case NetworkTemplate.MATCH_WIFI: |
| case NetworkTemplate.MATCH_WIFI_WILDCARD: |
| return ConnectivityManager.TYPE_WIFI; |
| case NetworkTemplate.MATCH_ETHERNET: |
| return ConnectivityManager.TYPE_ETHERNET; |
| default: |
| return ConnectivityManager.TYPE_MOBILE; |
| } |
| } |
| |
| private String getActiveSubscriberId() { |
| final String actualSubscriberId = getTelephonyManager().getSubscriberId(); |
| return actualSubscriberId; |
| } |
| |
| private String formatDateRange(long start, long end) { |
| final int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH; |
| synchronized (PERIOD_BUILDER) { |
| PERIOD_BUILDER.setLength(0); |
| return DateUtils.formatDateRange(mContext, PERIOD_FORMATTER, start, end, flags, null) |
| .toString(); |
| } |
| } |
| |
| public interface NetworkNameProvider { |
| String getMobileDataNetworkName(); |
| } |
| |
| public static class DataUsageInfo { |
| public String carrier; |
| public String period; |
| public long startDate; |
| public long limitLevel; |
| public long warningLevel; |
| public long usageLevel; |
| public long cycleStart; |
| public long cycleEnd; |
| } |
| |
| public interface Callback { |
| void onMobileDataEnabled(boolean enabled); |
| } |
| } |