Merge pull request #7395 from jdegroot-dss:add-storage-not-low-requirement

PiperOrigin-RevId: 313804207
This commit is contained in:
Oliver Woodman 2020-05-29 18:34:58 +01:00
commit 496a315d91
8 changed files with 133 additions and 12 deletions

View File

@ -142,6 +142,8 @@
directly instead. directly instead.
* Update `CachedContentIndex` to use `SecureRandom` for generating the * Update `CachedContentIndex` to use `SecureRandom` for generating the
initialization vector used to encrypt the cache contents. initialization vector used to encrypt the cache contents.
* Add `Requirements.DEVICE_STORAGE_NOT_LOW`, which can be specified as a
requirement to a `DownloadManager` for it to proceed with downloading.
* Audio: * Audio:
* Add a sample count parameter to `MediaCodecRenderer.processOutputBuffer` * Add a sample count parameter to `MediaCodecRenderer.processOutputBuffer`
and `AudioSink.handleBuffer` to allow batching multiple encoded frames and `AudioSink.handleBuffer` to allow batching multiple encoded frames

View File

@ -65,6 +65,11 @@ public final class JobDispatcherScheduler implements Scheduler {
private static final String KEY_SERVICE_ACTION = "service_action"; private static final String KEY_SERVICE_ACTION = "service_action";
private static final String KEY_SERVICE_PACKAGE = "service_package"; private static final String KEY_SERVICE_PACKAGE = "service_package";
private static final String KEY_REQUIREMENTS = "requirements"; private static final String KEY_REQUIREMENTS = "requirements";
private static final int SUPPORTED_REQUIREMENTS =
Requirements.NETWORK
| Requirements.NETWORK_UNMETERED
| Requirements.DEVICE_IDLE
| Requirements.DEVICE_CHARGING;
private final String jobTag; private final String jobTag;
private final FirebaseJobDispatcher jobDispatcher; private final FirebaseJobDispatcher jobDispatcher;
@ -96,24 +101,35 @@ public final class JobDispatcherScheduler implements Scheduler {
return result == FirebaseJobDispatcher.CANCEL_RESULT_SUCCESS; return result == FirebaseJobDispatcher.CANCEL_RESULT_SUCCESS;
} }
@Override
public Requirements getSupportedRequirements(Requirements requirements) {
return requirements.filterRequirements(SUPPORTED_REQUIREMENTS);
}
private static Job buildJob( private static Job buildJob(
FirebaseJobDispatcher dispatcher, FirebaseJobDispatcher dispatcher,
Requirements requirements, Requirements requirements,
String tag, String tag,
String servicePackage, String servicePackage,
String serviceAction) { String serviceAction) {
Requirements filteredRequirements = requirements.filterRequirements(SUPPORTED_REQUIREMENTS);
if (!filteredRequirements.equals(requirements)) {
Log.w(
TAG,
"Ignoring unsupported requirements: "
+ (filteredRequirements.getRequirements() ^ requirements.getRequirements()));
}
Job.Builder builder = Job.Builder builder =
dispatcher dispatcher
.newJobBuilder() .newJobBuilder()
.setService(JobDispatcherSchedulerService.class) // the JobService that will be called .setService(JobDispatcherSchedulerService.class) // the JobService that will be called
.setTag(tag); .setTag(tag);
if (requirements.isUnmeteredNetworkRequired()) { if (requirements.isUnmeteredNetworkRequired()) {
builder.addConstraint(Constraint.ON_UNMETERED_NETWORK); builder.addConstraint(Constraint.ON_UNMETERED_NETWORK);
} else if (requirements.isNetworkRequired()) { } else if (requirements.isNetworkRequired()) {
builder.addConstraint(Constraint.ON_ANY_NETWORK); builder.addConstraint(Constraint.ON_ANY_NETWORK);
} }
if (requirements.isIdleRequired()) { if (requirements.isIdleRequired()) {
builder.addConstraint(Constraint.DEVICE_IDLE); builder.addConstraint(Constraint.DEVICE_IDLE);
} }

View File

@ -40,6 +40,12 @@ public final class WorkManagerScheduler implements Scheduler {
private static final String KEY_SERVICE_ACTION = "service_action"; private static final String KEY_SERVICE_ACTION = "service_action";
private static final String KEY_SERVICE_PACKAGE = "service_package"; private static final String KEY_SERVICE_PACKAGE = "service_package";
private static final String KEY_REQUIREMENTS = "requirements"; private static final String KEY_REQUIREMENTS = "requirements";
private static final int SUPPORTED_REQUIREMENTS =
Requirements.NETWORK
| Requirements.NETWORK_UNMETERED
| (Util.SDK_INT >= 23 ? Requirements.DEVICE_IDLE : 0)
| Requirements.DEVICE_CHARGING
| Requirements.DEVICE_STORAGE_NOT_LOW;
private final String workName; private final String workName;
@ -70,9 +76,21 @@ public final class WorkManagerScheduler implements Scheduler {
return true; return true;
} }
private static Constraints buildConstraints(Requirements requirements) { @Override
Constraints.Builder builder = new Constraints.Builder(); public Requirements getSupportedRequirements(Requirements requirements) {
return requirements.filterRequirements(SUPPORTED_REQUIREMENTS);
}
private static Constraints buildConstraints(Requirements requirements) {
Requirements filteredRequirements = requirements.filterRequirements(SUPPORTED_REQUIREMENTS);
if (!filteredRequirements.equals(requirements)) {
Log.w(
TAG,
"Ignoring unsupported requirements: "
+ (filteredRequirements.getRequirements() ^ requirements.getRequirements()));
}
Constraints.Builder builder = new Constraints.Builder();
if (requirements.isUnmeteredNetworkRequired()) { if (requirements.isUnmeteredNetworkRequired()) {
builder.setRequiredNetworkType(NetworkType.UNMETERED); builder.setRequiredNetworkType(NetworkType.UNMETERED);
} else if (requirements.isNetworkRequired()) { } else if (requirements.isNetworkRequired()) {
@ -80,13 +98,14 @@ public final class WorkManagerScheduler implements Scheduler {
} else { } else {
builder.setRequiredNetworkType(NetworkType.NOT_REQUIRED); builder.setRequiredNetworkType(NetworkType.NOT_REQUIRED);
} }
if (Util.SDK_INT >= 23 && requirements.isIdleRequired()) {
setRequiresDeviceIdle(builder);
}
if (requirements.isChargingRequired()) { if (requirements.isChargingRequired()) {
builder.setRequiresCharging(true); builder.setRequiresCharging(true);
} }
if (requirements.isStorageNotLowRequired()) {
if (requirements.isIdleRequired() && Util.SDK_INT >= 23) { builder.setRequiresStorageNotLow(true);
setRequiresDeviceIdle(builder);
} }
return builder.build(); return builder.build();

View File

@ -658,6 +658,22 @@ public abstract class DownloadService extends Service {
if (requirements == null) { if (requirements == null) {
Log.e(TAG, "Ignored SET_REQUIREMENTS: Missing " + KEY_REQUIREMENTS + " extra"); Log.e(TAG, "Ignored SET_REQUIREMENTS: Missing " + KEY_REQUIREMENTS + " extra");
} else { } else {
@Nullable Scheduler scheduler = getScheduler();
if (scheduler != null) {
Requirements supportedRequirements = scheduler.getSupportedRequirements(requirements);
if (!supportedRequirements.equals(requirements)) {
Log.w(
TAG,
"Ignoring requirements not supported by the Scheduler: "
+ (requirements.getRequirements() ^ supportedRequirements.getRequirements()));
// We need to make sure DownloadManager only uses requirements supported by the
// Scheduler. If we don't do this, DownloadManager can report itself as idle due to an
// unmet requirement that the Scheduler doesn't support. This can then lead to the
// service being destroyed, even though the Scheduler won't be able to restart it when
// the requirement is subsequently met.
requirements = supportedRequirements;
}
}
downloadManager.setRequirements(requirements); downloadManager.setRequirements(requirements);
} }
break; break;

View File

@ -50,6 +50,12 @@ public final class PlatformScheduler implements Scheduler {
private static final String KEY_SERVICE_ACTION = "service_action"; private static final String KEY_SERVICE_ACTION = "service_action";
private static final String KEY_SERVICE_PACKAGE = "service_package"; private static final String KEY_SERVICE_PACKAGE = "service_package";
private static final String KEY_REQUIREMENTS = "requirements"; private static final String KEY_REQUIREMENTS = "requirements";
private static final int SUPPORTED_REQUIREMENTS =
Requirements.NETWORK
| Requirements.NETWORK_UNMETERED
| Requirements.DEVICE_IDLE
| Requirements.DEVICE_CHARGING
| (Util.SDK_INT >= 26 ? Requirements.DEVICE_STORAGE_NOT_LOW : 0);
private final int jobId; private final int jobId;
private final ComponentName jobServiceComponentName; private final ComponentName jobServiceComponentName;
@ -86,6 +92,11 @@ public final class PlatformScheduler implements Scheduler {
return true; return true;
} }
@Override
public Requirements getSupportedRequirements(Requirements requirements) {
return requirements.filterRequirements(SUPPORTED_REQUIREMENTS);
}
// @RequiresPermission constructor annotation should ensure the permission is present. // @RequiresPermission constructor annotation should ensure the permission is present.
@SuppressWarnings("MissingPermission") @SuppressWarnings("MissingPermission")
private static JobInfo buildJobInfo( private static JobInfo buildJobInfo(
@ -94,8 +105,15 @@ public final class PlatformScheduler implements Scheduler {
Requirements requirements, Requirements requirements,
String serviceAction, String serviceAction,
String servicePackage) { String servicePackage) {
JobInfo.Builder builder = new JobInfo.Builder(jobId, jobServiceComponentName); Requirements filteredRequirements = requirements.filterRequirements(SUPPORTED_REQUIREMENTS);
if (!filteredRequirements.equals(requirements)) {
Log.w(
TAG,
"Ignoring unsupported requirements: "
+ (filteredRequirements.getRequirements() ^ requirements.getRequirements()));
}
JobInfo.Builder builder = new JobInfo.Builder(jobId, jobServiceComponentName);
if (requirements.isUnmeteredNetworkRequired()) { if (requirements.isUnmeteredNetworkRequired()) {
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED); builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
} else if (requirements.isNetworkRequired()) { } else if (requirements.isNetworkRequired()) {
@ -103,6 +121,9 @@ public final class PlatformScheduler implements Scheduler {
} }
builder.setRequiresDeviceIdle(requirements.isIdleRequired()); builder.setRequiresDeviceIdle(requirements.isIdleRequired());
builder.setRequiresCharging(requirements.isChargingRequired()); builder.setRequiresCharging(requirements.isChargingRequired());
if (Util.SDK_INT >= 26 && requirements.isStorageNotLowRequired()) {
builder.setRequiresStorageNotLow(true);
}
builder.setPersisted(true); builder.setPersisted(true);
PersistableBundle extras = new PersistableBundle(); PersistableBundle extras = new PersistableBundle();

View File

@ -39,13 +39,13 @@ public final class Requirements implements Parcelable {
/** /**
* Requirement flags. Possible flag values are {@link #NETWORK}, {@link #NETWORK_UNMETERED}, * Requirement flags. Possible flag values are {@link #NETWORK}, {@link #NETWORK_UNMETERED},
* {@link #DEVICE_IDLE} and {@link #DEVICE_CHARGING}. * {@link #DEVICE_IDLE}, {@link #DEVICE_CHARGING} and {@link #DEVICE_STORAGE_NOT_LOW}.
*/ */
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@IntDef( @IntDef(
flag = true, flag = true,
value = {NETWORK, NETWORK_UNMETERED, DEVICE_IDLE, DEVICE_CHARGING}) value = {NETWORK, NETWORK_UNMETERED, DEVICE_IDLE, DEVICE_CHARGING, DEVICE_STORAGE_NOT_LOW})
public @interface RequirementFlags {} public @interface RequirementFlags {}
/** Requirement that the device has network connectivity. */ /** Requirement that the device has network connectivity. */
@ -56,6 +56,11 @@ public final class Requirements implements Parcelable {
public static final int DEVICE_IDLE = 1 << 2; public static final int DEVICE_IDLE = 1 << 2;
/** Requirement that the device is charging. */ /** Requirement that the device is charging. */
public static final int DEVICE_CHARGING = 1 << 3; public static final int DEVICE_CHARGING = 1 << 3;
/**
* Requirement that the device's <em>internal</em> storage is not low. Note that this requirement
* is not affected by the status of external storage.
*/
public static final int DEVICE_STORAGE_NOT_LOW = 1 << 4;
@RequirementFlags private final int requirements; @RequirementFlags private final int requirements;
@ -74,6 +79,18 @@ public final class Requirements implements Parcelable {
return requirements; return requirements;
} }
/**
* Filters the requirements, returning the subset that are enabled by the provided filter.
*
* @param requirementsFilter The enabled {@link RequirementFlags}.
* @return The filtered requirements. If the filter does not cause a change in the requirements
* then this instance will be returned.
*/
public Requirements filterRequirements(int requirementsFilter) {
int filteredRequirements = requirements & requirementsFilter;
return filteredRequirements == requirements ? this : new Requirements(filteredRequirements);
}
/** Returns whether network connectivity is required. */ /** Returns whether network connectivity is required. */
public boolean isNetworkRequired() { public boolean isNetworkRequired() {
return (requirements & NETWORK) != 0; return (requirements & NETWORK) != 0;
@ -94,6 +111,11 @@ public final class Requirements implements Parcelable {
return (requirements & DEVICE_IDLE) != 0; return (requirements & DEVICE_IDLE) != 0;
} }
/** Returns whether the device is required to not be low on <em>internal</em> storage. */
public boolean isStorageNotLowRequired() {
return (requirements & DEVICE_STORAGE_NOT_LOW) != 0;
}
/** /**
* Returns whether the requirements are met. * Returns whether the requirements are met.
* *
@ -119,6 +141,9 @@ public final class Requirements implements Parcelable {
if (isIdleRequired() && !isDeviceIdle(context)) { if (isIdleRequired() && !isDeviceIdle(context)) {
notMetRequirements |= DEVICE_IDLE; notMetRequirements |= DEVICE_IDLE;
} }
if (isStorageNotLowRequired() && !isStorageNotLow(context)) {
notMetRequirements |= DEVICE_STORAGE_NOT_LOW;
}
return notMetRequirements; return notMetRequirements;
} }
@ -145,8 +170,10 @@ public final class Requirements implements Parcelable {
} }
private boolean isDeviceCharging(Context context) { private boolean isDeviceCharging(Context context) {
@Nullable
Intent batteryStatus = Intent batteryStatus =
context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); context.registerReceiver(
/* receiver= */ null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
if (batteryStatus == null) { if (batteryStatus == null) {
return false; return false;
} }
@ -162,6 +189,12 @@ public final class Requirements implements Parcelable {
: Util.SDK_INT >= 20 ? !powerManager.isInteractive() : !powerManager.isScreenOn(); : Util.SDK_INT >= 20 ? !powerManager.isInteractive() : !powerManager.isScreenOn();
} }
private boolean isStorageNotLow(Context context) {
return context.registerReceiver(
/* receiver= */ null, new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW))
== null;
}
private static boolean isInternetConnectivityValidated(ConnectivityManager connectivityManager) { private static boolean isInternetConnectivityValidated(ConnectivityManager connectivityManager) {
// It's possible to query NetworkCapabilities from API level 23, but RequirementsWatcher only // It's possible to query NetworkCapabilities from API level 23, but RequirementsWatcher only
// fires an event to update its Requirements when NetworkCapabilities change from API level 24. // fires an event to update its Requirements when NetworkCapabilities change from API level 24.

View File

@ -104,6 +104,10 @@ public final class RequirementsWatcher {
filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_SCREEN_OFF);
} }
} }
if (requirements.isStorageNotLowRequired()) {
filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
}
receiver = new DeviceStatusChangeReceiver(); receiver = new DeviceStatusChangeReceiver();
context.registerReceiver(receiver, filter, null, handler); context.registerReceiver(receiver, filter, null, handler);
return notMetRequirements; return notMetRequirements;

View File

@ -45,4 +45,14 @@ public interface Scheduler {
* @return Whether cancellation was successful. * @return Whether cancellation was successful.
*/ */
boolean cancel(); boolean cancel();
/**
* Checks whether this {@link Scheduler} supports the provided {@link Requirements}. If all of the
* requirements are supported then the same {@link Requirements} instance is returned. If not then
* a new instance is returned containing the subset of the requirements that are supported.
*
* @param requirements The requirements to check.
* @return The supported requirements.
*/
Requirements getSupportedRequirements(Requirements requirements);
} }