blob: 7aed1b9eabe8d5f576b1f23fa952ff2ec12af230 [file] [log] [blame]
/*
* Copyright (C) 2020 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 static com.android.compatibility.common.util.TestUtils.waitUntil;
import android.annotation.TargetApi;
import android.app.UiModeManager;
import android.app.job.JobInfo;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.os.PowerManager;
import android.os.UserHandle;
import android.support.test.uiautomator.UiDevice;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
import com.android.compatibility.common.util.BatteryUtils;
/**
* Make sure the state of {@link android.app.job.JobScheduler} is correct.
*/
public class IdleConstraintTest extends BaseJobSchedulerTest {
/** Unique identifier for the job scheduled by this suite of tests. */
private static final int STATE_JOB_ID = IdleConstraintTest.class.hashCode();
private static final String TAG = "IdleConstraintTest";
private PowerManager mPowerManager;
private JobInfo.Builder mBuilder;
private UiDevice mUiDevice;
private String mInitialDisplayTimeout;
@Override
public void setUp() throws Exception {
super.setUp();
mBuilder = new JobInfo.Builder(STATE_JOB_ID, kJobServiceComponent);
mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
// Make sure the screen doesn't turn off when the test turns it on.
mInitialDisplayTimeout = mUiDevice.executeShellCommand(
"settings get system screen_off_timeout");
mUiDevice.executeShellCommand("settings put system screen_off_timeout 300000");
}
@Override
public void tearDown() throws Exception {
mJobScheduler.cancel(STATE_JOB_ID);
// Put device back in to normal operation.
toggleScreenOn(true);
if (isCarModeSupported()) {
setCarMode(false);
}
mUiDevice.executeShellCommand(
"settings put system screen_off_timeout " + mInitialDisplayTimeout);
super.tearDown();
}
void assertJobReady() throws Exception {
assertJobReady(STATE_JOB_ID);
}
void assertJobWaiting() throws Exception {
assertJobWaiting(STATE_JOB_ID);
}
void assertJobNotReady() throws Exception {
assertJobNotReady(STATE_JOB_ID);
}
/**
* Toggle device is dock idle or dock active.
*/
private void toggleFakeDeviceDockState(final boolean idle) throws Exception {
mUiDevice.executeShellCommand("cmd jobscheduler trigger-dock-state "
+ (idle ? "idle" : "active"));
// Wait a moment to let that happen before proceeding.
Thread.sleep(2_000);
}
/**
* Set the screen state.
*/
private void toggleScreenOn(final boolean screenon) throws Exception {
BatteryUtils.turnOnScreen(screenon);
// Wait a little bit for the broadcasts to be processed.
Thread.sleep(2_000);
}
/**
* Simulated for idle, and then perform idle maintenance now.
*/
private void triggerIdleMaintenance() throws Exception {
mUiDevice.executeShellCommand("cmd activity idle-maintenance");
// Wait a moment to let that happen before proceeding.
Thread.sleep(2_000);
}
/**
* Schedule a job that requires the device is idle, and assert it fired to make
* sure the device is idle.
*/
void verifyIdleState() throws Exception {
kTestEnvironment.setExpectedExecutions(1);
kTestEnvironment.setExpectedWaitForRun();
mJobScheduler.schedule(mBuilder.setRequiresDeviceIdle(true).build());
assertJobReady();
kTestEnvironment.readyToRun();
runSatisfiedJob();
assertTrue("Job with idle constraint did not fire on idle",
kTestEnvironment.awaitExecution());
}
/**
* Schedule a job that requires the device is idle, and assert it failed to make
* sure the device is active.
*/
void verifyActiveState() throws Exception {
kTestEnvironment.setExpectedExecutions(0);
kTestEnvironment.setExpectedWaitForRun();
mJobScheduler.schedule(mBuilder.setRequiresDeviceIdle(true).build());
assertJobWaiting();
assertJobNotReady();
kTestEnvironment.readyToRun();
runSatisfiedJob();
assertFalse("Job with idle constraint fired while not on idle.",
kTestEnvironment.awaitExecution(250));
}
/**
* Ensure that device can switch state normally.
*/
public void testDeviceChangeIdleActiveState() throws Exception {
toggleScreenOn(true);
verifyActiveState();
// Assert device is idle when screen is off for a while.
toggleScreenOn(false);
triggerIdleMaintenance();
verifyIdleState();
// Assert device is back to active when screen is on.
toggleScreenOn(true);
verifyActiveState();
}
private boolean isCarModeSupported() {
return !getContext().getSystemService(UiModeManager.class).isUiModeLocked();
}
/**
* Check if dock state is supported.
*/
private boolean isDockStateSupported() {
final boolean isCar = getContext().getPackageManager().hasSystemFeature(
PackageManager.FEATURE_AUTOMOTIVE);
final boolean isLeanback = getContext().getPackageManager().hasSystemFeature(
PackageManager.FEATURE_LEANBACK_ONLY);
// Car and Leanback do not support dock state.
return !isCar && !isLeanback;
}
/**
* Ensure that device can switch state on dock normally.
*/
@TargetApi(28)
public void testScreenOnDeviceOnDockChangeState() throws Exception {
if (!isDockStateSupported()) {
return;
}
toggleScreenOn(true);
verifyActiveState();
// Assert device go to idle if user doesn't interact with device for a while.
toggleFakeDeviceDockState(true /* idle */);
triggerIdleMaintenance();
verifyIdleState();
// Assert device go back to active if user interacts with device.
toggleFakeDeviceDockState(false /* active */);
verifyActiveState();
}
/**
* Ensure that the tracker ignores this dock intent during screen off.
*/
@TargetApi(28)
public void testScreenOffDeviceOnDockNoChangeState() throws Exception {
if (!isDockStateSupported()) {
return;
}
toggleScreenOn(false);
triggerIdleMaintenance();
verifyIdleState();
toggleFakeDeviceDockState(false /* active */);
verifyIdleState();
}
private void setCarMode(boolean on) throws Exception {
UiModeManager uiModeManager = getContext().getSystemService(UiModeManager.class);
final boolean wasScreenOn = mPowerManager.isInteractive();
if (on) {
uiModeManager.enableCarMode(0);
waitUntil("UI mode didn't change to " + Configuration.UI_MODE_TYPE_CAR,
() -> Configuration.UI_MODE_TYPE_CAR ==
(getContext().getResources().getConfiguration().uiMode
& Configuration.UI_MODE_TYPE_MASK));
} else {
uiModeManager.disableCarMode(0);
waitUntil("UI mode didn't change from " + Configuration.UI_MODE_TYPE_CAR,
() -> Configuration.UI_MODE_TYPE_CAR !=
(getContext().getResources().getConfiguration().uiMode
& Configuration.UI_MODE_TYPE_MASK));
}
Thread.sleep(2_000);
if (mPowerManager.isInteractive() != wasScreenOn) {
// Apparently setting the car mode can change the screen state >.<
Log.d(TAG, "Screen state changed");
toggleScreenOn(wasScreenOn);
}
}
/**
* Ensure car mode is considered active.
*/
public void testCarModePreventsIdle() throws Exception {
if (!isCarModeSupported()) {
return;
}
toggleScreenOn(false);
setCarMode(true);
triggerIdleMaintenance();
verifyActiveState();
setCarMode(false);
triggerIdleMaintenance();
verifyIdleState();
}
private void runIdleJobStartsOnlyWhenIdle() throws Exception {
if (!isCarModeSupported()) {
return;
}
toggleScreenOn(true);
kTestEnvironment.setExpectedExecutions(0);
kTestEnvironment.setExpectedWaitForRun();
mJobScheduler.schedule(mBuilder.setRequiresDeviceIdle(true).build());
triggerIdleMaintenance();
assertJobWaiting();
assertJobNotReady();
kTestEnvironment.readyToRun();
runSatisfiedJob();
assertFalse("Job fired when the device was active.", kTestEnvironment.awaitExecution(500));
kTestEnvironment.setExpectedExecutions(0);
kTestEnvironment.setExpectedWaitForRun();
setCarMode(true);
toggleScreenOn(false);
triggerIdleMaintenance();
assertJobWaiting();
assertJobNotReady();
kTestEnvironment.readyToRun();
runSatisfiedJob();
assertFalse("Job fired when the device was active.", kTestEnvironment.awaitExecution(500));
kTestEnvironment.setExpectedExecutions(1);
kTestEnvironment.setExpectedWaitForRun();
kTestEnvironment.setContinueAfterStart();
kTestEnvironment.setExpectedStopped();
setCarMode(false);
triggerIdleMaintenance();
assertJobReady();
kTestEnvironment.readyToRun();
runSatisfiedJob();
assertTrue("Job didn't fire when the device became idle.",
kTestEnvironment.awaitExecution());
}
public void testIdleJobStartsOnlyWhenIdle_carEndsIdle() throws Exception {
if (!isCarModeSupported()) {
return;
}
runIdleJobStartsOnlyWhenIdle();
setCarMode(true);
assertTrue("Job didn't stop when the device became active.",
kTestEnvironment.awaitStopped());
}
public void testIdleJobStartsOnlyWhenIdle_screenEndsIdle() throws Exception {
if (!isCarModeSupported()) {
return;
}
runIdleJobStartsOnlyWhenIdle();
toggleScreenOn(true);
assertTrue("Job didn't stop when the device became active.",
kTestEnvironment.awaitStopped());
}
/** Asks (not forces) JobScheduler to run the job if constraints are met. */
private void runSatisfiedJob() throws Exception {
mUiDevice.executeShellCommand("cmd jobscheduler run -s"
+ " -u " + UserHandle.myUserId()
+ " " + kJobServiceComponent.getPackageName()
+ " " + STATE_JOB_ID);
}
}