summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobInfo.java6
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java69
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java39
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