1 /* 2 * Copyright (C) 2011, The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.bandwidthtest; 18 19 import android.app.UiAutomation; 20 import android.content.Context; 21 import android.net.ConnectivityManager; 22 import android.net.NetworkInfo.State; 23 import android.net.NetworkStats; 24 import android.net.NetworkStats.Entry; 25 import android.net.TrafficStats; 26 import android.net.wifi.WifiManager; 27 import android.os.Bundle; 28 import android.os.Environment; 29 import android.os.Process; 30 import android.os.SystemClock; 31 import android.telephony.TelephonyManager; 32 import android.test.InstrumentationTestCase; 33 import android.util.Log; 34 35 import androidx.test.filters.LargeTest; 36 37 import com.android.bandwidthtest.util.BandwidthTestUtil; 38 import com.android.bandwidthtest.util.ConnectionUtil; 39 40 import java.io.File; 41 42 /** 43 * Test that downloads files from a test server and reports the bandwidth metrics collected. 44 */ 45 public class BandwidthTest extends InstrumentationTestCase { 46 47 private static final String LOG_TAG = "BandwidthTest"; 48 private final static String PROF_LABEL = "PROF_"; 49 private final static String PROC_LABEL = "PROC_"; 50 private final static int INSTRUMENTATION_IN_PROGRESS = 2; 51 52 private final static String BASE_DIR = 53 Environment.getExternalStorageDirectory().getAbsolutePath(); 54 private final static String TMP_FILENAME = "tmp.dat"; 55 // Download 10.486 * 106 bytes (+ headers) from app engine test server. 56 private final int FILE_SIZE = 10485613; 57 private Context mContext; 58 private ConnectionUtil mConnectionUtil; 59 private TelephonyManager mTManager; 60 private int mUid; 61 private String mSsid; 62 private String mTestServer; 63 private String mDeviceId; 64 private BandwidthTestRunner mRunner; 65 66 67 @Override setUp()68 protected void setUp() throws Exception { 69 super.setUp(); 70 mRunner = (BandwidthTestRunner) getInstrumentation(); 71 mSsid = mRunner.mSsid; 72 mTestServer = mRunner.mTestServer; 73 mContext = mRunner.getTargetContext(); 74 mConnectionUtil = new ConnectionUtil(mContext); 75 mConnectionUtil.initialize(); 76 Log.v(LOG_TAG, "Initialized mConnectionUtil"); 77 mUid = Process.myUid(); 78 mTManager = (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE); 79 final UiAutomation uiAutomation = getInstrumentation().getUiAutomation(); 80 try { 81 uiAutomation.adoptShellPermissionIdentity(); 82 mDeviceId = mTManager.getDeviceId(); 83 } finally { 84 uiAutomation.dropShellPermissionIdentity(); 85 } 86 } 87 88 @Override tearDown()89 protected void tearDown() throws Exception { 90 mConnectionUtil.cleanUp(); 91 super.tearDown(); 92 } 93 94 /** 95 * Ensure that downloading on wifi reports reasonable stats. 96 */ 97 @LargeTest testWifiDownload()98 public void testWifiDownload() throws Exception { 99 mConnectionUtil.wifiTestInit(); 100 assertTrue("Could not connect to wifi!", setDeviceWifiAndAirplaneMode(mSsid)); 101 downloadFile(); 102 } 103 104 /** 105 * Ensure that downloading on mobile reports reasonable stats. 106 */ 107 @LargeTest testMobileDownload()108 public void testMobileDownload() throws Exception { 109 // As part of the setup we disconnected from wifi; make sure we are connected to mobile and 110 // that we have data. 111 assertTrue("Do not have mobile data!", hasMobileData()); 112 downloadFile(); 113 } 114 115 /** 116 * Helper method that downloads a file using http connection from a test server and reports the 117 * data usage stats to instrumentation out. 118 */ downloadFile()119 protected void downloadFile() throws Exception { 120 NetworkStats pre_test_stats = fetchDataFromProc(mUid); 121 String ts = Long.toString(System.currentTimeMillis()); 122 123 String targetUrl = BandwidthTestUtil.buildDownloadUrl( 124 mTestServer, FILE_SIZE, mDeviceId, ts); 125 TrafficStats.startDataProfiling(mContext); 126 File tmpSaveFile = new File(BASE_DIR + File.separator + TMP_FILENAME); 127 assertTrue(BandwidthTestUtil.DownloadFromUrl(targetUrl, tmpSaveFile)); 128 NetworkStats prof_stats = TrafficStats.stopDataProfiling(mContext); 129 Log.d(LOG_TAG, prof_stats.toString()); 130 131 NetworkStats post_test_stats = fetchDataFromProc(mUid); 132 NetworkStats proc_stats = post_test_stats.subtract(pre_test_stats); 133 134 // Output measurements to instrumentation out, so that it can be compared to that of 135 // the server. 136 Bundle results = new Bundle(); 137 results.putString("device_id", mDeviceId); 138 results.putString("timestamp", ts); 139 results.putInt("size", FILE_SIZE); 140 addStatsToResults(PROF_LABEL, prof_stats, results, mUid); 141 addStatsToResults(PROC_LABEL, proc_stats, results, mUid); 142 getInstrumentation().sendStatus(INSTRUMENTATION_IN_PROGRESS, results); 143 144 // Clean up. 145 assertTrue(cleanUpFile(tmpSaveFile)); 146 } 147 148 /** 149 * Ensure that uploading on wifi reports reasonable stats. 150 */ 151 @LargeTest testWifiUpload()152 public void testWifiUpload() throws Exception { 153 mConnectionUtil.wifiTestInit(); 154 assertTrue(setDeviceWifiAndAirplaneMode(mSsid)); 155 uploadFile(); 156 } 157 158 /** 159 * Ensure that uploading on wifi reports reasonable stats. 160 */ 161 @LargeTest testMobileUpload()162 public void testMobileUpload() throws Exception { 163 assertTrue(hasMobileData()); 164 uploadFile(); 165 } 166 167 /** 168 * Helper method that downloads a test file to upload. The stats reported to instrumentation out 169 * only include upload stats. 170 */ uploadFile()171 protected void uploadFile() throws Exception { 172 // Download a file from the server. 173 String ts = Long.toString(System.currentTimeMillis()); 174 String targetUrl = BandwidthTestUtil.buildDownloadUrl( 175 mTestServer, FILE_SIZE, mDeviceId, ts); 176 File tmpSaveFile = new File(BASE_DIR + File.separator + TMP_FILENAME); 177 assertTrue(BandwidthTestUtil.DownloadFromUrl(targetUrl, tmpSaveFile)); 178 179 ts = Long.toString(System.currentTimeMillis()); 180 NetworkStats pre_test_stats = fetchDataFromProc(mUid); 181 TrafficStats.startDataProfiling(mContext); 182 assertTrue(BandwidthTestUtil.postFileToServer(mTestServer, mDeviceId, ts, tmpSaveFile)); 183 NetworkStats prof_stats = TrafficStats.stopDataProfiling(mContext); 184 Log.d(LOG_TAG, prof_stats.toString()); 185 NetworkStats post_test_stats = fetchDataFromProc(mUid); 186 NetworkStats proc_stats = post_test_stats.subtract(pre_test_stats); 187 188 // Output measurements to instrumentation out, so that it can be compared to that of 189 // the server. 190 Bundle results = new Bundle(); 191 results.putString("device_id", mDeviceId); 192 results.putString("timestamp", ts); 193 results.putInt("size", FILE_SIZE); 194 addStatsToResults(PROF_LABEL, prof_stats, results, mUid); 195 addStatsToResults(PROC_LABEL, proc_stats, results, mUid); 196 getInstrumentation().sendStatus(INSTRUMENTATION_IN_PROGRESS, results); 197 198 // Clean up. 199 assertTrue(cleanUpFile(tmpSaveFile)); 200 } 201 202 /** 203 * We want to make sure that if we use wifi and the Download Manager to download stuff, 204 * accounting still goes to the app making the call and that the numbers still make sense. 205 */ 206 @LargeTest testWifiDownloadWithDownloadManager()207 public void testWifiDownloadWithDownloadManager() throws Exception { 208 mConnectionUtil.wifiTestInit(); 209 assertTrue(setDeviceWifiAndAirplaneMode(mSsid)); 210 downloadFileUsingDownloadManager(); 211 } 212 213 /** 214 * We want to make sure that if we use mobile data and the Download Manager to download stuff, 215 * accounting still goes to the app making the call and that the numbers still make sense. 216 */ 217 @LargeTest testMobileDownloadWithDownloadManager()218 public void testMobileDownloadWithDownloadManager() throws Exception { 219 assertTrue(hasMobileData()); 220 downloadFileUsingDownloadManager(); 221 } 222 223 /** 224 * Helper method that downloads a file from a test server using the download manager and reports 225 * the stats to instrumentation out. 226 */ downloadFileUsingDownloadManager()227 protected void downloadFileUsingDownloadManager() throws Exception { 228 // If we are using the download manager, then the data that is written to /proc/uid_stat/ 229 // is accounted against download manager's uid, since it uses pre-ICS API. 230 int downloadManagerUid = mConnectionUtil.downloadManagerUid(); 231 assertTrue(downloadManagerUid >= 0); 232 NetworkStats pre_test_stats = fetchDataFromProc(downloadManagerUid); 233 // start profiling 234 TrafficStats.startDataProfiling(mContext); 235 String ts = Long.toString(System.currentTimeMillis()); 236 String targetUrl = BandwidthTestUtil.buildDownloadUrl( 237 mTestServer, FILE_SIZE, mDeviceId, ts); 238 Log.v(LOG_TAG, "Download url: " + targetUrl); 239 File tmpSaveFile = new File(BASE_DIR + File.separator + TMP_FILENAME); 240 assertTrue(mConnectionUtil.startDownloadAndWait(targetUrl, 500000)); 241 NetworkStats prof_stats = TrafficStats.stopDataProfiling(mContext); 242 NetworkStats post_test_stats = fetchDataFromProc(downloadManagerUid); 243 NetworkStats proc_stats = post_test_stats.subtract(pre_test_stats); 244 Log.d(LOG_TAG, prof_stats.toString()); 245 // Output measurements to instrumentation out, so that it can be compared to that of 246 // the server. 247 Bundle results = new Bundle(); 248 results.putString("device_id", mDeviceId); 249 results.putString("timestamp", ts); 250 results.putInt("size", FILE_SIZE); 251 addStatsToResults(PROF_LABEL, prof_stats, results, mUid); 252 // remember to use download manager uid for proc stats 253 addStatsToResults(PROC_LABEL, proc_stats, results, downloadManagerUid); 254 getInstrumentation().sendStatus(INSTRUMENTATION_IN_PROGRESS, results); 255 256 // Clean up. 257 assertTrue(cleanUpFile(tmpSaveFile)); 258 } 259 260 /** 261 * Fetch network data from /proc/uid_stat/uid 262 * 263 * @return populated {@link NetworkStats} 264 */ fetchDataFromProc(int uid)265 public NetworkStats fetchDataFromProc(int uid) { 266 String root_filepath = "/proc/uid_stat/" + uid + "/"; 267 File rcv_stat = new File (root_filepath + "tcp_rcv"); 268 int rx = BandwidthTestUtil.parseIntValueFromFile(rcv_stat); 269 File snd_stat = new File (root_filepath + "tcp_snd"); 270 int tx = BandwidthTestUtil.parseIntValueFromFile(snd_stat); 271 NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1); 272 stats.insertEntry(NetworkStats.IFACE_ALL, uid, NetworkStats.SET_DEFAULT, 273 NetworkStats.TAG_NONE, rx, 0, tx, 0, 0); 274 return stats; 275 } 276 277 /** 278 * Turn on Airplane mode and connect to the wifi. 279 * 280 * @param ssid of the wifi to connect to 281 * @return true if we successfully connected to a given network. 282 */ setDeviceWifiAndAirplaneMode(String ssid)283 public boolean setDeviceWifiAndAirplaneMode(String ssid) { 284 mConnectionUtil.setAirplaneMode(mContext, true); 285 assertTrue(mConnectionUtil.connectToWifi(ssid)); 286 assertTrue(mConnectionUtil.waitForWifiState(WifiManager.WIFI_STATE_ENABLED, 287 ConnectionUtil.LONG_TIMEOUT)); 288 assertTrue(mConnectionUtil.waitForNetworkState(ConnectivityManager.TYPE_WIFI, 289 State.CONNECTED, ConnectionUtil.LONG_TIMEOUT)); 290 return mConnectionUtil.hasData(); 291 } 292 293 /** 294 * Helper method to make sure we are connected to mobile data. 295 * 296 * @return true if we successfully connect to mobile data. 297 */ hasMobileData()298 public boolean hasMobileData() { 299 assertTrue(mConnectionUtil.waitForNetworkState(ConnectivityManager.TYPE_MOBILE, 300 State.CONNECTED, ConnectionUtil.LONG_TIMEOUT)); 301 assertTrue("Not connected to mobile", mConnectionUtil.isConnectedToMobile()); 302 assertFalse("Still connected to wifi.", mConnectionUtil.isConnectedToWifi()); 303 return mConnectionUtil.hasData(); 304 } 305 306 /** 307 * Output the {@link NetworkStats} to Instrumentation out. 308 * 309 * @param label to attach to this given stats. 310 * @param stats {@link NetworkStats} to add. 311 * @param results {@link Bundle} to be added to. 312 * @param uid for which to report the results. 313 */ addStatsToResults(String label, NetworkStats stats, Bundle results, int uid)314 public void addStatsToResults(String label, NetworkStats stats, Bundle results, int uid){ 315 if (results == null || results.isEmpty()) { 316 Log.e(LOG_TAG, "Empty bundle provided."); 317 return; 318 } 319 Entry totalStats = null; 320 for (int i = 0; i < stats.size(); ++i) { 321 Entry statsEntry = stats.getValues(i, null); 322 // We are only interested in the all inclusive stats. 323 if (statsEntry.tag != 0) { 324 continue; 325 } 326 // skip stats for other uids 327 if (statsEntry.uid != uid) { 328 continue; 329 } 330 if (totalStats == null || statsEntry.set == NetworkStats.SET_ALL) { 331 totalStats = statsEntry; 332 } else { 333 totalStats.rxBytes += statsEntry.rxBytes; 334 totalStats.txBytes += statsEntry.txBytes; 335 } 336 } 337 // Output merged stats to bundle. 338 results.putInt(label + "uid", totalStats.uid); 339 results.putLong(label + "tx", totalStats.txBytes); 340 results.putLong(label + "rx", totalStats.rxBytes); 341 } 342 343 /** 344 * Remove file if it exists. 345 * @param file {@link File} to delete. 346 * @return true if successfully deleted the file. 347 */ cleanUpFile(File file)348 private boolean cleanUpFile(File file) { 349 if (file.exists()) { 350 return file.delete(); 351 } 352 return true; 353 } 354 } 355