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.
* Update `CachedContentIndex` to use `SecureRandom` for generating the
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:
* Add a sample count parameter to `MediaCodecRenderer.processOutputBuffer`
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_PACKAGE = "service_package";
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 FirebaseJobDispatcher jobDispatcher;
@ -96,24 +101,35 @@ public final class JobDispatcherScheduler implements Scheduler {
return result == FirebaseJobDispatcher.CANCEL_RESULT_SUCCESS;
}
@Override
public Requirements getSupportedRequirements(Requirements requirements) {
return requirements.filterRequirements(SUPPORTED_REQUIREMENTS);
}
private static Job buildJob(
FirebaseJobDispatcher dispatcher,
Requirements requirements,
String tag,
String servicePackage,
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 =
dispatcher
.newJobBuilder()
.setService(JobDispatcherSchedulerService.class) // the JobService that will be called
.setTag(tag);
if (requirements.isUnmeteredNetworkRequired()) {
builder.addConstraint(Constraint.ON_UNMETERED_NETWORK);
} else if (requirements.isNetworkRequired()) {
builder.addConstraint(Constraint.ON_ANY_NETWORK);
}
if (requirements.isIdleRequired()) {
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_PACKAGE = "service_package";
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;
@ -70,9 +76,21 @@ public final class WorkManagerScheduler implements Scheduler {
return true;
}
private static Constraints buildConstraints(Requirements requirements) {
Constraints.Builder builder = new Constraints.Builder();
@Override
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()) {
builder.setRequiredNetworkType(NetworkType.UNMETERED);
} else if (requirements.isNetworkRequired()) {
@ -80,13 +98,14 @@ public final class WorkManagerScheduler implements Scheduler {
} else {
builder.setRequiredNetworkType(NetworkType.NOT_REQUIRED);
}
if (Util.SDK_INT >= 23 && requirements.isIdleRequired()) {
setRequiresDeviceIdle(builder);
}
if (requirements.isChargingRequired()) {
builder.setRequiresCharging(true);
}
if (requirements.isIdleRequired() && Util.SDK_INT >= 23) {
setRequiresDeviceIdle(builder);
if (requirements.isStorageNotLowRequired()) {
builder.setRequiresStorageNotLow(true);
}
return builder.build();

View File

@ -658,6 +658,22 @@ public abstract class DownloadService extends Service {
if (requirements == null) {
Log.e(TAG, "Ignored SET_REQUIREMENTS: Missing " + KEY_REQUIREMENTS + " extra");
} 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);
}
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_PACKAGE = "service_package";
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 ComponentName jobServiceComponentName;
@ -86,6 +92,11 @@ public final class PlatformScheduler implements Scheduler {
return true;
}
@Override
public Requirements getSupportedRequirements(Requirements requirements) {
return requirements.filterRequirements(SUPPORTED_REQUIREMENTS);
}
// @RequiresPermission constructor annotation should ensure the permission is present.
@SuppressWarnings("MissingPermission")
private static JobInfo buildJobInfo(
@ -94,8 +105,15 @@ public final class PlatformScheduler implements Scheduler {
Requirements requirements,
String serviceAction,
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()) {
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
} else if (requirements.isNetworkRequired()) {
@ -103,6 +121,9 @@ public final class PlatformScheduler implements Scheduler {
}
builder.setRequiresDeviceIdle(requirements.isIdleRequired());
builder.setRequiresCharging(requirements.isChargingRequired());
if (Util.SDK_INT >= 26 && requirements.isStorageNotLowRequired()) {
builder.setRequiresStorageNotLow(true);
}
builder.setPersisted(true);
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},
* {@link #DEVICE_IDLE} and {@link #DEVICE_CHARGING}.
* {@link #DEVICE_IDLE}, {@link #DEVICE_CHARGING} and {@link #DEVICE_STORAGE_NOT_LOW}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef(
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 {}
/** 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;
/** Requirement that the device is charging. */
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;
@ -74,6 +79,18 @@ public final class Requirements implements Parcelable {
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. */
public boolean isNetworkRequired() {
return (requirements & NETWORK) != 0;
@ -94,6 +111,11 @@ public final class Requirements implements Parcelable {
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.
*
@ -119,6 +141,9 @@ public final class Requirements implements Parcelable {
if (isIdleRequired() && !isDeviceIdle(context)) {
notMetRequirements |= DEVICE_IDLE;
}
if (isStorageNotLowRequired() && !isStorageNotLow(context)) {
notMetRequirements |= DEVICE_STORAGE_NOT_LOW;
}
return notMetRequirements;
}
@ -145,8 +170,10 @@ public final class Requirements implements Parcelable {
}
private boolean isDeviceCharging(Context context) {
@Nullable
Intent batteryStatus =
context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
context.registerReceiver(
/* receiver= */ null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
if (batteryStatus == null) {
return false;
}
@ -162,6 +189,12 @@ public final class Requirements implements Parcelable {
: 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) {
// 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.

View File

@ -104,6 +104,10 @@ public final class RequirementsWatcher {
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();
context.registerReceiver(receiver, filter, null, handler);
return notMetRequirements;

View File

@ -45,4 +45,14 @@ public interface Scheduler {
* @return Whether cancellation was successful.
*/
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);
}