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