Commit ec77f737 by eguven Committed by Oliver Woodman

Make DownloadManager watch requirements directly

PiperOrigin-RevId: 229544734
parent 8adc16a6
...@@ -101,10 +101,12 @@ public class DemoApplication extends Application { ...@@ -101,10 +101,12 @@ public class DemoApplication extends Application {
new DownloaderConstructorHelper(getDownloadCache(), buildHttpDataSourceFactory()); new DownloaderConstructorHelper(getDownloadCache(), buildHttpDataSourceFactory());
downloadManager = downloadManager =
new DownloadManager( new DownloadManager(
this,
new File(getDownloadDirectory(), DOWNLOAD_ACTION_FILE), new File(getDownloadDirectory(), DOWNLOAD_ACTION_FILE),
new DefaultDownloaderFactory(downloaderConstructorHelper), new DefaultDownloaderFactory(downloaderConstructorHelper),
MAX_SIMULTANEOUS_DOWNLOADS, MAX_SIMULTANEOUS_DOWNLOADS,
DownloadManager.DEFAULT_MIN_RETRY_COUNT); DownloadManager.DEFAULT_MIN_RETRY_COUNT,
DownloadManager.DEFAULT_REQUIREMENTS);
downloadTracker = downloadTracker =
new DownloadTracker( new DownloadTracker(
/* context= */ this, /* context= */ this,
......
...@@ -41,6 +41,7 @@ import com.google.android.exoplayer2.offline.DownloadService; ...@@ -41,6 +41,7 @@ import com.google.android.exoplayer2.offline.DownloadService;
import com.google.android.exoplayer2.offline.DownloadState; import com.google.android.exoplayer2.offline.DownloadState;
import com.google.android.exoplayer2.offline.ProgressiveDownloadHelper; import com.google.android.exoplayer2.offline.ProgressiveDownloadHelper;
import com.google.android.exoplayer2.offline.StreamKey; import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.scheduler.Requirements;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.dash.offline.DashDownloadHelper; import com.google.android.exoplayer2.source.dash.offline.DashDownloadHelper;
import com.google.android.exoplayer2.source.hls.offline.HlsDownloadHelper; import com.google.android.exoplayer2.source.hls.offline.HlsDownloadHelper;
...@@ -159,6 +160,14 @@ public class DownloadTracker implements DownloadManager.Listener { ...@@ -159,6 +160,14 @@ public class DownloadTracker implements DownloadManager.Listener {
// Do nothing. // Do nothing.
} }
@Override
public void onRequirementsStateChanged(
DownloadManager downloadManager,
Requirements requirements,
@Requirements.RequirementFlags int notMetRequirements) {
// Do nothing.
}
// Internal methods // Internal methods
private void loadTrackedActions() { private void loadTrackedActions() {
......
...@@ -28,12 +28,15 @@ import static com.google.android.exoplayer2.offline.DownloadState.STATE_STOPPED; ...@@ -28,12 +28,15 @@ import static com.google.android.exoplayer2.offline.DownloadState.STATE_STOPPED;
import static com.google.android.exoplayer2.offline.DownloadState.STOP_FLAG_DOWNLOAD_MANAGER_NOT_READY; import static com.google.android.exoplayer2.offline.DownloadState.STOP_FLAG_DOWNLOAD_MANAGER_NOT_READY;
import static com.google.android.exoplayer2.offline.DownloadState.STOP_FLAG_STOPPED; import static com.google.android.exoplayer2.offline.DownloadState.STOP_FLAG_STOPPED;
import android.content.Context;
import android.os.ConditionVariable; import android.os.ConditionVariable;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerThread; import android.os.HandlerThread;
import android.os.Looper; import android.os.Looper;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.scheduler.Requirements;
import com.google.android.exoplayer2.scheduler.RequirementsWatcher;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import java.io.File; import java.io.File;
...@@ -74,18 +77,35 @@ public final class DownloadManager { ...@@ -74,18 +77,35 @@ public final class DownloadManager {
* @param downloadManager The reporting instance. * @param downloadManager The reporting instance.
*/ */
void onIdle(DownloadManager downloadManager); void onIdle(DownloadManager downloadManager);
/**
* Called when the download requirements state changed.
*
* @param downloadManager The reporting instance.
* @param requirements Requirements needed to be met to start downloads.
* @param notMetRequirements {@link Requirements.RequirementFlags RequirementFlags} that are not
* met, or 0.
*/
void onRequirementsStateChanged(
DownloadManager downloadManager,
Requirements requirements,
@Requirements.RequirementFlags int notMetRequirements);
} }
/** The default maximum number of simultaneous downloads. */ /** The default maximum number of simultaneous downloads. */
public static final int DEFAULT_MAX_SIMULTANEOUS_DOWNLOADS = 1; public static final int DEFAULT_MAX_SIMULTANEOUS_DOWNLOADS = 1;
/** The default minimum number of times a download must be retried before failing. */ /** The default minimum number of times a download must be retried before failing. */
public static final int DEFAULT_MIN_RETRY_COUNT = 5; public static final int DEFAULT_MIN_RETRY_COUNT = 5;
/** The default requirement is that the device has network connectivity. */
public static final Requirements DEFAULT_REQUIREMENTS =
new Requirements(Requirements.NETWORK_TYPE_ANY, false, false);
private static final String TAG = "DownloadManager"; private static final String TAG = "DownloadManager";
private static final boolean DEBUG = false; private static final boolean DEBUG = false;
private final int maxActiveDownloads; private final int maxActiveDownloads;
private final int minRetryCount; private final int minRetryCount;
private final Context context;
private final ActionFile actionFile; private final ActionFile actionFile;
private final DownloaderFactory downloaderFactory; private final DownloaderFactory downloaderFactory;
private final ArrayList<Download> downloads; private final ArrayList<Download> downloads;
...@@ -99,31 +119,43 @@ public final class DownloadManager { ...@@ -99,31 +119,43 @@ public final class DownloadManager {
private boolean initialized; private boolean initialized;
private boolean released; private boolean released;
@DownloadState.StopFlags private int stickyStopFlags; @DownloadState.StopFlags private int stickyStopFlags;
private RequirementsWatcher requirementsWatcher;
/** /**
* Constructs a {@link DownloadManager}. * Constructs a {@link DownloadManager}.
* *
* @param context Any context.
* @param actionFile The file in which active actions are saved. * @param actionFile The file in which active actions are saved.
* @param downloaderFactory A factory for creating {@link Downloader}s. * @param downloaderFactory A factory for creating {@link Downloader}s.
*/ */
public DownloadManager(File actionFile, DownloaderFactory downloaderFactory) { public DownloadManager(Context context, File actionFile, DownloaderFactory downloaderFactory) {
this( this(
actionFile, downloaderFactory, DEFAULT_MAX_SIMULTANEOUS_DOWNLOADS, DEFAULT_MIN_RETRY_COUNT); context,
actionFile,
downloaderFactory,
DEFAULT_MAX_SIMULTANEOUS_DOWNLOADS,
DEFAULT_MIN_RETRY_COUNT,
DEFAULT_REQUIREMENTS);
} }
/** /**
* Constructs a {@link DownloadManager}. * Constructs a {@link DownloadManager}.
* *
* @param context Any context.
* @param actionFile The file in which active actions are saved. * @param actionFile The file in which active actions are saved.
* @param downloaderFactory A factory for creating {@link Downloader}s. * @param downloaderFactory A factory for creating {@link Downloader}s.
* @param maxSimultaneousDownloads The maximum number of simultaneous downloads. * @param maxSimultaneousDownloads The maximum number of simultaneous downloads.
* @param minRetryCount The minimum number of times a download must be retried before failing. * @param minRetryCount The minimum number of times a download must be retried before failing.
* @param requirements The requirements needed to be met to start downloads.
*/ */
public DownloadManager( public DownloadManager(
Context context,
File actionFile, File actionFile,
DownloaderFactory downloaderFactory, DownloaderFactory downloaderFactory,
int maxSimultaneousDownloads, int maxSimultaneousDownloads,
int minRetryCount) { int minRetryCount,
Requirements requirements) {
this.context = context.getApplicationContext();
this.actionFile = new ActionFile(actionFile); this.actionFile = new ActionFile(actionFile);
this.downloaderFactory = downloaderFactory; this.downloaderFactory = downloaderFactory;
this.maxActiveDownloads = maxSimultaneousDownloads; this.maxActiveDownloads = maxSimultaneousDownloads;
...@@ -146,11 +178,31 @@ public final class DownloadManager { ...@@ -146,11 +178,31 @@ public final class DownloadManager {
listeners = new CopyOnWriteArraySet<>(); listeners = new CopyOnWriteArraySet<>();
actionQueue = new ArrayDeque<>(); actionQueue = new ArrayDeque<>();
watchRequirements(requirements);
loadActions(); loadActions();
logd("Created"); logd("Created");
} }
/** /**
* Sets the requirements needed to be met to start downloads.
*
* @param requirements Need to be met to start downloads.
*/
public void setRequirements(Requirements requirements) {
Assertions.checkState(!released);
if (requirements.equals(requirementsWatcher.getRequirements())) {
return;
}
requirementsWatcher.stop();
notifyListenersRequirementsStateChange(watchRequirements(requirements));
}
/** Returns the requirements needed to be met to start downloads. */
public Requirements getRequirements() {
return requirementsWatcher.getRequirements();
}
/**
* Adds a {@link Listener}. * Adds a {@link Listener}.
* *
* @param listener The listener to be added. * @param listener The listener to be added.
...@@ -278,6 +330,9 @@ public final class DownloadManager { ...@@ -278,6 +330,9 @@ public final class DownloadManager {
} }
setStopFlags(STOP_FLAG_DOWNLOAD_MANAGER_NOT_READY); setStopFlags(STOP_FLAG_DOWNLOAD_MANAGER_NOT_READY);
released = true; released = true;
if (requirementsWatcher != null) {
requirementsWatcher.stop();
}
final ConditionVariable fileIOFinishedCondition = new ConditionVariable(); final ConditionVariable fileIOFinishedCondition = new ConditionVariable();
fileIOHandler.post(fileIOFinishedCondition::open); fileIOHandler.post(fileIOFinishedCondition::open);
fileIOFinishedCondition.block(); fileIOFinishedCondition.block();
...@@ -346,6 +401,15 @@ public final class DownloadManager { ...@@ -346,6 +401,15 @@ public final class DownloadManager {
} }
} }
private void notifyListenersRequirementsStateChange(
@Requirements.RequirementFlags int notMetRequirements) {
logdFlags("Not met requirements are changed", notMetRequirements);
for (Listener listener : listeners) {
listener.onRequirementsStateChanged(
DownloadManager.this, requirementsWatcher.getRequirements(), notMetRequirements);
}
}
private void loadActions() { private void loadActions() {
fileIOHandler.post( fileIOHandler.post(
() -> { () -> {
...@@ -420,6 +484,18 @@ public final class DownloadManager { ...@@ -420,6 +484,18 @@ public final class DownloadManager {
} }
} }
@Requirements.RequirementFlags
private int watchRequirements(Requirements requirements) {
requirementsWatcher = new RequirementsWatcher(context, new RequirementListener(), requirements);
@Requirements.RequirementFlags int notMetRequirements = requirementsWatcher.start();
if (notMetRequirements == 0) {
startDownloads();
} else {
stopDownloads();
}
return notMetRequirements;
}
private static final class Download { private static final class Download {
private final String id; private final String id;
...@@ -693,4 +769,20 @@ public final class DownloadManager { ...@@ -693,4 +769,20 @@ public final class DownloadManager {
return Math.min((errorCount - 1) * 1000, 5000); return Math.min((errorCount - 1) * 1000, 5000);
} }
} }
private class RequirementListener implements RequirementsWatcher.Listener {
@Override
public void requirementsMet(RequirementsWatcher requirementsWatcher) {
startDownloads();
notifyListenersRequirementsStateChange(0);
}
@Override
public void requirementsNotMet(
RequirementsWatcher requirementsWatcher,
@Requirements.RequirementFlags int notMetRequirements) {
stopDownloads();
notifyListenersRequirementsStateChange(notMetRequirements);
}
}
} }
...@@ -25,8 +25,8 @@ import android.os.Looper; ...@@ -25,8 +25,8 @@ import android.os.Looper;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.StringRes; import android.support.annotation.StringRes;
import com.google.android.exoplayer2.scheduler.Requirements; import com.google.android.exoplayer2.scheduler.Requirements;
import com.google.android.exoplayer2.scheduler.RequirementsWatcher;
import com.google.android.exoplayer2.scheduler.Scheduler; import com.google.android.exoplayer2.scheduler.Scheduler;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.NotificationUtil; import com.google.android.exoplayer2.util.NotificationUtil;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
...@@ -43,10 +43,6 @@ public abstract class DownloadService extends Service { ...@@ -43,10 +43,6 @@ public abstract class DownloadService extends Service {
/** Starts a download service, adding a new {@link DownloadAction} to be executed. */ /** Starts a download service, adding a new {@link DownloadAction} to be executed. */
public static final String ACTION_ADD = "com.google.android.exoplayer.downloadService.action.ADD"; public static final String ACTION_ADD = "com.google.android.exoplayer.downloadService.action.ADD";
/** Reloads the download requirements. */
public static final String ACTION_RELOAD_REQUIREMENTS =
"com.google.android.exoplayer.downloadService.action.RELOAD_REQUIREMENTS";
/** Like {@link #ACTION_INIT}, but with {@link #KEY_FOREGROUND} implicitly set to true. */ /** Like {@link #ACTION_INIT}, but with {@link #KEY_FOREGROUND} implicitly set to true. */
private static final String ACTION_RESTART = private static final String ACTION_RESTART =
"com.google.android.exoplayer.downloadService.action.RESTART"; "com.google.android.exoplayer.downloadService.action.RESTART";
...@@ -70,20 +66,16 @@ public abstract class DownloadService extends Service { ...@@ -70,20 +66,16 @@ public abstract class DownloadService extends Service {
private static final String TAG = "DownloadService"; private static final String TAG = "DownloadService";
private static final boolean DEBUG = false; private static final boolean DEBUG = false;
// Keep the requirements helper for each DownloadService as long as there are downloads (and the // Keep DownloadManagerListeners for each DownloadService as long as there are downloads (and the
// process is running). This allows downloads to resume when there's no scheduler. It may also // process is running). This allows DownloadService to restart when there's no scheduler.
// allow downloads the resume more quickly than when relying on the scheduler alone. private static final HashMap<Class<? extends DownloadService>, DownloadManagerHelper>
private static final HashMap<Class<? extends DownloadService>, RequirementsHelper> downloadManagerListeners = new HashMap<>();
requirementsHelpers = new HashMap<>();
private static final Requirements DEFAULT_REQUIREMENTS =
new Requirements(Requirements.NETWORK_TYPE_ANY, false, false);
private final @Nullable ForegroundNotificationUpdater foregroundNotificationUpdater; private final @Nullable ForegroundNotificationUpdater foregroundNotificationUpdater;
private final @Nullable String channelId; private final @Nullable String channelId;
private final @StringRes int channelName; private final @StringRes int channelName;
private DownloadManager downloadManager; private DownloadManager downloadManager;
private DownloadManagerListener downloadManagerListener;
private int lastStartId; private int lastStartId;
private boolean startedInForeground; private boolean startedInForeground;
private boolean taskRemoved; private boolean taskRemoved;
...@@ -227,9 +219,16 @@ public abstract class DownloadService extends Service { ...@@ -227,9 +219,16 @@ public abstract class DownloadService extends Service {
NotificationUtil.createNotificationChannel( NotificationUtil.createNotificationChannel(
this, channelId, channelName, NotificationUtil.IMPORTANCE_LOW); this, channelId, channelName, NotificationUtil.IMPORTANCE_LOW);
} }
downloadManager = getDownloadManager(); Class<? extends DownloadService> clazz = getClass();
downloadManagerListener = new DownloadManagerListener(); DownloadManagerHelper downloadManagerHelper = downloadManagerListeners.get(clazz);
downloadManager.addListener(downloadManagerListener); if (downloadManagerHelper == null) {
downloadManagerHelper =
new DownloadManagerHelper(
getApplicationContext(), getDownloadManager(), getScheduler(), clazz);
downloadManagerListeners.put(clazz, downloadManagerHelper);
}
downloadManager = downloadManagerHelper.downloadManager;
downloadManagerHelper.attachService(this);
} }
@Override @Override
...@@ -264,22 +263,11 @@ public abstract class DownloadService extends Service { ...@@ -264,22 +263,11 @@ public abstract class DownloadService extends Service {
} }
} }
break; break;
case ACTION_RELOAD_REQUIREMENTS:
stopWatchingRequirements();
break;
default: default:
Log.e(TAG, "Ignoring unrecognized action: " + intentAction); Log.e(TAG, "Ignoring unrecognized action: " + intentAction);
break; break;
} }
Requirements requirements = getRequirements();
if (requirements.checkRequirements(this)) {
downloadManager.startDownloads();
} else {
downloadManager.stopDownloads();
}
maybeStartWatchingRequirements(requirements);
if (downloadManager.isIdle()) { if (downloadManager.isIdle()) {
stop(); stop();
} }
...@@ -295,11 +283,12 @@ public abstract class DownloadService extends Service { ...@@ -295,11 +283,12 @@ public abstract class DownloadService extends Service {
@Override @Override
public void onDestroy() { public void onDestroy() {
logd("onDestroy"); logd("onDestroy");
DownloadManagerHelper downloadManagerHelper = downloadManagerListeners.get(getClass());
boolean unschedule = downloadManager.getDownloadCount() <= 0;
downloadManagerHelper.detachService(this, unschedule);
if (foregroundNotificationUpdater != null) { if (foregroundNotificationUpdater != null) {
foregroundNotificationUpdater.stopPeriodicUpdates(); foregroundNotificationUpdater.stopPeriodicUpdates();
} }
downloadManager.removeListener(downloadManagerListener);
maybeStopWatchingRequirements();
} }
/** DownloadService isn't designed to be bound. */ /** DownloadService isn't designed to be bound. */
...@@ -311,9 +300,7 @@ public abstract class DownloadService extends Service { ...@@ -311,9 +300,7 @@ public abstract class DownloadService extends Service {
/** /**
* Returns a {@link DownloadManager} to be used to downloaded content. Called only once in the * Returns a {@link DownloadManager} to be used to downloaded content. Called only once in the
* life cycle of the service. The service will call {@link DownloadManager#startDownloads()} and * life cycle of the process.
* {@link DownloadManager#stopDownloads} as necessary when requirements returned by {@link
* #getRequirements()} are met or stop being met.
*/ */
protected abstract DownloadManager getDownloadManager(); protected abstract DownloadManager getDownloadManager();
...@@ -325,14 +312,6 @@ public abstract class DownloadService extends Service { ...@@ -325,14 +312,6 @@ public abstract class DownloadService extends Service {
protected abstract @Nullable Scheduler getScheduler(); protected abstract @Nullable Scheduler getScheduler();
/** /**
* Returns requirements for downloads to take place. By default the only requirement is that the
* device has network connectivity.
*/
protected Requirements getRequirements() {
return DEFAULT_REQUIREMENTS;
}
/**
* Should be overridden in the subclass if the service will be run in the foreground. * Should be overridden in the subclass if the service will be run in the foreground.
* *
* <p>Returns a notification to be displayed when this service running in the foreground. * <p>Returns a notification to be displayed when this service running in the foreground.
...@@ -363,32 +342,16 @@ public abstract class DownloadService extends Service { ...@@ -363,32 +342,16 @@ public abstract class DownloadService extends Service {
// Do nothing. // Do nothing.
} }
private void maybeStartWatchingRequirements(Requirements requirements) { private void notifyDownloadStateChange(DownloadState downloadState) {
if (downloadManager.getDownloadCount() == 0) { onDownloadStateChanged(downloadState);
return; if (foregroundNotificationUpdater != null) {
} if (downloadState.state == DownloadState.STATE_DOWNLOADING
Class<? extends DownloadService> clazz = getClass(); || downloadState.state == DownloadState.STATE_REMOVING
RequirementsHelper requirementsHelper = requirementsHelpers.get(clazz); || downloadState.state == DownloadState.STATE_RESTARTING) {
if (requirementsHelper == null) { foregroundNotificationUpdater.startPeriodicUpdates();
requirementsHelper = new RequirementsHelper(this, requirements, getScheduler(), clazz); } else {
requirementsHelpers.put(clazz, requirementsHelper); foregroundNotificationUpdater.update();
requirementsHelper.start(); }
logd("started watching requirements");
}
}
private void maybeStopWatchingRequirements() {
if (downloadManager.getDownloadCount() > 0) {
return;
}
stopWatchingRequirements();
}
private void stopWatchingRequirements() {
RequirementsHelper requirementsHelper = requirementsHelpers.remove(getClass());
if (requirementsHelper != null) {
requirementsHelper.stop();
logd("stopped watching requirements");
} }
} }
...@@ -420,33 +383,6 @@ public abstract class DownloadService extends Service { ...@@ -420,33 +383,6 @@ public abstract class DownloadService extends Service {
return new Intent(context, clazz).setAction(action); return new Intent(context, clazz).setAction(action);
} }
private final class DownloadManagerListener implements DownloadManager.Listener {
@Override
public void onInitialized(DownloadManager downloadManager) {
maybeStartWatchingRequirements(getRequirements());
}
@Override
public void onDownloadStateChanged(
DownloadManager downloadManager, DownloadState downloadState) {
DownloadService.this.onDownloadStateChanged(downloadState);
if (foregroundNotificationUpdater != null) {
if (downloadState.state == DownloadState.STATE_DOWNLOADING
|| downloadState.state == DownloadState.STATE_REMOVING
|| downloadState.state == DownloadState.STATE_RESTARTING) {
foregroundNotificationUpdater.startPeriodicUpdates();
} else {
foregroundNotificationUpdater.update();
}
}
}
@Override
public final void onIdle(DownloadManager downloadManager) {
stop();
}
}
private final class ForegroundNotificationUpdater implements Runnable { private final class ForegroundNotificationUpdater implements Runnable {
private final int notificationId; private final int notificationId;
...@@ -494,58 +430,87 @@ public abstract class DownloadService extends Service { ...@@ -494,58 +430,87 @@ public abstract class DownloadService extends Service {
} }
} }
private static final class RequirementsHelper implements RequirementsWatcher.Listener { private static final class DownloadManagerHelper implements DownloadManager.Listener {
private final Context context; private final Context context;
private final Requirements requirements; private final DownloadManager downloadManager;
private final @Nullable Scheduler scheduler; @Nullable private final Scheduler scheduler;
private final Class<? extends DownloadService> serviceClass; private final Class<? extends DownloadService> serviceClass;
private final RequirementsWatcher requirementsWatcher; @Nullable private DownloadService downloadService;
private RequirementsHelper( private DownloadManagerHelper(
Context context, Context context,
Requirements requirements, DownloadManager downloadManager,
@Nullable Scheduler scheduler, @Nullable Scheduler scheduler,
Class<? extends DownloadService> serviceClass) { Class<? extends DownloadService> serviceClass) {
this.context = context; this.context = context;
this.requirements = requirements; this.downloadManager = downloadManager;
this.scheduler = scheduler; this.scheduler = scheduler;
this.serviceClass = serviceClass; this.serviceClass = serviceClass;
requirementsWatcher = new RequirementsWatcher(context, this, requirements); downloadManager.addListener(this);
if (scheduler != null) {
Requirements requirements = downloadManager.getRequirements();
setSchedulerEnabled(/* enabled= */ !requirements.checkRequirements(context), requirements);
}
} }
public void start() { public void attachService(DownloadService downloadService) {
requirementsWatcher.start(); Assertions.checkState(this.downloadService == null);
this.downloadService = downloadService;
} }
public void stop() { public void detachService(DownloadService downloadService, boolean unschedule) {
requirementsWatcher.stop(); Assertions.checkState(this.downloadService == downloadService);
if (scheduler != null) { this.downloadService = null;
if (unschedule) {
scheduler.cancel(); scheduler.cancel();
} }
} }
@Override @Override
public void requirementsMet(RequirementsWatcher requirementsWatcher) { public void onInitialized(DownloadManager downloadManager) {
try { // Do nothing.
notifyService(); }
} catch (Exception e) {
/* If we can't notify the service, don't stop the scheduler. */ @Override
return; public void onDownloadStateChanged(
DownloadManager downloadManager, DownloadState downloadState) {
if (downloadService != null) {
downloadService.notifyDownloadStateChange(downloadState);
} }
if (scheduler != null) { }
scheduler.cancel();
@Override
public final void onIdle(DownloadManager downloadManager) {
if (downloadService != null) {
downloadService.stop();
} }
} }
@Override @Override
public void requirementsNotMet(RequirementsWatcher requirementsWatcher) { public void onRequirementsStateChanged(
try { DownloadManager downloadManager,
notifyService(); Requirements requirements,
} catch (Exception e) { @Requirements.RequirementFlags int notMetRequirements) {
/* Do nothing. The service isn't running anyway. */ boolean requirementsMet = notMetRequirements == 0;
if (downloadService == null && requirementsMet) {
try {
Intent intent = getIntent(context, serviceClass, DownloadService.ACTION_INIT);
context.startService(intent);
} catch (IllegalStateException e) {
/* startService fails if the app is in the background then don't stop the scheduler. */
return;
}
} }
if (scheduler != null) { if (scheduler != null) {
setSchedulerEnabled(/* enabled= */ !requirementsMet, requirements);
}
}
private void setSchedulerEnabled(boolean enabled, Requirements requirements) {
if (!enabled) {
scheduler.cancel();
} else {
String servicePackage = context.getPackageName(); String servicePackage = context.getPackageName();
boolean success = scheduler.schedule(requirements, servicePackage, ACTION_RESTART); boolean success = scheduler.schedule(requirements, servicePackage, ACTION_RESTART);
if (!success) { if (!success) {
...@@ -553,15 +518,5 @@ public abstract class DownloadService extends Service { ...@@ -553,15 +518,5 @@ public abstract class DownloadService extends Service {
} }
} }
} }
private void notifyService() throws Exception {
Intent intent = getIntent(context, serviceClass, DownloadService.ACTION_INIT);
try {
context.startService(intent);
} catch (IllegalStateException e) {
/* startService will fail if the app is in the background and the service isn't running. */
throw new Exception(e);
}
}
} }
} }
...@@ -155,16 +155,14 @@ public final class Requirements { ...@@ -155,16 +155,14 @@ public final class Requirements {
* @return Whether the requirements are met. * @return Whether the requirements are met.
*/ */
public boolean checkRequirements(Context context) { public boolean checkRequirements(Context context) {
return checkNetworkRequirements(context) return getNotMetRequirements(context) == 0;
&& checkChargingRequirement(context)
&& checkIdleRequirement(context);
} }
/** /**
* Returns the requirement flags that are not met, or 0. * Returns {@link RequirementFlags} that are not met, or 0.
* *
* @param context Any context. * @param context Any context.
* @return The requirement flags that are not met, or 0. * @return RequirementFlags that are not met, or 0.
*/ */
@RequirementFlags @RequirementFlags
public int getNotMetRequirements(Context context) { public int getNotMetRequirements(Context context) {
...@@ -285,4 +283,20 @@ public final class Requirements { ...@@ -285,4 +283,20 @@ public final class Requirements {
+ (isIdleRequired() ? ",idle" : "") + (isIdleRequired() ? ",idle" : "")
+ '}'; + '}';
} }
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
return requirements == ((Requirements) o).requirements;
}
@Override
public int hashCode() {
return requirements;
}
} }
...@@ -55,8 +55,12 @@ public final class RequirementsWatcher { ...@@ -55,8 +55,12 @@ public final class RequirementsWatcher {
* requirements are not met. * requirements are not met.
* *
* @param requirementsWatcher Calling instance. * @param requirementsWatcher Calling instance.
* @param notMetRequirements {@link Requirements.RequirementFlags RequirementFlags} that are not
* met, or 0.
*/ */
void requirementsNotMet(RequirementsWatcher requirementsWatcher); void requirementsNotMet(
RequirementsWatcher requirementsWatcher,
@Requirements.RequirementFlags int notMetRequirements);
} }
private static final String TAG = "RequirementsWatcher"; private static final String TAG = "RequirementsWatcher";
...@@ -66,7 +70,7 @@ public final class RequirementsWatcher { ...@@ -66,7 +70,7 @@ public final class RequirementsWatcher {
private final Requirements requirements; private final Requirements requirements;
private DeviceStatusChangeReceiver receiver; private DeviceStatusChangeReceiver receiver;
private int notMetRequirements; @Requirements.RequirementFlags private int notMetRequirements;
private CapabilityValidatedCallback networkCallback; private CapabilityValidatedCallback networkCallback;
private Handler handler; private Handler handler;
...@@ -85,8 +89,11 @@ public final class RequirementsWatcher { ...@@ -85,8 +89,11 @@ public final class RequirementsWatcher {
/** /**
* Starts watching for changes. Must be called from a thread that has an associated {@link * Starts watching for changes. Must be called from a thread that has an associated {@link
* Looper}. Listener methods are called on the caller thread. * Looper}. Listener methods are called on the caller thread.
*
* @return Initial {@link Requirements.RequirementFlags RequirementFlags} that are not met, or 0.
*/ */
public void start() { @Requirements.RequirementFlags
public int start() {
Assertions.checkNotNull(Looper.myLooper()); Assertions.checkNotNull(Looper.myLooper());
handler = new Handler(); handler = new Handler();
...@@ -115,6 +122,7 @@ public final class RequirementsWatcher { ...@@ -115,6 +122,7 @@ public final class RequirementsWatcher {
receiver = new DeviceStatusChangeReceiver(); receiver = new DeviceStatusChangeReceiver();
context.registerReceiver(receiver, filter, null, handler); context.registerReceiver(receiver, filter, null, handler);
logd(this + " started"); logd(this + " started");
return notMetRequirements;
} }
/** Stops watching for changes. */ /** Stops watching for changes. */
...@@ -162,6 +170,7 @@ public final class RequirementsWatcher { ...@@ -162,6 +170,7 @@ public final class RequirementsWatcher {
} }
private void checkRequirements() { private void checkRequirements() {
@Requirements.RequirementFlags
int notMetRequirements = requirements.getNotMetRequirements(context); int notMetRequirements = requirements.getNotMetRequirements(context);
if (this.notMetRequirements == notMetRequirements) { if (this.notMetRequirements == notMetRequirements) {
logd("notMetRequirements hasn't changed: " + notMetRequirements); logd("notMetRequirements hasn't changed: " + notMetRequirements);
...@@ -173,7 +182,7 @@ public final class RequirementsWatcher { ...@@ -173,7 +182,7 @@ public final class RequirementsWatcher {
listener.requirementsMet(this); listener.requirementsMet(this);
} else { } else {
logd("stop job"); logd("stop job");
listener.requirementsNotMet(this); listener.requirementsNotMet(this, notMetRequirements);
} }
} }
......
...@@ -21,6 +21,7 @@ import static org.junit.Assert.fail; ...@@ -21,6 +21,7 @@ import static org.junit.Assert.fail;
import android.net.Uri; import android.net.Uri;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.offline.DownloadState.State; import com.google.android.exoplayer2.offline.DownloadState.State;
import com.google.android.exoplayer2.scheduler.Requirements;
import com.google.android.exoplayer2.testutil.DummyMainThread; import com.google.android.exoplayer2.testutil.DummyMainThread;
import com.google.android.exoplayer2.testutil.RobolectricUtil; import com.google.android.exoplayer2.testutil.RobolectricUtil;
import com.google.android.exoplayer2.testutil.TestDownloadManagerListener; import com.google.android.exoplayer2.testutil.TestDownloadManagerListener;
...@@ -426,7 +427,12 @@ public class DownloadManagerTest { ...@@ -426,7 +427,12 @@ public class DownloadManagerTest {
() -> { () -> {
downloadManager = downloadManager =
new DownloadManager( new DownloadManager(
actionFile, downloaderFactory, maxActiveDownloadTasks, MIN_RETRY_COUNT); RuntimeEnvironment.application,
actionFile,
downloaderFactory,
maxActiveDownloadTasks,
MIN_RETRY_COUNT,
new Requirements(0));
downloadManagerListener = downloadManagerListener =
new TestDownloadManagerListener(downloadManager, dummyMainThread); new TestDownloadManagerListener(downloadManager, dummyMainThread);
downloadManager.startDownloads(); downloadManager.startDownloads();
......
...@@ -30,6 +30,7 @@ import com.google.android.exoplayer2.offline.DownloadAction; ...@@ -30,6 +30,7 @@ import com.google.android.exoplayer2.offline.DownloadAction;
import com.google.android.exoplayer2.offline.DownloadManager; import com.google.android.exoplayer2.offline.DownloadManager;
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper; import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
import com.google.android.exoplayer2.offline.StreamKey; import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.scheduler.Requirements;
import com.google.android.exoplayer2.testutil.DummyMainThread; import com.google.android.exoplayer2.testutil.DummyMainThread;
import com.google.android.exoplayer2.testutil.FakeDataSet; import com.google.android.exoplayer2.testutil.FakeDataSet;
import com.google.android.exoplayer2.testutil.FakeDataSource; import com.google.android.exoplayer2.testutil.FakeDataSource;
...@@ -241,11 +242,13 @@ public class DownloadManagerDashTest { ...@@ -241,11 +242,13 @@ public class DownloadManagerDashTest {
Factory fakeDataSourceFactory = new FakeDataSource.Factory().setFakeDataSet(fakeDataSet); Factory fakeDataSourceFactory = new FakeDataSource.Factory().setFakeDataSet(fakeDataSet);
downloadManager = downloadManager =
new DownloadManager( new DownloadManager(
RuntimeEnvironment.application,
actionFile, actionFile,
new DefaultDownloaderFactory( new DefaultDownloaderFactory(
new DownloaderConstructorHelper(cache, fakeDataSourceFactory)), new DownloaderConstructorHelper(cache, fakeDataSourceFactory)),
/* maxSimultaneousDownloads= */ 1, /* maxSimultaneousDownloads= */ 1,
/* minRetryCount= */ 3); /* minRetryCount= */ 3,
new Requirements(0));
downloadManagerListener = downloadManagerListener =
new TestDownloadManagerListener(downloadManager, dummyMainThread); new TestDownloadManagerListener(downloadManager, dummyMainThread);
......
...@@ -30,6 +30,7 @@ import com.google.android.exoplayer2.offline.DownloadManager; ...@@ -30,6 +30,7 @@ import com.google.android.exoplayer2.offline.DownloadManager;
import com.google.android.exoplayer2.offline.DownloadService; import com.google.android.exoplayer2.offline.DownloadService;
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper; import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
import com.google.android.exoplayer2.offline.StreamKey; import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.scheduler.Requirements;
import com.google.android.exoplayer2.scheduler.Scheduler; import com.google.android.exoplayer2.scheduler.Scheduler;
import com.google.android.exoplayer2.testutil.DummyMainThread; import com.google.android.exoplayer2.testutil.DummyMainThread;
import com.google.android.exoplayer2.testutil.FakeDataSet; import com.google.android.exoplayer2.testutil.FakeDataSet;
...@@ -117,11 +118,13 @@ public class DownloadServiceDashTest { ...@@ -117,11 +118,13 @@ public class DownloadServiceDashTest {
actionFile.delete(); actionFile.delete();
final DownloadManager dashDownloadManager = final DownloadManager dashDownloadManager =
new DownloadManager( new DownloadManager(
RuntimeEnvironment.application,
actionFile, actionFile,
new DefaultDownloaderFactory( new DefaultDownloaderFactory(
new DownloaderConstructorHelper(cache, fakeDataSourceFactory)), new DownloaderConstructorHelper(cache, fakeDataSourceFactory)),
/* maxSimultaneousDownloads= */ 1, /* maxSimultaneousDownloads= */ 1,
/* minRetryCount= */ 3); /* minRetryCount= */ 3,
new Requirements(0));
downloadManagerListener = downloadManagerListener =
new TestDownloadManagerListener(dashDownloadManager, dummyMainThread); new TestDownloadManagerListener(dashDownloadManager, dummyMainThread);
dashDownloadManager.startDownloads(); dashDownloadManager.startDownloads();
......
...@@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; ...@@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
import android.os.ConditionVariable; import android.os.ConditionVariable;
import com.google.android.exoplayer2.offline.DownloadManager; import com.google.android.exoplayer2.offline.DownloadManager;
import com.google.android.exoplayer2.offline.DownloadState; import com.google.android.exoplayer2.offline.DownloadState;
import com.google.android.exoplayer2.scheduler.Requirements;
import java.util.HashMap; import java.util.HashMap;
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
...@@ -82,6 +83,12 @@ public final class TestDownloadManagerListener implements DownloadManager.Listen ...@@ -82,6 +83,12 @@ public final class TestDownloadManagerListener implements DownloadManager.Listen
} }
} }
@Override
public void onRequirementsStateChanged(
DownloadManager downloadManager, Requirements requirements, int notMetRequirements) {
// Do nothing.
}
/** /**
* Blocks until all remove and download tasks are complete and throws an exception if there was an * Blocks until all remove and download tasks are complete and throws an exception if there was an
* error. * error.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment