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