Make DownloadTracker use DownloadIndex

DownloadTracker will stop updating DownloadIndex when DownloadManager
starts using the same DownloadIndex.

PiperOrigin-RevId: 232306803
This commit is contained in:
eguven 2019-02-04 17:03:12 +00:00 committed by Oliver Woodman
parent fb99c26426
commit a5d64463c7
5 changed files with 74 additions and 53 deletions

View File

@ -18,7 +18,11 @@ package com.google.android.exoplayer2.demo;
import android.app.Application; import android.app.Application;
import com.google.android.exoplayer2.DefaultRenderersFactory; import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.database.ExoDatabaseProvider;
import com.google.android.exoplayer2.offline.ActionFile;
import com.google.android.exoplayer2.offline.DefaultDownloadIndex;
import com.google.android.exoplayer2.offline.DefaultDownloaderFactory; import com.google.android.exoplayer2.offline.DefaultDownloaderFactory;
import com.google.android.exoplayer2.offline.DownloadIndexUtil;
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.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
@ -31,14 +35,17 @@ import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory; import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor; import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor;
import com.google.android.exoplayer2.upstream.cache.SimpleCache; import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.File; import java.io.File;
import java.io.IOException;
/** /**
* Placeholder application to facilitate overriding Application methods for debugging and testing. * Placeholder application to facilitate overriding Application methods for debugging and testing.
*/ */
public class DemoApplication extends Application { public class DemoApplication extends Application {
private static final String TAG = "DemoApplication";
private static final String DOWNLOAD_ACTION_FILE = "actions"; private static final String DOWNLOAD_ACTION_FILE = "actions";
private static final String DOWNLOAD_TRACKER_ACTION_FILE = "tracked_actions"; private static final String DOWNLOAD_TRACKER_ACTION_FILE = "tracked_actions";
private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads"; private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads";
@ -97,6 +104,16 @@ public class DemoApplication extends Application {
private synchronized void initDownloadManager() { private synchronized void initDownloadManager() {
if (downloadManager == null) { if (downloadManager == null) {
DefaultDownloadIndex downloadIndex = new DefaultDownloadIndex(new ExoDatabaseProvider(this));
File actionFile = new File(getDownloadDirectory(), DOWNLOAD_TRACKER_ACTION_FILE);
if (actionFile.exists()) {
try {
DownloadIndexUtil.upgradeActionFile(new ActionFile(actionFile), downloadIndex, null);
} catch (IOException e) {
Log.e(TAG, "Upgrading action file failed", e);
}
actionFile.delete();
}
DownloaderConstructorHelper downloaderConstructorHelper = DownloaderConstructorHelper downloaderConstructorHelper =
new DownloaderConstructorHelper(getDownloadCache(), buildHttpDataSourceFactory()); new DownloaderConstructorHelper(getDownloadCache(), buildHttpDataSourceFactory());
downloadManager = downloadManager =
@ -108,10 +125,7 @@ public class DemoApplication extends Application {
DownloadManager.DEFAULT_MIN_RETRY_COUNT, DownloadManager.DEFAULT_MIN_RETRY_COUNT,
DownloadManager.DEFAULT_REQUIREMENTS); DownloadManager.DEFAULT_REQUIREMENTS);
downloadTracker = downloadTracker =
new DownloadTracker( new DownloadTracker(/* context= */ this, buildDataSourceFactory(), downloadIndex);
/* context= */ this,
buildDataSourceFactory(),
new File(getDownloadDirectory(), DOWNLOAD_TRACKER_ACTION_FILE));
downloadManager.addListener(downloadTracker); downloadManager.addListener(downloadTracker);
} }
} }

View File

@ -34,11 +34,13 @@ import android.widget.Toast;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.offline.ActionFile; import com.google.android.exoplayer2.offline.ActionFile;
import com.google.android.exoplayer2.offline.DefaultDownloadIndex;
import com.google.android.exoplayer2.offline.DownloadAction; import com.google.android.exoplayer2.offline.DownloadAction;
import com.google.android.exoplayer2.offline.DownloadHelper; import com.google.android.exoplayer2.offline.DownloadHelper;
import com.google.android.exoplayer2.offline.DownloadManager; 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.DownloadState; import com.google.android.exoplayer2.offline.DownloadState;
import com.google.android.exoplayer2.offline.DownloadStateCursor;
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.Requirements;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
@ -51,8 +53,8 @@ import com.google.android.exoplayer2.ui.TrackSelectionView;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -80,20 +82,21 @@ public class DownloadTracker implements DownloadManager.Listener {
private final DataSource.Factory dataSourceFactory; private final DataSource.Factory dataSourceFactory;
private final TrackNameProvider trackNameProvider; private final TrackNameProvider trackNameProvider;
private final CopyOnWriteArraySet<Listener> listeners; private final CopyOnWriteArraySet<Listener> listeners;
private final HashMap<Uri, DownloadAction> trackedDownloadStates; private final HashMap<Uri, DownloadState> trackedDownloadStates;
private final ActionFile actionFile; private final DefaultDownloadIndex downloadIndex;
private final Handler actionFileWriteHandler; private final Handler actionFileIOHandler;
public DownloadTracker(Context context, DataSource.Factory dataSourceFactory, File actionFile) { public DownloadTracker(
Context context, DataSource.Factory dataSourceFactory, DefaultDownloadIndex downloadIndex) {
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
this.actionFile = new ActionFile(actionFile); this.downloadIndex = downloadIndex;
trackNameProvider = new DefaultTrackNameProvider(context.getResources()); trackNameProvider = new DefaultTrackNameProvider(context.getResources());
listeners = new CopyOnWriteArraySet<>(); listeners = new CopyOnWriteArraySet<>();
trackedDownloadStates = new HashMap<>(); trackedDownloadStates = new HashMap<>();
HandlerThread actionFileWriteThread = new HandlerThread("DownloadTracker"); HandlerThread actionFileWriteThread = new HandlerThread("DownloadTracker");
actionFileWriteThread.start(); actionFileWriteThread.start();
actionFileWriteHandler = new Handler(actionFileWriteThread.getLooper()); actionFileIOHandler = new Handler(actionFileWriteThread.getLooper());
loadTrackedActions(); loadTrackedActions();
} }
@ -114,7 +117,7 @@ public class DownloadTracker implements DownloadManager.Listener {
if (!trackedDownloadStates.containsKey(uri)) { if (!trackedDownloadStates.containsKey(uri)) {
return Collections.emptyList(); return Collections.emptyList();
} }
return trackedDownloadStates.get(uri).getKeys(); return Arrays.asList(trackedDownloadStates.get(uri).streamKeys);
} }
public void toggleDownload( public void toggleDownload(
@ -146,7 +149,7 @@ public class DownloadTracker implements DownloadManager.Listener {
|| downloadState.state == DownloadState.STATE_FAILED) { || downloadState.state == DownloadState.STATE_FAILED) {
// A download has been removed, or has failed. Stop tracking it. // A download has been removed, or has failed. Stop tracking it.
if (trackedDownloadStates.remove(downloadState.uri) != null) { if (trackedDownloadStates.remove(downloadState.uri) != null) {
handleTrackedDownloadStatesChanged(); handleTrackedDownloadStateChanged(downloadState);
} }
} }
} }
@ -167,27 +170,24 @@ public class DownloadTracker implements DownloadManager.Listener {
// Internal methods // Internal methods
private void loadTrackedActions() { private void loadTrackedActions() {
try { DownloadStateCursor downloadStates = downloadIndex.getDownloadStates();
DownloadAction[] allActions = actionFile.load(); while (downloadStates.moveToNext()) {
for (DownloadAction action : allActions) { DownloadState downloadState = downloadStates.getDownloadState();
trackedDownloadStates.put(action.uri, action); trackedDownloadStates.put(downloadState.uri, downloadState);
}
} catch (IOException e) {
Log.e(TAG, "Failed to load tracked actions", e);
} }
downloadStates.close();
} }
private void handleTrackedDownloadStatesChanged() { private void handleTrackedDownloadStateChanged(DownloadState downloadState) {
for (Listener listener : listeners) { for (Listener listener : listeners) {
listener.onDownloadsChanged(); listener.onDownloadsChanged();
} }
final DownloadAction[] actions = trackedDownloadStates.values().toArray(new DownloadAction[0]); actionFileIOHandler.post(
actionFileWriteHandler.post(
() -> { () -> {
try { if (downloadState.state == DownloadState.STATE_REMOVED) {
actionFile.store(actions); downloadIndex.removeDownloadState(downloadState.id);
} catch (IOException e) { } else {
Log.e(TAG, "Failed to store tracked actions", e); downloadIndex.putDownloadState(downloadState);
} }
}); });
} }
@ -197,8 +197,9 @@ public class DownloadTracker implements DownloadManager.Listener {
// This content is already being downloaded. Do nothing. // This content is already being downloaded. Do nothing.
return; return;
} }
trackedDownloadStates.put(action.uri, action); DownloadState downloadState = new DownloadState(action);
handleTrackedDownloadStatesChanged(); trackedDownloadStates.put(downloadState.uri, downloadState);
handleTrackedDownloadStateChanged(downloadState);
startServiceWithAction(action); startServiceWithAction(action);
} }

