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;
}
}