Home | History | Annotate | Download | only in cts
      1 /**
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy
      6  * of the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations
     14  * under the License.
     15  */
     16 
     17 package android.app.usage.cts;
     18 
     19 import android.app.AppOpsManager;
     20 import android.app.usage.NetworkStatsManager;
     21 import android.app.usage.NetworkStats;
     22 import android.content.Context;
     23 import android.content.pm.PackageManager;
     24 import android.net.ConnectivityManager;
     25 import android.net.Network;
     26 import android.net.NetworkCapabilities;
     27 import android.net.NetworkInfo;
     28 import android.net.NetworkRequest;
     29 import android.net.TrafficStats;
     30 import android.os.Handler;
     31 import android.os.HandlerThread;
     32 import android.os.ParcelFileDescriptor;
     33 import android.os.Process;
     34 import android.os.RemoteException;
     35 import android.platform.test.annotations.AppModeFull;
     36 import android.telephony.TelephonyManager;
     37 import android.test.InstrumentationTestCase;
     38 import android.util.Log;
     39 
     40 import com.android.compatibility.common.util.ShellIdentityUtils;
     41 import com.android.compatibility.common.util.SystemUtil;
     42 
     43 import java.io.FileInputStream;
     44 import java.io.IOException;
     45 import java.io.InputStream;
     46 import java.io.InputStreamReader;
     47 import java.net.URL;
     48 import java.text.MessageFormat;
     49 import java.util.ArrayList;
     50 import java.util.Scanner;
     51 import java.net.HttpURLConnection;
     52 
     53 import libcore.io.IoUtils;
     54 import libcore.io.Streams;
     55 
     56 import static android.app.usage.NetworkStats.Bucket.DEFAULT_NETWORK_ALL;
     57 import static android.app.usage.NetworkStats.Bucket.DEFAULT_NETWORK_NO;
     58 import static android.app.usage.NetworkStats.Bucket.DEFAULT_NETWORK_YES;
     59 import static android.app.usage.NetworkStats.Bucket.METERED_ALL;
     60 import static android.app.usage.NetworkStats.Bucket.METERED_YES;
     61 import static android.app.usage.NetworkStats.Bucket.METERED_NO;
     62 import static android.app.usage.NetworkStats.Bucket.STATE_ALL;
     63 import static android.app.usage.NetworkStats.Bucket.STATE_DEFAULT;
     64 import static android.app.usage.NetworkStats.Bucket.STATE_FOREGROUND;
     65 import static android.app.usage.NetworkStats.Bucket.TAG_NONE;
     66 import static android.app.usage.NetworkStats.Bucket.UID_ALL;
     67 
     68 public class NetworkUsageStatsTest extends InstrumentationTestCase {
     69     private static final String LOG_TAG = "NetworkUsageStatsTest";
     70     private static final String APPOPS_SET_SHELL_COMMAND = "appops set {0} {1} {2}";
     71     private static final String APPOPS_GET_SHELL_COMMAND = "appops get {0} {1}";
     72 
     73     private static final long MINUTE = 1000 * 60;
     74     private static final int TIMEOUT_MILLIS = 15000;
     75 
     76     private static final String CHECK_CONNECTIVITY_URL = "http://www.265.com/";
     77     private static final String CHECK_CALLBACK_URL = CHECK_CONNECTIVITY_URL;
     78 
     79     private static final int NETWORK_TAG = 0xf00d;
     80     private static final long THRESHOLD_BYTES = 2 * 1024 * 1024;  // 2 MB
     81 
     82     private abstract class NetworkInterfaceToTest {
     83         private boolean mMetered;
     84         private boolean mIsDefault;
     85 
     86         abstract int getNetworkType();
     87         abstract int getTransportType();
     88 
     89         public boolean getMetered() {
     90             return mMetered;
     91         }
     92 
     93         public void setMetered(boolean metered) {
     94             this.mMetered = metered;
     95         }
     96 
     97         public boolean getIsDefault() {
     98             return mIsDefault;
     99         }
    100 
    101         public void setIsDefault(boolean isDefault) {
    102             mIsDefault = isDefault;
    103         }
    104 
    105         abstract String getSystemFeature();
    106         abstract String getErrorMessage();
    107     }
    108 
    109     private final NetworkInterfaceToTest[] mNetworkInterfacesToTest =
    110             new NetworkInterfaceToTest[] {
    111                     new NetworkInterfaceToTest() {
    112                         @Override
    113                         public int getNetworkType() {
    114                             return ConnectivityManager.TYPE_WIFI;
    115                         }
    116 
    117                         @Override
    118                         public int getTransportType() {
    119                             return NetworkCapabilities.TRANSPORT_WIFI;
    120                         }
    121 
    122                         @Override
    123                         public String getSystemFeature() {
    124                             return PackageManager.FEATURE_WIFI;
    125                         }
    126 
    127                         @Override
    128                         public String getErrorMessage() {
    129                             return " Please make sure you are connected to a WiFi access point.";
    130                         }
    131                     },
    132                     new NetworkInterfaceToTest() {
    133                         @Override
    134                         public int getNetworkType() {
    135                             return ConnectivityManager.TYPE_MOBILE;
    136                         }
    137 
    138                         @Override
    139                         public int getTransportType() {
    140                             return NetworkCapabilities.TRANSPORT_CELLULAR;
    141                         }
    142 
    143                         @Override
    144                         public String getSystemFeature() {
    145                             return PackageManager.FEATURE_TELEPHONY;
    146                         }
    147 
    148                         @Override
    149                         public String getErrorMessage() {
    150                             return " Please make sure you have added a SIM card with data plan to" +
    151                                     " your phone, have enabled data over cellular and in case of" +
    152                                     " dual SIM devices, have selected the right SIM " +
    153                                     "for data connection.";
    154                         }
    155                     }
    156     };
    157 
    158     private String mPkg;
    159     private NetworkStatsManager mNsm;
    160     private ConnectivityManager mCm;
    161     private PackageManager mPm;
    162     private long mStartTime;
    163     private long mEndTime;
    164 
    165     private long mBytesRead;
    166     private String mWriteSettingsMode;
    167     private String mUsageStatsMode;
    168 
    169     private void exerciseRemoteHost(Network network, URL url) throws Exception {
    170         NetworkInfo networkInfo = mCm.getNetworkInfo(network);
    171         if (networkInfo == null) {
    172             Log.w(LOG_TAG, "Network info is null");
    173         } else {
    174             Log.w(LOG_TAG, "Network: " + networkInfo.toString());
    175         }
    176         InputStreamReader in = null;
    177         HttpURLConnection urlc = null;
    178         String originalKeepAlive = System.getProperty("http.keepAlive");
    179         System.setProperty("http.keepAlive", "false");
    180         try {
    181             TrafficStats.setThreadStatsTag(NETWORK_TAG);
    182             urlc = (HttpURLConnection) network.openConnection(url);
    183             urlc.setConnectTimeout(TIMEOUT_MILLIS);
    184             urlc.setUseCaches(false);
    185             // Disable compression so we generate enough traffic that assertWithinPercentage will
    186             // not be affected by the small amount of traffic (5-10kB) sent by the test harness.
    187             urlc.setRequestProperty("Accept-Encoding", "identity");
    188             urlc.connect();
    189             boolean ping = urlc.getResponseCode() == 200;
    190             if (ping) {
    191                 in = new InputStreamReader(
    192                         (InputStream) urlc.getContent());
    193 
    194                 mBytesRead = 0;
    195                 while (in.read() != -1) ++mBytesRead;
    196             }
    197         } catch (Exception e) {
    198             Log.i(LOG_TAG, "Badness during exercising remote server: " + e);
    199         } finally {
    200             TrafficStats.clearThreadStatsTag();
    201             if (in != null) {
    202                 try {
    203                     in.close();
    204                 } catch (IOException e) {
    205                     // don't care
    206                 }
    207             }
    208             if (urlc != null) {
    209                 urlc.disconnect();
    210             }
    211             if (originalKeepAlive == null) {
    212                 System.clearProperty("http.keepAlive");
    213             } else {
    214                 System.setProperty("http.keepAlive", originalKeepAlive);
    215             }
    216         }
    217     }
    218 
    219     @Override
    220     protected void setUp() throws Exception {
    221         super.setUp();
    222         mNsm = (NetworkStatsManager) getInstrumentation().getContext()
    223                 .getSystemService(Context.NETWORK_STATS_SERVICE);
    224         mNsm.setPollForce(true);
    225 
    226         mCm = (ConnectivityManager) getInstrumentation().getContext()
    227                 .getSystemService(Context.CONNECTIVITY_SERVICE);
    228 
    229         mPm = getInstrumentation().getContext().getPackageManager();
    230 
    231         mPkg = getInstrumentation().getContext().getPackageName();
    232 
    233         mWriteSettingsMode = getAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS);
    234         setAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS, "allow");
    235         mUsageStatsMode = getAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS);
    236     }
    237 
    238     @Override
    239     protected void tearDown() throws Exception {
    240         if (mWriteSettingsMode != null) {
    241             setAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS, mWriteSettingsMode);
    242         }
    243         if (mUsageStatsMode != null) {
    244             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, mUsageStatsMode);
    245         }
    246         super.tearDown();
    247     }
    248 
    249     private void setAppOpsMode(String appop, String mode) throws Exception {
    250         final String command = MessageFormat.format(APPOPS_SET_SHELL_COMMAND, mPkg, appop, mode);
    251         SystemUtil.runShellCommand(command);
    252     }
    253 
    254     private String getAppOpsMode(String appop) throws Exception {
    255         final String command = MessageFormat.format(APPOPS_GET_SHELL_COMMAND, mPkg, appop);
    256         String result = SystemUtil.runShellCommand(command);
    257         if (result == null) {
    258             Log.w(LOG_TAG, "App op " + appop + " could not be read.");
    259         }
    260         return result;
    261     }
    262 
    263     private boolean isInForeground() throws IOException {
    264         String result = SystemUtil.runShellCommand(getInstrumentation(),
    265                 "cmd activity get-uid-state " + Process.myUid());
    266         return result.contains("FOREGROUND");
    267     }
    268 
    269     private class NetworkCallback extends ConnectivityManager.NetworkCallback {
    270         private long mTolerance;
    271         private URL mUrl;
    272         public boolean success;
    273         public boolean metered;
    274         public boolean isDefault;
    275 
    276         NetworkCallback(long tolerance, URL url) {
    277             mTolerance = tolerance;
    278             mUrl = url;
    279             success = false;
    280             metered = false;
    281             isDefault = false;
    282         }
    283 
    284         @Override
    285         public void onAvailable(Network network) {
    286             try {
    287                 mStartTime = System.currentTimeMillis() - mTolerance;
    288                 isDefault = network.equals(mCm.getActiveNetwork());
    289                 exerciseRemoteHost(network, mUrl);
    290                 mEndTime = System.currentTimeMillis() + mTolerance;
    291                 success = true;
    292                 metered = !mCm.getNetworkCapabilities(network)
    293                         .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
    294                 synchronized(NetworkUsageStatsTest.this) {
    295                     NetworkUsageStatsTest.this.notify();
    296                 }
    297             } catch (Exception e) {
    298                 Log.w(LOG_TAG, "exercising remote host failed.", e);
    299                 success = false;
    300             }
    301         }
    302     }
    303 
    304     private boolean shouldTestThisNetworkType(int networkTypeIndex, final long tolerance)
    305             throws Exception {
    306         boolean hasFeature = mPm.hasSystemFeature(
    307                 mNetworkInterfacesToTest[networkTypeIndex].getSystemFeature());
    308         if (!hasFeature) {
    309             return false;
    310         }
    311         NetworkCallback callback = new NetworkCallback(tolerance, new URL(CHECK_CONNECTIVITY_URL));
    312         mCm.requestNetwork(new NetworkRequest.Builder()
    313                 .addTransportType(mNetworkInterfacesToTest[networkTypeIndex].getTransportType())
    314                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
    315                 .build(), callback);
    316         synchronized(this) {
    317             try {
    318                 wait((int)(TIMEOUT_MILLIS * 1.2));
    319             } catch (InterruptedException e) {
    320             }
    321         }
    322         if (callback.success) {
    323             mNetworkInterfacesToTest[networkTypeIndex].setMetered(callback.metered);
    324             mNetworkInterfacesToTest[networkTypeIndex].setIsDefault(callback.isDefault);
    325             return true;
    326         }
    327 
    328         // This will always fail at this point as we know 'hasFeature' is true.
    329         assertFalse (mNetworkInterfacesToTest[networkTypeIndex].getSystemFeature() +
    330                 " is a reported system feature, " +
    331                 "however no corresponding connected network interface was found or the attempt " +
    332                 "to connect has timed out (timeout = " + TIMEOUT_MILLIS + "ms)." +
    333                 mNetworkInterfacesToTest[networkTypeIndex].getErrorMessage(), hasFeature);
    334         return false;
    335     }
    336 
    337     private String getSubscriberId(int networkIndex) {
    338         int networkType = mNetworkInterfacesToTest[networkIndex].getNetworkType();
    339         if (ConnectivityManager.TYPE_MOBILE == networkType) {
    340             TelephonyManager tm = (TelephonyManager) getInstrumentation().getContext()
    341                     .getSystemService(Context.TELEPHONY_SERVICE);
    342             return ShellIdentityUtils.invokeMethodWithShellPermissions(tm,
    343                     (telephonyManager) -> telephonyManager.getSubscriberId());
    344         }
    345         return "";
    346     }
    347 
    348     @AppModeFull
    349     public void testDeviceSummary() throws Exception {
    350         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
    351             if (!shouldTestThisNetworkType(i, MINUTE/2)) {
    352                 continue;
    353             }
    354             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
    355             NetworkStats.Bucket bucket = null;
    356             try {
    357                 bucket = mNsm.querySummaryForDevice(
    358                         mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
    359                         mStartTime, mEndTime);
    360             } catch (RemoteException | SecurityException e) {
    361                 fail("testDeviceSummary fails with exception: " + e.toString());
    362             }
    363             assertNotNull(bucket);
    364             assertTimestamps(bucket);
    365             assertEquals(bucket.getState(), STATE_ALL);
    366             assertEquals(bucket.getUid(), UID_ALL);
    367             assertEquals(bucket.getMetered(), METERED_ALL);
    368             assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
    369             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
    370             try {
    371                 bucket = mNsm.querySummaryForDevice(
    372                         mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
    373                         mStartTime, mEndTime);
    374                 fail("negative testDeviceSummary fails: no exception thrown.");
    375             } catch (RemoteException e) {
    376                 fail("testDeviceSummary fails with exception: " + e.toString());
    377             } catch (SecurityException e) {
    378                 // expected outcome
    379             }
    380         }
    381     }
    382 
    383     @AppModeFull
    384     public void testUserSummary() throws Exception {
    385         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
    386             if (!shouldTestThisNetworkType(i, MINUTE/2)) {
    387                 continue;
    388             }
    389             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
    390             NetworkStats.Bucket bucket = null;
    391             try {
    392                 bucket = mNsm.querySummaryForUser(
    393                         mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
    394                         mStartTime, mEndTime);
    395             } catch (RemoteException | SecurityException e) {
    396                 fail("testUserSummary fails with exception: " + e.toString());
    397             }
    398             assertNotNull(bucket);
    399             assertTimestamps(bucket);
    400             assertEquals(bucket.getState(), STATE_ALL);
    401             assertEquals(bucket.getUid(), UID_ALL);
    402             assertEquals(bucket.getMetered(), METERED_ALL);
    403             assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
    404             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
    405             try {
    406                 bucket = mNsm.querySummaryForUser(
    407                         mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
    408                         mStartTime, mEndTime);
    409                 fail("negative testUserSummary fails: no exception thrown.");
    410             } catch (RemoteException e) {
    411                 fail("testUserSummary fails with exception: " + e.toString());
    412             } catch (SecurityException e) {
    413                 // expected outcome
    414             }
    415         }
    416     }
    417 
    418     @AppModeFull
    419     public void testAppSummary() throws Exception {
    420         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
    421             if (!shouldTestThisNetworkType(i, MINUTE/2)) {
    422                 continue;
    423             }
    424             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
    425             NetworkStats result = null;
    426             try {
    427                 result = mNsm.querySummary(
    428                         mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
    429                         mStartTime, mEndTime);
    430                 assertNotNull(result);
    431                 NetworkStats.Bucket bucket = new NetworkStats.Bucket();
    432                 long totalTxPackets = 0;
    433                 long totalRxPackets = 0;
    434                 long totalTxBytes = 0;
    435                 long totalRxBytes = 0;
    436                 boolean hasCorrectMetering = false;
    437                 boolean hasCorrectDefaultStatus = false;
    438                 int expectedMetering = mNetworkInterfacesToTest[i].getMetered() ?
    439                         METERED_YES : METERED_NO;
    440                 int expectedDefaultStatus = mNetworkInterfacesToTest[i].getIsDefault() ?
    441                         DEFAULT_NETWORK_YES : DEFAULT_NETWORK_NO;
    442                 while (result.hasNextBucket()) {
    443                     assertTrue(result.getNextBucket(bucket));
    444                     assertTimestamps(bucket);
    445                     hasCorrectMetering |= bucket.getMetered() == expectedMetering;
    446                     if (bucket.getUid() == Process.myUid()) {
    447                         totalTxPackets += bucket.getTxPackets();
    448                         totalRxPackets += bucket.getRxPackets();
    449                         totalTxBytes += bucket.getTxBytes();
    450                         totalRxBytes += bucket.getRxBytes();
    451                         hasCorrectDefaultStatus |=
    452                                 bucket.getDefaultNetworkStatus() == expectedDefaultStatus;
    453                     }
    454                 }
    455                 assertFalse(result.getNextBucket(bucket));
    456                 assertTrue("Incorrect metering for NetworkType: " +
    457                         mNetworkInterfacesToTest[i].getNetworkType(), hasCorrectMetering);
    458                 assertTrue("Incorrect isDefault for NetworkType: " +
    459                         mNetworkInterfacesToTest[i].getNetworkType(), hasCorrectDefaultStatus);
    460                 assertTrue("No Rx bytes usage for uid " + Process.myUid(), totalRxBytes > 0);
    461                 assertTrue("No Rx packets usage for uid " + Process.myUid(), totalRxPackets > 0);
    462                 assertTrue("No Tx bytes usage for uid " + Process.myUid(), totalTxBytes > 0);
    463                 assertTrue("No Tx packets usage for uid " + Process.myUid(), totalTxPackets > 0);
    464             } finally {
    465                 if (result != null) {
    466                     result.close();
    467                 }
    468             }
    469             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
    470             try {
    471                 result = mNsm.querySummary(
    472                         mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
    473                         mStartTime, mEndTime);
    474                 fail("negative testAppSummary fails: no exception thrown.");
    475             } catch (RemoteException e) {
    476                 fail("testAppSummary fails with exception: " + e.toString());
    477             } catch (SecurityException e) {
    478                 // expected outcome
    479             }
    480         }
    481     }
    482 
    483     @AppModeFull
    484     public void testAppDetails() throws Exception {
    485         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
    486             // Relatively large tolerance to accommodate for history bucket size.
    487             if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
    488                 continue;
    489             }
    490             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
    491             NetworkStats result = null;
    492             try {
    493                 result = mNsm.queryDetails(
    494                         mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
    495                         mStartTime, mEndTime);
    496                 long totalBytesWithSubscriberId = getTotalAndAssertNotEmpty(result);
    497 
    498                 // Test without filtering by subscriberId
    499                 result = mNsm.queryDetails(
    500                         mNetworkInterfacesToTest[i].getNetworkType(), null,
    501                         mStartTime, mEndTime);
    502 
    503                 assertTrue("More bytes with subscriberId filter than without.",
    504                         getTotalAndAssertNotEmpty(result) >= totalBytesWithSubscriberId);
    505             } catch (RemoteException | SecurityException e) {
    506                 fail("testAppDetails fails with exception: " + e.toString());
    507             } finally {
    508                 if (result != null) {
    509                     result.close();
    510                 }
    511             }
    512             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
    513             try {
    514                 result = mNsm.queryDetails(
    515                         mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
    516                         mStartTime, mEndTime);
    517                 fail("negative testAppDetails fails: no exception thrown.");
    518             } catch (RemoteException e) {
    519                 fail("testAppDetails fails with exception: " + e.toString());
    520             } catch (SecurityException e) {
    521                 // expected outcome
    522             }
    523         }
    524     }
    525 
    526     @AppModeFull
    527     public void testUidDetails() throws Exception {
    528         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
    529             // Relatively large tolerance to accommodate for history bucket size.
    530             if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
    531                 continue;
    532             }
    533             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
    534             NetworkStats result = null;
    535             try {
    536                 result = mNsm.queryDetailsForUid(
    537                         mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
    538                         mStartTime, mEndTime, Process.myUid());
    539                 assertNotNull(result);
    540                 NetworkStats.Bucket bucket = new NetworkStats.Bucket();
    541                 long totalTxPackets = 0;
    542                 long totalRxPackets = 0;
    543                 long totalTxBytes = 0;
    544                 long totalRxBytes = 0;
    545                 while (result.hasNextBucket()) {
    546                     assertTrue(result.getNextBucket(bucket));
    547                     assertTimestamps(bucket);
    548                     assertEquals(bucket.getState(), STATE_ALL);
    549                     assertEquals(bucket.getMetered(), METERED_ALL);
    550                     assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
    551                     assertEquals(bucket.getUid(), Process.myUid());
    552                     totalTxPackets += bucket.getTxPackets();
    553                     totalRxPackets += bucket.getRxPackets();
    554                     totalTxBytes += bucket.getTxBytes();
    555                     totalRxBytes += bucket.getRxBytes();
    556                 }
    557                 assertFalse(result.getNextBucket(bucket));
    558                 assertTrue("No Rx bytes usage for uid " + Process.myUid(), totalRxBytes > 0);
    559                 assertTrue("No Rx packets usage for uid " + Process.myUid(), totalRxPackets > 0);
    560                 assertTrue("No Tx bytes usage for uid " + Process.myUid(), totalTxBytes > 0);
    561                 assertTrue("No Tx packets usage for uid " + Process.myUid(), totalTxPackets > 0);
    562             } finally {
    563                 if (result != null) {
    564                     result.close();
    565                 }
    566             }
    567             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
    568             try {
    569                 result = mNsm.queryDetailsForUid(
    570                         mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
    571                         mStartTime, mEndTime, Process.myUid());
    572                 fail("negative testUidDetails fails: no exception thrown.");
    573             } catch (SecurityException e) {
    574                 // expected outcome
    575             }
    576         }
    577     }
    578 
    579     @AppModeFull
    580     public void testTagDetails() throws Exception {
    581         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
    582             // Relatively large tolerance to accommodate for history bucket size.
    583             if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
    584                 continue;
    585             }
    586             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
    587             NetworkStats result = null;
    588             try {
    589                 result = mNsm.queryDetailsForUidTag(
    590                         mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
    591                         mStartTime, mEndTime, Process.myUid(), NETWORK_TAG);
    592                 assertNotNull(result);
    593                 NetworkStats.Bucket bucket = new NetworkStats.Bucket();
    594                 long totalTxPackets = 0;
    595                 long totalRxPackets = 0;
    596                 long totalTxBytes = 0;
    597                 long totalRxBytes = 0;
    598                 while (result.hasNextBucket()) {
    599                     assertTrue(result.getNextBucket(bucket));
    600                     assertTimestamps(bucket);
    601                     assertEquals(bucket.getState(), STATE_ALL);
    602                     assertEquals(bucket.getMetered(), METERED_ALL);
    603                     assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
    604                     assertEquals(bucket.getUid(), Process.myUid());
    605                     if (bucket.getTag() == NETWORK_TAG) {
    606                         totalTxPackets += bucket.getTxPackets();
    607                         totalRxPackets += bucket.getRxPackets();
    608                         totalTxBytes += bucket.getTxBytes();
    609                         totalRxBytes += bucket.getRxBytes();
    610                     }
    611                 }
    612                 assertTrue("No Rx bytes tagged with 0x" + Integer.toHexString(NETWORK_TAG)
    613                         + " for uid " + Process.myUid(), totalRxBytes > 0);
    614                 assertTrue("No Rx packets tagged with 0x" + Integer.toHexString(NETWORK_TAG)
    615                         + " for uid " + Process.myUid(), totalRxPackets > 0);
    616                 assertTrue("No Tx bytes tagged with 0x" + Integer.toHexString(NETWORK_TAG)
    617                         + " for uid " + Process.myUid(), totalTxBytes > 0);
    618                 assertTrue("No Tx packets tagged with 0x" + Integer.toHexString(NETWORK_TAG)
    619                         + " for uid " + Process.myUid(), totalTxPackets > 0);
    620             } finally {
    621                 if (result != null) {
    622                     result.close();
    623                 }
    624             }
    625             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
    626             try {
    627                 result = mNsm.queryDetailsForUidTag(
    628                         mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
    629                         mStartTime, mEndTime, Process.myUid(), NETWORK_TAG);
    630                 fail("negative testUidDetails fails: no exception thrown.");
    631             } catch (SecurityException e) {
    632                 // expected outcome
    633             }
    634         }
    635     }
    636 
    637     class QueryResult {
    638         public final int tag;
    639         public final int state;
    640         public final long total;
    641 
    642         public QueryResult(int tag, int state, NetworkStats stats) {
    643             this.tag = tag;
    644             this.state = state;
    645             total = getTotalAndAssertNotEmpty(stats, tag, state);
    646         }
    647 
    648         public String toString() {
    649             return String.format("QueryResult(tag=%s state=%s total=%d)",
    650                     tagToString(tag), stateToString(state), total);
    651         }
    652     }
    653 
    654     private NetworkStats getNetworkStatsForTagState(int i, int tag, int state) {
    655         return mNsm.queryDetailsForUidTagState(
    656                 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
    657                 mStartTime, mEndTime, Process.myUid(), tag, state);
    658     }
    659 
    660     private void assertWithinPercentage(String msg, long expected, long actual, int percentage) {
    661         long lowerBound = expected * (100 - percentage) / 100;
    662         long upperBound = expected * (100 + percentage) / 100;
    663         msg = String.format("%s: %d not within %d%% of %d", msg, actual, percentage, expected);
    664         assertTrue(msg, lowerBound <= actual);
    665         assertTrue(msg, upperBound >= actual);
    666     }
    667 
    668     private void assertAlmostNoUnexpectedTraffic(NetworkStats result, int expectedTag,
    669             int expectedState, long maxUnexpected) {
    670         long total = 0;
    671         NetworkStats.Bucket bucket = new NetworkStats.Bucket();
    672         while (result.hasNextBucket()) {
    673             assertTrue(result.getNextBucket(bucket));
    674             total += bucket.getRxBytes() + bucket.getTxBytes();
    675         }
    676         if (total <= maxUnexpected) return;
    677 
    678         fail(String.format("More than %d bytes of traffic when querying for "
    679                 + "tag %s state %s. Last bucket: uid=%d tag=%s state=%s bytes=%d/%d",
    680                 maxUnexpected, tagToString(expectedTag), stateToString(expectedState),
    681                 bucket.getUid(), tagToString(bucket.getTag()), stateToString(bucket.getState()),
    682                 bucket.getRxBytes(), bucket.getTxBytes()));
    683     }
    684 
    685     @AppModeFull
    686     public void testUidTagStateDetails() throws Exception {
    687         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
    688             // Relatively large tolerance to accommodate for history bucket size.
    689             if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
    690                 continue;
    691             }
    692             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
    693             NetworkStats result = null;
    694             try {
    695                 int currentState = isInForeground() ? STATE_FOREGROUND : STATE_DEFAULT;
    696                 int otherState = (currentState == STATE_DEFAULT) ? STATE_FOREGROUND : STATE_DEFAULT;
    697 
    698                 int[] tagsWithTraffic = {NETWORK_TAG, TAG_NONE};
    699                 int[] statesWithTraffic = {currentState, STATE_ALL};
    700                 ArrayList<QueryResult> resultsWithTraffic = new ArrayList<>();
    701 
    702                 int[] statesWithNoTraffic = {otherState};
    703                 int[] tagsWithNoTraffic = {NETWORK_TAG + 1};
    704                 ArrayList<QueryResult> resultsWithNoTraffic = new ArrayList<>();
    705 
    706                 // Expect to see traffic when querying for any combination of a tag in
    707                 // tagsWithTraffic and a state in statesWithTraffic.
    708                 for (int tag : tagsWithTraffic) {
    709                     for (int state : statesWithTraffic) {
    710                         result = getNetworkStatsForTagState(i, tag, state);
    711                         resultsWithTraffic.add(new QueryResult(tag, state, result));
    712                         result.close();
    713                         result = null;
    714                     }
    715                 }
    716 
    717                 // Expect that the results are within a few percentage points of each other.
    718                 // This is ensures that FIN retransmits after the transfer is complete don't cause
    719                 // the test to be flaky. The test URL currently returns just over 100k so this
    720                 // should not be too noisy. It also ensures that the traffic sent by the test
    721                 // harness, which is untagged, won't cause a failure.
    722                 long firstTotal = resultsWithTraffic.get(0).total;
    723                 for (QueryResult queryResult : resultsWithTraffic) {
    724                     assertWithinPercentage(queryResult + "", firstTotal, queryResult.total, 10);
    725                 }
    726 
    727                 // Expect to see no traffic when querying for any tag in tagsWithNoTraffic or any
    728                 // state in statesWithNoTraffic.
    729                 for (int tag : tagsWithNoTraffic) {
    730                     for (int state : statesWithTraffic) {
    731                         result = getNetworkStatsForTagState(i, tag, state);
    732                         assertAlmostNoUnexpectedTraffic(result, tag, state, firstTotal / 100);
    733                         result.close();
    734                         result = null;
    735                     }
    736                 }
    737                 for (int tag : tagsWithTraffic) {
    738                     for (int state : statesWithNoTraffic) {
    739                         result = getNetworkStatsForTagState(i, tag, state);
    740                         assertAlmostNoUnexpectedTraffic(result, tag, state, firstTotal / 100);
    741                         result.close();
    742                         result = null;
    743                     }
    744                 }
    745             } finally {
    746                 if (result != null) {
    747                     result.close();
    748                 }
    749             }
    750             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
    751             try {
    752                 result = mNsm.queryDetailsForUidTag(
    753                         mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
    754                         mStartTime, mEndTime, Process.myUid(), NETWORK_TAG);
    755                 fail("negative testUidDetails fails: no exception thrown.");
    756             } catch (SecurityException e) {
    757                 // expected outcome
    758             }
    759         }
    760     }
    761 
    762     @AppModeFull
    763     public void testCallback() throws Exception {
    764         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
    765             // Relatively large tolerance to accommodate for history bucket size.
    766             if (!shouldTestThisNetworkType(i, MINUTE/2)) {
    767                 continue;
    768             }
    769             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
    770 
    771             TestUsageCallback usageCallback = new TestUsageCallback();
    772             HandlerThread thread = new HandlerThread("callback-thread");
    773             thread.start();
    774             Handler handler = new Handler(thread.getLooper());
    775             mNsm.registerUsageCallback(mNetworkInterfacesToTest[i].getNetworkType(),
    776                     getSubscriberId(i), THRESHOLD_BYTES, usageCallback, handler);
    777 
    778             // TODO: Force traffic and check whether the callback is invoked.
    779             // Right now the test only covers whether the callback can be registered, but not
    780             // whether it is invoked upon data usage since we don't have a scalable way of
    781             // storing files of >2MB in CTS.
    782 
    783             mNsm.unregisterUsageCallback(usageCallback);
    784         }
    785     }
    786 
    787     private String tagToString(Integer tag) {
    788         if (tag == null) return "null";
    789         switch (tag) {
    790             case TAG_NONE:
    791                 return "TAG_NONE";
    792             default:
    793                 return "0x" + Integer.toHexString(tag);
    794         }
    795     }
    796 
    797     private String stateToString(Integer state) {
    798         if (state == null) return "null";
    799         switch (state) {
    800             case STATE_ALL:
    801                 return "STATE_ALL";
    802             case STATE_DEFAULT:
    803                 return "STATE_DEFAULT";
    804             case STATE_FOREGROUND:
    805                 return "STATE_FOREGROUND";
    806         }
    807         throw new IllegalArgumentException("Unknown state " + state);
    808     }
    809 
    810     private long getTotalAndAssertNotEmpty(NetworkStats result, Integer expectedTag,
    811             Integer expectedState) {
    812         assertTrue(result != null);
    813         NetworkStats.Bucket bucket = new NetworkStats.Bucket();
    814         long totalTxPackets = 0;
    815         long totalRxPackets = 0;
    816         long totalTxBytes = 0;
    817         long totalRxBytes = 0;
    818         while (result.hasNextBucket()) {
    819             assertTrue(result.getNextBucket(bucket));
    820             assertTimestamps(bucket);
    821             if (expectedTag != null) assertEquals(bucket.getTag(), (int) expectedTag);
    822             if (expectedState != null) assertEquals(bucket.getState(), (int) expectedState);
    823             assertEquals(bucket.getMetered(), METERED_ALL);
    824             assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
    825             if (bucket.getUid() == Process.myUid()) {
    826                 totalTxPackets += bucket.getTxPackets();
    827                 totalRxPackets += bucket.getRxPackets();
    828                 totalTxBytes += bucket.getTxBytes();
    829                 totalRxBytes += bucket.getRxBytes();
    830             }
    831         }
    832         assertFalse(result.getNextBucket(bucket));
    833         String msg = String.format("uid %d tag %s state %s",
    834                 Process.myUid(), tagToString(expectedTag), stateToString(expectedState));
    835         assertTrue("No Rx bytes usage for " + msg, totalRxBytes > 0);
    836         assertTrue("No Rx packets usage for " + msg, totalRxPackets > 0);
    837         assertTrue("No Tx bytes usage for " + msg, totalTxBytes > 0);
    838         assertTrue("No Tx packets usage for " + msg, totalTxPackets > 0);
    839 
    840         return totalRxBytes + totalTxBytes;
    841     }
    842 
    843     private long getTotalAndAssertNotEmpty(NetworkStats result) {
    844         return getTotalAndAssertNotEmpty(result, null, STATE_ALL);
    845     }
    846 
    847     private void assertTimestamps(final NetworkStats.Bucket bucket) {
    848         assertTrue("Start timestamp " + bucket.getStartTimeStamp() + " is less than " +
    849                 mStartTime, bucket.getStartTimeStamp() >= mStartTime);
    850         assertTrue("End timestamp " + bucket.getEndTimeStamp() + " is greater than " +
    851                 mEndTime, bucket.getEndTimeStamp() <= mEndTime);
    852     }
    853 
    854     private static class TestUsageCallback extends NetworkStatsManager.UsageCallback {
    855         @Override
    856         public void onThresholdReached(int networkType, String subscriberId) {
    857             Log.v(LOG_TAG, "Called onThresholdReached for networkType=" + networkType
    858                     + " subscriberId=" + subscriberId);
    859         }
    860     }
    861 }
    862