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

Commit 0d8e4b43 authored by Doris Ling's avatar Doris Ling
Browse files

Add loader for netork cycle data.

- Add the loader to retrieve usage data for each billing cycle.
- Rename NetworkStatsSummaryLoader and change its param from
subscription id to subscriber id.

Bug: 111751694
Test: make RunSettingsLibRoboTests
Change-Id: I0b68a0805222098bd6d52e762887cef2fcea667a
parent 16cd6145
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());
    }
}