View File

@ -81,7 +81,7 @@ public final class DownloadIndexUtil {
if (downloadState != null) { if (downloadState != null) {
downloadState = merge(downloadState, action); downloadState = merge(downloadState, action);
} else { } else {
downloadState = convert(action); downloadState = new DownloadState(action);
} }
downloadIndex.putDownloadState(downloadState); downloadIndex.putDownloadState(downloadState);
} }
@ -121,26 +121,4 @@ public final class DownloadIndexUtil {
newKeys, newKeys,
action.data); action.data);
} }
private static DownloadState convert(DownloadAction action) {
long currentTimeMs = System.currentTimeMillis();
return new DownloadState(
action.id,
action.type,
action.uri,
action.customCacheKey,
/* state= */ action.isRemoveAction
? DownloadState.STATE_REMOVING
: DownloadState.STATE_QUEUED,
/* downloadPercentage= */ C.PERCENTAGE_UNSET,
/* downloadedBytes= */ 0,
/* totalBytes= */ C.LENGTH_UNSET,
DownloadState.FAILURE_REASON_NONE,
/* stopFlags= */ 0,
/* notMetRequirements= */ 0,
/* startTimeMs= */ currentTimeMs,
/* updateTimeMs= */ currentTimeMs,
action.keys.toArray(new StreamKey[0]),
action.data);
}
} }

