Add DownloadIndex and DefaultDownloadIndex
DownloadIndex will be used to store and query DownloadStates. PiperOrigin-RevId: 228673766
This commit is contained in:
parent
637b52ae0e
commit
92bec21c03
@ -0,0 +1,528 @@
|
|||||||
|
/*
|
||||||
|
* 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 android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.DatabaseUtils;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.database.sqlite.SQLiteException;
|
||||||
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.IntDef;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.annotation.VisibleForTesting;
|
||||||
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link DownloadIndex} which uses SQLite to persist {@link DownloadState}s.
|
||||||
|
*
|
||||||
|
* <p class="caution">Database access may take a long time, do not call methods of this class from
|
||||||
|
* the application main thread.
|
||||||
|
*/
|
||||||
|
public final class DefaultDownloadIndex implements DownloadIndex {
|
||||||
|
|
||||||
|
/** Provides {@link SQLiteDatabase} instances. */
|
||||||
|
public interface DatabaseProvider {
|
||||||
|
/** Closes any open database object. */
|
||||||
|
void close();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and/or opens a database that will be used for reading and writing.
|
||||||
|
*
|
||||||
|
* <p>Once opened successfully, the database is cached, so you can call this method every time
|
||||||
|
* you need to write to the database. (Make sure to call {@link #close} when you no longer need
|
||||||
|
* the database.) Errors such as bad permissions or a full disk may cause this method to fail,
|
||||||
|
* but future attempts may succeed if the problem is fixed.
|
||||||
|
*
|
||||||
|
* @throws SQLiteException If the database cannot be opened for writing.
|
||||||
|
* @return A read/write database object valid until {@link #close} is called.
|
||||||
|
*/
|
||||||
|
SQLiteDatabase getWritableDatabase();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and/or opens a database. This will be the same object returned by {@link
|
||||||
|
* #getWritableDatabase} unless some problem, such as a full disk, requires the database to be
|
||||||
|
* opened read-only. In that case, a read-only database object will be returned. If the problem
|
||||||
|
* is fixed, a future call to {@link #getWritableDatabase} may succeed, in which case the
|
||||||
|
* read-only database object will be closed and the read/write object will be returned in the
|
||||||
|
* future.
|
||||||
|
*
|
||||||
|
* <p>Once opened successfully, the database should be cached. When the database is no longer
|
||||||
|
* needed, {@link #close} will be called.
|
||||||
|
*
|
||||||
|
* @throws SQLiteException If the database cannot be opened.
|
||||||
|
* @return A database object valid until {@link #getWritableDatabase} or {@link #close} is
|
||||||
|
* called.
|
||||||
|
*/
|
||||||
|
SQLiteDatabase getReadableDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String DATABASE_NAME = "exoplayer_internal.db";
|
||||||
|
|
||||||
|
private final DatabaseProvider databaseProvider;
|
||||||
|
@Nullable private DownloadStateTable downloadStateTable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a DefaultDownloadIndex which stores the {@link DownloadState}s on a SQLite database.
|
||||||
|
*
|
||||||
|
* @param context A Context.
|
||||||
|
*/
|
||||||
|
public DefaultDownloadIndex(Context context) {
|
||||||
|
this(new DefaultDatabaseProvider(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a DefaultDownloadIndex which stores the {@link DownloadState}s on a SQLite database
|
||||||
|
* provided by {@code databaseProvider}.
|
||||||
|
*
|
||||||
|
* @param databaseProvider A DatabaseProvider which provides the database which will be used to
|
||||||
|
* store DownloadStatus table.
|
||||||
|
*/
|
||||||
|
public DefaultDownloadIndex(DatabaseProvider databaseProvider) {
|
||||||
|
this.databaseProvider = databaseProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void release() {
|
||||||
|
databaseProvider.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public DownloadState getDownloadState(String id) {
|
||||||
|
return getDownloadStateTable().get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DownloadStateCursor getDownloadStates(@DownloadState.State int... states) {
|
||||||
|
return getDownloadStateTable().get(states);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void putDownloadState(DownloadState downloadState) {
|
||||||
|
getDownloadStateTable().replace(downloadState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeDownloadState(String id) {
|
||||||
|
getDownloadStateTable().delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DownloadStateTable getDownloadStateTable() {
|
||||||
|
if (downloadStateTable == null) {
|
||||||
|
downloadStateTable = new DownloadStateTable(databaseProvider);
|
||||||
|
}
|
||||||
|
return downloadStateTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
/* package */ static boolean doesTableExist(DatabaseProvider databaseProvider, String tableName) {
|
||||||
|
SQLiteDatabase readableDatabase = databaseProvider.getReadableDatabase();
|
||||||
|
long count =
|
||||||
|
DatabaseUtils.queryNumEntries(
|
||||||
|
readableDatabase, "sqlite_master", "tbl_name = ?", new String[] {tableName});
|
||||||
|
return count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class DownloadStateCursorImpl implements DownloadStateCursor {
|
||||||
|
|
||||||
|
private final Cursor cursor;
|
||||||
|
|
||||||
|
private DownloadStateCursorImpl(Cursor cursor) {
|
||||||
|
this.cursor = cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DownloadState getDownloadState() {
|
||||||
|
return DownloadStateTable.getDownloadState(cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return cursor.getCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPosition() {
|
||||||
|
return cursor.getPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean moveToPosition(int position) {
|
||||||
|
return cursor.moveToPosition(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isClosed() {
|
||||||
|
return cursor.isClosed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
/* package */ static final class DownloadStateTable {
|
||||||
|
@VisibleForTesting /* package */ static final String TABLE_NAME = "ExoPlayerDownloadStates";
|
||||||
|
@VisibleForTesting /* package */ static final int TABLE_VERSION = 1;
|
||||||
|
|
||||||
|
private static final String COLUMN_ID = "id";
|
||||||
|
private static final String COLUMN_TYPE = "title";
|
||||||
|
private static final String COLUMN_URI = "subtitle";
|
||||||
|
private static final String COLUMN_CACHE_KEY = "cache_key";
|
||||||
|
private static final String COLUMN_STATE = "state";
|
||||||
|
private static final String COLUMN_DOWNLOAD_PERCENTAGE = "download_percentage";
|
||||||
|
private static final String COLUMN_DOWNLOADED_BYTES = "downloaded_bytes";
|
||||||
|
private static final String COLUMN_TOTAL_BYTES = "total_bytes";
|
||||||
|
private static final String COLUMN_FAILURE_REASON = "failure_reason";
|
||||||
|
private static final String COLUMN_STOP_FLAGS = "stop_flags";
|
||||||
|
private static final String COLUMN_START_TIME_MS = "start_time_ms";
|
||||||
|
private static final String COLUMN_UPDATE_TIME_MS = "update_time_ms";
|
||||||
|
private static final String COLUMN_STREAM_KEYS = "stream_keys";
|
||||||
|
private static final String COLUMN_CUSTOM_METADATA = "custom_metadata";
|
||||||
|
|
||||||
|
private static final int COLUMN_INDEX_ID = 0;
|
||||||
|
private static final int COLUMN_INDEX_TYPE = 1;
|
||||||
|
private static final int COLUMN_INDEX_URI = 2;
|
||||||
|
private static final int COLUMN_INDEX_CACHE_KEY = 3;
|
||||||
|
private static final int COLUMN_INDEX_STATE = 4;
|
||||||
|
private static final int COLUMN_INDEX_DOWNLOAD_PERCENTAGE = 5;
|
||||||
|
private static final int COLUMN_INDEX_DOWNLOADED_BYTES = 6;
|
||||||
|
private static final int COLUMN_INDEX_TOTAL_BYTES = 7;
|
||||||
|
private static final int COLUMN_INDEX_FAILURE_REASON = 8;
|
||||||
|
private static final int COLUMN_INDEX_STOP_FLAGS = 9;
|
||||||
|
private static final int COLUMN_INDEX_START_TIME_MS = 10;
|
||||||
|
private static final int COLUMN_INDEX_UPDATE_TIME_MS = 11;
|
||||||
|
private static final int COLUMN_INDEX_STREAM_KEYS = 12;
|
||||||
|
private static final int COLUMN_INDEX_CUSTOM_METADATA = 13;
|
||||||
|
|
||||||
|
private static final String COLUMN_SELECTION_ID = COLUMN_ID + " = ?";
|
||||||
|
|
||||||
|
private static final String[] COLUMNS =
|
||||||
|
new String[] {
|
||||||
|
COLUMN_ID,
|
||||||
|
COLUMN_TYPE,
|
||||||
|
COLUMN_URI,
|
||||||
|
COLUMN_CACHE_KEY,
|
||||||
|
COLUMN_STATE,
|
||||||
|
COLUMN_DOWNLOAD_PERCENTAGE,
|
||||||
|
COLUMN_DOWNLOADED_BYTES,
|
||||||
|
COLUMN_TOTAL_BYTES,
|
||||||
|
COLUMN_FAILURE_REASON,
|
||||||
|
COLUMN_STOP_FLAGS,
|
||||||
|
COLUMN_START_TIME_MS,
|
||||||
|
COLUMN_UPDATE_TIME_MS,
|
||||||
|
COLUMN_STREAM_KEYS,
|
||||||
|
COLUMN_CUSTOM_METADATA
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final String SQL_DROP_TABLE = "DROP TABLE IF EXISTS " + TABLE_NAME;
|
||||||
|
private static final String SQL_CREATE_TABLE =
|
||||||
|
"CREATE TABLE IF NOT EXISTS "
|
||||||
|
+ TABLE_NAME
|
||||||
|
+ " ("
|
||||||
|
+ COLUMN_ID
|
||||||
|
+ " TEXT PRIMARY KEY NOT NULL,"
|
||||||
|
+ COLUMN_TYPE
|
||||||
|
+ " TEXT NOT NULL,"
|
||||||
|
+ COLUMN_URI
|
||||||
|
+ " TEXT NOT NULL,"
|
||||||
|
+ COLUMN_CACHE_KEY
|
||||||
|
+ " TEXT,"
|
||||||
|
+ COLUMN_STATE
|
||||||
|
+ " INTEGER NOT NULL,"
|
||||||
|
+ COLUMN_DOWNLOAD_PERCENTAGE
|
||||||
|
+ " REAL NOT NULL,"
|
||||||
|
+ COLUMN_DOWNLOADED_BYTES
|
||||||
|
+ " INTEGER NOT NULL,"
|
||||||
|
+ COLUMN_TOTAL_BYTES
|
||||||
|
+ " INTEGER NOT NULL,"
|
||||||
|
+ COLUMN_FAILURE_REASON
|
||||||
|
+ " INTEGER NOT NULL,"
|
||||||
|
+ COLUMN_STOP_FLAGS
|
||||||
|
+ " INTEGER NOT NULL,"
|
||||||
|
+ COLUMN_START_TIME_MS
|
||||||
|
+ " INTEGER NOT NULL,"
|
||||||
|
+ COLUMN_UPDATE_TIME_MS
|
||||||
|
+ " INTEGER NOT NULL,"
|
||||||
|
+ COLUMN_STREAM_KEYS
|
||||||
|
+ " TEXT NOT NULL,"
|
||||||
|
+ COLUMN_CUSTOM_METADATA
|
||||||
|
+ " BLOB NOT NULL)";
|
||||||
|
|
||||||
|
private final DatabaseProvider databaseProvider;
|
||||||
|
|
||||||
|
public DownloadStateTable(DatabaseProvider databaseProvider) {
|
||||||
|
this.databaseProvider = databaseProvider;
|
||||||
|
VersionTable versionTable = new VersionTable(databaseProvider);
|
||||||
|
int version = versionTable.getVersion(VersionTable.FEATURE_OFFLINE);
|
||||||
|
if (!doesTableExist(databaseProvider, TABLE_NAME)
|
||||||
|
|| version == 0
|
||||||
|
|| version > TABLE_VERSION) {
|
||||||
|
SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase();
|
||||||
|
writableDatabase.beginTransaction();
|
||||||
|
try {
|
||||||
|
writableDatabase.execSQL(SQL_DROP_TABLE);
|
||||||
|
writableDatabase.execSQL(SQL_CREATE_TABLE);
|
||||||
|
versionTable.setVersion(VersionTable.FEATURE_OFFLINE, TABLE_VERSION);
|
||||||
|
writableDatabase.setTransactionSuccessful();
|
||||||
|
} finally {
|
||||||
|
writableDatabase.endTransaction();
|
||||||
|
}
|
||||||
|
} else if (version < TABLE_VERSION) {
|
||||||
|
// There is no previous version currently.
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void replace(DownloadState downloadState) {
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(COLUMN_ID, downloadState.id);
|
||||||
|
values.put(COLUMN_TYPE, downloadState.type);
|
||||||
|
values.put(COLUMN_URI, downloadState.uri.toString());
|
||||||
|
values.put(COLUMN_CACHE_KEY, downloadState.cacheKey);
|
||||||
|
values.put(COLUMN_STATE, downloadState.state);
|
||||||
|
values.put(COLUMN_DOWNLOAD_PERCENTAGE, downloadState.downloadPercentage);
|
||||||
|
values.put(COLUMN_DOWNLOADED_BYTES, downloadState.downloadedBytes);
|
||||||
|
values.put(COLUMN_TOTAL_BYTES, downloadState.totalBytes);
|
||||||
|
values.put(COLUMN_FAILURE_REASON, downloadState.failureReason);
|
||||||
|
values.put(COLUMN_STOP_FLAGS, downloadState.stopFlags);
|
||||||
|
values.put(COLUMN_START_TIME_MS, downloadState.startTimeMs);
|
||||||
|
values.put(COLUMN_UPDATE_TIME_MS, downloadState.updateTimeMs);
|
||||||
|
values.put(COLUMN_STREAM_KEYS, encodeStreamKeys(downloadState.streamKeys));
|
||||||
|
values.put(COLUMN_CUSTOM_METADATA, downloadState.customMetadata);
|
||||||
|
SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase();
|
||||||
|
writableDatabase.replace(TABLE_NAME, /* nullColumnHack= */ null, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public DownloadState get(String id) {
|
||||||
|
String[] selectionArgs = {id};
|
||||||
|
try (Cursor cursor = query(COLUMN_SELECTION_ID, selectionArgs)) {
|
||||||
|
if (cursor.getCount() == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
cursor.moveToNext();
|
||||||
|
DownloadState downloadState = getDownloadState(cursor);
|
||||||
|
Assertions.checkState(id.equals(downloadState.id));
|
||||||
|
return downloadState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownloadStateCursor get(@DownloadState.State int... states) {
|
||||||
|
String selection = null;
|
||||||
|
if (states.length > 0) {
|
||||||
|
StringBuilder selectionBuilder = new StringBuilder();
|
||||||
|
selectionBuilder.append(COLUMN_STATE).append(" IN (");
|
||||||
|
for (int i = 0; i < states.length; i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
selectionBuilder.append(',');
|
||||||
|
}
|
||||||
|
selectionBuilder.append(states[i]);
|
||||||
|
}
|
||||||
|
selectionBuilder.append(')');
|
||||||
|
selection = selectionBuilder.toString();
|
||||||
|
}
|
||||||
|
Cursor cursor = query(selection, /* selectionArgs= */ null);
|
||||||
|
return new DownloadStateCursorImpl(cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(String id) {
|
||||||
|
String[] selectionArgs = {id};
|
||||||
|
databaseProvider.getWritableDatabase().delete(TABLE_NAME, COLUMN_SELECTION_ID, selectionArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cursor query(@Nullable String selection, @Nullable String[] selectionArgs) {
|
||||||
|
String sortOrder = COLUMN_START_TIME_MS + " ASC";
|
||||||
|
return databaseProvider
|
||||||
|
.getReadableDatabase()
|
||||||
|
.query(
|
||||||
|
TABLE_NAME,
|
||||||
|
COLUMNS,
|
||||||
|
selection,
|
||||||
|
selectionArgs,
|
||||||
|
/* groupBy= */ null,
|
||||||
|
/* having= */ null,
|
||||||
|
sortOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DownloadState getDownloadState(Cursor cursor) {
|
||||||
|
return new DownloadState(
|
||||||
|
cursor.getString(COLUMN_INDEX_ID),
|
||||||
|
cursor.getString(COLUMN_INDEX_TYPE),
|
||||||
|
Uri.parse(cursor.getString(COLUMN_INDEX_URI)),
|
||||||
|
cursor.getString(COLUMN_INDEX_CACHE_KEY),
|
||||||
|
cursor.getInt(COLUMN_INDEX_STATE),
|
||||||
|
cursor.getFloat(COLUMN_INDEX_DOWNLOAD_PERCENTAGE),
|
||||||
|
cursor.getLong(COLUMN_INDEX_DOWNLOADED_BYTES),
|
||||||
|
cursor.getLong(COLUMN_INDEX_TOTAL_BYTES),
|
||||||
|
cursor.getInt(COLUMN_INDEX_FAILURE_REASON),
|
||||||
|
cursor.getInt(COLUMN_INDEX_STOP_FLAGS),
|
||||||
|
cursor.getLong(COLUMN_INDEX_START_TIME_MS),
|
||||||
|
cursor.getLong(COLUMN_INDEX_UPDATE_TIME_MS),
|
||||||
|
decodeStreamKeys(cursor.getString(COLUMN_INDEX_STREAM_KEYS)),
|
||||||
|
cursor.getBlob(COLUMN_INDEX_CUSTOM_METADATA));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String encodeStreamKeys(StreamKey[] streamKeys) {
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
for (StreamKey streamKey : streamKeys) {
|
||||||
|
stringBuilder
|
||||||
|
.append(streamKey.periodIndex)
|
||||||
|
.append('.')
|
||||||
|
.append(streamKey.groupIndex)
|
||||||
|
.append('.')
|
||||||
|
.append(streamKey.trackIndex)
|
||||||
|
.append(',');
|
||||||
|
}
|
||||||
|
if (stringBuilder.length() > 0) {
|
||||||
|
stringBuilder.setLength(stringBuilder.length() - 1);
|
||||||
|
}
|
||||||
|
return stringBuilder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StreamKey[] decodeStreamKeys(String encodedStreamKeys) {
|
||||||
|
if (encodedStreamKeys.isEmpty()) {
|
||||||
|
return new StreamKey[0];
|
||||||
|
}
|
||||||
|
String[] streamKeysStrings = Util.split(encodedStreamKeys, ",");
|
||||||
|
int streamKeysCount = streamKeysStrings.length;
|
||||||
|
StreamKey[] streamKeys = new StreamKey[streamKeysCount];
|
||||||
|
for (int i = 0; i < streamKeysCount; i++) {
|
||||||
|
String[] indices = Util.split(streamKeysStrings[i], "\\.");
|
||||||
|
Assertions.checkState(indices.length == 3);
|
||||||
|
streamKeys[i] =
|
||||||
|
new StreamKey(
|
||||||
|
Integer.parseInt(indices[0]),
|
||||||
|
Integer.parseInt(indices[1]),
|
||||||
|
Integer.parseInt(indices[2]));
|
||||||
|
}
|
||||||
|
return streamKeys;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
/* package */ static final class VersionTable {
|
||||||
|
private static final String TABLE_NAME = "ExoPlayerVersions";
|
||||||
|
|
||||||
|
private static final String COLUMN_FEATURE = "feature";
|
||||||
|
private static final String COLUMN_VERSION = "version";
|
||||||
|
|
||||||
|
private static final String SQL_CREATE_TABLE =
|
||||||
|
"CREATE TABLE IF NOT EXISTS "
|
||||||
|
+ TABLE_NAME
|
||||||
|
+ " ("
|
||||||
|
+ COLUMN_FEATURE
|
||||||
|
+ " INTEGER PRIMARY KEY NOT NULL,"
|
||||||
|
+ COLUMN_VERSION
|
||||||
|
+ " INTEGER NOT NULL)";
|
||||||
|
|
||||||
|
@Documented
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({FEATURE_OFFLINE, FEATURE_CACHE})
|
||||||
|
private @interface Feature {}
|
||||||
|
|
||||||
|
public static final int FEATURE_OFFLINE = 0;
|
||||||
|
public static final int FEATURE_CACHE = 1;
|
||||||
|
|
||||||
|
private final DatabaseProvider databaseProvider;
|
||||||
|
|
||||||
|
public VersionTable(DatabaseProvider databaseProvider) {
|
||||||
|
this.databaseProvider = databaseProvider;
|
||||||
|
if (!doesTableExist(databaseProvider, TABLE_NAME)) {
|
||||||
|
databaseProvider.getWritableDatabase().execSQL(SQL_CREATE_TABLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVersion(@Feature int feature, int version) {
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(COLUMN_FEATURE, feature);
|
||||||
|
values.put(COLUMN_VERSION, version);
|
||||||
|
SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase();
|
||||||
|
writableDatabase.replace(TABLE_NAME, /* nullColumnHack= */ null, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getVersion(@Feature int feature) {
|
||||||
|
String selection = COLUMN_FEATURE + " = ?";
|
||||||
|
String[] selectionArgs = {Integer.toString(feature)};
|
||||||
|
try (Cursor cursor =
|
||||||
|
databaseProvider
|
||||||
|
.getReadableDatabase()
|
||||||
|
.query(
|
||||||
|
TABLE_NAME,
|
||||||
|
new String[] {COLUMN_VERSION},
|
||||||
|
selection,
|
||||||
|
selectionArgs,
|
||||||
|
/* groupBy= */ null,
|
||||||
|
/* having= */ null,
|
||||||
|
/* orderBy= */ null)) {
|
||||||
|
if (cursor.getCount() == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
cursor.moveToNext();
|
||||||
|
return cursor.getInt(/* COLUMN_VERSION index */ 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class DefaultDatabaseProvider extends SQLiteOpenHelper
|
||||||
|
implements DatabaseProvider {
|
||||||
|
public DefaultDatabaseProvider(Context context) {
|
||||||
|
super(context, DATABASE_NAME, /* factory= */ null, /* version= */ 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(SQLiteDatabase db) {
|
||||||
|
// Table creation is done in DownloadStateTable constructor.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||||
|
// Upgrade is handled in DownloadStateTable constructor.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||||
|
// TODO: Wipe the database.
|
||||||
|
super.onDowngrade(db, oldVersion, newVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DatabaseProvider implementation.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void close() {
|
||||||
|
super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SQLiteDatabase getWritableDatabase() {
|
||||||
|
return super.getWritableDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SQLiteDatabase getReadableDatabase() {
|
||||||
|
return super.getReadableDatabase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -156,7 +156,7 @@ public final class DownloadAction {
|
|||||||
ArrayList<StreamKey> mutableKeys = new ArrayList<>(keys);
|
ArrayList<StreamKey> mutableKeys = new ArrayList<>(keys);
|
||||||
Collections.sort(mutableKeys);
|
Collections.sort(mutableKeys);
|
||||||
this.keys = Collections.unmodifiableList(mutableKeys);
|
this.keys = Collections.unmodifiableList(mutableKeys);
|
||||||
this.data = data != null ? data : Util.EMPTY_BYTE_ARRAY;
|
this.data = data != null ? Arrays.copyOf(data, data.length) : Util.EMPTY_BYTE_ARRAY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* 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 android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
/** Persists {@link DownloadState}s. */
|
||||||
|
interface DownloadIndex {
|
||||||
|
/** Releases the used resources. */
|
||||||
|
void release();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link DownloadState} with the given {@code id}, or null.
|
||||||
|
*
|
||||||
|
* @param id ID of a {@link DownloadState}.
|
||||||
|
* @return The {@link DownloadState} with the given {@code id}, or null if a download state with
|
||||||
|
* this id doesn't exist.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
DownloadState getDownloadState(String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link DownloadStateCursor} to {@link DownloadState}s with the given {@code states}.
|
||||||
|
*
|
||||||
|
* @param states Returns only the {@link DownloadState}s with this states. If empty, returns all.
|
||||||
|
* @return A cursor to {@link DownloadState}s with the given {@code states}.
|
||||||
|
*/
|
||||||
|
DownloadStateCursor getDownloadStates(@DownloadState.State int... states);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds or replaces a {@link DownloadState}.
|
||||||
|
*
|
||||||
|
* @param downloadState The {@link DownloadState} to be added.
|
||||||
|
*/
|
||||||
|
void putDownloadState(DownloadState downloadState);
|
||||||
|
|
||||||
|
/** Removes the {@link DownloadState} with the given {@code id}. */
|
||||||
|
void removeDownloadState(String id);
|
||||||
|
}
|
@ -13,7 +13,6 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.google.android.exoplayer2.offline;
|
package com.google.android.exoplayer2.offline;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/** Provides random read-write access to the result set returned by a database query. */
|
||||||
|
interface DownloadStateCursor {
|
||||||
|
|
||||||
|
/** Returns the DownloadState at the current position. */
|
||||||
|
DownloadState getDownloadState();
|
||||||
|
|
||||||
|
/** Returns the numbers of DownloadStates in the cursor. */
|
||||||
|
int getCount();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current position of the cursor in the DownloadState set. The value is zero-based.
|
||||||
|
* When the DownloadState set is first returned the cursor will be at positon -1, which is before
|
||||||
|
* the first DownloadState. After the last DownloadState is returned another call to next() will
|
||||||
|
* leave the cursor past the last entry, at a position of count().
|
||||||
|
*
|
||||||
|
* @return the current cursor position.
|
||||||
|
*/
|
||||||
|
int getPosition();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the cursor to an absolute position. The valid range of values is -1 <= position <=
|
||||||
|
* count.
|
||||||
|
*
|
||||||
|
* <p>This method will return true if the request destination was reachable, otherwise, it returns
|
||||||
|
* false.
|
||||||
|
*
|
||||||
|
* @param position the zero-based position to move to.
|
||||||
|
* @return whether the requested move fully succeeded.
|
||||||
|
*/
|
||||||
|
boolean moveToPosition(int position);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the cursor to the first DownloadState.
|
||||||
|
*
|
||||||
|
* <p>This method will return false if the cursor is empty.
|
||||||
|
*
|
||||||
|
* @return whether the move succeeded.
|
||||||
|
*/
|
||||||
|
default boolean moveToFirst() {
|
||||||
|
return moveToPosition(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the cursor to the last DownloadState.
|
||||||
|
*
|
||||||
|
* <p>This method will return false if the cursor is empty.
|
||||||
|
*
|
||||||
|
* @return whether the move succeeded.
|
||||||
|
*/
|
||||||
|
default boolean moveToLast() {
|
||||||
|
return moveToPosition(getCount() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the cursor to the next DownloadState.
|
||||||
|
*
|
||||||
|
* <p>This method will return false if the cursor is already past the last entry in the result
|
||||||
|
* set.
|
||||||
|
*
|
||||||
|
* @return whether the move succeeded.
|
||||||
|
*/
|
||||||
|
default boolean moveToNext() {
|
||||||
|
return moveToPosition(getPosition() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the cursor to the previous DownloadState.
|
||||||
|
*
|
||||||
|
* <p>This method will return false if the cursor is already before the first entry in the result
|
||||||
|
* set.
|
||||||
|
*
|
||||||
|
* @return whether the move succeeded.
|
||||||
|
*/
|
||||||
|
default boolean moveToPrevious() {
|
||||||
|
return moveToPosition(getPosition() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns whether the cursor is pointing to the first DownloadState. */
|
||||||
|
default boolean isFirst() {
|
||||||
|
return getPosition() == 0 && getCount() != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns whether the cursor is pointing to the last DownloadState. */
|
||||||
|
default boolean isLast() {
|
||||||
|
int count = getCount();
|
||||||
|
return getPosition() == (count - 1) && count != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns whether the cursor is pointing to the position before the first DownloadState. */
|
||||||
|
default boolean isBeforeFirst() {
|
||||||
|
if (getCount() == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return getPosition() == -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns whether the cursor is pointing to the position after the last DownloadState. */
|
||||||
|
default boolean isAfterLast() {
|
||||||
|
if (getCount() == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return getPosition() == getCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Closes the Cursor, releasing all of its resources and making it completely invalid. */
|
||||||
|
void close();
|
||||||
|
|
||||||
|
/** Returns whether the cursor is closed */
|
||||||
|
boolean isClosed();
|
||||||
|
}
|
@ -0,0 +1,491 @@
|
|||||||
|
/*
|
||||||
|
* 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.android.exoplayer2.offline.DefaultDownloadIndex.VersionTable.FEATURE_CACHE;
|
||||||
|
import static com.google.android.exoplayer2.offline.DefaultDownloadIndex.VersionTable.FEATURE_OFFLINE;
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
import org.robolectric.RuntimeEnvironment;
|
||||||
|
|
||||||
|
/** Unit tests for {@link DefaultDownloadIndex}. */
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
public class DefaultDownloadIndexTest {
|
||||||
|
|
||||||
|
private DefaultDownloadIndex downloadIndex;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
downloadIndex = new DefaultDownloadIndex(RuntimeEnvironment.application);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
downloadIndex.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getDownloadState_nonExistingId_returnsNull() {
|
||||||
|
assertThat(downloadIndex.getDownloadState("non existing id")).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addAndGetDownloadState_nonExistingId_returnsTheSameDownloadState() {
|
||||||
|
String id = "id";
|
||||||
|
DownloadState downloadState = new DownloadStateBuilder(id).build();
|
||||||
|
|
||||||
|
downloadIndex.putDownloadState(downloadState);
|
||||||
|
DownloadState readDownloadState = downloadIndex.getDownloadState(id);
|
||||||
|
|
||||||
|
assertEqual(readDownloadState, downloadState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addAndGetDownloadState_existingId_returnsUpdatedDownloadState() {
|
||||||
|
String id = "id";
|
||||||
|
DownloadStateBuilder downloadStateBuilder = new DownloadStateBuilder(id);
|
||||||
|
downloadIndex.putDownloadState(downloadStateBuilder.build());
|
||||||
|
|
||||||
|
DownloadState downloadState =
|
||||||
|
downloadStateBuilder
|
||||||
|
.setType("different type")
|
||||||
|
.setUri("different uri")
|
||||||
|
.setCacheKey("different cacheKey")
|
||||||
|
.setState(DownloadState.STATE_FAILED)
|
||||||
|
.setDownloadPercentage(50)
|
||||||
|
.setDownloadedBytes(200)
|
||||||
|
.setTotalBytes(400)
|
||||||
|
.setFailureReason(DownloadState.FAILURE_REASON_UNKNOWN)
|
||||||
|
.setStopFlags(DownloadState.STOP_FLAG_STOPPED)
|
||||||
|
.setStartTimeMs(10)
|
||||||
|
.setUpdateTimeMs(20)
|
||||||
|
.setStreamKeys(
|
||||||
|
new StreamKey(/* periodIndex= */ 0, /* groupIndex= */ 1, /* trackIndex= */ 2),
|
||||||
|
new StreamKey(/* periodIndex= */ 3, /* groupIndex= */ 4, /* trackIndex= */ 5))
|
||||||
|
.setCustomMetadata(new byte[] {0, 1, 2, 3})
|
||||||
|
.build();
|
||||||
|
downloadIndex.putDownloadState(downloadState);
|
||||||
|
DownloadState readDownloadState = downloadIndex.getDownloadState(id);
|
||||||
|
|
||||||
|
assertThat(readDownloadState).isNotNull();
|
||||||
|
assertEqual(readDownloadState, downloadState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void releaseAndRecreateDownloadIndex_returnsTheSameDownloadState() {
|
||||||
|
String id = "id";
|
||||||
|
DownloadState downloadState = new DownloadStateBuilder(id).build();
|
||||||
|
|
||||||
|
downloadIndex.putDownloadState(downloadState);
|
||||||
|
downloadIndex.release();
|
||||||
|
downloadIndex = new DefaultDownloadIndex(RuntimeEnvironment.application);
|
||||||
|
DownloadState readDownloadState = downloadIndex.getDownloadState(id);
|
||||||
|
|
||||||
|
assertThat(readDownloadState).isNotNull();
|
||||||
|
assertEqual(readDownloadState, downloadState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void customDatabaseProvider_getDownloadStateReturnsNull() {
|
||||||
|
String id = "id";
|
||||||
|
DownloadState downloadState = new DownloadStateBuilder(id).build();
|
||||||
|
|
||||||
|
downloadIndex.putDownloadState(downloadState);
|
||||||
|
downloadIndex.release();
|
||||||
|
DatabaseProviderImpl databaseProvider = new DatabaseProviderImpl();
|
||||||
|
downloadIndex = new DefaultDownloadIndex(databaseProvider);
|
||||||
|
DownloadState readDownloadState = downloadIndex.getDownloadState(id);
|
||||||
|
|
||||||
|
assertThat(readDownloadState).isNull();
|
||||||
|
databaseProvider.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void removeDownloadState_nonExistingId_doesNotFail() {
|
||||||
|
downloadIndex.removeDownloadState("non existing id");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void removeDownloadState_existingId_getDownloadStateReturnsNull() {
|
||||||
|
String id = "id";
|
||||||
|
DownloadState downloadState = new DownloadStateBuilder(id).build();
|
||||||
|
downloadIndex.putDownloadState(downloadState);
|
||||||
|
|
||||||
|
downloadIndex.removeDownloadState(id);
|
||||||
|
DownloadState readDownloadState = downloadIndex.getDownloadState(id);
|
||||||
|
|
||||||
|
assertThat(readDownloadState).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getDownloadStates_emptyDownloadIndex_returnsEmptyArray() {
|
||||||
|
assertThat(downloadIndex.getDownloadStates().getCount()).isEqualTo(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getDownloadStates_noState_returnsAllDownloadStatusSortedByStartTime() {
|
||||||
|
DownloadState downloadState1 = new DownloadStateBuilder("id1").setStartTimeMs(1).build();
|
||||||
|
downloadIndex.putDownloadState(downloadState1);
|
||||||
|
DownloadState downloadState2 = new DownloadStateBuilder("id2").setStartTimeMs(0).build();
|
||||||
|
downloadIndex.putDownloadState(downloadState2);
|
||||||
|
|
||||||
|
DownloadStateCursor cursor = downloadIndex.getDownloadStates();
|
||||||
|
|
||||||
|
assertThat(cursor.getCount()).isEqualTo(2);
|
||||||
|
cursor.moveToNext();
|
||||||
|
assertEqual(cursor.getDownloadState(), downloadState2);
|
||||||
|
cursor.moveToNext();
|
||||||
|
assertEqual(cursor.getDownloadState(), downloadState1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getDownloadStates_withStates_returnsAllDownloadStatusWithTheSameStates() {
|
||||||
|
DownloadState downloadState1 =
|
||||||
|
new DownloadStateBuilder("id1")
|
||||||
|
.setStartTimeMs(0)
|
||||||
|
.setState(DownloadState.STATE_REMOVED)
|
||||||
|
.build();
|
||||||
|
downloadIndex.putDownloadState(downloadState1);
|
||||||
|
DownloadState downloadState2 =
|
||||||
|
new DownloadStateBuilder("id2")
|
||||||
|
.setStartTimeMs(1)
|
||||||
|
.setState(DownloadState.STATE_STOPPED)
|
||||||
|
.build();
|
||||||
|
downloadIndex.putDownloadState(downloadState2);
|
||||||
|
DownloadState downloadState3 =
|
||||||
|
new DownloadStateBuilder("id3")
|
||||||
|
.setStartTimeMs(2)
|
||||||
|
.setState(DownloadState.STATE_COMPLETED)
|
||||||
|
.build();
|
||||||
|
downloadIndex.putDownloadState(downloadState3);
|
||||||
|
|
||||||
|
DownloadStateCursor cursor =
|
||||||
|
downloadIndex.getDownloadStates(DownloadState.STATE_REMOVED, DownloadState.STATE_COMPLETED);
|
||||||
|
|
||||||
|
assertThat(cursor.getCount()).isEqualTo(2);
|
||||||
|
cursor.moveToNext();
|
||||||
|
assertEqual(cursor.getDownloadState(), downloadState1);
|
||||||
|
cursor.moveToNext();
|
||||||
|
assertEqual(cursor.getDownloadState(), downloadState3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doesTableExist_nonExistingTable_returnsFalse() {
|
||||||
|
DatabaseProviderImpl databaseProvider = new DatabaseProviderImpl();
|
||||||
|
|
||||||
|
assertThat(DefaultDownloadIndex.doesTableExist(databaseProvider, "NonExistingTable")).isFalse();
|
||||||
|
|
||||||
|
databaseProvider.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doesTableExist_existingTable_returnsTrue() {
|
||||||
|
DatabaseProviderImpl databaseProvider = new DatabaseProviderImpl();
|
||||||
|
String tableName = "ExistingTable";
|
||||||
|
databaseProvider.getWritableDatabase().execSQL("CREATE TABLE " + tableName + "(dummy)");
|
||||||
|
|
||||||
|
assertThat(DefaultDownloadIndex.doesTableExist(databaseProvider, tableName)).isTrue();
|
||||||
|
|
||||||
|
databaseProvider.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getVersion_nonExistingTable_returnsZero() {
|
||||||
|
DatabaseProviderImpl databaseProvider = new DatabaseProviderImpl();
|
||||||
|
DefaultDownloadIndex.VersionTable versionTable =
|
||||||
|
new DefaultDownloadIndex.VersionTable(databaseProvider);
|
||||||
|
|
||||||
|
int version = versionTable.getVersion(FEATURE_OFFLINE);
|
||||||
|
|
||||||
|
assertThat(version).isEqualTo(0);
|
||||||
|
databaseProvider.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getVersion_returnsSetVersion() {
|
||||||
|
DatabaseProviderImpl databaseProvider = new DatabaseProviderImpl();
|
||||||
|
DefaultDownloadIndex.VersionTable versionTable =
|
||||||
|
new DefaultDownloadIndex.VersionTable(databaseProvider);
|
||||||
|
|
||||||
|
versionTable.setVersion(FEATURE_OFFLINE, 1);
|
||||||
|
assertThat(versionTable.getVersion(FEATURE_OFFLINE)).isEqualTo(1);
|
||||||
|
|
||||||
|
versionTable.setVersion(FEATURE_OFFLINE, 10);
|
||||||
|
assertThat(versionTable.getVersion(FEATURE_OFFLINE)).isEqualTo(10);
|
||||||
|
|
||||||
|
versionTable.setVersion(FEATURE_CACHE, 5);
|
||||||
|
assertThat(versionTable.getVersion(FEATURE_CACHE)).isEqualTo(5);
|
||||||
|
assertThat(versionTable.getVersion(FEATURE_OFFLINE)).isEqualTo(10);
|
||||||
|
|
||||||
|
databaseProvider.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void downloadStateTableConstructor_noTable_createsTable() {
|
||||||
|
DatabaseProviderImpl databaseProvider = new DatabaseProviderImpl();
|
||||||
|
assertThat(
|
||||||
|
DefaultDownloadIndex.doesTableExist(
|
||||||
|
databaseProvider, DefaultDownloadIndex.DownloadStateTable.TABLE_NAME))
|
||||||
|
.isFalse();
|
||||||
|
|
||||||
|
new DefaultDownloadIndex.DownloadStateTable(databaseProvider);
|
||||||
|
|
||||||
|
assertThat(
|
||||||
|
DefaultDownloadIndex.doesTableExist(
|
||||||
|
databaseProvider, DefaultDownloadIndex.DownloadStateTable.TABLE_NAME))
|
||||||
|
.isTrue();
|
||||||
|
|
||||||
|
databaseProvider.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void downloadStateTableConstructor_versionZero_versionSet() {
|
||||||
|
DatabaseProviderImpl databaseProvider = new DatabaseProviderImpl();
|
||||||
|
|
||||||
|
new DefaultDownloadIndex.DownloadStateTable(databaseProvider);
|
||||||
|
|
||||||
|
DefaultDownloadIndex.VersionTable versionTable =
|
||||||
|
new DefaultDownloadIndex.VersionTable(databaseProvider);
|
||||||
|
assertThat(versionTable.getVersion(FEATURE_OFFLINE))
|
||||||
|
.isEqualTo(DefaultDownloadIndex.DownloadStateTable.TABLE_VERSION);
|
||||||
|
databaseProvider.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void downloadStateTableConstructor_greaterVersion_tableRecreated() {
|
||||||
|
DatabaseProviderImpl databaseProvider = new DatabaseProviderImpl();
|
||||||
|
databaseProvider
|
||||||
|
.getWritableDatabase()
|
||||||
|
.execSQL("CREATE TABLE " + DefaultDownloadIndex.DownloadStateTable.TABLE_NAME + "(dummy)");
|
||||||
|
DefaultDownloadIndex.VersionTable versionTable =
|
||||||
|
new DefaultDownloadIndex.VersionTable(databaseProvider);
|
||||||
|
versionTable.setVersion(FEATURE_OFFLINE, Integer.MAX_VALUE);
|
||||||
|
|
||||||
|
DefaultDownloadIndex.DownloadStateTable downloadStateTable =
|
||||||
|
new DefaultDownloadIndex.DownloadStateTable(databaseProvider);
|
||||||
|
String id = "id";
|
||||||
|
DownloadState downloadState = new DownloadStateBuilder(id).build();
|
||||||
|
downloadStateTable.replace(downloadState);
|
||||||
|
DownloadState readDownloadState = downloadStateTable.get(id);
|
||||||
|
assertEqual(readDownloadState, downloadState);
|
||||||
|
|
||||||
|
assertThat(versionTable.getVersion(FEATURE_OFFLINE))
|
||||||
|
.isEqualTo(DefaultDownloadIndex.DownloadStateTable.TABLE_VERSION);
|
||||||
|
databaseProvider.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertEqual(DownloadState downloadState, DownloadState expected) {
|
||||||
|
assertThat(areEqual(downloadState, expected)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean areEqual(DownloadState downloadState, DownloadState that) {
|
||||||
|
if (downloadState.state != that.state) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (Float.compare(that.downloadPercentage, downloadState.downloadPercentage) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (downloadState.downloadedBytes != that.downloadedBytes) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (downloadState.totalBytes != that.totalBytes) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (downloadState.startTimeMs != that.startTimeMs) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (downloadState.updateTimeMs != that.updateTimeMs) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (downloadState.failureReason != that.failureReason) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (downloadState.stopFlags != that.stopFlags) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!downloadState.id.equals(that.id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!downloadState.type.equals(that.type)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!downloadState.uri.equals(that.uri)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (downloadState.cacheKey != null
|
||||||
|
? !downloadState.cacheKey.equals(that.cacheKey)
|
||||||
|
: that.cacheKey != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Arrays.equals(downloadState.streamKeys, that.streamKeys)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Arrays.equals(downloadState.customMetadata, that.customMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DownloadStateBuilder {
|
||||||
|
private String id;
|
||||||
|
private String type;
|
||||||
|
private String uri;
|
||||||
|
@Nullable private String cacheKey;
|
||||||
|
private int state;
|
||||||
|
private float downloadPercentage;
|
||||||
|
private long downloadedBytes;
|
||||||
|
private long totalBytes;
|
||||||
|
private int failureReason;
|
||||||
|
private int stopFlags;
|
||||||
|
private long startTimeMs;
|
||||||
|
private long updateTimeMs;
|
||||||
|
private StreamKey[] streamKeys;
|
||||||
|
private byte[] customMetadata;
|
||||||
|
|
||||||
|
private DownloadStateBuilder(String id) {
|
||||||
|
this.id = id;
|
||||||
|
this.type = "type";
|
||||||
|
this.uri = "uri";
|
||||||
|
this.cacheKey = null;
|
||||||
|
this.state = DownloadState.STATE_QUEUED;
|
||||||
|
this.downloadPercentage = (float) C.PERCENTAGE_UNSET;
|
||||||
|
this.downloadedBytes = (long) 0;
|
||||||
|
this.totalBytes = (long) C.LENGTH_UNSET;
|
||||||
|
this.failureReason = DownloadState.FAILURE_REASON_NONE;
|
||||||
|
this.stopFlags = 0;
|
||||||
|
this.startTimeMs = (long) 0;
|
||||||
|
this.updateTimeMs = (long) 0;
|
||||||
|
this.streamKeys = new StreamKey[0];
|
||||||
|
this.customMetadata = new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownloadStateBuilder setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownloadStateBuilder setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownloadStateBuilder setUri(String uri) {
|
||||||
|
this.uri = uri;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownloadStateBuilder setCacheKey(@Nullable String cacheKey) {
|
||||||
|
this.cacheKey = cacheKey;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownloadStateBuilder setState(int state) {
|
||||||
|
this.state = state;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownloadStateBuilder setDownloadPercentage(float downloadPercentage) {
|
||||||
|
this.downloadPercentage = downloadPercentage;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownloadStateBuilder setDownloadedBytes(long downloadedBytes) {
|
||||||
|
this.downloadedBytes = downloadedBytes;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownloadStateBuilder setTotalBytes(long totalBytes) {
|
||||||
|
this.totalBytes = totalBytes;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownloadStateBuilder setFailureReason(int failureReason) {
|
||||||
|
this.failureReason = failureReason;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownloadStateBuilder setStopFlags(int stopFlags) {
|
||||||
|
this.stopFlags = stopFlags;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownloadStateBuilder setStartTimeMs(long startTimeMs) {
|
||||||
|
this.startTimeMs = startTimeMs;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownloadStateBuilder setUpdateTimeMs(long updateTimeMs) {
|
||||||
|
this.updateTimeMs = updateTimeMs;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownloadStateBuilder setStreamKeys(StreamKey... streamKeys) {
|
||||||
|
this.streamKeys = streamKeys;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownloadStateBuilder setCustomMetadata(byte[] customMetadata) {
|
||||||
|
this.customMetadata = customMetadata;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownloadState build() {
|
||||||
|
return new DownloadState(
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
Uri.parse(uri),
|
||||||
|
cacheKey,
|
||||||
|
state,
|
||||||
|
downloadPercentage,
|
||||||
|
downloadedBytes,
|
||||||
|
totalBytes,
|
||||||
|
failureReason,
|
||||||
|
stopFlags,
|
||||||
|
startTimeMs,
|
||||||
|
updateTimeMs,
|
||||||
|
streamKeys,
|
||||||
|
customMetadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class DatabaseProviderImpl extends SQLiteOpenHelper
|
||||||
|
implements DefaultDownloadIndex.DatabaseProvider {
|
||||||
|
private static final int DATABASE_VERSION = 1;
|
||||||
|
private static final String DATABASE_NAME = "TestExoPlayerDownloadIndex.db";
|
||||||
|
|
||||||
|
public DatabaseProviderImpl() {
|
||||||
|
super(RuntimeEnvironment.application, DATABASE_NAME, null, DATABASE_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(SQLiteDatabase db) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user