blob: 092cbf3c7c12617f218224d9aff5f506693868aa [file] [log] [blame]
/*
* 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);
}
}