diff options
Diffstat (limited to 'media')
44 files changed, 1306 insertions, 214 deletions
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 1dcfcb8..3a3f76d 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -428,7 +428,6 @@ public class AudioManager { public static final int USE_DEFAULT_STREAM_TYPE = Integer.MIN_VALUE; private static IAudioService sService; - private MediaSessionLegacyHelper mSessionHelper; /** * @hide @@ -439,9 +438,6 @@ public class AudioManager { com.android.internal.R.bool.config_useMasterVolume); mUseVolumeKeySounds = mContext.getResources().getBoolean( com.android.internal.R.bool.config_useVolumeKeySounds); - if (USE_SESSIONS) { - mSessionHelper = MediaSessionLegacyHelper.getHelper(context); - } } private static IAudioService getService() @@ -478,11 +474,16 @@ public class AudioManager { * or {@link KeyEvent#KEYCODE_MEDIA_AUDIO_TRACK}. */ public void dispatchMediaKeyEvent(KeyEvent keyEvent) { - IAudioService service = getService(); - try { - service.dispatchMediaKeyEvent(keyEvent); - } catch (RemoteException e) { - Log.e(TAG, "dispatchMediaKeyEvent threw exception ", e); + if (USE_SESSIONS) { + MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext); + helper.sendMediaButtonEvent(keyEvent, false); + } else { + IAudioService service = getService(); + try { + service.dispatchMediaKeyEvent(keyEvent); + } catch (RemoteException e) { + Log.e(TAG, "dispatchMediaKeyEvent threw exception ", e); + } } } @@ -2178,7 +2179,8 @@ public class AudioManager { Log.e(TAG, "Dead object in registerMediaButtonIntent"+e); } if (USE_SESSIONS) { - mSessionHelper.addMediaButtonListener(pi, mContext); + MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext); + helper.addMediaButtonListener(pi, mContext); } } @@ -2254,7 +2256,8 @@ public class AudioManager { Log.e(TAG, "Dead object in unregisterMediaButtonIntent"+e); } if (USE_SESSIONS) { - mSessionHelper.removeMediaButtonListener(pi); + MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext); + helper.removeMediaButtonListener(pi); } } @@ -2281,7 +2284,7 @@ public class AudioManager { Log.e(TAG, "Dead object in registerRemoteControlClient"+e); } if (USE_SESSIONS) { - rcClient.registerWithSession(mSessionHelper); + rcClient.registerWithSession(MediaSessionLegacyHelper.getHelper(mContext)); } } @@ -2303,7 +2306,7 @@ public class AudioManager { Log.e(TAG, "Dead object in unregisterRemoteControlClient"+e); } if (USE_SESSIONS) { - rcClient.unregisterWithSession(mSessionHelper); + rcClient.unregisterWithSession(MediaSessionLegacyHelper.getHelper(mContext)); } } diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java index 384e120..d4e85c8 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -61,25 +61,25 @@ public class AudioRecord */ public static final int RECORDSTATE_RECORDING = 3;// matches SL_RECORDSTATE_RECORDING - // Error codes: - // to keep in sync with frameworks/base/core/jni/android_media_AudioRecord.cpp /** * Denotes a successful operation. */ - public static final int SUCCESS = 0; + public static final int SUCCESS = AudioSystem.SUCCESS; /** * Denotes a generic operation failure. */ - public static final int ERROR = -1; + public static final int ERROR = AudioSystem.ERROR; /** * Denotes a failure due to the use of an invalid value. */ - public static final int ERROR_BAD_VALUE = -2; + public static final int ERROR_BAD_VALUE = AudioSystem.BAD_VALUE; /** * Denotes a failure due to the improper use of a method. */ - public static final int ERROR_INVALID_OPERATION = -3; + public static final int ERROR_INVALID_OPERATION = AudioSystem.INVALID_OPERATION; + // Error codes: + // to keep in sync with frameworks/base/core/jni/android_media_AudioRecord.cpp private static final int AUDIORECORD_ERROR_SETUP_ZEROFRAMECOUNT = -16; private static final int AUDIORECORD_ERROR_SETUP_INVALIDCHANNELMASK = -17; private static final int AUDIORECORD_ERROR_SETUP_INVALIDFORMAT = -18; diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index 724022b..bb8cfa6 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -46,6 +46,7 @@ import android.database.ContentObserver; import android.hardware.usb.UsbManager; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnErrorListener; +import android.media.session.MediaSessionLegacyHelper; import android.os.Binder; import android.os.Build; import android.os.Environment; @@ -108,6 +109,10 @@ public class AudioService extends IAudioService.Stub { /** Debug volumes */ protected static final boolean DEBUG_VOL = false; + /** Reroute calls to media session apis */ + private static final boolean USE_SESSIONS = true; + private static final boolean DEBUG_SESSIONS = true; + /** How long to delay before persisting a change in volume/ringer mode. */ private static final int PERSIST_DELAY = 500; @@ -3472,7 +3477,7 @@ public class AudioService extends IAudioService.Stub { if (volume < 0) { volFloat = (float)Math.pow(10, (float)sSoundEffectVolumeDb/20); } else { - volFloat = (float) volume / 1000.0f; + volFloat = volume / 1000.0f; } if (SOUND_EFFECT_FILES_MAP[effectType][1] > 0) { @@ -3554,7 +3559,7 @@ public class AudioService extends IAudioService.Stub { } Settings.System.putFloatForUser(mContentResolver, Settings.System.VOLUME_MASTER, - (float)msg.arg1 / (float)1000.0, + msg.arg1 / (float)1000.0, UserHandle.USER_CURRENT); break; @@ -4325,11 +4330,27 @@ public class AudioService extends IAudioService.Stub { } public void dispatchMediaKeyEvent(KeyEvent keyEvent) { - mMediaFocusControl.dispatchMediaKeyEvent(keyEvent); + if (USE_SESSIONS) { + if (DEBUG_SESSIONS) { + int pid = getCallingPid(); + Log.w(TAG, "Call to dispatchMediaKeyEvent from " + pid); + } + MediaSessionLegacyHelper.getHelper(mContext).sendMediaButtonEvent(keyEvent, false); + } else { + mMediaFocusControl.dispatchMediaKeyEvent(keyEvent); + } } public void dispatchMediaKeyEventUnderWakelock(KeyEvent keyEvent) { - mMediaFocusControl.dispatchMediaKeyEventUnderWakelock(keyEvent); + if (USE_SESSIONS) { + if (DEBUG_SESSIONS) { + int pid = getCallingPid(); + Log.w(TAG, "Call to dispatchMediaKeyEventUnderWakelock from " + pid); + } + MediaSessionLegacyHelper.getHelper(mContext).sendMediaButtonEvent(keyEvent, true); + } else { + mMediaFocusControl.dispatchMediaKeyEventUnderWakelock(keyEvent); + } } //========================================================================================== diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 327c10c..5ddb198 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -199,6 +199,17 @@ public class AudioSystem } } + /* + * Error codes used by public APIs (AudioTrack, AudioRecord, AudioManager ...) + * Must be kept in sync with frameworks/base/core/jni/android_media_AudioErrors.h + */ + public static final int SUCCESS = 0; + public static final int ERROR = -1; + public static final int BAD_VALUE = -2; + public static final int INVALID_OPERATION = -3; + public static final int PERMISSION_DENIED = -4; + public static final int NO_INIT = -5; + public static final int DEAD_OBJECT = -6; /* * AudioPolicyService methods diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index 1a64cff..1baaaa4 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -125,25 +125,25 @@ public class AudioTrack */ public static final int STATE_NO_STATIC_DATA = 2; - // Error codes: - // to keep in sync with frameworks/base/core/jni/android_media_AudioTrack.cpp /** * Denotes a successful operation. */ - public static final int SUCCESS = 0; + public static final int SUCCESS = AudioSystem.SUCCESS; /** * Denotes a generic operation failure. */ - public static final int ERROR = -1; + public static final int ERROR = AudioSystem.ERROR; /** * Denotes a failure due to the use of an invalid value. */ - public static final int ERROR_BAD_VALUE = -2; + public static final int ERROR_BAD_VALUE = AudioSystem.BAD_VALUE; /** * Denotes a failure due to the improper use of a method. */ - public static final int ERROR_INVALID_OPERATION = -3; + public static final int ERROR_INVALID_OPERATION = AudioSystem.INVALID_OPERATION; + // Error codes: + // to keep in sync with frameworks/base/core/jni/android_media_AudioTrack.cpp private static final int ERROR_NATIVESETUP_AUDIOSYSTEM = -16; private static final int ERROR_NATIVESETUP_INVALIDCHANNELMASK = -17; private static final int ERROR_NATIVESETUP_INVALIDFORMAT = -18; diff --git a/media/java/android/media/DngCreator.java b/media/java/android/media/DngCreator.java index b2a38ab..76c6d46 100644 --- a/media/java/android/media/DngCreator.java +++ b/media/java/android/media/DngCreator.java @@ -17,9 +17,12 @@ package android.media; import android.graphics.Bitmap; +import android.graphics.ImageFormat; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CaptureResult; +import android.hardware.camera2.impl.CameraMetadataNative; import android.location.Location; +import android.util.Size; import java.io.IOException; import java.io.InputStream; @@ -50,7 +53,7 @@ import java.nio.ByteBuffer; * Adobe DNG 1.4.0.0 specification</a>. * </p> */ -public final class DngCreator { +public final class DngCreator implements AutoCloseable { /** * Create a new DNG object. @@ -68,7 +71,12 @@ public final class DngCreator { * {@link android.hardware.camera2.CameraCharacteristics}. * @param metadata a metadata object to generate tags from. */ - public DngCreator(CameraCharacteristics characteristics, CaptureResult metadata) {/*TODO*/} + public DngCreator(CameraCharacteristics characteristics, CaptureResult metadata) { + if (characteristics == null || metadata == null) { + throw new NullPointerException("Null argument to DngCreator constructor"); + } + nativeInit(characteristics.getNativeCopy(), metadata.getNativeCopy()); + } /** * Set the orientation value to write. @@ -92,6 +100,13 @@ public final class DngCreator { * @return this {@link #DngCreator} object. */ public DngCreator setOrientation(int orientation) { + + if (orientation < ExifInterface.ORIENTATION_UNDEFINED || + orientation > ExifInterface.ORIENTATION_ROTATE_270) { + throw new IllegalArgumentException("Orientation " + orientation + + " is not a valid EXIF orientation value"); + } + nativeSetOrientation(orientation); return this; } @@ -111,6 +126,20 @@ public final class DngCreator { * @return this {@link #DngCreator} object. */ public DngCreator setThumbnail(Bitmap pixels) { + if (pixels == null) { + throw new NullPointerException("Null argument to setThumbnail"); + } + + Bitmap.Config config = pixels.getConfig(); + + if (config != Bitmap.Config.ARGB_8888) { + pixels = pixels.copy(Bitmap.Config.ARGB_8888, false); + if (pixels == null) { + throw new IllegalArgumentException("Unsupported Bitmap format " + config); + } + nativeSetThumbnailBitmap(pixels); + } + return this; } @@ -130,6 +159,21 @@ public final class DngCreator { * @return this {@link #DngCreator} object. */ public DngCreator setThumbnail(Image pixels) { + if (pixels == null) { + throw new NullPointerException("Null argument to setThumbnail"); + } + + int format = pixels.getFormat(); + if (format != ImageFormat.YUV_420_888) { + throw new IllegalArgumentException("Unsupported image format " + format); + } + + Image.Plane[] planes = pixels.getPlanes(); + nativeSetThumbnailImage(pixels.getWidth(), pixels.getHeight(), planes[0].getBuffer(), + planes[0].getRowStride(), planes[0].getPixelStride(), planes[1].getBuffer(), + planes[1].getRowStride(), planes[1].getPixelStride(), planes[1].getBuffer(), + planes[1].getRowStride(), planes[1].getPixelStride()); + return this; } @@ -150,7 +194,10 @@ public final class DngCreator { * @throws java.lang.IllegalArgumentException if the given location object doesn't * contain enough information to set location metadata. */ - public DngCreator setLocation(Location location) { return this; } + public DngCreator setLocation(Location location) { + /*TODO*/ + return this; + } /** * Set the user description string to write. @@ -163,6 +210,7 @@ public final class DngCreator { * @return this {@link #DngCreator} object. */ public DngCreator setDescription(String description) { + /*TODO*/ return this; } @@ -172,32 +220,33 @@ public final class DngCreator { * * <p> * Raw pixel data must have 16 bits per pixel, and the input must contain at least - * {@code offset + 2 * (stride * (height - 1) + width * height)} bytes. The width and height of + * {@code offset + 2 * width * height)} bytes. The width and height of * the input are taken from the width and height set in the {@link DngCreator} metadata tags, * and will typically be equal to the width and height of - * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}. - * If insufficient metadata is set to write a well-formatted DNG file, and + * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}. + * The pixel layout in the input is determined from the reported color filter arrangement (CFA) + * set in {@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT}. If insufficient + * metadata is available to write a well-formatted DNG file, an * {@link java.lang.IllegalStateException} will be thrown. * </p> * - * <p> - * When reading from the pixel input, {@code stride} pixels will be skipped - * after each row (excluding the last). - * </p> - * * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to. + * @param size the {@link Size} of the image to write, in pixels. * @param pixels an {@link java.io.InputStream} of pixel data to write. - * @param stride the stride of the raw image in pixels. * @param offset the offset of the raw image in bytes. This indicates how many bytes will * be skipped in the input before any pixel data is read. * * @throws IOException if an error was encountered in the input or output stream. * @throws java.lang.IllegalStateException if not enough metadata information has been * set to write a well-formatted DNG file. + * @throws java.lang.IllegalArgumentException if the size passed in does not match the */ - public void writeInputStream(OutputStream dngOutput, InputStream pixels, int stride, - long offset) throws IOException { - /*TODO*/ + public void writeInputStream(OutputStream dngOutput, Size size, InputStream pixels, long offset) + throws IOException { + if (dngOutput == null || pixels == null) { + throw new NullPointerException("Null argument to writeImage"); + } + nativeWriteInputStream(dngOutput, pixels, offset); } /** @@ -206,22 +255,18 @@ public final class DngCreator { * * <p> * Raw pixel data must have 16 bits per pixel, and the input must contain at least - * {@code offset + 2 * (stride * (height - 1) + width * height)} bytes. The width and height of + * {@code offset + 2 * width * height)} bytes. The width and height of * the input are taken from the width and height set in the {@link DngCreator} metadata tags, * and will typically be equal to the width and height of - * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}. - * If insufficient metadata is set to write a well-formatted DNG file, and + * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}. + * The pixel layout in the input is determined from the reported color filter arrangement (CFA) + * set in {@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT}. If insufficient + * metadata is available to write a well-formatted DNG file, an * {@link java.lang.IllegalStateException} will be thrown. * </p> * - * <p> - * When reading from the pixel input, {@code stride} pixels will be skipped - * after each row (excluding the last). - * </p> - * * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to. * @param pixels an {@link java.nio.ByteBuffer} of pixel data to write. - * @param stride the stride of the raw image in pixels. * @param offset the offset of the raw image in bytes. This indicates how many bytes will * be skipped in the input before any pixel data is read. * @@ -229,8 +274,13 @@ public final class DngCreator { * @throws java.lang.IllegalStateException if not enough metadata information has been * set to write a well-formatted DNG file. */ - public void writeByteBuffer(OutputStream dngOutput, ByteBuffer pixels, int stride, - long offset) throws IOException {/*TODO*/} + public void writeByteBuffer(OutputStream dngOutput, Size size, ByteBuffer pixels, long offset) + throws IOException { + if (dngOutput == null || pixels == null) { + throw new NullPointerException("Null argument to writeImage"); + } + nativeWriteByteBuffer(dngOutput, pixels, offset); + } /** * Write the pixel data to a DNG file with the currently configured metadata. @@ -249,6 +299,70 @@ public final class DngCreator { * @throws java.lang.IllegalStateException if not enough metadata information has been * set to write a well-formatted DNG file. */ - public void writeImage(OutputStream dngOutput, Image pixels) throws IOException {/*TODO*/} + public void writeImage(OutputStream dngOutput, Image pixels) throws IOException { + if (dngOutput == null || pixels == null) { + throw new NullPointerException("Null argument to writeImage"); + } + int format = pixels.getFormat(); + if (format != ImageFormat.RAW_SENSOR) { + throw new IllegalArgumentException("Unsupported image format " + format); + } + + Image.Plane[] planes = pixels.getPlanes(); + nativeWriteImage(dngOutput, pixels.getWidth(), pixels.getHeight(), planes[0].getBuffer(), + planes[0].getRowStride(), planes[0].getPixelStride()); + } + + @Override + public void close() { + nativeDestroy(); + } + + @Override + protected void finalize() throws Throwable { + try { + close(); + } finally { + super.finalize(); + } + } + + /** + * This field is used by native code, do not access or modify. + */ + private long mNativeContext; + + private static native void nativeClassInit(); + + private synchronized native void nativeInit(CameraMetadataNative nativeCharacteristics, + CameraMetadataNative nativeResult); + + private synchronized native void nativeDestroy(); + + private synchronized native void nativeSetOrientation(int orientation); + + private synchronized native void nativeSetThumbnailBitmap(Bitmap bitmap); + + private synchronized native void nativeSetThumbnailImage(int width, int height, + ByteBuffer yBuffer, int yRowStride, + int yPixStride, ByteBuffer uBuffer, + int uRowStride, int uPixStride, + ByteBuffer vBuffer, int vRowStride, + int vPixStride); + + private synchronized native void nativeWriteImage(OutputStream out, int width, int height, + ByteBuffer rawBuffer, int rowStride, + int pixStride) throws IOException; + + private synchronized native void nativeWriteByteBuffer(OutputStream out, ByteBuffer rawBuffer, + long offset) throws IOException; + + private synchronized native void nativeWriteInputStream(OutputStream out, InputStream rawStream, + long offset) throws IOException; + + static { + System.loadLibrary("media_jni"); + nativeClassInit(); + } } diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index 34c5520..c7b3fc9 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -212,6 +212,7 @@ final public class MediaCodec { * <li>"video/x-vnd.on2.vp8" - VP8 video (i.e. video in .webm) * <li>"video/x-vnd.on2.vp9" - VP9 video (i.e. video in .webm) * <li>"video/avc" - H.264/AVC video + * <li>"video/hevc" - H.265/HEVC video * <li>"video/mp4v-es" - MPEG4 video * <li>"video/3gpp" - H.263 video * <li>"audio/3gpp" - AMR narrowband audio diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index 90c12c6..b5d0a57 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -264,6 +264,37 @@ public final class MediaCodecInfo { // from OMX_VIDEO_VP8PROFILETYPE public static final int VP8ProfileMain = 0x01; + // from OMX_VIDEO_HEVCPROFILETYPE + public static final int HEVCProfileMain = 0x01; + public static final int HEVCProfileMain10 = 0x02; + + // from OMX_VIDEO_HEVCLEVELTYPE + public static final int HEVCMainTierLevel1 = 0x1; + public static final int HEVCHighTierLevel1 = 0x2; + public static final int HEVCMainTierLevel2 = 0x4; + public static final int HEVCHighTierLevel2 = 0x8; + public static final int HEVCMainTierLevel21 = 0x10; + public static final int HEVCHighTierLevel21 = 0x20; + public static final int HEVCMainTierLevel3 = 0x40; + public static final int HEVCHighTierLevel3 = 0x80; + public static final int HEVCMainTierLevel31 = 0x100; + public static final int HEVCHighTierLevel31 = 0x200; + public static final int HEVCMainTierLevel4 = 0x400; + public static final int HEVCHighTierLevel4 = 0x800; + public static final int HEVCMainTierLevel41 = 0x1000; + public static final int HEVCHighTierLevel41 = 0x2000; + public static final int HEVCMainTierLevel5 = 0x4000; + public static final int HEVCHighTierLevel5 = 0x8000; + public static final int HEVCMainTierLevel51 = 0x10000; + public static final int HEVCHighTierLevel51 = 0x20000; + public static final int HEVCMainTierLevel52 = 0x40000; + public static final int HEVCHighTierLevel52 = 0x80000; + public static final int HEVCMainTierLevel6 = 0x100000; + public static final int HEVCHighTierLevel6 = 0x200000; + public static final int HEVCMainTierLevel61 = 0x400000; + public static final int HEVCHighTierLevel61 = 0x800000; + public static final int HEVCMainTierLevel62 = 0x1000000; + public static final int HEVCHighTierLevel62 = 0x2000000; /** * Defined in the OpenMAX IL specs, depending on the type of media diff --git a/media/java/android/media/session/SessionToken.aidl b/media/java/android/media/MediaMetadata.aidl index db35f85..66ee483 100644 --- a/media/java/android/media/session/SessionToken.aidl +++ b/media/java/android/media/MediaMetadata.aidl @@ -13,6 +13,6 @@ ** limitations under the License. */ -package android.media.session; +package android.media; -parcelable SessionToken; +parcelable MediaMetadata; diff --git a/media/java/android/media/session/MediaMetadata.java b/media/java/android/media/MediaMetadata.java index 8a8af45..ff73a10 100644 --- a/media/java/android/media/session/MediaMetadata.java +++ b/media/java/android/media/MediaMetadata.java @@ -13,12 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.media.session; +package android.media; import android.graphics.Bitmap; -import android.media.MediaMetadataEditor; -import android.media.MediaMetadataRetriever; -import android.media.Rating; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; diff --git a/media/java/android/media/MediaMetadataEditor.java b/media/java/android/media/MediaMetadataEditor.java index 1a4e8da..ca44e9d 100644 --- a/media/java/android/media/MediaMetadataEditor.java +++ b/media/java/android/media/MediaMetadataEditor.java @@ -17,7 +17,6 @@ package android.media; import android.graphics.Bitmap; -import android.media.session.MediaMetadata; import android.os.Bundle; import android.os.Parcelable; import android.util.Log; diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java index 8368df9..37f45c2 100644 --- a/media/java/android/media/RemoteControlClient.java +++ b/media/java/android/media/RemoteControlClient.java @@ -24,10 +24,9 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.RectF; -import android.media.session.MediaMetadata; import android.media.session.MediaSessionLegacyHelper; import android.media.session.PlaybackState; -import android.media.session.Session; +import android.media.session.MediaSession; import android.media.session.TransportPerformer; import android.os.Bundle; import android.os.Handler; @@ -341,7 +340,7 @@ public class RemoteControlClient */ public final static int FLAG_INFORMATION_REQUEST_ALBUM_ART = 1 << 3; - private Session mSession; + private MediaSession mSession; /** * Class constructor. diff --git a/media/java/android/media/routeprovider/RouteConnection.java b/media/java/android/media/routeprovider/RouteConnection.java index 9214ff8..43692c1 100644 --- a/media/java/android/media/routeprovider/RouteConnection.java +++ b/media/java/android/media/routeprovider/RouteConnection.java @@ -40,6 +40,7 @@ import java.util.List; * interfaces. Use {@link #addRouteInterface(String)} to add an interface and * {@link #getRouteInterface(String)} to retrieve the interface's handle anytime * after it has been added. + * @hide */ public final class RouteConnection { private static final String TAG = "RouteConnection"; diff --git a/media/java/android/media/routeprovider/RouteInterfaceHandler.java b/media/java/android/media/routeprovider/RouteInterfaceHandler.java index 9693dc6..e7f8bbf 100644 --- a/media/java/android/media/routeprovider/RouteInterfaceHandler.java +++ b/media/java/android/media/routeprovider/RouteInterfaceHandler.java @@ -16,7 +16,7 @@ package android.media.routeprovider; import android.media.session.Route; -import android.media.session.Session; +import android.media.session.MediaSession; import android.media.session.RouteInterface; import android.os.Bundle; import android.os.Handler; @@ -33,7 +33,7 @@ import java.util.ArrayList; * connected media route. * <p> * A {@link RouteProviderService} may expose multiple interfaces on a - * {@link RouteConnection} for a {@link Session} to interact with. A + * {@link RouteConnection} for a {@link MediaSession} to interact with. A * provider creates an interface with * {@link RouteConnection#addRouteInterface(String)} to allow messages to be * routed appropriately. Events are then sent through a specific interface and @@ -47,6 +47,7 @@ import java.util.ArrayList; * It is recommended you wrap this interface with a standard implementation to * avoid errors, but for simple interfaces this class may be used directly. TODO * add link to sample code. + * @hide */ public final class RouteInterfaceHandler { private static final String TAG = "RouteInterfaceHandler"; @@ -184,7 +185,7 @@ public final class RouteInterfaceHandler { public abstract static class CommandListener { /** * This is called when a command is received that matches this - * interface. Commands are sent by a {@link Session} that is + * interface. Commands are sent by a {@link MediaSession} that is * connected to the route this interface is registered with. * * @param iface The interface the command was received on. @@ -197,7 +198,7 @@ public final class RouteInterfaceHandler { * true may be returned if the command will be handled * asynchronously. * @see Route - * @see Session + * @see MediaSession */ public abstract boolean onCommand(RouteInterfaceHandler iface, String command, Bundle args, ResultReceiver cb); diff --git a/media/java/android/media/routeprovider/RoutePlaybackControlsHandler.java b/media/java/android/media/routeprovider/RoutePlaybackControlsHandler.java index dcef79a..f2c40d2 100644 --- a/media/java/android/media/routeprovider/RoutePlaybackControlsHandler.java +++ b/media/java/android/media/routeprovider/RoutePlaybackControlsHandler.java @@ -28,6 +28,7 @@ import android.util.Log; * Standard wrapper for using playback controls over a {@link RouteInterfaceHandler}. * This is the provider half of the interface. Sessions should use * {@link RoutePlaybackControls} to interact with this interface. + * @hide */ public final class RoutePlaybackControlsHandler { private static final String TAG = "RoutePlaybackControls"; diff --git a/media/java/android/media/routeprovider/RouteProviderService.java b/media/java/android/media/routeprovider/RouteProviderService.java index 6ebfb5b..a6ef0bb 100644 --- a/media/java/android/media/routeprovider/RouteProviderService.java +++ b/media/java/android/media/routeprovider/RouteProviderService.java @@ -64,6 +64,7 @@ import java.util.List; * </intent-filter> * </service> * </pre> + * @hide */ public abstract class RouteProviderService extends Service { private static final String TAG = "RouteProvider"; diff --git a/media/java/android/media/routeprovider/RouteRequest.java b/media/java/android/media/routeprovider/RouteRequest.java index 68475c0..2ba75de 100644 --- a/media/java/android/media/routeprovider/RouteRequest.java +++ b/media/java/android/media/routeprovider/RouteRequest.java @@ -16,7 +16,7 @@ package android.media.routeprovider; import android.media.session.RouteOptions; -import android.media.session.SessionInfo; +import android.media.session.MediaSessionInfo; import android.os.Parcel; import android.os.Parcelable; @@ -30,16 +30,17 @@ import java.io.PrintWriter; * provides the full set of connection parameters they would like to use for a * connection. An app that can connect in multiple ways will be represented by * multiple requests. + * @hide */ public final class RouteRequest implements Parcelable { - private final SessionInfo mSessionInfo; + private final MediaSessionInfo mSessionInfo; private final RouteOptions mOptions; private final boolean mActive; /** * @hide */ - public RouteRequest(SessionInfo info, RouteOptions connRequest, + public RouteRequest(MediaSessionInfo info, RouteOptions connRequest, boolean active) { mSessionInfo = info; mOptions = connRequest; @@ -47,7 +48,7 @@ public final class RouteRequest implements Parcelable { } private RouteRequest(Parcel in) { - mSessionInfo = SessionInfo.CREATOR.createFromParcel(in); + mSessionInfo = MediaSessionInfo.CREATOR.createFromParcel(in); mOptions = RouteOptions.CREATOR.createFromParcel(in); mActive = in.readInt() != 0; } @@ -57,7 +58,7 @@ public final class RouteRequest implements Parcelable { * * @return Info on the session making the request */ - public SessionInfo getSessionInfo() { + public MediaSessionInfo getSessionInfo() { return mSessionInfo; } diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl index 096550f..c4233c3 100644 --- a/media/java/android/media/session/ISession.aidl +++ b/media/java/android/media/session/ISession.aidl @@ -15,8 +15,8 @@ package android.media.session; +import android.media.MediaMetadata; import android.media.session.ISessionController; -import android.media.session.MediaMetadata; import android.media.session.RouteOptions; import android.media.session.RouteCommand; import android.media.session.RouteInfo; diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl index 1552513..7b0412e 100644 --- a/media/java/android/media/session/ISessionCallback.aidl +++ b/media/java/android/media/session/ISessionCallback.aidl @@ -28,7 +28,7 @@ import android.os.ResultReceiver; */ oneway interface ISessionCallback { void onCommand(String command, in Bundle extras, in ResultReceiver cb); - void onMediaButton(in Intent mediaButtonIntent); + void onMediaButton(in Intent mediaButtonIntent, in ResultReceiver cb); void onRequestRouteChange(in RouteInfo route); void onRouteConnected(in RouteInfo route, in RouteOptions options); void onRouteDisconnected(in RouteInfo route, int reason); diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl index e2e046f..5ddb6db 100644 --- a/media/java/android/media/session/ISessionController.aidl +++ b/media/java/android/media/session/ISessionController.aidl @@ -16,9 +16,9 @@ package android.media.session; import android.content.Intent; +import android.media.MediaMetadata; import android.media.Rating; import android.media.session.ISessionControllerCallback; -import android.media.session.MediaMetadata; import android.media.session.PlaybackState; import android.os.Bundle; import android.os.ResultReceiver; diff --git a/media/java/android/media/session/ISessionControllerCallback.aidl b/media/java/android/media/session/ISessionControllerCallback.aidl index bc1ae05..e823153 100644 --- a/media/java/android/media/session/ISessionControllerCallback.aidl +++ b/media/java/android/media/session/ISessionControllerCallback.aidl @@ -15,7 +15,7 @@ package android.media.session; -import android.media.session.MediaMetadata; +import android.media.MediaMetadata; import android.media.session.RouteInfo; import android.media.session.PlaybackState; import android.os.Bundle; diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl index e341647..38b9293 100644 --- a/media/java/android/media/session/ISessionManager.aidl +++ b/media/java/android/media/session/ISessionManager.aidl @@ -19,6 +19,7 @@ import android.content.ComponentName; import android.media.session.ISession; import android.media.session.ISessionCallback; import android.os.Bundle; +import android.view.KeyEvent; /** * Interface to the MediaSessionManagerService @@ -27,4 +28,5 @@ import android.os.Bundle; interface ISessionManager { ISession createSession(String packageName, in ISessionCallback cb, String tag, int userId); List<IBinder> getSessions(in ComponentName compName, int userId); + void dispatchMediaKeyEvent(in KeyEvent keyEvent, boolean needWakeLock); }
\ No newline at end of file diff --git a/media/java/android/media/session/SessionController.java b/media/java/android/media/session/MediaController.java index dc4f7d9..642ac2f 100644 --- a/media/java/android/media/session/SessionController.java +++ b/media/java/android/media/session/MediaController.java @@ -16,6 +16,7 @@ package android.media.session; +import android.media.MediaMetadata; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -34,13 +35,13 @@ import java.util.ArrayList; * other commands can be sent to the session. A callback may be registered to * receive updates from the session, such as metadata and play state changes. * <p> - * A MediaController can be created through {@link SessionManager} if you + * A MediaController can be created through {@link MediaSessionManager} if you * hold the "android.permission.MEDIA_CONTENT_CONTROL" permission or directly if - * you have a {@link SessionToken} from the session owner. + * you have a {@link MediaSessionToken} from the session owner. * <p> * MediaController objects are thread-safe. */ -public final class SessionController { +public final class MediaController { private static final String TAG = "SessionController"; private static final int MSG_EVENT = 1; @@ -58,15 +59,15 @@ public final class SessionController { private TransportController mTransportController; - private SessionController(ISessionController sessionBinder) { + private MediaController(ISessionController sessionBinder) { mSessionBinder = sessionBinder; } /** * @hide */ - public static SessionController fromBinder(ISessionController sessionBinder) { - SessionController controller = new SessionController(sessionBinder); + public static MediaController fromBinder(ISessionController sessionBinder) { + MediaController controller = new MediaController(sessionBinder); try { controller.mSessionBinder.registerCallbackListener(controller.mCbStub); if (controller.mSessionBinder.isTransportControlEnabled()) { @@ -87,7 +88,7 @@ public final class SessionController { * @param token The session token to use * @return A controller for the session or null */ - public static SessionController fromToken(SessionToken token) { + public static MediaController fromToken(MediaSessionToken token) { return fromBinder(token.getBinder()); } @@ -184,6 +185,8 @@ public final class SessionController { /** * Request that the route picker be shown for this session. This should * generally be called in response to a user action. + * + * @hide */ public void showRoutePicker() { try { @@ -285,22 +288,23 @@ public final class SessionController { /** * Override to handle route changes for this session. * - * @param route + * @param route The new route + * @hide */ public void onRouteChanged(RouteInfo route) { } } private final static class CallbackStub extends ISessionControllerCallback.Stub { - private final WeakReference<SessionController> mController; + private final WeakReference<MediaController> mController; - public CallbackStub(SessionController controller) { - mController = new WeakReference<SessionController>(controller); + public CallbackStub(MediaController controller) { + mController = new WeakReference<MediaController>(controller); } @Override public void onEvent(String event, Bundle extras) { - SessionController controller = mController.get(); + MediaController controller = mController.get(); if (controller != null) { controller.postEvent(event, extras); } @@ -308,7 +312,7 @@ public final class SessionController { @Override public void onRouteChanged(RouteInfo route) { - SessionController controller = mController.get(); + MediaController controller = mController.get(); if (controller != null) { controller.postRouteChanged(route); } @@ -316,7 +320,7 @@ public final class SessionController { @Override public void onPlaybackStateChanged(PlaybackState state) { - SessionController controller = mController.get(); + MediaController controller = mController.get(); if (controller != null) { TransportController tc = controller.getTransportController(); if (tc != null) { @@ -327,7 +331,7 @@ public final class SessionController { @Override public void onMetadataChanged(MediaMetadata metadata) { - SessionController controller = mController.get(); + MediaController controller = mController.get(); if (controller != null) { TransportController tc = controller.getTransportController(); if (tc != null) { @@ -339,9 +343,9 @@ public final class SessionController { } private final static class MessageHandler extends Handler { - private final SessionController.Callback mCallback; + private final MediaController.Callback mCallback; - public MessageHandler(Looper looper, SessionController.Callback cb) { + public MessageHandler(Looper looper, MediaController.Callback cb) { super(looper, null, true); mCallback = cb; } diff --git a/media/java/android/media/session/Session.java b/media/java/android/media/session/MediaSession.java index 2ffced6..5b9adaa 100644 --- a/media/java/android/media/session/Session.java +++ b/media/java/android/media/session/MediaSession.java @@ -36,21 +36,21 @@ import java.util.ArrayList; import java.util.List; /** - * Allows interaction with media controllers, media routes, volume keys, media - * buttons, and transport controls. + * Allows interaction with media controllers, volume keys, media buttons, and + * transport controls. * <p> * A MediaSession should be created when an app wants to publish media playback - * information or negotiate with a media route. In general an app only needs one - * session for all playback, though multiple sessions can be created for sending - * media to multiple routes or to provide finer grain controls of media. + * information or handle media keys. In general an app only needs one session + * for all playback, though multiple sessions can be created to provide finer + * grain controls of media. * <p> * A MediaSession is created by calling - * {@link SessionManager#createSession(String)}. Once a session is created apps - * that have the MEDIA_CONTENT_CONTROL permission can interact with the session - * through - * {@link SessionManager#getActiveSessions(android.content.ComponentName)}. The - * owner of the session may also use {@link #getSessionToken()} to allow apps - * without this permission to create a {@link SessionController} to interact + * {@link MediaSessionManager#createSession(String)}. Once a session is created + * apps that have the MEDIA_CONTENT_CONTROL permission can interact with the + * session through + * {@link MediaSessionManager#getActiveSessions(android.content.ComponentName)}. + * The owner of the session may also use {@link #getSessionToken()} to allow + * apps without this permission to create a {@link MediaController} to interact * with this session. * <p> * To receive commands, media keys, and other events a Callback must be set with @@ -61,7 +61,7 @@ import java.util.List; * <p> * MediaSession objects are thread safe */ -public final class Session { +public final class MediaSession { private static final String TAG = "Session"; /** @@ -89,31 +89,43 @@ public final class Session { /** * Indicates the session was disconnected because the user that the session * belonged to is stopping. + * @hide */ public static final int DISCONNECT_REASON_USER_STOPPING = 1; /** * Indicates the session was disconnected because the provider disconnected * the route. + * @hide */ public static final int DISCONNECT_REASON_PROVIDER_DISCONNECTED = 2; /** * Indicates the session was disconnected because the route has changed. + * @hide */ public static final int DISCONNECT_REASON_ROUTE_CHANGED = 3; /** * Indicates the session was disconnected because the session owner * requested it disconnect. + * @hide */ public static final int DISCONNECT_REASON_SESSION_DISCONNECTED = 4; /** * Indicates the session was disconnected because it was destroyed. + * @hide */ public static final int DISCONNECT_REASON_SESSION_DESTROYED = 5; + /** + * Status code indicating the call was handled. + * + * @hide + */ + public static final int RESULT_SUCCESS = 0; + private static final int MSG_MEDIA_BUTTON = 1; private static final int MSG_COMMAND = 2; private static final int MSG_ROUTE_CHANGE = 3; @@ -126,7 +138,7 @@ public final class Session { private final Object mLock = new Object(); - private final SessionToken mSessionToken; + private final MediaSessionToken mSessionToken; private final ISession mBinder; private final CallbackStub mCbStub; @@ -143,7 +155,7 @@ public final class Session { /** * @hide */ - public Session(ISession binder, CallbackStub cbStub) { + public MediaSession(ISession binder, CallbackStub cbStub) { mBinder = binder; mCbStub = cbStub; ISessionController controllerBinder = null; @@ -152,7 +164,7 @@ public final class Session { } catch (RemoteException e) { throw new RuntimeException("Dead object in MediaSessionController constructor: ", e); } - mSessionToken = new SessionToken(controllerBinder); + mSessionToken = new MediaSessionToken(controllerBinder); mPerformer = new TransportPerformer(mBinder); } @@ -167,7 +179,7 @@ public final class Session { /** * Add a callback to receive updates for the MediaSession. This includes - * events like route updates, media buttons, and focus changes. + * media button and volume events. * * @param callback The callback to receive updates on. * @param handler The handler that events should be posted on. @@ -288,13 +300,13 @@ public final class Session { /** * Retrieve a token object that can be used by apps to create a - * {@link SessionController} for interacting with this session. The owner of + * {@link MediaController} for interacting with this session. The owner of * the session is responsible for deciding how to distribute these tokens. * * @return A token that can be used to create a MediaController for this * session */ - public SessionToken getSessionToken() { + public MediaSessionToken getSessionToken() { return mSessionToken; } @@ -304,8 +316,8 @@ public final class Session { * Connection updates will be sent to the callback's * {@link Callback#onRouteConnected(Route)} and * {@link Callback#onRouteDisconnected(Route, int)} methods. If the - * connection fails {@link Callback#onRouteDisconnected(Route, int)} - * will be called. + * connection fails {@link Callback#onRouteDisconnected(Route, int)} will be + * called. * <p> * If you already have a connection to this route it will be disconnected * before the new connection is established. TODO add an easy way to compare @@ -313,6 +325,7 @@ public final class Session { * * @param route The route the app is trying to connect to. * @param request The connection request to use. + * @hide */ public void connect(RouteInfo route, RouteOptions request) { if (route == null) { @@ -331,6 +344,8 @@ public final class Session { /** * Disconnect from the current route. After calling you will be switched * back to the default route. + * + * @hide */ public void disconnect() { if (mRoute != null) { @@ -347,6 +362,7 @@ public final class Session { * will be used for picking valid routes. * * @param options The set of route options your app may use to connect. + * @hide */ public void setRouteOptions(List<RouteOptions> options) { try { @@ -491,6 +507,7 @@ public final class Session { * ongoing playback if necessary. * * @param route + * @hide */ public void onRequestRouteChange(RouteInfo route) { } @@ -500,6 +517,7 @@ public final class Session { * are now valid. * * @param route The route that was connected + * @hide */ public void onRouteConnected(Route route) { } @@ -519,6 +537,7 @@ public final class Session { * * @param route The route that disconnected * @param reason The reason for the disconnect + * @hide */ public void onRouteDisconnected(Route route, int reason) { } @@ -528,32 +547,36 @@ public final class Session { * @hide */ public static class CallbackStub extends ISessionCallback.Stub { - private WeakReference<Session> mMediaSession; + private WeakReference<MediaSession> mMediaSession; - public void setMediaSession(Session session) { - mMediaSession = new WeakReference<Session>(session); + public void setMediaSession(MediaSession session) { + mMediaSession = new WeakReference<MediaSession>(session); } @Override public void onCommand(String command, Bundle extras, ResultReceiver cb) throws RemoteException { - Session session = mMediaSession.get(); + MediaSession session = mMediaSession.get(); if (session != null) { session.postCommand(command, extras, cb); } } @Override - public void onMediaButton(Intent mediaButtonIntent) throws RemoteException { - Session session = mMediaSession.get(); + public void onMediaButton(Intent mediaButtonIntent, ResultReceiver cb) + throws RemoteException { + MediaSession session = mMediaSession.get(); if (session != null) { session.postMediaButton(mediaButtonIntent); } + if (cb != null) { + cb.send(RESULT_SUCCESS, null); + } } @Override public void onRequestRouteChange(RouteInfo route) throws RemoteException { - Session session = mMediaSession.get(); + MediaSession session = mMediaSession.get(); if (session != null) { session.postRequestRouteChange(route); } @@ -561,7 +584,7 @@ public final class Session { @Override public void onRouteConnected(RouteInfo route, RouteOptions options) { - Session session = mMediaSession.get(); + MediaSession session = mMediaSession.get(); if (session != null) { session.postRouteConnected(route, options); } @@ -569,7 +592,7 @@ public final class Session { @Override public void onRouteDisconnected(RouteInfo route, int reason) { - Session session = mMediaSession.get(); + MediaSession session = mMediaSession.get(); if (session != null) { session.postRouteDisconnected(route, reason); } @@ -577,7 +600,7 @@ public final class Session { @Override public void onPlay() throws RemoteException { - Session session = mMediaSession.get(); + MediaSession session = mMediaSession.get(); if (session != null) { TransportPerformer tp = session.getTransportPerformer(); if (tp != null) { @@ -588,7 +611,7 @@ public final class Session { @Override public void onPause() throws RemoteException { - Session session = mMediaSession.get(); + MediaSession session = mMediaSession.get(); if (session != null) { TransportPerformer tp = session.getTransportPerformer(); if (tp != null) { @@ -599,7 +622,7 @@ public final class Session { @Override public void onStop() throws RemoteException { - Session session = mMediaSession.get(); + MediaSession session = mMediaSession.get(); if (session != null) { TransportPerformer tp = session.getTransportPerformer(); if (tp != null) { @@ -610,7 +633,7 @@ public final class Session { @Override public void onNext() throws RemoteException { - Session session = mMediaSession.get(); + MediaSession session = mMediaSession.get(); if (session != null) { TransportPerformer tp = session.getTransportPerformer(); if (tp != null) { @@ -621,7 +644,7 @@ public final class Session { @Override public void onPrevious() throws RemoteException { - Session session = mMediaSession.get(); + MediaSession session = mMediaSession.get(); if (session != null) { TransportPerformer tp = session.getTransportPerformer(); if (tp != null) { @@ -632,7 +655,7 @@ public final class Session { @Override public void onFastForward() throws RemoteException { - Session session = mMediaSession.get(); + MediaSession session = mMediaSession.get(); if (session != null) { TransportPerformer tp = session.getTransportPerformer(); if (tp != null) { @@ -643,7 +666,7 @@ public final class Session { @Override public void onRewind() throws RemoteException { - Session session = mMediaSession.get(); + MediaSession session = mMediaSession.get(); if (session != null) { TransportPerformer tp = session.getTransportPerformer(); if (tp != null) { @@ -654,7 +677,7 @@ public final class Session { @Override public void onSeekTo(long pos) throws RemoteException { - Session session = mMediaSession.get(); + MediaSession session = mMediaSession.get(); if (session != null) { TransportPerformer tp = session.getTransportPerformer(); if (tp != null) { @@ -665,7 +688,7 @@ public final class Session { @Override public void onRate(Rating rating) throws RemoteException { - Session session = mMediaSession.get(); + MediaSession session = mMediaSession.get(); if (session != null) { TransportPerformer tp = session.getTransportPerformer(); if (tp != null) { @@ -676,7 +699,7 @@ public final class Session { @Override public void onRouteEvent(RouteEvent event) throws RemoteException { - Session session = mMediaSession.get(); + MediaSession session = mMediaSession.get(); if (session != null) { RouteInterface.EventListener iface = session.mInterfaceListeners.get(event.getIface()); @@ -697,9 +720,9 @@ public final class Session { } private class MessageHandler extends Handler { - private Session.Callback mCallback; + private MediaSession.Callback mCallback; - public MessageHandler(Looper looper, Session.Callback callback) { + public MessageHandler(Looper looper, MediaSession.Callback callback) { super(looper, null, true); mCallback = callback; } diff --git a/media/java/android/media/session/SessionInfo.java b/media/java/android/media/session/MediaSessionInfo.java index 2b65528..3d8d33f 100644 --- a/media/java/android/media/session/SessionInfo.java +++ b/media/java/android/media/session/MediaSessionInfo.java @@ -21,19 +21,19 @@ import android.os.Parcelable; /** * Information about a media session, including the owner's package name. */ -public final class SessionInfo implements Parcelable { +public final class MediaSessionInfo implements Parcelable { private final String mId; private final String mPackageName; /** * @hide */ - public SessionInfo(String id, String packageName) { + public MediaSessionInfo(String id, String packageName) { mId = id; mPackageName = packageName; } - private SessionInfo(Parcel in) { + private MediaSessionInfo(Parcel in) { mId = in.readString(); mPackageName = in.readString(); } @@ -72,16 +72,16 @@ public final class SessionInfo implements Parcelable { dest.writeString(mPackageName); } - public static final Parcelable.Creator<SessionInfo> CREATOR - = new Parcelable.Creator<SessionInfo>() { + public static final Parcelable.Creator<MediaSessionInfo> CREATOR + = new Parcelable.Creator<MediaSessionInfo>() { @Override - public SessionInfo createFromParcel(Parcel in) { - return new SessionInfo(in); + public MediaSessionInfo createFromParcel(Parcel in) { + return new MediaSessionInfo(in); } @Override - public SessionInfo[] newArray(int size) { - return new SessionInfo[size]; + public MediaSessionInfo[] newArray(int size) { + return new MediaSessionInfo[size]; } }; } diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java index c07229d..2e02a66 100644 --- a/media/java/android/media/session/MediaSessionLegacyHelper.java +++ b/media/java/android/media/session/MediaSessionLegacyHelper.java @@ -35,11 +35,12 @@ import android.view.KeyEvent; */ public class MediaSessionLegacyHelper { private static final String TAG = "MediaSessionHelper"; + private static final boolean DEBUG = true; private static final Object sLock = new Object(); private static MediaSessionLegacyHelper sInstance; - private SessionManager mSessionManager; + private MediaSessionManager mSessionManager; private Handler mHandler = new Handler(Looper.getMainLooper()); // The legacy APIs use PendingIntents to register/unregister media button // receivers and these are associated with RCC. @@ -47,11 +48,14 @@ public class MediaSessionLegacyHelper { = new ArrayMap<PendingIntent, SessionHolder>(); private MediaSessionLegacyHelper(Context context) { - mSessionManager = (SessionManager) context + mSessionManager = (MediaSessionManager) context .getSystemService(Context.MEDIA_SESSION_SERVICE); } public static MediaSessionLegacyHelper getHelper(Context context) { + if (DEBUG) { + Log.d(TAG, "Attempting to get helper with context " + context); + } synchronized (sLock) { if (sInstance == null) { sInstance = new MediaSessionLegacyHelper(context); @@ -60,17 +64,30 @@ public class MediaSessionLegacyHelper { return sInstance; } - public Session getSession(PendingIntent pi) { + public MediaSession getSession(PendingIntent pi) { SessionHolder holder = mSessions.get(pi); return holder == null ? null : holder.mSession; } - public void addRccListener(PendingIntent pi, TransportPerformer.Listener listener) { + public void sendMediaButtonEvent(KeyEvent keyEvent, boolean needWakeLock) { + mSessionManager.dispatchMediaKeyEvent(keyEvent, needWakeLock); + if (DEBUG) { + Log.d(TAG, "dispatched media key " + keyEvent); + } + } + public void addRccListener(PendingIntent pi, TransportPerformer.Listener listener) { + if (pi == null) { + Log.w(TAG, "Pending intent was null, can't add rcc listener."); + return; + } SessionHolder holder = getHolder(pi, true); TransportPerformer performer = holder.mSession.getTransportPerformer(); if (holder.mRccListener != null) { if (holder.mRccListener == listener) { + if (DEBUG) { + Log.d(TAG, "addRccListener listener already added."); + } // This is already the registered listener, ignore return; } @@ -79,50 +96,82 @@ public class MediaSessionLegacyHelper { } performer.addListener(listener, mHandler); holder.mRccListener = listener; - holder.mFlags |= Session.FLAG_HANDLES_TRANSPORT_CONTROLS; + holder.mFlags |= MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS; holder.mSession.setFlags(holder.mFlags); holder.update(); + if (DEBUG) { + Log.d(TAG, "Added rcc listener for " + pi + "."); + } } public void removeRccListener(PendingIntent pi) { + if (pi == null) { + return; + } SessionHolder holder = getHolder(pi, false); if (holder != null && holder.mRccListener != null) { holder.mSession.getTransportPerformer().removeListener(holder.mRccListener); holder.mRccListener = null; - holder.mFlags &= ~Session.FLAG_HANDLES_TRANSPORT_CONTROLS; + holder.mFlags &= ~MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS; holder.mSession.setFlags(holder.mFlags); holder.update(); + if (DEBUG) { + Log.d(TAG, "Removed rcc listener for " + pi + "."); + } } } public void addMediaButtonListener(PendingIntent pi, Context context) { + if (pi == null) { + Log.w(TAG, "Pending intent was null, can't addMediaButtonListener."); + return; + } SessionHolder holder = getHolder(pi, true); if (holder.mMediaButtonListener != null) { - // Already have this listener registered + // Already have this listener registered, but update it anyway as + // the extras may have changed. + if (DEBUG) { + Log.d(TAG, "addMediaButtonListener already added " + pi); + } return; } holder.mMediaButtonListener = new MediaButtonListener(pi, context); - holder.mFlags |= Session.FLAG_HANDLES_MEDIA_BUTTONS; + holder.mFlags |= MediaSession.FLAG_HANDLES_MEDIA_BUTTONS; holder.mSession.setFlags(holder.mFlags); holder.mSession.getTransportPerformer().addListener(holder.mMediaButtonListener, mHandler); + + holder.mMediaButtonReceiver = new MediaButtonReceiver(pi, context); + holder.mSession.addCallback(holder.mMediaButtonReceiver, mHandler); + if (DEBUG) { + Log.d(TAG, "addMediaButtonListener added " + pi); + } } public void removeMediaButtonListener(PendingIntent pi) { + if (pi == null) { + return; + } SessionHolder holder = getHolder(pi, false); if (holder != null && holder.mMediaButtonListener != null) { holder.mSession.getTransportPerformer().removeListener(holder.mMediaButtonListener); - holder.mFlags &= ~Session.FLAG_HANDLES_MEDIA_BUTTONS; + holder.mFlags &= ~MediaSession.FLAG_HANDLES_MEDIA_BUTTONS; holder.mSession.setFlags(holder.mFlags); holder.mMediaButtonListener = null; + + holder.mSession.removeCallback(holder.mMediaButtonReceiver); + holder.mMediaButtonReceiver = null; holder.update(); + if (DEBUG) { + Log.d(TAG, "removeMediaButtonListener removed " + pi); + } } } private SessionHolder getHolder(PendingIntent pi, boolean createIfMissing) { SessionHolder holder = mSessions.get(pi); if (holder == null && createIfMissing) { - Session session = mSessionManager.createSession(TAG); + MediaSession session = mSessionManager.createSession(TAG); session.setActive(true); holder = new SessionHolder(session, pi); mSessions.put(pi, holder); @@ -130,7 +179,32 @@ public class MediaSessionLegacyHelper { return holder; } - public static class MediaButtonListener extends TransportPerformer.Listener { + private static void sendKeyEvent(PendingIntent pi, Context context, Intent intent) { + try { + pi.send(context, 0, intent); + } catch (CanceledException e) { + Log.e(TAG, "Error sending media key down event:", e); + // Don't bother sending up if down failed + return; + } + } + + private static final class MediaButtonReceiver extends MediaSession.Callback { + private final PendingIntent mPendingIntent; + private final Context mContext; + + public MediaButtonReceiver(PendingIntent pi, Context context) { + mPendingIntent = pi; + mContext = context; + } + + @Override + public void onMediaButton(Intent mediaButtonIntent) { + MediaSessionLegacyHelper.sendKeyEvent(mPendingIntent, mContext, mediaButtonIntent); + } + } + + private static final class MediaButtonListener extends TransportPerformer.Listener { private final PendingIntent mPendingIntent; private final Context mContext; @@ -179,32 +253,27 @@ public class MediaSessionLegacyHelper { Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON); intent.putExtra(Intent.EXTRA_KEY_EVENT, ke); - try { - mPendingIntent.send(mContext, 0, intent); - } catch (CanceledException e) { - Log.e(TAG, "Error sending media key down event:", e); - // Don't bother sending up if down failed - return; - } + MediaSessionLegacyHelper.sendKeyEvent(mPendingIntent, mContext, intent); ke = new KeyEvent(KeyEvent.ACTION_UP, keyCode); intent.putExtra(Intent.EXTRA_KEY_EVENT, ke); - try { - mPendingIntent.send(mContext, 0, intent); - } catch (CanceledException e) { - Log.e(TAG, "Error sending media key up event:", e); + MediaSessionLegacyHelper.sendKeyEvent(mPendingIntent, mContext, intent); + + if (DEBUG) { + Log.d(TAG, "Sent " + keyCode + " to pending intent " + mPendingIntent); } } } private class SessionHolder { - public final Session mSession; + public final MediaSession mSession; public final PendingIntent mPi; public MediaButtonListener mMediaButtonListener; + public MediaButtonReceiver mMediaButtonReceiver; public TransportPerformer.Listener mRccListener; public int mFlags; - public SessionHolder(Session session, PendingIntent pi) { + public SessionHolder(MediaSession session, PendingIntent pi) { mSession = session; mPi = pi; } @@ -213,10 +282,6 @@ public class MediaSessionLegacyHelper { if (mMediaButtonListener == null && mRccListener == null) { mSession.release(); mSessions.remove(mPi); - } else if (mMediaButtonListener != null && mRccListener != null) { - // TODO set session to active - } else { - // TODO set session to inactive } } } diff --git a/media/java/android/media/session/SessionManager.java b/media/java/android/media/session/MediaSessionManager.java index 1eb3b7a..0589a7d 100644 --- a/media/java/android/media/session/SessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -25,6 +25,7 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.service.notification.NotificationListenerService; import android.util.Log; +import android.view.KeyEvent; import java.util.ArrayList; import java.util.List; @@ -38,10 +39,10 @@ import java.util.List; * get an instance of this class. * <p> * - * @see Session - * @see SessionController + * @see MediaSession + * @see MediaController */ -public final class SessionManager { +public final class MediaSessionManager { private static final String TAG = "SessionManager"; private final ISessionManager mService; @@ -51,7 +52,7 @@ public final class SessionManager { /** * @hide */ - public SessionManager(Context context) { + public MediaSessionManager(Context context) { // Consider rewriting like DisplayManagerGlobal // Decide if we need context mContext = context; @@ -63,9 +64,9 @@ public final class SessionManager { * Creates a new session. * * @param tag A short name for debugging purposes - * @return a {@link Session} for the new session + * @return a {@link MediaSession} for the new session */ - public Session createSession(String tag) { + public MediaSession createSession(String tag) { return createSessionAsUser(tag, UserHandle.myUserId()); } @@ -77,13 +78,13 @@ public final class SessionManager { * * @param tag A short name for debugging purposes * @param userId The user id to create the session as. - * @return a {@link Session} for the new session + * @return a {@link MediaSession} for the new session * @hide */ - public Session createSessionAsUser(String tag, int userId) { + public MediaSession createSessionAsUser(String tag, int userId) { try { - Session.CallbackStub cbStub = new Session.CallbackStub(); - Session session = new Session(mService + MediaSession.CallbackStub cbStub = new MediaSession.CallbackStub(); + MediaSession session = new MediaSession(mService .createSession(mContext.getPackageName(), cbStub, tag, userId), cbStub); cbStub.setMediaSession(session); @@ -106,7 +107,7 @@ public final class SessionManager { * May be null. * @return A list of controllers for ongoing sessions */ - public List<SessionController> getActiveSessions(ComponentName notificationListener) { + public List<MediaController> getActiveSessions(ComponentName notificationListener) { return getActiveSessionsForUser(notificationListener, UserHandle.myUserId()); } @@ -123,13 +124,13 @@ public final class SessionManager { * @return A list of controllers for ongoing sessions. * @hide */ - public List<SessionController> getActiveSessionsForUser(ComponentName notificationListener, + public List<MediaController> getActiveSessionsForUser(ComponentName notificationListener, int userId) { - ArrayList<SessionController> controllers = new ArrayList<SessionController>(); + ArrayList<MediaController> controllers = new ArrayList<MediaController>(); try { List<IBinder> binders = mService.getSessions(notificationListener, userId); for (int i = binders.size() - 1; i >= 0; i--) { - SessionController controller = SessionController.fromBinder(ISessionController.Stub + MediaController controller = MediaController.fromBinder(ISessionController.Stub .asInterface(binders.get(i))); controllers.add(controller); } @@ -138,4 +139,30 @@ public final class SessionManager { } return controllers; } + + /** + * Send a media key event. The receiver will be selected automatically. + * + * @param keyEvent The KeyEvent to send. + * @hide + */ + public void dispatchMediaKeyEvent(KeyEvent keyEvent) { + dispatchMediaKeyEvent(keyEvent, false); + } + + /** + * Send a media key event. The receiver will be selected automatically. + * + * @param keyEvent The KeyEvent to send + * @param needWakeLock true if a wake lock should be held while sending the + * key + * @hide + */ + public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) { + try { + mService.dispatchMediaKeyEvent(keyEvent, needWakeLock); + } catch (RemoteException e) { + Log.e(TAG, "Failed to send key event.", e); + } + } } diff --git a/media/java/android/media/session/MediaMetadata.aidl b/media/java/android/media/session/MediaSessionToken.aidl index 4431d9d..5812682 100644 --- a/media/java/android/media/session/MediaMetadata.aidl +++ b/media/java/android/media/session/MediaSessionToken.aidl @@ -15,4 +15,4 @@ package android.media.session; -parcelable MediaMetadata; +parcelable MediaSessionToken; diff --git a/media/java/android/media/session/SessionToken.java b/media/java/android/media/session/MediaSessionToken.java index 59486f6..f5569a4 100644 --- a/media/java/android/media/session/SessionToken.java +++ b/media/java/android/media/session/MediaSessionToken.java @@ -20,17 +20,17 @@ import android.media.session.ISessionController; import android.os.Parcel; import android.os.Parcelable; -public class SessionToken implements Parcelable { +public class MediaSessionToken implements Parcelable { private ISessionController mBinder; /** * @hide */ - SessionToken(ISessionController binder) { + MediaSessionToken(ISessionController binder) { mBinder = binder; } - private SessionToken(Parcel in) { + private MediaSessionToken(Parcel in) { mBinder = ISessionController.Stub.asInterface(in.readStrongBinder()); } @@ -51,16 +51,16 @@ public class SessionToken implements Parcelable { dest.writeStrongBinder(mBinder.asBinder()); } - public static final Parcelable.Creator<SessionToken> CREATOR - = new Parcelable.Creator<SessionToken>() { + public static final Parcelable.Creator<MediaSessionToken> CREATOR + = new Parcelable.Creator<MediaSessionToken>() { @Override - public SessionToken createFromParcel(Parcel in) { - return new SessionToken(in); + public MediaSessionToken createFromParcel(Parcel in) { + return new MediaSessionToken(in); } @Override - public SessionToken[] newArray(int size) { - return new SessionToken[size]; + public MediaSessionToken[] newArray(int size) { + return new MediaSessionToken[size]; } }; } diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java index 3254e5d..7ef38eaa 100644 --- a/media/java/android/media/session/PlaybackState.java +++ b/media/java/android/media/session/PlaybackState.java @@ -21,7 +21,7 @@ import android.os.Parcelable; import android.os.SystemClock; /** - * Playback state for a {@link Session}. This includes a state like + * Playback state for a {@link MediaSession}. This includes a state like * {@link PlaybackState#PLAYSTATE_PLAYING}, the current playback position, * and the current control capabilities. */ @@ -160,6 +160,7 @@ public final class PlaybackState implements Parcelable { * route. Depending on the implementation you may return to the previous * state when the connection finishes or enter {@link #PLAYSTATE_NONE}. If * the connection failed {@link #PLAYSTATE_ERROR} should be used. + * @hide */ public final static int PLAYSTATE_CONNECTING = 8; diff --git a/media/java/android/media/session/Route.java b/media/java/android/media/session/Route.java index c9530a6..935eb5b 100644 --- a/media/java/android/media/session/Route.java +++ b/media/java/android/media/session/Route.java @@ -28,17 +28,18 @@ import java.util.List; * to. The MediaRoute must be used to get {@link RouteInterface} * instances which can be used to communicate over a specific interface on the * route. + * @hide */ public final class Route { private static final String TAG = "Route"; private final RouteInfo mInfo; - private final Session mSession; + private final MediaSession mSession; private final RouteOptions mOptions; /** * @hide */ - public Route(RouteInfo info, RouteOptions options, Session session) { + public Route(RouteInfo info, RouteOptions options, MediaSession session) { if (info == null || options == null) { throw new IllegalStateException("Route info was not valid!"); } @@ -93,7 +94,7 @@ public final class Route { /** * @hide */ - Session getSession() { + MediaSession getSession() { return mSession; } } diff --git a/media/java/android/media/session/RouteInfo.java b/media/java/android/media/session/RouteInfo.java index 17df969..02f78f9 100644 --- a/media/java/android/media/session/RouteInfo.java +++ b/media/java/android/media/session/RouteInfo.java @@ -25,6 +25,7 @@ import java.util.List; /** * Information about a route, including its display name, a way to identify it, * and the ways it can be connected to. + * @hide */ public final class RouteInfo implements Parcelable { private final String mName; diff --git a/media/java/android/media/session/RouteInterface.java b/media/java/android/media/session/RouteInterface.java index e9c9fd3..8de4d89 100644 --- a/media/java/android/media/session/RouteInterface.java +++ b/media/java/android/media/session/RouteInterface.java @@ -25,7 +25,7 @@ import android.util.Log; import java.util.ArrayList; /** - * A route can support multiple interfaces for a {@link Session} to + * A route can support multiple interfaces for a {@link MediaSession} to * interact with. To use a specific interface with a route a * MediaSessionRouteInterface needs to be retrieved from the route. An * implementation of the specific interface, like @@ -33,6 +33,7 @@ import java.util.ArrayList; * and reduce errors on that interface. * * @see RoutePlaybackControls for an example + * @hide */ public final class RouteInterface { private static final String TAG = "RouteInterface"; @@ -67,7 +68,7 @@ public final class RouteInterface { private final Route mRoute; private final String mIface; - private final Session mSession; + private final MediaSession mSession; private final Object mLock = new Object(); private final ArrayList<EventHandler> mListeners = new ArrayList<EventHandler>(); @@ -75,7 +76,7 @@ public final class RouteInterface { /** * @hide */ - RouteInterface(Route route, String iface, Session session) { + RouteInterface(Route route, String iface, MediaSession session) { mRoute = route; mIface = iface; mSession = session; diff --git a/media/java/android/media/session/RouteOptions.java b/media/java/android/media/session/RouteOptions.java index 5105867..b4fb341 100644 --- a/media/java/android/media/session/RouteOptions.java +++ b/media/java/android/media/session/RouteOptions.java @@ -34,6 +34,7 @@ import java.util.List; * appropriate route options when it is ready to connect to the route. Each * route options instance must specify a complete set of capabilities to request * when the connection is established. + * @hide */ public final class RouteOptions implements Parcelable { private static final String TAG = "RouteOptions"; diff --git a/media/java/android/media/session/RoutePlaybackControls.java b/media/java/android/media/session/RoutePlaybackControls.java index a3ffb58..8211983 100644 --- a/media/java/android/media/session/RoutePlaybackControls.java +++ b/media/java/android/media/session/RoutePlaybackControls.java @@ -15,6 +15,7 @@ */ package android.media.session; +import android.media.MediaMetadata; import android.os.Bundle; import android.os.Handler; import android.os.ResultReceiver; @@ -23,6 +24,7 @@ import android.os.ResultReceiver; * A standard media control interface for Routes that support queueing and * transport controls. Routes may support multiple interfaces for MediaSessions * to interact with. + * @hide */ public final class RoutePlaybackControls { private static final String TAG = "RoutePlaybackControls"; diff --git a/media/java/android/media/session/TransportController.java b/media/java/android/media/session/TransportController.java index 9574df6..090489b 100644 --- a/media/java/android/media/session/TransportController.java +++ b/media/java/android/media/session/TransportController.java @@ -15,6 +15,7 @@ */ package android.media.session; +import android.media.MediaMetadata; import android.media.Rating; import android.os.Handler; import android.os.Looper; diff --git a/media/java/android/media/session/TransportPerformer.java b/media/java/android/media/session/TransportPerformer.java index 187f48d..1588d8f 100644 --- a/media/java/android/media/session/TransportPerformer.java +++ b/media/java/android/media/session/TransportPerformer.java @@ -16,6 +16,7 @@ package android.media.session; import android.media.AudioManager; +import android.media.MediaMetadata; import android.media.Rating; import android.os.Handler; import android.os.Looper; diff --git a/media/jni/Android.mk b/media/jni/Android.mk index 90fe695..d658654 100644 --- a/media/jni/Android.mk +++ b/media/jni/Android.mk @@ -2,6 +2,7 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ + android_media_DngCreator.cpp \ android_media_ImageReader.cpp \ android_media_MediaCrypto.cpp \ android_media_MediaCodec.cpp \ @@ -41,6 +42,7 @@ LOCAL_SHARED_LIBRARIES := \ libjhead \ libexif \ libstagefright_amrnb_common \ + libimg_utils \ LOCAL_REQUIRED_MODULES := \ libjhead_jni @@ -53,6 +55,7 @@ LOCAL_C_INCLUDES += \ external/tremor/Tremor \ frameworks/base/core/jni \ frameworks/av/media/libmedia \ + frameworks/av/media/img_utils/include \ frameworks/av/media/libstagefright \ frameworks/av/media/libstagefright/codecs/amrnb/enc/src \ frameworks/av/media/libstagefright/codecs/amrnb/common \ diff --git a/media/jni/android_media_DngCreator.cpp b/media/jni/android_media_DngCreator.cpp new file mode 100644 index 0000000..860d896 --- /dev/null +++ b/media/jni/android_media_DngCreator.cpp @@ -0,0 +1,772 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "DngCreator_JNI" + +#include <system/camera_metadata.h> +#include <camera/CameraMetadata.h> +#include <img_utils/DngUtils.h> +#include <img_utils/TagDefinitions.h> +#include <img_utils/TiffIfd.h> +#include <img_utils/TiffWriter.h> +#include <img_utils/Output.h> + +#include <utils/Log.h> +#include <utils/Errors.h> +#include <utils/StrongPointer.h> +#include <utils/RefBase.h> +#include <cutils/properties.h> + +#include "android_runtime/AndroidRuntime.h" +#include "android_runtime/android_hardware_camera2_CameraMetadata.h" + +#include <jni.h> +#include <JNIHelp.h> + +using namespace android; +using namespace img_utils; + +#define BAIL_IF_INVALID(expr, jnienv, tagId) \ + if ((expr) != OK) { \ + jniThrowExceptionFmt(jnienv, "java/lang/IllegalArgumentException", \ + "Invalid metadata for tag %x", tagId); \ + return; \ + } + +#define BAIL_IF_EMPTY(entry, jnienv, tagId) \ + if (entry.count == 0) { \ + jniThrowExceptionFmt(jnienv, "java/lang/IllegalArgumentException", \ + "Missing metadata fields for tag %x", tagId); \ + return; \ + } + +#define ANDROID_MEDIA_DNGCREATOR_CTX_JNI_ID "mNativeContext" + +static struct { + jfieldID mNativeContext; +} gDngCreatorClassInfo; + +static struct { + jmethodID mWriteMethod; +} gOutputStreamClassInfo; + +enum { + BITS_PER_SAMPLE = 16, + BYTES_PER_SAMPLE = 2, + TIFF_IFD_0 = 0 +}; + +// ---------------------------------------------------------------------------- + +// This class is not intended to be used across JNI calls. +class JniOutputStream : public Output, public LightRefBase<JniOutputStream> { +public: + JniOutputStream(JNIEnv* env, jobject outStream); + + virtual ~JniOutputStream(); + + status_t open(); + status_t write(const uint8_t* buf, size_t offset, size_t count); + status_t close(); +private: + enum { + BYTE_ARRAY_LENGTH = 1024 + }; + jobject mOutputStream; + JNIEnv* mEnv; + jbyteArray mByteArray; +}; + +JniOutputStream::JniOutputStream(JNIEnv* env, jobject outStream) : mOutputStream(outStream), + mEnv(env) { + mByteArray = env->NewByteArray(BYTE_ARRAY_LENGTH); + if (mByteArray == NULL) { + jniThrowException(env, "java/lang/OutOfMemoryError", "Could not allocate byte array."); + } +} + +JniOutputStream::~JniOutputStream() { + mEnv->DeleteLocalRef(mByteArray); +} + +status_t JniOutputStream::open() { + // Do nothing + return OK; +} + +status_t JniOutputStream::write(const uint8_t* buf, size_t offset, size_t count) { + while(count > 0) { + size_t len = BYTE_ARRAY_LENGTH; + len = (count > len) ? len : count; + mEnv->SetByteArrayRegion(mByteArray, 0, len, reinterpret_cast<const jbyte*>(buf + offset)); + + if (mEnv->ExceptionCheck()) { + return BAD_VALUE; + } + + mEnv->CallVoidMethod(mOutputStream, gOutputStreamClassInfo.mWriteMethod, mByteArray, + 0, len); + + if (mEnv->ExceptionCheck()) { + return BAD_VALUE; + } + + count -= len; + offset += len; + } + return OK; +} + +status_t JniOutputStream::close() { + // Do nothing + return OK; +} + +// ---------------------------------------------------------------------------- + +extern "C" { + +static TiffWriter* DngCreator_getCreator(JNIEnv* env, jobject thiz) { + ALOGV("%s:", __FUNCTION__); + return reinterpret_cast<TiffWriter*>(env->GetLongField(thiz, + gDngCreatorClassInfo.mNativeContext)); +} + +static void DngCreator_setCreator(JNIEnv* env, jobject thiz, sp<TiffWriter> writer) { + ALOGV("%s:", __FUNCTION__); + TiffWriter* current = DngCreator_getCreator(env, thiz); + if (writer != NULL) { + writer->incStrong((void*) DngCreator_setCreator); + } + if (current) { + current->decStrong((void*) DngCreator_setCreator); + } + env->SetLongField(thiz, gDngCreatorClassInfo.mNativeContext, + reinterpret_cast<jlong>(writer.get())); +} + +static void DngCreator_nativeClassInit(JNIEnv* env, jclass clazz) { + ALOGV("%s:", __FUNCTION__); + + gDngCreatorClassInfo.mNativeContext = env->GetFieldID(clazz, + ANDROID_MEDIA_DNGCREATOR_CTX_JNI_ID, "J"); + LOG_ALWAYS_FATAL_IF(gDngCreatorClassInfo.mNativeContext == NULL, + "can't find android/media/DngCreator.%s", ANDROID_MEDIA_DNGCREATOR_CTX_JNI_ID); + + jclass outputStreamClazz = env->FindClass("java/io/OutputStream"); + LOG_ALWAYS_FATAL_IF(outputStreamClazz == NULL, "Can't find java/io/OutputStream class"); + gOutputStreamClassInfo.mWriteMethod = env->GetMethodID(outputStreamClazz, "write", "([BII)V"); + LOG_ALWAYS_FATAL_IF(gOutputStreamClassInfo.mWriteMethod == NULL, "Can't find write method"); +} + +static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPtr, + jobject resultsPtr) { + ALOGV("%s:", __FUNCTION__); + CameraMetadata characteristics; + CameraMetadata results; + if (CameraMetadata_getNativeMetadata(env, characteristicsPtr, &characteristics) != OK) { + jniThrowException(env, "java/lang/AssertionError", + "No native metadata defined for camera characteristics."); + return; + } + if (CameraMetadata_getNativeMetadata(env, resultsPtr, &results) != OK) { + jniThrowException(env, "java/lang/AssertionError", + "No native metadata defined for capture results."); + return; + } + + sp<TiffWriter> writer = new TiffWriter(); + + writer->addIfd(TIFF_IFD_0); + + status_t err = OK; + + const uint32_t samplesPerPixel = 1; + const uint32_t bitsPerSample = BITS_PER_SAMPLE; + const uint32_t bitsPerByte = BITS_PER_SAMPLE / BYTES_PER_SAMPLE; + uint32_t imageWidth = 0; + uint32_t imageHeight = 0; + + OpcodeListBuilder::CfaLayout opcodeCfaLayout = OpcodeListBuilder::CFA_RGGB; + + // TODO: Greensplit. + // TODO: UniqueCameraModel + // TODO: Add remaining non-essential tags + { + // Set orientation + uint16_t orientation = 1; // Normal + BAIL_IF_INVALID(writer->addEntry(TAG_ORIENTATION, 1, &orientation, TIFF_IFD_0), env, + TAG_ORIENTATION); + } + + { + // Set subfiletype + uint32_t subfileType = 0; // Main image + BAIL_IF_INVALID(writer->addEntry(TAG_NEWSUBFILETYPE, 1, &subfileType, TIFF_IFD_0), env, + TAG_NEWSUBFILETYPE); + } + + { + // Set bits per sample + uint16_t bits = static_cast<uint16_t>(bitsPerSample); + BAIL_IF_INVALID(writer->addEntry(TAG_BITSPERSAMPLE, 1, &bits, TIFF_IFD_0), env, + TAG_BITSPERSAMPLE); + } + + { + // Set compression + uint16_t compression = 1; // None + BAIL_IF_INVALID(writer->addEntry(TAG_COMPRESSION, 1, &compression, TIFF_IFD_0), env, + TAG_COMPRESSION); + } + + { + // Set dimensions + camera_metadata_entry entry = + characteristics.find(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE); + BAIL_IF_EMPTY(entry, env, TAG_IMAGEWIDTH); + uint32_t width = static_cast<uint32_t>(entry.data.i32[2]); + uint32_t height = static_cast<uint32_t>(entry.data.i32[3]); + BAIL_IF_INVALID(writer->addEntry(TAG_IMAGEWIDTH, 1, &width, TIFF_IFD_0), env, + TAG_IMAGEWIDTH); + BAIL_IF_INVALID(writer->addEntry(TAG_IMAGELENGTH, 1, &height, TIFF_IFD_0), env, + TAG_IMAGELENGTH); + imageWidth = width; + imageHeight = height; + } + + { + // Set photometric interpretation + uint16_t interpretation = 32803; + BAIL_IF_INVALID(writer->addEntry(TAG_PHOTOMETRICINTERPRETATION, 1, &interpretation, + TIFF_IFD_0), env, TAG_PHOTOMETRICINTERPRETATION); + } + + { + // Set blacklevel tags + camera_metadata_entry entry = + characteristics.find(ANDROID_SENSOR_BLACK_LEVEL_PATTERN); + BAIL_IF_EMPTY(entry, env, TAG_BLACKLEVEL); + const uint32_t* blackLevel = reinterpret_cast<const uint32_t*>(entry.data.i32); + BAIL_IF_INVALID(writer->addEntry(TAG_BLACKLEVEL, entry.count, blackLevel, TIFF_IFD_0), env, + TAG_BLACKLEVEL); + + uint16_t repeatDim[2] = {2, 2}; + BAIL_IF_INVALID(writer->addEntry(TAG_BLACKLEVELREPEATDIM, 2, repeatDim, TIFF_IFD_0), env, + TAG_BLACKLEVELREPEATDIM); + } + + { + // Set samples per pixel + uint16_t samples = static_cast<uint16_t>(samplesPerPixel); + BAIL_IF_INVALID(writer->addEntry(TAG_SAMPLESPERPIXEL, 1, &samples, TIFF_IFD_0), + env, TAG_SAMPLESPERPIXEL); + } + + { + // Set planar configuration + uint16_t config = 1; // Chunky + BAIL_IF_INVALID(writer->addEntry(TAG_PLANARCONFIGURATION, 1, &config, TIFF_IFD_0), + env, TAG_PLANARCONFIGURATION); + } + + { + // Set CFA pattern dimensions + uint16_t repeatDim[2] = {2, 2}; + BAIL_IF_INVALID(writer->addEntry(TAG_CFAREPEATPATTERNDIM, 2, repeatDim, TIFF_IFD_0), + env, TAG_CFAREPEATPATTERNDIM); + } + + { + // Set CFA pattern + camera_metadata_entry entry = + characteristics.find(ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT); + BAIL_IF_EMPTY(entry, env, TAG_CFAPATTERN); + camera_metadata_enum_android_sensor_info_color_filter_arrangement_t cfa = + static_cast<camera_metadata_enum_android_sensor_info_color_filter_arrangement_t>( + entry.data.u8[0]); + switch(cfa) { + case ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGGB: { + uint8_t cfa[4] = {0, 1, 1, 2}; + BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, 4, cfa, TIFF_IFD_0), + env, TAG_CFAPATTERN); + opcodeCfaLayout = OpcodeListBuilder::CFA_RGGB; + break; + } + case ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GRBG: { + uint8_t cfa[4] = {1, 0, 2, 1}; + BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, 4, cfa, TIFF_IFD_0), + env, TAG_CFAPATTERN); + opcodeCfaLayout = OpcodeListBuilder::CFA_GRBG; + break; + } + case ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GBRG: { + uint8_t cfa[4] = {1, 2, 0, 1}; + BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, 4, cfa, TIFF_IFD_0), + env, TAG_CFAPATTERN); + opcodeCfaLayout = OpcodeListBuilder::CFA_GBRG; + break; + } + case ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_BGGR: { + uint8_t cfa[4] = {2, 1, 1, 0}; + BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, 4, cfa, TIFF_IFD_0), + env, TAG_CFAPATTERN); + opcodeCfaLayout = OpcodeListBuilder::CFA_BGGR; + break; + } + default: { + jniThrowExceptionFmt(env, "java/lang/IllegalStateException", + "Invalid metadata for tag %d", TAG_CFAPATTERN); + return; + } + } + } + + { + // Set CFA plane color + uint8_t cfaPlaneColor[3] = {0, 1, 2}; + BAIL_IF_INVALID(writer->addEntry(TAG_CFAPLANECOLOR, 3, cfaPlaneColor, TIFF_IFD_0), + env, TAG_CFAPLANECOLOR); + } + + { + // Set CFA layout + uint16_t cfaLayout = 1; + BAIL_IF_INVALID(writer->addEntry(TAG_CFALAYOUT, 1, &cfaLayout, TIFF_IFD_0), + env, TAG_CFALAYOUT); + } + + { + // Set DNG version information + uint8_t version[4] = {1, 4, 0, 0}; + BAIL_IF_INVALID(writer->addEntry(TAG_DNGVERSION, 4, version, TIFF_IFD_0), + env, TAG_DNGVERSION); + + uint8_t backwardVersion[4] = {1, 1, 0, 0}; + BAIL_IF_INVALID(writer->addEntry(TAG_DNGBACKWARDVERSION, 4, backwardVersion, TIFF_IFD_0), + env, TAG_DNGBACKWARDVERSION); + } + + { + // Set whitelevel + camera_metadata_entry entry = + characteristics.find(ANDROID_SENSOR_INFO_WHITE_LEVEL); + BAIL_IF_EMPTY(entry, env, TAG_WHITELEVEL); + uint32_t whiteLevel = static_cast<uint32_t>(entry.data.i32[0]); + BAIL_IF_INVALID(writer->addEntry(TAG_WHITELEVEL, 1, &whiteLevel, TIFF_IFD_0), env, + TAG_WHITELEVEL); + } + + { + // Set default scale + uint32_t defaultScale[4] = {1, 1, 1, 1}; + BAIL_IF_INVALID(writer->addEntry(TAG_DEFAULTSCALE, 2, defaultScale, TIFF_IFD_0), + env, TAG_DEFAULTSCALE); + } + + bool singleIlluminant = false; + { + // Set calibration illuminants + camera_metadata_entry entry1 = + characteristics.find(ANDROID_SENSOR_REFERENCE_ILLUMINANT1); + BAIL_IF_EMPTY(entry1, env, TAG_CALIBRATIONILLUMINANT1); + camera_metadata_entry entry2 = + characteristics.find(ANDROID_SENSOR_REFERENCE_ILLUMINANT2); + if (entry2.count == 0) { + singleIlluminant = true; + } + uint16_t ref1 = entry1.data.u8[0]; + + BAIL_IF_INVALID(writer->addEntry(TAG_CALIBRATIONILLUMINANT1, 1, &ref1, + TIFF_IFD_0), env, TAG_CALIBRATIONILLUMINANT1); + + if (!singleIlluminant) { + uint16_t ref2 = entry2.data.u8[0]; + BAIL_IF_INVALID(writer->addEntry(TAG_CALIBRATIONILLUMINANT2, 1, &ref2, + TIFF_IFD_0), env, TAG_CALIBRATIONILLUMINANT2); + } + } + + { + // Set color transforms + camera_metadata_entry entry1 = + characteristics.find(ANDROID_SENSOR_COLOR_TRANSFORM1); + BAIL_IF_EMPTY(entry1, env, TAG_COLORMATRIX1); + + int32_t colorTransform1[entry1.count * 2]; + + size_t ctr = 0; + for(size_t i = 0; i < entry1.count; ++i) { + colorTransform1[ctr++] = entry1.data.r[i].numerator; + colorTransform1[ctr++] = entry1.data.r[i].denominator; + } + + BAIL_IF_INVALID(writer->addEntry(TAG_COLORMATRIX1, entry1.count, colorTransform1, TIFF_IFD_0), + env, TAG_COLORMATRIX1); + + if (!singleIlluminant) { + camera_metadata_entry entry2 = characteristics.find(ANDROID_SENSOR_COLOR_TRANSFORM2); + BAIL_IF_EMPTY(entry2, env, TAG_COLORMATRIX2); + int32_t colorTransform2[entry2.count * 2]; + + ctr = 0; + for(size_t i = 0; i < entry2.count; ++i) { + colorTransform2[ctr++] = entry2.data.r[i].numerator; + colorTransform2[ctr++] = entry2.data.r[i].denominator; + } + + BAIL_IF_INVALID(writer->addEntry(TAG_COLORMATRIX2, entry2.count, colorTransform2, TIFF_IFD_0), + env, TAG_COLORMATRIX2); + } + } + + { + // Set calibration transforms + camera_metadata_entry entry1 = + characteristics.find(ANDROID_SENSOR_CALIBRATION_TRANSFORM1); + BAIL_IF_EMPTY(entry1, env, TAG_CAMERACALIBRATION1); + + int32_t calibrationTransform1[entry1.count * 2]; + + size_t ctr = 0; + for(size_t i = 0; i < entry1.count; ++i) { + calibrationTransform1[ctr++] = entry1.data.r[i].numerator; + calibrationTransform1[ctr++] = entry1.data.r[i].denominator; + } + + BAIL_IF_INVALID(writer->addEntry(TAG_CAMERACALIBRATION1, entry1.count, calibrationTransform1, + TIFF_IFD_0), env, TAG_CAMERACALIBRATION1); + + if (!singleIlluminant) { + camera_metadata_entry entry2 = + characteristics.find(ANDROID_SENSOR_CALIBRATION_TRANSFORM2); + BAIL_IF_EMPTY(entry2, env, TAG_CAMERACALIBRATION2); + int32_t calibrationTransform2[entry2.count * 2]; + + ctr = 0; + for(size_t i = 0; i < entry2.count; ++i) { + calibrationTransform2[ctr++] = entry2.data.r[i].numerator; + calibrationTransform2[ctr++] = entry2.data.r[i].denominator; + } + + BAIL_IF_INVALID(writer->addEntry(TAG_CAMERACALIBRATION2, entry2.count, calibrationTransform1, + TIFF_IFD_0), env, TAG_CAMERACALIBRATION2); + } + } + + { + // Set forward transforms + camera_metadata_entry entry1 = + characteristics.find(ANDROID_SENSOR_FORWARD_MATRIX1); + BAIL_IF_EMPTY(entry1, env, TAG_FORWARDMATRIX1); + + int32_t forwardTransform1[entry1.count * 2]; + + size_t ctr = 0; + for(size_t i = 0; i < entry1.count; ++i) { + forwardTransform1[ctr++] = entry1.data.r[i].numerator; + forwardTransform1[ctr++] = entry1.data.r[i].denominator; + } + + BAIL_IF_INVALID(writer->addEntry(TAG_FORWARDMATRIX1, entry1.count, forwardTransform1, + TIFF_IFD_0), env, TAG_FORWARDMATRIX1); + + if (!singleIlluminant) { + camera_metadata_entry entry2 = + characteristics.find(ANDROID_SENSOR_FORWARD_MATRIX2); + BAIL_IF_EMPTY(entry2, env, TAG_FORWARDMATRIX2); + int32_t forwardTransform2[entry2.count * 2]; + + ctr = 0; + for(size_t i = 0; i < entry2.count; ++i) { + forwardTransform2[ctr++] = entry2.data.r[i].numerator; + forwardTransform2[ctr++] = entry2.data.r[i].denominator; + } + + BAIL_IF_INVALID(writer->addEntry(TAG_FORWARDMATRIX2, entry2.count, forwardTransform2, + TIFF_IFD_0), env, TAG_FORWARDMATRIX2); + } + } + + { + // Set camera neutral + camera_metadata_entry entry = + results.find(ANDROID_SENSOR_NEUTRAL_COLOR_POINT); + BAIL_IF_EMPTY(entry, env, TAG_ASSHOTNEUTRAL); + uint32_t cameraNeutral[entry.count * 2]; + + size_t ctr = 0; + for(size_t i = 0; i < entry.count; ++i) { + cameraNeutral[ctr++] = + static_cast<uint32_t>(entry.data.r[i].numerator); + cameraNeutral[ctr++] = + static_cast<uint32_t>(entry.data.r[i].denominator); + } + + BAIL_IF_INVALID(writer->addEntry(TAG_ASSHOTNEUTRAL, entry.count, cameraNeutral, + TIFF_IFD_0), env, TAG_ASSHOTNEUTRAL); + } + + { + // Setup data strips + // TODO: Switch to tiled implementation. + uint32_t offset = 0; + BAIL_IF_INVALID(writer->addEntry(TAG_STRIPOFFSETS, 1, &offset, TIFF_IFD_0), env, + TAG_STRIPOFFSETS); + + BAIL_IF_INVALID(writer->addEntry(TAG_ROWSPERSTRIP, 1, &imageHeight, TIFF_IFD_0), env, + TAG_ROWSPERSTRIP); + + uint32_t byteCount = imageWidth * imageHeight * bitsPerSample * samplesPerPixel / + bitsPerByte; + BAIL_IF_INVALID(writer->addEntry(TAG_STRIPBYTECOUNTS, 1, &byteCount, TIFF_IFD_0), env, + TAG_STRIPBYTECOUNTS); + } + + { + // Setup default crop + crop origin tags + uint32_t margin = 8; // Default margin recommended by Adobe for interpolation. + uint32_t dimensionLimit = 128; // Smallest image dimension crop margin from. + if (imageWidth >= dimensionLimit && imageHeight >= dimensionLimit) { + uint32_t defaultCropOrigin[] = {margin, margin}; + uint32_t defaultCropSize[] = {imageWidth - margin, imageHeight - margin}; + BAIL_IF_INVALID(writer->addEntry(TAG_DEFAULTCROPORIGIN, 2, defaultCropOrigin, + TIFF_IFD_0), env, TAG_DEFAULTCROPORIGIN); + BAIL_IF_INVALID(writer->addEntry(TAG_DEFAULTCROPSIZE, 2, defaultCropSize, + TIFF_IFD_0), env, TAG_DEFAULTCROPSIZE); + } + } + + { + // Setup unique camera model tag + char model[PROPERTY_VALUE_MAX]; + property_get("ro.product.model", model, ""); + + char manufacturer[PROPERTY_VALUE_MAX]; + property_get("ro.product.manufacturer", manufacturer, ""); + + char brand[PROPERTY_VALUE_MAX]; + property_get("ro.product.brand", brand, ""); + + String8 cameraModel(model); + cameraModel += "-"; + cameraModel += manufacturer; + cameraModel += "-"; + cameraModel += brand; + + BAIL_IF_INVALID(writer->addEntry(TAG_UNIQUECAMERAMODEL, cameraModel.size() + 1, + reinterpret_cast<const uint8_t*>(cameraModel.string()), TIFF_IFD_0), env, + TAG_UNIQUECAMERAMODEL); + } + + { + // Setup opcode List 2 + camera_metadata_entry entry1 = + characteristics.find(ANDROID_LENS_INFO_SHADING_MAP_SIZE); + BAIL_IF_EMPTY(entry1, env, TAG_OPCODELIST2); + uint32_t lsmWidth = static_cast<uint32_t>(entry1.data.i32[0]); + uint32_t lsmHeight = static_cast<uint32_t>(entry1.data.i32[1]); + + camera_metadata_entry entry2 = + results.find(ANDROID_STATISTICS_LENS_SHADING_MAP); + BAIL_IF_EMPTY(entry2, env, TAG_OPCODELIST2); + if (entry2.count == lsmWidth * lsmHeight * 4) { + + OpcodeListBuilder builder; + status_t err = builder.addGainMapsForMetadata(lsmWidth, + lsmHeight, + 0, + 0, + imageHeight, + imageWidth, + opcodeCfaLayout, + entry2.data.f); + if (err == OK) { + size_t listSize = builder.getSize(); + uint8_t opcodeListBuf[listSize]; + err = builder.buildOpList(opcodeListBuf); + if (err == OK) { + BAIL_IF_INVALID(writer->addEntry(TAG_OPCODELIST2, listSize, opcodeListBuf, + TIFF_IFD_0), env, TAG_OPCODELIST2); + } else { + ALOGE("%s: Could not build Lens shading map opcode.", __FUNCTION__); + jniThrowRuntimeException(env, "failed to construct lens shading map opcode."); + } + } else { + ALOGE("%s: Could not add Lens shading map.", __FUNCTION__); + jniThrowRuntimeException(env, "failed to add lens shading map."); + } + } else { + ALOGW("%s: Lens shading map not present in results, skipping...", __FUNCTION__); + } + } + + DngCreator_setCreator(env, thiz, writer); +} + +static void DngCreator_destroy(JNIEnv* env, jobject thiz) { + ALOGV("%s:", __FUNCTION__); + DngCreator_setCreator(env, thiz, NULL); +} + +static void DngCreator_nativeSetOrientation(JNIEnv* env, jobject thiz) { + ALOGV("%s:", __FUNCTION__); + jniThrowRuntimeException(env, "nativeSetOrientation is not implemented"); +} + +static void DngCreator_nativeSetThumbnailBitmap(JNIEnv* env, jobject thiz, jobject bitmap) { + ALOGV("%s:", __FUNCTION__); + jniThrowRuntimeException(env, "nativeSetThumbnailBitmap is not implemented"); +} + +static void DngCreator_nativeSetThumbnailImage(JNIEnv* env, jobject thiz, jint width, jint height, + jobject yBuffer, jint yRowStride, jint yPixStride, jobject uBuffer, jint uRowStride, + jint uPixStride, jobject vBuffer, jint vRowStride, jint vPixStride) { + ALOGV("%s:", __FUNCTION__); + jniThrowRuntimeException(env, "nativeSetThumbnailImage is not implemented"); +} + +static void DngCreator_nativeWriteImage(JNIEnv* env, jobject thiz, jobject outStream, jint width, + jint height, jobject inBuffer, jint rowStride, jint pixStride) { + ALOGV("%s:", __FUNCTION__); + + sp<JniOutputStream> out = new JniOutputStream(env, outStream); + if(env->ExceptionCheck()) { + ALOGE("%s: Could not allocate buffers for output stream", __FUNCTION__); + return; + } + + uint8_t* pixelBytes = reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(inBuffer)); + if (pixelBytes == NULL) { + ALOGE("%s: Could not get native byte buffer", __FUNCTION__); + jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid bytebuffer"); + return; + } + + TiffWriter* writer = DngCreator_getCreator(env, thiz); + if (writer == NULL) { + ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__); + jniThrowException(env, "java/lang/AssertionError", + "Write called with uninitialized DngCreator"); + return; + } + // TODO: handle lens shading map, etc. conversions for other raw buffer sizes. + uint32_t metadataWidth = *(writer->getEntry(TAG_IMAGEWIDTH, TIFF_IFD_0)->getData<uint32_t>()); + uint32_t metadataHeight = *(writer->getEntry(TAG_IMAGELENGTH, TIFF_IFD_0)->getData<uint32_t>()); + if (metadataWidth != width) { + jniThrowExceptionFmt(env, "java/lang/IllegalStateException", \ + "Metadata width %d doesn't match image width %d", metadataWidth, width); + return; + } + + if (metadataHeight != height) { + jniThrowExceptionFmt(env, "java/lang/IllegalStateException", \ + "Metadata height %d doesn't match image height %d", metadataHeight, height); + return; + } + + uint32_t stripOffset = writer->getTotalSize(); + + BAIL_IF_INVALID(writer->addEntry(TAG_STRIPOFFSETS, 1, &stripOffset, TIFF_IFD_0), env, + TAG_STRIPOFFSETS); + + if (writer->write(out.get()) != OK) { + if (!env->ExceptionCheck()) { + jniThrowException(env, "java/io/IOException", "Failed to write metadata"); + } + return; + } + + size_t fullSize = rowStride * height; + jlong capacity = env->GetDirectBufferCapacity(inBuffer); + if (capacity < 0 || fullSize > capacity) { + jniThrowExceptionFmt(env, "java/lang/IllegalStateException", + "Invalid size %d for Image, size given in metadata is %d at current stride", + capacity, fullSize); + return; + } + + if (pixStride == BYTES_PER_SAMPLE && rowStride == width * BYTES_PER_SAMPLE) { + if (out->write(pixelBytes, 0, fullSize) != OK || env->ExceptionCheck()) { + if (!env->ExceptionCheck()) { + jniThrowException(env, "java/io/IOException", "Failed to write pixel data"); + } + return; + } + } else if (pixStride == BYTES_PER_SAMPLE) { + for (size_t i = 0; i < height; ++i) { + if (out->write(pixelBytes, i * rowStride, pixStride * width) != OK || + env->ExceptionCheck()) { + if (!env->ExceptionCheck()) { + jniThrowException(env, "java/io/IOException", "Failed to write pixel data"); + } + return; + } + } + } else { + for (size_t i = 0; i < height; ++i) { + for (size_t j = 0; j < width; ++j) { + if (out->write(pixelBytes, i * rowStride + j * pixStride, + BYTES_PER_SAMPLE) != OK || !env->ExceptionCheck()) { + if (env->ExceptionCheck()) { + jniThrowException(env, "java/io/IOException", "Failed to write pixel data"); + } + return; + } + } + } + } + +} + +static void DngCreator_nativeWriteByteBuffer(JNIEnv* env, jobject thiz, jobject outStream, + jobject rawBuffer, jlong offset) { + ALOGV("%s:", __FUNCTION__); + jniThrowRuntimeException(env, "nativeWriteByteBuffer is not implemented."); +} + +static void DngCreator_nativeWriteInputStream(JNIEnv* env, jobject thiz, jobject outStream, + jobject inStream, jlong offset) { + ALOGV("%s:", __FUNCTION__); + jniThrowRuntimeException(env, "nativeWriteInputStream is not implemented."); +} + +} /*extern "C" */ + +static JNINativeMethod gDngCreatorMethods[] = { + {"nativeClassInit", "()V", (void*) DngCreator_nativeClassInit}, + {"nativeInit", "(Landroid/hardware/camera2/impl/CameraMetadataNative;" + "Landroid/hardware/camera2/impl/CameraMetadataNative;)V", (void*) DngCreator_init}, + {"nativeDestroy", "()V", (void*) DngCreator_destroy}, + {"nativeSetOrientation", "(I)V", (void*) DngCreator_nativeSetOrientation}, + {"nativeSetThumbnailBitmap","(Landroid/graphics/Bitmap;)V", + (void*) DngCreator_nativeSetThumbnailBitmap}, + {"nativeSetThumbnailImage", + "(IILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;II)V", + (void*) DngCreator_nativeSetThumbnailImage}, + {"nativeWriteImage", "(Ljava/io/OutputStream;IILjava/nio/ByteBuffer;II)V", + (void*) DngCreator_nativeWriteImage}, + {"nativeWriteByteBuffer", "(Ljava/io/OutputStream;Ljava/nio/ByteBuffer;J)V", + (void*) DngCreator_nativeWriteByteBuffer}, + {"nativeWriteInputStream", "(Ljava/io/OutputStream;Ljava/io/InputStream;J)V", + (void*) DngCreator_nativeWriteInputStream}, +}; + +int register_android_media_DngCreator(JNIEnv *env) { + return AndroidRuntime::registerNativeMethods(env, + "android/media/DngCreator", gDngCreatorMethods, NELEM(gDngCreatorMethods)); +} diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index 6f42057..9d03cc3 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -884,6 +884,7 @@ static int register_android_media_MediaPlayer(JNIEnv *env) "android/media/MediaPlayer", gMethods, NELEM(gMethods)); } +extern int register_android_media_DngCreator(JNIEnv *env); extern int register_android_media_ImageReader(JNIEnv *env); extern int register_android_media_Crypto(JNIEnv *env); extern int register_android_media_Drm(JNIEnv *env); @@ -913,6 +914,11 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) } assert(env != NULL); + if (register_android_media_DngCreator(env) < 0) { + ALOGE("ERROR: ImageReader native registration failed"); + goto bail; + } + if (register_android_media_ImageReader(env) < 0) { ALOGE("ERROR: ImageReader native registration failed"); goto bail; diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java index 89886ef..8a7e642 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java @@ -23,10 +23,10 @@ import android.hardware.ICameraServiceListener; import android.hardware.IProCameraCallbacks; import android.hardware.IProCameraUser; import android.hardware.camera2.CameraMetadata; -import android.hardware.camera2.CaptureResultExtras; import android.hardware.camera2.ICameraDeviceCallbacks; import android.hardware.camera2.ICameraDeviceUser; import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.impl.CaptureResultExtras; import android.hardware.camera2.utils.BinderHolder; import android.hardware.camera2.utils.CameraBinderDecorator; import android.os.Binder; diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java index 74ce997..7b2e7dd 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java @@ -21,10 +21,10 @@ import android.graphics.SurfaceTexture; import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CaptureRequest; -import android.hardware.camera2.CaptureResultExtras; import android.hardware.camera2.ICameraDeviceCallbacks; import android.hardware.camera2.ICameraDeviceUser; import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.impl.CaptureResultExtras; import android.hardware.camera2.utils.BinderHolder; import android.media.Image; import android.media.ImageReader; diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java index 5ab586f..a77b647 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java @@ -18,6 +18,7 @@ package com.android.mediaframeworktest.unit; import android.test.suitebuilder.annotation.SmallTest; import android.util.Range; +import android.util.Rational; import android.util.SizeF; import android.graphics.ImageFormat; import android.graphics.Point; @@ -26,15 +27,14 @@ import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CaptureResult; -import android.hardware.camera2.ColorSpaceTransform; -import android.hardware.camera2.Face; -import android.hardware.camera2.MeteringRectangle; -import android.hardware.camera2.Rational; -import android.hardware.camera2.RggbChannelVector; -import android.hardware.camera2.Size; +import android.util.Size; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.marshal.impl.MarshalQueryableEnum; +import android.hardware.camera2.params.ColorSpaceTransform; +import android.hardware.camera2.params.Face; +import android.hardware.camera2.params.MeteringRectangle; import android.hardware.camera2.params.ReprocessFormatsMap; +import android.hardware.camera2.params.RggbChannelVector; import android.hardware.camera2.params.StreamConfiguration; import android.hardware.camera2.params.StreamConfigurationDuration; import android.hardware.camera2.params.StreamConfigurationMap; diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RationalTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RationalTest.java index 9621f92..18c0d3e 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RationalTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RationalTest.java @@ -17,7 +17,7 @@ package com.android.mediaframeworktest.unit; import android.test.suitebuilder.annotation.SmallTest; -import android.hardware.camera2.Rational; +import android.util.Rational; /** * <pre> |