/* * 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.android.settingslib.net; import android.annotation.NonNull; import android.app.usage.NetworkStats; import android.app.usage.NetworkStatsManager; import android.content.Context; import android.net.NetworkPolicy; import android.net.NetworkPolicyManager; import android.net.NetworkTemplate; import android.text.format.DateUtils; import android.util.Pair; import android.util.Range; import androidx.annotation.VisibleForTesting; import androidx.loader.content.AsyncTaskLoader; import com.android.settingslib.NetworkPolicyEditor; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Iterator; /** * Loader for network data usage history. It returns a list of usage data per billing cycle. */ public abstract class NetworkCycleDataLoader extends AsyncTaskLoader { private static final String TAG = "NetworkCycleDataLoader"; protected final NetworkStatsManager mNetworkStatsManager; protected final NetworkTemplate mNetworkTemplate; private final NetworkPolicy mPolicy; private final ArrayList mCycles; protected NetworkCycleDataLoader(Builder builder) { super(builder.mContext); mNetworkTemplate = builder.mNetworkTemplate; mCycles = builder.mCycles; mNetworkStatsManager = (NetworkStatsManager) builder.mContext.getSystemService(Context.NETWORK_STATS_SERVICE); final NetworkPolicyEditor policyEditor = new NetworkPolicyEditor(NetworkPolicyManager.from(builder.mContext)); policyEditor.read(); mPolicy = policyEditor.getPolicy(mNetworkTemplate); } @Override protected void onStartLoading() { super.onStartLoading(); forceLoad(); } public D loadInBackground() { if (mCycles != null && mCycles.size() > 1) { loadDataForSpecificCycles(); } else if (mPolicy == null) { loadFourWeeksData(); } else { loadPolicyData(); } return getCycleUsage(); } @VisibleForTesting void loadPolicyData() { final Iterator> iterator = NetworkPolicyManager.cycleIterator(mPolicy); while (iterator.hasNext()) { final Pair cycle = iterator.next(); final long cycleStart = cycle.first.toInstant().toEpochMilli(); final long cycleEnd = cycle.second.toInstant().toEpochMilli(); recordUsage(cycleStart, cycleEnd); } } @Override protected void onStopLoading() { super.onStopLoading(); cancelLoad(); } @Override protected void onReset() { super.onReset(); cancelLoad(); } @VisibleForTesting void loadFourWeeksData() { if (mNetworkTemplate == null) return; final NetworkStats stats = mNetworkStatsManager.queryDetailsForDevice( mNetworkTemplate, Long.MIN_VALUE, Long.MAX_VALUE); try { final Range historyTimeRange = getTimeRangeOf(stats); long cycleEnd = historyTimeRange.getUpper(); while (cycleEnd > historyTimeRange.getLower()) { final long cycleStart = cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4); recordUsage(cycleStart, cycleEnd); cycleEnd = cycleStart; } } catch (IllegalArgumentException e) { // Empty history, ignore. } } @VisibleForTesting void loadDataForSpecificCycles() { long cycleEnd = mCycles.get(0); final int lastCycleIndex = mCycles.size() - 1; for (int i = 1; i <= lastCycleIndex; i++) { final long cycleStart = mCycles.get(i); recordUsage(cycleStart, cycleEnd); cycleEnd = cycleStart; } } @VisibleForTesting abstract void recordUsage(long start, long end); abstract D getCycleUsage(); public static Builder builder(Context context) { return new Builder(context) { @Override public NetworkCycleDataLoader build() { return null; } }; } protected long getTotalUsage(NetworkStats stats) { long bytes = 0L; if (stats != null) { final NetworkStats.Bucket bucket = new NetworkStats.Bucket(); while (stats.hasNextBucket() && stats.getNextBucket(bucket)) { bytes += bucket.getRxBytes() + bucket.getTxBytes(); } stats.close(); } return bytes; } @NonNull @VisibleForTesting Range getTimeRangeOf(@NonNull NetworkStats stats) { long start = Long.MAX_VALUE; long end = Long.MIN_VALUE; while (hasNextBucket(stats)) { final NetworkStats.Bucket bucket = getNextBucket(stats); start = Math.min(start, bucket.getStartTimeStamp()); end = Math.max(end, bucket.getEndTimeStamp()); } return new Range(start, end); } @VisibleForTesting boolean hasNextBucket(@NonNull NetworkStats stats) { return stats.hasNextBucket(); } @NonNull @VisibleForTesting NetworkStats.Bucket getNextBucket(@NonNull NetworkStats stats) { NetworkStats.Bucket bucket = new NetworkStats.Bucket(); stats.getNextBucket(bucket); return bucket; } @VisibleForTesting(otherwise = VisibleForTesting.NONE) public ArrayList getCycles() { return mCycles; } public static abstract class Builder { private final Context mContext; private NetworkTemplate mNetworkTemplate; private ArrayList mCycles; public Builder (Context context) { mContext = context; } public Builder setNetworkTemplate(NetworkTemplate template) { mNetworkTemplate = template; return this; } /** * Sets the network cycles to be used to query the usage data. * @param cycles the time slots for the network cycle to be used to query the network usage. * @return the builder */ public Builder setCycles(ArrayList cycles) { mCycles = cycles; return this; } public abstract T build(); } }