summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xapi/current.txt27
-rw-r--r--core/java/android/content/ContentResolver.java67
-rw-r--r--core/java/android/provider/DocumentsContract.java82
-rw-r--r--core/java/android/provider/MediaStore.java264
-rw-r--r--core/tests/coretests/src/android/content/ContentResolverTest.java166
-rw-r--r--graphics/java/android/graphics/Point.java11
6 files changed, 416 insertions, 201 deletions
diff --git a/api/current.txt b/api/current.txt
index 49d63673de11..abba44742a11 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -9316,6 +9316,7 @@ package android.content {
method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
method public static boolean isSyncActive(android.accounts.Account, java.lang.String);
method public static boolean isSyncPending(android.accounts.Account, java.lang.String);
+ method public android.graphics.Bitmap loadThumbnail(android.net.Uri, android.util.Size, android.os.CancellationSignal) throws java.io.IOException;
method public void notifyChange(android.net.Uri, android.database.ContentObserver);
method public void notifyChange(android.net.Uri, android.database.ContentObserver, boolean);
method public void notifyChange(android.net.Uri, android.database.ContentObserver, int);
@@ -36810,7 +36811,7 @@ package android.provider {
public static abstract interface MediaStore.Audio.AlbumColumns {
field public static final java.lang.String ALBUM = "album";
- field public static final java.lang.String ALBUM_ART = "album_art";
+ field public static final deprecated java.lang.String ALBUM_ART = "album_art";
field public static final java.lang.String ALBUM_ID = "album_id";
field public static final java.lang.String ALBUM_KEY = "album_key";
field public static final java.lang.String ARTIST = "artist";
@@ -36932,7 +36933,7 @@ package android.provider {
}
public static abstract interface MediaStore.Audio.PlaylistsColumns {
- field public static final java.lang.String DATA = "_data";
+ field public static final deprecated java.lang.String DATA = "_data";
field public static final java.lang.String DATE_ADDED = "date_added";
field public static final java.lang.String DATE_MODIFIED = "date_modified";
field public static final java.lang.String NAME = "name";
@@ -36994,15 +36995,15 @@ package android.provider {
public static class MediaStore.Images.Thumbnails implements android.provider.BaseColumns {
ctor public MediaStore.Images.Thumbnails();
- method public static void cancelThumbnailRequest(android.content.ContentResolver, long);
- method public static void cancelThumbnailRequest(android.content.ContentResolver, long, long);
+ method public static deprecated void cancelThumbnailRequest(android.content.ContentResolver, long);
+ method public static deprecated void cancelThumbnailRequest(android.content.ContentResolver, long, long);
method public static android.net.Uri getContentUri(java.lang.String);
- method public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options);
- method public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options);
+ method public static deprecated android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options);
+ method public static deprecated android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options);
method public static final android.database.Cursor query(android.content.ContentResolver, android.net.Uri, java.lang.String[]);
method public static final android.database.Cursor queryMiniThumbnail(android.content.ContentResolver, long, int, java.lang.String[]);
method public static final android.database.Cursor queryMiniThumbnails(android.content.ContentResolver, android.net.Uri, int, java.lang.String[]);
- field public static final java.lang.String DATA = "_data";
+ field public static final deprecated java.lang.String DATA = "_data";
field public static final java.lang.String DEFAULT_SORT_ORDER = "image_id ASC";
field public static final android.net.Uri EXTERNAL_CONTENT_URI;
field public static final int FULL_SCREEN_KIND = 2; // 0x2
@@ -37017,7 +37018,7 @@ package android.provider {
}
public static abstract interface MediaStore.MediaColumns implements android.provider.BaseColumns {
- field public static final java.lang.String DATA = "_data";
+ field public static final deprecated java.lang.String DATA = "_data";
field public static final java.lang.String DATE_ADDED = "date_added";
field public static final java.lang.String DATE_MODIFIED = "date_modified";
field public static final java.lang.String DISPLAY_NAME = "_display_name";
@@ -37045,12 +37046,12 @@ package android.provider {
public static class MediaStore.Video.Thumbnails implements android.provider.BaseColumns {
ctor public MediaStore.Video.Thumbnails();
- method public static void cancelThumbnailRequest(android.content.ContentResolver, long);
- method public static void cancelThumbnailRequest(android.content.ContentResolver, long, long);
+ method public static deprecated void cancelThumbnailRequest(android.content.ContentResolver, long);
+ method public static deprecated void cancelThumbnailRequest(android.content.ContentResolver, long, long);
method public static android.net.Uri getContentUri(java.lang.String);
- method public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options);
- method public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options);
- field public static final java.lang.String DATA = "_data";
+ method public static deprecated android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options);
+ method public static deprecated android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options);
+ field public static final deprecated java.lang.String DATA = "_data";
field public static final java.lang.String DEFAULT_SORT_ORDER = "video_id ASC";
field public static final android.net.Uri EXTERNAL_CONTENT_URI;
field public static final int FULL_SCREEN_KIND = 2; // 0x2
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index c19909da3007..599c2d2d3594 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -35,6 +35,12 @@ import android.database.ContentObserver;
import android.database.CrossProcessCursorWrapper;
import android.database.Cursor;
import android.database.IContentObserver;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ImageDecoder;
+import android.graphics.ImageDecoder.ImageInfo;
+import android.graphics.ImageDecoder.Source;
+import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.net.Uri;
@@ -49,9 +55,11 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.provider.DocumentsContract;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
+import android.util.Size;
import com.android.internal.util.MimeIconUtils;
import com.android.internal.util.Preconditions;
@@ -68,6 +76,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -3188,4 +3197,62 @@ public abstract class ContentResolver {
}
return query;
}
+
+ /**
+ * Convenience method that efficiently loads a visual thumbnail for the
+ * given {@link Uri}. Internally calls
+ * {@link ContentProvider#openTypedAssetFile} on the remote provider, but
+ * also defensively resizes any returned content to match the requested
+ * target size.
+ *
+ * @param uri The item that should be visualized as a thumbnail.
+ * @param size The target area on the screen where this thumbnail will be
+ * shown. This is passed to the provider as {@link #EXTRA_SIZE}
+ * to help it avoid downloading or generating heavy resources.
+ * @param signal A signal to cancel the operation in progress.
+ * @return Valid {@link Bitmap} which is a visual thumbnail.
+ * @throws IOException If any trouble was encountered while generating or
+ * loading the thumbnail, or if
+ * {@link CancellationSignal#cancel()} was invoked.
+ */
+ public @NonNull Bitmap loadThumbnail(@NonNull Uri uri, @NonNull Size size,
+ @Nullable CancellationSignal signal) throws IOException {
+ Objects.requireNonNull(uri);
+ Objects.requireNonNull(size);
+
+ try (ContentProviderClient client = acquireContentProviderClient(uri)) {
+ return loadThumbnail(client, uri, size, signal, ImageDecoder.ALLOCATOR_DEFAULT);
+ }
+ }
+
+ /** {@hide} */
+ public static Bitmap loadThumbnail(@NonNull ContentProviderClient client, @NonNull Uri uri,
+ @NonNull Size size, @Nullable CancellationSignal signal, int allocator)
+ throws IOException {
+ Objects.requireNonNull(client);
+ Objects.requireNonNull(uri);
+ Objects.requireNonNull(size);
+
+ // Convert to Point, since that's what the API is defined as
+ final Bundle opts = new Bundle();
+ opts.putParcelable(EXTRA_SIZE, Point.convert(size));
+
+ return ImageDecoder.decodeBitmap(ImageDecoder.createSource(() -> {
+ return client.openTypedAssetFileDescriptor(uri, "image/*", opts, signal);
+ }), (ImageDecoder decoder, ImageInfo info, Source source) -> {
+ decoder.setAllocator(allocator);
+
+ // One last-ditch check to see if we've been canceled.
+ if (signal != null) signal.throwIfCanceled();
+
+ // We requested a rough thumbnail size, but the remote size may have
+ // returned something giant, so defensively scale down as needed.
+ final int widthSample = info.getSize().getWidth() / size.getWidth();
+ final int heightSample = info.getSize().getHeight() / size.getHeight();
+ final int sample = Math.min(widthSample, heightSample);
+ if (sample > 1) {
+ decoder.setTargetSampleSize(sample);
+ }
+ });
+ }
}
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 8c40e0e6cb8c..954d18abc6e1 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -34,6 +34,7 @@ import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.graphics.ImageDecoder;
import android.graphics.Matrix;
import android.graphics.Point;
import android.media.ExifInterface;
@@ -53,6 +54,7 @@ import android.system.ErrnoException;
import android.system.Os;
import android.util.DataUnit;
import android.util.Log;
+import android.util.Size;
import libcore.io.IoUtils;
@@ -136,10 +138,11 @@ public final class DocumentsContract {
public static final String EXTRA_EXCLUDE_SELF = "android.provider.extra.EXCLUDE_SELF";
/**
- * Included in {@link AssetFileDescriptor#getExtras()} when returned
- * thumbnail should be rotated.
+ * An extra number of degrees that an image should be rotated during the
+ * decode process to be presented correctly.
*
- * @see MediaStore.Images.ImageColumns#ORIENTATION
+ * @see AssetFileDescriptor#getExtras()
+ * @see android.provider.MediaStore.Images.ImageColumns#ORIENTATION
*/
public static final String EXTRA_ORIENTATION = "android.provider.extra.ORIENTATION";
@@ -1093,75 +1096,10 @@ public final class DocumentsContract {
/** {@hide} */
@UnsupportedAppUsage
- public static Bitmap getDocumentThumbnail(
- ContentProviderClient client, Uri documentUri, Point size, CancellationSignal signal)
- throws RemoteException, IOException {
- final Bundle openOpts = new Bundle();
- openOpts.putParcelable(ContentResolver.EXTRA_SIZE, size);
-
- AssetFileDescriptor afd = null;
- Bitmap bitmap = null;
- try {
- afd = client.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal);
-
- final FileDescriptor fd = afd.getFileDescriptor();
- final long offset = afd.getStartOffset();
-
- // Try seeking on the returned FD, since it gives us the most
- // optimal decode path; otherwise fall back to buffering.
- BufferedInputStream is = null;
- try {
- Os.lseek(fd, offset, SEEK_SET);
- } catch (ErrnoException e) {
- is = new BufferedInputStream(new FileInputStream(fd), THUMBNAIL_BUFFER_SIZE);
- is.mark(THUMBNAIL_BUFFER_SIZE);
- }
-
- // We requested a rough thumbnail size, but the remote size may have
- // returned something giant, so defensively scale down as needed.
- final BitmapFactory.Options opts = new BitmapFactory.Options();
- opts.inJustDecodeBounds = true;
- if (is != null) {
- BitmapFactory.decodeStream(is, null, opts);
- } else {
- BitmapFactory.decodeFileDescriptor(fd, null, opts);
- }
-
- final int widthSample = opts.outWidth / size.x;
- final int heightSample = opts.outHeight / size.y;
-
- opts.inJustDecodeBounds = false;
- opts.inSampleSize = Math.min(widthSample, heightSample);
- if (is != null) {
- is.reset();
- bitmap = BitmapFactory.decodeStream(is, null, opts);
- } else {
- try {
- Os.lseek(fd, offset, SEEK_SET);
- } catch (ErrnoException e) {
- e.rethrowAsIOException();
- }
- bitmap = BitmapFactory.decodeFileDescriptor(fd, null, opts);
- }
-
- // Transform the bitmap if requested. We use a side-channel to
- // communicate the orientation, since EXIF thumbnails don't contain
- // the rotation flags of the original image.
- final Bundle extras = afd.getExtras();
- final int orientation = (extras != null) ? extras.getInt(EXTRA_ORIENTATION, 0) : 0;
- if (orientation != 0) {
- final int width = bitmap.getWidth();
- final int height = bitmap.getHeight();
-
- final Matrix m = new Matrix();
- m.setRotate(orientation, width / 2, height / 2);
- bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, m, false);
- }
- } finally {
- IoUtils.closeQuietly(afd);
- }
-
- return bitmap;
+ public static Bitmap getDocumentThumbnail(ContentProviderClient client, Uri documentUri,
+ Point size, CancellationSignal signal) throws IOException {
+ return ContentResolver.loadThumbnail(client, documentUri, Point.convert(size), signal,
+ ImageDecoder.ALLOCATOR_DEFAULT);
}
/**
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 828fd7386d80..f5660b950c0a 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -29,7 +29,6 @@ import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.UriPermission;
-import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.graphics.Bitmap;
@@ -39,6 +38,7 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Environment;
+import android.os.OperationCanceledException;
import android.os.RemoteException;
import android.service.media.CameraPrewarmService;
import android.util.ArrayMap;
@@ -402,7 +402,16 @@ public final class MediaStore {
* access.
* <p>
* Type: TEXT
+ *
+ * @deprecated Apps may not have filesystem permissions to directly
+ * access this path. Instead of trying to open this path
+ * directly, apps should use
+ * {@link ContentResolver#openFileDescriptor(Uri, String)}
+ * to gain access. This value will always be {@code NULL}
+ * for apps targeting
+ * {@link android.os.Build.VERSION_CODES#Q} or higher.
*/
+ @Deprecated
public static final String DATA = "_data";
/**
@@ -641,6 +650,7 @@ public final class MediaStore {
* This class is used internally by Images.Thumbnails and Video.Thumbnails, it's not intended
* to be accessed elsewhere.
*/
+ @Deprecated
private static class InternalThumbnails implements BaseColumns {
/**
* Currently outstanding thumbnail requests that can be cancelled.
@@ -654,13 +664,14 @@ public final class MediaStore {
*
* @see #cancelThumbnail(ContentResolver, Uri)
*/
+ @Deprecated
static @Nullable Bitmap getThumbnail(@NonNull ContentResolver cr, @NonNull Uri uri,
int kind, @Nullable BitmapFactory.Options opts) {
- final Bundle openOpts = new Bundle();
+ final Point size;
if (kind == ThumbnailConstants.MICRO_KIND) {
- openOpts.putParcelable(ContentResolver.EXTRA_SIZE, ThumbnailConstants.MICRO_SIZE);
+ size = ThumbnailConstants.MICRO_SIZE;
} else if (kind == ThumbnailConstants.MINI_KIND) {
- openOpts.putParcelable(ContentResolver.EXTRA_SIZE, ThumbnailConstants.MINI_SIZE);
+ size = ThumbnailConstants.MINI_SIZE;
} else {
throw new IllegalArgumentException("Unsupported kind: " + kind);
}
@@ -674,9 +685,8 @@ public final class MediaStore {
}
}
- try (AssetFileDescriptor afd = cr.openTypedAssetFileDescriptor(uri,
- "image/*", openOpts, signal)) {
- return BitmapFactory.decodeFileDescriptor(afd.getFileDescriptor(), null, opts);
+ try {
+ return cr.loadThumbnail(uri, Point.convert(size), signal);
} catch (IOException e) {
Log.w(TAG, "Failed to obtain thumbnail for " + uri, e);
return null;
@@ -693,6 +703,7 @@ public final class MediaStore {
* Only the original process which made the request can cancel their own
* requests.
*/
+ @Deprecated
static void cancelThumbnail(@NonNull ContentResolver cr, @NonNull Uri uri) {
synchronized (sPending) {
final CancellationSignal signal = sPending.get(uri);
@@ -936,9 +947,8 @@ public final class MediaStore {
}
/**
- * This class allows developers to query and get two kinds of thumbnails:
- * MINI_KIND: 512 x 384 thumbnail
- * MICRO_KIND: 96 x 96 thumbnail
+ * This class provides utility methods to obtain thumbnails for various
+ * {@link Images} items.
*/
public static class Thumbnails implements BaseColumns {
public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) {
@@ -958,13 +968,19 @@ public final class MediaStore {
}
/**
- * This method cancels the thumbnail request so clients waiting for getThumbnail will be
- * interrupted and return immediately. Only the original process which made the getThumbnail
- * requests can cancel their own requests.
+ * Cancel any outstanding {@link #getThumbnail} requests, causing
+ * them to return by throwing a {@link OperationCanceledException}.
+ * <p>
+ * This method has no effect on
+ * {@link ContentResolver#loadThumbnail} calls, since they provide
+ * their own {@link CancellationSignal}.
*
- * @param cr ContentResolver
- * @param origId original image id
+ * @deprecated Callers should migrate to using
+ * {@link ContentResolver#loadThumbnail}, since it
+ * offers richer control over requested thumbnail sizes
+ * and cancellation behavior.
*/
+ @Deprecated
public static void cancelThumbnailRequest(ContentResolver cr, long origId) {
final Uri uri = ContentUris.withAppendedId(
Images.Media.EXTERNAL_CONTENT_URI, origId);
@@ -972,51 +988,66 @@ public final class MediaStore {
}
/**
- * This method checks if the thumbnails of the specified image (origId) has been created.
- * It will be blocked until the thumbnails are generated.
+ * Return thumbnail representing a specific image item. If a
+ * thumbnail doesn't exist, this method will block until it's
+ * generated. Callers are responsible for their own in-memory
+ * caching of returned values.
*
- * @param cr ContentResolver used to dispatch queries to MediaProvider.
- * @param origId Original image id associated with thumbnail of interest.
- * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
- * @param options this is only used for MINI_KIND when decoding the Bitmap
- * @return A Bitmap instance. It could be null if the original image
- * associated with origId doesn't exist or memory is not enough.
- */
- public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
+ * @param imageId the image item to obtain a thumbnail for.
+ * @param kind optimal thumbnail size desired.
+ * @return decoded thumbnail, or {@code null} if problem was
+ * encountered.
+ * @deprecated Callers should migrate to using
+ * {@link ContentResolver#loadThumbnail}, since it
+ * offers richer control over requested thumbnail sizes
+ * and cancellation behavior.
+ */
+ @Deprecated
+ public static Bitmap getThumbnail(ContentResolver cr, long imageId, int kind,
BitmapFactory.Options options) {
final Uri uri = ContentUris.withAppendedId(
- Images.Media.EXTERNAL_CONTENT_URI, origId);
+ Images.Media.EXTERNAL_CONTENT_URI, imageId);
return InternalThumbnails.getThumbnail(cr, uri, kind, options);
}
/**
- * This method cancels the thumbnail request so clients waiting for getThumbnail will be
- * interrupted and return immediately. Only the original process which made the getThumbnail
- * requests can cancel their own requests.
+ * Cancel any outstanding {@link #getThumbnail} requests, causing
+ * them to return by throwing a {@link OperationCanceledException}.
+ * <p>
+ * This method has no effect on
+ * {@link ContentResolver#loadThumbnail} calls, since they provide
+ * their own {@link CancellationSignal}.
*
- * @param cr ContentResolver
- * @param origId original image id
- * @param groupId the same groupId used in getThumbnail.
+ * @deprecated Callers should migrate to using
+ * {@link ContentResolver#loadThumbnail}, since it
+ * offers richer control over requested thumbnail sizes
+ * and cancellation behavior.
*/
- public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) {
+ @Deprecated
+ public static void cancelThumbnailRequest(ContentResolver cr, long origId,
+ long groupId) {
cancelThumbnailRequest(cr, origId);
}
/**
- * This method checks if the thumbnails of the specified image (origId) has been created.
- * It will be blocked until the thumbnails are generated.
+ * Return thumbnail representing a specific image item. If a
+ * thumbnail doesn't exist, this method will block until it's
+ * generated. Callers are responsible for their own in-memory
+ * caching of returned values.
*
- * @param cr ContentResolver used to dispatch queries to MediaProvider.
- * @param origId Original image id associated with thumbnail of interest.
- * @param groupId the id of group to which this request belongs
- * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
- * @param options this is only used for MINI_KIND when decoding the Bitmap
- * @return A Bitmap instance. It could be null if the original image
- * associated with origId doesn't exist or memory is not enough.
- */
- public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId,
+ * @param imageId the image item to obtain a thumbnail for.
+ * @param kind optimal thumbnail size desired.
+ * @return decoded thumbnail, or {@code null} if problem was
+ * encountered.
+ * @deprecated Callers should migrate to using
+ * {@link ContentResolver#loadThumbnail}, since it
+ * offers richer control over requested thumbnail sizes
+ * and cancellation behavior.
+ */
+ @Deprecated
+ public static Bitmap getThumbnail(ContentResolver cr, long imageId, long groupId,
int kind, BitmapFactory.Options options) {
- return getThumbnail(cr, origId, kind, options);
+ return getThumbnail(cr, imageId, kind, options);
}
/**
@@ -1059,7 +1090,16 @@ public final class MediaStore {
* access.
* <p>
* Type: TEXT
+ *
+ * @deprecated Apps may not have filesystem permissions to directly
+ * access this path. Instead of trying to open this path
+ * directly, apps should use
+ * {@link ContentResolver#loadThumbnail}
+ * to gain access. This value will always be
+ * {@code NULL} for apps targeting
+ * {@link android.os.Build.VERSION_CODES#Q} or higher.
*/
+ @Deprecated
public static final String DATA = "_data";
/**
@@ -1509,7 +1549,16 @@ public final class MediaStore {
* access.
* <p>
* Type: TEXT
+ *
+ * @deprecated Apps may not have filesystem permissions to directly
+ * access this path. Instead of trying to open this path
+ * directly, apps should use
+ * {@link ContentResolver#openFileDescriptor(Uri, String)}
+ * to gain access. This value will always be
+ * {@code NULL} for apps targeting
+ * {@link android.os.Build.VERSION_CODES#Q} or higher.
*/
+ @Deprecated
public static final String DATA = "_data";
/**
@@ -1790,7 +1839,16 @@ public final class MediaStore {
/**
* Cached album art.
* <P>Type: TEXT</P>
+ *
+ * @deprecated Apps may not have filesystem permissions to directly
+ * access this path. Instead of trying to open this path
+ * directly, apps should use
+ * {@link ContentResolver#loadThumbnail}
+ * to gain access. This value will always be
+ * {@code NULL} for apps targeting
+ * {@link android.os.Build.VERSION_CODES#Q} or higher.
*/
+ @Deprecated
public static final String ALBUM_ART = "album_art";
}
@@ -2009,20 +2067,24 @@ public final class MediaStore {
}
/**
- * This class allows developers to query and get two kinds of thumbnails:
- * MINI_KIND: 512 x 384 thumbnail
- * MICRO_KIND: 96 x 96 thumbnail
- *
+ * This class provides utility methods to obtain thumbnails for various
+ * {@link Video} items.
*/
public static class Thumbnails implements BaseColumns {
/**
- * This method cancels the thumbnail request so clients waiting for getThumbnail will be
- * interrupted and return immediately. Only the original process which made the getThumbnail
- * requests can cancel their own requests.
+ * Cancel any outstanding {@link #getThumbnail} requests, causing
+ * them to return by throwing a {@link OperationCanceledException}.
+ * <p>
+ * This method has no effect on
+ * {@link ContentResolver#loadThumbnail} calls, since they provide
+ * their own {@link CancellationSignal}.
*
- * @param cr ContentResolver
- * @param origId original video id
+ * @deprecated Callers should migrate to using
+ * {@link ContentResolver#loadThumbnail}, since it
+ * offers richer control over requested thumbnail sizes
+ * and cancellation behavior.
*/
+ @Deprecated
public static void cancelThumbnailRequest(ContentResolver cr, long origId) {
final Uri uri = ContentUris.withAppendedId(
Video.Media.EXTERNAL_CONTENT_URI, origId);
@@ -2030,51 +2092,66 @@ public final class MediaStore {
}
/**
- * This method checks if the thumbnails of the specified image (origId) has been created.
- * It will be blocked until the thumbnails are generated.
+ * Return thumbnail representing a specific video item. If a
+ * thumbnail doesn't exist, this method will block until it's
+ * generated. Callers are responsible for their own in-memory
+ * caching of returned values.
*
- * @param cr ContentResolver used to dispatch queries to MediaProvider.
- * @param origId Original image id associated with thumbnail of interest.
- * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
- * @param options this is only used for MINI_KIND when decoding the Bitmap
- * @return A Bitmap instance. It could be null if the original image
- * associated with origId doesn't exist or memory is not enough.
- */
- public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
+ * @param videoId the video item to obtain a thumbnail for.
+ * @param kind optimal thumbnail size desired.
+ * @return decoded thumbnail, or {@code null} if problem was
+ * encountered.
+ * @deprecated Callers should migrate to using
+ * {@link ContentResolver#loadThumbnail}, since it
+ * offers richer control over requested thumbnail sizes
+ * and cancellation behavior.
+ */
+ @Deprecated
+ public static Bitmap getThumbnail(ContentResolver cr, long videoId, int kind,
BitmapFactory.Options options) {
final Uri uri = ContentUris.withAppendedId(
- Video.Media.EXTERNAL_CONTENT_URI, origId);
+ Video.Media.EXTERNAL_CONTENT_URI, videoId);
return InternalThumbnails.getThumbnail(cr, uri, kind, options);
}
/**
- * This method checks if the thumbnails of the specified image (origId) has been created.
- * It will be blocked until the thumbnails are generated.
+ * Cancel any outstanding {@link #getThumbnail} requests, causing
+ * them to return by throwing a {@link OperationCanceledException}.
+ * <p>
+ * This method has no effect on
+ * {@link ContentResolver#loadThumbnail} calls, since they provide
+ * their own {@link CancellationSignal}.
*
- * @param cr ContentResolver used to dispatch queries to MediaProvider.
- * @param origId Original image id associated with thumbnail of interest.
- * @param groupId the id of group to which this request belongs
- * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND
- * @param options this is only used for MINI_KIND when decoding the Bitmap
- * @return A Bitmap instance. It could be null if the original image associated with
- * origId doesn't exist or memory is not enough.
- */
- public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId,
- int kind, BitmapFactory.Options options) {
- return getThumbnail(cr, origId, kind, options);
+ * @deprecated Callers should migrate to using
+ * {@link ContentResolver#loadThumbnail}, since it
+ * offers richer control over requested thumbnail sizes
+ * and cancellation behavior.
+ */
+ @Deprecated
+ public static void cancelThumbnailRequest(ContentResolver cr, long videoId,
+ long groupId) {
+ cancelThumbnailRequest(cr, videoId);
}
/**
- * This method cancels the thumbnail request so clients waiting for getThumbnail will be
- * interrupted and return immediately. Only the original process which made the getThumbnail
- * requests can cancel their own requests.
+ * Return thumbnail representing a specific video item. If a
+ * thumbnail doesn't exist, this method will block until it's
+ * generated. Callers are responsible for their own in-memory
+ * caching of returned values.
*
- * @param cr ContentResolver
- * @param origId original video id
- * @param groupId the same groupId used in getThumbnail.
+ * @param videoId the video item to obtain a thumbnail for.
+ * @param kind optimal thumbnail size desired.
+ * @return decoded thumbnail, or {@code null} if problem was
+ * encountered.
+ * @deprecated Callers should migrate to using
+ * {@link ContentResolver#loadThumbnail}, since it
+ * offers richer control over requested thumbnail sizes
+ * and cancellation behavior.
*/
- public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) {
- cancelThumbnailRequest(cr, origId);
+ @Deprecated
+ public static Bitmap getThumbnail(ContentResolver cr, long videoId, long groupId,
+ int kind, BitmapFactory.Options options) {
+ return getThumbnail(cr, videoId, kind, options);
}
/**
@@ -2110,14 +2187,17 @@ public final class MediaStore {
/**
* Path to the thumbnail file on disk.
* <p>
- * Note that apps may not have filesystem permissions to directly
- * access this path. Instead of trying to open this path directly,
- * apps should use
- * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain
- * access.
- * <p>
* Type: TEXT
+ *
+ * @deprecated Apps may not have filesystem permissions to directly
+ * access this path. Instead of trying to open this path
+ * directly, apps should use
+ * {@link ContentResolver#openFileDescriptor(Uri, String)}
+ * to gain access. This value will always be
+ * {@code NULL} for apps targeting
+ * {@link android.os.Build.VERSION_CODES#Q} or higher.
*/
+ @Deprecated
public static final String DATA = "_data";
/**
diff --git a/core/tests/coretests/src/android/content/ContentResolverTest.java b/core/tests/coretests/src/android/content/ContentResolverTest.java
index 6256d08adb00..0036186994fe 100644
--- a/core/tests/coretests/src/android/content/ContentResolverTest.java
+++ b/core/tests/coretests/src/android/content/ContentResolverTest.java
@@ -13,31 +13,149 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package android.content;
-import android.content.ContentResolver;
-import android.provider.ContactsContract;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.Suppress;
-
-@Suppress // Failing.
-public class ContentResolverTest extends AndroidTestCase {
- private ContentResolver mContentResolver;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mContentResolver = mContext.getContentResolver();
- }
-
- @LargeTest
- public void testCursorFinalizer() throws Exception {
- // TODO: Want a test case that more predictably reproduce this issue. Selected
- // 600 as this causes the problem 100% of the runs on current hw, it might not
- // do so on some other configuration though.
- for (int i = 0; i < 600; i++) {
- mContentResolver.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
- }
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.res.AssetFileDescriptor;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ImageDecoder;
+import android.graphics.Paint;
+import android.net.Uri;
+import android.os.MemoryFile;
+import android.os.ParcelFileDescriptor;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Size;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class ContentResolverTest {
+
+ private ContentResolver mResolver;
+ private IContentProvider mProvider;
+ private ContentProviderClient mClient;
+
+ private int mSize = 256_000;
+ private MemoryFile mImage;
+
+ @Before
+ public void setUp() throws Exception {
+ mResolver = InstrumentationRegistry.getInstrumentation().getTargetContext()
+ .getContentResolver();
+ mProvider = mock(IContentProvider.class);
+ mClient = new ContentProviderClient(mResolver, mProvider, false);
+
+ mImage = new MemoryFile("temp.png", mSize);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mImage.close();
+ mImage = null;
+ }
+
+ private void initImage(int width, int height) throws Exception {
+ final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(bitmap);
+
+ canvas.drawColor(Color.RED);
+
+ final Paint paint = new Paint();
+ paint.setColor(Color.BLUE);
+ paint.setStyle(Paint.Style.FILL);
+ canvas.drawRect(0, 0, width / 2, height / 2, paint);
+
+ bitmap.compress(Bitmap.CompressFormat.PNG, 90, mImage.getOutputStream());
+
+ final AssetFileDescriptor afd = new AssetFileDescriptor(
+ new ParcelFileDescriptor(mImage.getFileDescriptor()), 0, mSize, null);
+ when(mProvider.openTypedAssetFile(any(), any(), any(), any(), any())).thenReturn(afd);
+ }
+
+ private static void assertImageAspectAndContents(Bitmap bitmap) {
+ // And correct aspect ratio
+ final int before = (100 * 1280) / 960;
+ final int after = (100 * bitmap.getWidth()) / bitmap.getHeight();
+ assertEquals(before, after);
+
+ // And scaled correctly
+ final int halfX = bitmap.getWidth() / 2;
+ final int halfY = bitmap.getHeight() / 2;
+ assertEquals(Color.BLUE, bitmap.getPixel(halfX - 10, halfY - 10));
+ assertEquals(Color.RED, bitmap.getPixel(halfX + 10, halfY - 10));
+ assertEquals(Color.RED, bitmap.getPixel(halfX - 10, halfY + 10));
+ assertEquals(Color.RED, bitmap.getPixel(halfX + 10, halfY + 10));
+ }
+
+ @Test
+ public void testLoadThumbnail_Normal() throws Exception {
+ initImage(1280, 960);
+
+ Bitmap res = ContentResolver.loadThumbnail(mClient,
+ Uri.parse("content://com.example/"), new Size(1280, 960), null,
+ ImageDecoder.ALLOCATOR_SOFTWARE);
+
+ // Size should be untouched
+ assertEquals(1280, res.getWidth());
+ assertEquals(960, res.getHeight());
+
+ assertImageAspectAndContents(res);
+ }
+
+ @Test
+ public void testLoadThumbnail_Scaling() throws Exception {
+ initImage(1280, 960);
+
+ Bitmap res = ContentResolver.loadThumbnail(mClient,
+ Uri.parse("content://com.example/"), new Size(320, 240), null,
+ ImageDecoder.ALLOCATOR_SOFTWARE);
+
+ // Size should be much smaller
+ assertTrue(res.getWidth() <= 640);
+ assertTrue(res.getHeight() <= 480);
+
+ assertImageAspectAndContents(res);
+ }
+
+ @Test
+ public void testLoadThumbnail_Aspect() throws Exception {
+ initImage(1280, 960);
+
+ Bitmap res = ContentResolver.loadThumbnail(mClient,
+ Uri.parse("content://com.example/"), new Size(240, 320), null,
+ ImageDecoder.ALLOCATOR_SOFTWARE);
+
+ // Size should be much smaller
+ assertTrue(res.getWidth() <= 640);
+ assertTrue(res.getHeight() <= 480);
+
+ assertImageAspectAndContents(res);
+ }
+
+ @Test
+ public void testLoadThumbnail_Tiny() throws Exception {
+ initImage(32, 24);
+
+ Bitmap res = ContentResolver.loadThumbnail(mClient,
+ Uri.parse("content://com.example/"), new Size(320, 240), null,
+ ImageDecoder.ALLOCATOR_SOFTWARE);
+
+ // Size should be untouched
+ assertEquals(32, res.getWidth());
+ assertEquals(24, res.getHeight());
+
+ assertImageAspectAndContents(res);
}
}
diff --git a/graphics/java/android/graphics/Point.java b/graphics/java/android/graphics/Point.java
index f291e2702948..ea9350174f97 100644
--- a/graphics/java/android/graphics/Point.java
+++ b/graphics/java/android/graphics/Point.java
@@ -19,6 +19,7 @@ package android.graphics;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Size;
import android.util.proto.ProtoOutputStream;
import java.io.PrintWriter;
@@ -168,4 +169,14 @@ public class Point implements Parcelable {
x = in.readInt();
y = in.readInt();
}
+
+ /** {@hide} */
+ public static @NonNull Point convert(@NonNull Size size) {
+ return new Point(size.getWidth(), size.getHeight());
+ }
+
+ /** {@hide} */
+ public static @NonNull Size convert(@NonNull Point point) {
+ return new Size(point.x, point.y);
+ }
}