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