Don't use InputStream.available in ContentDataSource

Issue: #3426

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=174700804
This commit is contained in:
olly 2017-11-06 07:03:09 -08:00 committed by Oliver Woodman
parent 585e70c139
commit e6e75a536a
4 changed files with 104 additions and 46 deletions

View File

@ -30,14 +30,14 @@ public final class AssetDataSourceTest extends InstrumentationTestCase {
AssetDataSource dataSource = new AssetDataSource(getInstrumentation().getContext()); AssetDataSource dataSource = new AssetDataSource(getInstrumentation().getContext());
DataSpec dataSpec = new DataSpec(Uri.parse("file:///android_asset/" + DATA_PATH)); DataSpec dataSpec = new DataSpec(Uri.parse("file:///android_asset/" + DATA_PATH));
TestUtil.assertDataSourceContent(dataSource, dataSpec, TestUtil.assertDataSourceContent(dataSource, dataSpec,
TestUtil.getByteArray(getInstrumentation(), DATA_PATH)); TestUtil.getByteArray(getInstrumentation(), DATA_PATH), true);
} }
public void testReadAssetUri() throws Exception { public void testReadAssetUri() throws Exception {
AssetDataSource dataSource = new AssetDataSource(getInstrumentation().getContext()); AssetDataSource dataSource = new AssetDataSource(getInstrumentation().getContext());
DataSpec dataSpec = new DataSpec(Uri.parse("asset:///" + DATA_PATH)); DataSpec dataSpec = new DataSpec(Uri.parse("asset:///" + DATA_PATH));
TestUtil.assertDataSourceContent(dataSource, dataSpec, TestUtil.assertDataSourceContent(dataSource, dataSpec,
TestUtil.getByteArray(getInstrumentation(), DATA_PATH)); TestUtil.getByteArray(getInstrumentation(), DATA_PATH), true);
} }
} }

View File

