Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3fcda51f authored by Doris Ling's avatar Doris Ling Committed by Android (Google) Code Review
Browse files

Merge "Add loader for netork cycle data."

parents 076d9b85 0d8e4b43
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -36,7 +36,12 @@ import com.android.settingslib.AppItem;

/**
 * Loader for historical chart data for both network and UID details.
 *
 * Deprecated in favor of {@link NetworkCycleDataLoader}
 *
 * @deprecated
 */
@Deprecated
public class ChartDataLoaderCompat extends AsyncTaskLoader<ChartData> {
    private static final String KEY_TEMPLATE = "template";
    private static final String KEY_APP = "app";
+69 −0
Original line number Diff line number Diff line
/*
 * 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 java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Data structure representing usage data in a billing cycle.
 */
public class NetworkCycleData {
    public static final long BUCKET_DURATION_MS = TimeUnit.DAYS.toMillis(1);
    public long startTime;
    public long endTime;
    public long totalUsage;
    public List<NetworkCycleData> usageBuckets;

    private NetworkCycleData(Builder builder) {
        startTime = builder.mStart;
        endTime = builder.mEnd;
        totalUsage = builder.mTotalUsage;
        usageBuckets = builder.mUsageBuckets;
    }

    public static class Builder {
        private long mStart;
        private long mEnd;
        private long mTotalUsage;
        private List<NetworkCycleData> mUsageBuckets;

        public Builder setStartTime(long start) {
            mStart = start;
            return this;
        }

        public Builder setEndTime(long end) {
            mEnd = end;
            return this;
        }

        public Builder setTotalUsage(long total) {
            mTotalUsage = total;
            return this;
        }

        public Builder setUsageBuckets(List<NetworkCycleData> buckets) {
            mUsageBuckets = buckets;
            return this;
        }

        public NetworkCycleData build() {
            return new NetworkCycleData(this);
        }
    }
}
+219 −0
Original line number Diff line number Diff line
/*
 * 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 static android.net.NetworkStatsHistory.FIELD_RX_BYTES;
import static android.net.NetworkStatsHistory.FIELD_TX_BYTES;

import android.app.usage.NetworkStats;
import android.app.usage.NetworkStatsManager;
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.net.TrafficStats;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.text.format.DateUtils;
import android.util.Log;
import android.util.Pair;

import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.loader.content.AsyncTaskLoader;

/**
 * Loader for network data usage history. It returns a list of usage data per billing cycle.
 */
public class NetworkCycleDataLoader extends AsyncTaskLoader<List<NetworkCycleData>> {
    private static final String TAG = "CycleDataSummaryLoader";
    private final NetworkStatsManager mNetworkStatsManager;
    private final String mSubId;
    private final int mNetworkType;
    private final NetworkPolicy mPolicy;
    private final NetworkTemplate mNetworkTemplate;
    @VisibleForTesting
    final INetworkStatsService mNetworkStatsService;

    private NetworkCycleDataLoader(Builder builder) {
        super(builder.mContext);
        mPolicy = builder.mPolicy;
        mSubId = builder.mSubId;
        mNetworkType = builder.mNetworkType;
        mNetworkTemplate = builder.mNetworkTemplate;
        mNetworkStatsManager = (NetworkStatsManager)
            builder.mContext.getSystemService(Context.NETWORK_STATS_SERVICE);
        mNetworkStatsService = INetworkStatsService.Stub.asInterface(
            ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
    }

    @Override
    protected void onStartLoading() {
        super.onStartLoading();
        forceLoad();
    }

    @Override
    public List<NetworkCycleData> loadInBackground() {
        if (mPolicy == null) {
            return loadFourWeeksData();
        }
        final List<NetworkCycleData> data = new ArrayList<>();
        final Iterator<Pair<ZonedDateTime, ZonedDateTime>> iterator = NetworkPolicyManager
            .cycleIterator(mPolicy);
        while (iterator.hasNext()) {
            final Pair<ZonedDateTime, ZonedDateTime> cycle = iterator.next();
            final long cycleStart = cycle.first.toInstant().toEpochMilli();
            final long cycleEnd = cycle.second.toInstant().toEpochMilli();
            getUsage(cycleStart, cycleEnd, data);
        }
        return data;
    }

    @Override
    protected void onStopLoading() {
        super.onStopLoading();
        cancelLoad();
    }

    @Override
    protected void onReset() {
        super.onReset();
        cancelLoad();
    }

    @VisibleForTesting
    List<NetworkCycleData> loadFourWeeksData() {
        final List<NetworkCycleData> data = new ArrayList<>();
        try {
            final INetworkStatsSession networkSession = mNetworkStatsService.openSession();
            final NetworkStatsHistory networkHistory = networkSession.getHistoryForNetwork(
                mNetworkTemplate, FIELD_RX_BYTES | FIELD_TX_BYTES);
            final long historyStart = networkHistory.getStart();
            final long historyEnd = networkHistory.getEnd();

            long cycleEnd = historyEnd;
            while (cycleEnd > historyStart) {
                final long cycleStart = cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4);
                getUsage(cycleStart, cycleEnd, data);
                cycleEnd = cycleStart;
            }

            TrafficStats.closeQuietly(networkSession);
        } catch (RemoteException e) {
            throw new RuntimeException(e);
        }
        return data;
    }

