Remove DownloadAction serialization

Implement Parcelable instead, which is a more intented way of
passing structured data via Intents.

PiperOrigin-RevId: 242646441
This commit is contained in:
olly 2019-04-09 13:06:06 +01:00 committed by Oliver Woodman
parent 14d40b0faf
commit d3b63a97ad
21 changed files with 302 additions and 445 deletions

View File

@ -136,7 +136,8 @@ public class DemoApplication extends Application {
ActionFile actionFile = new ActionFile(new File(getDownloadDirectory(), file));
if (actionFile.exists()) {
try {
DownloadIndexUtil.upgradeActionFile(actionFile, downloadIndex, null);
DownloadIndexUtil.mergeActionFile(
actionFile, /* downloadIdProvider= */ null, downloadIndex);
} catch (IOException e) {
Log.e(TAG, "Upgrading action file failed", e);
}

View File

@ -15,6 +15,8 @@
*/
package com.google.android.exoplayer2.offline;
import android.net.Uri;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.offline.DownloadAction.UnsupportedActionException;
import com.google.android.exoplayer2.util.AtomicFile;
import com.google.android.exoplayer2.util.Util;
@ -23,10 +25,9 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
* Stores and loads {@link DownloadAction}s to/from a file.
*/
/** Loads {@link DownloadAction DownloadActions} from legacy action files. */
public final class ActionFile {
private static final int VERSION = 0;
@ -34,17 +35,28 @@ public final class ActionFile {
private final AtomicFile atomicFile;
/**
* @param actionFile File to be used to store and load {@link DownloadAction}s.
* @param actionFile The file from which {@link DownloadAction DownloadActions} will be loaded.
*/
public ActionFile(File actionFile) {
atomicFile = new AtomicFile(actionFile);
}
/** Returns whether the file or its backup exists. */
public boolean exists() {
return atomicFile.exists();
}
/** Deletes the action file and its backup. */
public void delete() {
atomicFile.delete();
}
/**
* Loads {@link DownloadAction}s from file.
* Loads {@link DownloadAction DownloadActions} from the file.
*
* @return Loaded DownloadActions. If the action file doesn't exists returns an empty array.
* @throws IOException If there is an error during loading.
* @return The loaded {@link DownloadAction DownloadActions}, or an empty array if the file does
* not exist.
* @throws IOException If there is an error reading the file.
*/
public DownloadAction[] load() throws IOException {
if (!exists()) {
@ -62,7 +74,7 @@ public final class ActionFile {
ArrayList<DownloadAction> actions = new ArrayList<>();
for (int i = 0; i < actionCount; i++) {
try {
actions.add(DownloadAction.deserializeFromStream(dataInputStream));
actions.add(readDownloadAction(dataInputStream));
} catch (UnsupportedActionException e) {
// remove DownloadAction is not supported. Ignore the exception and continue loading rest.
}
@ -73,13 +85,74 @@ public final class ActionFile {
}
}
/** Returns whether the file or its backup exists. */
public boolean exists() {
return atomicFile.exists();
private static DownloadAction readDownloadAction(DataInputStream input) throws IOException {
String type = input.readUTF();
int version = input.readInt();
Uri uri = Uri.parse(input.readUTF());
boolean isRemoveAction = input.readBoolean();
int dataLength = input.readInt();
byte[] data;
if (dataLength != 0) {
data = new byte[dataLength];
input.readFully(data);
} else {
data = null;
}
/** Delete the action file and its backup. */
public void delete() {
atomicFile.delete();
// Serialized version 0 progressive actions did not contain keys.
boolean isLegacyProgressive = version == 0 && DownloadAction.TYPE_PROGRESSIVE.equals(type);
List<StreamKey> keys = new ArrayList<>();
if (!isLegacyProgressive) {
int keyCount = input.readInt();
for (int i = 0; i < keyCount; i++) {
keys.add(readKey(type, version, input));
}
}
// Serialized version 0 and 1 DASH/HLS/SS actions did not contain a custom cache key.
boolean isLegacySegmented =
version < 2
&& (DownloadAction.TYPE_DASH.equals(type)
|| DownloadAction.TYPE_HLS.equals(type)
|| DownloadAction.TYPE_SS.equals(type));
String customCacheKey = null;
if (!isLegacySegmented) {
customCacheKey = input.readBoolean() ? input.readUTF() : null;
}
// Serialized version 0, 1 and 2 did not contain an id. We need to generate one.
String id = version < 3 ? generateDownloadActionId(uri, customCacheKey) : input.readUTF();
if (isRemoveAction) {
// Remove actions are not supported anymore.
throw new UnsupportedActionException();
}
return new DownloadAction(id, type, uri, keys, customCacheKey, data);
}
private static StreamKey readKey(String type, int version, DataInputStream input)
throws IOException {
int periodIndex;
int groupIndex;
int trackIndex;
// Serialized version 0 HLS/SS actions did not contain a period index.
if ((DownloadAction.TYPE_HLS.equals(type) || DownloadAction.TYPE_SS.equals(type))
&& version == 0) {
periodIndex = 0;
groupIndex = input.readInt();
trackIndex = input.readInt();
} else {
periodIndex = input.readInt();
groupIndex = input.readInt();
trackIndex = input.readInt();
}
return new StreamKey(periodIndex, groupIndex, trackIndex);
}
private static String generateDownloadActionId(Uri uri, @Nullable String customCacheKey) {
return customCacheKey != null ? customCacheKey : uri.toString();
}
}

View File

@ -341,7 +341,7 @@ public final class DefaultDownloadIndex implements DownloadIndex {
private static DownloadState getDownloadStateForCurrentRow(Cursor cursor) {
DownloadAction action =
DownloadAction.createDownloadAction(
new DownloadAction(
cursor.getString(COLUMN_INDEX_ID),
cursor.getString(COLUMN_INDEX_TYPE),
Uri.parse(cursor.getString(COLUMN_INDEX_URI)),

View File

@ -15,24 +15,22 @@
*/
package com.google.android.exoplayer2.offline;
import static com.google.android.exoplayer2.util.Util.castNonNull;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/** Contains the necessary parameters for a download action. */
public final class DownloadAction {
public final class DownloadAction implements Parcelable {
/** Thrown when the encoded action data belongs to an unsupported DownloadAction type. */
public static class UnsupportedActionException extends IOException {}
@ -46,56 +44,6 @@ public final class DownloadAction {
/** Type for SmoothStreaming downloads. */
public static final String TYPE_SS = "ss";
private static final int VERSION = 3;
/**
* Deserializes a download action from the {@code data}.
*
* @param data The action data to deserialize.
* @return The deserialized action.
* @throws IOException If the data could not be deserialized.
* @throws UnsupportedActionException If the data belongs to an unsupported {@link DownloadAction}
* type. Input read position is set to the end of the data.
*/
public static DownloadAction fromByteArray(byte[] data) throws IOException {
ByteArrayInputStream input = new ByteArrayInputStream(data);
return deserializeFromStream(input);
}
/**
* Deserializes a single download action from {@code input}.
*
* @param input The stream from which to read.
* @return The deserialized action.
* @throws IOException If there is an IO error reading from {@code input}, or if the data could
* not be deserialized.
* @throws UnsupportedActionException If the data belongs to an unsupported {@link DownloadAction}
* type. Input read position is set to the end of the data.
*/
public static DownloadAction deserializeFromStream(InputStream input) throws IOException {
return readFromStream(new DataInputStream(input));
}
/**
* Creates a DASH download action.
*
* @param id The content id.
* @param type The type of the action.
* @param uri The URI of the media to be downloaded.
* @param keys Keys of streams to be downloaded. If empty, all streams will be downloaded.
* @param customCacheKey A custom key for cache indexing, or null.
* @param data Optional custom data for this action. If {@code null} an empty array will be used.
*/
public static DownloadAction createDownloadAction(
String id,
String type,
Uri uri,
List<StreamKey> keys,
@Nullable String customCacheKey,
@Nullable byte[] data) {
return new DownloadAction(id, type, uri, keys, customCacheKey, data);
}
/** The unique content id. */
public final String id;
/** The type of the action. */
@ -117,7 +65,7 @@ public final class DownloadAction {
* @param customCacheKey See {@link #customCacheKey}.
* @param data See {@link #data}.
*/
private DownloadAction(
public DownloadAction(
String id,
String type,
Uri uri,
@ -127,13 +75,38 @@ public final class DownloadAction {
this.id = id;
this.type = type;
this.uri = uri;
this.customCacheKey = customCacheKey;
ArrayList<StreamKey> mutableKeys = new ArrayList<>(streamKeys);
Collections.sort(mutableKeys);
this.streamKeys = Collections.unmodifiableList(mutableKeys);
this.customCacheKey = customCacheKey;
this.data = data != null ? Arrays.copyOf(data, data.length) : Util.EMPTY_BYTE_ARRAY;
}
/* package */ DownloadAction(Parcel in) {
id = castNonNull(in.readString());
type = castNonNull(in.readString());
uri = Uri.parse(castNonNull(in.readString()));
int streamKeyCount = in.readInt();
ArrayList<StreamKey> mutableStreamKeys = new ArrayList<>(streamKeyCount);
for (int i = 0; i < streamKeyCount; i++) {
mutableStreamKeys.add(in.readParcelable(StreamKey.class.getClassLoader()));
}
streamKeys = Collections.unmodifiableList(mutableStreamKeys);
customCacheKey = in.readString();
data = new byte[in.readInt()];
in.readByteArray(data);
}
/**
* Returns a copy with the specified ID.
*
* @param id The ID of the copy.
* @return The copy with the specified ID.
*/
public DownloadAction copyWithId(String id) {
return new DownloadAction(id, type, uri, streamKeys, customCacheKey, data);
}
/**
* Returns the result of merging {@code newAction} into this action.
*
@ -143,7 +116,6 @@ public final class DownloadAction {
public DownloadAction copyWithMergedAction(DownloadAction newAction) {
Assertions.checkState(id.equals(newAction.id));
Assertions.checkState(type.equals(newAction.type));
List<StreamKey> mergedKeys;
if (streamKeys.isEmpty() || newAction.streamKeys.isEmpty()) {
// If either streamKeys is empty then all streams should be downloaded.
@ -161,18 +133,6 @@ public final class DownloadAction {
id, type, newAction.uri, mergedKeys, newAction.customCacheKey, newAction.data);
}
/** Serializes itself into a byte array. */
public byte[] toByteArray() {
ByteArrayOutputStream output = new ByteArrayOutputStream();
try {
serializeToStream(output);
} catch (IOException e) {
// ByteArrayOutputStream shouldn't throw IOException.
throw new IllegalStateException();
}
return output.toByteArray();
}
@Override
public boolean equals(@Nullable Object o) {
if (!(o instanceof DownloadAction)) {
@ -191,6 +151,7 @@ public final class DownloadAction {
public final int hashCode() {
int result = type.hashCode();
result = 31 * result + id.hashCode();
result = 31 * result + type.hashCode();
result = 31 * result + uri.hashCode();
result = 31 * result + streamKeys.hashCode();
result = 31 * result + (customCacheKey != null ? customCacheKey.hashCode() : 0);
@ -198,101 +159,38 @@ public final class DownloadAction {
return result;
}
// Serialization.
// Parcelable implementation.
/**
* Serializes this action into an {@link OutputStream}.
*
* @param output The stream to write to.
*/
public final void serializeToStream(OutputStream output) throws IOException {
// Don't close the stream as it closes the underlying stream too.
DataOutputStream dataOutputStream = new DataOutputStream(output);
dataOutputStream.writeUTF(type);
dataOutputStream.writeInt(VERSION);
dataOutputStream.writeUTF(uri.toString());
dataOutputStream.writeBoolean(false);
dataOutputStream.writeInt(data.length);
dataOutputStream.write(data);
dataOutputStream.writeInt(streamKeys.size());
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(id);
dest.writeString(type);
dest.writeString(uri.toString());
dest.writeInt(streamKeys.size());
for (int i = 0; i < streamKeys.size(); i++) {
StreamKey key = streamKeys.get(i);
dataOutputStream.writeInt(key.periodIndex);
dataOutputStream.writeInt(key.groupIndex);
dataOutputStream.writeInt(key.trackIndex);
dest.writeParcelable(streamKeys.get(i), /* parcelableFlags= */ 0);
}
dataOutputStream.writeBoolean(customCacheKey != null);
if (customCacheKey != null) {
dataOutputStream.writeUTF(customCacheKey);
}
dataOutputStream.writeUTF(id);
dataOutputStream.flush();
dest.writeString(customCacheKey);
dest.writeInt(data.length);
dest.writeByteArray(data);
}
private static DownloadAction readFromStream(DataInputStream input) throws IOException {
String type = input.readUTF();
int version = input.readInt();
public static final Parcelable.Creator<DownloadAction> CREATOR =
new Parcelable.Creator<DownloadAction>() {
Uri uri = Uri.parse(input.readUTF());
boolean isRemoveAction = input.readBoolean();
int dataLength = input.readInt();
byte[] data;
if (dataLength != 0) {
data = new byte[dataLength];
input.readFully(data);
} else {
data = null;
@Override
public DownloadAction createFromParcel(Parcel in) {
return new DownloadAction(in);
}
// Serialized version 0 progressive actions did not contain keys.
boolean isLegacyProgressive = version == 0 && TYPE_PROGRESSIVE.equals(type);
List<StreamKey> keys = new ArrayList<>();
if (!isLegacyProgressive) {
int keyCount = input.readInt();
for (int i = 0; i < keyCount; i++) {
keys.add(readKey(type, version, input));
}
}
// Serialized version 0 and 1 DASH/HLS/SS actions did not contain a custom cache key.
boolean isLegacySegmented =
version < 2 && (TYPE_DASH.equals(type) || TYPE_HLS.equals(type) || TYPE_SS.equals(type));
String customCacheKey = null;
if (!isLegacySegmented) {
customCacheKey = input.readBoolean() ? input.readUTF() : null;
}
// Serialized version 0, 1 and 2 did not contain an id.
String id = version < 3 ? generateId(uri, customCacheKey) : input.readUTF();
if (isRemoveAction) {
// Remove actions are not supported anymore.
throw new UnsupportedActionException();
}
return new DownloadAction(id, type, uri, keys, customCacheKey, data);
}
/* package */ static String generateId(Uri uri, @Nullable String customCacheKey) {
return customCacheKey != null ? customCacheKey : uri.toString();
}
private static StreamKey readKey(String type, int version, DataInputStream input)
throws IOException {
int periodIndex;
int groupIndex;
int trackIndex;
// Serialized version 0 HLS/SS actions did not contain a period index.
if ((TYPE_HLS.equals(type) || TYPE_SS.equals(type)) && version == 0) {
periodIndex = 0;
groupIndex = input.readInt();
trackIndex = input.readInt();
} else {
periodIndex = input.readInt();
groupIndex = input.readInt();
trackIndex = input.readInt();
}
return new StreamKey(periodIndex, groupIndex, trackIndex);
@Override
public DownloadAction[] newArray(int size) {
return new DownloadAction[size];
}
};
}

View File

@ -586,8 +586,8 @@ public final class DownloadHelper {
public DownloadAction getDownloadAction(@Nullable byte[] data) {
String downloadId = uri.toString();
if (mediaSource == null) {
return DownloadAction.createDownloadAction(
downloadId, downloadType, uri, /* keys= */ Collections.emptyList(), cacheKey, data);
return new DownloadAction(
downloadId, downloadType, uri, /* streamKeys= */ Collections.emptyList(), cacheKey, data);
}
assertPreparedWithMedia();
List<StreamKey> streamKeys = new ArrayList<>();
@ -601,8 +601,7 @@ public final class DownloadHelper {
}
streamKeys.addAll(mediaPreparer.mediaPeriods[periodIndex].getStreamKeys(allSelections));
}
return DownloadAction.createDownloadAction(
downloadId, downloadType, uri, streamKeys, cacheKey, data);
return new DownloadAction(downloadId, downloadType, uri, streamKeys, cacheKey, data);
}
// Initialization of array of Lists.

View File

@ -36,45 +36,40 @@ public final class DownloadIndexUtil {
private DownloadIndexUtil() {}
/**
* Upgrades an {@link ActionFile} to {@link DownloadIndex}.
* Merges {@link DownloadAction DownloadActions} contained in an {@link ActionFile} into a {@link
* DownloadIndex}.
*
* <p>This method shouldn't be called while {@link DownloadIndex} is used by {@link
* <p>This method must not be called while the {@link DownloadIndex} is being used by a {@link
* DownloadManager}.
*
* @param actionFile The action file to upgrade.
* @param downloadIndex Actions are converted to {@link DownloadState}s and stored in this index.
* @param downloadIdProvider A nullable custom download id provider.
* @throws IOException If there is an error during loading actions.
* @param actionFile The action file.
* @param downloadIdProvider A custom download id provider, or {@code null}.
* @param downloadIndex The index into which the action will be merged.
* @throws IOException If an error occurs loading or merging the actions.
*/
public static void upgradeActionFile(
public static void mergeActionFile(
ActionFile actionFile,
DefaultDownloadIndex downloadIndex,
@Nullable DownloadIdProvider downloadIdProvider)
@Nullable DownloadIdProvider downloadIdProvider,
DefaultDownloadIndex downloadIndex)
throws IOException {
if (downloadIdProvider == null) {
downloadIdProvider = downloadAction -> downloadAction.id;
}
for (DownloadAction action : actionFile.load()) {
addAction(downloadIndex, downloadIdProvider.getId(action), action);
if (downloadIdProvider != null) {
action = action.copyWithId(downloadIdProvider.getId(action));
}
mergeAction(action, downloadIndex);
}
}
/**
* Converts a {@link DownloadAction} to {@link DownloadState} and stored in the given {@link
* DownloadIndex}.
* Merges a {@link DownloadAction} into a {@link DownloadIndexUtil}.
*
* <p>This method shouldn't be called while {@link DownloadIndex} is used by {@link
* DownloadManager}.
*
* @param downloadIndex The action is converted to {@link DownloadState} and stored in this index.
* @param id A nullable custom download id which overwrites {@link DownloadAction#id}.
* @param action The action to be stored in {@link DownloadIndex}.
* @throws IOException If an error occurs storing the state in the {@link DownloadIndex}.
* @param action The action to be merged.
* @param downloadIndex The index into which the action will be merged.
* @throws IOException If an error occurs merging the action.
*/
public static void addAction(
DefaultDownloadIndex downloadIndex, @Nullable String id, DownloadAction action)
/* package */ static void mergeAction(DownloadAction action, DefaultDownloadIndex downloadIndex)
throws IOException {
DownloadState downloadState = downloadIndex.getDownloadState(id != null ? id : action.id);
DownloadState downloadState = downloadIndex.getDownloadState(action.id);
if (downloadState != null) {
downloadState = downloadState.copyWithMergedAction(action);
} else {

View File

@ -30,7 +30,6 @@ import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.NotificationUtil;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.util.HashMap;
/** A {@link Service} for downloading media. */
@ -54,9 +53,8 @@ public abstract class DownloadService extends Service {
* Adds a new download. Extras:
*
* <ul>
* <li>{@link #KEY_DOWNLOAD_ACTION} - The {@code byte[]} representation of the {@link
* DownloadAction} that defines the download to be added. The required representation can be
* obtained by calling {@link DownloadAction#toByteArray()}.
* <li>{@link #KEY_DOWNLOAD_ACTION} - A {@link DownloadAction} defining the download to be
* added.
* <li>{@link #KEY_FOREGROUND} - See {@link #KEY_FOREGROUND}.
* </ul>
*/
@ -231,7 +229,7 @@ public abstract class DownloadService extends Service {
DownloadAction downloadAction,
boolean foreground) {
return getIntent(context, clazz, ACTION_ADD)
.putExtra(KEY_DOWNLOAD_ACTION, downloadAction.toByteArray())
.putExtra(KEY_DOWNLOAD_ACTION, downloadAction)
.putExtra(KEY_FOREGROUND, foreground);
}
@ -390,20 +388,12 @@ public abstract class DownloadService extends Service {
// Do nothing.
break;
case ACTION_ADD:
byte[] actionData = intent.getByteArrayExtra(KEY_DOWNLOAD_ACTION);
if (actionData == null) {
DownloadAction downloadAction = intent.getParcelableExtra(KEY_DOWNLOAD_ACTION);
if (downloadAction == null) {
Log.e(TAG, "Ignored ADD action: Missing download_action extra");
} else {
DownloadAction downloadAction = null;
try {
downloadAction = DownloadAction.fromByteArray(actionData);
} catch (IOException e) {
Log.e(TAG, "Ignored ADD action: Failed to deserialize download_action extra", e);
}
if (downloadAction != null) {
downloadManager.addDownload(downloadAction);
}
}
break;
case ACTION_START:
String contentId = intent.getStringExtra(KEY_CONTENT_ID);

View File

@ -15,6 +15,8 @@
*/
package com.google.android.exoplayer2.offline;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -25,7 +27,7 @@ import androidx.annotation.Nullable;
* within the group. The interpretation of these indices depends on the type of media for which the
* stream key is used.
*/
public final class StreamKey implements Comparable<StreamKey> {
public final class StreamKey implements Comparable<StreamKey>, Parcelable {
/** The period index. */
public final int periodIndex;
@ -53,6 +55,12 @@ public final class StreamKey implements Comparable<StreamKey> {
this.trackIndex = trackIndex;
}
/* package */ StreamKey(Parcel in) {
periodIndex = in.readInt();
groupIndex = in.readInt();
trackIndex = in.readInt();
}
@Override
public String toString() {
return periodIndex + "." + groupIndex + "." + trackIndex;
@ -94,4 +102,32 @@ public final class StreamKey implements Comparable<StreamKey> {
}
return result;
}
// Parcelable implementation.
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(periodIndex);
dest.writeInt(groupIndex);
dest.writeInt(trackIndex);
}
public static final Parcelable.Creator<StreamKey> CREATOR =
new Parcelable.Creator<StreamKey>() {
@Override
public StreamKey createFromParcel(Parcel in) {
return new StreamKey(in);
}
@Override
public StreamKey[] newArray(int size) {
return new StreamKey[size];
}
};
}

View File

@ -124,11 +124,11 @@ public class ActionFileTest {
}
private static DownloadAction buildExpectedAction(Uri uri, byte[] data) {
return DownloadAction.createDownloadAction(
return new DownloadAction(
/* id= */ uri.toString(),
DownloadAction.TYPE_PROGRESSIVE,
uri,
/* keys= */ Collections.emptyList(),
/* streamKeys= */ Collections.emptyList(),
/* customCacheKey= */ null,
data);
}

View File

@ -38,11 +38,11 @@ public final class DefaultDownloaderFactoryTest {
Downloader downloader =
factory.createDownloader(
DownloadAction.createDownloadAction(
new DownloadAction(
"id",
DownloadAction.TYPE_PROGRESSIVE,
Uri.parse("https://www.test.com/download"),
/* keys= */ Collections.emptyList(),
/* streamKeys= */ Collections.emptyList(),
/* customCacheKey= */ null,
/* data= */ null));
assertThat(downloader).isInstanceOf(ProgressiveDownloader.class);

View File

@ -16,24 +16,11 @@
package com.google.android.exoplayer2.offline;
import static com.google.android.exoplayer2.offline.DownloadAction.TYPE_DASH;
import static com.google.android.exoplayer2.offline.DownloadAction.TYPE_HLS;
import static com.google.android.exoplayer2.offline.DownloadAction.TYPE_PROGRESSIVE;
import static com.google.android.exoplayer2.offline.DownloadAction.TYPE_SS;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import android.net.Uri;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import android.os.Parcel;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.offline.DownloadAction.UnsupportedActionException;
import com.google.android.exoplayer2.testutil.TestUtil;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -47,41 +34,34 @@ public class DownloadActionTest {
private Uri uri1;
private Uri uri2;
private byte[] data;
@Before
public void setUp() {
uri1 = Uri.parse("http://test/1.uri");
uri2 = Uri.parse("http://test/2.uri");
data = TestUtil.buildTestData(32);
}
@Test
public void testSameUri_hasSameId() {
DownloadAction action1 = createAction(uri1);
DownloadAction action2 = createAction(uri1);
assertThat(action1.id.equals(action2.id)).isTrue();
}
public void testParcelable() {
ArrayList<StreamKey> streamKeys = new ArrayList<>();
streamKeys.add(new StreamKey(1, 2, 3));
streamKeys.add(new StreamKey(4, 5, 6));
DownloadAction actionToParcel =
new DownloadAction(
"id",
"type",
Uri.parse("https://abc.def/ghi"),
streamKeys,
"key",
new byte[] {1, 2, 3, 4, 5});
Parcel parcel = Parcel.obtain();
actionToParcel.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
@Test
public void testSameUriDifferentAction_hasSameId() {
DownloadAction action1 = createAction(uri1);
DownloadAction action2 = createAction(uri1);
assertThat(action1.id.equals(action2.id)).isTrue();
}
DownloadAction actionFromParcel = DownloadAction.CREATOR.createFromParcel(parcel);
assertThat(actionFromParcel).isEqualTo(actionToParcel);
@Test
public void testDifferentUri_IsNotSameMedia() {
DownloadAction action1 = createAction(uri1);
DownloadAction action2 = createAction(uri2);
assertThat(action1.id.equals(action2.id)).isFalse();
}
@Test
public void testSameCacheKeyDifferentUri_hasSameId() {
DownloadAction action1 = createAction(uri1, "key123");
DownloadAction action2 = createAction(uri2, "key123");
assertThat(action1.id.equals(action2.id)).isTrue();
parcel.recycle();
}
@SuppressWarnings("EqualsWithItself")
@ -119,128 +99,6 @@ public class DownloadActionTest {
assertEqual(action16, action17);
}
@Test
public void testSerializerWriteRead() throws Exception {
assertStreamSerializationRoundTrip(
DownloadAction.createDownloadAction(
"id",
TYPE_DASH,
uri1,
toList(new StreamKey(0, 1, 2), new StreamKey(3, 4, 5)),
"key123",
data));
assertStreamSerializationRoundTrip(createAction(uri1, "key123"));
}
@Test
public void testArraySerialization() throws Exception {
assertArraySerializationRoundTrip(
DownloadAction.createDownloadAction(
"id",
TYPE_DASH,
uri1,
toList(new StreamKey(0, 1, 2), new StreamKey(3, 4, 5)),
"key123",
data));
assertArraySerializationRoundTrip(createAction(uri1, "key123"));
}
@Test
public void testSerializerProgressiveVersion0() throws Exception {
String customCacheKey = "key123";
String expectedId = DownloadAction.generateId(uri1, customCacheKey);
assertDeserialization(
"progressive-download-v0",
DownloadAction.createDownloadAction(
expectedId, TYPE_PROGRESSIVE, uri1, Collections.emptyList(), customCacheKey, data));
assertUnsupportedAction("progressive-remove-v0");
}
@Test
public void testSerializerDashVersion0() throws Exception {
assertDeserialization(
"dash-download-v0",
DownloadAction.createDownloadAction(
uri1.toString(),
TYPE_DASH,
uri1,
toList(new StreamKey(0, 1, 2), new StreamKey(3, 4, 5)),
/* customCacheKey= */ null,
data));
assertUnsupportedAction("dash-remove-v0");
}
@Test
public void testSerializerHlsVersion0() throws Exception {
assertDeserialization(
"hls-download-v0",
DownloadAction.createDownloadAction(
uri1.toString(),
TYPE_HLS,
uri1,
toList(new StreamKey(0, 1), new StreamKey(2, 3)),
/* customCacheKey= */ null,
data));
assertUnsupportedAction("hls-remove-v0");
}
@Test
public void testSerializerHlsVersion1() throws Exception {
assertDeserialization(
"hls-download-v1",
DownloadAction.createDownloadAction(
uri1.toString(),
TYPE_HLS,
uri1,
toList(new StreamKey(0, 1, 2), new StreamKey(3, 4, 5)),
/* customCacheKey= */ null,
data));
assertUnsupportedAction("hls-remove-v1");
}
@Test
public void testSerializerSsVersion0() throws Exception {
assertDeserialization(
"ss-download-v0",
DownloadAction.createDownloadAction(
uri1.toString(),
TYPE_SS,
uri1,
toList(new StreamKey(0, 1), new StreamKey(2, 3)),
/* customCacheKey= */ null,
data));
assertUnsupportedAction("ss-remove-v0");
}
@Test
public void testSerializerSsVersion1() throws Exception {
assertDeserialization(
"ss-download-v1",
DownloadAction.createDownloadAction(
uri1.toString(),
TYPE_SS,
uri1,
toList(new StreamKey(0, 1, 2), new StreamKey(3, 4, 5)),
/* customCacheKey= */ null,
data));
assertUnsupportedAction("ss-remove-v1");
}
private DownloadAction createAction(Uri uri, StreamKey... keys) {
return DownloadAction.createDownloadAction(
uri.toString(), TYPE_DASH, uri, toList(keys), /* customCacheKey= */ null, data);
}
private DownloadAction createAction(Uri uri, @Nullable String customCacheKey) {
return DownloadAction.createDownloadAction(
"id",
DownloadAction.TYPE_DASH,
uri,
Collections.emptyList(),
customCacheKey,
/* data= */ null);
}
private static void assertNotEqual(DownloadAction action1, DownloadAction action2) {
assertThat(action1).isNotEqualTo(action2);
assertThat(action2).isNotEqualTo(action1);
@ -251,42 +109,9 @@ public class DownloadActionTest {
assertThat(action2).isEqualTo(action1);
}
private static void assertStreamSerializationRoundTrip(DownloadAction action) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream output = new DataOutputStream(out);
action.serializeToStream(output);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
DataInputStream input = new DataInputStream(in);
DownloadAction deserializedAction = DownloadAction.deserializeFromStream(input);
assertEqual(action, deserializedAction);
}
private static void assertArraySerializationRoundTrip(DownloadAction action) throws IOException {
assertEqual(action, DownloadAction.fromByteArray(action.toByteArray()));
}
private static void assertDeserialization(String fileName, DownloadAction expectedAction)
throws IOException {
InputStream input =
TestUtil.getInputStream(
ApplicationProvider.getApplicationContext(), "download-actions/" + fileName);
DownloadAction deserializedAction = DownloadAction.deserializeFromStream(input);
assertEqual(deserializedAction, expectedAction);
}
private static void assertUnsupportedAction(String fileName) throws IOException {
InputStream input =
TestUtil.getInputStream(
ApplicationProvider.getApplicationContext(), "download-actions/" + fileName);
try {
DownloadAction.deserializeFromStream(input);
fail();
} catch (UnsupportedActionException e) {
// Expected exception.
}
private static DownloadAction createAction(Uri uri, StreamKey... keys) {
return new DownloadAction(
uri.toString(), TYPE_DASH, uri, toList(keys), /* customCacheKey= */ null, /* data= */ null);
}
private static List<StreamKey> toList(StreamKey... keys) {

View File

@ -59,7 +59,7 @@ public class DownloadIndexUtilTest {
public void addAction_nonExistingDownloadState_createsNewDownloadState() throws IOException {
byte[] data = new byte[] {1, 2, 3, 4};
DownloadAction action =
DownloadAction.createDownloadAction(
new DownloadAction(
"id",
TYPE_DASH,
Uri.parse("https://www.test.com/download"),
@ -69,7 +69,7 @@ public class DownloadIndexUtilTest {
/* customCacheKey= */ "key123",
data);
DownloadIndexUtil.addAction(downloadIndex, action.id, action);
DownloadIndexUtil.mergeAction(action, downloadIndex);
assertDownloadIndexContainsAction(action, DownloadState.STATE_QUEUED);
}
@ -81,7 +81,7 @@ public class DownloadIndexUtilTest {
StreamKey streamKey2 =
new StreamKey(/* periodIndex= */ 0, /* groupIndex= */ 1, /* trackIndex= */ 2);
DownloadAction action1 =
DownloadAction.createDownloadAction(
new DownloadAction(
"id",
TYPE_DASH,
Uri.parse("https://www.test.com/download1"),
@ -89,16 +89,16 @@ public class DownloadIndexUtilTest {
/* customCacheKey= */ "key123",
new byte[] {1, 2, 3, 4});
DownloadAction action2 =
DownloadAction.createDownloadAction(
new DownloadAction(
"id",
TYPE_DASH,
Uri.parse("https://www.test.com/download2"),
asList(streamKey2),
/* customCacheKey= */ "key123",
new byte[] {5, 4, 3, 2, 1});
DownloadIndexUtil.addAction(downloadIndex, action1.id, action1);
DownloadIndexUtil.mergeAction(action1, downloadIndex);
DownloadIndexUtil.addAction(downloadIndex, action2.id, action2);
DownloadIndexUtil.mergeAction(action2, downloadIndex);
DownloadState downloadState = downloadIndex.getDownloadState(action2.id);
assertThat(downloadState).isNotNull();
@ -126,7 +126,7 @@ public class DownloadIndexUtilTest {
StreamKey expectedStreamKey2 =
new StreamKey(/* periodIndex= */ 0, /* groupIndex= */ 1, /* trackIndex= */ 2);
DownloadAction expectedAction1 =
DownloadAction.createDownloadAction(
new DownloadAction(
"key123",
TYPE_DASH,
Uri.parse("https://www.test.com/download1"),
@ -134,7 +134,7 @@ public class DownloadIndexUtilTest {
/* customCacheKey= */ "key123",
new byte[] {1, 2, 3, 4});
DownloadAction expectedAction2 =
DownloadAction.createDownloadAction(
new DownloadAction(
"key234",
TYPE_DASH,
Uri.parse("https://www.test.com/download2"),
@ -143,7 +143,7 @@ public class DownloadIndexUtilTest {
new byte[] {5, 4, 3, 2, 1});
ActionFile actionFile = new ActionFile(tempFile);
DownloadIndexUtil.upgradeActionFile(actionFile, downloadIndex, /* downloadIdProvider= */ null);
DownloadIndexUtil.mergeActionFile(actionFile, /* downloadIdProvider= */ null, downloadIndex);
assertDownloadIndexContainsAction(expectedAction1, DownloadState.STATE_QUEUED);
assertDownloadIndexContainsAction(expectedAction2, DownloadState.STATE_QUEUED);
}

View File

@ -507,7 +507,7 @@ public class DownloadManagerTest {
private DownloadRunner postDownloadAction(StreamKey... keys) {
DownloadAction downloadAction =
DownloadAction.createDownloadAction(
new DownloadAction(
id,
DownloadAction.TYPE_PROGRESSIVE,
uri,

View File

@ -153,8 +153,7 @@ class DownloadStateBuilder {
}
public DownloadState build() {
DownloadAction action =
DownloadAction.createDownloadAction(id, type, uri, streamKeys, cacheKey, customMetadata);
DownloadAction action = new DownloadAction(id, type, uri, streamKeys, cacheKey, customMetadata);
return new DownloadState(
action,
state,

View File

@ -99,7 +99,7 @@ public class DownloadStateTest {
@Test
public void mergeAction_actionHaveDifferentData_downloadStateDataIsUpdated() {
DownloadAction downloadAction =
DownloadAction.createDownloadAction(
new DownloadAction(
"id",
DownloadAction.TYPE_DASH,
testUri,
@ -246,7 +246,7 @@ public class DownloadStateTest {
private void doTestMergeActionReturnsMergedKeys(
StreamKey[] keys1, StreamKey[] keys2, StreamKey[] expectedKeys) {
DownloadAction downloadAction =
DownloadAction.createDownloadAction(
new DownloadAction(
"id",
DownloadAction.TYPE_DASH,
testUri,
@ -286,7 +286,7 @@ public class DownloadStateTest {
}
private DownloadAction createDownloadAction() {
return DownloadAction.createDownloadAction(
return new DownloadAction(
"id",
DownloadAction.TYPE_DASH,
testUri,

View File

@ -0,0 +1,41 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.offline;
import static com.google.common.truth.Truth.assertThat;
import android.os.Parcel;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Unit tests for {@link StreamKey}. */
@RunWith(AndroidJUnit4.class)
public class StreamKeyTest {
@Test
public void testParcelable() {
StreamKey streamKeyToParcel = new StreamKey(1, 2, 3);
Parcel parcel = Parcel.obtain();
streamKeyToParcel.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
StreamKey streamKeyFromParcel = StreamKey.CREATOR.createFromParcel(parcel);
assertThat(streamKeyFromParcel).isEqualTo(streamKeyToParcel);
parcel.recycle();
}
}

View File

@ -84,7 +84,7 @@ public class DashDownloaderTest {
Downloader downloader =
factory.createDownloader(
DownloadAction.createDownloadAction(
new DownloadAction(
"id",
DownloadAction.TYPE_DASH,
Uri.parse("https://www.test.com/download"),

View File

@ -232,7 +232,7 @@ public class DownloadManagerDashTest {
ArrayList<StreamKey> keysList = new ArrayList<>();
Collections.addAll(keysList, keys);
DownloadAction action =
DownloadAction.createDownloadAction(
new DownloadAction(
TEST_ID,
DownloadAction.TYPE_DASH,
TEST_MPD_URI,

View File

@ -208,7 +208,7 @@ public class DownloadServiceDashTest {
ArrayList<StreamKey> keysList = new ArrayList<>();
Collections.addAll(keysList, keys);
DownloadAction action =
DownloadAction.createDownloadAction(
new DownloadAction(
TEST_ID,
DownloadAction.TYPE_DASH,
TEST_MPD_URI,

View File

@ -101,7 +101,7 @@ public class HlsDownloaderTest {
Downloader downloader =
factory.createDownloader(
DownloadAction.createDownloadAction(
new DownloadAction(
"id",
DownloadAction.TYPE_HLS,
Uri.parse("https://www.test.com/download"),

View File

@ -44,7 +44,7 @@ public final class SsDownloaderTest {
Downloader downloader =
factory.createDownloader(
DownloadAction.createDownloadAction(
new DownloadAction(
"id",
DownloadAction.TYPE_SS,
Uri.parse("https://www.test.com/download"),