diff --git a/demos/main/build.gradle b/demos/main/build.gradle index b7a8666fe3..dd4fec385c 100644 --- a/demos/main/build.gradle +++ b/demos/main/build.gradle @@ -70,6 +70,7 @@ dependencies { implementation project(modulePrefix + 'library-hls') implementation project(modulePrefix + 'library-smoothstreaming') implementation project(modulePrefix + 'library-ui') + withExtensionsImplementation project(path: modulePrefix + 'extension-workmanager') withExtensionsImplementation project(path: modulePrefix + 'extension-av1') withExtensionsImplementation project(path: modulePrefix + 'extension-ffmpeg') withExtensionsImplementation project(path: modulePrefix + 'extension-flac') diff --git a/extensions/jobdispatcher/src/main/java/com/google/android/exoplayer2/ext/jobdispatcher/JobDispatcherScheduler.java b/extensions/jobdispatcher/src/main/java/com/google/android/exoplayer2/ext/jobdispatcher/JobDispatcherScheduler.java index 8841f8355f..9c72e4b86d 100644 --- a/extensions/jobdispatcher/src/main/java/com/google/android/exoplayer2/ext/jobdispatcher/JobDispatcherScheduler.java +++ b/extensions/jobdispatcher/src/main/java/com/google/android/exoplayer2/ext/jobdispatcher/JobDispatcherScheduler.java @@ -96,6 +96,19 @@ public final class JobDispatcherScheduler implements Scheduler { return result == FirebaseJobDispatcher.CANCEL_RESULT_SUCCESS; } + @Override + public Requirements getSupportedRequirements(Requirements requirements) { + Requirements supportedRequirements = requirements; + if (requirements.isStorageNotLowRequired()) { + Log.w(TAG, "Storage not low requirement not supported on the JobDispatcherScheduler " + + "Requirement removed."); + int newRequirements = + supportedRequirements.getRequirements() ^ Requirements.DEVICE_STORAGE_NOT_LOW; + supportedRequirements = new Requirements(newRequirements); + } + return supportedRequirements; + } + private static Job buildJob( FirebaseJobDispatcher dispatcher, Requirements requirements, diff --git a/extensions/workmanager/src/main/java/com/google/android/exoplayer2/ext/workmanager/WorkManagerScheduler.java b/extensions/workmanager/src/main/java/com/google/android/exoplayer2/ext/workmanager/WorkManagerScheduler.java index 97b132980d..9ec09c6c92 100644 --- a/extensions/workmanager/src/main/java/com/google/android/exoplayer2/ext/workmanager/WorkManagerScheduler.java +++ b/extensions/workmanager/src/main/java/com/google/android/exoplayer2/ext/workmanager/WorkManagerScheduler.java @@ -87,6 +87,12 @@ public final class WorkManagerScheduler implements Scheduler { if (requirements.isIdleRequired() && Util.SDK_INT >= 23) { setRequiresDeviceIdle(builder); + } else if (requirements.isIdleRequired()) { + Log.w(TAG, "Is idle requirement is only available on API 23 and up."); + } + + if (requirements.isStorageNotLowRequired()) { + builder.setRequiresStorageNotLow(true); } return builder.build(); @@ -108,6 +114,19 @@ public final class WorkManagerScheduler implements Scheduler { return builder.build(); } + @Override + public Requirements getSupportedRequirements(Requirements requirements) { + Requirements supportedRequirements = requirements; + if (requirements.isIdleRequired() && Util.SDK_INT < 23) { + Log.w(TAG, "Is idle requirement not supported on the WorkManagerScheduler on API below 23. " + + "Requirement removed."); + int newRequirements = + supportedRequirements.getRequirements() ^ Requirements.DEVICE_IDLE; + supportedRequirements = new Requirements(newRequirements); + } + return supportedRequirements; + } + private static OneTimeWorkRequest buildWorkRequest(Constraints constraints, Data inputData) { OneTimeWorkRequest.Builder builder = new OneTimeWorkRequest.Builder(SchedulerWorker.class); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java index 0ee9a83260..6f3a9fd4ca 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java @@ -658,6 +658,10 @@ 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 = scheduler.getSupportedRequirements(requirements); + } downloadManager.setRequirements(requirements); } break; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/scheduler/PlatformScheduler.java b/library/core/src/main/java/com/google/android/exoplayer2/scheduler/PlatformScheduler.java index c4861abdf3..e754c75d74 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/scheduler/PlatformScheduler.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/scheduler/PlatformScheduler.java @@ -86,6 +86,21 @@ public final class PlatformScheduler implements Scheduler { return true; } + @Override + public Requirements getSupportedRequirements(Requirements requirements) { + Requirements supportedRequirements = requirements; + if (Util.SDK_INT < 26) { + if (requirements.isStorageNotLowRequired()) { + Log.w(TAG, "Storage not low requirement not supported on the PlatformScheduler" + + "on API below 26. Requirement removed."); + int newRequirements = + supportedRequirements.getRequirements() ^ Requirements.DEVICE_STORAGE_NOT_LOW; + supportedRequirements = new Requirements(newRequirements); + } + } + return supportedRequirements; + } + // @RequiresPermission constructor annotation should ensure the permission is present. @SuppressWarnings("MissingPermission") private static JobInfo buildJobInfo( diff --git a/library/core/src/main/java/com/google/android/exoplayer2/scheduler/Requirements.java b/library/core/src/main/java/com/google/android/exoplayer2/scheduler/Requirements.java index 8919a26720..ab364a53b8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/scheduler/Requirements.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/scheduler/Requirements.java @@ -45,7 +45,7 @@ public final class Requirements implements Parcelable { @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,8 @@ 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 storage is not low. */ + public static final int DEVICE_STORAGE_NOT_LOW = 1 << 4; @RequirementFlags private final int requirements; @@ -94,6 +96,10 @@ public final class Requirements implements Parcelable { return (requirements & DEVICE_IDLE) != 0; } + public boolean isStorageNotLowRequired() { + return (requirements & DEVICE_STORAGE_NOT_LOW) != 0; + } + /** * Returns whether the requirements are met. * @@ -119,6 +125,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; } @@ -162,6 +171,34 @@ public final class Requirements implements Parcelable { : Util.SDK_INT >= 20 ? !powerManager.isInteractive() : !powerManager.isScreenOn(); } + /** + * Implementation taken from the the WorkManager source. + * @see StorageNotLowTracker + */ + private boolean isStorageNotLow(Context context) { + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); + intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW); + Intent intent = context.registerReceiver(null, intentFilter); + if (intent == null || intent.getAction() == null) { + // ACTION_DEVICE_STORAGE_LOW is a sticky broadcast that is removed when sufficient + // storage is available again. ACTION_DEVICE_STORAGE_OK is not sticky. So if we + // don't receive anything here, we can assume that the storage state is okay. + return true; + } else { + switch (intent.getAction()) { + case Intent.ACTION_DEVICE_STORAGE_OK: + return true; + case Intent.ACTION_DEVICE_STORAGE_LOW: + return false; + default: + // This should never happen because the intent filter is configured + // correctly. + return true; + } + } + } + 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. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/scheduler/RequirementsWatcher.java b/library/core/src/main/java/com/google/android/exoplayer2/scheduler/RequirementsWatcher.java index 797b7f7170..9109242db1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/scheduler/RequirementsWatcher.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/scheduler/RequirementsWatcher.java @@ -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; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/scheduler/Scheduler.java b/library/core/src/main/java/com/google/android/exoplayer2/scheduler/Scheduler.java index b5a6f40424..bcbd7420b0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/scheduler/Scheduler.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/scheduler/Scheduler.java @@ -45,4 +45,13 @@ public interface Scheduler { * @return Whether cancellation was successful. */ boolean cancel(); + + /** + * Checks if this {@link Scheduler} supports the provided {@link Requirements}. If all + * requirements are supported the same object is returned. If not all requirements are + * supported a new {@code Requirements} object is returned containing the supported requirements. + * @param requirements The requirements to check. + * @return The requirements supported by this scheduler. + */ + Requirements getSupportedRequirements(Requirements requirements); }