/* * Copyright (C) 2018 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.googlecode.android_scripting.facade; import static android.app.usage.NetworkStats.Bucket.METERED_YES; import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.NetworkStats.UID_ALL; import static android.net.NetworkTemplate.MATCH_MOBILE; import static android.net.NetworkTemplate.MATCH_WIFI; 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 static android.text.format.DateUtils.FORMAT_SHOW_TIME; import static android.text.format.DateUtils.FORMAT_SHOW_YEAR; import android.app.usage.NetworkStats; import android.app.usage.NetworkStatsManager; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkPolicy; import android.net.NetworkPolicyManager; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; import android.telephony.TelephonyManager; import android.text.format.DateUtils; import android.util.Log; import android.util.Pair; import java.time.ZonedDateTime; import java.util.Locale; import java.util.Set; /** * DataUsageController. */ public class DataUsageController { private static final String TAG = "DataUsageController"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 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 TelephonyManager mTelephonyManager; private final ConnectivityManager mConnectivityManager; private final NetworkPolicyManager mPolicyManager; private Callback mCallback; private NetworkNameProvider mNetworkController; public DataUsageController(Context context) { mContext = context; mTelephonyManager = TelephonyManager.from(context); mConnectivityManager = ConnectivityManager.from(context); mPolicyManager = NetworkPolicyManager.from(mContext); } public void setNetworkController(NetworkNameProvider networkController) { mNetworkController = networkController; } public void setCallback(Callback callback) { mCallback = callback; } private DataUsageInfo warn(String msg) { Log.w(TAG, "Failed to get data usage, " + msg); return null; } /** * Get mobile data usage info. * @return DataUsageInfo: The Mobile data usage information. */ public DataUsageInfo getMobileDataUsageInfoForSubscriber(String subscriberId) { if (subscriberId == null) { return warn("no subscriber id"); } NetworkTemplate template = new NetworkTemplate.Builder(MATCH_MOBILE) .setMeteredness(METERED_YES).setSubscriberIds(Set.of(subscriberId)).build(); template = NetworkTemplate.normalize(template, mTelephonyManager.getMergedSubscriberIds()); return getDataUsageInfo(template); } /** * Get mobile data usage info. * @return DataUsageInfo: The Mobile data usage information. */ public DataUsageInfo getMobileDataUsageInfoForUid(Integer uId, String subscriberId) { if (subscriberId == null) { return warn("no subscriber id"); } NetworkTemplate template = new NetworkTemplate.Builder(MATCH_MOBILE) .setMeteredness(METERED_YES).setSubscriberIds(Set.of(subscriberId)).build(); template = NetworkTemplate.normalize(template, mTelephonyManager.getMergedSubscriberIds()); return getDataUsageInfo(template, uId); } /** * Get wifi data usage info. * @return DataUsageInfo: The Wifi data usage information. */ public DataUsageInfo getWifiDataUsageInfo() { NetworkTemplate template = new NetworkTemplate.Builder(MATCH_WIFI).build(); return getDataUsageInfo(template); } /** * Get data usage info for a given template. * @param template A given template. * @return DataUsageInfo: The data usage information. */ public DataUsageInfo getDataUsageInfo(NetworkTemplate template) { return getDataUsageInfo(template, UID_ALL); } private static long getTotalBytesForUid(NetworkStats stats, int uid) { if (!stats.hasNextBucket()) { return 0; } NetworkStats.Bucket bucket = new NetworkStats.Bucket(); long rx = 0; long tx = 0; while (stats.hasNextBucket() && stats.getNextBucket(bucket)) { if (uid != bucket.getUid()) continue; rx += bucket.getRxBytes(); tx += bucket.getTxBytes(); } return rx + tx; } /** * Get data usage info for a given template. * @param template A given template. * @param uid UID of app, uid = -1 {@link NetworkStats#UID_ALL} value indicates summarised * data usage without UID details. * @return DataUsageInfo: The data usage information. */ public DataUsageInfo getDataUsageInfo(NetworkTemplate template, int uid) { final NetworkPolicy policy = findNetworkPolicy(template); final long totalBytes; final long now = System.currentTimeMillis(); final long start, end; final NetworkStatsManager mNetworkStatsManager = mContext.getSystemService(NetworkStatsManager.class); if (policy != null) { final Pair cycle = NetworkPolicyManager .cycleIterator(policy).next(); start = cycle.first.toInstant().toEpochMilli(); end = cycle.second.toInstant().toEpochMilli(); } else { // period = last 4 wks end = now; start = now - DateUtils.WEEK_IN_MILLIS * 4; } if (uid == UID_ALL) { final NetworkStats.Bucket ret = mNetworkStatsManager .querySummaryForDevice(template, start, end); totalBytes = ret.getRxBytes() + ret.getTxBytes(); } else { final NetworkStats ret = mNetworkStatsManager.querySummary(template, start, end); totalBytes = getTotalBytesForUid(ret, uid); } final DataUsageInfo usage = new DataUsageInfo(); usage.subscriberId = template.getSubscriberId(); usage.startEpochMilli = start; usage.endEpochMilli = end; usage.usageLevel = totalBytes; usage.period = formatDateRange(start, end); usage.cycleStart = DateUtils.formatDateTime(mContext, start, FORMAT_SHOW_DATE + FORMAT_SHOW_YEAR + FORMAT_SHOW_TIME); usage.cycleEnd = DateUtils.formatDateTime(mContext, end, FORMAT_SHOW_DATE + FORMAT_SHOW_YEAR + FORMAT_SHOW_TIME); if (policy != null) { usage.limitLevel = policy.limitBytes > 0 ? policy.limitBytes : -1; usage.warningLevel = policy.warningBytes > 0 ? policy.warningBytes : -1; } if (uid != UID_ALL) { usage.uId = uid; } return usage; } private NetworkPolicy findNetworkPolicy(NetworkTemplate template) { if (mPolicyManager == null || template == null) return null; final NetworkPolicy[] policies = mPolicyManager.getNetworkPolicies(); if (policies == null) return null; final int mLength = policies.length; for (int i = 0; i < mLength; i++) { final NetworkPolicy policy = policies[i]; if (policy != null && template.equals(policy.template)) { return policy; } } return null; } private static String historyEntryToString(NetworkStatsHistory.Entry entry) { return entry == null ? null : new StringBuilder("Entry[") .append("bucketDuration=").append(entry.bucketDuration) .append(",bucketStart=").append(entry.bucketStart) .append(",activeTime=").append(entry.activeTime) .append(",rxBytes=").append(entry.rxBytes) .append(",rxPackets=").append(entry.rxPackets) .append(",txBytes=").append(entry.txBytes) .append(",txPackets=").append(entry.txPackets) .append(",operations=").append(entry.operations) .append(']').toString(); } /** * Enable or disable mobile data. * @param enabled Enable data: True: Disable data: False. */ public void setMobileDataEnabled(boolean enabled) { Log.d(TAG, "setMobileDataEnabled: enabled=" + enabled); mTelephonyManager.setDataEnabled(enabled); if (mCallback != null) { mCallback.onMobileDataEnabled(enabled); } } /** * Check if mobile data is supported. * @return True supported, False: Not supported. */ public boolean isMobileDataSupported() { // require both supported network and ready SIM return mConnectivityManager.isNetworkSupported(TYPE_MOBILE) && mTelephonyManager.getSimState() == SIM_STATE_READY; } /** * Check if mobile data is enabled. * @return True: enabled, False: Not enabled. */ public boolean isMobileDataEnabled() { return mTelephonyManager.getDataEnabled(); } 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 subscriberId; public String period; public Integer uId; public long startEpochMilli; public long endEpochMilli; public long limitLevel; public long warningLevel; public long usageLevel; public String cycleStart; public String cycleEnd; } public interface Callback { void onMobileDataEnabled(boolean enabled); } }