View File

@ -164,6 +164,34 @@ public final class DownloadState {
/** Not met requirements to download. */ /** Not met requirements to download. */
@Requirements.RequirementFlags public final int notMetRequirements; @Requirements.RequirementFlags public final int notMetRequirements;
/**
* Creates a {@link DownloadState} using a {@link DownloadAction}.
*
* @param action The {@link DownloadAction}.
*/
public DownloadState(DownloadAction action) {
this(action, System.currentTimeMillis());
}
private DownloadState(DownloadAction action, long currentTimeMs) {
this(
action.id,
action.type,
action.uri,
action.customCacheKey,
/* state= */ action.isRemoveAction ? STATE_REMOVING : STATE_QUEUED,
/* downloadPercentage= */ C.PERCENTAGE_UNSET,
/* downloadedBytes= */ 0,
/* totalBytes= */ C.LENGTH_UNSET,
FAILURE_REASON_NONE,
/* stopFlags= */ 0,
/* notMetRequirements= */ 0,
/* startTimeMs= */ currentTimeMs,
/* updateTimeMs= */ currentTimeMs,
action.keys.toArray(new StreamKey[0]),
action.data);
}
/* package */ DownloadState( /* package */ DownloadState(
String id, String id,
String type, String type,

View File

@ -16,7 +16,7 @@
package com.google.android.exoplayer2.offline; package com.google.android.exoplayer2.offline;
/** Provides random read-write access to the result set returned by a database query. */ /** Provides random read-write access to the result set returned by a database query. */
interface DownloadStateCursor { public interface DownloadStateCursor {
/** Returns the DownloadState at the current position. */ /** Returns the DownloadState at the current position. */
DownloadState getDownloadState(); DownloadState getDownloadState();