diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 3fb1fadba062c..12a8654c61f70 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -1556,7 +1556,7 @@ public class JobSchedulerService extends com.android.server.SystemService Slog.d(TAG, "UID " + uid + " bias changed from " + prevBias + " to " + newBias); } for (int c = 0; c < mControllers.size(); ++c) { - mControllers.get(c).onUidBiasChangedLocked(uid, newBias); + mControllers.get(c).onUidBiasChangedLocked(uid, prevBias, newBias); } } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java index f7338494384d1..63781ae334d23 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java @@ -18,6 +18,14 @@ package com.android.server.job.controllers; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; +import android.annotation.NonNull; +import android.app.job.JobInfo; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; +import android.os.BatteryManagerInternal; import android.os.UserHandle; import android.util.ArraySet; import android.util.IndentingPrintWriter; @@ -27,6 +35,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.server.JobSchedulerBackgroundThread; +import com.android.server.LocalServices; import com.android.server.job.JobSchedulerService; import com.android.server.job.StateControllerProto; @@ -42,10 +51,26 @@ public final class BatteryController extends RestrictingController { private static final boolean DEBUG = JobSchedulerService.DEBUG || Log.isLoggable(TAG, Log.DEBUG); + @GuardedBy("mLock") private final ArraySet mTrackedTasks = new ArraySet<>(); + /** + * List of jobs that started while the UID was in the TOP state. + */ + @GuardedBy("mLock") + private final ArraySet mTopStartedJobs = new ArraySet<>(); + + private final PowerTracker mPowerTracker; + + /** + * Helper set to avoid too much GC churn from frequent calls to + * {@link #maybeReportNewChargingStateLocked()}. + */ + private final ArraySet mChangedJobs = new ArraySet<>(); public BatteryController(JobSchedulerService service) { super(service); + mPowerTracker = new PowerTracker(); + mPowerTracker.startTracking(); } @Override @@ -54,8 +79,15 @@ public final class BatteryController extends RestrictingController { final long nowElapsed = sElapsedRealtimeClock.millis(); mTrackedTasks.add(taskStatus); taskStatus.setTrackingController(JobStatus.TRACKING_BATTERY); - taskStatus.setChargingConstraintSatisfied(nowElapsed, - mService.isBatteryCharging() && mService.isBatteryNotLow()); + if (taskStatus.hasChargingConstraint()) { + if (hasTopExemptionLocked(taskStatus)) { + taskStatus.setChargingConstraintSatisfied(nowElapsed, + mPowerTracker.isPowerConnected()); + } else { + taskStatus.setChargingConstraintSatisfied(nowElapsed, + mService.isBatteryCharging() && mService.isBatteryNotLow()); + } + } taskStatus.setBatteryNotLowConstraintSatisfied(nowElapsed, mService.isBatteryNotLow()); } } @@ -65,10 +97,33 @@ public final class BatteryController extends RestrictingController { maybeStartTrackingJobLocked(jobStatus, null); } + @Override + @GuardedBy("mLock") + public void prepareForExecutionLocked(JobStatus jobStatus) { + if (DEBUG) { + Slog.d(TAG, "Prepping for " + jobStatus.toShortString()); + } + + final int uid = jobStatus.getSourceUid(); + if (mService.getUidBias(uid) == JobInfo.BIAS_TOP_APP) { + if (DEBUG) { + Slog.d(TAG, jobStatus.toShortString() + " is top started job"); + } + mTopStartedJobs.add(jobStatus); + } + } + + @Override + @GuardedBy("mLock") + public void unprepareFromExecutionLocked(JobStatus jobStatus) { + mTopStartedJobs.remove(jobStatus); + } + @Override public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) { if (taskStatus.clearTrackingController(JobStatus.TRACKING_BATTERY)) { mTrackedTasks.remove(taskStatus); + mTopStartedJobs.remove(taskStatus); } } @@ -90,33 +145,124 @@ public final class BatteryController extends RestrictingController { }); } + @Override + @GuardedBy("mLock") + public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) { + if (prevBias == JobInfo.BIAS_TOP_APP || newBias == JobInfo.BIAS_TOP_APP) { + maybeReportNewChargingStateLocked(); + } + } + + @GuardedBy("mLock") + private boolean hasTopExemptionLocked(@NonNull JobStatus taskStatus) { + return mService.getUidBias(taskStatus.getSourceUid()) == JobInfo.BIAS_TOP_APP + || mTopStartedJobs.contains(taskStatus); + } + @GuardedBy("mLock") private void maybeReportNewChargingStateLocked() { + final boolean powerConnected = mPowerTracker.isPowerConnected(); final boolean stablePower = mService.isBatteryCharging() && mService.isBatteryNotLow(); final boolean batteryNotLow = mService.isBatteryNotLow(); if (DEBUG) { - Slog.d(TAG, "maybeReportNewChargingStateLocked: " + stablePower); + Slog.d(TAG, "maybeReportNewChargingStateLocked: " + + powerConnected + "/" + stablePower + "/" + batteryNotLow); } final long nowElapsed = sElapsedRealtimeClock.millis(); - boolean reportChange = false; for (int i = mTrackedTasks.size() - 1; i >= 0; i--) { final JobStatus ts = mTrackedTasks.valueAt(i); - reportChange |= ts.setChargingConstraintSatisfied(nowElapsed, stablePower); - reportChange |= ts.setBatteryNotLowConstraintSatisfied(nowElapsed, batteryNotLow); + if (ts.hasChargingConstraint()) { + if (hasTopExemptionLocked(ts) + && ts.getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT) { + // If the job started while the app was on top or the app is currently on top, + // let the job run as long as there's power connected, even if the device isn't + // officially charging. + // For user requested/initiated jobs, users may be confused when the task stops + // running even though the device is plugged in. + // Low priority jobs don't need to be exempted. + if (ts.setChargingConstraintSatisfied(nowElapsed, powerConnected)) { + mChangedJobs.add(ts); + } + } else if (ts.setChargingConstraintSatisfied(nowElapsed, stablePower)) { + mChangedJobs.add(ts); + } + } + if (ts.hasBatteryNotLowConstraint() + && ts.setBatteryNotLowConstraintSatisfied(nowElapsed, batteryNotLow)) { + mChangedJobs.add(ts); + } } if (stablePower || batteryNotLow) { // If one of our conditions has been satisfied, always schedule any newly ready jobs. mStateChangedListener.onRunJobNow(null); - } else if (reportChange) { + } else if (mChangedJobs.size() > 0) { // Otherwise, just let the job scheduler know the state has changed and take care of it // as it thinks is best. - mStateChangedListener.onControllerStateChanged(mTrackedTasks); + mStateChangedListener.onControllerStateChanged(mChangedJobs); + } + mChangedJobs.clear(); + } + + private final class PowerTracker extends BroadcastReceiver { + /** + * Track whether there is power connected. It doesn't mean the device is charging. + * Use {@link JobSchedulerService#isBatteryCharging()} to determine if the device is + * charging. + */ + private boolean mPowerConnected; + + PowerTracker() { + } + + void startTracking() { + IntentFilter filter = new IntentFilter(); + + filter.addAction(Intent.ACTION_POWER_CONNECTED); + filter.addAction(Intent.ACTION_POWER_DISCONNECTED); + mContext.registerReceiver(this, filter); + + // Initialize tracker state. + BatteryManagerInternal batteryManagerInternal = + LocalServices.getService(BatteryManagerInternal.class); + mPowerConnected = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY); + } + + boolean isPowerConnected() { + return mPowerConnected; + } + + @Override + public void onReceive(Context context, Intent intent) { + synchronized (mLock) { + final String action = intent.getAction(); + + if (Intent.ACTION_POWER_CONNECTED.equals(action)) { + if (DEBUG) { + Slog.d(TAG, "Power connected @ " + sElapsedRealtimeClock.millis()); + } + if (mPowerConnected) { + return; + } + mPowerConnected = true; + } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) { + if (DEBUG) { + Slog.d(TAG, "Power disconnected @ " + sElapsedRealtimeClock.millis()); + } + if (!mPowerConnected) { + return; + } + mPowerConnected = false; + } + + maybeReportNewChargingStateLocked(); + } } } @Override public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate predicate) { + pw.println("Power connected: " + mPowerTracker.isPowerConnected()); pw.println("Stable power: " + (mService.isBatteryCharging() && mService.isBatteryNotLow())); pw.println("Not low: " + mService.isBatteryNotLow()); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java index 9fb7ab5946079..c678755f73dac 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java @@ -517,7 +517,7 @@ public final class ConnectivityController extends RestrictingController implemen @GuardedBy("mLock") @Override - public void onUidBiasChangedLocked(int uid, int newBias) { + public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) { UidStats uidStats = mUidStats.get(uid); if (uidStats != null && uidStats.baseBias != newBias) { uidStats.baseBias = newBias; diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java index 9749c8087caf8..428f2cbaefc29 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java @@ -40,7 +40,6 @@ import android.util.IndentingPrintWriter; import android.util.Log; import android.util.Slog; import android.util.SparseArrayMap; -import android.util.SparseBooleanArray; import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; @@ -81,9 +80,6 @@ public class PrefetchController extends StateController { */ @GuardedBy("mLock") private final SparseArrayMap mEstimatedLaunchTimes = new SparseArrayMap<>(); - /** Cached list of UIDs in the TOP state. */ - @GuardedBy("mLock") - private final SparseBooleanArray mTopUids = new SparseBooleanArray(); private final ThresholdAlarmListener mThresholdAlarmListener; /** @@ -186,15 +182,9 @@ public class PrefetchController extends StateController { @GuardedBy("mLock") @Override - public void onUidBiasChangedLocked(int uid, int newBias) { + public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) { final boolean isNowTop = newBias == JobInfo.BIAS_TOP_APP; - final boolean wasTop = mTopUids.get(uid); - if (isNowTop) { - mTopUids.put(uid, true); - } else { - // Delete entries of non-top apps so the set doesn't get too large. - mTopUids.delete(uid); - } + final boolean wasTop = prevBias == JobInfo.BIAS_TOP_APP; if (isNowTop != wasTop) { mHandler.obtainMessage(MSG_PROCESS_TOP_STATE_CHANGE, uid, 0).sendToTarget(); } @@ -314,7 +304,8 @@ public class PrefetchController extends StateController { // 3. The app is not open but has an active widget (we can't tell if a widget displays // status/data, so this assumes the prefetch job is to update the data displayed on // the widget). - final boolean appIsOpen = mTopUids.get(jobStatus.getSourceUid()); + final boolean appIsOpen = + mService.getUidBias(jobStatus.getSourceUid()) == JobInfo.BIAS_TOP_APP; final boolean satisfied; if (!appIsOpen) { final int userId = jobStatus.getSourceUserId(); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java index 509fb6963a3c7..2a2d602b24bf2 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java @@ -144,7 +144,7 @@ public abstract class StateController { * important the UID is. */ @GuardedBy("mLock") - public void onUidBiasChangedLocked(int uid, int newBias) { + public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) { } protected boolean wouldBeReadyWithConstraintLocked(JobStatus jobStatus, int constraint) { diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java new file mode 100644 index 0000000000000..52d0494bd9f02 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2018 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 com.android.server.job.controllers; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; +import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.verify; + +import android.app.AppGlobals; +import android.app.job.JobInfo; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManagerInternal; +import android.content.pm.ServiceInfo; +import android.os.BatteryManagerInternal; +import android.os.RemoteException; +import android.util.ArraySet; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.JobSchedulerBackgroundThread; +import com.android.server.LocalServices; +import com.android.server.job.JobSchedulerService; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; + +@RunWith(AndroidJUnit4.class) +public class BatteryControllerTest { + private static final int CALLING_UID = 1000; + private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests"; + private static final int SOURCE_USER_ID = 0; + + private BatteryController mBatteryController; + private BroadcastReceiver mPowerReceiver; + private JobSchedulerService.Constants mConstants = new JobSchedulerService.Constants(); + private int mSourceUid; + + private MockitoSession mMockingSession; + @Mock + private Context mContext; + @Mock + private BatteryManagerInternal mBatteryManagerInternal; + @Mock + private JobSchedulerService mJobSchedulerService; + @Mock + private PackageManagerInternal mPackageManagerInternal; + + @Before + public void setUp() { + mMockingSession = mockitoSession() + .initMocks(this) + .strictness(Strictness.LENIENT) + .mockStatic(LocalServices.class) + .startMocking(); + + // Called in StateController constructor. + when(mJobSchedulerService.getTestableContext()).thenReturn(mContext); + when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService); + when(mJobSchedulerService.getConstants()).thenReturn(mConstants); + // Called in BatteryController constructor. + doReturn(mBatteryManagerInternal) + .when(() -> LocalServices.getService(BatteryManagerInternal.class)); + // Used in JobStatus. + doReturn(mPackageManagerInternal) + .when(() -> LocalServices.getService(PackageManagerInternal.class)); + + // Initialize real objects. + // Capture the listeners. + ArgumentCaptor receiverCaptor = + ArgumentCaptor.forClass(BroadcastReceiver.class); + mBatteryController = new BatteryController(mJobSchedulerService); + + verify(mContext).registerReceiver(receiverCaptor.capture(), + ArgumentMatchers.argThat(filter -> + filter.hasAction(Intent.ACTION_POWER_CONNECTED) + && filter.hasAction(Intent.ACTION_POWER_DISCONNECTED))); + mPowerReceiver = receiverCaptor.getValue(); + try { + mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0); + // Need to do this since we're using a mock JS and not a real object. + doReturn(new ArraySet<>(new String[]{SOURCE_PACKAGE})) + .when(mJobSchedulerService).getPackagesForUidLocked(mSourceUid); + } catch (RemoteException e) { + fail(e.getMessage()); + } + setPowerConnected(false); + } + + @After + public void tearDown() { + if (mMockingSession != null) { + mMockingSession.finishMocking(); + } + } + + private void setBatteryNotLow(boolean notLow) { + doReturn(notLow).when(mJobSchedulerService).isBatteryNotLow(); + synchronized (mBatteryController.mLock) { + mBatteryController.onBatteryStateChangedLocked(); + } + waitForNonDelayedMessagesProcessed(); + } + + private void setCharging() { + doReturn(true).when(mJobSchedulerService).isBatteryCharging(); + synchronized (mBatteryController.mLock) { + mBatteryController.onBatteryStateChangedLocked(); + } + waitForNonDelayedMessagesProcessed(); + } + + private void setDischarging() { + doReturn(false).when(mJobSchedulerService).isBatteryCharging(); + synchronized (mBatteryController.mLock) { + mBatteryController.onBatteryStateChangedLocked(); + } + waitForNonDelayedMessagesProcessed(); + } + + private void setPowerConnected(boolean connected) { + Intent intent = new Intent( + connected ? Intent.ACTION_POWER_CONNECTED : Intent.ACTION_POWER_DISCONNECTED); + mPowerReceiver.onReceive(mContext, intent); + } + + private void setUidBias(int uid, int bias) { + int prevBias = mJobSchedulerService.getUidBias(uid); + doReturn(bias).when(mJobSchedulerService).getUidBias(uid); + synchronized (mBatteryController.mLock) { + mBatteryController.onUidBiasChangedLocked(uid, prevBias, bias); + } + } + + private void trackJobs(JobStatus... jobs) { + for (JobStatus job : jobs) { + synchronized (mBatteryController.mLock) { + mBatteryController.maybeStartTrackingJobLocked(job, null); + } + } + } + + private void waitForNonDelayedMessagesProcessed() { + JobSchedulerBackgroundThread.getHandler().runWithScissors(() -> {}, 15_000); + } + + private JobInfo.Builder createBaseJobInfoBuilder(int jobId) { + return new JobInfo.Builder(jobId, new ComponentName(mContext, "TestBatteryJobService")); + } + + private JobInfo.Builder createBaseJobInfoBuilder(int jobId, String pkgName) { + return new JobInfo.Builder(jobId, new ComponentName(pkgName, "TestBatteryJobService")); + } + + private JobStatus createJobStatus(String testTag, String packageName, int callingUid, + JobInfo jobInfo) { + JobStatus js = JobStatus.createFromJobInfo( + jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag); + js.serviceInfo = mock(ServiceInfo.class); + // Make sure tests aren't passing just because the default bucket is likely ACTIVE. + js.setStandbyBucket(FREQUENT_INDEX); + return js; + } + + @Test + public void testBatteryNotLow() { + JobStatus job1 = createJobStatus("testBatteryNotLow", SOURCE_PACKAGE, CALLING_UID, + createBaseJobInfoBuilder(1).setRequiresBatteryNotLow(true).build()); + JobStatus job2 = createJobStatus("testBatteryNotLow", SOURCE_PACKAGE, CALLING_UID, + createBaseJobInfoBuilder(2).setRequiresBatteryNotLow(true).build()); + + setBatteryNotLow(false); + trackJobs(job1); + assertFalse(job1.isConstraintSatisfied(JobStatus.CONSTRAINT_BATTERY_NOT_LOW)); + + setBatteryNotLow(true); + assertTrue(job1.isConstraintSatisfied(JobStatus.CONSTRAINT_BATTERY_NOT_LOW)); + + trackJobs(job2); + assertTrue(job2.isConstraintSatisfied(JobStatus.CONSTRAINT_BATTERY_NOT_LOW)); + } + + @Test + public void testCharging_BatteryNotLow() { + JobStatus job1 = createJobStatus("testCharging_BatteryNotLow", SOURCE_PACKAGE, CALLING_UID, + createBaseJobInfoBuilder(1) + .setRequiresCharging(true) + .setRequiresBatteryNotLow(true).build()); + JobStatus job2 = createJobStatus("testCharging_BatteryNotLow", SOURCE_PACKAGE, CALLING_UID, + createBaseJobInfoBuilder(2) + .setRequiresCharging(true) + .setRequiresBatteryNotLow(false).build()); + + setBatteryNotLow(true); + setDischarging(); + trackJobs(job1, job2); + assertFalse(job1.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(job2.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + + setCharging(); + assertTrue(job1.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertTrue(job2.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + } + + @Test + public void testTopPowerConnectedExemption() { + final int uid1 = mSourceUid; + final int uid2 = mSourceUid + 1; + final int uid3 = mSourceUid + 2; + JobStatus jobFg = createJobStatus("testTopPowerConnectedExemption", SOURCE_PACKAGE, uid1, + createBaseJobInfoBuilder(1).setRequiresCharging(true).build()); + JobStatus jobFgRunner = createJobStatus("testTopPowerConnectedExemption", + SOURCE_PACKAGE, uid1, + createBaseJobInfoBuilder(2).setRequiresCharging(true).build()); + JobStatus jobFgLow = createJobStatus("testTopPowerConnectedExemption", SOURCE_PACKAGE, uid1, + createBaseJobInfoBuilder(3) + .setRequiresCharging(true) + .setPriority(JobInfo.PRIORITY_LOW) + .build()); + JobStatus jobBg = createJobStatus("testTopPowerConnectedExemption", + "some.background.app", uid2, + createBaseJobInfoBuilder(4, "some.background.app") + .setRequiresCharging(true) + .build()); + JobStatus jobLateFg = createJobStatus("testTopPowerConnectedExemption", + "switch.to.fg", uid3, + createBaseJobInfoBuilder(5, "switch.to.fg").setRequiresCharging(true).build()); + JobStatus jobLateFgLow = createJobStatus("testTopPowerConnectedExemption", + "switch.to.fg", uid3, + createBaseJobInfoBuilder(6, "switch.to.fg") + .setRequiresCharging(true) + .setPriority(JobInfo.PRIORITY_MIN) + .build()); + + setBatteryNotLow(false); + setDischarging(); + setUidBias(uid1, JobInfo.BIAS_TOP_APP); + setUidBias(uid2, JobInfo.BIAS_DEFAULT); + setUidBias(uid3, JobInfo.BIAS_DEFAULT); + + // Jobs are scheduled when power isn't connected. + setPowerConnected(false); + trackJobs(jobFg, jobFgLow, jobBg, jobLateFg, jobLateFgLow); + assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobLateFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobLateFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + + // Power is connected. TOP app should be allowed to start job DEFAULT+ jobs. + setPowerConnected(true); + assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobLateFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobLateFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + + // Test that newly scheduled job of TOP app is correctly allowed to run. + trackJobs(jobFgRunner); + assertTrue(jobFgRunner.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + + // Switch top app. New TOP app should be allowed to run job and the running job of + // previously TOP app should be allowed to continue to run. + synchronized (mBatteryController.mLock) { + mBatteryController.prepareForExecutionLocked(jobFgRunner); + } + setUidBias(uid1, JobInfo.BIAS_DEFAULT); + setUidBias(uid2, JobInfo.BIAS_DEFAULT); + setUidBias(uid3, JobInfo.BIAS_TOP_APP); + assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertTrue(jobFgRunner.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertTrue(jobLateFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobLateFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + + setPowerConnected(false); + assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobFgRunner.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobLateFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobLateFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java index 95912b26e830f..d7414591f83c7 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java @@ -200,8 +200,10 @@ public class PrefetchControllerTest { } private void setUidBias(int uid, int bias) { + int prevBias = mJobSchedulerService.getUidBias(uid); + doReturn(bias).when(mJobSchedulerService).getUidBias(uid); synchronized (mPrefetchController.mLock) { - mPrefetchController.onUidBiasChangedLocked(uid, bias); + mPrefetchController.onUidBiasChangedLocked(uid, prevBias, bias); } }