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