package tk.wasdennnoch.androidn_ify.extracted.systemui; import android.annotation.SuppressLint; import android.content.Context; import android.net.INetworkStatsService; import android.net.INetworkStatsSession; import android.net.NetworkPolicy; import android.net.NetworkPolicyManager; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; import android.os.RemoteException; import android.os.ServiceManager; import android.telephony.TelephonyManager; import android.text.format.DateUtils; import android.text.format.Time; import java.util.Date; import java.util.Locale; import de.robv.android.xposed.XposedHelpers; import tk.wasdennnoch.androidn_ify.XposedHook; import static android.net.NetworkStatsHistory.FIELD_RX_BYTES; import static android.net.NetworkStatsHistory.FIELD_TX_BYTES; import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH; import static android.text.format.DateUtils.FORMAT_SHOW_DATE; @SuppressWarnings("SameReturnValue") public class MobileDataController { private static final String TAG = "MobileDataController"; private static final long DEFAULT_WARNING_LEVEL = 2L * 1024 * 1024 * 1024; 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 INetworkStatsService mStatsService; private final NetworkPolicyManager mPolicyManager; private INetworkStatsSession mSession; @SuppressLint("InlinedApi") // Is available in pre 23, but hidden public MobileDataController(Context context) { mContext = context; mStatsService = INetworkStatsService.Stub.asInterface( ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); mPolicyManager = NetworkPolicyManager.from(mContext); } @SuppressWarnings("deprecation") private static Time addMonth(Time t, int months) { final Time rt = new Time(t); rt.set(t.monthDay, t.month + months, t.year); rt.normalize(false); return rt; } private static String historyEntryToString(NetworkStatsHistory.Entry entry) { return entry == null ? null : "Entry[" + "bucketDuration=" + entry.bucketDuration + ",bucketStart=" + entry.bucketStart + ",activeTime=" + entry.activeTime + ",rxBytes=" + entry.rxBytes + ",rxPackets=" + entry.rxPackets + ",txBytes=" + entry.txBytes + ",txPackets=" + entry.txPackets + ",operations=" + entry.operations + ']'; } private static String getActiveSubscriberId(Context context) { final TelephonyManager tele = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); Class<?> SubscriptionManager = XposedHelpers.findClass("android.telephony.SubscriptionManager", null); // On some ROMs this seems to be a Float, so don't cast it Object getDefaultDataSubId = XposedHelpers.callStaticMethod(SubscriptionManager, "getDefaultDataSubId"); return (String) XposedHelpers.callMethod(tele, "getSubscriberId", getDefaultDataSubId); } private INetworkStatsSession getSession() { if (mSession == null) { try { mSession = mStatsService.openSession(); } catch (RemoteException | RuntimeException e) { XposedHook.logW(TAG, "Failed to open stats session"); } } return mSession; } private DataUsageInfo warn(String msg) { XposedHook.logW(TAG, "Failed to get data usage, " + msg); return null; } public DataUsageInfo getDataUsageInfo() { final String subscriberId = getActiveSubscriberId(mContext); if (subscriberId == null) { return warn("no subscriber id"); } final INetworkStatsSession session = getSession(); if (session == null) { return warn("no stats session"); } final NetworkTemplate template = NetworkTemplate.buildTemplateMobileAll(subscriberId); final NetworkPolicy policy = findNetworkPolicy(template); try { final NetworkStatsHistory history = mSession.getHistoryForNetwork(template, FIELDS); final long now = System.currentTimeMillis(); final long start, end; if (policy != null && policy.cycleDay > 0) { // period = determined from cycleDay XposedHook.logD(TAG, "Cycle day=" + policy.cycleDay + " tz=" + policy.cycleTimezone); //noinspection deprecation final Time nowTime = new Time(policy.cycleTimezone); nowTime.setToNow(); //noinspection deprecation final Time policyTime = new Time(nowTime); policyTime.set(policy.cycleDay, policyTime.month, policyTime.year); policyTime.normalize(false); if (nowTime.after(policyTime)) { start = policyTime.toMillis(false); end = addMonth(policyTime, 1).toMillis(false); } else { start = addMonth(policyTime, -1).toMillis(false); end = policyTime.toMillis(false); } } else { // period = last 4 wks end = now; start = now - DateUtils.WEEK_IN_MILLIS * 4; } final long callStart = System.currentTimeMillis(); final NetworkStatsHistory.Entry entry = history.getValues(start, end, now, null); final long callEnd = System.currentTimeMillis(); XposedHook.logD(TAG, String.format("history call from %s to %s now=%s took %sms: %s", new Date(start), new Date(end), new Date(now), callEnd - callStart, historyEntryToString(entry))); if (entry == null) { return warn("no entry data"); } final long totalBytes = entry.rxBytes + entry.txBytes; final DataUsageInfo usage = new DataUsageInfo(); usage.usageLevel = totalBytes; usage.period = formatDateRange(start, end); if (policy != null) { usage.limitLevel = policy.limitBytes > 0 ? policy.limitBytes : 0; usage.warningLevel = policy.warningBytes > 0 ? policy.warningBytes : 0; } else { usage.warningLevel = DEFAULT_WARNING_LEVEL; } return usage; } catch (RemoteException e) { return warn("remote call failed"); } } private NetworkPolicy findNetworkPolicy(NetworkTemplate template) { if (mPolicyManager == null || template == null) return null; final NetworkPolicy[] policies = mPolicyManager.getNetworkPolicies(); if (policies == null) return null; for (final NetworkPolicy policy : policies) { if (policy != null && template.equals(policy.template)) { return policy; } } return null; } 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(); } } @SuppressWarnings("WeakerAccess") public static class DataUsageInfo { public String period; public long limitLevel; public long warningLevel; public long usageLevel; } }