diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoApplication.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoApplication.java index 380aa98bde..705ae2eeef 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoApplication.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoApplication.java @@ -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); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/ActionFile.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/ActionFile.java index 1203758e71..d715583921 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/ActionFile.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/ActionFile.java @@ -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 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; + } + + // Serialized version 0 progressive actions did not contain keys. + boolean isLegacyProgressive = version == 0 && DownloadAction.TYPE_PROGRESSIVE.equals(type); + List 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); } - /** Delete the action file and its backup. */ - public void delete() { - atomicFile.delete(); + 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(); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DefaultDownloadIndex.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DefaultDownloadIndex.java index 32e7c3a66f..16e5c37b3d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DefaultDownloadIndex.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DefaultDownloadIndex.java @@ -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)), diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadAction.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadAction.java index c0e1ab66ab..6e11ec9042 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadAction.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadAction.java @@ -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 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 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 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 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 CREATOR = + new Parcelable.Creator() { - Uri uri = Uri.parse(input.readUTF()); - boolean isRemoveAction = input.readBoolean(); + @Override + public DownloadAction createFromParcel(Parcel in) { + return new DownloadAction(in); + } - int dataLength = input.readInt(); - byte[] data; - if (dataLength != 0) { - data = new byte[dataLength]; - input.readFully(data); - } else { - data = null; - } - - // Serialized version 0 progressive actions did not contain keys. - boolean isLegacyProgressive = version == 0 && TYPE_PROGRESSIVE.equals(type); - List 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]; + } + }; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java index 72893f17b0..39dd944552 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java @@ -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 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. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadIndexUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadIndexUtil.java index 4cbf35f523..02e95eaf4d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadIndexUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadIndexUtil.java @@ -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}. * - *

This method shouldn't be called while {@link DownloadIndex} is used by {@link + *

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}. * - *

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 { 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 f10793c5f4..5370ba4182 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 @@ -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: * *

    - *
  • {@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()}. + *
  • {@link #KEY_DOWNLOAD_ACTION} - A {@link DownloadAction} defining the download to be + * added. *
  • {@link #KEY_FOREGROUND} - See {@link #KEY_FOREGROUND}. *
*/ @@ -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,19 +388,11 @@ 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); - } + downloadManager.addDownload(downloadAction); } break; case ACTION_START: diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/StreamKey.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/StreamKey.java index 4d1de5f32d..977be9a198 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/StreamKey.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/StreamKey.java @@ -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 { +public final class StreamKey implements Comparable, Parcelable { /** The period index. */ public final int periodIndex; @@ -53,6 +55,12 @@ public final class StreamKey implements Comparable { 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 { } 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 CREATOR = + new Parcelable.Creator() { + + @Override + public StreamKey createFromParcel(Parcel in) { + return new StreamKey(in); + } + + @Override + public StreamKey[] newArray(int size) { + return new StreamKey[size]; + } + }; } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/offline/ActionFileTest.java b/library/core/src/test/java/com/google/android/exoplayer2/offline/ActionFileTest.java index 0d38337dfb..3273fef71f 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/offline/ActionFileTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/offline/ActionFileTest.java @@ -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); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/offline/DefaultDownloaderFactoryTest.java b/library/core/src/test/java/com/google/android/exoplayer2/offline/DefaultDownloaderFactoryTest.java index c13e69aa01..08a0edd5d6 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/offline/DefaultDownloaderFactoryTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/offline/DefaultDownloaderFactoryTest.java @@ -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); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadActionTest.java b/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadActionTest.java index 2bc6d0809e..0dd00307b9 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadActionTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadActionTest.java @@ -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 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 toList(StreamKey... keys) { diff --git a/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadIndexUtilTest.java b/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadIndexUtilTest.java index d321ba00d1..619d36f5ab 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadIndexUtilTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadIndexUtilTest.java @@ -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); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java index e25b2913e3..905b3a25fa 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java @@ -507,7 +507,7 @@ public class DownloadManagerTest { private DownloadRunner postDownloadAction(StreamKey... keys) { DownloadAction downloadAction = - DownloadAction.createDownloadAction( + new DownloadAction( id, DownloadAction.TYPE_PROGRESSIVE, uri, diff --git a/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadStateBuilder.java b/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadStateBuilder.java index 6a41494a6f..7b1c8d7423 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadStateBuilder.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadStateBuilder.java @@ -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, diff --git a/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadStateTest.java b/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadStateTest.java index 4028677bb2..2f07fa0f6c 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadStateTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadStateTest.java @@ -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, diff --git a/library/core/src/test/java/com/google/android/exoplayer2/offline/StreamKeyTest.java b/library/core/src/test/java/com/google/android/exoplayer2/offline/StreamKeyTest.java new file mode 100644 index 0000000000..a18c278913 --- /dev/null +++ b/library/core/src/test/java/com/google/android/exoplayer2/offline/StreamKeyTest.java @@ -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(); + } +} diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java index dc112c631d..2b11b0affd 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java @@ -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"), diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadManagerDashTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadManagerDashTest.java index 201b41c9b0..35ca34999f 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadManagerDashTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadManagerDashTest.java @@ -232,7 +232,7 @@ public class DownloadManagerDashTest { ArrayList keysList = new ArrayList<>(); Collections.addAll(keysList, keys); DownloadAction action = - DownloadAction.createDownloadAction( + new DownloadAction( TEST_ID, DownloadAction.TYPE_DASH, TEST_MPD_URI, diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadServiceDashTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadServiceDashTest.java index c06e6aee20..50d044ff10 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadServiceDashTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadServiceDashTest.java @@ -208,7 +208,7 @@ public class DownloadServiceDashTest { ArrayList keysList = new ArrayList<>(); Collections.addAll(keysList, keys); DownloadAction action = - DownloadAction.createDownloadAction( + new DownloadAction( TEST_ID, DownloadAction.TYPE_DASH, TEST_MPD_URI, diff --git a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java index e591146ca3..b5b4e47385 100644 --- a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java +++ b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java @@ -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"), diff --git a/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloaderTest.java b/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloaderTest.java index 510fa522f0..b130f02bc3 100644 --- a/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloaderTest.java +++ b/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloaderTest.java @@ -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"),