summaryrefslogtreecommitdiff
path: root/media/java
diff options
context:
space:
mode:
Diffstat (limited to 'media/java')
-rw-r--r--media/java/android/media/AudioSystem.java10
-rw-r--r--media/java/android/media/AudioTrack.java4
-rw-r--r--media/java/android/media/ExifInterface.java750
-rw-r--r--media/java/android/media/MediaCodecInfo.java1
-rw-r--r--media/java/android/media/MediaDescription.java62
-rw-r--r--media/java/android/media/MediaMetadata.java50
-rw-r--r--media/java/android/media/MediaRecorder.java14
-rw-r--r--media/java/android/media/browse/MediaBrowser.java41
-rw-r--r--media/java/android/media/session/MediaSession.java6
-rw-r--r--media/java/android/media/tv/TvView.java4
-rw-r--r--media/java/android/mtp/MtpConstants.java37
-rw-r--r--media/java/android/mtp/MtpDevice.java48
-rw-r--r--media/java/android/mtp/MtpDeviceInfo.java16
-rw-r--r--media/java/android/mtp/MtpObjectInfo.java25
-rw-r--r--media/java/android/mtp/MtpServer.java9
-rw-r--r--media/java/android/mtp/MtpStorageInfo.java6
-rw-r--r--media/java/android/service/media/MediaBrowserService.java41
17 files changed, 856 insertions, 268 deletions
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index f597440ccbb3..f9bc95c88c45 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -805,6 +805,16 @@ public class AudioSystem
}
}
+ /**
+ * @hide
+ * @return whether the system uses a single volume stream.
+ */
+ public static boolean isSingleVolume(Context context) {
+ boolean forceSingleVolume = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_single_volume);
+ return getPlatformType(context) == PLATFORM_TELEVISION || forceSingleVolume;
+ }
+
public static final int DEFAULT_MUTE_STREAMS_AFFECTED =
(1 << STREAM_MUSIC) |
(1 << STREAM_RING) |
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 12d5eade36d2..b5e3af07b7e6 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -2483,8 +2483,8 @@ public class AudioTrack extends PlayerBase
//--------------------
/**
* The list of AudioRouting.OnRoutingChangedListener interfaces added (with
- * {@link AudioRecord#addOnRoutingChangedListener} by an app to receive
- * (re)routing notifications.
+ * {@link #addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, Handler)}
+ * by an app to receive (re)routing notifications.
*/
@GuardedBy("mRoutingChangeListeners")
private ArrayMap<AudioRouting.OnRoutingChangedListener,
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 0e7f995af6e8..2c98e98eb963 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -42,6 +42,7 @@ import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Arrays;
@@ -67,8 +68,9 @@ import libcore.io.Streams;
public class ExifInterface {
private static final String TAG = "ExifInterface";
private static final boolean DEBUG = false;
+ private static final boolean HANDLE_RAW = false;
- // The Exif tag names
+ // The Exif tag names. See Tiff 6.0 Section 3 and Section 8.
/** Type is String. */
public static final String TAG_ARTIST = "Artist";
/** Type is int. */
@@ -213,6 +215,8 @@ public class ExifInterface {
public static final String TAG_MAX_APERTURE_VALUE = "MaxApertureValue";
/** Type is int. */
public static final String TAG_METERING_MODE = "MeteringMode";
+ /** Type is int. */
+ public static final String TAG_NEW_SUBFILE_TYPE = "NewSubfileType";
/** Type is String. */
public static final String TAG_OECF = "OECF";
/** Type is int. */
@@ -237,6 +241,8 @@ public class ExifInterface {
public static final String TAG_SPATIAL_FREQUENCY_RESPONSE = "SpatialFrequencyResponse";
/** Type is String. */
public static final String TAG_SPECTRAL_SENSITIVITY = "SpectralSensitivity";
+ /** Type is int. */
+ public static final String TAG_SUBFILE_TYPE = "SubfileType";
/** Type is String. */
public static final String TAG_SUBSEC_TIME = "SubSecTime";
/**
@@ -341,17 +347,26 @@ public class ExifInterface {
public static final String TAG_THUMBNAIL_IMAGE_LENGTH = "ThumbnailImageLength";
/** Type is int. */
public static final String TAG_THUMBNAIL_IMAGE_WIDTH = "ThumbnailImageWidth";
+ /** Type is int. DNG Specification 1.4.0.0. Section 4 */
+ public static final String TAG_DEFAULT_CROP_SIZE = "DefaultCropSize";
- // Private tags used for pointing the other IFD offset. The types of the following tags are int.
+ /**
+ * Private tags used for pointing the other IFD offsets.
+ * The types of the following tags are int.
+ * See JEITA CP-3451C Section 4.6.3: Exif-specific IFD.
+ * For SubIFD, see Note 1 of Adobe PageMaker® 6.0 TIFF Technical Notes.
+ */
private static final String TAG_EXIF_IFD_POINTER = "ExifIFDPointer";
private static final String TAG_GPS_INFO_IFD_POINTER = "GPSInfoIFDPointer";
private static final String TAG_INTEROPERABILITY_IFD_POINTER = "InteroperabilityIFDPointer";
+ private static final String TAG_SUB_IFD_POINTER = "SubIFDPointer";
// Private tags used for thumbnail information.
private static final String TAG_HAS_THUMBNAIL = "HasThumbnail";
private static final String TAG_THUMBNAIL_OFFSET = "ThumbnailOffset";
private static final String TAG_THUMBNAIL_LENGTH = "ThumbnailLength";
private static final String TAG_THUMBNAIL_DATA = "ThumbnailData";
+ private static final int MAX_THUMBNAIL_SIZE = 512;
// Constants used for the Orientation Exif tag.
public static final int ORIENTATION_UNDEFINED = 0;
@@ -370,8 +385,16 @@ public class ExifInterface {
public static final int WHITEBALANCE_AUTO = 0;
public static final int WHITEBALANCE_MANUAL = 1;
+ // Maximum size for checking file type signature (see image_type_recognition_lite.cc)
+ private static final int SIGNATURE_CHECK_SIZE = 5000;
+
private static final byte[] JPEG_SIGNATURE = new byte[] {(byte) 0xff, (byte) 0xd8, (byte) 0xff};
private static final int JPEG_SIGNATURE_SIZE = 3;
+ private static final String RAF_SIGNATURE = "FUJIFILMCCD-RAW";
+ private static final int RAF_SIGNATURE_SIZE = 15;
+ private static final int RAF_OFFSET_TO_JPEG_IMAGE_OFFSET = 84;
+ private static final int RAF_INFO_SIZE = 160;
+ private int mRafJpegOffset, mRafJpegLength, mRafCfaHeaderOffset, mRafCfaHeaderLength;
private static SimpleDateFormat sFormatter;
@@ -380,11 +403,15 @@ public class ExifInterface {
// They are called "Image File Directory". They have multiple data formats to cover various
// image metadata from GPS longitude to camera model name.
- // Types of Exif byte alignments (see JEITA CP-3451 page 10)
+ // Types of Exif byte alignments (see JEITA CP-3451C Section 4.5.2)
private static final short BYTE_ALIGN_II = 0x4949; // II: Intel order
private static final short BYTE_ALIGN_MM = 0x4d4d; // MM: Motorola order
- // Formats for the value in IFD entry (See TIFF 6.0 spec Types page 15).
+ // TIFF Header Fixed Constant (see JEITA CP-3451C Section 4.5.2)
+ private static final byte START_CODE = 0x2a; // 42
+ private static final int IFD_OFFSET = 8;
+
+ // Formats for the value in IFD entry (See TIFF 6.0 Section 2, "Image File Directory".)
private static final int IFD_FORMAT_BYTE = 1;
private static final int IFD_FORMAT_STRING = 2;
private static final int IFD_FORMAT_USHORT = 3;
@@ -410,6 +437,27 @@ public class ExifInterface {
0x41, 0x53, 0x43, 0x49, 0x49, 0x0, 0x0, 0x0
};
+ /**
+ * Constants used for Compression tag.
+ * For Value 1, 2, 32773, see TIFF 6.0 Spec Section 3: Bilevel Images, Compression
+ * For Value 6, see TIFF 6.0 Spec Section 22: JPEG Compression, Extensions to Existing Fields
+ * For Value 7, 8, 34892, see DNG Specification 1.4.0.0. Section 3, Compression
+ */
+ private static final int DATA_UNCOMPRESSED = 1;
+ private static final int DATA_HUFFMAN_COMPRESSED = 2;
+ private static final int DATA_JPEG = 6;
+ private static final int DATA_JPEG_COMPRESSED = 7;
+ private static final int DATA_DEFLATE_ZIP = 8;
+ private static final int DATA_PACK_BITS_COMPRESSED = 32773;
+ private static final int DATA_LOSSY_JPEG = 34892;
+
+ /**
+ * Constants used for NewSubfileType tag.
+ * See TIFF 6.0 Spec Section 8
+ * */
+ private static final int ORIGINAL_RESOLUTION_IMAGE = 0;
+ private static final int REDUCED_RESOLUTION_IMAGE = 1;
+
// A class for indicating EXIF rational type.
private static class Rational {
public final long numerator;
@@ -814,8 +862,11 @@ public class ExifInterface {
}
}
- // Primary image IFD TIFF tags (See JEITA CP-3451 Table 14. page 54).
+ // Primary image IFD TIFF tags (See JEITA CP-3451C Section 4.6.8 Tag Support Levels)
private static final ExifTag[] IFD_TIFF_TAGS = new ExifTag[] {
+ // For below two, see TIFF 6.0 Spec Section 3: Bilevel Images.
+ new ExifTag(TAG_NEW_SUBFILE_TYPE, 254, IFD_FORMAT_ULONG),
+ new ExifTag(TAG_SUBFILE_TYPE, 255, IFD_FORMAT_ULONG),
new ExifTag(TAG_IMAGE_WIDTH, 256, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
new ExifTag(TAG_IMAGE_LENGTH, 257, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
new ExifTag(TAG_BITS_PER_SAMPLE, 258, IFD_FORMAT_USHORT),
@@ -839,6 +890,8 @@ public class ExifInterface {
new ExifTag(TAG_ARTIST, 315, IFD_FORMAT_STRING),
new ExifTag(TAG_WHITE_POINT, 318, IFD_FORMAT_URATIONAL),
new ExifTag(TAG_PRIMARY_CHROMATICITIES, 319, IFD_FORMAT_URATIONAL),
+ // See Adobe PageMaker® 6.0 TIFF Technical Notes, Note 1.
+ new ExifTag(TAG_SUB_IFD_POINTER, 330, IFD_FORMAT_ULONG),
new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT, 513, IFD_FORMAT_ULONG),
new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, 514, IFD_FORMAT_ULONG),
new ExifTag(TAG_Y_CB_CR_COEFFICIENTS, 529, IFD_FORMAT_URATIONAL),
@@ -847,10 +900,10 @@ public class ExifInterface {
new ExifTag(TAG_REFERENCE_BLACK_WHITE, 532, IFD_FORMAT_URATIONAL),
new ExifTag(TAG_COPYRIGHT, 33432, IFD_FORMAT_STRING),
new ExifTag(TAG_EXIF_IFD_POINTER, 34665, IFD_FORMAT_ULONG),
- new ExifTag(TAG_GPS_INFO_IFD_POINTER, 34853, IFD_FORMAT_ULONG),
+ new ExifTag(TAG_GPS_INFO_IFD_POINTER, 34853, IFD_FORMAT_ULONG)
};
- // Primary image IFD Exif Private tags (See JEITA CP-3451 Table 15. page 55).
+ // Primary image IFD Exif Private tags (See JEITA CP-3451C Section 4.6.8 Tag Support Levels)
private static final ExifTag[] IFD_EXIF_TAGS = new ExifTag[] {
new ExifTag(TAG_EXPOSURE_TIME, 33434, IFD_FORMAT_URATIONAL),
new ExifTag(TAG_F_NUMBER, 33437, IFD_FORMAT_URATIONAL),
@@ -909,9 +962,10 @@ public class ExifInterface {
new ExifTag(TAG_DEVICE_SETTING_DESCRIPTION, 41995, IFD_FORMAT_UNDEFINED),
new ExifTag(TAG_SUBJECT_DISTANCE_RANGE, 41996, IFD_FORMAT_USHORT),
new ExifTag(TAG_IMAGE_UNIQUE_ID, 42016, IFD_FORMAT_STRING),
+ new ExifTag(TAG_DEFAULT_CROP_SIZE, 50720, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG)
};
- // Primary image IFD GPS Info tags (See JEITA CP-3451 Table 16. page 56).
+ // Primary image IFD GPS Info tags (See JEITA CP-3451C Section 4.6.8 Tag Support Levels)
private static final ExifTag[] IFD_GPS_TAGS = new ExifTag[] {
new ExifTag(TAG_GPS_VERSION_ID, 0, IFD_FORMAT_BYTE),
new ExifTag(TAG_GPS_LATITUDE_REF, 1, IFD_FORMAT_STRING),
@@ -943,14 +997,17 @@ public class ExifInterface {
new ExifTag(TAG_GPS_PROCESSING_METHOD, 27, IFD_FORMAT_UNDEFINED),
new ExifTag(TAG_GPS_AREA_INFORMATION, 28, IFD_FORMAT_UNDEFINED),
new ExifTag(TAG_GPS_DATESTAMP, 29, IFD_FORMAT_STRING),
- new ExifTag(TAG_GPS_DIFFERENTIAL, 30, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_GPS_DIFFERENTIAL, 30, IFD_FORMAT_USHORT)
};
- // Primary image IFD Interoperability tag (See JEITA CP-3451 Table 17. page 56).
+ // Primary image IFD Interoperability tag (See JEITA CP-3451C Section 4.6.8 Tag Support Levels)
private static final ExifTag[] IFD_INTEROPERABILITY_TAGS = new ExifTag[] {
- new ExifTag(TAG_INTEROPERABILITY_INDEX, 1, IFD_FORMAT_STRING),
+ new ExifTag(TAG_INTEROPERABILITY_INDEX, 1, IFD_FORMAT_STRING)
};
- // IFD Thumbnail tags (See JEITA CP-3451 Table 18. page 57).
+ // IFD Thumbnail tags (See JEITA CP-3451C Section 4.6.8 Tag Support Levels)
private static final ExifTag[] IFD_THUMBNAIL_TAGS = new ExifTag[] {
+ // For below two, see TIFF 6.0 Spec Section 3: Bilevel Images.
+ new ExifTag(TAG_NEW_SUBFILE_TYPE, 254, IFD_FORMAT_ULONG),
+ new ExifTag(TAG_SUBFILE_TYPE, 255, IFD_FORMAT_ULONG),
new ExifTag(TAG_THUMBNAIL_IMAGE_WIDTH, 256, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
new ExifTag(TAG_THUMBNAIL_IMAGE_LENGTH, 257, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
new ExifTag(TAG_BITS_PER_SAMPLE, 258, IFD_FORMAT_USHORT),
@@ -959,7 +1016,7 @@ public class ExifInterface {
new ExifTag(TAG_IMAGE_DESCRIPTION, 270, IFD_FORMAT_STRING),
new ExifTag(TAG_MAKE, 271, IFD_FORMAT_STRING),
new ExifTag(TAG_MODEL, 272, IFD_FORMAT_STRING),
- new ExifTag(TAG_STRIP_OFFSETS, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
+ new ExifTag(TAG_STRIP_OFFSETS, 273, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
new ExifTag(TAG_ORIENTATION, 274, IFD_FORMAT_USHORT),
new ExifTag(TAG_SAMPLES_PER_PIXEL, 277, IFD_FORMAT_USHORT),
new ExifTag(TAG_ROWS_PER_STRIP, 278, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
@@ -974,6 +1031,8 @@ public class ExifInterface {
new ExifTag(TAG_ARTIST, 315, IFD_FORMAT_STRING),
new ExifTag(TAG_WHITE_POINT, 318, IFD_FORMAT_URATIONAL),
new ExifTag(TAG_PRIMARY_CHROMATICITIES, 319, IFD_FORMAT_URATIONAL),
+ // See Adobe PageMaker® 6.0 TIFF Technical Notes, Note 1.
+ new ExifTag(TAG_SUB_IFD_POINTER, 330, IFD_FORMAT_ULONG),
new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT, 513, IFD_FORMAT_ULONG),
new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, 514, IFD_FORMAT_ULONG),
new ExifTag(TAG_Y_CB_CR_COEFFICIENTS, 529, IFD_FORMAT_URATIONAL),
@@ -982,11 +1041,14 @@ public class ExifInterface {
new ExifTag(TAG_REFERENCE_BLACK_WHITE, 532, IFD_FORMAT_URATIONAL),
new ExifTag(TAG_COPYRIGHT, 33432, IFD_FORMAT_STRING),
new ExifTag(TAG_EXIF_IFD_POINTER, 34665, IFD_FORMAT_ULONG),
- new ExifTag(TAG_GPS_INFO_IFD_POINTER, 34853, IFD_FORMAT_ULONG),
+ new ExifTag(TAG_GPS_INFO_IFD_POINTER, 34853, IFD_FORMAT_ULONG)
};
+ // RAF file tag (See piex.cc line 372)
+ private static final ExifTag TAG_RAF_IMAGE_SIZE =
+ new ExifTag(TAG_STRIP_OFFSETS, 273, IFD_FORMAT_USHORT);
- // See JEITA CP-3451 Figure 5. page 9.
- // The following values are used for indicating pointers to the other Image File Directorys.
+ // See JEITA CP-3451C Section 4.6.3: Exif-specific IFD.
+ // The following values are used for indicating pointers to the other Image File Directories.
// Indices of Exif Ifd tag groups
private static final int IFD_TIFF_HINT = 0;
@@ -994,20 +1056,22 @@ public class ExifInterface {
private static final int IFD_GPS_HINT = 2;
private static final int IFD_INTEROPERABILITY_HINT = 3;
private static final int IFD_THUMBNAIL_HINT = 4;
+ private static final int IFD_PREVIEW_HINT = 5;
// List of Exif tag groups
private static final ExifTag[][] EXIF_TAGS = new ExifTag[][] {
IFD_TIFF_TAGS, IFD_EXIF_TAGS, IFD_GPS_TAGS, IFD_INTEROPERABILITY_TAGS,
- IFD_THUMBNAIL_TAGS
+ IFD_THUMBNAIL_TAGS, IFD_TIFF_TAGS
};
// List of tags for pointing to the other image file directory offset.
private static final ExifTag[] IFD_POINTER_TAGS = new ExifTag[] {
+ new ExifTag(TAG_SUB_IFD_POINTER, 330, IFD_FORMAT_ULONG),
new ExifTag(TAG_EXIF_IFD_POINTER, 34665, IFD_FORMAT_ULONG),
new ExifTag(TAG_GPS_INFO_IFD_POINTER, 34853, IFD_FORMAT_ULONG),
- new ExifTag(TAG_INTEROPERABILITY_IFD_POINTER, 40965, IFD_FORMAT_ULONG),
+ new ExifTag(TAG_INTEROPERABILITY_IFD_POINTER, 40965, IFD_FORMAT_ULONG)
};
// List of indices of the indicated tag groups according to the IFD_POINTER_TAGS
private static final int[] IFD_POINTER_TAG_HINTS = new int[] {
- IFD_EXIF_HINT, IFD_GPS_HINT, IFD_INTEROPERABILITY_HINT
+ IFD_TIFF_HINT, IFD_EXIF_HINT, IFD_GPS_HINT, IFD_INTEROPERABILITY_HINT
};
// Tags for indicating the thumbnail offset and length
private static final ExifTag JPEG_INTERCHANGE_FORMAT_TAG =
@@ -1054,6 +1118,20 @@ public class ExifInterface {
private static final byte MARKER_COM = (byte) 0xfe;
private static final byte MARKER_EOI = (byte) 0xd9;
+ // Supported Image File Types
+ private static final int IMAGE_TYPE_UNKNOWN = 0;
+ private static final int IMAGE_TYPE_ARW = 1;
+ private static final int IMAGE_TYPE_CR2 = 2;
+ private static final int IMAGE_TYPE_DNG = 3;
+ private static final int IMAGE_TYPE_JPEG = 4;
+ private static final int IMAGE_TYPE_NEF = 5;
+ private static final int IMAGE_TYPE_NRW = 6;
+ private static final int IMAGE_TYPE_ORF = 7;
+ private static final int IMAGE_TYPE_PEF = 8;
+ private static final int IMAGE_TYPE_RAF = 9;
+ private static final int IMAGE_TYPE_RW2 = 10;
+ private static final int IMAGE_TYPE_SRW = 11;
+
static {
System.loadLibrary("media_jni");
nativeInitRaw();
@@ -1076,6 +1154,7 @@ public class ExifInterface {
private final AssetManager.AssetInputStream mAssetInputStream;
private final boolean mIsInputStream;
private boolean mIsRaw;
+ private int mMimeType;
private final HashMap[] mAttributes = new HashMap[EXIF_TAGS.length];
private ByteOrder mExifByteOrder = ByteOrder.BIG_ENDIAN;
private boolean mHasThumbnail;
@@ -1451,27 +1530,51 @@ public class ExifInterface {
mAttributes[i] = new HashMap();
}
- // Process RAW input stream
- if (mAssetInputStream != null) {
- long asset = mAssetInputStream.getNativeAsset();
- if (handleRawResult(nativeGetRawAttributesFromAsset(asset))) {
- return;
- }
- } else if (mSeekableFileDescriptor != null) {
- if (handleRawResult(nativeGetRawAttributesFromFileDescriptor(
- mSeekableFileDescriptor))) {
- return;
+ if (HANDLE_RAW) {
+ // Check file type
+ in = new BufferedInputStream(in, SIGNATURE_CHECK_SIZE);
+ mMimeType = getMimeType((BufferedInputStream) in);
+
+ switch (mMimeType) {
+ case IMAGE_TYPE_JPEG: {
+ getJpegAttributes(in, IFD_TIFF_HINT);
+ break;
+ }
+ case IMAGE_TYPE_RAF: {
+ getRafAttributes(in);
+ break;
+ }
+ default: {
+ getRawAttributes(in);
+ break;
+ }
}
} else {
- in = new BufferedInputStream(in, JPEG_SIGNATURE_SIZE);
- if (!isJpegInputStream((BufferedInputStream) in) && handleRawResult(
- nativeGetRawAttributesFromInputStream(in))) {
- return;
+ if (mAssetInputStream != null) {
+ long asset = mAssetInputStream.getNativeAsset();
+ if (handleRawResult(nativeGetRawAttributesFromAsset(asset))) {
+ return;
+ }
+ } else if (mSeekableFileDescriptor != null) {
+ if (handleRawResult(nativeGetRawAttributesFromFileDescriptor(
+ mSeekableFileDescriptor))) {
+ return;
+ }
+ } else {
+ in.mark(JPEG_SIGNATURE_SIZE);
+ byte[] signatureBytes = new byte[JPEG_SIGNATURE_SIZE];
+ if (in.read(signatureBytes) != JPEG_SIGNATURE_SIZE) {
+ throw new EOFException();
+ }
+ in.reset();
+ if (!isJpegInputStream(signatureBytes) && handleRawResult(
+ nativeGetRawAttributesFromInputStream(in))) {
+ return;
+ }
}
+ // Process JPEG input stream
+ getJpegAttributes(in, IFD_TIFF_HINT);
}
-
- // Process JPEG input stream
- getJpegAttributes(in);
} catch (IOException e) {
// Ignore exceptions in order to keep the compatibility with the old versions of
// ExifInterface.
@@ -1487,15 +1590,13 @@ public class ExifInterface {
}
}
- private static boolean isJpegInputStream(BufferedInputStream in) throws IOException {
- in.mark(JPEG_SIGNATURE_SIZE);
- byte[] signatureBytes = new byte[JPEG_SIGNATURE_SIZE];
- if (in.read(signatureBytes) != JPEG_SIGNATURE_SIZE) {
- throw new EOFException();
+ private static boolean isJpegInputStream(byte[] signatureBytes) throws IOException {
+ for (int i = 0; i < JPEG_SIGNATURE_SIZE; i++) {
+ if (signatureBytes[i] != JPEG_SIGNATURE[i]) {
+ return false;
+ }
}
- boolean isJpeg = Arrays.equals(JPEG_SIGNATURE, signatureBytes);
- in.reset();
- return isJpeg;
+ return true;
}
private boolean handleRawResult(HashMap map) {
@@ -1817,9 +1918,33 @@ public class ExifInterface {
}
}
- // Loads EXIF attributes from a JPEG input stream.
- private void getJpegAttributes(InputStream inputStream) throws IOException {
- // See JPEG File Interchange Format Specification page 5.
+ // Checks the type of image file
+ private int getMimeType(BufferedInputStream in) throws IOException {
+ in.mark(SIGNATURE_CHECK_SIZE);
+ byte[] signatureBytes = new byte[SIGNATURE_CHECK_SIZE];
+ if (in.read(signatureBytes) != SIGNATURE_CHECK_SIZE) {
+ throw new EOFException();
+ }
+ in.reset();
+ if (isJpegInputStream(signatureBytes)) {
+ return IMAGE_TYPE_JPEG;
+ } else if (isRafInputStream(signatureBytes)) {
+ return IMAGE_TYPE_RAF;
+ }
+ return IMAGE_TYPE_UNKNOWN;
+ }
+
+ /**
+ * Loads EXIF attributes from a JPEG input stream.
+ *
+ * @param inputStream The input stream that starts with the JPEG data.
+ * @param imageTypes The image type from which to retrieve metadata. Use IFD_TIFF_HINT for
+ * primary image, IFD_PREVIEW_HINT for preview image, and
+ * IFD_THUMBNAIL_HINT for thumbnail image.
+ * @throws IOException If the data contains invalid JPEG markers, offsets, or length values.
+ */
+ private void getJpegAttributes(InputStream inputStream, int imageType) throws IOException {
+ // See JPEG File Interchange Format Specification, "JFIF Specification"
if (DEBUG) {
Log.d(TAG, "getJpegAttributes starting with: " + inputStream);
}
@@ -1889,7 +2014,7 @@ public class ExifInterface {
if (dataInputStream.read(bytes) != length) {
throw new IOException("Invalid exif");
}
- readExifSegment(bytes, bytesRead);
+ readExifSegment(bytes, bytesRead, imageType);
bytesRead += length;
length = 0;
break;
@@ -1924,9 +2049,9 @@ public class ExifInterface {
if (dataInputStream.skipBytes(1) != 1) {
throw new IOException("Invalid SOFx");
}
- mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_LENGTH, ExifAttribute.createULong(
+ mAttributes[imageType].put(TAG_IMAGE_LENGTH, ExifAttribute.createULong(
dataInputStream.readUnsignedShort(), mExifByteOrder));
- mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_WIDTH, ExifAttribute.createULong(
+ mAttributes[imageType].put(TAG_IMAGE_WIDTH, ExifAttribute.createULong(
dataInputStream.readUnsignedShort(), mExifByteOrder));
length -= 5;
break;
@@ -1946,10 +2071,148 @@ public class ExifInterface {
}
}
+ private void getRawAttributes(InputStream in) throws IOException {
+ int bytesRead = 0;
+ int totalBytes = in.available();
+ byte[] exifBytes = new byte[totalBytes];
+ in.mark(in.available());
+ in.read(exifBytes);
+
+ ByteOrderAwarenessDataInputStream dataInputStream =
+ new ByteOrderAwarenessDataInputStream(exifBytes);
+
+ // Parse TIFF Headers. See JEITA CP-3451C Section 4.5.2. Table 1.
+ parseTiffHeaders(dataInputStream, exifBytes.length);
+
+ // Read TIFF image file directories. See JEITA CP-3451C Section 4.5.2. Figure 6.
+ readImageFileDirectory(dataInputStream, IFD_PREVIEW_HINT);
+
+ // Check if the preview image data should be a primary image data.
+ // The 0th IFD (first to be parsed) is presumed to be a preview image data, with a SubIFD
+ // that is a primary image data.
+ // But if the 0th IFD does not have a SubIFD, then it must be a primary image data since
+ // a primary image data must exist, but a preview image data does not have to.
+ if (mAttributes[IFD_TIFF_HINT].isEmpty() && !mAttributes[IFD_PREVIEW_HINT].isEmpty()) {
+ mAttributes[IFD_TIFF_HINT] = mAttributes[IFD_PREVIEW_HINT];
+ mAttributes[IFD_PREVIEW_HINT] = new HashMap();
+ }
+
+ // Update TAG_IMAGE_WIDTH and TAG_IMAGE_LENGTH for primary image.
+ updatePrimaryImageSizeValues(in);
+
+ // Check if the preview image data should be a thumbnail image data.
+ // In a RAW file, there may be a preview image, which is smaller than a primary image but
+ // larger than a thumbnail image. Normally, the preview image can be considered a thumbnail
+ // image if its size meets the requirements. Therefore, when a thumbnail image has not yet
+ // been found, we should check if the preview image can be one.
+ if (!mAttributes[IFD_PREVIEW_HINT].isEmpty() && mAttributes[IFD_THUMBNAIL_HINT].isEmpty()) {
+ // Update preview image size if necessary
+ retrieveJpegImageSize(in, IFD_PREVIEW_HINT);
+
+ if (isThumbnail(mAttributes[IFD_PREVIEW_HINT])) {
+ mAttributes[IFD_THUMBNAIL_HINT] = mAttributes[IFD_PREVIEW_HINT];
+ mAttributes[IFD_PREVIEW_HINT] = new HashMap();
+ }
+ }
+ // Process thumbnail.
+ processThumbnail(dataInputStream, 0, exifBytes.length);
+ }
+
+ /**
+ * RAF files contains a JPEG and a CFA data.
+ * The JPEG contains two images, a preview and a thumbnail, while the CFA contains a RAW image.
+ * This method looks at the first 160 bytes to determine if this file is a RAF file.
+ * There is no official specification for RAF files from Fuji, but there is an online archive of
+ * image file specifications:
+ * http://fileformats.archiveteam.org/wiki/Fujifilm_RAF
+ */
+ private boolean isRafInputStream(byte[] signatureBytes) throws IOException {
+ byte[] rafSignatureBytes = RAF_SIGNATURE.getBytes();
+ for (int i = 0; i < RAF_SIGNATURE_SIZE; i++) {
+ if (signatureBytes[i] != rafSignatureBytes[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * This method looks at the first 160 bytes of a RAF file to retrieve the offset and length
+ * values for the JPEG and CFA data.
+ * Using that data, it parses the JPEG data to retrieve the preview and thumbnail image data,
+ * then parses the CFA metadata to retrieve the primary image length/width values.
+ * For data format details, see http://fileformats.archiveteam.org/wiki/Fujifilm_RAF
+ */
+ private void getRafAttributes(InputStream in) throws IOException {
+ // Retrieve offset & length values
+ in.mark(RAF_INFO_SIZE);
+ in.skip(RAF_OFFSET_TO_JPEG_IMAGE_OFFSET);
+ byte[] jpegOffsetBytes = new byte[4];
+ byte[] jpegLengthBytes = new byte[4];
+ byte[] cfaHeaderOffsetBytes = new byte[4];
+ byte[] cfaHeaderLengthBytes = new byte[4];
+ in.read(jpegOffsetBytes);
+ in.read(jpegLengthBytes);
+ in.read(cfaHeaderOffsetBytes);
+ in.read(cfaHeaderLengthBytes);
+ mRafJpegOffset = ByteBuffer.wrap(jpegOffsetBytes).getInt();
+ mRafJpegLength = ByteBuffer.wrap(jpegLengthBytes).getInt();
+ mRafCfaHeaderOffset = ByteBuffer.wrap(cfaHeaderOffsetBytes).getInt();
+ mRafCfaHeaderLength = ByteBuffer.wrap(cfaHeaderLengthBytes).getInt();
+ in.reset();
+
+ // Retrieve JPEG image metadata
+ in.mark(mRafJpegOffset + mRafJpegLength);
+ in.skip(mRafJpegOffset);
+ getJpegAttributes(in, IFD_PREVIEW_HINT);
+ in.reset();
+
+ // Skip to CFA header offset.
+ // A while loop is used because the skip method may not be able to skip the requested amount
+ // at once because the size of the buffer may be restricted.
+ int totalSkip = mRafCfaHeaderOffset;
+ while (totalSkip > 0) {
+ long skipped = in.skip(totalSkip);
+ totalSkip -= skipped;
+ }
+
+ // Retrieve primary image length/width values, if TAG_RAF_IMAGE_SIZE exists
+ byte[] exifBytes = new byte[mRafCfaHeaderLength];
+ int read = in.read(exifBytes);
+ ByteOrderAwarenessDataInputStream dataInputStream =
+ new ByteOrderAwarenessDataInputStream(exifBytes);
+ int numberOfDirectoryEntry = dataInputStream.readInt();
+ if (DEBUG) {
+ Log.d(TAG, "numberOfDirectoryEntry: " + numberOfDirectoryEntry);
+ }
+ // CFA stores some metadata about the RAW image. Since CFA uses proprietary tags, can only
+ // find and retrieve image size information tags, while skipping others.
+ // See piex.cc RafGetDimension()
+ for (int i = 0; i < numberOfDirectoryEntry; ++i) {
+ int tagNumber = dataInputStream.readUnsignedShort();
+ int numberOfBytes = dataInputStream.readUnsignedShort();
+ if (tagNumber == TAG_RAF_IMAGE_SIZE.number) {
+ int imageLength = dataInputStream.readShort();
+ int imageWidth = dataInputStream.readShort();
+ ExifAttribute imageLengthAttribute =
+ ExifAttribute.createUShort(imageLength, mExifByteOrder);
+ ExifAttribute imageWidthAttribute =
+ ExifAttribute.createUShort(imageWidth, mExifByteOrder);
+ mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_LENGTH, imageLengthAttribute);
+ mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_WIDTH, imageWidthAttribute);
+ if (DEBUG) {
+ Log.d(TAG, "Updated to length: " + imageLength + ", width: " + imageWidth);
+ }
+ return;
+ }
+ dataInputStream.skip(numberOfBytes);
+ }
+ }
+
// Stores a new JPEG image with EXIF attributes into a given output stream.
private void saveJpegAttributes(InputStream inputStream, OutputStream outputStream)
throws IOException {
- // See JPEG File Interchange Format Specification page 5.
+ // See JPEG File Interchange Format Specification, "JFIF Specification"
if (DEBUG) {
Log.d(TAG, "saveJpegAttributes starting with (inputStream: " + inputStream
+ ", outputStream: " + outputStream + ")");
@@ -2045,11 +2308,50 @@ public class ExifInterface {
}
// Reads the given EXIF byte area and save its tag data into attributes.
- private void readExifSegment(byte[] exifBytes, int exifOffsetFromBeginning) throws IOException {
- // Parse TIFF Headers. See JEITA CP-3451C Table 1. page 10.
+ private void readExifSegment(byte[] exifBytes, int exifOffsetFromBeginning, int imageType)
+ throws IOException {
ByteOrderAwarenessDataInputStream dataInputStream =
new ByteOrderAwarenessDataInputStream(exifBytes);
+ // Parse TIFF Headers. See JEITA CP-3451C Section 4.5.2. Table 1.
+ parseTiffHeaders(dataInputStream, exifBytes.length);
+
+ // Read TIFF image file directories. See JEITA CP-3451C Section 4.5.2. Figure 6.
+ readImageFileDirectory(dataInputStream, imageType);
+
+ // Process thumbnail.
+ processThumbnail(dataInputStream, exifOffsetFromBeginning, exifBytes.length);
+ }
+
+ private void addDefaultValuesForCompatibility() {
+ // The value of DATETIME tag has the same value of DATETIME_ORIGINAL tag.
+ String valueOfDateTimeOriginal = getAttribute(TAG_DATETIME_ORIGINAL);
+ if (valueOfDateTimeOriginal != null) {
+ mAttributes[IFD_TIFF_HINT].put(TAG_DATETIME,
+ ExifAttribute.createString(valueOfDateTimeOriginal));
+ }
+
+ // Add the default value.
+ if (getAttribute(TAG_IMAGE_WIDTH) == null) {
+ mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_WIDTH,
+ ExifAttribute.createULong(0, mExifByteOrder));
+ }
+ if (getAttribute(TAG_IMAGE_LENGTH) == null) {
+ mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_LENGTH,
+ ExifAttribute.createULong(0, mExifByteOrder));
+ }
+ if (getAttribute(TAG_ORIENTATION) == null) {
+ mAttributes[IFD_TIFF_HINT].put(TAG_ORIENTATION,
+ ExifAttribute.createULong(0, mExifByteOrder));
+ }
+ if (getAttribute(TAG_LIGHT_SOURCE) == null) {
+ mAttributes[IFD_EXIF_HINT].put(TAG_LIGHT_SOURCE,
+ ExifAttribute.createULong(0, mExifByteOrder));
+ }
+ }
+
+ private void parseTiffHeaders(ByteOrderAwarenessDataInputStream dataInputStream,
+ int exifBytesLength) throws IOException {
// Read byte align
short byteOrder = dataInputStream.readShort();
switch (byteOrder) {
@@ -2073,13 +2375,13 @@ public class ExifInterface {
dataInputStream.setByteOrder(mExifByteOrder);
int startCode = dataInputStream.readUnsignedShort();
- if (startCode != 0x2a) {
- throw new IOException("Invalid exif start: " + Integer.toHexString(startCode));
+ if (startCode != START_CODE) {
+ throw new IOException("Invalid exif start code: " + Integer.toHexString(startCode));
}
// Read first ifd offset
long firstIfdOffset = dataInputStream.readUnsignedInt();
- if (firstIfdOffset < 8 || firstIfdOffset >= exifBytes.length) {
+ if (firstIfdOffset < 8 || firstIfdOffset >= exifBytesLength) {
throw new IOException("Invalid first Ifd offset: " + firstIfdOffset);
}
firstIfdOffset -= 8;
@@ -2088,76 +2390,6 @@ public class ExifInterface {
throw new IOException("Couldn't jump to first Ifd: " + firstIfdOffset);
}
}
-
- // Read primary image TIFF image file directory.
- readImageFileDirectory(dataInputStream, IFD_TIFF_HINT);
-
- // Process thumbnail.
- String jpegInterchangeFormatString = getAttribute(JPEG_INTERCHANGE_FORMAT_TAG.name);
- String jpegInterchangeFormatLengthString =
- getAttribute(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.name);
- if (jpegInterchangeFormatString != null && jpegInterchangeFormatLengthString != null) {
- try {
- int jpegInterchangeFormat = Integer.parseInt(jpegInterchangeFormatString);
- int jpegInterchangeFormatLength = Integer
- .parseInt(jpegInterchangeFormatLengthString);
- // The following code limits the size of thumbnail size not to overflow EXIF data area.
- jpegInterchangeFormatLength = Math.min(jpegInterchangeFormat
- + jpegInterchangeFormatLength, exifBytes.length) - jpegInterchangeFormat;
- if (jpegInterchangeFormat > 0 && jpegInterchangeFormatLength > 0) {
- mHasThumbnail = true;
- mThumbnailOffset = exifOffsetFromBeginning + jpegInterchangeFormat;
- mThumbnailLength = jpegInterchangeFormatLength;
-
- if (mFilename == null && mAssetInputStream == null
- && mSeekableFileDescriptor == null) {
- // Save the thumbnail in memory if the input doesn't support reading again.
- byte[] thumbnailBytes = new byte[jpegInterchangeFormatLength];
- dataInputStream.seek(jpegInterchangeFormat);
- dataInputStream.readFully(thumbnailBytes);
- mThumbnailBytes = thumbnailBytes;
-
- if (DEBUG) {
- Bitmap bitmap = BitmapFactory.decodeByteArray(
- thumbnailBytes, 0, thumbnailBytes.length);
- Log.d(TAG, "Thumbnail offset: " + mThumbnailOffset + ", length: "
- + mThumbnailLength + ", width: " + bitmap.getWidth()
- + ", height: "
- + bitmap.getHeight());
- }
- }
- }
- } catch (NumberFormatException e) {
- // Ignored the corrupted image.
- }
- }
- }
-
- private void addDefaultValuesForCompatibility() {
- // The value of DATETIME tag has the same value of DATETIME_ORIGINAL tag.
- String valueOfDateTimeOriginal = getAttribute(TAG_DATETIME_ORIGINAL);
- if (valueOfDateTimeOriginal != null) {
- mAttributes[IFD_TIFF_HINT].put(TAG_DATETIME,
- ExifAttribute.createString(valueOfDateTimeOriginal));
- }
-
- // Add the default value.
- if (getAttribute(TAG_IMAGE_WIDTH) == null) {
- mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_WIDTH,
- ExifAttribute.createULong(0, mExifByteOrder));
- }
- if (getAttribute(TAG_IMAGE_LENGTH) == null) {
- mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_LENGTH,
- ExifAttribute.createULong(0, mExifByteOrder));
- }
- if (getAttribute(TAG_ORIENTATION) == null) {
- mAttributes[IFD_TIFF_HINT].put(TAG_ORIENTATION,
- ExifAttribute.createULong(0, mExifByteOrder));
- }
- if (getAttribute(TAG_LIGHT_SOURCE) == null) {
- mAttributes[IFD_EXIF_HINT].put(TAG_LIGHT_SOURCE,
- ExifAttribute.createULong(0, mExifByteOrder));
- }
}
// Reads image file directory, which is a tag group in EXIF.
@@ -2167,7 +2399,7 @@ public class ExifInterface {
// Return if there is no data from the offset.
return;
}
- // See JEITA CP-3451 Figure 5. page 9.
+ // See TIFF 6.0 Section 2: TIFF Structure, Figure 1.
short numberOfDirectoryEntry = dataInputStream.readShort();
if (dataInputStream.peek() + 12 * numberOfDirectoryEntry > dataInputStream.mLength) {
// Return if the size of entries is too big.
@@ -2178,6 +2410,7 @@ public class ExifInterface {
Log.d(TAG, "numberOfDirectoryEntry: " + numberOfDirectoryEntry);
}
+ // See TIFF 6.0 Section 2: TIFF Structure, "Image File Directory".
for (short i = 0; i < numberOfDirectoryEntry; ++i) {
int tagNumber = dataInputStream.readUnsignedShort();
int dataFormat = dataInputStream.readUnsignedShort();
@@ -2216,7 +2449,7 @@ public class ExifInterface {
if (offset + byteCount <= dataInputStream.mLength) {
dataInputStream.seek(offset);
} else {
- // Skip if invalid data offset.
+ // Skip if invalid data offset.
Log.w(TAG, "Skip the tag entry since data offset is invalid: " + offset);
dataInputStream.seek(nextEntryOffset);
continue;
@@ -2291,6 +2524,219 @@ public class ExifInterface {
}
}
+ /**
+ * JPEG compressed images do not contain IMAGE_LENGTH & IMAGE_WIDTH tags.
+ * This value uses JpegInterchangeFormat(JPEG data offset) value, and calls getJpegAttributes()
+ * to locate SOF(Start of Frame) marker and update the image length & width values.
+ * See JEITA CP-3451C Table 5 and Section 4.8.1. B.
+ */
+ private void retrieveJpegImageSize(InputStream in, int imageType) throws IOException {
+ // Check if image already has IMAGE_LENGTH & IMAGE_WIDTH values
+ ExifAttribute imageLengthAttribute =
+ (ExifAttribute) mAttributes[imageType].get(TAG_IMAGE_LENGTH);
+ ExifAttribute imageWidthAttribute =
+ (ExifAttribute) mAttributes[imageType].get(TAG_IMAGE_WIDTH);
+
+ if (imageLengthAttribute == null || imageWidthAttribute == null) {
+ // Find if offset for JPEG data exists
+ ExifAttribute jpegInterchangeFormatAttribute =
+ (ExifAttribute) mAttributes[imageType].get(TAG_JPEG_INTERCHANGE_FORMAT);
+ if (jpegInterchangeFormatAttribute != null) {
+ int jpegInterchangeFormat =
+ jpegInterchangeFormatAttribute.getIntValue(mExifByteOrder);
+
+ // Skip to the JPEG data offset
+ in.reset();
+ in.mark(in.available());
+ if (in.skip(jpegInterchangeFormat) != jpegInterchangeFormat) {
+ Log.d(TAG, "Invalid JPEG offset");
+ }
+
+ // Searches for SOF marker in JPEG data and updates IMAGE_LENGTH & IMAGE_WIDTH tags
+ getJpegAttributes(in, imageType);
+ }
+ }
+ }
+
+ // Processes thumbnail based on Compression Value
+ private void processThumbnail(ByteOrderAwarenessDataInputStream dataInputStream,
+ int exifOffsetFromBeginning, int exifBytesLength) throws IOException {
+ HashMap thumbnailData = mAttributes[IFD_THUMBNAIL_HINT];
+ ExifAttribute compressionAttribute = (ExifAttribute) thumbnailData.get(TAG_COMPRESSION);
+ if (compressionAttribute != null) {
+ int compressionValue = compressionAttribute.getIntValue(mExifByteOrder);
+ switch (compressionValue) {
+ case DATA_UNCOMPRESSED: {
+ // TODO: add implementation for reading uncompressed thumbnail data (b/28156704)
+ Log.d(TAG, "Uncompressed thumbnail data cannot be processed");
+ break;
+ }
+ case DATA_JPEG: {
+ ExifAttribute jpegInterchangeFormatAttribute =
+ (ExifAttribute) thumbnailData.get(TAG_JPEG_INTERCHANGE_FORMAT);
+ ExifAttribute jpegInterchangeFormatLengthAttribute =
+ (ExifAttribute) thumbnailData.get(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+ if (jpegInterchangeFormatAttribute != null
+ && jpegInterchangeFormatLengthAttribute != null) {
+ int jpegInterchangeFormat =
+ jpegInterchangeFormatAttribute.getIntValue(mExifByteOrder);
+ int jpegInterchangeFormatLength =
+ jpegInterchangeFormatLengthAttribute.getIntValue(mExifByteOrder);
+ retrieveJPEGThumbnail(dataInputStream, jpegInterchangeFormat,
+ jpegInterchangeFormatLength, exifOffsetFromBeginning,
+ exifBytesLength);
+ }
+ break;
+ }
+ case DATA_JPEG_COMPRESSED: {
+ ExifAttribute stripOffsetsAttribute =
+ (ExifAttribute) thumbnailData.get(TAG_STRIP_OFFSETS);
+ ExifAttribute stripByteCountsAttribute =
+ (ExifAttribute) thumbnailData.get(TAG_STRIP_BYTE_COUNTS);
+ if (stripOffsetsAttribute != null && stripByteCountsAttribute != null) {
+ long[] stripOffsetsArray =
+ (long[]) stripOffsetsAttribute.getValue(mExifByteOrder);
+ long[] stripByteCountsArray =
+ (long[]) stripByteCountsAttribute.getValue(mExifByteOrder);
+ if (stripOffsetsArray.length == 1) {
+ int stripOffsetsSum = (int) Arrays.stream(stripOffsetsArray).sum();
+ int stripByteCountsSum = (int) Arrays.stream(stripByteCountsArray).sum();
+ retrieveJPEGThumbnail(dataInputStream, stripOffsetsSum,
+ stripByteCountsSum, exifOffsetFromBeginning,
+ exifBytesLength);
+ } else {
+ // TODO: implement method to read multiple strips (b/29737797)
+ Log.d(TAG, "Multiple strip thumbnail data cannot be processed");
+ }
+ }
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ }
+ }
+
+ // Retrieves thumbnail for JPEG Compression
+ private void retrieveJPEGThumbnail(ByteOrderAwarenessDataInputStream dataInputStream,
+ int thumbnailOffset, int thumbnailLength, int exifOffsetFromBeginning,
+ int exifBytesLength) throws IOException {
+ if (DEBUG) {
+ Log.d(TAG, "Retrieving JPEG Thumbnail");
+ }
+ // The following code limits the size of thumbnail size not to overflow EXIF data area.
+ thumbnailLength = Math.min(thumbnailLength, exifBytesLength - thumbnailOffset);
+ // The following code changes the offset value for RAF files.
+ if (mMimeType == IMAGE_TYPE_RAF) {
+ exifOffsetFromBeginning += mRafJpegOffset;
+ }
+ if (thumbnailOffset > 0 && thumbnailLength > 0) {
+ mHasThumbnail = true;
+ mThumbnailOffset = exifOffsetFromBeginning + thumbnailOffset;
+ mThumbnailLength = thumbnailLength;
+ if (mFilename == null && mAssetInputStream == null && mSeekableFileDescriptor == null) {
+ // Save the thumbnail in memory if the input doesn't support reading again.
+ byte[] thumbnailBytes = new byte[thumbnailLength];
+ dataInputStream.seek(thumbnailOffset);
+ dataInputStream.readFully(thumbnailBytes);
+ mThumbnailBytes = thumbnailBytes;
+ if (DEBUG) {
+ Bitmap bitmap = BitmapFactory.decodeByteArray(
+ thumbnailBytes, 0, thumbnailBytes.length);
+ Log.d(TAG, "Thumbnail offset: " + mThumbnailOffset + ", length: "
+ + mThumbnailLength + ", width: " + bitmap.getWidth()
+ + ", height: "
+ + bitmap.getHeight());
+ }
+ }
+ }
+ }
+
+ // Returns true if the image length and width values are <= 512.
+ // See Section 4.8 of http://standardsproposals.bsigroup.com/Home/getPDF/567
+ private boolean isThumbnail(HashMap map) throws IOException {
+ ExifAttribute imageLengthAttribute = (ExifAttribute) map.get(TAG_IMAGE_LENGTH);
+ ExifAttribute imageWidthAttribute = (ExifAttribute) map.get(TAG_IMAGE_WIDTH);
+
+ if (imageLengthAttribute != null && imageWidthAttribute != null) {
+ int imageLengthValue = imageLengthAttribute.getIntValue(mExifByteOrder);
+ int imageWidthValue = imageWidthAttribute.getIntValue(mExifByteOrder);
+ if (imageLengthValue <= MAX_THUMBNAIL_SIZE && imageWidthValue <= MAX_THUMBNAIL_SIZE) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * If image is uncompressed, ImageWidth/Length tags are used to store size info.
+ * However, uncompressed images often store extra pixels around the edges of the final image,
+ * which results in larger values for TAG_IMAGE_WIDTH and TAG_IMAGE_LENGTH tags.
+ * This method corrects those tag values by checking first the values of TAG_DEFAULT_CROP_SIZE
+ * See DNG Specification 1.4.0.0. Section 4. (DefaultCropSize)
+ *
+ * If image is JPEG compressed, PixelXDimension/PixelYDimension tags are used for size info.
+ * However, an image may have padding at the right end or bottom end of the image to make sure
+ * that the values are multiples of 64. If so, the increased value will be saved in the
+ * SOF(Start of Frame). In order to assure that valid image size values are stored, this method
+ * checks TAG_PIXEL_X_DIMENSION & TAG_PIXEL_Y_DIMENSION and updates values if necessary.
+ * See JEITA CP-3451C Table 5 and Section 4.8.1. B.
+ * */
+ private void updatePrimaryImageSizeValues(InputStream in) throws IOException {
+ // Checks for the NewSubfileType tag and returns if the image is not original resolution,
+ // which means that it is not the primary imiage
+ ExifAttribute newSubfileTypeAttribute =
+ (ExifAttribute) mAttributes[IFD_TIFF_HINT].get(TAG_NEW_SUBFILE_TYPE);
+ if (newSubfileTypeAttribute != null) {
+ int newSubfileTypeValue = newSubfileTypeAttribute.getIntValue(mExifByteOrder);
+ if (newSubfileTypeValue != ORIGINAL_RESOLUTION_IMAGE) {
+ // TODO: Need to address case when NewSubFile value is REDUCED_RESOLUTION_IMAGE.
+ return;
+ }
+ }
+
+ // Uncompressed image valid image size values
+ ExifAttribute defaultCropSizeAttribute =
+ (ExifAttribute) mAttributes[IFD_TIFF_HINT].get(TAG_DEFAULT_CROP_SIZE);
+ // Compressed image valid image size values
+ ExifAttribute pixelXDimAttribute =
+ (ExifAttribute) mAttributes[IFD_EXIF_HINT].get(TAG_PIXEL_X_DIMENSION);
+ ExifAttribute pixelYDimAttribute =
+ (ExifAttribute) mAttributes[IFD_EXIF_HINT].get(TAG_PIXEL_Y_DIMENSION);
+
+ if (defaultCropSizeAttribute != null) {
+ // Update for uncompressed image
+ ExifAttribute defaultCropSizeXAttribute, defaultCropSizeYAttribute;
+ if (defaultCropSizeAttribute.format == IFD_FORMAT_URATIONAL) {
+ Rational[] defaultCropSizeValue =
+ (Rational[]) defaultCropSizeAttribute.getValue(mExifByteOrder);
+ defaultCropSizeXAttribute =
+ ExifAttribute.createURational(defaultCropSizeValue[0], mExifByteOrder);
+ defaultCropSizeYAttribute =
+ ExifAttribute.createURational(defaultCropSizeValue[1], mExifByteOrder);
+ } else {
+ int[] defaultCropSizeValue =
+ (int[]) defaultCropSizeAttribute.getValue(mExifByteOrder);
+ defaultCropSizeXAttribute =
+ ExifAttribute.createUShort(defaultCropSizeValue[0], mExifByteOrder);
+ defaultCropSizeYAttribute =
+ ExifAttribute.createUShort(defaultCropSizeValue[1], mExifByteOrder);
+ }
+ mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_WIDTH, defaultCropSizeXAttribute);
+ mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_LENGTH, defaultCropSizeYAttribute);
+ } else {
+ // Update for JPEG image
+ if (pixelXDimAttribute != null && pixelYDimAttribute != null) {
+ mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_WIDTH, pixelXDimAttribute);
+ mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_LENGTH, pixelYDimAttribute);
+ } else {
+ // Update image size values from SOF marker if necessary
+ retrieveJpegImageSize(in, IFD_TIFF_HINT);
+ }
+ }
+ }
+
// Gets the corresponding IFD group index of the given tag number for writing Exif Tags.
private static int getIfdHintFromTagNumber(int tagNumber) {
for (int i = 0; i < IFD_POINTER_TAG_HINTS.length; ++i) {
@@ -2328,22 +2774,22 @@ public class ExifInterface {
// Add IFD pointer tags. The next offset of primary image TIFF IFD will have thumbnail IFD
// offset when there is one or more tags in the thumbnail IFD.
- if (!mAttributes[IFD_INTEROPERABILITY_HINT].isEmpty()) {
- mAttributes[IFD_EXIF_HINT].put(IFD_POINTER_TAGS[2].name,
- ExifAttribute.createULong(0, mExifByteOrder));
- }
if (!mAttributes[IFD_EXIF_HINT].isEmpty()) {
- mAttributes[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[0].name,
+ mAttributes[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[1].name,
ExifAttribute.createULong(0, mExifByteOrder));
}
if (!mAttributes[IFD_GPS_HINT].isEmpty()) {
- mAttributes[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[1].name,
+ mAttributes[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[2].name,
+ ExifAttribute.createULong(0, mExifByteOrder));
+ }
+ if (!mAttributes[IFD_INTEROPERABILITY_HINT].isEmpty()) {
+ mAttributes[IFD_EXIF_HINT].put(IFD_POINTER_TAGS[3].name,
ExifAttribute.createULong(0, mExifByteOrder));
}
if (mHasThumbnail) {
- mAttributes[IFD_TIFF_HINT].put(JPEG_INTERCHANGE_FORMAT_TAG.name,
+ mAttributes[IFD_THUMBNAIL_HINT].put(JPEG_INTERCHANGE_FORMAT_TAG.name,
ExifAttribute.createULong(0, mExifByteOrder));
- mAttributes[IFD_TIFF_HINT].put(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.name,
+ mAttributes[IFD_THUMBNAIL_HINT].put(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.name,
ExifAttribute.createULong(mThumbnailLength, mExifByteOrder));
}
@@ -2371,7 +2817,7 @@ public class ExifInterface {
}
if (mHasThumbnail) {
int thumbnailOffset = position;
- mAttributes[IFD_TIFF_HINT].put(JPEG_INTERCHANGE_FORMAT_TAG.name,
+ mAttributes[IFD_THUMBNAIL_HINT].put(JPEG_INTERCHANGE_FORMAT_TAG.name,
ExifAttribute.createULong(thumbnailOffset, mExifByteOrder));
mThumbnailOffset = exifOffsetFromBeginning + thumbnailOffset;
position += mThumbnailLength;
@@ -2389,31 +2835,31 @@ public class ExifInterface {
// Update IFD pointer tags with the calculated offsets.
if (!mAttributes[IFD_EXIF_HINT].isEmpty()) {
- mAttributes[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[0].name,
+ mAttributes[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[1].name,
ExifAttribute.createULong(ifdOffsets[IFD_EXIF_HINT], mExifByteOrder));
}
if (!mAttributes[IFD_GPS_HINT].isEmpty()) {
- mAttributes[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[1].name,
+ mAttributes[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[2].name,
ExifAttribute.createULong(ifdOffsets[IFD_GPS_HINT], mExifByteOrder));
}
if (!mAttributes[IFD_INTEROPERABILITY_HINT].isEmpty()) {
- mAttributes[IFD_EXIF_HINT].put(IFD_POINTER_TAGS[2].name, ExifAttribute.createULong(
+ mAttributes[IFD_EXIF_HINT].put(IFD_POINTER_TAGS[3].name, ExifAttribute.createULong(
ifdOffsets[IFD_INTEROPERABILITY_HINT], mExifByteOrder));
}
- // Write TIFF Headers. See JEITA CP-3451C Table 1. page 10.
+ // Write TIFF Headers. See JEITA CP-3451C Section 4.5.2. Table 1.
dataOutputStream.writeUnsignedShort(totalSize);
dataOutputStream.write(IDENTIFIER_EXIF_APP1);
dataOutputStream.writeShort(mExifByteOrder == ByteOrder.BIG_ENDIAN
? BYTE_ALIGN_MM : BYTE_ALIGN_II);
dataOutputStream.setByteOrder(mExifByteOrder);
- dataOutputStream.writeUnsignedShort(0x2a);
- dataOutputStream.writeUnsignedInt(8);
+ dataOutputStream.writeUnsignedShort(START_CODE);
+ dataOutputStream.writeUnsignedInt(IFD_OFFSET);
- // Write IFD groups. See JEITA CP-3451C Figure 7. page 12.
+ // Write IFD groups. See JEITA CP-3451C Section 4.5.8. Figure 9.
for (int hint = 0; hint < EXIF_TAGS.length; ++hint) {
if (!mAttributes[hint].isEmpty()) {
- // See JEITA CP-3451C 4.6.2 IFD structure. page 13.
+ // See JEITA CP-3451C Section 4.6.2: IFD structure.
// Write entry count
dataOutputStream.writeUnsignedShort(mAttributes[hint].size());
@@ -2482,7 +2928,7 @@ public class ExifInterface {
data formats for the given entry value, returns {@code -1} in the second of the pair.
*/
private static Pair<Integer, Integer> guessDataFormat(String entryValue) {
- // See TIFF 6.0 spec Types. page 15.
+ // See TIFF 6.0 Section 2, "Image File Directory".
// Take the first component if there are more than one component.
if (entryValue.contains(",")) {
String[] entryValues = entryValue.split(",");
@@ -2523,7 +2969,7 @@ public class ExifInterface {
long numerator = Long.parseLong(rationalNumber[0]);
long denominator = Long.parseLong(rationalNumber[1]);
if (numerator < 0L || denominator < 0L) {
- return new Pair<>(IFD_FORMAT_SRATIONAL, - 1);
+ return new Pair<>(IFD_FORMAT_SRATIONAL, -1);
}
if (numerator > Integer.MAX_VALUE || denominator > Integer.MAX_VALUE) {
return new Pair<>(IFD_FORMAT_URATIONAL, -1);
@@ -2577,8 +3023,12 @@ public class ExifInterface {
}
public void seek(long byteCount) throws IOException {
- mPosition = 0L;
- reset();
+ if (mPosition > byteCount) {
+ mPosition = 0L;
+ reset();
+ } else {
+ byteCount -= mPosition;
+ }
if (skip(byteCount) != byteCount) {
throw new IOException("Couldn't seek up to the byteCount");
}
@@ -2782,4 +3232,4 @@ public class ExifInterface {
private static native HashMap nativeGetRawAttributesFromAsset(long asset);
private static native HashMap nativeGetRawAttributesFromFileDescriptor(FileDescriptor fd);
private static native HashMap nativeGetRawAttributesFromInputStream(InputStream in);
-}
+} \ No newline at end of file
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 0bfeaed36282..8ada295f529c 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -2943,6 +2943,7 @@ public final class MediaCodecInfo {
public static final int AACObjectHE = 5;
public static final int AACObjectScalable = 6;
public static final int AACObjectERLC = 17;
+ public static final int AACObjectERScalable = 20;
public static final int AACObjectLD = 23;
public static final int AACObjectHE_PS = 29;
public static final int AACObjectELD = 39;
diff --git a/media/java/android/media/MediaDescription.java b/media/java/android/media/MediaDescription.java
index afc3ca7ef5f8..14485d3c43a3 100644
--- a/media/java/android/media/MediaDescription.java
+++ b/media/java/android/media/MediaDescription.java
@@ -2,6 +2,7 @@ package android.media;
import android.annotation.Nullable;
import android.graphics.Bitmap;
+import android.media.browse.MediaBrowser;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
@@ -46,6 +47,67 @@ public class MediaDescription implements Parcelable {
*/
private final Uri mMediaUri;
+ /**
+ * Used as a long extra field to indicate the bluetooth folder type of the media item as
+ * specified in the section 6.10.2.2 of the Bluetooth AVRCP 1.5. This is valid only for
+ * {@link MediaBrowser.MediaItem} with {@link MediaBrowser.MediaItem#FLAG_BROWSABLE}. The value
+ * should be one of the following:
+ * <ul>
+ * <li>{@link #BT_FOLDER_TYPE_MIXED}</li>
+ * <li>{@link #BT_FOLDER_TYPE_TITLES}</li>
+ * <li>{@link #BT_FOLDER_TYPE_ALBUMS}</li>
+ * <li>{@link #BT_FOLDER_TYPE_ARTISTS}</li>
+ * <li>{@link #BT_FOLDER_TYPE_GENRES}</li>
+ * <li>{@link #BT_FOLDER_TYPE_PLAYLISTS}</li>
+ * <li>{@link #BT_FOLDER_TYPE_YEARS}</li>
+ * </ul>
+ *
+ * @see #getExtras()
+ */
+ public static final String EXTRA_BT_FOLDER_TYPE = "android.media.extra.BT_FOLDER_TYPE";
+
+ /**
+ * The type of folder that is unknown or contains media elements of mixed types as specified in
+ * the section 6.10.2.2 of the Bluetooth AVRCP 1.5.
+ */
+ public static final long BT_FOLDER_TYPE_MIXED = 0;
+
+ /**
+ * The type of folder that contains media elements only as specified in the section 6.10.2.2 of
+ * the Bluetooth AVRCP 1.5.
+ */
+ public static final long BT_FOLDER_TYPE_TITLES = 1;
+
+ /**
+ * The type of folder that contains folders categorized by album as specified in the section
+ * 6.10.2.2 of the Bluetooth AVRCP 1.5.
+ */
+ public static final long BT_FOLDER_TYPE_ALBUMS = 2;
+
+ /**
+ * The type of folder that contains folders categorized by artist as specified in the section
+ * 6.10.2.2 of the Bluetooth AVRCP 1.5.
+ */
+ public static final long BT_FOLDER_TYPE_ARTISTS = 3;
+
+ /**
+ * The type of folder that contains folders categorized by genre as specified in the section
+ * 6.10.2.2 of the Bluetooth AVRCP 1.5.
+ */
+ public static final long BT_FOLDER_TYPE_GENRES = 4;
+
+ /**
+ * The type of folder that contains folders categorized by playlist as specified in the section
+ * 6.10.2.2 of the Bluetooth AVRCP 1.5.
+ */
+ public static final long BT_FOLDER_TYPE_PLAYLISTS = 5;
+
+ /**
+ * The type of folder that contains folders categorized by year as specified in the section
+ * 6.10.2.2 of the Bluetooth AVRCP 1.5.
+ */
+ public static final long BT_FOLDER_TYPE_YEARS = 6;
+
private MediaDescription(String mediaId, CharSequence title, CharSequence subtitle,
CharSequence description, Bitmap icon, Uri iconUri, Bundle extras, Uri mediaUri) {
mMediaId = mediaId;
diff --git a/media/java/android/media/MediaMetadata.java b/media/java/android/media/MediaMetadata.java
index 9ebf10f52728..bdc0fda6aca3 100644
--- a/media/java/android/media/MediaMetadata.java
+++ b/media/java/android/media/MediaMetadata.java
@@ -49,7 +49,7 @@ public final class MediaMetadata implements Parcelable {
METADATA_KEY_DATE, METADATA_KEY_GENRE, METADATA_KEY_ALBUM_ARTIST, METADATA_KEY_ART_URI,
METADATA_KEY_ALBUM_ART_URI, METADATA_KEY_DISPLAY_TITLE, METADATA_KEY_DISPLAY_SUBTITLE,
METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_KEY_DISPLAY_ICON_URI,
- METADATA_KEY_MEDIA_ID})
+ METADATA_KEY_MEDIA_ID, METADATA_KEY_MEDIA_URI})
@Retention(RetentionPolicy.SOURCE)
public @interface TextKey {}
@@ -57,7 +57,7 @@ public final class MediaMetadata implements Parcelable {
* @hide
*/
@StringDef({METADATA_KEY_DURATION, METADATA_KEY_YEAR, METADATA_KEY_TRACK_NUMBER,
- METADATA_KEY_NUM_TRACKS, METADATA_KEY_DISC_NUMBER})
+ METADATA_KEY_NUM_TRACKS, METADATA_KEY_DISC_NUMBER, METADATA_KEY_BT_FOLDER_TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface LongKey {}
@@ -269,6 +269,31 @@ public final class MediaMetadata implements Parcelable {
*/
public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
+ /**
+ * A Uri formatted String representing the content. This value is specific to the
+ * service providing the content. It may be used with
+ * {@link MediaController.TransportControls#playFromUri(Uri, Bundle)}
+ * to initiate playback when provided by a {@link MediaBrowser} connected to
+ * the same app.
+ */
+ public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
+
+ /**
+ * The bluetooth folder type of the media specified in the section 6.10.2.2 of the Bluetooth
+ * AVRCP 1.5. It should be one of the following:
+ * <ul>
+ * <li>{@link MediaDescription#BT_FOLDER_TYPE_MIXED}</li>
+ * <li>{@link MediaDescription#BT_FOLDER_TYPE_TITLES}</li>
+ * <li>{@link MediaDescription#BT_FOLDER_TYPE_ALBUMS}</li>
+ * <li>{@link MediaDescription#BT_FOLDER_TYPE_ARTISTS}</li>
+ * <li>{@link MediaDescription#BT_FOLDER_TYPE_GENRES}</li>
+ * <li>{@link MediaDescription#BT_FOLDER_TYPE_PLAYLISTS}</li>
+ * <li>{@link MediaDescription#BT_FOLDER_TYPE_YEARS}</li>
+ * </ul>
+ */
+ public static final String METADATA_KEY_BT_FOLDER_TYPE
+ = "android.media.metadata.BT_FOLDER_TYPE";
+
private static final @TextKey String[] PREFERRED_DESCRIPTION_ORDER = {
METADATA_KEY_TITLE,
METADATA_KEY_ARTIST,
@@ -326,6 +351,9 @@ public final class MediaMetadata implements Parcelable {
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON, METADATA_TYPE_BITMAP);
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON_URI, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_BT_FOLDER_TYPE, METADATA_TYPE_LONG);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_ID, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_URI, METADATA_TYPE_TEXT);
}
private static final SparseArray<String> EDITOR_KEY_MAPPING;
@@ -537,6 +565,12 @@ public final class MediaMetadata implements Parcelable {
}
}
+ Uri mediaUri = null;
+ String mediaUriStr = getString(METADATA_KEY_MEDIA_URI);
+ if (!TextUtils.isEmpty(mediaUriStr)) {
+ mediaUri = Uri.parse(mediaUriStr);
+ }
+
MediaDescription.Builder bob = new MediaDescription.Builder();
bob.setMediaId(mediaId);
bob.setTitle(text[0]);
@@ -544,6 +578,13 @@ public final class MediaMetadata implements Parcelable {
bob.setDescription(text[2]);
bob.setIconBitmap(icon);
bob.setIconUri(iconUri);
+ bob.setMediaUri(mediaUri);
+ if (mBundle.containsKey(METADATA_KEY_BT_FOLDER_TYPE)) {
+ Bundle bundle = new Bundle();
+ bundle.putLong(MediaDescription.EXTRA_BT_FOLDER_TYPE,
+ getLong(METADATA_KEY_BT_FOLDER_TYPE));
+ bob.setExtras(bundle);
+ }
mDescription = bob.build();
return mDescription;
@@ -763,8 +804,9 @@ public final class MediaMetadata implements Parcelable {
* <li>{@link #METADATA_KEY_DISPLAY_ICON}</li>
* </ul>
* <p>
- * Large bitmaps may be scaled down by the system. To pass full
- * resolution images {@link Uri Uris} should be used with
+ * Large bitmaps may be scaled down by the system when
+ * {@link android.media.session.MediaSession#setMetadata} is called.
+ * To pass full resolution images {@link Uri Uris} should be used with
* {@link #putString}.
*
* @param key The key for referencing this value
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 73485afcecff..8971ac50d206 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -952,7 +952,7 @@ public class MediaRecorder
/* Do not change these values without updating their counterparts
* in include/media/mediarecorder.h!
*/
- /** Unspecified media recorder error.
+ /** Unspecified media recorder info.
* @see android.media.MediaRecorder.OnInfoListener
*/
public static final int MEDIA_RECORDER_INFO_UNKNOWN = 1;
@@ -1035,22 +1035,22 @@ public class MediaRecorder
/**
- * Interface definition for a callback to be invoked when an error
- * occurs while recording.
+ * Interface definition of a callback to be invoked to communicate some
+ * info and/or warning about the recording.
*/
public interface OnInfoListener
{
/**
- * Called when an error occurs while recording.
+ * Called to indicate an info or a warning during recording.
*
- * @param mr the MediaRecorder that encountered the error
- * @param what the type of error that has occurred:
+ * @param mr the MediaRecorder the info pertains to
+ * @param what the type of info or warning that has occurred
* <ul>
* <li>{@link #MEDIA_RECORDER_INFO_UNKNOWN}
* <li>{@link #MEDIA_RECORDER_INFO_MAX_DURATION_REACHED}
* <li>{@link #MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED}
* </ul>
- * @param extra an extra code, specific to the error type
+ * @param extra an extra code, specific to the info type
*/
void onInfo(MediaRecorder mr, int what, int extra);
}
diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java
index 79b415f8903d..a56b1b809bc1 100644
--- a/media/java/android/media/browse/MediaBrowser.java
+++ b/media/java/android/media/browse/MediaBrowser.java
@@ -99,13 +99,14 @@ public final class MediaBrowser {
private final Handler mHandler = new Handler();
private final ArrayMap<String, Subscription> mSubscriptions = new ArrayMap<>();
- private int mState = CONNECT_STATE_DISCONNECTED;
+ private volatile int mState = CONNECT_STATE_DISCONNECTED;
+ private volatile String mRootId;
+ private volatile MediaSession.Token mMediaSessionToken;
+ private volatile Bundle mExtras;
+
private MediaServiceConnection mServiceConnection;
private IMediaBrowserService mServiceBinder;
private IMediaBrowserServiceCallbacks mServiceCallbacks;
- private String mRootId;
- private MediaSession.Token mMediaSessionToken;
- private Bundle mExtras;
/**
* Creates a media browser for the specified media browse service.
@@ -212,21 +213,25 @@ public final class MediaBrowser {
// It's ok to call this any state, because allowing this lets apps not have
// to check isConnected() unnecessarily. They won't appreciate the extra
// assertions for this. We do everything we can here to go back to a sane state.
- if (mServiceCallbacks != null) {
- try {
- mServiceBinder.disconnect(mServiceCallbacks);
- } catch (RemoteException ex) {
- // We are disconnecting anyway. Log, just for posterity but it's not
- // a big problem.
- Log.w(TAG, "RemoteException during connect for " + mServiceComponent);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mServiceCallbacks != null) {
+ try {
+ mServiceBinder.disconnect(mServiceCallbacks);
+ } catch (RemoteException ex) {
+ // We are disconnecting anyway. Log, just for posterity but it's not
+ // a big problem.
+ Log.w(TAG, "RemoteException during connect for " + mServiceComponent);
+ }
+ }
+ forceCloseConnection();
+ if (DBG) {
+ Log.d(TAG, "disconnect...");
+ dump();
+ }
}
- }
- forceCloseConnection();
-
- if (DBG) {
- Log.d(TAG, "disconnect...");
- dump();
- }
+ });
}
/**
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 7f9653d336df..095996132192 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -407,12 +407,14 @@ public final class MediaSession {
/**
* Update the current metadata. New metadata can be created using
- * {@link android.media.MediaMetadata.Builder}.
+ * {@link android.media.MediaMetadata.Builder}. This operation may take time proportional to
+ * the size of the bitmap to replace large bitmaps with a scaled down copy.
*
* @param metadata The new metadata
+ * @see android.media.MediaMetadata.Builder#putBitmap
*/
public void setMetadata(@Nullable MediaMetadata metadata) {
- if (metadata != null ) {
+ if (metadata != null) {
metadata = (new MediaMetadata.Builder(metadata, mMaxBitmapSize)).build();
}
try {
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index ecc4a0d6d497..aee9d38e0a27 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -776,8 +776,8 @@ public class TvView extends ViewGroup {
mSurface = null;
mSurfaceView = new SurfaceView(getContext(), mAttrs, mDefStyleAttr) {
@Override
- protected void updateWindow(boolean force, boolean redrawNeeded) {
- super.updateWindow(force, redrawNeeded);
+ protected void updateWindow() {
+ super.updateWindow();
relayoutSessionOverlayView();
}};
// The surface view's content should be treated as secure all the time.
diff --git a/media/java/android/mtp/MtpConstants.java b/media/java/android/mtp/MtpConstants.java
index 5bbf2ec4b379..7d078d7a97cf 100644
--- a/media/java/android/mtp/MtpConstants.java
+++ b/media/java/android/mtp/MtpConstants.java
@@ -583,43 +583,6 @@ public final class MtpConstants {
*/
public static final int ASSOCIATION_TYPE_GENERIC_FOLDER = 0x0001;
- /** @removed */
- public static final int EVENT_UNDEFINED = 0x4000;
- /** @removed */
- public static final int EVENT_CANCEL_TRANSACTION = 0x4001;
- /** @removed */
- public static final int EVENT_OBJECT_ADDED = 0x4002;
- /** @removed */
- public static final int EVENT_OBJECT_REMOVED = 0x4003;
- /** @removed */
- public static final int EVENT_STORE_ADDED = 0x4004;
- /** @removed */
- public static final int EVENT_STORE_REMOVED = 0x4005;
- /** @removed */
- public static final int EVENT_DEVICE_PROP_CHANGED = 0x4006;
- /** @removed */
- public static final int EVENT_OBJECT_INFO_CHANGED = 0x4007;
- /** @removed */
- public static final int EVENT_DEVICE_INFO_CHANGED = 0x4008;
- /** @removed */
- public static final int EVENT_REQUEST_OBJECT_TRANSFER = 0x4009;
- /** @removed */
- public static final int EVENT_STORE_FULL = 0x400A;
- /** @removed */
- public static final int EVENT_DEVICE_RESET = 0x400B;
- /** @removed */
- public static final int EVENT_STORAGE_INFO_CHANGED = 0x400C;
- /** @removed */
- public static final int EVENT_CAPTURE_COMPLETE = 0x400D;
- /** @removed */
- public static final int EVENT_UNREPORTED_STATUS = 0x400E;
- /** @removed */
- public static final int EVENT_OBJECT_PROP_CHANGED = 0xC801;
- /** @removed */
- public static final int EVENT_OBJECT_PROP_DESC_CHANGED = 0xC802;
- /** @removed */
- public static final int EVENT_OBJECT_REFERENCES_CHANGED = 0xC803;
-
/** Operation code for GetDeviceInfo */
public static final int OPERATION_GET_DEVICE_INFO = 0x1001;
/** Operation code for OpenSession */
diff --git a/media/java/android/mtp/MtpDevice.java b/media/java/android/mtp/MtpDevice.java
index 4082778986c6..6970cffa2c6e 100644
--- a/media/java/android/mtp/MtpDevice.java
+++ b/media/java/android/mtp/MtpDevice.java
@@ -48,7 +48,8 @@ public final class MtpDevice {
*
* @param device the {@link android.hardware.usb.UsbDevice} for the MTP or PTP device
*/
- public MtpDevice(UsbDevice device) {
+ public MtpDevice(@NonNull UsbDevice device) {
+ Preconditions.checkNotNull(device);
mDevice = device;
}
@@ -61,7 +62,7 @@ public final class MtpDevice {
* @param connection an open {@link android.hardware.usb.UsbDeviceConnection} for the device
* @return true if the device was successfully opened.
*/
- public boolean open(UsbDeviceConnection connection) {
+ public boolean open(@NonNull UsbDeviceConnection connection) {
boolean result = native_open(mDevice.getDeviceName(), connection.getFileDescriptor());
if (!result) {
connection.close();
@@ -94,7 +95,7 @@ public final class MtpDevice {
*
* @return the device name
*/
- public String getDeviceName() {
+ public @NonNull String getDeviceName() {
return mDevice.getDeviceName();
}
@@ -110,16 +111,16 @@ public final class MtpDevice {
}
@Override
- public String toString() {
+ public @NonNull String toString() {
return mDevice.getDeviceName();
}
/**
* Returns the {@link MtpDeviceInfo} for this device
*
- * @return the device info
+ * @return the device info, or null if fetching device info fails
*/
- public MtpDeviceInfo getDeviceInfo() {
+ public @Nullable MtpDeviceInfo getDeviceInfo() {
return native_get_device_info();
}
@@ -127,9 +128,9 @@ public final class MtpDevice {
* Returns the list of IDs for all storage units on this device
* Information about each storage unit can be accessed via {@link #getStorageInfo}.
*
- * @return the list of storage IDs
+ * @return the list of storage IDs, or null if fetching storage IDs fails
*/
- public int[] getStorageIds() {
+ public @Nullable int[] getStorageIds() {
return native_get_storage_ids();
}
@@ -142,9 +143,9 @@ public final class MtpDevice {
* @param format the format of the object to return, or zero for all formats
* @param objectHandle the parent object to query, -1 for the storage root,
* or zero for all objects
- * @return the object handles
+ * @return the object handles, or null if fetching object handles fails
*/
- public int[] getObjectHandles(int storageId, int format, int objectHandle) {
+ public @Nullable int[] getObjectHandles(int storageId, int format, int objectHandle) {
return native_get_object_handles(storageId, format, objectHandle);
}
@@ -158,7 +159,7 @@ public final class MtpDevice {
* {@link MtpObjectInfo#getCompressedSize})
* @return the object's data, or null if reading fails
*/
- public byte[] getObject(int objectHandle, int objectSize) {
+ public @Nullable byte[] getObject(int objectHandle, int objectSize) {
Preconditions.checkArgumentNonnegative(objectSize, "objectSize should not be negative");
return native_get_object(objectHandle, objectSize);
}
@@ -176,7 +177,7 @@ public final class MtpDevice {
* @param buffer Array to write data.
* @return Size of bytes that are actually read.
*/
- public long getPartialObject(int objectHandle, long offset, long size, byte[] buffer)
+ public long getPartialObject(int objectHandle, long offset, long size, @NonNull byte[] buffer)
throws IOException {
return native_get_partial_object(objectHandle, offset, size, buffer);
}
@@ -197,7 +198,7 @@ public final class MtpDevice {
* @return Size of bytes that are actually read.
* @see MtpConstants#OPERATION_GET_PARTIAL_OBJECT_64
*/
- public long getPartialObject64(int objectHandle, long offset, long size, byte[] buffer)
+ public long getPartialObject64(int objectHandle, long offset, long size, @NonNull byte[] buffer)
throws IOException {
return native_get_partial_object_64(objectHandle, offset, size, buffer);
}
@@ -212,7 +213,7 @@ public final class MtpDevice {
* @param objectHandle handle of the object to read
* @return the object's thumbnail, or null if reading fails
*/
- public byte[] getThumbnail(int objectHandle) {
+ public @Nullable byte[] getThumbnail(int objectHandle) {
return native_get_thumbnail(objectHandle);
}
@@ -220,9 +221,9 @@ public final class MtpDevice {
* Retrieves the {@link MtpStorageInfo} for a storage unit.
*
* @param storageId the ID of the storage unit
- * @return the MtpStorageInfo
+ * @return the MtpStorageInfo, or null if fetching storage info fails
*/
- public MtpStorageInfo getStorageInfo(int storageId) {
+ public @Nullable MtpStorageInfo getStorageInfo(int storageId) {
return native_get_storage_info(storageId);
}
@@ -230,9 +231,9 @@ public final class MtpDevice {
* Retrieves the {@link MtpObjectInfo} for an object.
*
* @param objectHandle the handle of the object
- * @return the MtpObjectInfo
+ * @return the MtpObjectInfo, or null if fetching object info fails
*/
- public MtpObjectInfo getObjectInfo(int objectHandle) {
+ public @Nullable MtpObjectInfo getObjectInfo(int objectHandle) {
return native_get_object_info(objectHandle);
}
@@ -279,7 +280,7 @@ public final class MtpDevice {
* {@link android.os.Environment#getExternalStorageDirectory}
* @return true if the file transfer succeeds
*/
- public boolean importFile(int objectHandle, String destPath) {
+ public boolean importFile(int objectHandle, @NonNull String destPath) {
return native_import_file(objectHandle, destPath);
}
@@ -293,7 +294,7 @@ public final class MtpDevice {
* @param descriptor file descriptor to write the data to for the file transfer.
* @return true if the file transfer succeeds
*/
- public boolean importFile(int objectHandle, ParcelFileDescriptor descriptor) {
+ public boolean importFile(int objectHandle, @NonNull ParcelFileDescriptor descriptor) {
return native_import_file(objectHandle, descriptor.getFd());
}
@@ -308,7 +309,8 @@ public final class MtpDevice {
* @param descriptor file descriptor to read the data from.
* @return true if the file transfer succeeds
*/
- public boolean sendObject(int objectHandle, long size, ParcelFileDescriptor descriptor) {
+ public boolean sendObject(
+ int objectHandle, long size, @NonNull ParcelFileDescriptor descriptor) {
return native_send_object(objectHandle, size, descriptor.getFd());
}
@@ -319,9 +321,9 @@ public final class MtpDevice {
* The returned {@link MtpObjectInfo} has the new object handle field filled in.
*
* @param info metadata of the entry
- * @return object info of the created entry or null if the operation failed.
+ * @return object info of the created entry, or null if sending object info fails
*/
- public MtpObjectInfo sendObjectInfo(MtpObjectInfo info) {
+ public @Nullable MtpObjectInfo sendObjectInfo(@NonNull MtpObjectInfo info) {
return native_send_object_info(info);
}
diff --git a/media/java/android/mtp/MtpDeviceInfo.java b/media/java/android/mtp/MtpDeviceInfo.java
index 86bd599e442c..0304ee386ace 100644
--- a/media/java/android/mtp/MtpDeviceInfo.java
+++ b/media/java/android/mtp/MtpDeviceInfo.java
@@ -16,6 +16,8 @@
package android.mtp;
+import android.annotation.NonNull;
+
/**
* This class encapsulates information about an MTP device.
* This corresponds to the DeviceInfo Dataset described in
@@ -39,7 +41,7 @@ public class MtpDeviceInfo {
*
* @return the manufacturer name
*/
- public final String getManufacturer() {
+ public final @NonNull String getManufacturer() {
return mManufacturer;
}
@@ -48,7 +50,7 @@ public class MtpDeviceInfo {
*
* @return the model name
*/
- public final String getModel() {
+ public final @NonNull String getModel() {
return mModel;
}
@@ -57,7 +59,7 @@ public class MtpDeviceInfo {
*
* @return the device version
*/
- public final String getVersion() {
+ public final @NonNull String getVersion() {
return mVersion;
}
@@ -66,7 +68,7 @@ public class MtpDeviceInfo {
*
* @return the serial number
*/
- public final String getSerialNumber() {
+ public final @NonNull String getSerialNumber() {
return mSerialNumber;
}
@@ -110,7 +112,7 @@ public class MtpDeviceInfo {
* @see MtpConstants#OPERATION_SET_OBJECT_REFERENCES
* @see MtpConstants#OPERATION_SKIP
*/
- public final int[] getOperationsSupported() {
+ public final @NonNull int[] getOperationsSupported() {
return mOperationsSupported;
}
@@ -137,7 +139,7 @@ public class MtpDeviceInfo {
* @see MtpEvent#EVENT_OBJECT_PROP_DESC_CHANGED
* @see MtpEvent#EVENT_OBJECT_REFERENCES_CHANGED
*/
- public final int[] getEventsSupported() {
+ public final @NonNull int[] getEventsSupported() {
return mEventsSupported;
}
@@ -163,7 +165,7 @@ public class MtpDeviceInfo {
* Returns if the code set contains code.
* @hide
*/
- private static boolean isSupported(int[] set, int code) {
+ private static boolean isSupported(@NonNull int[] set, int code) {
for (final int element : set) {
if (element == code) {
return true;
diff --git a/media/java/android/mtp/MtpObjectInfo.java b/media/java/android/mtp/MtpObjectInfo.java
index 02092b177fe0..35d8dfbad84a 100644
--- a/media/java/android/mtp/MtpObjectInfo.java
+++ b/media/java/android/mtp/MtpObjectInfo.java
@@ -16,8 +16,13 @@
package android.mtp;
+import android.annotation.NonNull;
+import android.os.Build;
+
import com.android.internal.util.Preconditions;
+import dalvik.system.VMRuntime;
+
/**
* This class encapsulates information about an object on an MTP device.
* This corresponds to the ObjectInfo Dataset described in
@@ -40,10 +45,10 @@ public final class MtpObjectInfo {
private int mAssociationType;
private int mAssociationDesc;
private int mSequenceNumber;
- private String mName;
+ private String mName = "";
private long mDateCreated;
private long mDateModified;
- private String mKeywords;
+ private String mKeywords = "";
// only instantiated via JNI or via a builder
private MtpObjectInfo() {
@@ -311,7 +316,7 @@ public final class MtpObjectInfo {
*
* @return the object's name
*/
- public final String getName() {
+ public final @NonNull String getName() {
return mName;
}
@@ -340,7 +345,7 @@ public final class MtpObjectInfo {
*
* @return the object's keyword list
*/
- public final String getKeywords() {
+ public final @NonNull String getKeywords() {
return mKeywords;
}
@@ -435,12 +440,20 @@ public final class MtpObjectInfo {
return this;
}
- public Builder setKeywords(String value) {
+ public Builder setKeywords(@NonNull String value) {
+ if (VMRuntime.getRuntime().getTargetSdkVersion() > Build.VERSION_CODES.N_MR1) {
+ Preconditions.checkNotNull(value);
+ } else if (value == null) {
+ // Before N_MR1 we accept null value and it was regarded as an empty string in
+ // MtpDevice#sendObjectInfo.
+ value = "";
+ }
mObjectInfo.mKeywords = value;
return this;
}
- public Builder setName(String value) {
+ public Builder setName(@NonNull String value) {
+ Preconditions.checkNotNull(value);
mObjectInfo.mName = value;
return this;
}
diff --git a/media/java/android/mtp/MtpServer.java b/media/java/android/mtp/MtpServer.java
index 61fbfb902d0d..99f93e412b11 100644
--- a/media/java/android/mtp/MtpServer.java
+++ b/media/java/android/mtp/MtpServer.java
@@ -16,6 +16,8 @@
package android.mtp;
+import com.android.internal.util.Preconditions;
+
/**
* Java wrapper for MTP/PTP support as USB responder.
* {@hide}
@@ -24,13 +26,15 @@ public class MtpServer implements Runnable {
private long mNativeContext; // accessed by native methods
private final MtpDatabase mDatabase;
+ private final Runnable mOnTerminate;
static {
System.loadLibrary("media_jni");
}
- public MtpServer(MtpDatabase database, boolean usePtp) {
- mDatabase = database;
+ public MtpServer(MtpDatabase database, boolean usePtp, Runnable onTerminate) {
+ mDatabase = Preconditions.checkNotNull(database);
+ mOnTerminate = Preconditions.checkNotNull(onTerminate);
native_setup(database, usePtp);
database.setServer(this);
}
@@ -45,6 +49,7 @@ public class MtpServer implements Runnable {
native_run();
native_cleanup();
mDatabase.close();
+ mOnTerminate.run();
}
public void sendObjectAdded(int handle) {
diff --git a/media/java/android/mtp/MtpStorageInfo.java b/media/java/android/mtp/MtpStorageInfo.java
index d1b86fcfc2e6..af9f24a13db5 100644
--- a/media/java/android/mtp/MtpStorageInfo.java
+++ b/media/java/android/mtp/MtpStorageInfo.java
@@ -16,6 +16,8 @@
package android.mtp;
+import android.annotation.NonNull;
+
/**
* This class encapsulates information about a storage unit on an MTP device.
* This corresponds to the StorageInfo Dataset described in
@@ -68,7 +70,7 @@ public final class MtpStorageInfo {
*
* @return the storage unit description
*/
- public final String getDescription() {
+ public final @NonNull String getDescription() {
return mDescription;
}
@@ -77,7 +79,7 @@ public final class MtpStorageInfo {
*
* @return the storage volume identifier
*/
- public final String getVolumeIdentifier() {
+ public final @NonNull String getVolumeIdentifier() {
return mVolumeIdentifier;
}
}
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index c0d950323db5..a811ad0e5a1f 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -48,6 +48,7 @@ import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
/**
@@ -362,6 +363,7 @@ public abstract class MediaBrowserService extends Service {
* @see BrowserRoot#EXTRA_RECENT
* @see BrowserRoot#EXTRA_OFFLINE
* @see BrowserRoot#EXTRA_SUGGESTED
+ * @see BrowserRoot#EXTRA_SUGGESTION_KEYWORDS
*/
public abstract @Nullable BrowserRoot onGetRoot(@NonNull String clientPackageName,
int clientUid, @Nullable Bundle rootHints);
@@ -465,14 +467,15 @@ public abstract class MediaBrowserService extends Service {
mHandler.post(new Runnable() {
@Override
public void run() {
- for (IBinder key : mConnections.keySet()) {
- ConnectionRecord connection = mConnections.get(key);
+ Iterator<ConnectionRecord> iter = mConnections.values().iterator();
+ while (iter.hasNext()){
+ ConnectionRecord connection = iter.next();
try {
connection.callbacks.onConnect(connection.root.getRootId(), token,
connection.root.getExtras());
} catch (RemoteException e) {
Log.w(TAG, "Connection for " + connection.pkg + " is no longer valid.");
- mConnections.remove(key);
+ iter.remove();
}
}
}
@@ -498,6 +501,7 @@ public abstract class MediaBrowserService extends Service {
* @see MediaBrowserService.BrowserRoot#EXTRA_RECENT
* @see MediaBrowserService.BrowserRoot#EXTRA_OFFLINE
* @see MediaBrowserService.BrowserRoot#EXTRA_SUGGESTED
+ * @see MediaBrowserService.BrowserRoot#EXTRA_SUGGESTION_KEYWORDS
*/
public final Bundle getBrowserRootHints() {
if (mCurConnection == null) {
@@ -610,10 +614,11 @@ public abstract class MediaBrowserService extends Service {
boolean removed = false;
List<Pair<IBinder, Bundle>> callbackList = connection.subscriptions.get(id);
if (callbackList != null) {
- for (Pair<IBinder, Bundle> callback : callbackList) {
- if (token == callback.first) {
+ Iterator<Pair<IBinder, Bundle>> iter = callbackList.iterator();
+ while (iter.hasNext()){
+ if (token == iter.next().first) {
removed = true;
- callbackList.remove(callback);
+ iter.remove();
}
}
if (callbackList.size() == 0) {
@@ -732,6 +737,7 @@ public abstract class MediaBrowserService extends Service {
*
* @see #EXTRA_OFFLINE
* @see #EXTRA_SUGGESTED
+ * @see #EXTRA_SUGGESTION_KEYWORDS
*/
public static final String EXTRA_RECENT = "android.service.media.extra.RECENT";
@@ -749,6 +755,7 @@ public abstract class MediaBrowserService extends Service {
*
* @see #EXTRA_RECENT
* @see #EXTRA_SUGGESTED
+ * @see #EXTRA_SUGGESTION_KEYWORDS
*/
public static final String EXTRA_OFFLINE = "android.service.media.extra.OFFLINE";
@@ -767,9 +774,31 @@ public abstract class MediaBrowserService extends Service {
*
* @see #EXTRA_RECENT
* @see #EXTRA_OFFLINE
+ * @see #EXTRA_SUGGESTION_KEYWORDS
*/
public static final String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
+ /**
+ * The lookup key for a string that indicates specific keywords which will be considered
+ * when the browser service suggests media items.
+ *
+ * <p>When creating a media browser for a given media browser service, this key can be
+ * supplied as a root hint together with {@link #EXTRA_SUGGESTED} for retrieving suggested
+ * media items related with the keywords. The list of media items passed in
+ * {@link android.media.browse.MediaBrowser.SubscriptionCallback#onChildrenLoaded(String, List)}
+ * is considered ordered by relevance, first being the top suggestion.
+ * If the media browser service can provide such media items, the implementation must return
+ * the key in the root hint when {@link #onGetRoot(String, int, Bundle)} is called back.
+ *
+ * <p>The root hint may contain multiple keys.
+ *
+ * @see #EXTRA_RECENT
+ * @see #EXTRA_OFFLINE
+ * @see #EXTRA_SUGGESTED
+ */
+ public static final String EXTRA_SUGGESTION_KEYWORDS
+ = "android.service.media.extra.SUGGESTION_KEYWORDS";
+
final private String mRootId;
final private Bundle mExtras;