Add cross-package support to RawResourceDataSource
This has been documented to work since this class was created, but until now we were always trying to resolve using the current application's `Resources.getIdentifier` method. This commit changes to resolve to the other app's `Resources` object if the package name doesn't match the current package. This will only work if the current app has package-visibility to the destination package: http://g.co/dev/packagevisibility 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: 577864992
This commit is contained in:
parent
26789b56c2
commit
88f554c74b
@ -3,6 +3,10 @@
|
|||||||
### Unreleased changes
|
### Unreleased changes
|
||||||
|
|
||||||
* Common Library:
|
* Common Library:
|
||||||
|
* Implement support for `android.resource://package/[type/]name` raw
|
||||||
|
resource URIs where `package` is different to the package of the current
|
||||||
|
application. This has always been documented to work, but wasn't
|
||||||
|
correctly implemented until now.
|
||||||
* ExoPlayer:
|
* ExoPlayer:
|
||||||
* Add `PreloadMediaSource` and `PreloadMediaPeriod` that allows apps to
|
* Add `PreloadMediaSource` and `PreloadMediaPeriod` that allows apps to
|
||||||
preload the media source at a specific start position before playback,
|
preload the media source at a specific start position before playback,
|
||||||
|
@ -18,8 +18,10 @@ package androidx.media3.datasource;
|
|||||||
import static androidx.media3.common.util.Util.castNonNull;
|
import static androidx.media3.common.util.Util.castNonNull;
|
||||||
import static java.lang.Math.min;
|
import static java.lang.Math.min;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.content.res.AssetFileDescriptor;
|
import android.content.res.AssetFileDescriptor;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@ -36,19 +38,25 @@ import java.io.InputStream;
|
|||||||
import java.nio.channels.FileChannel;
|
import java.nio.channels.FileChannel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link DataSource} for reading a raw resource inside the APK.
|
* A {@link DataSource} for reading a raw resource.
|
||||||
*
|
*
|
||||||
* <p>URIs supported by this source are of one of the forms:
|
* <p>URIs supported by this source are of one of the forms:
|
||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@code rawresource:///id}, where {@code id} is the integer identifier of a raw resource.
|
* <li>{@code rawresource:///id}, where {@code id} is the integer identifier of a raw resource in
|
||||||
|
* this application.
|
||||||
* <li>{@code android.resource:///id}, where {@code id} is the integer identifier of a raw
|
* <li>{@code android.resource:///id}, where {@code id} is the integer identifier of a raw
|
||||||
* resource.
|
* resource in this application.
|
||||||
* <li>{@code android.resource://[package]/[type/]name}, where {@code package} is the name of the
|
* <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
|
* 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
|
* 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 two other forms is more
|
||||||
* efficient.
|
* 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>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <p>URIs of the form {@code android.resource://package/id} are also supported, although the
|
* <p>URIs of the form {@code android.resource://package/id} are also supported, although the
|
||||||
@ -101,8 +109,7 @@ public final class RawResourceDataSource extends BaseDataSource {
|
|||||||
/** The scheme part of a raw resource URI. */
|
/** The scheme part of a raw resource URI. */
|
||||||
public static final String RAW_RESOURCE_SCHEME = "rawresource";
|
public static final String RAW_RESOURCE_SCHEME = "rawresource";
|
||||||
|
|
||||||
private final Resources resources;
|
private final Context applicationContext;
|
||||||
private final String packageName;
|
|
||||||
|
|
||||||
@Nullable private Uri uri;
|
@Nullable private Uri uri;
|
||||||
@Nullable private AssetFileDescriptor assetFileDescriptor;
|
@Nullable private AssetFileDescriptor assetFileDescriptor;
|
||||||
@ -115,8 +122,7 @@ public final class RawResourceDataSource extends BaseDataSource {
|
|||||||
*/
|
*/
|
||||||
public RawResourceDataSource(Context context) {
|
public RawResourceDataSource(Context context) {
|
||||||
super(/* isNetwork= */ false);
|
super(/* isNetwork= */ false);
|
||||||
this.resources = context.getResources();
|
this.applicationContext = context.getApplicationContext();
|
||||||
this.packageName = context.getPackageName();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -124,11 +130,13 @@ public final class RawResourceDataSource extends BaseDataSource {
|
|||||||
Uri uri = dataSpec.uri.normalizeScheme();
|
Uri uri = dataSpec.uri.normalizeScheme();
|
||||||
this.uri = uri;
|
this.uri = uri;
|
||||||
|
|
||||||
|
Resources resources;
|
||||||
int resourceId;
|
int resourceId;
|
||||||
if (TextUtils.equals(RAW_RESOURCE_SCHEME, uri.getScheme())
|
if (TextUtils.equals(RAW_RESOURCE_SCHEME, uri.getScheme())
|
||||||
|| (TextUtils.equals(ContentResolver.SCHEME_ANDROID_RESOURCE, uri.getScheme())
|
|| (TextUtils.equals(ContentResolver.SCHEME_ANDROID_RESOURCE, uri.getScheme())
|
||||||
&& uri.getPathSegments().size() == 1
|
&& uri.getPathSegments().size() == 1
|
||||||
&& Assertions.checkNotNull(uri.getLastPathSegment()).matches("\\d+"))) {
|
&& Assertions.checkNotNull(uri.getLastPathSegment()).matches("\\d+"))) {
|
||||||
|
resources = applicationContext.getResources();
|
||||||
try {
|
try {
|
||||||
resourceId = Integer.parseInt(Assertions.checkNotNull(uri.getLastPathSegment()));
|
resourceId = Integer.parseInt(Assertions.checkNotNull(uri.getLastPathSegment()));
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
@ -142,12 +150,31 @@ public final class RawResourceDataSource extends BaseDataSource {
|
|||||||
if (path.startsWith("/")) {
|
if (path.startsWith("/")) {
|
||||||
path = path.substring(1);
|
path = path.substring(1);
|
||||||
}
|
}
|
||||||
@Nullable String host = uri.getHost();
|
String packageName =
|
||||||
String resourceName = (TextUtils.isEmpty(host) ? "" : (host + ":")) + path;
|
TextUtils.isEmpty(uri.getHost()) ? applicationContext.getPackageName() : uri.getHost();
|
||||||
resourceId =
|
if (packageName.equals(applicationContext.getPackageName())) {
|
||||||
|
resources = applicationContext.getResources();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
resources =
|
||||||
|
applicationContext.getPackageManager().getResourcesForApplication(packageName);
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
throw new RawResourceDataSourceException(
|
||||||
|
"Package in "
|
||||||
|
+ ContentResolver.SCHEME_ANDROID_RESOURCE
|
||||||
|
+ ":// URI not found. Check http://g.co/dev/packagevisibility.",
|
||||||
|
e,
|
||||||
|
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(
|
resources.getIdentifier(
|
||||||
resourceName, /* defType= */ "raw", /* defPackage= */ packageName);
|
packageName + ":" + path, /* defType= */ "raw", /* defPackage= */ null);
|
||||||
if (resourceId == 0) {
|
if (resourceIdFromName != 0) {
|
||||||
|
resourceId = resourceIdFromName;
|
||||||
|
} else {
|
||||||
throw new RawResourceDataSourceException(
|
throw new RawResourceDataSourceException(
|
||||||
"Resource not found.",
|
"Resource not found.",
|
||||||
/* cause= */ null,
|
/* cause= */ null,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user