blob: 547b205ae414e009997a45231c1675c00852e258 [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.jobscheduler.cts;
import android.annotation.TargetApi;
import android.app.job.JobInfo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.util.Log;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Schedules jobs with the {@link android.app.job.JobScheduler} that have network connectivity
* constraints.
* Requires manipulating the {@link android.net.wifi.WifiManager} to ensure an unmetered network.
* Similarly, requires that the phone be connected to a wifi hotspot, or else the test will fail.
*/
@TargetApi(21)
public class ConnectivityConstraintTest extends ConstraintTest {
private static final String TAG = "ConnectivityConstraintTest";
/** Unique identifier for the job scheduled by this suite of tests. */
public static final int CONNECTIVITY_JOB_ID = ConnectivityConstraintTest.class.hashCode();
private WifiManager mWifiManager;
private ConnectivityManager mCm;
/** Whether the device running these tests supports WiFi. */
private boolean mHasWifi;
/** Whether the device running these tests supports telephony. */
private boolean mHasTelephony;
/** Track whether WiFi was enabled in case we turn it off. */
private boolean mInitialWiFiState;
private JobInfo.Builder mBuilder;
@Override
public void setUp() throws Exception {
super.setUp();
mWifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
mCm =
(ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
PackageManager packageManager = mContext.getPackageManager();
mHasWifi = packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI);
mHasTelephony = packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
mBuilder =
new JobInfo.Builder(CONNECTIVITY_JOB_ID, kJobServiceComponent);
mInitialWiFiState = mWifiManager.isWifiEnabled();
}
@Override
public void tearDown() throws Exception {
// Ensure that we leave WiFi in its previous state.
mWifiManager.setWifiEnabled(mInitialWiFiState);
}
// --------------------------------------------------------------------------------------------
// Positives - schedule jobs under conditions that require them to pass.
// --------------------------------------------------------------------------------------------
/**
* Schedule a job that requires a WiFi connection, and assert that it executes when the device
* is connected to WiFi. This will fail if a wifi connection is unavailable.
*/
public void testUnmeteredConstraintExecutes_withWifi() throws Exception {
if (!mHasWifi) {
Log.d(TAG, "Skipping test that requires the device be WiFi enabled.");
return;
}
connectToWiFi();
kTestEnvironment.setExpectedExecutions(1);
mJobScheduler.schedule(
mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
.build());
sendExpediteStableChargingBroadcast();
assertTrue("Job with unmetered constraint did not fire on WiFi.",
kTestEnvironment.awaitExecution());
}
/**
* Schedule a job with a connectivity constraint, and ensure that it executes on WiFi.
*/
public void testConnectivityConstraintExecutes_withWifi() throws Exception {
if (!mHasWifi) {
Log.d(TAG, "Skipping test that requires the device be WiFi enabled.");
return;
}
connectToWiFi();
kTestEnvironment.setExpectedExecutions(1);
mJobScheduler.schedule(
mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.build());
sendExpediteStableChargingBroadcast();
assertTrue("Job with connectivity constraint did not fire on WiFi.",
kTestEnvironment.awaitExecution());
}
/**
* Schedule a job with a connectivity constraint, and ensure that it executes on on a mobile
* data connection.
*/
public void testConnectivityConstraintExecutes_withMobile() throws Exception {
if (!checkDeviceSupportsMobileData()) {
return;
}
disconnectWifiToConnectToMobile();
kTestEnvironment.setExpectedExecutions(1);
mJobScheduler.schedule(
mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.build());
sendExpediteStableChargingBroadcast();
assertTrue("Job with connectivity constraint did not fire on mobile.",
kTestEnvironment.awaitExecution());
}
// --------------------------------------------------------------------------------------------
// Negatives - schedule jobs under conditions that require that they fail.
// --------------------------------------------------------------------------------------------
/**
* Schedule a job that requires a WiFi connection, and assert that it fails when the device is
* connected to a cellular provider.
* This test assumes that if the device supports a mobile data connection, then this connection
* will be available.
*/
public void testUnmeteredConstraintFails_withMobile() throws Exception {
if (!checkDeviceSupportsMobileData()) {
return;
}
disconnectWifiToConnectToMobile();
kTestEnvironment.setExpectedExecutions(0);
mJobScheduler.schedule(
mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
.build());
sendExpediteStableChargingBroadcast();
assertTrue("Job requiring unmetered connectivity still executed on mobile.",
kTestEnvironment.awaitTimeout());
}
/**
* Determine whether the device running these CTS tests should be subject to tests involving
* mobile data.
* @return True if this device will support a mobile data connection.
*/
private boolean checkDeviceSupportsMobileData() {
if (!mHasTelephony) {
Log.d(TAG, "Skipping test that requires telephony features, not supported by this" +
" device");
return false;
}
if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
return false;
}
return true;
}
/**
* Ensure WiFi is enabled, and block until we've verified that we are in fact connected.
* Taken from {@link android.net.http.cts.ApacheHttpClientTest}.
*/
private void connectToWiFi() throws InterruptedException {
if (!mWifiManager.isWifiEnabled()) {
ConnectivityActionReceiver receiver =
new ConnectivityActionReceiver(ConnectivityManager.TYPE_WIFI,
NetworkInfo.State.CONNECTED);
IntentFilter filter = new IntentFilter();
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
mContext.registerReceiver(receiver, filter);
assertTrue(mWifiManager.setWifiEnabled(true));
assertTrue("Wifi must be configured to connect to an access point for this test.",
receiver.waitForStateChange());
mContext.unregisterReceiver(receiver);
}
}
/**
* Disconnect from WiFi in an attempt to connect to cellular data. Worth noting that this is
* best effort - there are no public APIs to force connecting to cell data. We disable WiFi
* and wait for a broadcast that we're connected to cell.
* We will not call into this function if the device doesn't support telephony.
* @see #mHasTelephony
* @see #checkDeviceSupportsMobileData()
*/
private void disconnectWifiToConnectToMobile() throws InterruptedException {
if (mHasWifi && mWifiManager.isWifiEnabled()) {
ConnectivityActionReceiver connectMobileReceiver =
new ConnectivityActionReceiver(ConnectivityManager.TYPE_MOBILE,
NetworkInfo.State.CONNECTED);
ConnectivityActionReceiver disconnectWifiReceiver =
new ConnectivityActionReceiver(ConnectivityManager.TYPE_WIFI,
NetworkInfo.State.DISCONNECTED);
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
mContext.registerReceiver(connectMobileReceiver, filter);
mContext.registerReceiver(disconnectWifiReceiver, filter);
assertTrue(mWifiManager.setWifiEnabled(false));
assertTrue("Failure disconnecting from WiFi.",
disconnectWifiReceiver.waitForStateChange());
assertTrue("Device must have access to a metered network for this test.",
connectMobileReceiver.waitForStateChange());
mContext.unregisterReceiver(connectMobileReceiver);
mContext.unregisterReceiver(disconnectWifiReceiver);
}
}
/** Capture the last connectivity change's network type and state. */
private class ConnectivityActionReceiver extends BroadcastReceiver {
private final CountDownLatch mReceiveLatch = new CountDownLatch(1);
private final int mNetworkType;
private final NetworkInfo.State mExpectedState;
ConnectivityActionReceiver(int networkType, NetworkInfo.State expectedState) {
mNetworkType = networkType;
mExpectedState = expectedState;
}
public void onReceive(Context context, Intent intent) {
// Dealing with a connectivity changed event for this network type.
final int networkTypeChanged =
intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, -1);
if (networkTypeChanged == -1) {
Log.e(TAG, "No network type provided in intent");
return;
}
if (networkTypeChanged != mNetworkType) {
// Only track changes for the connectivity event that we are interested in.
return;
}
// Pull out the NetworkState object that we're interested in. Necessary because
// the ConnectivityManager will filter on uid for background connectivity.
NetworkInfo[] allNetworkInfo = mCm.getAllNetworkInfo();
NetworkInfo networkInfo = null;
for (int i=0; i<allNetworkInfo.length; i++) {
NetworkInfo ni = allNetworkInfo[i];
if (ni.getType() == mNetworkType) {
networkInfo = ni;
break;
}
}
if (networkInfo == null) {
Log.e(TAG, "Could not find correct network type.");
return;
}
NetworkInfo.State networkState = networkInfo.getState();
Log.i(TAG, "Network type: " + mNetworkType + " State: " + networkState);
if (networkState == mExpectedState) {
mReceiveLatch.countDown();
}
}
public boolean waitForStateChange() throws InterruptedException {
return mReceiveLatch.await(30, TimeUnit.SECONDS) || hasExpectedState();
}
private boolean hasExpectedState() {
return mExpectedState == mCm.getNetworkInfo(mNetworkType).getState();
}
}
}