diff options
Diffstat (limited to 'media/java/android')
-rw-r--r-- | media/java/android/media/AudioSystem.java | 10 | ||||
-rw-r--r-- | media/java/android/media/AudioTrack.java | 4 | ||||
-rw-r--r-- | media/java/android/media/ExifInterface.java | 750 | ||||
-rw-r--r-- | media/java/android/media/MediaCodecInfo.java | 1 | ||||
-rw-r--r-- | media/java/android/media/MediaDescription.java | 62 | ||||
-rw-r--r-- | media/java/android/media/MediaMetadata.java | 50 | ||||
-rw-r--r-- | media/java/android/media/MediaRecorder.java | 14 | ||||
-rw-r--r-- | media/java/android/media/browse/MediaBrowser.java | 41 | ||||
-rw-r--r-- | media/java/android/media/session/MediaSession.java | 6 | ||||
-rw-r--r-- | media/java/android/media/tv/TvView.java | 4 | ||||
-rw-r--r-- | media/java/android/mtp/MtpConstants.java | 37 | ||||
-rw-r--r-- | media/java/android/mtp/MtpDevice.java | 48 | ||||
-rw-r--r-- | media/java/android/mtp/MtpDeviceInfo.java | 16 | ||||
-rw-r--r-- | media/java/android/mtp/MtpObjectInfo.java | 25 | ||||
-rw-r--r-- | media/java/android/mtp/MtpServer.java | 9 | ||||
-rw-r--r-- | media/java/android/mtp/MtpStorageInfo.java | 6 | ||||
-rw-r--r-- | media/java/android/service/media/MediaBrowserService.java | 41 |
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; |