Adjust execution limits for new job types.

Set new execution limits for user-initiated and data transfer jobs.
To start, we'll have the following execution limits:
* User-initiated: [10 minutes, 60 minutes]
* Data transfer: [10 minutes, 30 minutes] (same as regular jobs)
* User-initiated data transfer:
    [max{10 minutes, 1.35 * estimated transfer time}, 292471 millenia)

Also fix a bug in the estimated transfer time calculation.

Bug: 261622663
Test: atest FrameworksMockingServicesTests:ConnectivityControllerTest
Test: atest FrameworksMockingServicesTests:JobSchedulerServiceTest
Change-Id: Ib80008acba98f740746d7f2d74ce33c57221bee5
This commit is contained in:
Kweku Adams
2022-12-06 21:12:13 +00:00
parent 045bda7472
commit 6a1058da06
5 changed files with 454 additions and 36 deletions

View File

@@ -266,6 +266,8 @@ public class JobSchedulerService extends com.android.server.SystemService
private final List<RestrictingController> mRestrictiveControllers;
/** Need direct access to this for testing. */
private final StorageController mStorageController;
/** Needed to get estimated transfer time. */
private final ConnectivityController mConnectivityController;
/** Need directly for sending uid state changes */
private final DeviceIdleJobsController mDeviceIdleJobsController;
/** Needed to get next estimated launch time. */
@@ -464,6 +466,13 @@ public class JobSchedulerService extends com.android.server.SystemService
case Constants.KEY_RUNTIME_MIN_GUARANTEE_MS:
case Constants.KEY_RUNTIME_MIN_EJ_GUARANTEE_MS:
case Constants.KEY_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS:
case Constants.KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS:
case Constants.KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS:
case Constants.KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS:
case Constants.KEY_RUNTIME_USER_INITIATED_LIMIT_MS:
case Constants.KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR:
case Constants.KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS:
case Constants.KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS:
if (!runtimeUpdated) {
mConstants.updateRuntimeConstantsLocked();
runtimeUpdated = true;
@@ -555,6 +564,21 @@ public class JobSchedulerService extends com.android.server.SystemService
private static final String KEY_RUNTIME_MIN_EJ_GUARANTEE_MS = "runtime_min_ej_guarantee_ms";
private static final String KEY_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS =
"runtime_min_high_priority_guarantee_ms";
private static final String KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS =
"runtime_min_data_transfer_guarantee_ms";
private static final String KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS =
"runtime_data_transfer_limit_ms";
private static final String KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS =
"runtime_min_user_initiated_guarantee_ms";
private static final String KEY_RUNTIME_USER_INITIATED_LIMIT_MS =
"runtime_user_initiated_limit_ms";
private static final String
KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR =
"runtime_min_user_initiated_data_transfer_guarantee_buffer_factor";
private static final String KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS =
"runtime_min_user_initiated_data_transfer_guarantee_ms";
private static final String KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS =
"runtime_user_initiated_data_transfer_limit_ms";
private static final String KEY_PERSIST_IN_SPLIT_FILES = "persist_in_split_files";
@@ -584,6 +608,20 @@ public class JobSchedulerService extends com.android.server.SystemService
public static final long DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS = 3 * MINUTE_IN_MILLIS;
@VisibleForTesting
static final long DEFAULT_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS = 5 * MINUTE_IN_MILLIS;
public static final long DEFAULT_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS =
DEFAULT_RUNTIME_MIN_GUARANTEE_MS;
public static final long DEFAULT_RUNTIME_DATA_TRANSFER_LIMIT_MS =
DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
public static final long DEFAULT_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS =
Math.max(10 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_MIN_GUARANTEE_MS);
public static final long DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS =
Math.max(60 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS);
public static final float
DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = 1.35f;
public static final long DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS =
Math.max(10 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS);
public static final long DEFAULT_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS =
Math.max(Long.MAX_VALUE, DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS);
static final boolean DEFAULT_PERSIST_IN_SPLIT_FILES = true;
private static final boolean DEFAULT_USE_TARE_POLICY = false;
@@ -699,6 +737,49 @@ public class JobSchedulerService extends com.android.server.SystemService
public long RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS =
DEFAULT_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS;
/**
* The minimum amount of time we try to guarantee normal data transfer jobs will run for.
*/
public long RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS =
DEFAULT_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS;
/**
* The maximum amount of time we will let a normal data transfer job run for. This will only
* apply if there are no other limits that apply to the specific data transfer job.
*/
public long RUNTIME_DATA_TRANSFER_LIMIT_MS = DEFAULT_RUNTIME_DATA_TRANSFER_LIMIT_MS;
/**
* The minimum amount of time we try to guarantee normal user-initiated jobs will run for.
*/
public long RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS =
DEFAULT_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS;
/**
* The maximum amount of time we will let a user-initiated job run for. This will only
* apply if there are no other limits that apply to the specific user-initiated job.
*/
public long RUNTIME_USER_INITIATED_LIMIT_MS = DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS;
/**
* A factor to apply to estimated transfer durations for user-initiated data transfer jobs
* so that we give some extra time for unexpected situations. This will be at least 1 and
* so can just be multiplied with the original value to get the final value.
*/
public float RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR =
DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR;
/**
* The minimum amount of time we try to guarantee user-initiated data transfer jobs
* will run for.
*/
public long RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS =
DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS;
/** The maximum amount of time we will let a user-initiated data transfer job run for. */
public long RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS =
DEFAULT_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS;
/**
* Whether to persist jobs in split files (by UID). If false, all persisted jobs will be
* saved in a single file.
@@ -801,7 +882,14 @@ public class JobSchedulerService extends com.android.server.SystemService
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
KEY_RUNTIME_MIN_GUARANTEE_MS, KEY_RUNTIME_MIN_EJ_GUARANTEE_MS,
KEY_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS);
KEY_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS,
KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR,
KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS,
KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
KEY_RUNTIME_USER_INITIATED_LIMIT_MS,
KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS);
// Make sure min runtime for regular jobs is at least 10 minutes.
RUNTIME_MIN_GUARANTEE_MS = Math.max(10 * MINUTE_IN_MILLIS,
@@ -819,6 +907,49 @@ public class JobSchedulerService extends com.android.server.SystemService
RUNTIME_FREE_QUOTA_MAX_LIMIT_MS = Math.max(RUNTIME_MIN_GUARANTEE_MS,
properties.getLong(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS));
// Make sure min runtime is at least as long as regular jobs.
RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS = Math.max(RUNTIME_MIN_GUARANTEE_MS,
properties.getLong(
KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
DEFAULT_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS));
// Max limit should be at least the min guarantee AND the free quota.
RUNTIME_DATA_TRANSFER_LIMIT_MS = Math.max(RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
Math.max(RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
properties.getLong(
KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS,
DEFAULT_RUNTIME_DATA_TRANSFER_LIMIT_MS)));
// Make sure min runtime is at least as long as regular jobs.
RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS = Math.max(RUNTIME_MIN_GUARANTEE_MS,
properties.getLong(
KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
DEFAULT_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS));
// Max limit should be at least the min guarantee AND the free quota.
RUNTIME_USER_INITIATED_LIMIT_MS = Math.max(RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
Math.max(RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
properties.getLong(
KEY_RUNTIME_USER_INITIATED_LIMIT_MS,
DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS)));
// The buffer factor should be at least 1 (so we don't decrease the time).
RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = Math.max(1,
properties.getFloat(
KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR,
DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR
));
// Make sure min runtime is at least as long as other user-initiated jobs.
RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS = Math.max(
RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
properties.getLong(
KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS));
// Data transfer requires RUN_LONG_JOBS permission, so the upper limit will be higher
// than other jobs.
// Max limit should be the min guarantee and the max of other user-initiated jobs.
RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS = Math.max(
RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
Math.max(RUNTIME_USER_INITIATED_LIMIT_MS,
properties.getLong(
KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
DEFAULT_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS)));
}
private boolean updateTareSettingsLocked(boolean isTareEnabled) {
@@ -867,6 +998,20 @@ public class JobSchedulerService extends com.android.server.SystemService
RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS).println();
pw.print(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
.println();
pw.print(KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS).println();
pw.print(KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS,
RUNTIME_DATA_TRANSFER_LIMIT_MS).println();
pw.print(KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS).println();
pw.print(KEY_RUNTIME_USER_INITIATED_LIMIT_MS,
RUNTIME_USER_INITIATED_LIMIT_MS).println();
pw.print(KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR,
RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR).println();
pw.print(KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS).println();
pw.print(KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS).println();
pw.print(KEY_PERSIST_IN_SPLIT_FILES, PERSIST_IN_SPLIT_FILES).println();
@@ -1858,9 +2003,9 @@ public class JobSchedulerService extends com.android.server.SystemService
final FlexibilityController flexibilityController =
new FlexibilityController(this, mPrefetchController);
mControllers.add(flexibilityController);
final ConnectivityController connectivityController =
mConnectivityController =
new ConnectivityController(this, flexibilityController);
mControllers.add(connectivityController);
mControllers.add(mConnectivityController);
mControllers.add(new TimeController(this));
final IdleController idleController = new IdleController(this, flexibilityController);
mControllers.add(idleController);
@@ -1876,16 +2021,16 @@ public class JobSchedulerService extends com.android.server.SystemService
mDeviceIdleJobsController = new DeviceIdleJobsController(this);
mControllers.add(mDeviceIdleJobsController);
mQuotaController =
new QuotaController(this, backgroundJobsController, connectivityController);
new QuotaController(this, backgroundJobsController, mConnectivityController);
mControllers.add(mQuotaController);
mControllers.add(new ComponentController(this));
mTareController =
new TareController(this, backgroundJobsController, connectivityController);
new TareController(this, backgroundJobsController, mConnectivityController);
mControllers.add(mTareController);
mRestrictiveControllers = new ArrayList<>();
mRestrictiveControllers.add(batteryController);
mRestrictiveControllers.add(connectivityController);
mRestrictiveControllers.add(mConnectivityController);
mRestrictiveControllers.add(idleController);
// Create restrictions
@@ -3028,7 +3173,30 @@ public class JobSchedulerService extends com.android.server.SystemService
/** Returns the minimum amount of time we should let this job run before timing out. */
public long getMinJobExecutionGuaranteeMs(JobStatus job) {
synchronized (mLock) {
if (job.shouldTreatAsExpeditedJob()) {
final boolean shouldTreatAsDataTransfer = job.getJob().isDataTransfer()
&& checkRunLongJobsPermission(job.getSourceUid(), job.getSourcePackageName());
if (job.shouldTreatAsUserInitiated()) {
if (shouldTreatAsDataTransfer) {
final long estimatedTransferTimeMs =
mConnectivityController.getEstimatedTransferTimeMs(job);
if (estimatedTransferTimeMs == ConnectivityController.UNKNOWN_TIME) {
return mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS;
}
// Try to give the job at least as much time as we think the transfer will take,
// but cap it at the maximum limit
final long factoredTransferTimeMs = (long) (estimatedTransferTimeMs
* mConstants
.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR);
return Math.min(mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
Math.max(factoredTransferTimeMs,
mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS
));
}
return mConstants.RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS;
} else if (shouldTreatAsDataTransfer) {
// For now, don't increase a bg data transfer's minimum guarantee.
return mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS;
} else if (job.shouldTreatAsExpeditedJob()) {
// Don't guarantee RESTRICTED jobs more than 5 minutes.
return job.getEffectiveStandbyBucket() != RESTRICTED_INDEX
? mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS
@@ -3044,6 +3212,16 @@ public class JobSchedulerService extends com.android.server.SystemService
/** Returns the maximum amount of time this job could run for. */
public long getMaxJobExecutionTimeMs(JobStatus job) {
synchronized (mLock) {
final boolean shouldTreatAsDataTransfer = job.getJob().isDataTransfer()
&& checkRunLongJobsPermission(job.getSourceUid(), job.getSourcePackageName());
if (job.shouldTreatAsUserInitiated()) {
if (shouldTreatAsDataTransfer) {
return mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS;
}
return mConstants.RUNTIME_USER_INITIATED_LIMIT_MS;
} else if (shouldTreatAsDataTransfer) {
return mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS;
}
return Math.min(mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mConstants.USE_TARE_POLICY
? mTareController.getMaxJobExecutionTimeMsLocked(job)
@@ -3703,13 +3881,6 @@ public class JobSchedulerService extends com.android.server.SystemService
return checkRunLongJobsPermission(uid, packageName);
}
private boolean checkRunLongJobsPermission(int packageUid, String packageName) {
// Returns true if both the appop and permission are granted.
return PermissionChecker.checkPermissionForPreflight(getContext(),
android.Manifest.permission.RUN_LONG_JOBS, PermissionChecker.PID_UNKNOWN,
packageUid, packageName) == PermissionChecker.PERMISSION_GRANTED;
}
/**
* "dumpsys" infrastructure
*/
@@ -3985,13 +4156,27 @@ public class JobSchedulerService extends com.android.server.SystemService
}
}
private boolean checkRunLongJobsPermission(int packageUid, String packageName) {
// Returns true if both the appop and permission are granted.
return PermissionChecker.checkPermissionForPreflight(getTestableContext(),
android.Manifest.permission.RUN_LONG_JOBS, PermissionChecker.PID_UNKNOWN,
packageUid, packageName) == PermissionChecker.PERMISSION_GRANTED;
}
@VisibleForTesting
protected ConnectivityController getConnectivityController() {
return mConnectivityController;
}
// Shell command infrastructure
int getJobState(PrintWriter pw, String pkgName, int userId, int jobId) {
try {
final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM);
if (uid < 0) {
pw.print("unknown("); pw.print(pkgName); pw.println(")");
pw.print("unknown(");
pw.print(pkgName);
pw.println(")");
return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
}

View File

@@ -42,7 +42,6 @@ import android.telephony.TelephonyManager;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.DataUnit;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Pools;
@@ -82,6 +81,8 @@ public final class ConnectivityController extends RestrictingController implemen
private static final boolean DEBUG = JobSchedulerService.DEBUG
|| Log.isLoggable(TAG, Log.DEBUG);
public static final long UNKNOWN_TIME = -1L;
// The networking stack has a hard limit so we can't make this configurable.
private static final int MAX_NETWORK_CALLBACKS = 125;
/**
@@ -570,9 +571,8 @@ public final class ConnectivityController extends RestrictingController implemen
// If we don't know the bandwidth, all we can do is hope the job finishes the minimum
// chunk in time.
if (bandwidthDown > 0) {
// Divide by 8 to convert bits to bytes.
final long estimatedMillis = ((minimumChunkBytes * DateUtils.SECOND_IN_MILLIS)
/ (DataUnit.KIBIBYTES.toBytes(bandwidthDown) / 8));
final long estimatedMillis =
calculateTransferTimeMs(minimumChunkBytes, bandwidthDown);
if (estimatedMillis > maxJobExecutionTimeMs) {
// If we'd never finish the minimum chunk before the timeout, we'd be insane!
Slog.w(TAG, "Minimum chunk " + minimumChunkBytes + " bytes over "
@@ -585,9 +585,8 @@ public final class ConnectivityController extends RestrictingController implemen
final long bandwidthUp = capabilities.getLinkUpstreamBandwidthKbps();
// If we don't know the bandwidth, all we can do is hope the job finishes in time.
if (bandwidthUp > 0) {
// Divide by 8 to convert bits to bytes.
final long estimatedMillis = ((minimumChunkBytes * DateUtils.SECOND_IN_MILLIS)
/ (DataUnit.KIBIBYTES.toBytes(bandwidthUp) / 8));
final long estimatedMillis =
calculateTransferTimeMs(minimumChunkBytes, bandwidthUp);
if (estimatedMillis > maxJobExecutionTimeMs) {
// If we'd never finish the minimum chunk before the timeout, we'd be insane!
Slog.w(TAG, "Minimum chunk " + minimumChunkBytes + " bytes over " + bandwidthUp
@@ -615,9 +614,7 @@ public final class ConnectivityController extends RestrictingController implemen
final long bandwidth = capabilities.getLinkDownstreamBandwidthKbps();
// If we don't know the bandwidth, all we can do is hope the job finishes in time.
if (bandwidth > 0) {
// Divide by 8 to convert bits to bytes.
final long estimatedMillis = ((downloadBytes * DateUtils.SECOND_IN_MILLIS)
/ (DataUnit.KIBIBYTES.toBytes(bandwidth) / 8));
final long estimatedMillis = calculateTransferTimeMs(downloadBytes, bandwidth);
if (estimatedMillis > maxJobExecutionTimeMs) {
// If we'd never finish before the timeout, we'd be insane!
Slog.w(TAG, "Estimated " + downloadBytes + " download bytes over " + bandwidth
@@ -633,9 +630,7 @@ public final class ConnectivityController extends RestrictingController implemen
final long bandwidth = capabilities.getLinkUpstreamBandwidthKbps();
// If we don't know the bandwidth, all we can do is hope the job finishes in time.
if (bandwidth > 0) {
// Divide by 8 to convert bits to bytes.
final long estimatedMillis = ((uploadBytes * DateUtils.SECOND_IN_MILLIS)
/ (DataUnit.KIBIBYTES.toBytes(bandwidth) / 8));
final long estimatedMillis = calculateTransferTimeMs(uploadBytes, bandwidth);
if (estimatedMillis > maxJobExecutionTimeMs) {
// If we'd never finish before the timeout, we'd be insane!
Slog.w(TAG, "Estimated " + uploadBytes + " upload bytes over " + bandwidth
@@ -649,6 +644,48 @@ public final class ConnectivityController extends RestrictingController implemen
return false;
}
/**
* Return the estimated amount of time this job will be transferring data,
* based on the current network speed.
*/
public long getEstimatedTransferTimeMs(JobStatus jobStatus) {
final long downloadBytes = jobStatus.getEstimatedNetworkDownloadBytes();
final long uploadBytes = jobStatus.getEstimatedNetworkUploadBytes();
if (downloadBytes == JobInfo.NETWORK_BYTES_UNKNOWN
&& uploadBytes == JobInfo.NETWORK_BYTES_UNKNOWN) {
return UNKNOWN_TIME;
}
if (jobStatus.network == null) {
// This job doesn't have a network assigned.
return UNKNOWN_TIME;
}
NetworkCapabilities capabilities = getNetworkCapabilities(jobStatus.network);
if (capabilities == null) {
return UNKNOWN_TIME;
}
final long estimatedDownloadTimeMs = calculateTransferTimeMs(downloadBytes,
capabilities.getLinkDownstreamBandwidthKbps());
final long estimatedUploadTimeMs = calculateTransferTimeMs(uploadBytes,
capabilities.getLinkUpstreamBandwidthKbps());
if (estimatedDownloadTimeMs == UNKNOWN_TIME) {
return estimatedUploadTimeMs;
} else if (estimatedUploadTimeMs == UNKNOWN_TIME) {
return estimatedDownloadTimeMs;
}
return estimatedDownloadTimeMs + estimatedUploadTimeMs;
}
@VisibleForTesting
static long calculateTransferTimeMs(long transferBytes, long bandwidthKbps) {
if (transferBytes == JobInfo.NETWORK_BYTES_UNKNOWN || bandwidthKbps <= 0) {
return UNKNOWN_TIME;
}
return (transferBytes * DateUtils.SECOND_IN_MILLIS)
// Multiply by 1000 to convert kilobits to bits.
// Divide by 8 to convert bits to bytes.
/ (bandwidthKbps * 1000 / 8);
}
private static boolean isCongestionDelayed(JobStatus jobStatus, Network network,
NetworkCapabilities capabilities, Constants constants) {
// If network is congested, and job is less than 50% through the

View File

@@ -1344,6 +1344,15 @@ public final class JobStatus {
return mExpeditedQuotaApproved && mExpeditedTareApproved && isRequestedExpeditedJob();
}
/**
* @return true if the job was scheduled as a user-initiated job and it hasn't been downgraded
* for any reason.
*/
public boolean shouldTreatAsUserInitiated() {
// TODO(248386641): implement
return false;
}
/**
* Return a summary that uniquely identifies the underlying job.
*/

View File

@@ -34,6 +34,8 @@ import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
@@ -46,6 +48,7 @@ import android.app.job.JobScheduler;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.Context;
import android.content.PermissionChecker;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.res.Resources;
@@ -63,6 +66,7 @@ import com.android.server.DeviceIdleInternal;
import com.android.server.LocalServices;
import com.android.server.PowerAllowlistInternal;
import com.android.server.SystemServiceManager;
import com.android.server.job.controllers.ConnectivityController;
import com.android.server.job.controllers.JobStatus;
import com.android.server.pm.UserManagerInternal;
import com.android.server.usage.AppStandbyInternal;
@@ -102,6 +106,7 @@ public class JobSchedulerServiceTest {
.initMocks(this)
.strictness(Strictness.LENIENT)
.mockStatic(LocalServices.class)
.mockStatic(PermissionChecker.class)
.mockStatic(ServiceManager.class)
.startMocking();
@@ -193,6 +198,15 @@ public class JobSchedulerServiceTest {
jobInfoBuilder.build(), callingUid, "com.android.test", 0, testTag);
}
private void grantRunLongJobsPermission(boolean grant) {
final int permissionStatus = grant
? PermissionChecker.PERMISSION_GRANTED : PermissionChecker.PERMISSION_HARD_DENIED;
doReturn(permissionStatus)
.when(() -> PermissionChecker.checkPermissionForPreflight(
any(), eq(android.Manifest.permission.RUN_LONG_JOBS),
anyInt(), anyInt(), anyString()));
}
@Test
public void testGetMinJobExecutionGuaranteeMs() {
JobStatus ejMax = createJobStatus("testGetMinJobExecutionGuaranteeMs",
@@ -207,6 +221,15 @@ public class JobSchedulerServiceTest {
createJobInfo(5).setPriority(JobInfo.PRIORITY_HIGH));
JobStatus jobDef = createJobStatus("testGetMinJobExecutionGuaranteeMs",
createJobInfo(6));
JobStatus jobDT = createJobStatus("testGetMinJobExecutionGuaranteeMs",
createJobInfo(7)
.setDataTransfer(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
JobStatus jobUI = createJobStatus("testGetMinJobExecutionGuaranteeMs",
createJobInfo(8)); // TODO(255371817): add setUserInitiated(true)
JobStatus jobUIDT = createJobStatus("testGetMinJobExecutionGuaranteeMs",
// TODO(255371817): add setUserInitiated(true)
createJobInfo(9)
.setDataTransfer(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
spyOn(ejMax);
spyOn(ejHigh);
@@ -214,6 +237,9 @@ public class JobSchedulerServiceTest {
spyOn(ejHighDowngraded);
spyOn(jobHigh);
spyOn(jobDef);
spyOn(jobDT);
spyOn(jobUI);
spyOn(jobUIDT);
when(ejMax.shouldTreatAsExpeditedJob()).thenReturn(true);
when(ejHigh.shouldTreatAsExpeditedJob()).thenReturn(true);
@@ -221,6 +247,16 @@ public class JobSchedulerServiceTest {
when(ejHighDowngraded.shouldTreatAsExpeditedJob()).thenReturn(false);
when(jobHigh.shouldTreatAsExpeditedJob()).thenReturn(false);
when(jobDef.shouldTreatAsExpeditedJob()).thenReturn(false);
when(jobUI.shouldTreatAsUserInitiated()).thenReturn(true);
when(jobUIDT.shouldTreatAsUserInitiated()).thenReturn(true);
ConnectivityController connectivityController = mService.getConnectivityController();
spyOn(connectivityController);
mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS = 10 * MINUTE_IN_MILLIS;
mService.mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS = 60 * MINUTE_IN_MILLIS;
mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = 1.5f;
mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS = HOUR_IN_MILLIS;
mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS = 6 * HOUR_IN_MILLIS;
assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(ejMax));
@@ -234,8 +270,81 @@ public class JobSchedulerServiceTest {
mService.getMinJobExecutionGuaranteeMs(jobHigh));
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobDef));
grantRunLongJobsPermission(false); // Without permission
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobDT));
grantRunLongJobsPermission(true); // With permission
doReturn(ConnectivityController.UNKNOWN_TIME)
.when(connectivityController).getEstimatedTransferTimeMs(any());
assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobDT));
doReturn(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS / 2)
.when(connectivityController).getEstimatedTransferTimeMs(any());
assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobDT));
doReturn(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS * 2)
.when(connectivityController).getEstimatedTransferTimeMs(any());
assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobDT));
doReturn(mService.mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS * 2)
.when(connectivityController).getEstimatedTransferTimeMs(any());
assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobDT));
// UserInitiated
assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUI));
grantRunLongJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUIDT));
grantRunLongJobsPermission(true); // With permission
doReturn(ConnectivityController.UNKNOWN_TIME)
.when(connectivityController).getEstimatedTransferTimeMs(any());
assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUIDT));
doReturn(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS / 2)
.when(connectivityController).getEstimatedTransferTimeMs(any());
assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUIDT));
doReturn(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS * 2)
.when(connectivityController).getEstimatedTransferTimeMs(any());
assertEquals(
(long) (mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS
* 2 * 1.5),
mService.getMinJobExecutionGuaranteeMs(jobUIDT));
doReturn(mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS * 2)
.when(connectivityController).getEstimatedTransferTimeMs(any());
assertEquals(mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
mService.getMinJobExecutionGuaranteeMs(jobUIDT));
}
@Test
public void testGetMaxJobExecutionTimeMs() {
JobStatus jobDT = createJobStatus("testGetMaxJobExecutionTimeMs",
createJobInfo(7)
.setDataTransfer(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
JobStatus jobUI = createJobStatus("testGetMaxJobExecutionTimeMs",
createJobInfo(9)); // TODO(255371817): add setUserInitiated(true)
JobStatus jobUIDT = createJobStatus("testGetMaxJobExecutionTimeMs",
// TODO(255371817): add setUserInitiated(true)
createJobInfo(10)
.setDataTransfer(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
spyOn(jobDT);
spyOn(jobUI);
spyOn(jobUIDT);
when(jobUI.shouldTreatAsUserInitiated()).thenReturn(true);
when(jobUIDT.shouldTreatAsUserInitiated()).thenReturn(true);
grantRunLongJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobDT));
assertEquals(mService.mConstants.RUNTIME_USER_INITIATED_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUI));
assertEquals(mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUIDT));
}
/**
* Confirm that {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int)}

View File

@@ -24,6 +24,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.text.format.DateUtils.SECOND_IN_MILLIS;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -35,6 +36,7 @@ import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
import static com.android.server.job.JobSchedulerService.RARE_INDEX;
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -240,20 +242,20 @@ public class ConnectivityControllerTest {
.setLinkDownstreamBandwidthKbps(1).build(), mConstants));
// Slow downstream
assertFalse(controller.isSatisfied(createJobStatus(job), net,
createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(137)
createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(140)
.setLinkDownstreamBandwidthKbps(1).build(), mConstants));
// Slow upstream
assertFalse(controller.isSatisfied(createJobStatus(job), net,
createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(1)
.setLinkDownstreamBandwidthKbps(137).build(), mConstants));
.setLinkDownstreamBandwidthKbps(140).build(), mConstants));
// Network good enough
assertTrue(controller.isSatisfied(createJobStatus(job), net,
createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(137)
.setLinkDownstreamBandwidthKbps(137).build(), mConstants));
createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(140)
.setLinkDownstreamBandwidthKbps(140).build(), mConstants));
// Network slightly too slow given reduced time
assertFalse(controller.isSatisfied(createJobStatus(job), net,
createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(130)
.setLinkDownstreamBandwidthKbps(130).build(), mConstants));
createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(139)
.setLinkDownstreamBandwidthKbps(139).build(), mConstants));
// Slow network is too slow, but device is charging and network is unmetered.
when(mService.isBatteryCharging()).thenReturn(true);
controller.onBatteryStateChangedLocked();
@@ -1188,6 +1190,78 @@ public class ConnectivityControllerTest {
assertFalse(unnetworked.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
}
@Test
public void testCalculateTransferTimeMs() {
assertEquals(ConnectivityController.UNKNOWN_TIME,
ConnectivityController.calculateTransferTimeMs(1, 0));
assertEquals(ConnectivityController.UNKNOWN_TIME,
ConnectivityController.calculateTransferTimeMs(JobInfo.NETWORK_BYTES_UNKNOWN, 512));
assertEquals(1, ConnectivityController.calculateTransferTimeMs(1, 8));
assertEquals(1000, ConnectivityController.calculateTransferTimeMs(1000, 8));
assertEquals(8, ConnectivityController.calculateTransferTimeMs(1024, 1024));
}
@Test
public void testGetEstimatedTransferTimeMs() {
final ArgumentCaptor<NetworkCallback> callbackCaptor =
ArgumentCaptor.forClass(NetworkCallback.class);
doNothing().when(mConnManager).registerNetworkCallback(any(), callbackCaptor.capture());
final JobStatus job = createJobStatus(createJob()
.setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(10_000),
DataUnit.MEBIBYTES.toBytes(1_000))
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
final ConnectivityController controller = new ConnectivityController(mService,
mFlexibilityController);
final JobStatus jobNoEstimates = createJobStatus(createJob());
assertEquals(ConnectivityController.UNKNOWN_TIME,
controller.getEstimatedTransferTimeMs(jobNoEstimates));
// No network
job.network = null;
assertEquals(ConnectivityController.UNKNOWN_TIME,
controller.getEstimatedTransferTimeMs(job));
final NetworkCallback generalCallback = callbackCaptor.getValue();
// No capabilities
final Network network = mock(Network.class);
answerNetwork(generalCallback, null, null, network, null);
job.network = network;
assertEquals(ConnectivityController.UNKNOWN_TIME,
controller.getEstimatedTransferTimeMs(job));
// Capabilities don't have bandwidth values
NetworkCapabilities caps = createCapabilitiesBuilder().build();
answerNetwork(generalCallback, null, null, network, caps);
assertEquals(ConnectivityController.UNKNOWN_TIME,
controller.getEstimatedTransferTimeMs(job));
// Capabilities only has downstream bandwidth
caps = createCapabilitiesBuilder()
.setLinkDownstreamBandwidthKbps(1024)
.build();
answerNetwork(generalCallback, null, null, network, caps);
assertEquals(81920 * SECOND_IN_MILLIS, controller.getEstimatedTransferTimeMs(job));
// Capabilities only has upstream bandwidth
caps = createCapabilitiesBuilder()
.setLinkUpstreamBandwidthKbps(2 * 1024)
.build();
answerNetwork(generalCallback, null, null, network, caps);
assertEquals(4096 * SECOND_IN_MILLIS, controller.getEstimatedTransferTimeMs(job));
// Capabilities only both stream bandwidths
caps = createCapabilitiesBuilder()
.setLinkDownstreamBandwidthKbps(1024)
.setLinkUpstreamBandwidthKbps(2 * 1024)
.build();
answerNetwork(generalCallback, null, null, network, caps);
assertEquals((81920 + 4096) * SECOND_IN_MILLIS, controller.getEstimatedTransferTimeMs(job));
}
private void answerNetwork(@NonNull NetworkCallback generalCallback,
@Nullable NetworkCallback uidCallback, @Nullable Network lastNetwork,
@Nullable Network net, @Nullable NetworkCapabilities caps) {
@@ -1198,11 +1272,15 @@ public class ConnectivityControllerTest {
}
} else {
generalCallback.onAvailable(net);
generalCallback.onCapabilitiesChanged(net, caps);
if (caps != null) {
generalCallback.onCapabilitiesChanged(net, caps);
}
if (uidCallback != null) {
uidCallback.onAvailable(net);
uidCallback.onBlockedStatusChanged(net, ConnectivityManager.BLOCKED_REASON_NONE);
uidCallback.onCapabilitiesChanged(net, caps);
if (caps != null) {
uidCallback.onCapabilitiesChanged(net, caps);
}
}
}
}