Add documented support for android.resource://package/id
URIs
These were previously somewhat supported, but the `package` part was never read (so it only worked if it was either absent or the same as the current application's package). This change parses and uses the `package` part. This partially reverts35121a2d3d
. This is hard to test for the same reasons as88f554c74b
: > This is hard to test because to do so robustly requires being able to guaranteed that another test APK will be installed with a known raw resource inside it. PiperOrigin-RevId: 608270463
This commit is contained in:
parent
e43f96687c
commit
3bbb0e7453
@ -18,6 +18,11 @@
|
||||
* Metadata:
|
||||
* Image:
|
||||
* DRM:
|
||||
* DataSource:
|
||||
* Implement support for `android.resource://package/id` raw resource URIs
|
||||
where `package` is different to the package of the current application.
|
||||
This wasn't previously documented to work, but is a more efficient way
|
||||
of accessing resources in another package than by name.
|
||||
* Effect:
|
||||
* Improved PQ to SDR tone-mapping by converting color spaces.
|
||||
* Support multiple speed changes within the same `EditedMediaItem` or
|
||||
|
@ -93,8 +93,13 @@ public final class RawResourceDataSourceContractTest extends DataSourceContractT
|
||||
.setExpectedBytes(RESOURCE_1_DATA)
|
||||
.build(),
|
||||
new TestResource.Builder()
|
||||
.setName("android.resource:// with (unused) package and ID")
|
||||
.setUri(Uri.parse("android.resource://unused.package.name/" + R.raw.resource1))
|
||||
.setName("android.resource:// with package and ID")
|
||||
.setUri(
|
||||
Uri.parse(
|
||||
"android.resource://"
|
||||
+ ApplicationProvider.getApplicationContext().getPackageName()
|
||||
+ "/"
|
||||
+ R.raw.resource1))
|
||||
.setExpectedBytes(RESOURCE_1_DATA)
|
||||
.build());
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A {@link DataSource} for reading a raw resource.
|
||||
@ -43,28 +44,29 @@ import java.nio.channels.FileChannel;
|
||||
* <p>URIs supported by this source are:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@code android.resource:///id}, where {@code id} is the integer identifier of a raw
|
||||
* resource in this application.
|
||||
* <li>{@code android.resource://[package]/id}, where {@code package} is the name of the package
|
||||
* in which the resource is located and {@code id} is the integer identifier of the resource.
|
||||
* {@code package} is optional, its default value is the package of this application.
|
||||
* <li>{@code android.resource://[package]/[type/]name}, where {@code package} is the name of the
|
||||
* package in which the resource is located, {@code type} is the resource type and {@code
|
||||
* name} is the resource name. The package and the type are optional. Their default value is
|
||||
* the package of this application and "raw", respectively. Using the two other forms is more
|
||||
* the package of this application and "raw", respectively. Using the other form is more
|
||||
* efficient.
|
||||
* <ul>
|
||||
* <li>If {@code package} is specified, it must be <a
|
||||
* href="https://developer.android.com/training/package-visibility">visible</a> to the
|
||||
* current application.
|
||||
* </ul>
|
||||
* </ul>
|
||||
*
|
||||
* <p>URIs of the form {@code android.resource://package/id} are also supported, although the
|
||||
* package part is not needed and is not used. This support is due to this format being prevalent in
|
||||
* the ecosystem (including being <a href="https://stackoverflow.com/a/4896272">recommended on Stack
|
||||
* Overflow</a>).
|
||||
* <p>If {@code package} is specified in either of the above URI forms, it must be <a
|
||||
* href="https://developer.android.com/training/package-visibility">visible</a> to the current
|
||||
* application.
|
||||
*
|
||||
* <p>{@code new
|
||||
* Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE).path(Integer.toString(resourceId)).build()}
|
||||
* can be used to build supported {@link Uri}s.
|
||||
* <p>Supported {@link Uri} instances can be built as follows:
|
||||
*
|
||||
* <pre>{@code
|
||||
* Uri.Builder()
|
||||
* .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
|
||||
* .authority(packageName)
|
||||
* .path(Integer.toString(resourceId))
|
||||
* .build();
|
||||
* }</pre>
|
||||
*/
|
||||
@UnstableApi
|
||||
public final class RawResourceDataSource extends BaseDataSource {
|
||||
@ -209,18 +211,16 @@ public final class RawResourceDataSource extends BaseDataSource {
|
||||
Uri normalizedUri = dataSpec.uri.normalizeScheme();
|
||||
Resources resources;
|
||||
int resourceId;
|
||||
if (TextUtils.equals(RAW_RESOURCE_SCHEME, normalizedUri.getScheme())
|
||||
|| (TextUtils.equals(ContentResolver.SCHEME_ANDROID_RESOURCE, normalizedUri.getScheme())
|
||||
&& normalizedUri.getPathSegments().size() == 1
|
||||
&& Assertions.checkNotNull(normalizedUri.getLastPathSegment()).matches("\\d+"))) {
|
||||
if (TextUtils.equals(RAW_RESOURCE_SCHEME, normalizedUri.getScheme())) {
|
||||
resources = applicationContext.getResources();
|
||||
try {
|
||||
resourceId = Integer.parseInt(Assertions.checkNotNull(normalizedUri.getLastPathSegment()));
|
||||
} catch (NumberFormatException e) {
|
||||
List<String> pathSegments = normalizedUri.getPathSegments();
|
||||
if (pathSegments.size() == 1) {
|
||||
resourceId = parseResourceId(pathSegments.get(0));
|
||||
} else {
|
||||
throw new RawResourceDataSourceException(
|
||||
"Resource identifier must be an integer.",
|
||||
/* cause= */ null,
|
||||
PlaybackException.ERROR_CODE_FAILED_RUNTIME_CHECK);
|
||||
RAW_RESOURCE_SCHEME
|
||||
+ ":// URI must have exactly one path element, found "
|
||||
+ pathSegments.size());
|
||||
}
|
||||
} else if (TextUtils.equals(
|
||||
ContentResolver.SCHEME_ANDROID_RESOURCE, normalizedUri.getScheme())) {
|
||||
@ -247,18 +247,22 @@ public final class RawResourceDataSource extends BaseDataSource {
|
||||
PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
// The javadoc of this class already discourages the URI form that requires this API call.
|
||||
@SuppressLint("DiscouragedApi")
|
||||
int resourceIdFromName =
|
||||
resources.getIdentifier(
|
||||
packageName + ":" + path, /* defType= */ "raw", /* defPackage= */ null);
|
||||
if (resourceIdFromName != 0) {
|
||||
resourceId = resourceIdFromName;
|
||||
if (path.matches("\\d+")) {
|
||||
resourceId = parseResourceId(path);
|
||||
} else {
|
||||
throw new RawResourceDataSourceException(
|
||||
"Resource not found.",
|
||||
/* cause= */ null,
|
||||
PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND);
|
||||
// The javadoc of this class already discourages the URI form that requires this API call.
|
||||
@SuppressLint("DiscouragedApi")
|
||||
int resourceIdFromName =
|
||||
resources.getIdentifier(
|
||||
packageName + ":" + path, /* defType= */ "raw", /* defPackage= */ null);
|
||||
if (resourceIdFromName != 0) {
|
||||
resourceId = resourceIdFromName;
|
||||
} else {
|
||||
throw new RawResourceDataSourceException(
|
||||
"Resource not found.",
|
||||
/* cause= */ null,
|
||||
PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new RawResourceDataSourceException(
|
||||
@ -288,6 +292,17 @@ public final class RawResourceDataSource extends BaseDataSource {
|
||||
return assetFileDescriptor;
|
||||
}
|
||||
|
||||
private static int parseResourceId(String resourceId) throws RawResourceDataSourceException {
|
||||
try {
|
||||
return Integer.parseInt(resourceId);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new RawResourceDataSourceException(
|
||||
"Resource identifier must be an integer.",
|
||||
/* cause= */ null,
|
||||
PlaybackException.ERROR_CODE_FAILED_RUNTIME_CHECK);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] buffer, int offset, int length) throws RawResourceDataSourceException {
|
||||
if (length == 0) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user