diff options
3 files changed, 111 insertions, 3 deletions
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index b3c33b6615f4..144536ec2644 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -1229,7 +1229,11 @@ public class JobInfo implements Parcelable { * </ul> * Note that the system may choose to delay jobs with large network * usage estimates when the device has a poor network connection, in - * order to save battery. + * order to save battery and possible network costs. + * Starting from Android version {@link Build.VERSION_CODES#S}, JobScheduler may attempt + * to run large jobs when the device is charging and on an unmetered network, even if the + * network is slow. This gives large jobs an opportunity to make forward progress, even if + * they risk timing out. * <p> * The values provided here only reflect the traffic that will be * performed by the base job; if you're using {@link JobWorkItem} then 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 7b947fd3ab43..e8065aa535f3 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 @@ -25,12 +25,18 @@ import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.job.JobInfo; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkPolicyManager; import android.net.NetworkRequest; +import android.os.BatteryManager; +import android.os.BatteryManagerInternal; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -101,6 +107,8 @@ public final class ConnectivityController extends RestrictingController implemen private final ConnectivityManager mConnManager; private final NetworkPolicyManagerInternal mNetPolicyManagerInternal; + private final ChargingTracker mChargingTracker; + /** List of tracked jobs keyed by source UID. */ @GuardedBy("mLock") private final SparseArray<ArraySet<JobStatus>> mTrackedJobs = new SparseArray<>(); @@ -229,6 +237,9 @@ public final class ConnectivityController extends RestrictingController implemen // network changes against the active network for each UID with jobs. final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build(); mConnManager.registerNetworkCallback(request, mNetworkCallback); + + mChargingTracker = new ChargingTracker(); + mChargingTracker.startTracking(); } @GuardedBy("mLock") @@ -538,6 +549,14 @@ public final class ConnectivityController extends RestrictingController implemen */ private boolean isInsane(JobStatus jobStatus, Network network, NetworkCapabilities capabilities, Constants constants) { + if (capabilities.hasCapability(NET_CAPABILITY_NOT_METERED) + && mChargingTracker.isCharging()) { + // We're charging and on an unmetered network. We don't have to be as conservative about + // making sure the job will run within its max execution time. Let's just hope the app + // supports interruptible work. + return false; + } + // Use the maximum possible time since it gives us an upper bound, even though the job // could end up stopping earlier. final long maxJobExecutionTimeMs = mService.getMaxJobExecutionTimeMs(jobStatus); @@ -922,7 +941,7 @@ public final class ConnectivityController extends RestrictingController implemen * {@link Network}, or {@code null} to update all tracked jobs. */ @GuardedBy("mLock") - private void updateTrackedJobsLocked(int filterUid, Network filterNetwork) { + private void updateTrackedJobsLocked(int filterUid, @Nullable Network filterNetwork) { boolean changed = false; if (filterUid == -1) { for (int i = mTrackedJobs.size() - 1; i >= 0; i--) { @@ -937,7 +956,8 @@ public final class ConnectivityController extends RestrictingController implemen } @GuardedBy("mLock") - private boolean updateTrackedJobsLocked(ArraySet<JobStatus> jobs, Network filterNetwork) { + private boolean updateTrackedJobsLocked(ArraySet<JobStatus> jobs, + @Nullable Network filterNetwork) { if (jobs == null || jobs.size() == 0) { return false; } @@ -993,6 +1013,51 @@ public final class ConnectivityController extends RestrictingController implemen } } + private final class ChargingTracker extends BroadcastReceiver { + /** + * Track whether we're "charging", where charging means that we're ready to commit to + * doing work. + */ + private boolean mCharging; + + ChargingTracker() {} + + public void startTracking() { + IntentFilter filter = new IntentFilter(); + filter.addAction(BatteryManager.ACTION_CHARGING); + filter.addAction(BatteryManager.ACTION_DISCHARGING); + mContext.registerReceiver(this, filter); + + // Initialise tracker state. + final BatteryManagerInternal batteryManagerInternal = + LocalServices.getService(BatteryManagerInternal.class); + mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY); + } + + public boolean isCharging() { + return mCharging; + } + + @Override + public void onReceive(Context context, Intent intent) { + synchronized (mLock) { + final String action = intent.getAction(); + if (BatteryManager.ACTION_CHARGING.equals(action)) { + if (mCharging) { + return; + } + mCharging = true; + } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) { + if (!mCharging) { + return; + } + mCharging = false; + } + updateTrackedJobsLocked(-1, null); + } + } + } + private final NetworkCallback mNetworkCallback = new NetworkCallback() { @Override public void onAvailable(Network network) { diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java index 68cb8f94aa0a..3dc7bc1154ea 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java @@ -48,14 +48,18 @@ import static org.mockito.Mockito.verify; import android.annotation.NonNull; import android.annotation.Nullable; 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.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkPolicyManager; +import android.os.BatteryManager; +import android.os.BatteryManagerInternal; import android.os.Build; import android.os.Looper; import android.os.SystemClock; @@ -70,6 +74,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; @@ -83,6 +88,8 @@ public class ConnectivityControllerTest { @Mock private Context mContext; @Mock + private BatteryManagerInternal mBatteryManagerInternal; + @Mock private ConnectivityManager mConnManager; @Mock private NetworkPolicyManager mNetPolicyManager; @@ -108,6 +115,9 @@ public class ConnectivityControllerTest { LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class); LocalServices.addService(NetworkPolicyManagerInternal.class, mNetPolicyManagerInternal); + LocalServices.removeServiceForTest(BatteryManagerInternal.class); + LocalServices.addService(BatteryManagerInternal.class, mBatteryManagerInternal); + when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper()); // Freeze the clocks at this moment in time @@ -143,8 +153,18 @@ public class ConnectivityControllerTest { DataUnit.MEBIBYTES.toBytes(1)) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); + final ArgumentCaptor<BroadcastReceiver> chargingCaptor = + ArgumentCaptor.forClass(BroadcastReceiver.class); + when(mBatteryManagerInternal.isPowered(eq(BatteryManager.BATTERY_PLUGGED_ANY))) + .thenReturn(false); final ConnectivityController controller = new ConnectivityController(mService); + verify(mContext).registerReceiver(chargingCaptor.capture(), + ArgumentMatchers.argThat(filter -> + filter.hasAction(BatteryManager.ACTION_CHARGING) + && filter.hasAction(BatteryManager.ACTION_DISCHARGING))); when(mService.getMaxJobExecutionTimeMs(any())).thenReturn(10 * 60_000L); + final BroadcastReceiver chargingReceiver = chargingCaptor.getValue(); + chargingReceiver.onReceive(mContext, new Intent(BatteryManager.ACTION_DISCHARGING)); // Slow network is too slow assertFalse(controller.isSatisfied(createJobStatus(job), net, @@ -166,7 +186,18 @@ public class ConnectivityControllerTest { assertTrue(controller.isSatisfied(createJobStatus(job), net, createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(130) .setLinkDownstreamBandwidthKbps(130).build(), mConstants)); + // Slow network is too slow, but device is charging and network is unmetered. + when(mBatteryManagerInternal.isPowered(eq(BatteryManager.BATTERY_PLUGGED_ANY))) + .thenReturn(true); + chargingReceiver.onReceive(mContext, new Intent(BatteryManager.ACTION_CHARGING)); + assertTrue(controller.isSatisfied(createJobStatus(job), net, + createCapabilitiesBuilder().addCapability(NET_CAPABILITY_NOT_METERED) + .setLinkUpstreamBandwidthKbps(1).setLinkDownstreamBandwidthKbps(1).build(), + mConstants)); + when(mBatteryManagerInternal.isPowered(eq(BatteryManager.BATTERY_PLUGGED_ANY))) + .thenReturn(false); + chargingReceiver.onReceive(mContext, new Intent(BatteryManager.ACTION_DISCHARGING)); when(mService.getMaxJobExecutionTimeMs(any())).thenReturn(60_000L); // Slow network is too slow @@ -189,6 +220,14 @@ public class ConnectivityControllerTest { assertFalse(controller.isSatisfied(createJobStatus(job), net, createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(130) .setLinkDownstreamBandwidthKbps(130).build(), mConstants)); + // Slow network is too slow, but device is charging and network is unmetered. + when(mBatteryManagerInternal.isPowered(eq(BatteryManager.BATTERY_PLUGGED_ANY))) + .thenReturn(true); + chargingReceiver.onReceive(mContext, new Intent(BatteryManager.ACTION_CHARGING)); + assertTrue(controller.isSatisfied(createJobStatus(job), net, + createCapabilitiesBuilder().addCapability(NET_CAPABILITY_NOT_METERED) + .setLinkUpstreamBandwidthKbps(1).setLinkDownstreamBandwidthKbps(1).build(), + mConstants)); } @Test |