@ -15,17 +15,22 @@
*/ */
package com.google.android.exoplayer2.upstream; package com.google.android.exoplayer2.upstream;
import android.app.Instrumentation;
import android.content.ContentProvider; import android.content.ContentProvider;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.res.AssetFileDescriptor; import android.content.res.AssetFileDescriptor;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.TestUtil;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
@ -37,23 +42,33 @@ public final class ContentDataSourceTest extends InstrumentationTestCase {
private static final String AUTHORITY = "com.google.android.exoplayer2.core.test"; private static final String AUTHORITY = "com.google.android.exoplayer2.core.test";
private static final String DATA_PATH = "binary/1024_incrementing_bytes.mp3"; private static final String DATA_PATH = "binary/1024_incrementing_bytes.mp3";
public void testReadValidUri() throws Exception { public void testRead() throws Exception {
ContentDataSource dataSource = new ContentDataSource(getInstrumentation().getContext()); assertData(getInstrumentation(), 0, C.LENGTH_UNSET, false);
Uri contentUri = new Uri.Builder() }
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(AUTHORITY) public void testReadPipeMode() throws Exception {
.path(DATA_PATH).build(); assertData(getInstrumentation(), 0, C.LENGTH_UNSET, true);
DataSpec dataSpec = new DataSpec(contentUri); }
TestUtil.assertDataSourceContent(dataSource, dataSpec,
TestUtil.getByteArray(getInstrumentation(), DATA_PATH)); public void testReadFixedLength() throws Exception {
assertData(getInstrumentation(), 0, 100, false);
}
public void testReadFromOffsetToEndOfInput() throws Exception {
assertData(getInstrumentation(), 1, C.LENGTH_UNSET, false);
}
public void testReadFromOffsetToEndOfInputPipeMode() throws Exception {
assertData(getInstrumentation(), 1, C.LENGTH_UNSET, true);
}
public void testReadFromOffsetFixedLength() throws Exception {
assertData(getInstrumentation(), 1, 100, false);
} }
public void testReadInvalidUri() throws Exception { public void testReadInvalidUri() throws Exception {
ContentDataSource dataSource = new ContentDataSource(getInstrumentation().getContext()); ContentDataSource dataSource = new ContentDataSource(getInstrumentation().getContext());
Uri contentUri = new Uri.Builder() Uri contentUri = TestContentProvider.buildUri("does/not.exist", false);
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(AUTHORITY)
.build();
DataSpec dataSpec = new DataSpec(contentUri); DataSpec dataSpec = new DataSpec(contentUri);
try { try {
dataSource.open(dataSpec); dataSource.open(dataSpec);
@ -66,18 +81,16 @@ public final class ContentDataSourceTest extends InstrumentationTestCase {
} }
} }
public void testReadFromOffsetToEndOfInput() throws Exception { private static void assertData(Instrumentation instrumentation, int offset, int length,
ContentDataSource dataSource = new ContentDataSource(getInstrumentation().getContext()); boolean pipeMode) throws IOException {
Uri contentUri = new Uri.Builder() Uri contentUri = TestContentProvider.buildUri(DATA_PATH, pipeMode);
.scheme(ContentResolver.SCHEME_CONTENT) ContentDataSource dataSource = new ContentDataSource(instrumentation.getContext());
.authority(AUTHORITY)
.path(DATA_PATH).build();
try { try {
int testOffset = 1; DataSpec dataSpec = new DataSpec(contentUri, offset, length, null);
DataSpec dataSpec = new DataSpec(contentUri, testOffset, C.LENGTH_UNSET, null); byte[] completeData = TestUtil.getByteArray(instrumentation, DATA_PATH);
byte[] completeData = TestUtil.getByteArray(getInstrumentation(), DATA_PATH); byte[] expectedData = Arrays.copyOfRange(completeData, offset,
byte[] expectedData = Arrays.copyOfRange(completeData, testOffset, completeData.length); length == C.LENGTH_UNSET ? completeData.length : offset + length);
TestUtil.assertDataSourceContent(dataSource, dataSpec, expectedData); TestUtil.assertDataSourceContent(dataSource, dataSpec, expectedData, !pipeMode);
} finally { } finally {
dataSource.close(); dataSource.close();
} }
@ -86,7 +99,21 @@ public final class ContentDataSourceTest extends InstrumentationTestCase {
/** /**
* A {@link ContentProvider} for the test. * A {@link ContentProvider} for the test.
*/ */
public static final class TestContentProvider extends ContentProvider { public static final class TestContentProvider extends ContentProvider
implements ContentProvider.PipeDataWriter<Object> {
private static final String PARAM_PIPE_MODE = "pipe-mode";
public static Uri buildUri(String filePath, boolean pipeMode) {
Uri.Builder builder = new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(AUTHORITY)
.path(filePath);
if (pipeMode) {
builder.appendQueryParameter(TestContentProvider.PARAM_PIPE_MODE, "1");
}
return builder.build();
}
@Override @Override
public boolean onCreate() { public boolean onCreate() {
@ -106,7 +133,14 @@ public final class ContentDataSourceTest extends InstrumentationTestCase {
return null; return null;
} }
try { try {
return getContext().getAssets().openFd(uri.getPath().replaceFirst("/", "")); String fileName = getFileName(uri);
boolean pipeMode = uri.getQueryParameter(PARAM_PIPE_MODE) != null;
if (pipeMode) {
ParcelFileDescriptor fileDescriptor = openPipeHelper(uri, null, null, null, this);
return new AssetFileDescriptor(fileDescriptor, 0, C.LENGTH_UNSET);
} else {
return getContext().getAssets().openFd(fileName);
}
} catch (IOException e) { } catch (IOException e) {
FileNotFoundException exception = new FileNotFoundException(e.getMessage()); FileNotFoundException exception = new FileNotFoundException(e.getMessage());
exception.initCause(e); exception.initCause(e);
@ -125,15 +159,31 @@ public final class ContentDataSourceTest extends InstrumentationTestCase {
} }
@Override @Override
public int delete(@NonNull Uri uri, String selection, public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException();
}
@Override
public int update(@NonNull Uri uri, ContentValues values, String selection,
String[] selectionArgs) { String[] selectionArgs) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override @Override
public int update(@NonNull Uri uri, ContentValues values, public void writeDataToPipe(@NonNull ParcelFileDescriptor output, @NonNull Uri uri,
String selection, String[] selectionArgs) { @NonNull String mimeType, @Nullable Bundle opts, @Nullable Object args) {
throw new UnsupportedOperationException(); try {
byte[] data = TestUtil.getByteArray(getContext(), getFileName(uri));
FileOutputStream outputStream = new FileOutputStream(output.getFileDescriptor());
outputStream.write(data);
outputStream.close();
} catch (IOException e) {
throw new RuntimeException("Error writing to pipe", e);
}
}
private static String getFileName(Uri uri) {
return uri.getPath().replaceFirst("/", "");
} }
} }

View File

@ -24,7 +24,7 @@ import java.io.EOFException;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.nio.channels.FileChannel;
/** /**
* A {@link DataSource} for reading from a content URI. * A {@link DataSource} for reading from a content URI.
@ -47,7 +47,7 @@ public final class ContentDataSource implements DataSource {
private Uri uri; private Uri uri;
private AssetFileDescriptor assetFileDescriptor; private AssetFileDescriptor assetFileDescriptor;
private InputStream inputStream; private FileInputStream inputStream;
private long bytesRemaining; private long bytesRemaining;
private boolean opened; private boolean opened;
@ -88,14 +88,11 @@ public final class ContentDataSource implements DataSource {
} else { } else {
long assetFileDescriptorLength = assetFileDescriptor.getLength(); long assetFileDescriptorLength = assetFileDescriptor.getLength();
if (assetFileDescriptorLength == AssetFileDescriptor.UNKNOWN_LENGTH) { if (assetFileDescriptorLength == AssetFileDescriptor.UNKNOWN_LENGTH) {
// The asset must extend to the end of the file. // The asset must extend to the end of the file. If FileInputStream.getChannel().size()
bytesRemaining = inputStream.available(); // returns 0 then the remaining length cannot be determined.
if (bytesRemaining == 0) { FileChannel channel = inputStream.getChannel();
// FileInputStream.available() returns 0 if the remaining length cannot be determined, long channelSize = channel.size();
// or if it's greater than Integer.MAX_VALUE. We don't know the true length in either bytesRemaining = channelSize == 0 ? C.LENGTH_UNSET : channelSize - channel.position();
// case, so treat as unbounded.
bytesRemaining = C.LENGTH_UNSET;
}
} else { } else {
bytesRemaining = assetFileDescriptorLength - skipped; bytesRemaining = assetFileDescriptorLength - skipped;
} }

View File

@ -16,6 +16,7 @@
package com.google.android.exoplayer2.testutil; package com.google.android.exoplayer2.testutil;
import android.app.Instrumentation; import android.app.Instrumentation;
import android.content.Context;
import android.test.MoreAsserts; import android.test.MoreAsserts;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
@ -121,12 +122,20 @@ public class TestUtil {
public static byte[] getByteArray(Instrumentation instrumentation, String fileName) public static byte[] getByteArray(Instrumentation instrumentation, String fileName)
throws IOException { throws IOException {
return Util.toByteArray(getInputStream(instrumentation, fileName)); return getByteArray(instrumentation.getContext(), fileName);
}
public static byte[] getByteArray(Context context, String fileName) throws IOException {
return Util.toByteArray(getInputStream(context, fileName));
} }
public static InputStream getInputStream(Instrumentation instrumentation, String fileName) public static InputStream getInputStream(Instrumentation instrumentation, String fileName)
throws IOException { throws IOException {
return instrumentation.getContext().getResources().getAssets().open(fileName); return getInputStream(instrumentation.getContext(), fileName);
}
public static InputStream getInputStream(Context context, String fileName) throws IOException {
return context.getResources().getAssets().open(fileName);
} }
public static String getString(Instrumentation instrumentation, String fileName) public static String getString(Instrumentation instrumentation, String fileName)
@ -167,13 +176,15 @@ public class TestUtil {
* @param dataSource The {@link DataSource} through which to read. * @param dataSource The {@link DataSource} through which to read.
* @param dataSpec The {@link DataSpec} to use when opening the {@link DataSource}. * @param dataSpec The {@link DataSpec} to use when opening the {@link DataSource}.
* @param expectedData The expected data. * @param expectedData The expected data.
* @param expectKnownLength Whether to assert that {@link DataSource#open} returns the expected
* data length. If false then it's asserted that {@link C#LENGTH_UNSET} is returned.
* @throws IOException If an error occurs reading fom the {@link DataSource}. * @throws IOException If an error occurs reading fom the {@link DataSource}.
*/ */
public static void assertDataSourceContent(DataSource dataSource, DataSpec dataSpec, public static void assertDataSourceContent(DataSource dataSource, DataSpec dataSpec,
byte[] expectedData) throws IOException { byte[] expectedData, boolean expectKnownLength) throws IOException {
try { try {
long length = dataSource.open(dataSpec); long length = dataSource.open(dataSpec);
Assert.assertEquals(expectedData.length, length); Assert.assertEquals(expectKnownLength ? expectedData.length : C.LENGTH_UNSET, length);
byte[] readData = TestUtil.readToEnd(dataSource); byte[] readData = TestUtil.readToEnd(dataSource);
MoreAsserts.assertEquals(expectedData, readData); MoreAsserts.assertEquals(expectedData, readData);
} finally { } finally {