summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
Diffstat (limited to 'media')
-rw-r--r--media/java/android/media/AudioManager.java29
-rw-r--r--media/java/android/media/AudioRecord.java12
-rw-r--r--media/java/android/media/AudioService.java29
-rw-r--r--media/java/android/media/AudioSystem.java11
-rw-r--r--media/java/android/media/AudioTrack.java12
-rw-r--r--media/java/android/media/DngCreator.java168
-rw-r--r--media/java/android/media/MediaMetadata.aidl (renamed from media/java/android/media/session/SessionToken.aidl)4
-rw-r--r--media/java/android/media/MediaMetadata.java (renamed from media/java/android/media/session/MediaMetadata.java)5
-rw-r--r--media/java/android/media/MediaMetadataEditor.java1
-rw-r--r--media/java/android/media/RemoteControlClient.java5
-rw-r--r--media/java/android/media/TtmlRenderer.java751
-rw-r--r--media/java/android/media/routeprovider/RouteConnection.java1
-rw-r--r--media/java/android/media/routeprovider/RouteInterfaceHandler.java9
-rw-r--r--media/java/android/media/routeprovider/RoutePlaybackControlsHandler.java1
-rw-r--r--media/java/android/media/routeprovider/RouteProviderService.java1
-rw-r--r--media/java/android/media/routeprovider/RouteRequest.java11
-rw-r--r--media/java/android/media/session/ISession.aidl3
-rw-r--r--media/java/android/media/session/ISessionCallback.aidl3
-rw-r--r--media/java/android/media/session/ISessionController.aidl2
-rw-r--r--media/java/android/media/session/ISessionControllerCallback.aidl2
-rw-r--r--media/java/android/media/session/ISessionManager.aidl2
-rw-r--r--media/java/android/media/session/MediaController.java (renamed from media/java/android/media/session/SessionController.java)38
-rw-r--r--media/java/android/media/session/MediaSession.java (renamed from media/java/android/media/session/Session.java)172
-rw-r--r--media/java/android/media/session/MediaSessionInfo.java (renamed from media/java/android/media/session/SessionInfo.java)18
-rw-r--r--media/java/android/media/session/MediaSessionLegacyHelper.java121
-rw-r--r--media/java/android/media/session/MediaSessionManager.java (renamed from media/java/android/media/session/SessionManager.java)55
-rw-r--r--media/java/android/media/session/MediaSessionToken.aidl (renamed from media/java/android/media/session/MediaMetadata.aidl)2
-rw-r--r--media/java/android/media/session/MediaSessionToken.java (renamed from media/java/android/media/session/SessionToken.java)18
-rw-r--r--media/java/android/media/session/PlaybackState.java3
-rw-r--r--media/java/android/media/session/Route.java7
-rw-r--r--media/java/android/media/session/RouteInfo.java1
-rw-r--r--media/java/android/media/session/RouteInterface.java7
-rw-r--r--media/java/android/media/session/RouteOptions.java1
-rw-r--r--media/java/android/media/session/RoutePlaybackControls.java2
-rw-r--r--media/java/android/media/session/TransportController.java1
-rw-r--r--media/java/android/media/session/TransportPerformer.java1
-rw-r--r--media/jni/Android.mk3
-rw-r--r--media/jni/android_media_DngCreator.cpp772
-rw-r--r--media/jni/android_media_MediaPlayer.cpp6
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java2
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java2
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java387
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RationalTest.java2
43 files changed, 2389 insertions, 294 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/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/TtmlRenderer.java b/media/java/android/media/TtmlRenderer.java
new file mode 100644
index 0000000..0309334
--- /dev/null
+++ b/media/java/android/media/TtmlRenderer.java
@@ -0,0 +1,751 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package android.media;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.media.SubtitleTrack.RenderingWidget.OnChangedListener;
+import android.text.Layout.Alignment;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup.LayoutParams;
+import android.view.accessibility.CaptioningManager;
+import android.view.accessibility.CaptioningManager.CaptionStyle;
+import android.view.accessibility.CaptioningManager.CaptioningChangeListener;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.widget.SubtitleView;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.TreeSet;
+import java.util.Vector;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+/** @hide */
+public class TtmlRenderer extends SubtitleController.Renderer {
+ private final Context mContext;
+
+ private static final String MEDIA_MIMETYPE_TEXT_TTML = "application/ttml+xml";
+
+ private TtmlRenderingWidget mRenderingWidget;
+
+ public TtmlRenderer(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public boolean supports(MediaFormat format) {
+ if (format.containsKey(MediaFormat.KEY_MIME)) {
+ return format.getString(MediaFormat.KEY_MIME).equals(MEDIA_MIMETYPE_TEXT_TTML);
+ }
+ return false;
+ }
+
+ @Override
+ public SubtitleTrack createTrack(MediaFormat format) {
+ if (mRenderingWidget == null) {
+ mRenderingWidget = new TtmlRenderingWidget(mContext);
+ }
+ return new TtmlTrack(mRenderingWidget, format);
+ }
+}
+
+/**
+ * A class which provides utillity methods for TTML parsing.
+ *
+ * @hide
+ */
+final class TtmlUtils {
+ public static final String TAG_TT = "tt";
+ public static final String TAG_HEAD = "head";
+ public static final String TAG_BODY = "body";
+ public static final String TAG_DIV = "div";
+ public static final String TAG_P = "p";
+ public static final String TAG_SPAN = "span";
+ public static final String TAG_BR = "br";
+ public static final String TAG_STYLE = "style";
+ public static final String TAG_STYLING = "styling";
+ public static final String TAG_LAYOUT = "layout";
+ public static final String TAG_REGION = "region";
+ public static final String TAG_METADATA = "metadata";
+ public static final String TAG_SMPTE_IMAGE = "smpte:image";
+ public static final String TAG_SMPTE_DATA = "smpte:data";
+ public static final String TAG_SMPTE_INFORMATION = "smpte:information";
+ public static final String PCDATA = "#pcdata";
+ public static final String ATTR_BEGIN = "begin";
+ public static final String ATTR_DURATION = "dur";
+ public static final String ATTR_END = "end";
+ public static final long INVALID_TIMESTAMP = Long.MAX_VALUE;
+
+ /**
+ * Time expression RE according to the spec:
+ * http://www.w3.org/TR/ttaf1-dfxp/#timing-value-timeExpression
+ */
+ private static final Pattern CLOCK_TIME = Pattern.compile(
+ "^([0-9][0-9]+):([0-9][0-9]):([0-9][0-9])"
+ + "(?:(\\.[0-9]+)|:([0-9][0-9])(?:\\.([0-9]+))?)?$");
+
+ private static final Pattern OFFSET_TIME = Pattern.compile(
+ "^([0-9]+(?:\\.[0-9]+)?)(h|m|s|ms|f|t)$");
+
+ private TtmlUtils() {
+ }
+
+ /**
+ * Parses the given time expression and returns a timestamp in millisecond.
+ * <p>
+ * For the format of the time expression, please refer <a href=
+ * "http://www.w3.org/TR/ttaf1-dfxp/#timing-value-timeExpression">timeExpression</a>
+ *
+ * @param time A string which includes time expression.
+ * @param frameRate the framerate of the stream.
+ * @param subframeRate the sub-framerate of the stream
+ * @param tickRate the tick rate of the stream.
+ * @return the parsed timestamp in micro-second.
+ * @throws NumberFormatException if the given string does not match to the
+ * format.
+ */
+ public static long parseTimeExpression(String time, int frameRate, int subframeRate,
+ int tickRate) throws NumberFormatException {
+ Matcher matcher = CLOCK_TIME.matcher(time);
+ if (matcher.matches()) {
+ String hours = matcher.group(1);
+ double durationSeconds = Long.parseLong(hours) * 3600;
+ String minutes = matcher.group(2);
+ durationSeconds += Long.parseLong(minutes) * 60;
+ String seconds = matcher.group(3);
+ durationSeconds += Long.parseLong(seconds);
+ String fraction = matcher.group(4);
+ durationSeconds += (fraction != null) ? Double.parseDouble(fraction) : 0;
+ String frames = matcher.group(5);
+ durationSeconds += (frames != null) ? ((double)Long.parseLong(frames)) / frameRate : 0;
+ String subframes = matcher.group(6);
+ durationSeconds += (subframes != null) ? ((double)Long.parseLong(subframes))
+ / subframeRate / frameRate
+ : 0;
+ return (long)(durationSeconds * 1000);
+ }
+ matcher = OFFSET_TIME.matcher(time);
+ if (matcher.matches()) {
+ String timeValue = matcher.group(1);
+ double value = Double.parseDouble(timeValue);
+ String unit = matcher.group(2);
+ if (unit.equals("h")) {
+ value *= 3600L * 1000000L;
+ } else if (unit.equals("m")) {
+ value *= 60 * 1000000;
+ } else if (unit.equals("s")) {
+ value *= 1000000;
+ } else if (unit.equals("ms")) {
+ value *= 1000;
+ } else if (unit.equals("f")) {
+ value = value / frameRate * 1000000;
+ } else if (unit.equals("t")) {
+ value = value / tickRate * 1000000;
+ }
+ return (long)value;
+ }
+ throw new NumberFormatException("Malformed time expression : " + time);
+ }
+
+ /**
+ * Applies <a href
+ * src="http://www.w3.org/TR/ttaf1-dfxp/#content-attribute-space">the
+ * default space policy</a> to the given string.
+ *
+ * @param in A string to apply the policy.
+ */
+ public static String applyDefaultSpacePolicy(String in) {
+ return applySpacePolicy(in, true);
+ }
+
+ /**
+ * Applies the space policy to the given string. This applies <a href
+ * src="http://www.w3.org/TR/ttaf1-dfxp/#content-attribute-space">the
+ * default space policy</a> with linefeed-treatment as treat-as-space
+ * or preserve.
+ *
+ * @param in A string to apply the policy.
+ * @param treatLfAsSpace Whether convert line feeds to spaces or not.
+ */
+ public static String applySpacePolicy(String in, boolean treatLfAsSpace) {
+ // Removes CR followed by LF. ref:
+ // http://www.w3.org/TR/xml/#sec-line-ends
+ String crRemoved = in.replaceAll("\r\n", "\n");
+ // Apply suppress-at-line-break="auto" and
+ // white-space-treatment="ignore-if-surrounding-linefeed"
+ String spacesNeighboringLfRemoved = crRemoved.replaceAll(" *\n *", "\n");
+ // Apply linefeed-treatment="treat-as-space"
+ String lfToSpace = treatLfAsSpace ? spacesNeighboringLfRemoved.replaceAll("\n", " ")
+ : spacesNeighboringLfRemoved;
+ // Apply white-space-collapse="true"
+ String spacesCollapsed = lfToSpace.replaceAll("[ \t\\x0B\f\r]+", " ");
+ return spacesCollapsed;
+ }
+
+ /**
+ * Returns the timed text for the given time period.
+ *
+ * @param root The root node of the TTML document.
+ * @param startUs The start time of the time period in microsecond.
+ * @param endUs The end time of the time period in microsecond.
+ */
+ public static String extractText(TtmlNode root, long startUs, long endUs) {
+ StringBuilder text = new StringBuilder();
+ extractText(root, startUs, endUs, text, false);
+ return text.toString().replaceAll("\n$", "");
+ }
+
+ private static void extractText(TtmlNode node, long startUs, long endUs, StringBuilder out,
+ boolean inPTag) {
+ if (node.mName.equals(TtmlUtils.PCDATA) && inPTag) {
+ out.append(node.mText);
+ } else if (node.mName.equals(TtmlUtils.TAG_BR) && inPTag) {
+ out.append("\n");
+ } else if (node.mName.equals(TtmlUtils.TAG_METADATA)) {
+ // do nothing.
+ } else if (node.isActive(startUs, endUs)) {
+ boolean pTag = node.mName.equals(TtmlUtils.TAG_P);
+ int length = out.length();
+ for (int i = 0; i < node.mChildren.size(); ++i) {
+ extractText(node.mChildren.get(i), startUs, endUs, out, pTag || inPTag);
+ }
+ if (pTag && length != out.length()) {
+ out.append("\n");
+ }
+ }
+ }
+
+ /**
+ * Returns a TTML fragment string for the given time period.
+ *
+ * @param root The root node of the TTML document.
+ * @param startUs The start time of the time period in microsecond.
+ * @param endUs The end time of the time period in microsecond.
+ */
+ public static String extractTtmlFragment(TtmlNode root, long startUs, long endUs) {
+ StringBuilder fragment = new StringBuilder();
+ extractTtmlFragment(root, startUs, endUs, fragment);
+ return fragment.toString();
+ }
+
+ private static void extractTtmlFragment(TtmlNode node, long startUs, long endUs,
+ StringBuilder out) {
+ if (node.mName.equals(TtmlUtils.PCDATA)) {
+ out.append(node.mText);
+ } else if (node.mName.equals(TtmlUtils.TAG_BR)) {
+ out.append("<br/>");
+ } else if (node.isActive(startUs, endUs)) {
+ out.append("<");
+ out.append(node.mName);
+ out.append(node.mAttributes);
+ out.append(">");
+ for (int i = 0; i < node.mChildren.size(); ++i) {
+ extractTtmlFragment(node.mChildren.get(i), startUs, endUs, out);
+ }
+ out.append("</");
+ out.append(node.mName);
+ out.append(">");
+ }
+ }
+}
+
+/**
+ * A container class which represents a cue in TTML.
+ * @hide
+ */
+class TtmlCue extends SubtitleTrack.Cue {
+ public String mText;
+ public String mTtmlFragment;
+
+ public TtmlCue(long startTimeMs, long endTimeMs, String text, String ttmlFragment) {
+ this.mStartTimeMs = startTimeMs;
+ this.mEndTimeMs = endTimeMs;
+ this.mText = text;
+ this.mTtmlFragment = ttmlFragment;
+ }
+}
+
+/**
+ * A container class which represents a node in TTML.
+ *
+ * @hide
+ */
+class TtmlNode {
+ public final String mName;
+ public final String mAttributes;
+ public final TtmlNode mParent;
+ public final String mText;
+ public final List<TtmlNode> mChildren = new ArrayList<TtmlNode>();
+ public final long mRunId;
+ public final long mStartTimeMs;
+ public final long mEndTimeMs;
+
+ public TtmlNode(String name, String attributes, String text, long startTimeMs, long endTimeMs,
+ TtmlNode parent, long runId) {
+ this.mName = name;
+ this.mAttributes = attributes;
+ this.mText = text;
+ this.mStartTimeMs = startTimeMs;
+ this.mEndTimeMs = endTimeMs;
+ this.mParent = parent;
+ this.mRunId = runId;
+ }
+
+ /**
+ * Check if this node is active in the given time range.
+ *
+ * @param startTimeMs The start time of the range to check in microsecond.
+ * @param endTimeMs The end time of the range to check in microsecond.
+ * @return return true if the given range overlaps the time range of this
+ * node.
+ */
+ public boolean isActive(long startTimeMs, long endTimeMs) {
+ return this.mEndTimeMs > startTimeMs && this.mStartTimeMs < endTimeMs;
+ }
+}
+
+/**
+ * A simple TTML parser (http://www.w3.org/TR/ttaf1-dfxp/) which supports DFXP
+ * presentation profile.
+ * <p>
+ * Supported features in this parser are:
+ * <ul>
+ * <li>content
+ * <li>core
+ * <li>presentation
+ * <li>profile
+ * <li>structure
+ * <li>time-offset
+ * <li>timing
+ * <li>tickRate
+ * <li>time-clock-with-frames
+ * <li>time-clock
+ * <li>time-offset-with-frames
+ * <li>time-offset-with-ticks
+ * </ul>
+ * </p>
+ *
+ * @hide
+ */
+class TtmlParser {
+ static final String TAG = "TtmlParser";
+
+ // TODO: read and apply the following attributes if specified.
+ private static final int DEFAULT_FRAMERATE = 30;
+ private static final int DEFAULT_SUBFRAMERATE = 1;
+ private static final int DEFAULT_TICKRATE = 1;
+
+ private XmlPullParser mParser;
+ private final TtmlNodeListener mListener;
+ private long mCurrentRunId;
+
+ public TtmlParser(TtmlNodeListener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * Parse TTML data. Once this is called, all the previous data are
+ * reset and it starts parsing for the given text.
+ *
+ * @param ttmlText TTML text to parse.
+ * @throws XmlPullParserException
+ * @throws IOException
+ */
+ public void parse(String ttmlText, long runId) throws XmlPullParserException, IOException {
+ mParser = null;
+ mCurrentRunId = runId;
+ loadParser(ttmlText);
+ parseTtml();
+ }
+
+ private void loadParser(String ttmlFragment) throws XmlPullParserException {
+ XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
+ factory.setNamespaceAware(false);
+ mParser = factory.newPullParser();
+ StringReader in = new StringReader(ttmlFragment);
+ mParser.setInput(in);
+ }
+
+ private void extractAttribute(XmlPullParser parser, int i, StringBuilder out) {
+ out.append(" ");
+ out.append(parser.getAttributeName(i));
+ out.append("=\"");
+ out.append(parser.getAttributeValue(i));
+ out.append("\"");
+ }
+
+ private void parseTtml() throws XmlPullParserException, IOException {
+ LinkedList<TtmlNode> nodeStack = new LinkedList<TtmlNode>();
+ int depthInUnsupportedTag = 0;
+ boolean active = true;
+ while (!isEndOfDoc()) {
+ int eventType = mParser.getEventType();
+ TtmlNode parent = nodeStack.peekLast();
+ if (active) {
+ if (eventType == XmlPullParser.START_TAG) {
+ if (!isSupportedTag(mParser.getName())) {
+ Log.w(TAG, "Unsupported tag " + mParser.getName() + " is ignored.");
+ depthInUnsupportedTag++;
+ active = false;
+ } else {
+ TtmlNode node = parseNode(parent);
+ nodeStack.addLast(node);
+ if (parent != null) {
+ parent.mChildren.add(node);
+ }
+ }
+ } else if (eventType == XmlPullParser.TEXT) {
+ String text = TtmlUtils.applyDefaultSpacePolicy(mParser.getText());
+ if (!TextUtils.isEmpty(text)) {
+ parent.mChildren.add(new TtmlNode(
+ TtmlUtils.PCDATA, "", text, 0, TtmlUtils.INVALID_TIMESTAMP,
+ parent, mCurrentRunId));
+
+ }
+ } else if (eventType == XmlPullParser.END_TAG) {
+ if (mParser.getName().equals(TtmlUtils.TAG_P)) {
+ mListener.onTtmlNodeParsed(nodeStack.getLast());
+ } else if (mParser.getName().equals(TtmlUtils.TAG_TT)) {
+ mListener.onRootNodeParsed(nodeStack.getLast());
+ }
+ nodeStack.removeLast();
+ }
+ } else {
+ if (eventType == XmlPullParser.START_TAG) {
+ depthInUnsupportedTag++;
+ } else if (eventType == XmlPullParser.END_TAG) {
+ depthInUnsupportedTag--;
+ if (depthInUnsupportedTag == 0) {
+ active = true;
+ }
+ }
+ }
+ mParser.next();
+ }
+ }
+
+ private TtmlNode parseNode(TtmlNode parent) throws XmlPullParserException, IOException {
+ int eventType = mParser.getEventType();
+ if (!(eventType == XmlPullParser.START_TAG)) {
+ return null;
+ }
+ StringBuilder attrStr = new StringBuilder();
+ long start = 0;
+ long end = TtmlUtils.INVALID_TIMESTAMP;
+ long dur = 0;
+ for (int i = 0; i < mParser.getAttributeCount(); ++i) {
+ String attr = mParser.getAttributeName(i);
+ String value = mParser.getAttributeValue(i);
+ // TODO: check if it's safe to ignore the namespace of attributes as follows.
+ attr = attr.replaceFirst("^.*:", "");
+ if (attr.equals(TtmlUtils.ATTR_BEGIN)) {
+ start = TtmlUtils.parseTimeExpression(value, DEFAULT_FRAMERATE,
+ DEFAULT_SUBFRAMERATE, DEFAULT_TICKRATE);
+ } else if (attr.equals(TtmlUtils.ATTR_END)) {
+ end = TtmlUtils.parseTimeExpression(value, DEFAULT_FRAMERATE, DEFAULT_SUBFRAMERATE,
+ DEFAULT_TICKRATE);
+ } else if (attr.equals(TtmlUtils.ATTR_DURATION)) {
+ dur = TtmlUtils.parseTimeExpression(value, DEFAULT_FRAMERATE, DEFAULT_SUBFRAMERATE,
+ DEFAULT_TICKRATE);
+ } else {
+ extractAttribute(mParser, i, attrStr);
+ }
+ }
+ if (parent != null) {
+ start += parent.mStartTimeMs;
+ if (end != TtmlUtils.INVALID_TIMESTAMP) {
+ end += parent.mStartTimeMs;
+ }
+ }
+ if (dur > 0) {
+ if (end != TtmlUtils.INVALID_TIMESTAMP) {
+ Log.e(TAG, "'dur' and 'end' attributes are defined at the same time." +
+ "'end' value is ignored.");
+ }
+ end = start + dur;
+ }
+ if (parent != null) {
+ // If the end time remains unspecified, then the end point is
+ // interpreted as the end point of the external time interval.
+ if (end == TtmlUtils.INVALID_TIMESTAMP &&
+ parent.mEndTimeMs != TtmlUtils.INVALID_TIMESTAMP &&
+ end > parent.mEndTimeMs) {
+ end = parent.mEndTimeMs;
+ }
+ }
+ TtmlNode node = new TtmlNode(mParser.getName(), attrStr.toString(), null, start, end,
+ parent, mCurrentRunId);
+ return node;
+ }
+
+ private boolean isEndOfDoc() throws XmlPullParserException {
+ return (mParser.getEventType() == XmlPullParser.END_DOCUMENT);
+ }
+
+ private static boolean isSupportedTag(String tag) {
+ if (tag.equals(TtmlUtils.TAG_TT) || tag.equals(TtmlUtils.TAG_HEAD) ||
+ tag.equals(TtmlUtils.TAG_BODY) || tag.equals(TtmlUtils.TAG_DIV) ||
+ tag.equals(TtmlUtils.TAG_P) || tag.equals(TtmlUtils.TAG_SPAN) ||
+ tag.equals(TtmlUtils.TAG_BR) || tag.equals(TtmlUtils.TAG_STYLE) ||
+ tag.equals(TtmlUtils.TAG_STYLING) || tag.equals(TtmlUtils.TAG_LAYOUT) ||
+ tag.equals(TtmlUtils.TAG_REGION) || tag.equals(TtmlUtils.TAG_METADATA) ||
+ tag.equals(TtmlUtils.TAG_SMPTE_IMAGE) || tag.equals(TtmlUtils.TAG_SMPTE_DATA) ||
+ tag.equals(TtmlUtils.TAG_SMPTE_INFORMATION)) {
+ return true;
+ }
+ return false;
+ }
+}
+
+/** @hide */
+interface TtmlNodeListener {
+ void onTtmlNodeParsed(TtmlNode node);
+ void onRootNodeParsed(TtmlNode node);
+}
+
+/** @hide */
+class TtmlTrack extends SubtitleTrack implements TtmlNodeListener {
+ private static final String TAG = "TtmlTrack";
+
+ private final TtmlParser mParser = new TtmlParser(this);
+ private final TtmlRenderingWidget mRenderingWidget;
+ private String mParsingData;
+ private Long mCurrentRunID;
+
+ private final LinkedList<TtmlNode> mTtmlNodes;
+ private final TreeSet<Long> mTimeEvents;
+ private TtmlNode mRootNode;
+
+ TtmlTrack(TtmlRenderingWidget renderingWidget, MediaFormat format) {
+ super(format);
+
+ mTtmlNodes = new LinkedList<TtmlNode>();
+ mTimeEvents = new TreeSet<Long>();
+ mRenderingWidget = renderingWidget;
+ mParsingData = "";
+ }
+
+ @Override
+ public TtmlRenderingWidget getRenderingWidget() {
+ return mRenderingWidget;
+ }
+
+ @Override
+ public void onData(String data, boolean eos, long runID) {
+ // implement intermixing restriction for TTML.
+ synchronized(mParser) {
+ if (mCurrentRunID != null && runID != mCurrentRunID) {
+ throw new IllegalStateException(
+ "Run #" + mCurrentRunID +
+ " in progress. Cannot process run #" + runID);
+ }
+ mCurrentRunID = runID;
+ mParsingData += data;
+ if (eos) {
+ try {
+ mParser.parse(mParsingData, mCurrentRunID);
+ } catch (XmlPullParserException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ finishedRun(runID);
+ mParsingData = "";
+ mCurrentRunID = null;
+ }
+ }
+ }
+
+ @Override
+ public void onTtmlNodeParsed(TtmlNode node) {
+ mTtmlNodes.addLast(node);
+ addTimeEvents(node);
+ }
+
+ @Override
+ public void onRootNodeParsed(TtmlNode node) {
+ mRootNode = node;
+ TtmlCue cue = null;
+ while ((cue = getNextResult()) != null) {
+ addCue(cue);
+ }
+ mRootNode = null;
+ mTtmlNodes.clear();
+ mTimeEvents.clear();
+ }
+
+ @Override
+ public void updateView(Vector<SubtitleTrack.Cue> activeCues) {
+ if (!mVisible) {
+ // don't keep the state if we are not visible
+ return;
+ }
+
+ if (DEBUG && mTimeProvider != null) {
+ try {
+ Log.d(TAG, "at " +
+ (mTimeProvider.getCurrentTimeUs(false, true) / 1000) +
+ " ms the active cues are:");
+ } catch (IllegalStateException e) {
+ Log.d(TAG, "at (illegal state) the active cues are:");
+ }
+ }
+
+ mRenderingWidget.setActiveCues(activeCues);
+ }
+
+ /**
+ * Returns a {@link TtmlCue} in the presentation time order.
+ * {@code null} is returned if there is no more timed text to show.
+ */
+ public TtmlCue getNextResult() {
+ while (mTimeEvents.size() >= 2) {
+ long start = mTimeEvents.pollFirst();
+ long end = mTimeEvents.first();
+ List<TtmlNode> activeCues = getActiveNodes(start, end);
+ if (!activeCues.isEmpty()) {
+ return new TtmlCue(start, end,
+ TtmlUtils.applySpacePolicy(TtmlUtils.extractText(
+ mRootNode, start, end), false),
+ TtmlUtils.extractTtmlFragment(mRootNode, start, end));
+ }
+ }
+ return null;
+ }
+
+ private void addTimeEvents(TtmlNode node) {
+ mTimeEvents.add(node.mStartTimeMs);
+ mTimeEvents.add(node.mEndTimeMs);
+ for (int i = 0; i < node.mChildren.size(); ++i) {
+ addTimeEvents(node.mChildren.get(i));
+ }
+ }
+
+ private List<TtmlNode> getActiveNodes(long startTimeUs, long endTimeUs) {
+ List<TtmlNode> activeNodes = new ArrayList<TtmlNode>();
+ for (int i = 0; i < mTtmlNodes.size(); ++i) {
+ TtmlNode node = mTtmlNodes.get(i);
+ if (node.isActive(startTimeUs, endTimeUs)) {
+ activeNodes.add(node);
+ }
+ }
+ return activeNodes;
+ }
+}
+
+/**
+ * Widget capable of rendering TTML captions.
+ *
+ * @hide
+ */
+class TtmlRenderingWidget extends LinearLayout implements SubtitleTrack.RenderingWidget {
+
+ /** Callback for rendering changes. */
+ private OnChangedListener mListener;
+ private final TextView mTextView;
+
+ public TtmlRenderingWidget(Context context) {
+ this(context, null);
+ }
+
+ public TtmlRenderingWidget(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public TtmlRenderingWidget(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public TtmlRenderingWidget(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ // Cannot render text over video when layer type is hardware.
+ setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+
+ CaptioningManager captionManager = (CaptioningManager) context.getSystemService(
+ Context.CAPTIONING_SERVICE);
+ mTextView = new TextView(context);
+ mTextView.setTextColor(captionManager.getUserStyle().foregroundColor);
+ addView(mTextView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ mTextView.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
+ }
+
+ @Override
+ public void setOnChangedListener(OnChangedListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public void setSize(int width, int height) {
+ final int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
+ final int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+
+ measure(widthSpec, heightSpec);
+ layout(0, 0, width, height);
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ if (visible) {
+ setVisibility(View.VISIBLE);
+ } else {
+ setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ }
+
+ public void setActiveCues(Vector<SubtitleTrack.Cue> activeCues) {
+ final int count = activeCues.size();
+ String subtitleText = "";
+ for (int i = 0; i < count; i++) {
+ TtmlCue cue = (TtmlCue) activeCues.get(i);
+ subtitleText += cue.mText + "\n";
+ }
+ mTextView.setText(subtitleText);
+
+ if (mListener != null) {
+ mListener.onChanged(this);
+ }
+ }
+}
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;
* &lt;/intent-filter>
* &lt;/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 3ff07d9..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;
@@ -40,6 +40,7 @@ interface ISession {
boolean setRoute(in RouteInfo route);
void setRouteOptions(in List<RouteOptions> options);
void connectToRoute(in RouteInfo route, in RouteOptions options);
+ void disconnectFromRoute(in RouteInfo route);
void sendRouteCommand(in RouteCommand event, in ResultReceiver cb);
// These commands are for the TransportPerformer
diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl
index f04cbcc..7b0412e 100644
--- a/media/java/android/media/session/ISessionCallback.aidl
+++ b/media/java/android/media/session/ISessionCallback.aidl
@@ -28,9 +28,10 @@ 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);
void onRouteStateChange(int state);
void onRouteEvent(in RouteEvent event);
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 194679e7..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";
/**
@@ -86,10 +86,51 @@ public final class Session {
*/
public static final int FLAG_EXCLUSIVE_GLOBAL_PRIORITY = 1 << 16;
+ /**
+ * 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;
private static final int MSG_ROUTE_CONNECTED = 4;
+ private static final int MSG_ROUTE_DISCONNECTED = 5;
private static final String KEY_COMMAND = "command";
private static final String KEY_EXTRAS = "extras";
@@ -97,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;
@@ -114,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;
@@ -123,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);
}
@@ -138,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.
@@ -259,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;
}
@@ -275,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
@@ -284,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) {
@@ -303,10 +345,16 @@ public final class Session {
* Disconnect from the current route. After calling you will be switched
* back to the default route.
*
- * @param route The route to disconnect from.
+ * @hide
*/
- public void disconnect(RouteInfo route) {
- // TODO
+ public void disconnect() {
+ if (mRoute != null) {
+ try {
+ mBinder.disconnectFromRoute(mRoute.getRouteInfo());
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error disconnecting from route");
+ }
+ }
}
/**
@@ -314,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 {
@@ -406,6 +455,16 @@ public final class Session {
}
}
+ private void postRouteDisconnected(RouteInfo route, int reason) {
+ synchronized (mLock) {
+ if (mRoute != null && TextUtils.equals(mRoute.getRouteInfo().getId(), route.getId())) {
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ mCallbacks.get(i).post(MSG_ROUTE_DISCONNECTED, mRoute, reason);
+ }
+ }
+ }
+ }
+
/**
* Receives commands or updates from controllers and routes. An app can
* specify what commands and buttons it supports by setting them on the
@@ -448,6 +507,7 @@ public final class Session {
* ongoing playback if necessary.
*
* @param route
+ * @hide
*/
public void onRequestRouteChange(RouteInfo route) {
}
@@ -457,6 +517,7 @@ public final class Session {
* are now valid.
*
* @param route The route that was connected
+ * @hide
*/
public void onRouteConnected(Route route) {
}
@@ -467,10 +528,16 @@ public final class Session {
* <p>
* Valid reasons are:
* <ul>
+ * <li>{@link #DISCONNECT_REASON_USER_STOPPING}</li>
+ * <li>{@link #DISCONNECT_REASON_PROVIDER_DISCONNECTED}</li>
+ * <li>{@link #DISCONNECT_REASON_ROUTE_CHANGED}</li>
+ * <li>{@link #DISCONNECT_REASON_SESSION_DISCONNECTED}</li>
+ * <li>{@link #DISCONNECT_REASON_SESSION_DESTROYED}</li>
* </ul>
*
* @param route The route that disconnected
* @param reason The reason for the disconnect
+ * @hide
*/
public void onRouteDisconnected(Route route, int reason) {
}
@@ -480,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);
}
@@ -513,15 +584,23 @@ 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);
}
}
@Override
+ public void onRouteDisconnected(RouteInfo route, int reason) {
+ MediaSession session = mMediaSession.get();
+ if (session != null) {
+ session.postRouteDisconnected(route, reason);
+ }
+ }
+
+ @Override
public void onPlay() throws RemoteException {
- Session session = mMediaSession.get();
+ MediaSession session = mMediaSession.get();
if (session != null) {
TransportPerformer tp = session.getTransportPerformer();
if (tp != null) {
@@ -532,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) {
@@ -543,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) {
@@ -554,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) {
@@ -565,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) {
@@ -576,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) {
@@ -587,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) {
@@ -598,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) {
@@ -609,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) {
@@ -620,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());
@@ -641,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;
}
@@ -668,6 +747,9 @@ public final class Session {
case MSG_ROUTE_CONNECTED:
mCallback.onRouteConnected((Route) msg.obj);
break;
+ case MSG_ROUTE_DISCONNECTED:
+ mCallback.onRouteDisconnected((Route) msg.obj, msg.arg1);
+ break;
}
}
}
@@ -675,6 +757,10 @@ public final class Session {
public void post(int what, Object obj) {
obtainMessage(what, obj).sendToTarget();
}
+
+ public void post(int what, Object obj, int arg1) {
+ obtainMessage(what, arg1, 0, obj).sendToTarget();
+ }
}
private static final class Command {
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 b28733a..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,24 +18,26 @@ 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;
import android.graphics.PointF;
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.ReprocessFormatsMap;
-import android.hardware.camera2.RggbChannelVector;
-import android.hardware.camera2.Size;
-import android.hardware.camera2.StreamConfiguration;
-import android.hardware.camera2.StreamConfigurationDuration;
+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;
import android.hardware.camera2.utils.TypeReference;
import static android.hardware.camera2.impl.CameraMetadataNative.*;
@@ -72,6 +74,9 @@ public class CameraMetadataTest extends junit.framework.TestCase {
static final int ANDROID_CONTROL_AE_ANTIBANDING_MODE = ANDROID_CONTROL_START;
static final int ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION = ANDROID_CONTROL_START + 1;
+ // From graphics.h
+ private static final int HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED = 0x22;
+
@Override
public void setUp() {
mMetadata = new CameraMetadataNative();
@@ -293,6 +298,28 @@ public class CameraMetadataTest extends junit.framework.TestCase {
}
}
+ private static <T, T2> void assertArrayContains(T needle, T2 array) {
+ if (!array.getClass().isArray()) {
+ throw new IllegalArgumentException("actual must be array");
+ }
+
+ int len = Array.getLength(array);
+ for (int i = 0; i < len; ++i) {
+
+ Object actualElement = Array.get(array, i);
+
+ if (needle.equals(actualElement)) {
+ return;
+ }
+ }
+
+ fail(String.format(
+ "could not find element in array (needle %s). "
+ + "Array was: %s.",
+ needle,
+ formatArray(array, len)));
+ }
+
private <T> void checkKeyGetAndSet(String keyStr, TypeReference<T> typeToken, T expected,
boolean reuse) {
Key<T> key = new Key<T>(keyStr, typeToken);
@@ -804,18 +831,48 @@ public class CameraMetadataTest extends junit.framework.TestCase {
@SmallTest
public void testReadWriteReprocessFormatsMap() {
- final int RAW_OPAQUE = 0x24;
+ // final int RAW_OPAQUE = 0x24; // TODO: add RAW_OPAQUE to ImageFormat
final int RAW16 = ImageFormat.RAW_SENSOR;
final int YUV_420_888 = ImageFormat.YUV_420_888;
final int BLOB = 0x21;
+ // TODO: also test HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED as an output
int[] contents = new int[] {
- RAW_OPAQUE, 3, RAW16, YUV_420_888, BLOB,
+ YUV_420_888, 3, YUV_420_888, ImageFormat.NV21, BLOB,
RAW16, 2, YUV_420_888, BLOB,
+
};
// int32 x n
- checkKeyMarshal("android.scaler.availableInputOutputFormatsMap",
+ Key<ReprocessFormatsMap> key = new Key<ReprocessFormatsMap>(
+ "android.scaler.availableInputOutputFormatsMap", ReprocessFormatsMap.class);
+ mMetadata.writeValues(key.getTag(), toByteArray(contents));
+
+ ReprocessFormatsMap map = mMetadata.get(key);
+
+ /*
+ * Make sure the inputs/outputs were what we expected.
+ * - Use public image format constants here.
+ */
+
+ int[] expectedInputs = new int[] {
+ YUV_420_888, RAW16
+ };
+ assertArrayEquals(expectedInputs, map.getInputs());
+
+ int[] expectedYuvOutputs = new int[] {
+ YUV_420_888, ImageFormat.NV21, ImageFormat.JPEG,
+ };
+ assertArrayEquals(expectedYuvOutputs, map.getOutputs(ImageFormat.YUV_420_888));
+
+ int[] expectedRaw16Outputs = new int[] {
+ YUV_420_888, ImageFormat.JPEG,
+ };
+ assertArrayEquals(expectedRaw16Outputs, map.getOutputs(ImageFormat.RAW_SENSOR));
+
+ // Finally, do a round-trip check as a sanity
+ checkKeyMarshal(
+ "android.scaler.availableInputOutputFormatsMap",
new ReprocessFormatsMap(contents),
toByteArray(contents)
);
@@ -889,68 +946,6 @@ public class CameraMetadataTest extends junit.framework.TestCase {
expectedIntValues, availableFormatTag);
//
- // android.scaler.availableStreamConfigurations (int x n x 4 array)
- //
- final int OUTPUT = CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT;
- int[] availableStreamConfigs = new int[] {
- 0x20, 3280, 2464, OUTPUT, // RAW16
- 0x23, 3264, 2448, OUTPUT, // YCbCr_420_888
- 0x23, 3200, 2400, OUTPUT, // YCbCr_420_888
- 0x100, 3264, 2448, OUTPUT, // ImageFormat.JPEG
- 0x100, 3200, 2400, OUTPUT, // ImageFormat.JPEG
- 0x100, 2592, 1944, OUTPUT, // ImageFormat.JPEG
- 0x100, 2048, 1536, OUTPUT, // ImageFormat.JPEG
- 0x100, 1920, 1080, OUTPUT // ImageFormat.JPEG
- };
- int[] expectedAvailableStreamConfigs = new int[] {
- 0x20, 3280, 2464, OUTPUT, // RAW16
- 0x23, 3264, 2448, OUTPUT, // YCbCr_420_888
- 0x23, 3200, 2400, OUTPUT, // YCbCr_420_888
- 0x21, 3264, 2448, OUTPUT, // BLOB
- 0x21, 3200, 2400, OUTPUT, // BLOB
- 0x21, 2592, 1944, OUTPUT, // BLOB
- 0x21, 2048, 1536, OUTPUT, // BLOB
- 0x21, 1920, 1080, OUTPUT // BLOB
- };
- int availableStreamConfigTag =
- CameraMetadataNative.getTag("android.scaler.availableStreamConfigurations");
-
- Key<int[]> configKey = CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS;
- validateArrayMetadataReadWriteOverride(configKey, availableStreamConfigs,
- expectedAvailableStreamConfigs, availableStreamConfigTag);
-
- //
- // android.scaler.availableMinFrameDurations (int x n x 4 array)
-
- //
- long[] availableMinDurations = new long[] {
- 0x20, 3280, 2464, 33333336, // RAW16
- 0x23, 3264, 2448, 33333336, // YCbCr_420_888
- 0x23, 3200, 2400, 33333336, // YCbCr_420_888
- 0x100, 3264, 2448, 33333336, // ImageFormat.JPEG
- 0x100, 3200, 2400, 33333336, // ImageFormat.JPEG
- 0x100, 2592, 1944, 33333336, // ImageFormat.JPEG
- 0x100, 2048, 1536, 33333336, // ImageFormat.JPEG
- 0x100, 1920, 1080, 33333336 // ImageFormat.JPEG
- };
- long[] expectedAvailableMinDurations = new long[] {
- 0x20, 3280, 2464, 33333336, // RAW16
- 0x23, 3264, 2448, 33333336, // YCbCr_420_888
- 0x23, 3200, 2400, 33333336, // YCbCr_420_888
- 0x21, 3264, 2448, 33333336, // BLOB
- 0x21, 3200, 2400, 33333336, // BLOB
- 0x21, 2592, 1944, 33333336, // BLOB
- 0x21, 2048, 1536, 33333336, // BLOB
- 0x21, 1920, 1080, 33333336 // BLOB
- };
- int availableMinDurationsTag =
- CameraMetadataNative.getTag("android.scaler.availableMinFrameDurations");
-
- Key<long[]> durationKey = CameraCharacteristics.SCALER_AVAILABLE_MIN_FRAME_DURATIONS;
- validateArrayMetadataReadWriteOverride(durationKey, availableMinDurations,
- expectedAvailableMinDurations, availableMinDurationsTag);
-
- //
// android.statistics.faces (Face x n array)
//
int[] expectedFaceIds = new int[] {1, 2, 3, 4, 5};
@@ -1015,14 +1010,238 @@ public class CameraMetadataTest extends junit.framework.TestCase {
}
/**
+ * Set the raw native value of the available stream configurations; ensure that
+ * the read-out managed value is consistent with what we write in.
+ */
+ @SmallTest
+ public void testOverrideStreamConfigurationMap() {
+
+ /*
+ * First, write all the raw values:
+ * - availableStreamConfigurations
+ * - availableMinFrameDurations
+ * - availableStallDurations
+ *
+ * Then, read this out as a synthetic multi-key 'streamConfigurationMap'
+ *
+ * Finally, validate that the map was unmarshaled correctly
+ * and is converting the internal formats to public formats properly.
+ */
+
+ //
+ // android.scaler.availableStreamConfigurations (int x n x 4 array)
+ //
+ final int OUTPUT = 0;
+ final int INPUT = 1;
+ int[] rawAvailableStreamConfigs = new int[] {
+ 0x20, 3280, 2464, OUTPUT, // RAW16
+ 0x23, 3264, 2448, OUTPUT, // YCbCr_420_888
+ 0x23, 3200, 2400, OUTPUT, // YCbCr_420_888
+ 0x21, 3264, 2448, OUTPUT, // BLOB
+ 0x21, 3200, 2400, OUTPUT, // BLOB
+ 0x21, 2592, 1944, OUTPUT, // BLOB
+ 0x21, 2048, 1536, OUTPUT, // BLOB
+ 0x21, 1920, 1080, OUTPUT, // BLOB
+ 0x22, 640, 480, OUTPUT, // IMPLEMENTATION_DEFINED
+ 0x20, 320, 240, INPUT, // RAW16
+ };
+ Key<StreamConfiguration[]> configKey =
+ CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS;
+ mMetadata.writeValues(configKey.getTag(),
+ toByteArray(rawAvailableStreamConfigs));
+
+ //
+ // android.scaler.availableMinFrameDurations (int x n x 4 array)
+ //
+ long[] expectedAvailableMinDurations = new long[] {
+ 0x20, 3280, 2464, 33333331, // RAW16
+ 0x23, 3264, 2448, 33333332, // YCbCr_420_888
+ 0x23, 3200, 2400, 33333333, // YCbCr_420_888
+ 0x100, 3264, 2448, 33333334, // ImageFormat.JPEG
+ 0x100, 3200, 2400, 33333335, // ImageFormat.JPEG
+ 0x100, 2592, 1944, 33333336, // ImageFormat.JPEG
+ 0x100, 2048, 1536, 33333337, // ImageFormat.JPEG
+ 0x100, 1920, 1080, 33333338 // ImageFormat.JPEG
+ };
+ long[] rawAvailableMinDurations = new long[] {
+ 0x20, 3280, 2464, 33333331, // RAW16
+ 0x23, 3264, 2448, 33333332, // YCbCr_420_888
+ 0x23, 3200, 2400, 33333333, // YCbCr_420_888
+ 0x21, 3264, 2448, 33333334, // BLOB
+ 0x21, 3200, 2400, 33333335, // BLOB
+ 0x21, 2592, 1944, 33333336, // BLOB
+ 0x21, 2048, 1536, 33333337, // BLOB
+ 0x21, 1920, 1080, 33333338 // BLOB
+ };
+ Key<StreamConfigurationDuration[]> durationKey =
+ CameraCharacteristics.SCALER_AVAILABLE_MIN_FRAME_DURATIONS;
+ mMetadata.writeValues(durationKey.getTag(),
+ toByteArray(rawAvailableMinDurations));
+
+ //
+ // android.scaler.availableStallDurations (int x n x 4 array)
+ //
+ long[] expectedAvailableStallDurations = new long[] {
+ 0x20, 3280, 2464, 0, // RAW16
+ 0x23, 3264, 2448, 0, // YCbCr_420_888
+ 0x23, 3200, 2400, 0, // YCbCr_420_888
+ 0x100, 3264, 2448, 33333334, // ImageFormat.JPEG
+ 0x100, 3200, 2400, 33333335, // ImageFormat.JPEG
+ 0x100, 2592, 1944, 33333336, // ImageFormat.JPEG
+ 0x100, 2048, 1536, 33333337, // ImageFormat.JPEG
+ 0x100, 1920, 1080, 33333338 // ImageFormat.JPEG
+ };
+ // Note: RAW16 and YUV_420_888 omitted intentionally; omitted values should default to 0
+ long[] rawAvailableStallDurations = new long[] {
+ 0x21, 3264, 2448, 33333334, // BLOB
+ 0x21, 3200, 2400, 33333335, // BLOB
+ 0x21, 2592, 1944, 33333336, // BLOB
+ 0x21, 2048, 1536, 33333337, // BLOB
+ 0x21, 1920, 1080, 33333338 // BLOB
+ };
+ Key<StreamConfigurationDuration[]> stallDurationKey =
+ CameraCharacteristics.SCALER_AVAILABLE_STALL_DURATIONS;
+ mMetadata.writeValues(stallDurationKey.getTag(),
+ toByteArray(rawAvailableStallDurations));
+
+ //
+ // android.scaler.streamConfigurationMap (synthetic as StreamConfigurationMap)
+ //
+ StreamConfigurationMap streamConfigMap = mMetadata.get(
+ CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+
+ // Inputs
+ checkStreamConfigurationMapByFormatSize(
+ streamConfigMap, ImageFormat.RAW_SENSOR, 320, 240, /*output*/false);
+
+ // Outputs
+ checkStreamConfigurationMapByFormatSize(
+ streamConfigMap, HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED, 640, 480, /*output*/true);
+ checkStreamConfigurationMapByFormatSize(
+ streamConfigMap, ImageFormat.JPEG, 1920, 1080, /*output*/true);
+ checkStreamConfigurationMapByFormatSize(
+ streamConfigMap, ImageFormat.JPEG, 2048, 1536, /*output*/true);
+ checkStreamConfigurationMapByFormatSize(
+ streamConfigMap, ImageFormat.JPEG, 2592, 1944, /*output*/true);
+ checkStreamConfigurationMapByFormatSize(
+ streamConfigMap, ImageFormat.JPEG, 3200, 2400, /*output*/true);
+ checkStreamConfigurationMapByFormatSize(
+ streamConfigMap, ImageFormat.YUV_420_888, 3200, 2400, /*output*/true);
+ checkStreamConfigurationMapByFormatSize(
+ streamConfigMap, ImageFormat.YUV_420_888, 3264, 2448, /*output*/true);
+ checkStreamConfigurationMapByFormatSize(
+ streamConfigMap, ImageFormat.RAW_SENSOR, 3280, 2464, /*output*/true);
+
+ // Min Frame Durations
+
+ final int DURATION_TUPLE_SIZE = 4;
+ for (int i = 0; i < expectedAvailableMinDurations.length; i += DURATION_TUPLE_SIZE) {
+ checkStreamConfigurationMapDurationByFormatSize(
+ streamConfigMap,
+ (int)expectedAvailableMinDurations[i],
+ (int)expectedAvailableMinDurations[i+1],
+ (int)expectedAvailableMinDurations[i+2],
+ Duration.MinFrame,
+ expectedAvailableMinDurations[i+3]);
+ }
+
+ // Stall Frame Durations
+
+ for (int i = 0; i < expectedAvailableStallDurations.length; i += DURATION_TUPLE_SIZE) {
+ checkStreamConfigurationMapDurationByFormatSize(
+ streamConfigMap,
+ (int)expectedAvailableStallDurations[i],
+ (int)expectedAvailableStallDurations[i+1],
+ (int)expectedAvailableStallDurations[i+2],
+ Duration.Stall,
+ expectedAvailableStallDurations[i+3]);
+ }
+ }
+
+ private static void checkStreamConfigurationMapByFormatSize(StreamConfigurationMap configMap,
+ int format, int width, int height,
+ boolean output) {
+
+ /** arbitrary class for which StreamConfigurationMap#isOutputSupportedFor(Class) is true */
+ final Class<?> IMPLEMENTATION_DEFINED_OUTPUT_CLASS = SurfaceTexture.class;
+
+ android.util.Size[] sizes;
+ int[] formats;
+
+ if (output) {
+ if (format == HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED) {
+ sizes = configMap.getOutputSizes(IMPLEMENTATION_DEFINED_OUTPUT_CLASS);
+ // in this case the 'is output format supported' is vacuously true
+ formats = new int[] { HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED };
+ } else {
+ sizes = configMap.getOutputSizes(format);
+ formats = configMap.getOutputFormats();
+ assertTrue("Format must be supported by stream configuration map",
+ configMap.isOutputSupportedFor(format));
+ }
+ } else {
+ // NOTE: No function to do input sizes from IMPL_DEFINED, so it would just fail for that
+ sizes = configMap.getInputSizes(format);
+ formats = configMap.getInputFormats();
+ }
+
+ android.util.Size expectedSize = new android.util.Size(width, height);
+
+ assertArrayContains(format, formats);
+ assertArrayContains(expectedSize, sizes);
+ }
+
+ private enum Duration {
+ MinFrame,
+ Stall
+ }
+
+ private static void checkStreamConfigurationMapDurationByFormatSize(
+ StreamConfigurationMap configMap,
+ int format, int width, int height, Duration durationKind, long expectedDuration) {
+
+ /** arbitrary class for which StreamConfigurationMap#isOutputSupportedFor(Class) is true */
+ final Class<?> IMPLEMENTATION_DEFINED_OUTPUT_CLASS = SurfaceTexture.class;
+
+ long actualDuration;
+
+ android.util.Size size = new android.util.Size(width, height);
+ switch (durationKind) {
+ case MinFrame:
+ if (format == HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED) {
+ actualDuration = configMap.getOutputMinFrameDuration(
+ IMPLEMENTATION_DEFINED_OUTPUT_CLASS, size);
+ } else {
+ actualDuration = configMap.getOutputMinFrameDuration(format, size);
+ }
+
+ break;
+ case Stall:
+ if (format == HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED) {
+ actualDuration = configMap.getOutputStallDuration(
+ IMPLEMENTATION_DEFINED_OUTPUT_CLASS, size);
+ } else {
+ actualDuration = configMap.getOutputStallDuration(format, size);
+ }
+
+ break;
+ default:
+ throw new AssertionError();
+ }
+
+ assertEquals("Expected " + durationKind + " to match actual value", expectedDuration,
+ actualDuration);
+ }
+
+ /**
* Validate metadata array tag read/write override.
*
* <p>Only support long and int array for now, can be easily extend to support other
* primitive arrays.</p>
*/
- private <T> void validateArrayMetadataReadWriteOverride(Key<T> key, T writeValues,
- T readValues, int tag) {
- Class<?> type = writeValues.getClass();
+ private <T> void validateArrayMetadataReadWriteOverride(Key<T> key, T expectedWriteValues,
+ T expectedReadValues, int tag) {
+ Class<?> type = expectedWriteValues.getClass();
if (!type.isArray()) {
throw new IllegalArgumentException("This function expects an key with array type");
} else if (type != int[].class && type != long[].class) {
@@ -1030,13 +1249,13 @@ public class CameraMetadataTest extends junit.framework.TestCase {
}
// Write
- mMetadata.set(key, writeValues);
+ mMetadata.set(key, expectedWriteValues);
byte[] readOutValues = mMetadata.readValues(tag);
ByteBuffer bf = ByteBuffer.wrap(readOutValues).order(ByteOrder.nativeOrder());
- int readValuesLength = Array.getLength(readValues);
+ int readValuesLength = Array.getLength(expectedReadValues);
int readValuesNumBytes = readValuesLength * 4;
if (type == long[].class) {
readValuesNumBytes = readValuesLength * 8;
@@ -1045,9 +1264,9 @@ public class CameraMetadataTest extends junit.framework.TestCase {
assertEquals(readValuesNumBytes, readOutValues.length);
for (int i = 0; i < readValuesLength; ++i) {
if (type == int[].class) {
- assertEquals(Array.getInt(readValues, i), bf.getInt());
+ assertEquals(Array.getInt(expectedReadValues, i), bf.getInt());
} else if (type == long[].class) {
- assertEquals(Array.getLong(readValues, i), bf.getLong());
+ assertEquals(Array.getLong(expectedReadValues, i), bf.getLong());
}
}
@@ -1057,16 +1276,16 @@ public class CameraMetadataTest extends junit.framework.TestCase {
ByteBuffer.wrap(readOutValuesAsByteArray).order(ByteOrder.nativeOrder());
for (int i = 0; i < readValuesLength; ++i) {
if (type == int[].class) {
- readOutValuesByteBuffer.putInt(Array.getInt(readValues, i));
+ readOutValuesByteBuffer.putInt(Array.getInt(expectedReadValues, i));
} else if (type == long[].class) {
- readOutValuesByteBuffer.putLong(Array.getLong(readValues, i));
+ readOutValuesByteBuffer.putLong(Array.getLong(expectedReadValues, i));
}
}
mMetadata.writeValues(tag, readOutValuesAsByteArray);
T result = mMetadata.get(key);
assertNotNull(key.getName() + " result shouldn't be null", result);
- assertArrayEquals(writeValues, result);
+ assertArrayEquals(expectedWriteValues, result);
}
// TODO: move somewhere else
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>