1 /* 2 * Copyright (C) 2014 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 package android.jobscheduler.cts; 17 18 19 import android.annotation.TargetApi; 20 import android.app.job.JobInfo; 21 import android.content.BroadcastReceiver; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.IntentFilter; 25 import android.content.pm.PackageManager; 26 import android.net.ConnectivityManager; 27 import android.net.NetworkInfo; 28 import android.net.wifi.WifiManager; 29 import android.util.Log; 30 31 import java.util.concurrent.CountDownLatch; 32 import java.util.concurrent.TimeUnit; 33 34 /** 35 * Schedules jobs with the {@link android.app.job.JobScheduler} that have network connectivity 36 * constraints. 37 * Requires manipulating the {@link android.net.wifi.WifiManager} to ensure an unmetered network. 38 * Similarly, requires that the phone be connected to a wifi hotspot, or else the test will fail. 39 */ 40 @TargetApi(21) 41 public class ConnectivityConstraintTest extends ConstraintTest { 42 private static final String TAG = "ConnectivityConstraintTest"; 43 44 /** Unique identifier for the job scheduled by this suite of tests. */ 45 public static final int CONNECTIVITY_JOB_ID = ConnectivityConstraintTest.class.hashCode(); 46 47 private WifiManager mWifiManager; 48 private ConnectivityManager mCm; 49 50 /** Whether the device running these tests supports WiFi. */ 51 private boolean mHasWifi; 52 /** Whether the device running these tests supports telephony. */ 53 private boolean mHasTelephony; 54 /** Track whether WiFi was enabled in case we turn it off. */ 55 private boolean mInitialWiFiState; 56 57 private JobInfo.Builder mBuilder; 58 59 @Override setUp()60 public void setUp() throws Exception { 61 super.setUp(); 62 63 mWifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE); 64 mCm = 65 (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE); 66 67 PackageManager packageManager = mContext.getPackageManager(); 68 mHasWifi = packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI); 69 mHasTelephony = packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY); 70 mBuilder = 71 new JobInfo.Builder(CONNECTIVITY_JOB_ID, kJobServiceComponent); 72 73 mInitialWiFiState = mWifiManager.isWifiEnabled(); 74 } 75 76 @Override tearDown()77 public void tearDown() throws Exception { 78 mJobScheduler.cancel(CONNECTIVITY_JOB_ID); 79 80 // Ensure that we leave WiFi in its previous state. 81 if (mWifiManager.isWifiEnabled() == mInitialWiFiState) { 82 return; 83 } 84 NetworkInfo.State expectedState = mInitialWiFiState ? 85 NetworkInfo.State.CONNECTED : NetworkInfo.State.DISCONNECTED; 86 ConnectivityActionReceiver receiver = 87 new ConnectivityActionReceiver(ConnectivityManager.TYPE_WIFI, 88 expectedState); 89 IntentFilter filter = new IntentFilter(); 90 filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); 91 mContext.registerReceiver(receiver, filter); 92 93 assertTrue(mWifiManager.setWifiEnabled(mInitialWiFiState)); 94 receiver.waitForStateChange(); 95 assertTrue("Failure to restore previous WiFi state", 96 mWifiManager.isWifiEnabled() == mInitialWiFiState); 97 98 mContext.unregisterReceiver(receiver); 99 } 100 101 // -------------------------------------------------------------------------------------------- 102 // Positives - schedule jobs under conditions that require them to pass. 103 // -------------------------------------------------------------------------------------------- 104 105 /** 106 * Schedule a job that requires a WiFi connection, and assert that it executes when the device 107 * is connected to WiFi. This will fail if a wifi connection is unavailable. 108 */ testUnmeteredConstraintExecutes_withWifi()109 public void testUnmeteredConstraintExecutes_withWifi() throws Exception { 110 if (!mHasWifi) { 111 Log.d(TAG, "Skipping test that requires the device be WiFi enabled."); 112 return; 113 } 114 connectToWiFi(); 115 116 kTestEnvironment.setExpectedExecutions(1); 117 mJobScheduler.schedule( 118 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) 119 .build()); 120 121 sendExpediteStableChargingBroadcast(); 122 123 assertTrue("Job with unmetered constraint did not fire on WiFi.", 124 kTestEnvironment.awaitExecution()); 125 } 126 127 /** 128 * Schedule a job with a connectivity constraint, and ensure that it executes on WiFi. 129 */ testConnectivityConstraintExecutes_withWifi()130 public void testConnectivityConstraintExecutes_withWifi() throws Exception { 131 if (!mHasWifi) { 132 Log.d(TAG, "Skipping test that requires the device be WiFi enabled."); 133 return; 134 } 135 connectToWiFi(); 136 137 kTestEnvironment.setExpectedExecutions(1); 138 mJobScheduler.schedule( 139 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) 140 .build()); 141 142 sendExpediteStableChargingBroadcast(); 143 144 assertTrue("Job with connectivity constraint did not fire on WiFi.", 145 kTestEnvironment.awaitExecution()); 146 } 147 148 /** 149 * Schedule a job with a generic connectivity constraint, and ensure that it executes 150 * on a cellular data connection. 151 */ testConnectivityConstraintExecutes_withMobile()152 public void testConnectivityConstraintExecutes_withMobile() throws Exception { 153 if (!checkDeviceSupportsMobileData()) { 154 return; 155 } 156 disconnectWifiToConnectToMobile(); 157 158 kTestEnvironment.setExpectedExecutions(1); 159 mJobScheduler.schedule( 160 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) 161 .build()); 162 163 sendExpediteStableChargingBroadcast(); 164 165 assertTrue("Job with connectivity constraint did not fire on mobile.", 166 kTestEnvironment.awaitExecution()); 167 } 168 169 /** 170 * Schedule a job with a metered connectivity constraint, and ensure that it executes 171 * on a mobile data connection. 172 */ testConnectivityConstraintExecutes_metered()173 public void testConnectivityConstraintExecutes_metered() throws Exception { 174 if (!checkDeviceSupportsMobileData()) { 175 return; 176 } 177 disconnectWifiToConnectToMobile(); 178 179 kTestEnvironment.setExpectedExecutions(1); 180 mJobScheduler.schedule( 181 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED) 182 .build()); 183 184 sendExpediteStableChargingBroadcast(); 185 assertTrue("Job with metered connectivity constraint did not fire on mobile.", 186 kTestEnvironment.awaitExecution()); 187 } 188 189 // -------------------------------------------------------------------------------------------- 190 // Negatives - schedule jobs under conditions that require that they fail. 191 // -------------------------------------------------------------------------------------------- 192 193 /** 194 * Schedule a job that requires a WiFi connection, and assert that it fails when the device is 195 * connected to a cellular provider. 196 * This test assumes that if the device supports a mobile data connection, then this connection 197 * will be available. 198 */ testUnmeteredConstraintFails_withMobile()199 public void testUnmeteredConstraintFails_withMobile() throws Exception { 200 if (!checkDeviceSupportsMobileData()) { 201 return; 202 } 203 disconnectWifiToConnectToMobile(); 204 205 kTestEnvironment.setExpectedExecutions(0); 206 mJobScheduler.schedule( 207 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) 208 .build()); 209 sendExpediteStableChargingBroadcast(); 210 211 assertTrue("Job requiring unmetered connectivity still executed on mobile.", 212 kTestEnvironment.awaitTimeout()); 213 } 214 215 /** 216 * Schedule a job that requires a metered connection, and verify that it does not run when 217 * the device is connected to a WiFi provider. 218 * This test assumes that if the device supports a mobile data connection, then this connection 219 * will be available. 220 */ testMeteredConstraintFails_withWiFi()221 public void testMeteredConstraintFails_withWiFi() throws Exception { 222 if (!mHasWifi) { 223 Log.d(TAG, "Skipping test that requires the device be WiFi enabled."); 224 return; 225 } 226 if (!checkDeviceSupportsMobileData()) { 227 Log.d(TAG, "Skipping test that requires the device be mobile data enabled."); 228 return; 229 } 230 connectToWiFi(); 231 232 kTestEnvironment.setExpectedExecutions(0); 233 mJobScheduler.schedule( 234 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED) 235 .build()); 236 sendExpediteStableChargingBroadcast(); 237 238 assertTrue("Job requiring metered connectivity still executed on WiFi.", 239 kTestEnvironment.awaitTimeout()); 240 } 241 242 // -------------------------------------------------------------------------------------------- 243 // Utility methods 244 // -------------------------------------------------------------------------------------------- 245 246 /** 247 * Determine whether the device running these CTS tests should be subject to tests involving 248 * mobile data. 249 * @return True if this device will support a mobile data connection. 250 */ checkDeviceSupportsMobileData()251 private boolean checkDeviceSupportsMobileData() { 252 if (!mHasTelephony) { 253 Log.d(TAG, "Skipping test that requires telephony features, not supported by this" + 254 " device"); 255 return false; 256 } 257 if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) { 258 Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE"); 259 return false; 260 } 261 return true; 262 } 263 264 /** 265 * Ensure WiFi is enabled, and block until we've verified that we are in fact connected. 266 * Taken from {@link android.net.http.cts.ApacheHttpClientTest}. 267 */ connectToWiFi()268 private void connectToWiFi() throws InterruptedException { 269 if (!mWifiManager.isWifiEnabled()) { 270 ConnectivityActionReceiver receiver = 271 new ConnectivityActionReceiver(ConnectivityManager.TYPE_WIFI, 272 NetworkInfo.State.CONNECTED); 273 IntentFilter filter = new IntentFilter(); 274 filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); 275 mContext.registerReceiver(receiver, filter); 276 277 assertTrue(mWifiManager.setWifiEnabled(true)); 278 assertTrue("Wifi must be configured to connect to an access point for this test.", 279 receiver.waitForStateChange()); 280 281 mContext.unregisterReceiver(receiver); 282 } 283 } 284 285 /** 286 * Disconnect from WiFi in an attempt to connect to cellular data. Worth noting that this is 287 * best effort - there are no public APIs to force connecting to cell data. We disable WiFi 288 * and wait for a broadcast that we're connected to cell. 289 * We will not call into this function if the device doesn't support telephony. 290 * @see #mHasTelephony 291 * @see #checkDeviceSupportsMobileData() 292 */ disconnectWifiToConnectToMobile()293 private void disconnectWifiToConnectToMobile() throws InterruptedException { 294 if (mHasWifi && mWifiManager.isWifiEnabled()) { 295 ConnectivityActionReceiver connectMobileReceiver = 296 new ConnectivityActionReceiver(ConnectivityManager.TYPE_MOBILE, 297 NetworkInfo.State.CONNECTED); 298 ConnectivityActionReceiver disconnectWifiReceiver = 299 new ConnectivityActionReceiver(ConnectivityManager.TYPE_WIFI, 300 NetworkInfo.State.DISCONNECTED); 301 IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); 302 mContext.registerReceiver(connectMobileReceiver, filter); 303 mContext.registerReceiver(disconnectWifiReceiver, filter); 304 305 assertTrue(mWifiManager.setWifiEnabled(false)); 306 assertTrue("Failure disconnecting from WiFi.", 307 disconnectWifiReceiver.waitForStateChange()); 308 assertTrue("Device must have access to a metered network for this test.", 309 connectMobileReceiver.waitForStateChange()); 310 311 mContext.unregisterReceiver(connectMobileReceiver); 312 mContext.unregisterReceiver(disconnectWifiReceiver); 313 } 314 } 315 316 /** Capture the last connectivity change's network type and state. */ 317 private class ConnectivityActionReceiver extends BroadcastReceiver { 318 319 private final CountDownLatch mReceiveLatch = new CountDownLatch(1); 320 321 private final int mNetworkType; 322 323 private final NetworkInfo.State mExpectedState; 324 ConnectivityActionReceiver(int networkType, NetworkInfo.State expectedState)325 ConnectivityActionReceiver(int networkType, NetworkInfo.State expectedState) { 326 mNetworkType = networkType; 327 mExpectedState = expectedState; 328 } 329 onReceive(Context context, Intent intent)330 public void onReceive(Context context, Intent intent) { 331 // Dealing with a connectivity changed event for this network type. 332 final int networkTypeChanged = 333 intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, -1); 334 if (networkTypeChanged == -1) { 335 Log.e(TAG, "No network type provided in intent"); 336 return; 337 } 338 339 if (networkTypeChanged != mNetworkType) { 340 // Only track changes for the connectivity event that we are interested in. 341 return; 342 } 343 // Pull out the NetworkState object that we're interested in. Necessary because 344 // the ConnectivityManager will filter on uid for background connectivity. 345 NetworkInfo[] allNetworkInfo = mCm.getAllNetworkInfo(); 346 NetworkInfo networkInfo = null; 347 for (int i=0; i<allNetworkInfo.length; i++) { 348 NetworkInfo ni = allNetworkInfo[i]; 349 if (ni.getType() == mNetworkType) { 350 networkInfo = ni; 351 break; 352 } 353 } 354 if (networkInfo == null) { 355 Log.e(TAG, "Could not find correct network type."); 356 return; 357 } 358 359 NetworkInfo.State networkState = networkInfo.getState(); 360 Log.i(TAG, "Network type: " + mNetworkType + " State: " + networkState); 361 if (networkState == mExpectedState) { 362 mReceiveLatch.countDown(); 363 } 364 } 365 waitForStateChange()366 public boolean waitForStateChange() throws InterruptedException { 367 return mReceiveLatch.await(30, TimeUnit.SECONDS) || hasExpectedState(); 368 } 369 hasExpectedState()370 private boolean hasExpectedState() { 371 return mExpectedState == mCm.getNetworkInfo(mNetworkType).getState(); 372 } 373 } 374 375 } 376