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 import static android.server.wm.WindowManagerState.STATE_RESUMED; 19 20 import static com.android.compatibility.common.util.TestUtils.waitUntil; 21 22 import android.annotation.CallSuper; 23 import android.annotation.TargetApi; 24 import android.app.Instrumentation; 25 import android.app.job.JobScheduler; 26 import android.content.ClipData; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.pm.PackageManager; 31 import android.jobscheduler.MockJobService; 32 import android.jobscheduler.TestActivity; 33 import android.jobscheduler.TriggerContentJobService; 34 import android.net.Uri; 35 import android.os.Bundle; 36 import android.os.PowerManager; 37 import android.os.Process; 38 import android.os.SystemClock; 39 import android.os.SystemProperties; 40 import android.os.UserHandle; 41 import android.provider.DeviceConfig; 42 import android.provider.Settings; 43 import android.server.wm.WindowManagerStateHelper; 44 import android.test.InstrumentationTestCase; 45 import android.util.Log; 46 47 import com.android.compatibility.common.util.BatteryUtils; 48 import com.android.compatibility.common.util.DeviceConfigStateHelper; 49 import com.android.compatibility.common.util.SystemUtil; 50 51 import java.io.IOException; 52 53 /** 54 * Common functionality from which the other test case classes derive. 55 * TODO: b/338305140 - Move to JUnit4. 56 */ 57 @TargetApi(21) 58 public abstract class BaseJobSchedulerTest extends InstrumentationTestCase { 59 private static final String TAG = BaseJobSchedulerTest.class.getSimpleName(); 60 static final int HW_TIMEOUT_MULTIPLIER = SystemProperties.getInt("ro.hw_timeout_multiplier", 1); 61 62 /** Environment that notifies of JobScheduler callbacks. */ 63 static MockJobService.TestEnvironment kTestEnvironment = 64 MockJobService.TestEnvironment.getTestEnvironment(); 65 static TriggerContentJobService.TestEnvironment kTriggerTestEnvironment = 66 TriggerContentJobService.TestEnvironment.getTestEnvironment(); 67 /** Handle for the service which receives the execution callbacks from the JobScheduler. */ 68 static ComponentName kJobServiceComponent; 69 static ComponentName kTriggerContentServiceComponent; 70 JobScheduler mJobScheduler; 71 72 Context mContext; 73 DeviceConfigStateHelper mDeviceConfigStateHelper; 74 75 static final String MY_PACKAGE = "android.jobscheduler.cts"; 76 77 static final String JOBPERM_PACKAGE = "android.jobscheduler.cts.jobperm"; 78 static final String JOBPERM_AUTHORITY = "android.jobscheduler.cts.jobperm.provider"; 79 static final String JOBPERM_PERM = "android.jobscheduler.cts.jobperm.perm"; 80 81 Uri mFirstUri; 82 Bundle mFirstUriBundle; 83 Uri mSecondUri; 84 Bundle mSecondUriBundle; 85 ClipData mFirstClipData; 86 ClipData mSecondClipData; 87 88 boolean mStorageStateChanged; 89 boolean mActivityStarted; 90 91 private boolean mDeviceIdleEnabled; 92 private boolean mDeviceLightIdleEnabled; 93 94 private String mInitialBatteryStatsConstants; 95 96 @Override injectInstrumentation(Instrumentation instrumentation)97 public void injectInstrumentation(Instrumentation instrumentation) { 98 super.injectInstrumentation(instrumentation); 99 mContext = instrumentation.getContext(); 100 kJobServiceComponent = new ComponentName(getContext(), MockJobService.class); 101 kTriggerContentServiceComponent = new ComponentName(getContext(), 102 TriggerContentJobService.class); 103 mJobScheduler = (JobScheduler) getContext().getSystemService(Context.JOB_SCHEDULER_SERVICE); 104 mFirstUri = Uri.parse("content://" + JOBPERM_AUTHORITY + "/protected/foo"); 105 mFirstUriBundle = new Bundle(); 106 mFirstUriBundle.putParcelable("uri", mFirstUri); 107 mSecondUri = Uri.parse("content://" + JOBPERM_AUTHORITY + "/protected/bar"); 108 mSecondUriBundle = new Bundle(); 109 mSecondUriBundle.putParcelable("uri", mSecondUri); 110 mFirstClipData = new ClipData("JobPerm1", new String[] { "application/*" }, 111 new ClipData.Item(mFirstUri)); 112 mSecondClipData = new ClipData("JobPerm2", new String[] { "application/*" }, 113 new ClipData.Item(mSecondUri)); 114 try { 115 SystemUtil.runShellCommand(getInstrumentation(), "cmd activity set-inactive " 116 + mContext.getPackageName() + " false"); 117 } catch (IOException e) { 118 Log.w("ConstraintTest", "Failed setting inactive false", e); 119 } 120 if (HW_TIMEOUT_MULTIPLIER != 0) { 121 Log.i(TAG, "HW multiplier set to " + HW_TIMEOUT_MULTIPLIER); 122 } 123 } 124 getContext()125 public Context getContext() { 126 return mContext; 127 } 128 129 @CallSuper 130 @Override setUp()131 public void setUp() throws Exception { 132 super.setUp(); 133 mDeviceConfigStateHelper = 134 new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_JOB_SCHEDULER); 135 SystemUtil.runShellCommand("cmd jobscheduler cache-config-changes on"); 136 // Disable batching behavior. 137 mDeviceConfigStateHelper.set("min_ready_cpu_only_jobs_count", "0"); 138 mDeviceConfigStateHelper.set("min_ready_non_active_jobs_count", "0"); 139 mDeviceConfigStateHelper.set("conn_transport_batch_threshold", ""); 140 // Disable flex behavior. 141 mDeviceConfigStateHelper.set("fc_applied_constraints", "0"); 142 kTestEnvironment.setUp(); 143 kTriggerTestEnvironment.setUp(); 144 mJobScheduler.cancelAll(); 145 146 mDeviceIdleEnabled = isDeviceIdleEnabled(); 147 mDeviceLightIdleEnabled = isDeviceLightIdleEnabled(); 148 if (mDeviceIdleEnabled || mDeviceLightIdleEnabled) { 149 // Make sure the device isn't dozing since it will affect execution of regular jobs 150 setDeviceIdleState(false); 151 } 152 153 mInitialBatteryStatsConstants = Settings.Global.getString(mContext.getContentResolver(), 154 Settings.Global.BATTERY_STATS_CONSTANTS); 155 // Make sure ACTION_CHARGING is sent immediately. 156 Settings.Global.putString(mContext.getContentResolver(), 157 Settings.Global.BATTERY_STATS_CONSTANTS, "battery_charged_delay_ms=0"); 158 } 159 160 @CallSuper 161 @Override tearDown()162 public void tearDown() throws Exception { 163 SystemUtil.runShellCommand("cmd jobscheduler cache-config-changes off"); 164 SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler monitor-battery off"); 165 SystemUtil.runShellCommand(getInstrumentation(), "cmd battery reset"); 166 Settings.Global.putString(mContext.getContentResolver(), 167 Settings.Global.BATTERY_STATS_CONSTANTS, mInitialBatteryStatsConstants); 168 if (mStorageStateChanged) { 169 // Put storage service back in to normal operation. 170 SystemUtil.runShellCommand(getInstrumentation(), "cmd devicestoragemonitor reset"); 171 mStorageStateChanged = false; 172 } 173 SystemUtil.runShellCommand(getInstrumentation(), 174 "cmd jobscheduler reset-execution-quota -u current " 175 + kJobServiceComponent.getPackageName()); 176 mDeviceConfigStateHelper.restoreOriginalValues(); 177 178 if (mActivityStarted) { 179 closeActivity(); 180 } 181 182 if (mDeviceIdleEnabled || mDeviceLightIdleEnabled) { 183 resetDeviceIdleState(); 184 } 185 186 // The super method should be called at the end. 187 super.tearDown(); 188 } 189 assertHasUriPermission(Uri uri, int grantFlags)190 public void assertHasUriPermission(Uri uri, int grantFlags) { 191 if ((grantFlags&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) { 192 assertEquals(PackageManager.PERMISSION_GRANTED, 193 getContext().checkUriPermission(uri, Process.myPid(), 194 Process.myUid(), Intent.FLAG_GRANT_READ_URI_PERMISSION)); 195 } 196 if ((grantFlags&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) { 197 assertEquals(PackageManager.PERMISSION_GRANTED, 198 getContext().checkUriPermission(uri, Process.myPid(), 199 Process.myUid(), Intent.FLAG_GRANT_WRITE_URI_PERMISSION)); 200 } 201 } 202 waitPermissionRevoke(Uri uri, int access, long timeout)203 void waitPermissionRevoke(Uri uri, int access, long timeout) { 204 long startTime = SystemClock.elapsedRealtime(); 205 while (getContext().checkUriPermission(uri, Process.myPid(), Process.myUid(), access) 206 != PackageManager.PERMISSION_DENIED) { 207 try { 208 Thread.sleep(50); 209 } catch (InterruptedException e) { 210 } 211 if ((SystemClock.elapsedRealtime()-startTime) >= timeout) { 212 fail("Timed out waiting for permission revoke"); 213 } 214 } 215 } 216 isDeviceIdleFeatureEnabled()217 boolean isDeviceIdleFeatureEnabled() throws Exception { 218 return mDeviceIdleEnabled || mDeviceLightIdleEnabled; 219 } 220 isDeviceIdleEnabled()221 static boolean isDeviceIdleEnabled() throws Exception { 222 final String output = SystemUtil.runShellCommand("cmd deviceidle enabled deep").trim(); 223 return Integer.parseInt(output) != 0; 224 } 225 isDeviceLightIdleEnabled()226 static boolean isDeviceLightIdleEnabled() throws Exception { 227 final String output = SystemUtil.runShellCommand("cmd deviceidle enabled light").trim(); 228 return Integer.parseInt(output) != 0; 229 } 230 231 /** Returns the current storage-low state, as believed by JobScheduler. */ isJsStorageStateLow()232 private boolean isJsStorageStateLow() throws Exception { 233 return !Boolean.parseBoolean( 234 SystemUtil.runShellCommand(getInstrumentation(), 235 "cmd jobscheduler get-storage-not-low").trim()); 236 } 237 238 // Note we are just using storage state as a way to control when the job gets executed. setStorageStateLow(boolean low)239 void setStorageStateLow(boolean low) throws Exception { 240 if (isJsStorageStateLow() == low) { 241 // Nothing to do here 242 return; 243 } 244 mStorageStateChanged = true; 245 String res; 246 if (low) { 247 res = SystemUtil.runShellCommand(getInstrumentation(), 248 "cmd devicestoragemonitor force-low -f"); 249 } else { 250 res = SystemUtil.runShellCommand(getInstrumentation(), 251 "cmd devicestoragemonitor force-not-low -f"); 252 } 253 int seq = Integer.parseInt(res.trim()); 254 long startTime = SystemClock.elapsedRealtime(); 255 256 // Wait for the storage update to be processed by job scheduler before proceeding. 257 int curSeq; 258 do { 259 curSeq = Integer.parseInt(SystemUtil.runShellCommand(getInstrumentation(), 260 "cmd jobscheduler get-storage-seq").trim()); 261 if (curSeq == seq) { 262 return; 263 } 264 Thread.sleep(500); 265 } while ((SystemClock.elapsedRealtime() - startTime) < 10_000); 266 267 fail("Timed out waiting for job scheduler: expected seq=" + seq + ", cur=" + curSeq); 268 } 269 startAndKeepTestActivity()270 void startAndKeepTestActivity() { 271 final Intent testActivity = new Intent(); 272 testActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 273 ComponentName testComponentName = new ComponentName(mContext, TestActivity.class); 274 testActivity.setComponent(testComponentName); 275 mContext.startActivity(testActivity); 276 new WindowManagerStateHelper().waitForActivityState(testComponentName, STATE_RESUMED); 277 mActivityStarted = true; 278 } 279 closeActivity()280 void closeActivity() { 281 mContext.sendBroadcast(new Intent(TestActivity.ACTION_FINISH_ACTIVITY)); 282 mActivityStarted = false; 283 } 284 setDeviceConfigFlag(String key, String value, boolean waitForConfirmation)285 void setDeviceConfigFlag(String key, String value, boolean waitForConfirmation) 286 throws Exception { 287 mDeviceConfigStateHelper.set(key, value); 288 if (waitForConfirmation) { 289 waitUntil("Config didn't update appropriately to '" + value 290 + "'. Current value=" + getConfigValue(key), 291 5 /* seconds */, 292 () -> { 293 final String curVal = getConfigValue(key); 294 if (value == null) { 295 return "null".equals(curVal); 296 } else { 297 return curVal.equals(value); 298 } 299 }); 300 } 301 } 302 getConfigValue(String key)303 static String getConfigValue(String key) { 304 return SystemUtil.runShellCommand("cmd jobscheduler get-config-value " + key).trim(); 305 } 306 getJobState(int jobId)307 String getJobState(int jobId) throws Exception { 308 return SystemUtil.runShellCommand(getInstrumentation(), 309 "cmd jobscheduler get-job-state --user cur " 310 + kJobServiceComponent.getPackageName() + " " + jobId).trim(); 311 } 312 assertJobReady(int jobId)313 void assertJobReady(int jobId) throws Exception { 314 String state = getJobState(jobId); 315 assertTrue("Job unexpectedly not ready, in state: " + state, state.contains("ready")); 316 } 317 assertJobWaiting(int jobId)318 void assertJobWaiting(int jobId) throws Exception { 319 String state = getJobState(jobId); 320 assertTrue("Job unexpectedly not waiting, in state: " + state, state.contains("waiting")); 321 } 322 assertJobNotReady(int jobId)323 void assertJobNotReady(int jobId) throws Exception { 324 String state = getJobState(jobId); 325 assertTrue("Job unexpectedly ready, in state: " + state, !state.contains("ready")); 326 } 327 328 /** 329 * Set the screen state. 330 */ toggleScreenOn(final boolean screenon)331 static void toggleScreenOn(final boolean screenon) throws Exception { 332 BatteryUtils.turnOnScreen(screenon); 333 if (screenon) { 334 SystemUtil.runShellCommand("wm dismiss-keyguard"); 335 } 336 // Wait a little bit for the broadcasts to be processed. 337 Thread.sleep(2_000); 338 } 339 resetDeviceIdleState()340 void resetDeviceIdleState() throws Exception { 341 SystemUtil.runShellCommand("cmd deviceidle unforce"); 342 } 343 setBatteryState(boolean plugged, int level)344 void setBatteryState(boolean plugged, int level) throws Exception { 345 SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler monitor-battery on"); 346 if (plugged) { 347 SystemUtil.runShellCommand(getInstrumentation(), "cmd battery set ac 1"); 348 final int curLevel = Integer.parseInt(SystemUtil.runShellCommand(getInstrumentation(), 349 "dumpsys battery get level").trim()); 350 if (curLevel >= level) { 351 // Lower the level so when we set it to the desired level, JobScheduler thinks 352 // the device is charging. 353 SystemUtil.runShellCommand(getInstrumentation(), 354 "cmd battery set level " + Math.max(1, level - 1)); 355 } 356 } else { 357 SystemUtil.runShellCommand(getInstrumentation(), "cmd battery unplug"); 358 } 359 int seq = Integer.parseInt(SystemUtil.runShellCommand(getInstrumentation(), 360 "cmd battery set -f level " + level).trim()); 361 362 // Wait for the battery update to be processed by job scheduler before proceeding. 363 waitUntil("JobScheduler didn't update charging status to " + plugged, 15 /* seconds */, 364 () -> { 365 int curSeq; 366 boolean curCharging; 367 curSeq = Integer.parseInt(SystemUtil.runShellCommand(getInstrumentation(), 368 "cmd jobscheduler get-battery-seq").trim()); 369 curCharging = Boolean.parseBoolean( 370 SystemUtil.runShellCommand(getInstrumentation(), 371 "cmd jobscheduler get-battery-charging").trim()); 372 return curSeq >= seq && curCharging == plugged; 373 }); 374 } 375 setDeviceIdleState(final boolean idle)376 void setDeviceIdleState(final boolean idle) throws Exception { 377 final String changeCommand; 378 if (idle) { 379 changeCommand = "force-idle " + (mDeviceIdleEnabled ? "deep" : "light"); 380 } else { 381 changeCommand = "force-active"; 382 } 383 SystemUtil.runShellCommand("cmd deviceidle " + changeCommand); 384 waitUntil("Could not change device idle state to " + idle, 15 /* seconds */, 385 () -> { 386 PowerManager powerManager = getContext().getSystemService(PowerManager.class); 387 if (idle) { 388 return mDeviceIdleEnabled 389 ? powerManager.isDeviceIdleMode() 390 : powerManager.isDeviceLightIdleMode(); 391 } else { 392 return !powerManager.isDeviceIdleMode() 393 && !powerManager.isDeviceLightIdleMode(); 394 } 395 }); 396 } 397 398 /** Asks (not forces) JobScheduler to run the job if constraints are met. */ runSatisfiedJob(int jobId)399 void runSatisfiedJob(int jobId) throws Exception { 400 runSatisfiedJob(jobId, null); 401 } 402 runSatisfiedJob(int jobId, String namespace)403 void runSatisfiedJob(int jobId, String namespace) throws Exception { 404 if (HW_TIMEOUT_MULTIPLIER > 1) { 405 // Device has increased HW multiplier. Wait a short amount of time before sending the 406 // run command since there's a higher chance JobScheduler's processing is delayed. 407 Thread.sleep(1_000L); 408 } 409 SystemUtil.runShellCommand(getInstrumentation(), 410 "cmd jobscheduler run -s" 411 + " -u " + UserHandle.myUserId() 412 + (namespace == null ? "" : " -n " + namespace) 413 + " " + kJobServiceComponent.getPackageName() 414 + " " + jobId); 415 } 416 } 417