From 1e3337e4b716f24bfb4e871ed9af1e4344361fdc Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 26 Feb 2021 14:15:25 +0000 Subject: [PATCH] Add RawResourceDataSource contract test PiperOrigin-RevId: 359743165 --- .../RawResourceDataSourceContractTest.java | 85 +++++++++++++++++++ .../core/src/androidTest/res/raw/resource1 | 1 + .../core/src/androidTest/res/raw/resource2 | 1 + .../upstream/RawResourceDataSource.java | 27 ++++-- 4 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/RawResourceDataSourceContractTest.java create mode 100644 library/core/src/androidTest/res/raw/resource1 create mode 100644 library/core/src/androidTest/res/raw/resource2 diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/RawResourceDataSourceContractTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/RawResourceDataSourceContractTest.java new file mode 100644 index 0000000000..d55e162a49 --- /dev/null +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/RawResourceDataSourceContractTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2020 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.upstream; + +import android.content.res.Resources; +import android.net.Uri; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.core.test.R; +import com.google.android.exoplayer2.testutil.DataSourceContractTest; +import com.google.android.exoplayer2.util.Util; +import com.google.common.collect.ImmutableList; +import org.junit.runner.RunWith; + +/** {@link DataSource} contract tests for {@link RawResourceDataSource}. */ +@RunWith(AndroidJUnit4.class) +public final class RawResourceDataSourceContractTest extends DataSourceContractTest { + + private static final byte[] RESOURCE_1_DATA = Util.getUtf8Bytes("resource1 abc\n"); + private static final byte[] RESOURCE_2_DATA = Util.getUtf8Bytes("resource2 abcdef\n"); + + @Override + protected DataSource createDataSource() { + return new RawResourceDataSource(ApplicationProvider.getApplicationContext()); + } + + @Override + protected ImmutableList getTestResources() { + // Android packages raw resources into a single file. When reading a resource other than the + // last one, Android does not prevent accidentally reading beyond the end of the resource and + // into the next one. We use two resources in this test to ensure that when packaged, at least + // one of them has a subsequent resource. This allows the contract test to enforce that the + // RawResourceDataSource implementation doesn't erroneously read into the second resource when + // opened to read the first. + return ImmutableList.of( + new TestResource.Builder() + .setName("resource 1") + .setUri(RawResourceDataSource.buildRawResourceUri(R.raw.resource1)) + .setExpectedBytes(RESOURCE_1_DATA) + .build(), + new TestResource.Builder() + .setName("resource 2") + .setUri(RawResourceDataSource.buildRawResourceUri(R.raw.resource2)) + .setExpectedBytes(RESOURCE_2_DATA) + .build(), + // Additional resources using different URI schemes. + new TestResource.Builder() + .setName("android.resource:// with path") + .setUri( + Uri.parse( + "android.resource://" + + ApplicationProvider.getApplicationContext().getPackageName() + + "/raw/resource1")) + .setExpectedBytes(RESOURCE_1_DATA) + .build(), + new TestResource.Builder() + .setName("android.resource:// with ID") + .setUri( + Uri.parse( + "android.resource://" + + ApplicationProvider.getApplicationContext().getPackageName() + + "/" + + R.raw.resource1)) + .setExpectedBytes(RESOURCE_1_DATA) + .build()); + } + + @Override + protected Uri getNotFoundUri() { + return RawResourceDataSource.buildRawResourceUri(Resources.ID_NULL); + } +} diff --git a/library/core/src/androidTest/res/raw/resource1 b/library/core/src/androidTest/res/raw/resource1 new file mode 100644 index 0000000000..8b59777753 --- /dev/null +++ b/library/core/src/androidTest/res/raw/resource1 @@ -0,0 +1 @@ +resource1 abc diff --git a/library/core/src/androidTest/res/raw/resource2 b/library/core/src/androidTest/res/raw/resource2 new file mode 100644 index 0000000000..37927749f2 --- /dev/null +++ b/library/core/src/androidTest/res/raw/resource2 @@ -0,0 +1 @@ +resource2 abcdef diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java index 7538cc67a4..2568d49c3a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java @@ -60,7 +60,7 @@ public final class RawResourceDataSource extends BaseDataSource { super(message); } - public RawResourceDataSourceException(IOException e) { + public RawResourceDataSourceException(Throwable e) { super(e); } } @@ -133,21 +133,39 @@ public final class RawResourceDataSource extends BaseDataSource { } transferInitializing(dataSpec); - AssetFileDescriptor assetFileDescriptor = resources.openRawResourceFd(resourceId); + + AssetFileDescriptor assetFileDescriptor; + try { + assetFileDescriptor = resources.openRawResourceFd(resourceId); + } catch (Resources.NotFoundException e) { + throw new RawResourceDataSourceException(e); + } + this.assetFileDescriptor = assetFileDescriptor; if (assetFileDescriptor == null) { throw new RawResourceDataSourceException("Resource is compressed: " + uri); } + long assetFileDescriptorLength = assetFileDescriptor.getLength(); FileInputStream inputStream = new FileInputStream(assetFileDescriptor.getFileDescriptor()); this.inputStream = inputStream; try { + // We can't rely only on the "skipped < dataSpec.position" check below to detect whether the + // position is beyond the end of the resource being read. This is because the file will + // typically contain multiple resources, and there's nothing to prevent InputStream.skip() + // from succeeding by skipping into the data of the next resource. Hence we also need to check + // against the resource length explicitly, which is guaranteed to be set unless the resource + // extends to the end of the file. + if (assetFileDescriptorLength != AssetFileDescriptor.UNKNOWN_LENGTH + && dataSpec.position > assetFileDescriptorLength) { + throw new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE); + } inputStream.skip(assetFileDescriptor.getStartOffset()); long skipped = inputStream.skip(dataSpec.position); if (skipped < dataSpec.position) { // We expect the skip to be satisfied in full. If it isn't then we're probably trying to - // skip beyond the end of the data. - throw new EOFException(); + // read beyond the end of the last resource in the file. + throw new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE); } } catch (IOException e) { throw new RawResourceDataSourceException(e); @@ -156,7 +174,6 @@ public final class RawResourceDataSource extends BaseDataSource { if (dataSpec.length != C.LENGTH_UNSET) { bytesRemaining = dataSpec.length; } else { - long assetFileDescriptorLength = assetFileDescriptor.getLength(); // If the length is UNKNOWN_LENGTH then the asset extends to the end of the file. bytesRemaining = assetFileDescriptorLength == AssetFileDescriptor.UNKNOWN_LENGTH