    @VisibleForTesting
    void getUsage(long start, long end, @NonNull List<NetworkCycleData> data) {
        try {
            final NetworkStats stats = mNetworkStatsManager.querySummary(
                mNetworkType, mSubId, start, end);
            final long total = getTotalUsage(stats);
            if (total > 0L) {
                data.add(new NetworkCycleData.Builder()
                    .setStartTime(start)
                    .setEndTime(end)
                    .setTotalUsage(total)
                    .setUsageBuckets(getUsageBuckets(start, end))
                    .build());
            }
        } catch (RemoteException e) {
            Log.e(TAG, "Exception querying network detail.", e);
        }
    }

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

    private List<NetworkCycleData> getUsageBuckets(long start, long end) {
        final List<NetworkCycleData> data = new ArrayList<>();
        long bucketStart = start;
        long bucketEnd = start + NetworkCycleData.BUCKET_DURATION_MS;
        while (bucketEnd <= end) {
            long usage = 0L;
            try {
                final NetworkStats stats = mNetworkStatsManager.querySummary(
                    mNetworkType, mSubId, bucketStart, bucketEnd);
                usage = getTotalUsage(stats);
            } catch (RemoteException e) {
                Log.e(TAG, "Exception querying network detail.", e);
            }
            data.add(new NetworkCycleData.Builder()
                .setStartTime(bucketStart).setEndTime(bucketEnd).setTotalUsage(usage).build());
            bucketStart = bucketEnd;
            bucketEnd += NetworkCycleData.BUCKET_DURATION_MS;
        }
        return data;
    }

    public static class Builder {
        private final Context mContext;
        private NetworkPolicy mPolicy;
        private String mSubId;
        private int mNetworkType;
        private NetworkTemplate mNetworkTemplate;

        public Builder(Context context) {
            mContext = context;
        }

        public Builder setNetworkPolicy(NetworkPolicy policy) {
            mPolicy = policy;
            return this;
        }

        public Builder setSubscriberId(String subId) {
            mSubId = subId;
            return this;
        }

        public Builder setNetworkType(int networkType) {
            mNetworkType = networkType;
            return this;
        }

        public Builder setNetworkTemplate(NetworkTemplate template) {
            mNetworkTemplate = template;
            return this;
        }

        public NetworkCycleDataLoader build() {
            return new NetworkCycleDataLoader(this);
        }
    }

}
+9 −14
Original line number Diff line number Diff line
@@ -20,25 +20,23 @@ import android.app.usage.NetworkStatsManager;
import android.app.usage.NetworkStats;
import android.content.Context;
import android.os.RemoteException;
import android.telephony.TelephonyManager;
import android.util.Log;

import androidx.loader.content.AsyncTaskLoader;

/**
 * Loader for retrieving the network stats details for all UIDs.
 * Loader for retrieving the network stats summary for all UIDs.
 */
public class NetworkStatsDetailLoader extends AsyncTaskLoader<NetworkStats> {
public class NetworkStatsSummaryLoader extends AsyncTaskLoader<NetworkStats> {

    private static final String TAG = "NetworkDetailLoader";
    private final NetworkStatsManager mNetworkStatsManager;
    private final TelephonyManager mTelephonyManager;
    private final long mStart;
    private final long mEnd;
    private final int mSubId;
    private final String mSubId;
    private final int mNetworkType;

