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 // Ensure that we leave WiFi in its previous state. 79 NetworkInfo.State expectedState = mInitialWiFiState ? 80 NetworkInfo.State.CONNECTED : NetworkInfo.State.DISCONNECTED; 81 ConnectivityActionReceiver receiver = 82 new ConnectivityActionReceiver(ConnectivityManager.TYPE_WIFI, 83 expectedState); 84 IntentFilter filter = new IntentFilter(); 85 filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); 86 mContext.registerReceiver(receiver, filter); 87 88 assertTrue(mWifiManager.setWifiEnabled(mInitialWiFiState)); 89 assertTrue("Failure to restore previous WiFi state.", 90 receiver.waitForStateChange()); 91 92 mContext.unregisterReceiver(receiver); 93 } 94 95 // -------------------------------------------------------------------------------------------- 96 // Positives - schedule jobs under conditions that require them to pass. 97 // -------------------------------------------------------------------------------------------- 98 99 /** 100 * Schedule a job that requires a WiFi connection, and assert that it executes when the device 101 * is connected to WiFi. This will fail if a wifi connection is unavailable. 102 */ testUnmeteredConstraintExecutes_withWifi()103 public void testUnmeteredConstraintExecutes_withWifi() throws Exception { 104 if (!mHasWifi) { 105 Log.d(TAG, "Skipping test that requires the device be WiFi enabled."); 106 return; 107 } 108 connectToWiFi(); 109 110 kTestEnvironment.setExpectedExecutions(1); 111 mJobScheduler.schedule( 112 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) 113 .build()); 114 115 sendExpediteStableChargingBroadcast(); 116 117 assertTrue("Job with unmetered constraint did not fire on WiFi.", 118 kTestEnvironment.awaitExecution()); 119 } 120 121 /** 122 * Schedule a job with a connectivity constraint, and ensure that it executes on WiFi. 123 */ testConnectivityConstraintExecutes_withWifi()124 public void testConnectivityConstraintExecutes_withWifi() throws Exception { 125 if (!mHasWifi) { 126 Log.d(TAG, "Skipping test that requires the device be WiFi enabled."); 127 return; 128 } 129 connectToWiFi(); 130 131 kTestEnvironment.setExpectedExecutions(1); 132 mJobScheduler.schedule( 133 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) 134 .build()); 135 136 sendExpediteStableChargingBroadcast(); 137 138 assertTrue("Job with connectivity constraint did not fire on WiFi.", 139 kTestEnvironment.awaitExecution()); 140 } 141 142 /** 143 * Schedule a job with a connectivity constraint, and ensure that it executes on on a mobile 144 * data connection. 145 */ testConnectivityConstraintExecutes_withMobile()146 public void testConnectivityConstraintExecutes_withMobile() throws Exception { 147 if (!checkDeviceSupportsMobileData()) { 148 return; 149 } 150 disconnectWifiToConnectToMobile(); 151 152 kTestEnvironment.setExpectedExecutions(1); 153 mJobScheduler.schedule( 154 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) 155 .build()); 156 157 sendExpediteStableChargingBroadcast(); 158 159 assertTrue("Job with connectivity constraint did not fire on mobile.", 160 kTestEnvironment.awaitExecution()); 161 } 162 163 // -------------------------------------------------------------------------------------------- 164 // Negatives - schedule jobs under conditions that require that they fail. 165 // -------------------------------------------------------------------------------------------- 166 167 /** 168 * Schedule a job that requires a WiFi connection, and assert that it fails when the device is 169 * connected to a cellular provider. 170 * This test assumes that if the device supports a mobile data connection, then this connection 171 * will be available. 172 */ testUnmeteredConstraintFails_withMobile()173 public void testUnmeteredConstraintFails_withMobile() throws Exception { 174 if (!checkDeviceSupportsMobileData()) { 175 return; 176 } 177 disconnectWifiToConnectToMobile(); 178 179 kTestEnvironment.setExpectedExecutions(0); 180 mJobScheduler.schedule( 181 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) 182 .build()); 183 sendExpediteStableChargingBroadcast(); 184 185 assertTrue("Job requiring unmetered connectivity still executed on mobile.", 186 kTestEnvironment.awaitTimeout()); 187 } 188 189 /** 190 * Determine whether the device running these CTS tests should be subject to tests involving 191 * mobile data. 192 * @return True if this device will support a mobile data connection. 193 */ checkDeviceSupportsMobileData()194 private boolean checkDeviceSupportsMobileData() { 195 if (!mHasTelephony) { 196 Log.d(TAG, "Skipping test that requires telephony features, not supported by this" + 197 " device"); 198 return false; 199 } 200 if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) { 201 Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE"); 202 return false; 203 } 204 return true; 205 } 206 207 /** 208 * Ensure WiFi is enabled, and block until we've verified that we are in fact connected. 209 * Taken from {@link android.net.http.cts.ApacheHttpClientTest}. 210 */ connectToWiFi()211 private void connectToWiFi() throws InterruptedException { 212 if (!mWifiManager.isWifiEnabled()) { 213 ConnectivityActionReceiver receiver = 214 new ConnectivityActionReceiver(ConnectivityManager.TYPE_WIFI, 215 NetworkInfo.State.CONNECTED); 216 IntentFilter filter = new IntentFilter(); 217 filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); 218 mContext.registerReceiver(receiver, filter); 219 220 assertTrue(mWifiManager.setWifiEnabled(true)); 221 assertTrue("Wifi must be configured to connect to an access point for this test.", 222 receiver.waitForStateChange()); 223 224 mContext.unregisterReceiver(receiver); 225 } 226 } 227 228 /** 229 * Disconnect from WiFi in an attempt to connect to cellular data. Worth noting that this is 230 * best effort - there are no public APIs to force connecting to cell data. We disable WiFi 231 * and wait for a broadcast that we're connected to cell. 232 * We will not call into this function if the device doesn't support telephony. 233 * @see #mHasTelephony 234 * @see #checkDeviceSupportsMobileData() 235 */ disconnectWifiToConnectToMobile()236 private void disconnectWifiToConnectToMobile() throws InterruptedException { 237 if (mHasWifi && mWifiManager.isWifiEnabled()) { 238 ConnectivityActionReceiver connectMobileReceiver = 239 new ConnectivityActionReceiver(ConnectivityManager.TYPE_MOBILE, 240 NetworkInfo.State.CONNECTED); 241 ConnectivityActionReceiver disconnectWifiReceiver = 242 new ConnectivityActionReceiver(ConnectivityManager.TYPE_WIFI, 243 NetworkInfo.State.DISCONNECTED); 244 IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); 245 mContext.registerReceiver(connectMobileReceiver, filter); 246 mContext.registerReceiver(disconnectWifiReceiver, filter); 247 248 assertTrue(mWifiManager.setWifiEnabled(false)); 249 assertTrue("Failure disconnecting from WiFi.", 250 disconnectWifiReceiver.waitForStateChange()); 251 assertTrue("Device must have access to a metered network for this test.", 252 connectMobileReceiver.waitForStateChange()); 253 254 mContext.unregisterReceiver(connectMobileReceiver); 255 mContext.unregisterReceiver(disconnectWifiReceiver); 256 } 257 } 258 259 /** Capture the last connectivity change's network type and state. */ 260 private class ConnectivityActionReceiver extends BroadcastReceiver { 261 262 private final CountDownLatch mReceiveLatch = new CountDownLatch(1); 263 264 private final int mNetworkType; 265 266 private final NetworkInfo.State mExpectedState; 267 ConnectivityActionReceiver(int networkType, NetworkInfo.State expectedState)268 ConnectivityActionReceiver(int networkType, NetworkInfo.State expectedState) { 269 mNetworkType = networkType; 270 mExpectedState = expectedState; 271 } 272 onReceive(Context context, Intent intent)273 public void onReceive(Context context, Intent intent) { 274 // Dealing with a connectivity changed event for this network type. 275 final int networkTypeChanged = 276 intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, -1); 277 if (networkTypeChanged == -1) { 278 Log.e(TAG, "No network type provided in intent"); 279 return; 280 } 281 282 if (networkTypeChanged != mNetworkType) { 283 // Only track changes for the connectivity event that we are interested in. 284 return; 285 } 286 // Pull out the NetworkState object that we're interested in. Necessary because 287 // the ConnectivityManager will filter on uid for background connectivity. 288 NetworkInfo[] allNetworkInfo = mCm.getAllNetworkInfo(); 289 NetworkInfo networkInfo = null; 290 for (int i=0; i<allNetworkInfo.length; i++) { 291 NetworkInfo ni = allNetworkInfo[i]; 292 if (ni.getType() == mNetworkType) { 293 networkInfo = ni; 294 break; 295 } 296 } 297 if (networkInfo == null) { 298 Log.e(TAG, "Could not find correct network type."); 299 return; 300 } 301 302 NetworkInfo.State networkState = networkInfo.getState(); 303 Log.i(TAG, "Network type: " + mNetworkType + " State: " + networkState); 304 if (networkState == mExpectedState) { 305 mReceiveLatch.countDown(); 306 } 307 } 308 waitForStateChange()309 public boolean waitForStateChange() throws InterruptedException { 310 return mReceiveLatch.await(30, TimeUnit.SECONDS) || hasExpectedState(); 311 } 312 hasExpectedState()313 private boolean hasExpectedState() { 314 return mExpectedState == mCm.getNetworkInfo(mNetworkType).getState(); 315 } 316 } 317 318 } 319