    private NetworkStatsDetailLoader(Builder builder) {
    private NetworkStatsSummaryLoader(Builder builder) {
        super(builder.mContext);
        mStart = builder.mStart;
        mEnd = builder.mEnd;
@@ -46,8 +44,6 @@ public class NetworkStatsDetailLoader extends AsyncTaskLoader<NetworkStats> {
        mNetworkType = builder.mNetworkType;
        mNetworkStatsManager = (NetworkStatsManager)
                builder.mContext.getSystemService(Context.NETWORK_STATS_SERVICE);
        mTelephonyManager =
                (TelephonyManager) builder.mContext.getSystemService(Context.TELEPHONY_SERVICE);
    }

    @Override
@@ -59,8 +55,7 @@ public class NetworkStatsDetailLoader extends AsyncTaskLoader<NetworkStats> {
    @Override
    public NetworkStats loadInBackground() {
        try {
            return mNetworkStatsManager.queryDetails(
                    mNetworkType, mTelephonyManager.getSubscriberId(mSubId), mStart, mEnd);
            return mNetworkStatsManager.querySummary(mNetworkType, mSubId, mStart, mEnd);
        } catch (RemoteException e) {
            Log.e(TAG, "Exception querying network detail.", e);
            return null;
@@ -83,7 +78,7 @@ public class NetworkStatsDetailLoader extends AsyncTaskLoader<NetworkStats> {
        private final Context mContext;
        private long mStart;
        private long mEnd;
        private int mSubId;
        private String mSubId;
        private int mNetworkType;

        public Builder(Context context) {
@@ -100,7 +95,7 @@ public class NetworkStatsDetailLoader extends AsyncTaskLoader<NetworkStats> {
            return this;
        }

        public Builder setSubscriptionId(int subId) {
        public Builder setSubscriberId(String subId) {
            mSubId = subId;
            return this;
        }
@@ -110,8 +105,8 @@ public class NetworkStatsDetailLoader extends AsyncTaskLoader<NetworkStats> {
            return this;
        }

        public NetworkStatsDetailLoader build() {
            return new NetworkStatsDetailLoader(this);
        public NetworkStatsSummaryLoader build() {
            return new NetworkStatsSummaryLoader(this);
        }
    }
}
+123 −0
Original line number Diff line number Diff line
/*
 * 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 static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Matchers.nullable;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

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.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.os.RemoteException;
import android.text.format.DateUtils;
import android.util.Range;

import com.android.settingslib.SettingsLibRobolectricTestRunner;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.util.ReflectionHelpers;

import java.time.ZonedDateTime;
import java.util.Iterator;

@RunWith(SettingsLibRobolectricTestRunner.class)
public class NetworkCycleDataLoaderTest {

    @Mock
    private NetworkStatsManager mNetworkStatsManager;
    @Mock
    private Context mContext;
    @Mock
    private NetworkPolicy mPolicy;
    @Mock
    private Iterator<Range<ZonedDateTime>> mIterator;
    @Mock
    private INetworkStatsService mNetworkStatsService;

    private NetworkCycleDataLoader mLoader;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        when(mContext.getSystemService(Context.NETWORK_STATS_SERVICE))
            .thenReturn(mNetworkStatsManager);
        when(mPolicy.cycleIterator()).thenReturn(mIterator);
    }

    @Test
    public void loadInBackground_noNetworkPolicy_shouldLoad4WeeksData() {
        mLoader = spy(new NetworkCycleDataLoader.Builder(mContext).build());
        doReturn(null).when(mLoader).loadFourWeeksData();

        mLoader.loadInBackground();

        verify(mLoader).loadFourWeeksData();
    }

    @Test
    public void loadInBackground_shouldQueryNetworkSummary() throws RemoteException {
        final int networkType = ConnectivityManager.TYPE_MOBILE;
        final String subId = "TestSubscriber";
        final ZonedDateTime now = ZonedDateTime.now();
        final Range<ZonedDateTime> cycle = new Range<>(now, now);
        // mock 1 cycle data.
        // hasNext() will be called internally in next(), hence setting it to return true twice.
        when(mIterator.hasNext()).thenReturn(true).thenReturn(true).thenReturn(false);
        when(mIterator.next()).thenReturn(cycle);
        mLoader = new NetworkCycleDataLoader.Builder(mContext)
            .setNetworkPolicy(mPolicy).setNetworkType(networkType).setSubscriberId(subId).build();

        mLoader.loadInBackground();

        verify(mNetworkStatsManager).querySummary(eq(networkType), eq(subId), anyLong(), anyLong());
    }

    @Test
    public void loadFourWeeksData_shouldGetUsageForLast4Weeks() throws RemoteException {
        mLoader = spy(new NetworkCycleDataLoader.Builder(mContext).build());
        ReflectionHelpers.setField(mLoader, "mNetworkStatsService", mNetworkStatsService);
        final INetworkStatsSession networkSession = mock(INetworkStatsSession.class);
        when(mNetworkStatsService.openSession()).thenReturn(networkSession);
        final NetworkStatsHistory networkHistory = mock(NetworkStatsHistory.class);
        when(networkSession.getHistoryForNetwork(nullable(NetworkTemplate.class), anyInt())).thenReturn(networkHistory);
        final long now = System.currentTimeMillis();
        final long fourWeeksAgo = now - (DateUtils.WEEK_IN_MILLIS * 4);
        when(networkHistory.getStart()).thenReturn(fourWeeksAgo);
        when(networkHistory.getEnd()).thenReturn(now);

        mLoader.loadFourWeeksData();

        verify(mLoader).getUsage(eq(fourWeeksAgo), eq(now), any());
    }
}