summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
Diffstat (limited to 'media')
-rw-r--r--media/java/android/media/AmrInputStream.java2
-rw-r--r--media/java/android/media/AsyncPlayer.java2
-rw-r--r--media/java/android/media/AudioFormat.java12
-rw-r--r--media/java/android/media/AudioManager.java1
-rw-r--r--media/java/android/media/AudioRecord.java16
-rw-r--r--media/java/android/media/AudioService.java113
-rw-r--r--media/java/android/media/AudioSystem.java5
-rw-r--r--media/java/android/media/AudioTrack.java271
-rw-r--r--media/java/android/media/EncoderCapabilities.java1
-rw-r--r--media/java/android/media/IAudioService.aidl3
-rw-r--r--media/java/android/media/IMediaHTTPConnection.aidl34
-rw-r--r--media/java/android/media/IMediaHTTPService.aidl27
-rw-r--r--media/java/android/media/Image.java12
-rw-r--r--media/java/android/media/ImageReader.java3
-rw-r--r--media/java/android/media/MediaCodec.java103
-rw-r--r--media/java/android/media/MediaDrm.java2
-rw-r--r--media/java/android/media/MediaExtractor.java22
-rw-r--r--media/java/android/media/MediaFile.java5
-rw-r--r--media/java/android/media/MediaFocusControl.java880
-rw-r--r--media/java/android/media/MediaHTTPConnection.java274
-rw-r--r--media/java/android/media/MediaHTTPService.java44
-rw-r--r--media/java/android/media/MediaMetadataEditor.java1
-rw-r--r--media/java/android/media/MediaMetadataRetriever.java10
-rw-r--r--media/java/android/media/MediaMuxer.java21
-rw-r--r--media/java/android/media/MediaPlayer.java142
-rw-r--r--media/java/android/media/MediaRecorder.java35
-rw-r--r--media/java/android/media/MediaScanner.java66
-rw-r--r--media/java/android/media/Metadata.java1
-rw-r--r--media/java/android/media/MiniThumbFile.java1
-rw-r--r--media/java/android/media/PlayerRecord.java354
-rw-r--r--media/java/android/media/Rating.java8
-rw-r--r--media/java/android/media/RemoteControlClient.java2
-rw-r--r--media/java/android/media/RemoteController.java5
-rw-r--r--media/java/android/media/ResampleInputStream.java2
-rw-r--r--media/java/android/media/RingtoneManager.java1
-rw-r--r--media/java/android/media/SoundPool.java44
-rw-r--r--media/java/android/media/SubtitleData.java1
-rw-r--r--media/java/android/media/ThumbnailUtils.java6
-rw-r--r--media/java/android/media/WebVttRenderer.java51
-rw-r--r--media/java/android/media/audiofx/AudioEffect.java1
-rw-r--r--media/java/android/media/audiofx/BassBoost.java7
-rw-r--r--media/java/android/media/audiofx/EnvironmentalReverb.java8
-rw-r--r--media/java/android/media/audiofx/Equalizer.java7
-rw-r--r--media/java/android/media/audiofx/PresetReverb.java8
-rw-r--r--media/java/android/media/audiofx/Virtualizer.java7
-rw-r--r--media/java/android/media/audiofx/Visualizer.java1
-rw-r--r--media/java/android/media/routeprovider/IRouteConnection.aidl28
-rw-r--r--media/java/android/media/routeprovider/IRouteProvider.aidl36
-rw-r--r--media/java/android/media/routeprovider/IRouteProviderCallback.aidl32
-rw-r--r--media/java/android/media/routeprovider/RouteConnection.java164
-rw-r--r--media/java/android/media/routeprovider/RouteInterfaceHandler.java245
-rw-r--r--media/java/android/media/routeprovider/RoutePlaybackControlsHandler.java221
-rw-r--r--media/java/android/media/routeprovider/RouteProviderService.java227
-rw-r--r--media/java/android/media/routeprovider/RouteRequest.aidl18
-rw-r--r--media/java/android/media/routeprovider/RouteRequest.java96
-rw-r--r--media/java/android/media/session/ISession.aidl49
-rw-r--r--media/java/android/media/session/ISessionCallback.aidl47
-rw-r--r--media/java/android/media/session/ISessionController.aidl52
-rw-r--r--media/java/android/media/session/ISessionControllerCallback.aidl33
-rw-r--r--media/java/android/media/session/ISessionManager.aidl28
-rw-r--r--media/java/android/media/session/MediaMetadata.aidl18
-rw-r--r--media/java/android/media/session/MediaMetadata.java404
-rw-r--r--media/java/android/media/session/PlaybackState.aidl18
-rw-r--r--media/java/android/media/session/PlaybackState.java358
-rw-r--r--media/java/android/media/session/Route.java99
-rw-r--r--media/java/android/media/session/RouteCommand.aidl18
-rw-r--r--media/java/android/media/session/RouteCommand.java117
-rw-r--r--media/java/android/media/session/RouteEvent.aidl18
-rw-r--r--media/java/android/media/session/RouteEvent.java120
-rw-r--r--media/java/android/media/session/RouteInfo.aidl18
-rw-r--r--media/java/android/media/session/RouteInfo.java233
-rw-r--r--media/java/android/media/session/RouteInterface.java212
-rw-r--r--media/java/android/media/session/RouteOptions.aidl18
-rw-r--r--media/java/android/media/session/RouteOptions.java163
-rw-r--r--media/java/android/media/session/RoutePlaybackControls.java161
-rw-r--r--media/java/android/media/session/Session.java666
-rw-r--r--media/java/android/media/session/SessionController.java365
-rw-r--r--media/java/android/media/session/SessionInfo.java82
-rw-r--r--media/java/android/media/session/SessionManager.java90
-rw-r--r--media/java/android/media/session/SessionToken.aidl18
-rw-r--r--media/java/android/media/session/SessionToken.java66
-rw-r--r--media/java/android/media/session/TransportController.java342
-rw-r--r--media/java/android/media/session/TransportPerformer.java357
-rw-r--r--media/java/android/media/videoeditor/MediaArtistNativeHelper.java1
-rwxr-xr-xmedia/java/android/mtp/MtpDatabase.java55
-rw-r--r--media/java/android/mtp/MtpDevice.java2
-rw-r--r--media/java/android/mtp/MtpPropertyGroup.java1
-rw-r--r--media/java/android/mtp/MtpServer.java6
-rw-r--r--media/jni/Android.mk5
-rw-r--r--media/jni/android_media_ImageReader.cpp30
-rw-r--r--media/jni/android_media_MediaCodec.cpp164
-rw-r--r--media/jni/android_media_MediaCodec.h21
-rw-r--r--media/jni/android_media_MediaExtractor.cpp27
-rw-r--r--media/jni/android_media_MediaExtractor.h2
-rw-r--r--media/jni/android_media_MediaHTTPConnection.cpp179
-rw-r--r--media/jni/android_media_MediaHTTPConnection.h57
-rw-r--r--media/jni/android_media_MediaMetadataRetriever.cpp19
-rw-r--r--media/jni/android_media_MediaMuxer.cpp2
-rw-r--r--media/jni/android_media_MediaPlayer.cpp95
-rw-r--r--media/jni/android_media_MediaRecorder.cpp21
-rw-r--r--media/jni/android_mtp_MtpDatabase.cpp175
-rw-r--r--media/jni/android_mtp_MtpServer.cpp14
-rw-r--r--media/jni/mediaeditor/Android.mk1
-rw-r--r--media/jni/soundpool/android_media_SoundPool_SoundPoolImpl.cpp4
-rw-r--r--media/lib/remotedisplay/java/com/android/media/remotedisplay/RemoteDisplay.java1
-rw-r--r--media/mca/effect/java/android/media/effect/EffectContext.java3
-rw-r--r--media/mca/effect/java/android/media/effect/EffectFactory.java1
-rw-r--r--media/mca/effect/java/android/media/effect/FilterEffect.java3
-rw-r--r--media/mca/effect/java/android/media/effect/FilterGraphEffect.java4
-rw-r--r--media/mca/effect/java/android/media/effect/SingleFilterEffect.java3
-rw-r--r--media/mca/effect/java/android/media/effect/SizeChangeEffect.java6
-rw-r--r--media/mca/effect/java/android/media/effect/effects/CropEffect.java1
-rw-r--r--media/mca/filterfw/java/android/filterfw/FilterFunctionEnvironment.java1
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/AsyncRunner.java8
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/Filter.java1
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/Frame.java1
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/NativeFrame.java2
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/OneShotScheduler.java1
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/SerializedFrame.java4
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/SimpleFrame.java2
-rw-r--r--media/mca/filterfw/native/core/gl_env.cpp8
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/base/CallbackFilter.java5
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/base/FrameBranch.java1
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/base/FrameFetch.java2
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/base/FrameStore.java1
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/base/GLTextureSource.java3
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/base/GLTextureTarget.java3
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/base/InputStreamSource.java1
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/base/NullFilter.java2
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/base/ObjectSource.java3
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/base/OutputStreamTarget.java1
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/base/RetargetFilter.java2
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/AlphaBlendFilter.java9
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/AutoFixFilter.java3
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapOverlayFilter.java6
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapSource.java6
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/BlackWhiteFilter.java3
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/BlendFilter.java9
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/BrightnessFilter.java5
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/ColorTemperatureFilter.java4
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/ContrastFilter.java7
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/CropFilter.java6
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/CropRectFilter.java5
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/CrossProcessFilter.java5
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/DocumentaryFilter.java3
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/DrawOverlayFilter.java6
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/DrawRectFilter.java3
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/DuotoneFilter.java3
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/FillLightFilter.java4
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/FisheyeFilter.java6
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/FixedRotationFilter.java1
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/FlipFilter.java4
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/GrainFilter.java5
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/ImageCombineFilter.java6
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/ImageEncoder.java5
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/ImageSlicer.java2
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/ImageStitcher.java4
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/Invert.java4
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/LomoishFilter.java3
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/NegativeFilter.java3
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/PosterizeFilter.java3
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/RedEyeFilter.java5
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/ResizeFilter.java3
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/RotateFilter.java5
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/SaturateFilter.java3
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/SepiaFilter.java3
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/SharpenFilter.java5
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/SimpleImageFilter.java6
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/StraightenFilter.java5
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/TintFilter.java3
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/ToGrayFilter.java9
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/ToPackedGrayFilter.java2
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBAFilter.java5
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBFilter.java5
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/VignetteFilter.java3
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/text/StringSource.java2
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/ui/SurfaceRenderFilter.java8
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/ui/SurfaceTargetFilter.java8
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/videoproc/BackDropperFilter.java2
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/videosink/MediaEncoderFilter.java9
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/videosink/MediaRecorderStopException.java1
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/videosrc/CameraSource.java7
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/videosrc/MediaSource.java8
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureSource.java11
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureTarget.java10
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java2
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaProfileReader.java4
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediarecorder/MediaRecorderTest.java308
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java48
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java89
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java138
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java12
-rw-r--r--media/tests/omxjpegdecoder/Android.mk6
-rw-r--r--media/tests/omxjpegdecoder/SkOmxPixelRef.cpp46
-rw-r--r--media/tests/omxjpegdecoder/SkOmxPixelRef.h52
-rw-r--r--media/tests/omxjpegdecoder/StreamSource.h2
-rw-r--r--media/tests/omxjpegdecoder/omx_jpeg_decoder.cpp16
-rw-r--r--media/tests/omxjpegdecoder/omx_jpeg_decoder.h2
-rw-r--r--media/tests/players/invoke_mock_media_player.cpp2
199 files changed, 8145 insertions, 1657 deletions
diff --git a/media/java/android/media/AmrInputStream.java b/media/java/android/media/AmrInputStream.java
index e2115e4..f90f1e2 100644
--- a/media/java/android/media/AmrInputStream.java
+++ b/media/java/android/media/AmrInputStream.java
@@ -16,8 +16,6 @@
package android.media;
-import android.util.Log;
-
import java.io.InputStream;
import java.io.IOException;
diff --git a/media/java/android/media/AsyncPlayer.java b/media/java/android/media/AsyncPlayer.java
index 804528e..14b199e 100644
--- a/media/java/android/media/AsyncPlayer.java
+++ b/media/java/android/media/AsyncPlayer.java
@@ -22,8 +22,6 @@ import android.os.PowerManager;
import android.os.SystemClock;
import android.util.Log;
-import java.io.IOException;
-import java.lang.IllegalStateException;
import java.util.LinkedList;
/**
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index 49f498e..04c6e97 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -31,7 +31,8 @@ public class AudioFormat {
public static final int ENCODING_INVALID = 0;
/** Default audio data format */
public static final int ENCODING_DEFAULT = 1;
- // These two values must be kept in sync with JNI code for AudioTrack, AudioRecord
+
+ // These two values must be kept in sync with core/jni/android_media_AudioFormat.h
/** Audio data format: PCM 16 bit per sample. Guaranteed to be supported by devices. */
public static final int ENCODING_PCM_16BIT = 2;
/** Audio data format: PCM 8 bit per sample. Not guaranteed to be supported by devices. */
@@ -55,7 +56,7 @@ public class AudioFormat {
/** Default audio channel mask */
public static final int CHANNEL_OUT_DEFAULT = 1;
- // Channel mask definitions below are translated to the native values defined in
+ // Output channel mask definitions below are translated to the native values defined in
// in /system/core/include/system/audio.h in the JNI code of AudioTrack
public static final int CHANNEL_OUT_FRONT_LEFT = 0x4;
public static final int CHANNEL_OUT_FRONT_RIGHT = 0x8;
@@ -93,17 +94,21 @@ public class AudioFormat {
CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_BACK_CENTER);
public static final int CHANNEL_OUT_5POINT1 = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT |
CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_LOW_FREQUENCY | CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT);
+ // different from AUDIO_CHANNEL_OUT_7POINT1
public static final int CHANNEL_OUT_7POINT1 = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT |
CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_LOW_FREQUENCY | CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT |
CHANNEL_OUT_FRONT_LEFT_OF_CENTER | CHANNEL_OUT_FRONT_RIGHT_OF_CENTER);
/** @hide */
+ // matches AUDIO_CHANNEL_OUT_7POINT1
public static final int CHANNEL_OUT_7POINT1_SURROUND = (
CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_FRONT_RIGHT |
CHANNEL_OUT_SIDE_LEFT | CHANNEL_OUT_SIDE_RIGHT |
CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT |
CHANNEL_OUT_LOW_FREQUENCY);
+ // CHANNEL_OUT_ALL is not yet defined; if added then it should match AUDIO_CHANNEL_OUT_ALL
public static final int CHANNEL_IN_DEFAULT = 1;
+ // These directly match native
public static final int CHANNEL_IN_LEFT = 0x4;
public static final int CHANNEL_IN_RIGHT = 0x8;
public static final int CHANNEL_IN_FRONT = 0x10;
@@ -120,5 +125,8 @@ public class AudioFormat {
public static final int CHANNEL_IN_VOICE_DNLINK = 0x8000;
public static final int CHANNEL_IN_MONO = CHANNEL_IN_FRONT;
public static final int CHANNEL_IN_STEREO = (CHANNEL_IN_LEFT | CHANNEL_IN_RIGHT);
+ /** @hide */
+ public static final int CHANNEL_IN_FRONT_BACK = CHANNEL_IN_FRONT | CHANNEL_IN_BACK;
+ // CHANNEL_IN_ALL is not yet defined; if added then it should match AUDIO_CHANNEL_IN_ALL
}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index d652cae..8ae06e0 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -26,7 +26,6 @@ import android.content.Context;
import android.content.Intent;
import android.media.RemoteController.OnClientUpdateListener;
import android.os.Binder;
-import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index 461b52f..a4891f8 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -179,13 +179,16 @@ public class AudioRecord
/**
* Audio session ID
*/
- private int mSessionId = 0;
+ private int mSessionId = AudioSystem.AUDIO_SESSION_ALLOCATE;
//---------------------------------------------------------
// Constructor, Finalize
//--------------------
/**
* Class constructor.
+ * Though some invalid parameters will result in an {@link IllegalArgumentException} exception,
+ * other errors do not. Thus you should call {@link #getState()} immediately after construction
+ * to confirm that the object is usable.
* @param audioSource the recording source. See {@link MediaRecorder.AudioSource} for
* recording source definitions.
* @param sampleRateInHz the sample rate expressed in Hertz. 44100Hz is currently the only
@@ -221,7 +224,7 @@ public class AudioRecord
// native initialization
int[] session = new int[1];
- session[0] = 0;
+ session[0] = AudioSystem.AUDIO_SESSION_ALLOCATE;
//TODO: update native initialization when information about hardware init failure
// due to capture device already open is available.
int initResult = native_setup( new WeakReference<AudioRecord>(this),
@@ -239,7 +242,7 @@ public class AudioRecord
// Convenience method for the constructor's parameter checks.
- // This is where constructor IllegalArgumentException-s are thrown
+ // This and audioBuffSizeCheck are where constructor IllegalArgumentException-s are thrown
// postconditions:
// mRecordSource is valid
// mChannelCount is valid
@@ -247,7 +250,8 @@ public class AudioRecord
// mAudioFormat is valid
// mSampleRate is valid
private void audioParamCheck(int audioSource, int sampleRateInHz,
- int channelConfig, int audioFormat) {
+ int channelConfig, int audioFormat)
+ throws IllegalArgumentException {
//--------------
// audio source
@@ -311,7 +315,7 @@ public class AudioRecord
// mAudioFormat is AudioFormat.ENCODING_PCM_8BIT OR AudioFormat.ENCODING_PCM_16BIT
// postcondition:
// mNativeBufferSizeInBytes is valid (multiple of frame size, positive)
- private void audioBuffSizeCheck(int audioBufferSize) {
+ private void audioBuffSizeCheck(int audioBufferSize) throws IllegalArgumentException {
// NB: this section is only valid with PCM data.
// To update when supporting compressed formats
int frameSizeInBytes = mChannelCount
@@ -800,7 +804,7 @@ public class AudioRecord
//--------------------
private native final int native_setup(Object audiorecord_this,
- int recordSource, int sampleRate, int nbChannels, int audioFormat,
+ int recordSource, int sampleRate, int channelMask, int audioFormat,
int buffSizeInBytes, int[] sessionId);
private native final void native_finalize();
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 92474df..4513ead 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -21,20 +21,17 @@ import static android.media.AudioManager.RINGER_MODE_NORMAL;
import static android.media.AudioManager.RINGER_MODE_SILENT;
import static android.media.AudioManager.RINGER_MODE_VIBRATE;
-import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.AppOpsManager;
import android.app.KeyguardManager;
import android.app.PendingIntent;
-import android.app.PendingIntent.CanceledException;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
-import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -46,14 +43,11 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.database.ContentObserver;
+import android.hardware.usb.UsbManager;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
-import android.net.Uri;
-import android.net.http.CertificateChainValidator;
-import android.net.http.SslError;
import android.os.Binder;
import android.os.Build;
-import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
@@ -68,9 +62,6 @@ import android.os.UserHandle;
import android.os.Vibrator;
import android.provider.Settings;
import android.provider.Settings.System;
-import android.speech.RecognizerIntent;
-import android.telephony.PhoneStateListener;
-import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
@@ -83,22 +74,18 @@ import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParserException;
-import java.io.ByteArrayInputStream;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
-import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
-import java.util.Stack;
/**
* The implementation of the volume manager service.
@@ -120,8 +107,6 @@ public class AudioService extends IAudioService.Stub {
protected static final boolean DEBUG_RC = false;
/** Debug volumes */
protected static final boolean DEBUG_VOL = false;
- /** Debug cert verification */
- private static final boolean DEBUG_CERTS = false;
/** How long to delay before persisting a change in volume/ringer mode. */
private static final int PERSIST_DELAY = 500;
@@ -274,7 +259,7 @@ public class AudioService extends IAudioService.Stub {
private final boolean mUseFixedVolume;
// stream names used by dumpStreamStates()
- private final String[] STREAM_NAMES = new String[] {
+ private static final String[] STREAM_NAMES = new String[] {
"STREAM_VOICE_CALL",
"STREAM_SYSTEM",
"STREAM_RING",
@@ -544,6 +529,7 @@ public class AudioService extends IAudioService.Stub {
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
+ intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
// TODO merge orientation and rotation
@@ -630,6 +616,12 @@ public class AudioService extends IAudioService.Stub {
pw.println(Integer.toHexString(mMuteAffectedStreams));
}
+ /** @hide */
+ public static String streamToString(int stream) {
+ if (stream >= 0 && stream < STREAM_NAMES.length) return STREAM_NAMES[stream];
+ if (stream == AudioManager.USE_DEFAULT_STREAM_TYPE) return "USE_DEFAULT_STREAM_TYPE";
+ return "UNKNOWN_STREAM_" + stream;
+ }
private void updateStreamVolumeAlias(boolean updateVolumes) {
int dtmfStreamAlias;
@@ -1699,7 +1691,7 @@ public class AudioService extends IAudioService.Stub {
private static final String ASSET_FILE_VERSION = "1.0";
private static final String GROUP_TOUCH_SOUNDS = "touch_sounds";
- private static final int SOUND_EFECTS_LOAD_TIMEOUT_MS = 5000;
+ private static final int SOUND_EFFECTS_LOAD_TIMEOUT_MS = 5000;
class LoadSoundEffectReply {
public int mStatus = 1;
@@ -1811,7 +1803,7 @@ public class AudioService extends IAudioService.Stub {
sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SENDMSG_QUEUE, 0, 0, reply, 0);
while ((reply.mStatus == 1) && (attempts-- > 0)) {
try {
- reply.wait(SOUND_EFECTS_LOAD_TIMEOUT_MS);
+ reply.wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS);
} catch (InterruptedException e) {
Log.w(TAG, "loadSoundEffects Interrupted while waiting sound pool loaded.");
}
@@ -3299,7 +3291,7 @@ public class AudioService extends IAudioService.Stub {
while ((mSoundPoolCallBack == null) && (attempts-- > 0)) {
try {
// Wait for mSoundPoolCallBack to be set by the other thread
- mSoundEffectsLock.wait(SOUND_EFECTS_LOAD_TIMEOUT_MS);
+ mSoundEffectsLock.wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS);
} catch (InterruptedException e) {
Log.w(TAG, "Interrupted while waiting sound pool listener thread.");
}
@@ -3363,7 +3355,7 @@ public class AudioService extends IAudioService.Stub {
status = 1;
while ((status == 1) && (attempts-- > 0)) {
try {
- mSoundEffectsLock.wait(SOUND_EFECTS_LOAD_TIMEOUT_MS);
+ mSoundEffectsLock.wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS);
status = mSoundPoolCallBack.status();
} catch (InterruptedException e) {
Log.w(TAG, "Interrupted while waiting sound pool callback.");
@@ -3985,7 +3977,8 @@ public class AudioService extends IAudioService.Stub {
(device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE))) {
setBluetoothA2dpOnInt(true);
}
- boolean isUsb = ((device & AudioSystem.DEVICE_OUT_ALL_USB) != 0);
+ boolean isUsb = ((device & AudioSystem.DEVICE_OUT_ALL_USB) != 0) ||
+ ((device & AudioSystem.DEVICE_IN_ALL_USB) != 0);
handleDeviceConnection((state == 1), device, (isUsb ? name : ""));
if (state != 0) {
if ((device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) ||
@@ -4090,20 +4083,41 @@ public class AudioService extends IAudioService.Stub {
}
}
}
- } else if (action.equals(Intent.ACTION_USB_AUDIO_ACCESSORY_PLUG) ||
- action.equals(Intent.ACTION_USB_AUDIO_DEVICE_PLUG)) {
+ } else if (action.equals(Intent.ACTION_USB_AUDIO_ACCESSORY_PLUG)) {
state = intent.getIntExtra("state", 0);
+
int alsaCard = intent.getIntExtra("card", -1);
int alsaDevice = intent.getIntExtra("device", -1);
+
String params = (alsaCard == -1 && alsaDevice == -1 ? ""
: "card=" + alsaCard + ";device=" + alsaDevice);
- device = action.equals(Intent.ACTION_USB_AUDIO_ACCESSORY_PLUG) ?
- AudioSystem.DEVICE_OUT_USB_ACCESSORY : AudioSystem.DEVICE_OUT_USB_DEVICE;
- Log.v(TAG, "Broadcast Receiver: Got "
- + (action.equals(Intent.ACTION_USB_AUDIO_ACCESSORY_PLUG) ?
- "ACTION_USB_AUDIO_ACCESSORY_PLUG" : "ACTION_USB_AUDIO_DEVICE_PLUG")
- + ", state = " + state + ", card: " + alsaCard + ", device: " + alsaDevice);
+
+ // Playback Device
+ device = AudioSystem.DEVICE_OUT_USB_ACCESSORY;
setWiredDeviceConnectionState(device, state, params);
+ } else if (action.equals(Intent.ACTION_USB_AUDIO_DEVICE_PLUG)) {
+ state = intent.getIntExtra("state", 0);
+
+ int alsaCard = intent.getIntExtra("card", -1);
+ int alsaDevice = intent.getIntExtra("device", -1);
+ boolean hasPlayback = intent.getBooleanExtra("hasPlayback", false);
+ boolean hasCapture = intent.getBooleanExtra("hasCapture", false);
+ boolean hasMIDI = intent.getBooleanExtra("hasMIDI", false);
+
+ String params = (alsaCard == -1 && alsaDevice == -1 ? ""
+ : "card=" + alsaCard + ";device=" + alsaDevice);
+
+ // Playback Device
+ if (hasPlayback) {
+ device = AudioSystem.DEVICE_OUT_USB_DEVICE;
+ setWiredDeviceConnectionState(device, state, params);
+ }
+
+ // Capture Device
+ if (hasCapture) {
+ device = AudioSystem.DEVICE_IN_USB_DEVICE;
+ setWiredDeviceConnectionState(device, state, params);
+ }
} else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
boolean broadcast = false;
int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
@@ -4209,7 +4223,7 @@ public class AudioService extends IAudioService.Stub {
mStreamStates[AudioSystem.STREAM_MUSIC], 0);
}
}
- }
+ } // end class AudioServiceBroadcastReceiver
//==========================================================================================
// RemoteControlDisplay / RemoteControlClient / Remote info
@@ -4587,43 +4601,6 @@ public class AudioService extends IAudioService.Stub {
}
}
- public int verifyX509CertChain(int numcerts, byte [] chain, String domain, String authType) {
-
- if (DEBUG_CERTS) {
- Log.v(TAG, "java side verify for "
- + numcerts + " certificates (" + chain.length + " bytes"
- + ")for "+ domain + "/" + authType);
- }
-
- byte[][] certChain = new byte[numcerts][];
-
- ByteBuffer buf = ByteBuffer.wrap(chain);
- for (int i = 0; i < numcerts; i++) {
- int certlen = buf.getInt();
- if (DEBUG_CERTS) {
- Log.i(TAG, "cert " + i +": " + certlen);
- }
- certChain[i] = new byte[certlen];
- buf.get(certChain[i]);
- }
-
- try {
- SslError err = CertificateChainValidator.verifyServerCertificates(certChain,
- domain, authType);
- if (DEBUG_CERTS) {
- Log.i(TAG, "verified: " + err);
- }
- if (err == null) {
- return -1;
- } else {
- return err.getPrimaryError();
- }
- } catch (Exception e) {
- Log.e(TAG, "failed to verify chain: " + e);
- }
- return SslError.SSL_INVALID;
- }
-
//==========================================================================================
// Camera shutter sound policy.
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 661b0fd..327c10c 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -103,6 +103,9 @@ public class AudioSystem
/** @deprecated */
@Deprecated public static final int ROUTE_ALL = 0xFFFFFFFF;
+ // Keep in sync with system/core/include/system/audio.h
+ public static final int AUDIO_SESSION_ALLOCATE = 0;
+
/*
* Checks whether the specified stream type is active.
*
@@ -285,6 +288,8 @@ public class AudioSystem
DEVICE_IN_USB_DEVICE |
DEVICE_IN_DEFAULT);
public static final int DEVICE_IN_ALL_SCO = DEVICE_IN_BLUETOOTH_SCO_HEADSET;
+ public static final int DEVICE_IN_ALL_USB = (DEVICE_IN_USB_ACCESSORY |
+ DEVICE_IN_USB_DEVICE);
// device states, must match AudioSystem::device_connection_state
public static final int DEVICE_STATE_UNAVAILABLE = 0;
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 01a6fc2..17840f2 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -16,13 +16,27 @@
package android.media;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
+import java.nio.ByteBuffer;
+import java.nio.NioUtils;
+import android.annotation.IntDef;
+import android.app.ActivityThread;
+import android.app.AppOpsManager;
+import android.content.Context;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.util.Log;
+import com.android.internal.app.IAppOpsService;
+
/**
* The AudioTrack class manages and plays a single audio resource for Java applications.
@@ -63,10 +77,14 @@ public class AudioTrack
//---------------------------------------------------------
// Constants
//--------------------
- /** Minimum value for a channel volume */
- private static final float VOLUME_MIN = 0.0f;
- /** Maximum value for a channel volume */
- private static final float VOLUME_MAX = 1.0f;
+ /** Minimum value for a linear gain or auxiliary effect level.
+ * This value must be exactly equal to 0.0f; do not change it.
+ */
+ private static final float GAIN_MIN = 0.0f;
+ /** Maximum value for a linear gain or auxiliary effect level.
+ * This value must be greater than or equal to 1.0f.
+ */
+ private static final float GAIN_MAX = 1.0f;
/** Minimum value for sample rate */
private static final int SAMPLE_RATE_HZ_MIN = 4000;
@@ -145,6 +163,28 @@ public class AudioTrack
private final static String TAG = "android.media.AudioTrack";
+ /** @hide */
+ @IntDef({
+ WRITE_BLOCKING,
+ WRITE_NON_BLOCKING
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface WriteMode {}
+
+ /**
+ * @hide CANDIDATE FOR PUBLIC API
+ * The write mode indicating the write operation will block until all data has been written,
+ * to be used in {@link #write(ByteBuffer, int, int, int)}.
+ */
+ public final static int WRITE_BLOCKING = 0;
+ /**
+ * @hide CANDIDATE FOR PUBLIC API
+ * The write mode indicating the write operation will return immediately after
+ * queuing as much audio data for playback as possible without blocking, to be used in
+ * {@link #write(ByteBuffer, int, int, int)}.
+ */
+ public final static int WRITE_NON_BLOCKING = 1;
+
//--------------------------------------------------------------------------
// Member variables
//--------------------
@@ -211,8 +251,11 @@ public class AudioTrack
/**
* Audio session ID
*/
- private int mSessionId = 0;
-
+ private int mSessionId = AudioSystem.AUDIO_SESSION_ALLOCATE;
+ /**
+ * Reference to the app-ops service.
+ */
+ private final IAppOpsService mAppOps;
//--------------------------------
// Used exclusively by native code
@@ -263,7 +306,7 @@ public class AudioTrack
int bufferSizeInBytes, int mode)
throws IllegalArgumentException {
this(streamType, sampleRateInHz, channelConfig, audioFormat,
- bufferSizeInBytes, mode, 0 /*session*/);
+ bufferSizeInBytes, mode, AudioSystem.AUDIO_SESSION_ALLOCATE);
}
/**
@@ -316,6 +359,9 @@ public class AudioTrack
audioBuffSizeCheck(bufferSizeInBytes);
+ IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
+ mAppOps = IAppOpsService.Stub.asInterface(b);
+
if (sessionId < 0) {
throw new IllegalArgumentException("Invalid audio session ID: "+sessionId);
}
@@ -506,21 +552,25 @@ public class AudioTrack
// Getters
//--------------------
/**
- * Returns the minimum valid volume value. Volume values set under this one will
- * be clamped at this value.
- * @return the minimum volume expressed as a linear attenuation.
+ * Returns the minimum gain value, which is the constant 0.0.
+ * Gain values less than 0.0 will be clamped to 0.0.
+ * <p>The word "volume" in the API name is historical; this is actually a linear gain.
+ * @return the minimum value, which is the constant 0.0.
*/
static public float getMinVolume() {
- return VOLUME_MIN;
+ return GAIN_MIN;
}
/**
- * Returns the maximum valid volume value. Volume values set above this one will
- * be clamped at this value.
- * @return the maximum volume expressed as a linear attenuation.
+ * Returns the maximum gain value, which is greater than or equal to 1.0.
+ * Gain values greater than the maximum will be clamped to the maximum.
+ * <p>The word "volume" in the API name is historical; this is actually a gain.
+ * expressed as a linear multiplier on sample values, where a maximum value of 1.0
+ * corresponds to a gain of 0 dB (sample values left unmodified).
+ * @return the maximum value, which is greater than or equal to 1.0.
*/
static public float getMaxVolume() {
- return VOLUME_MAX;
+ return GAIN_MAX;
}
/**
@@ -803,47 +853,67 @@ public class AudioTrack
}
+ private static float clampGainOrLevel(float gainOrLevel) {
+ if (Float.isNaN(gainOrLevel)) {
+ throw new IllegalArgumentException();
+ }
+ if (gainOrLevel < GAIN_MIN) {
+ gainOrLevel = GAIN_MIN;
+ } else if (gainOrLevel > GAIN_MAX) {
+ gainOrLevel = GAIN_MAX;
+ }
+ return gainOrLevel;
+ }
+
/**
- * Sets the specified left/right output volume values on the AudioTrack. Values are clamped
- * to the ({@link #getMinVolume()}, {@link #getMaxVolume()}) interval if outside this range.
- * @param leftVolume output attenuation for the left channel. A value of 0.0f is silence,
- * a value of 1.0f is no attenuation.
- * @param rightVolume output attenuation for the right channel
+ * Sets the specified left and right output gain values on the AudioTrack.
+ * <p>Gain values are clamped to the closed interval [0.0, max] where
+ * max is the value of {@link #getMaxVolume}.
+ * A value of 0.0 results in zero gain (silence), and
+ * a value of 1.0 means unity gain (signal unchanged).
+ * The default value is 1.0 meaning unity gain.
+ * <p>The word "volume" in the API name is historical; this is actually a linear gain.
+ * @param leftGain output gain for the left channel.
+ * @param rightGain output gain for the right channel
* @return error code or success, see {@link #SUCCESS},
* {@link #ERROR_INVALID_OPERATION}
+ * @deprecated Applications should use {@link #setVolume} instead, as it
+ * more gracefully scales down to mono, and up to multi-channel content beyond stereo.
*/
- public int setStereoVolume(float leftVolume, float rightVolume) {
+ public int setStereoVolume(float leftGain, float rightGain) {
+ if (isRestricted()) {
+ return SUCCESS;
+ }
if (mState == STATE_UNINITIALIZED) {
return ERROR_INVALID_OPERATION;
}
- // clamp the volumes
- if (leftVolume < getMinVolume()) {
- leftVolume = getMinVolume();
- }
- if (leftVolume > getMaxVolume()) {
- leftVolume = getMaxVolume();
- }
- if (rightVolume < getMinVolume()) {
- rightVolume = getMinVolume();
- }
- if (rightVolume > getMaxVolume()) {
- rightVolume = getMaxVolume();
- }
+ leftGain = clampGainOrLevel(leftGain);
+ rightGain = clampGainOrLevel(rightGain);
- native_setVolume(leftVolume, rightVolume);
+ native_setVolume(leftGain, rightGain);
return SUCCESS;
}
/**
- * Similar, except set volume of all channels to same value.
- * @hide
+ * Sets the specified output gain value on all channels of this track.
+ * <p>Gain values are clamped to the closed interval [0.0, max] where
+ * max is the value of {@link #getMaxVolume}.
+ * A value of 0.0 results in zero gain (silence), and
+ * a value of 1.0 means unity gain (signal unchanged).
+ * The default value is 1.0 meaning unity gain.
+ * <p>This API is preferred over {@link #setStereoVolume}, as it
+ * more gracefully scales down to mono, and up to multi-channel content beyond stereo.
+ * <p>The word "volume" in the API name is historical; this is actually a linear gain.
+ * @param gain output gain for all channels.
+ * @return error code or success, see {@link #SUCCESS},
+ * {@link #ERROR_INVALID_OPERATION}
*/
- public int setVolume(float volume) {
- return setStereoVolume(volume, volume);
+ public int setVolume(float gain) {
+ return setStereoVolume(gain, gain);
}
@@ -983,13 +1053,25 @@ public class AudioTrack
if (mState != STATE_INITIALIZED) {
throw new IllegalStateException("play() called on uninitialized AudioTrack.");
}
-
+ if (isRestricted()) {
+ setVolume(0);
+ }
synchronized(mPlayStateLock) {
native_start();
mPlayState = PLAYSTATE_PLAYING;
}
}
+ private boolean isRestricted() {
+ try {
+ final int mode = mAppOps.checkAudioOperation(AppOpsManager.OP_PLAY_AUDIO, mStreamType,
+ Process.myUid(), ActivityThread.currentPackageName());
+ return mode != AppOpsManager.MODE_ALLOWED;
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
/**
* Stops playing the audio data.
* When used on an instance created in {@link #MODE_STREAM} mode, audio will stop playing
@@ -1080,7 +1162,8 @@ public class AudioTrack
return ERROR_BAD_VALUE;
}
- int ret = native_write_byte(audioData, offsetInBytes, sizeInBytes, mAudioFormat);
+ int ret = native_write_byte(audioData, offsetInBytes, sizeInBytes, mAudioFormat,
+ true /*isBlocking*/);
if ((mDataLoadMode == MODE_STATIC)
&& (mState == STATE_NO_STATIC_DATA)
@@ -1137,6 +1220,73 @@ public class AudioTrack
/**
+ * @hide CANDIDATE FOR PUBLIC API
+ * Writes the audio data to the audio sink for playback (streaming mode),
+ * or copies audio data for later playback (static buffer mode).
+ * In static buffer mode, copies the data to the buffer starting at its 0 offset, and the write
+ * mode is ignored.
+ * In streaming mode, the blocking behavior will depend on the write mode.
+ * @param audioData the buffer that holds the data to play, starting at the position reported
+ * by <code>audioData.position()</code>.
+ * <BR>Note that upon return, the buffer position (<code>audioData.position()</code>) will
+ * have been advanced to reflect the amount of data that was successfully written to
+ * the AudioTrack.
+ * @param sizeInBytes number of bytes to write.
+ * <BR>Note this may differ from <code>audioData.remaining()</code>, but cannot exceed it.
+ * @param writeMode one of {@link #WRITE_BLOCKING}, {@link #WRITE_NON_BLOCKING}. It has no
+ * effect in static mode.
+ * <BR>With {@link #WRITE_BLOCKING}, the write will block until all data has been written
+ * to the audio sink.
+ * <BR>With {@link #WRITE_NON_BLOCKING}, the write will return immediately after
+ * queuing as much audio data for playback as possible without blocking.
+ * @return 0 or a positive number of bytes that were written, or
+ * {@link #ERROR_BAD_VALUE}, {@link #ERROR_INVALID_OPERATION}
+ */
+ public int write(ByteBuffer audioData, int sizeInBytes,
+ @WriteMode int writeMode) {
+
+ if (mState == STATE_UNINITIALIZED) {
+ Log.e(TAG, "AudioTrack.write() called in invalid state STATE_UNINITIALIZED");
+ return ERROR_INVALID_OPERATION;
+ }
+
+ if ((writeMode != WRITE_BLOCKING) && (writeMode != WRITE_NON_BLOCKING)) {
+ Log.e(TAG, "AudioTrack.write() called with invalid blocking mode");
+ return ERROR_BAD_VALUE;
+ }
+
+ if ( (audioData == null) || (sizeInBytes < 0) || (sizeInBytes > audioData.remaining())) {
+ Log.e(TAG, "AudioTrack.write() called with invalid size (" + sizeInBytes + ") value");
+ return ERROR_BAD_VALUE;
+ }
+
+ int ret = 0;
+ if (audioData.isDirect()) {
+ ret = native_write_native_bytes(audioData,
+ audioData.position(), sizeInBytes, mAudioFormat,
+ writeMode == WRITE_BLOCKING);
+ } else {
+ ret = native_write_byte(NioUtils.unsafeArray(audioData),
+ NioUtils.unsafeArrayOffset(audioData) + audioData.position(),
+ sizeInBytes, mAudioFormat,
+ writeMode == WRITE_BLOCKING);
+ }
+
+ if ((mDataLoadMode == MODE_STATIC)
+ && (mState == STATE_NO_STATIC_DATA)
+ && (ret > 0)) {
+ // benign race with respect to other APIs that read mState
+ mState = STATE_INITIALIZED;
+ }
+
+ if (ret > 0) {
+ audioData.position(audioData.position() + ret);
+ }
+
+ return ret;
+ }
+
+ /**
* Notifies the native resource to reuse the audio data already loaded in the native
* layer, that is to rewind to start of buffer.
* The track's creation mode must be {@link #MODE_STATIC}.
@@ -1180,33 +1330,32 @@ public class AudioTrack
/**
* Sets the send level of the audio track to the attached auxiliary effect
- * {@link #attachAuxEffect(int)}. The level value range is 0.0f to 1.0f.
- * Values are clamped to the (0.0f, 1.0f) interval if outside this range.
+ * {@link #attachAuxEffect(int)}. Effect levels
+ * are clamped to the closed interval [0.0, max] where
+ * max is the value of {@link #getMaxVolume}.
+ * A value of 0.0 results in no effect, and a value of 1.0 is full send.
* <p>By default the send level is 0.0f, so even if an effect is attached to the player
* this method must be called for the effect to be applied.
- * <p>Note that the passed level value is a raw scalar. UI controls should be scaled
- * logarithmically: the gain applied by audio framework ranges from -72dB to 0dB,
+ * <p>Note that the passed level value is a linear scalar. UI controls should be scaled
+ * logarithmically: the gain applied by audio framework ranges from -72dB to at least 0dB,
* so an appropriate conversion from linear UI input x to level is:
* x == 0 -&gt; level = 0
* 0 &lt; x &lt;= R -&gt; level = 10^(72*(x-R)/20/R)
*
- * @param level send level scalar
+ * @param level linear send level
* @return error code or success, see {@link #SUCCESS},
- * {@link #ERROR_INVALID_OPERATION}
+ * {@link #ERROR_INVALID_OPERATION}, {@link #ERROR}
*/
public int setAuxEffectSendLevel(float level) {
+ if (isRestricted()) {
+ return SUCCESS;
+ }
if (mState == STATE_UNINITIALIZED) {
return ERROR_INVALID_OPERATION;
}
- // clamp the level
- if (level < getMinVolume()) {
- level = getMinVolume();
- }
- if (level > getMaxVolume()) {
- level = getMaxVolume();
- }
- native_setAuxEffectSendLevel(level);
- return SUCCESS;
+ level = clampGainOrLevel(level);
+ int err = native_setAuxEffectSendLevel(level);
+ return err == 0 ? SUCCESS : ERROR;
}
//---------------------------------------------------------
@@ -1319,7 +1468,7 @@ public class AudioTrack
//--------------------
private native final int native_setup(Object audiotrack_this,
- int streamType, int sampleRate, int nbChannels, int audioFormat,
+ int streamType, int sampleRate, int channelMask, int audioFormat,
int buffSizeInBytes, int mode, int[] sessionId);
private native final void native_finalize();
@@ -1335,11 +1484,15 @@ public class AudioTrack
private native final void native_flush();
private native final int native_write_byte(byte[] audioData,
- int offsetInBytes, int sizeInBytes, int format);
+ int offsetInBytes, int sizeInBytes, int format,
+ boolean isBlocking);
private native final int native_write_short(short[] audioData,
int offsetInShorts, int sizeInShorts, int format);
+ private native final int native_write_native_bytes(Object audioData,
+ int positionInBytes, int sizeInBytes, int format, boolean blocking);
+
private native final int native_reload_static();
private native final int native_get_native_frame_count();
@@ -1372,7 +1525,7 @@ public class AudioTrack
int sampleRateInHz, int channelConfig, int audioFormat);
private native final int native_attachAuxEffect(int effectId);
- private native final void native_setAuxEffectSendLevel(float level);
+ private native final int native_setAuxEffectSendLevel(float level);
//---------------------------------------------------------
// Utility methods
diff --git a/media/java/android/media/EncoderCapabilities.java b/media/java/android/media/EncoderCapabilities.java
index 71cb1b3..332e360 100644
--- a/media/java/android/media/EncoderCapabilities.java
+++ b/media/java/android/media/EncoderCapabilities.java
@@ -18,7 +18,6 @@ package android.media;
import java.util.List;
import java.util.ArrayList;
-import android.util.Log;
/**
* The EncoderCapabilities class is used to retrieve the
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index b5c3631..2f08325 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -35,8 +35,6 @@ import android.view.KeyEvent;
*/
interface IAudioService {
- int verifyX509CertChain(int chainsize, in byte[] chain, String host, String authtype);
-
void adjustVolume(int direction, int flags, String callingPackage);
boolean isLocalOrRemoteMusicActive();
@@ -238,5 +236,4 @@ interface IAudioService {
AudioRoutesInfo startWatchingRoutes(in IAudioRoutesObserver observer);
boolean isCameraSoundForced();
-
}
diff --git a/media/java/android/media/IMediaHTTPConnection.aidl b/media/java/android/media/IMediaHTTPConnection.aidl
new file mode 100644
index 0000000..55ffc2e
--- /dev/null
+++ b/media/java/android/media/IMediaHTTPConnection.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2013 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.os.IBinder;
+
+/** MUST STAY IN SYNC WITH NATIVE CODE at libmedia/IMediaHTTPConnection.{cpp,h} */
+
+/** @hide */
+interface IMediaHTTPConnection
+{
+ IBinder connect(in String uri, in String headers);
+ void disconnect();
+
+ int readAt(long offset, int size);
+ long getSize();
+ String getMIMEType();
+ String getUri();
+}
+
diff --git a/media/java/android/media/IMediaHTTPService.aidl b/media/java/android/media/IMediaHTTPService.aidl
new file mode 100644
index 0000000..8aaf6b3
--- /dev/null
+++ b/media/java/android/media/IMediaHTTPService.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2013 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.media.IMediaHTTPConnection;
+
+/** MUST STAY IN SYNC WITH NATIVE CODE at libmedia/IMediaHTTPService.{cpp,h} */
+
+/** @hide */
+interface IMediaHTTPService
+{
+ IMediaHTTPConnection makeHTTPConnection();
+}
diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java
index 23abce7..a346e17 100644
--- a/media/java/android/media/Image.java
+++ b/media/java/android/media/Image.java
@@ -21,7 +21,8 @@ import java.lang.AutoCloseable;
/**
* <p>A single complete image buffer to use with a media source such as a
- * {@link MediaCodec}.</p>
+ * {@link MediaCodec} or a
+ * {@link android.hardware.camera2.CameraDevice CameraDevice}.</p>
*
* <p>This class allows for efficient direct application access to the pixel
* data of the Image through one or more
@@ -82,6 +83,15 @@ public abstract class Image implements AutoCloseable {
* plane (4:2:0 subsampling). Each pixel sample in each plane has 8 bits.
* Each plane has its own row stride and pixel stride.</td>
* </tr>
+ * <tr>
+ * <td>{@link android.graphics.ImageFormat#RAW_SENSOR RAW_SENSOR}</td>
+ * <td>1</td>
+ * <td>A single plane of raw sensor image data, with 16 bits per color
+ * sample. The details of the layout need to be queried from the source of
+ * the raw sensor data, such as
+ * {@link android.hardware.camera2.CameraDevice CameraDevice}.
+ * </td>
+ * </tr>
* </table>
*
* @see android.graphics.ImageFormat
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index d454c42..1bd32c4 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -32,7 +32,8 @@ import java.nio.ByteOrder;
* rendered into a {@link android.view.Surface}</p>
*
* <p>Several Android media API classes accept Surface objects as targets to
- * render to, including {@link MediaPlayer}, {@link MediaCodec}, and
+ * render to, including {@link MediaPlayer}, {@link MediaCodec},
+ * {@link android.hardware.camera2.CameraDevice}, and
* {@link android.renderscript.Allocation RenderScript Allocations}. The image
* sizes and formats that can be used with each source vary, and should be
* checked in the documentation for the specific API.</p>
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index ddf88df..115786c 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -21,8 +21,12 @@ import android.media.MediaCodecList;
import android.media.MediaCrypto;
import android.media.MediaFormat;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
import android.view.Surface;
+import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Map;
@@ -66,8 +70,8 @@ import java.util.Map;
*
* Each codec maintains a number of input and output buffers that are
* referred to by index in API calls.
- * The contents of these buffers is represented by the ByteBuffer[] arrays
- * accessible through getInputBuffers() and getOutputBuffers().
+ * The contents of these buffers are represented by the ByteBuffer[] arrays
+ * accessible through {@link #getInputBuffers} and {@link #getOutputBuffers}.
*
* After a successful call to {@link #start} the client "owns" neither
* input nor output buffers, subsequent calls to {@link #dequeueInputBuffer}
@@ -117,7 +121,18 @@ import java.util.Map;
* own any buffers anymore.
* Note that the format of the data submitted after a flush must not change,
* flush does not support format discontinuities,
- * for this a full stop(), configure(), start() cycle is necessary.
+ * for this a full {@link #stop}, {@link #configure}, {@link #start}
+ * cycle is necessary.
+ *
+ * <p> The factory methods
+ * {@link #createByCodecName},
+ * {@link #createDecoderByType},
+ * and {@link #createEncoderByType}
+ * throw {@link java.io.IOException} on failure which
+ * the caller must catch or declare to pass up.
+ * Other methods will throw {@link java.lang.IllegalStateException}
+ * if the codec is in an Uninitialized, Invalid, or Error state (e.g. not
+ * initialized properly). Exceptions are thrown elsewhere as noted. </p>
*
*/
final public class MediaCodec {
@@ -161,6 +176,34 @@ final public class MediaCodec {
*/
public static final int BUFFER_FLAG_END_OF_STREAM = 4;
+ private EventHandler mEventHandler;
+ private volatile NotificationCallback mNotificationCallback;
+
+ static final int EVENT_NOTIFY = 1;
+
+ private class EventHandler extends Handler {
+ private MediaCodec mCodec;
+
+ public EventHandler(MediaCodec codec, Looper looper) {
+ super(looper);
+ mCodec = codec;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_NOTIFY:
+ {
+ NotificationCallback cb = mNotificationCallback;
+ if (cb != null) {
+ cb.onCodecNotify(mCodec);
+ }
+ break;
+ }
+ }
+ }
+ }
+
/**
* Instantiate a decoder supporting input data of the given mime type.
*
@@ -181,16 +224,22 @@ final public class MediaCodec {
* </ul>
*
* @param type The mime type of the input data.
+ * @throws IOException if the codec cannot be created.
+ * @throws IllegalArgumentException if type is null.
*/
- public static MediaCodec createDecoderByType(String type) {
+ public static MediaCodec createDecoderByType(String type)
+ throws IOException {
return new MediaCodec(type, true /* nameIsType */, false /* encoder */);
}
/**
* Instantiate an encoder supporting output data of the given mime type.
* @param type The desired mime type of the output data.
+ * @throws IOException if the codec cannot be created.
+ * @throws IllegalArgumentException if type is null.
*/
- public static MediaCodec createEncoderByType(String type) {
+ public static MediaCodec createEncoderByType(String type)
+ throws IOException {
return new MediaCodec(type, true /* nameIsType */, true /* encoder */);
}
@@ -199,14 +248,26 @@ final public class MediaCodec {
* use this method to instantiate it. Use with caution.
* Likely to be used with information obtained from {@link android.media.MediaCodecList}
* @param name The name of the codec to be instantiated.
+ * @throws IOException if the codec cannot be created.
+ * @throws IllegalArgumentException if name is null.
*/
- public static MediaCodec createByCodecName(String name) {
+ public static MediaCodec createByCodecName(String name)
+ throws IOException {
return new MediaCodec(
name, false /* nameIsType */, false /* unused */);
}
private MediaCodec(
String name, boolean nameIsType, boolean encoder) {
+ Looper looper;
+ if ((looper = Looper.myLooper()) != null) {
+ mEventHandler = new EventHandler(this, looper);
+ } else if ((looper = Looper.getMainLooper()) != null) {
+ mEventHandler = new EventHandler(this, looper);
+ } else {
+ mEventHandler = null;
+ }
+
native_setup(name, nameIsType, encoder);
}
@@ -272,6 +333,10 @@ final public class MediaCodec {
* <p>
* The application is responsible for calling release() on the Surface when
* done.
+ * <p>
+ * The Surface must be rendered with a hardware-accelerated API, such as OpenGL ES.
+ * {@link android.view.Surface#lockCanvas(android.graphics.Rect)} may fail or produce
+ * unexpected results.
*/
public native final Surface createInputSurface();
@@ -287,7 +352,15 @@ final public class MediaCodec {
* To ensure that it is available to other client call {@link #release}
* and don't just rely on garbage collection to eventually do this for you.
*/
- public native final void stop();
+ public final void stop() {
+ native_stop();
+
+ if (mEventHandler != null) {
+ mEventHandler.removeMessages(EVENT_NOTIFY);
+ }
+ }
+
+ private native final void native_stop();
/**
* Flush both input and output ports of the component, all indices
@@ -618,6 +691,22 @@ final public class MediaCodec {
setParameters(keys, values);
}
+ public void setNotificationCallback(NotificationCallback cb) {
+ mNotificationCallback = cb;
+ }
+
+ public interface NotificationCallback {
+ void onCodecNotify(MediaCodec codec);
+ }
+
+ private void postEventFromNative(
+ int what, int arg1, int arg2, Object obj) {
+ if (mEventHandler != null) {
+ Message msg = mEventHandler.obtainMessage(what, arg1, arg2, obj);
+ mEventHandler.sendMessage(msg);
+ }
+ }
+
private native final void setParameters(String[] keys, Object[] values);
/**
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 532e39a..440653a 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -16,7 +16,6 @@
package android.media;
-import android.media.MediaDrmException;
import java.lang.ref.WeakReference;
import java.util.UUID;
import java.util.HashMap;
@@ -26,7 +25,6 @@ import android.os.Debug;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.os.Bundle;
import android.os.Parcel;
import android.util.Log;
diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java
index c3e5035..f2753ee 100644
--- a/media/java/android/media/MediaExtractor.java
+++ b/media/java/android/media/MediaExtractor.java
@@ -21,7 +21,9 @@ import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.media.MediaCodec;
import android.media.MediaFormat;
+import android.media.MediaHTTPService;
import android.net.Uri;
+import android.os.IBinder;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -137,11 +139,19 @@ final public class MediaExtractor {
++i;
}
}
- setDataSource(path, keys, values);
+
+ nativeSetDataSource(
+ MediaHTTPService.createHttpServiceBinderIfNecessary(path),
+ path,
+ keys,
+ values);
}
- private native final void setDataSource(
- String path, String[] keys, String[] values) throws IOException;
+ private native final void nativeSetDataSource(
+ IBinder httpServiceBinder,
+ String path,
+ String[] keys,
+ String[] values) throws IOException;
/**
* Sets the data source (file-path or http URL) to use.
@@ -156,7 +166,11 @@ final public class MediaExtractor {
* and then use the file descriptor form {@link #setDataSource(FileDescriptor)}.
*/
public final void setDataSource(String path) throws IOException {
- setDataSource(path, null, null);
+ nativeSetDataSource(
+ MediaHTTPService.createHttpServiceBinderIfNecessary(path),
+ path,
+ null,
+ null);
}
/**
diff --git a/media/java/android/media/MediaFile.java b/media/java/android/media/MediaFile.java
index 761ecca..526656a 100644
--- a/media/java/android/media/MediaFile.java
+++ b/media/java/android/media/MediaFile.java
@@ -16,17 +16,12 @@
package android.media;
-import android.content.ContentValues;
-import android.provider.MediaStore.Audio;
-import android.provider.MediaStore.Images;
-import android.provider.MediaStore.Video;
import android.media.DecoderCapabilities;
import android.media.DecoderCapabilities.VideoDecoder;
import android.media.DecoderCapabilities.AudioDecoder;
import android.mtp.MtpConstants;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.List;
import java.util.Locale;
diff --git a/media/java/android/media/MediaFocusControl.java b/media/java/android/media/MediaFocusControl.java
index 34008bb..214306c 100644
--- a/media/java/android/media/MediaFocusControl.java
+++ b/media/java/android/media/MediaFocusControl.java
@@ -32,6 +32,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
+import android.media.PlayerRecord.RemotePlaybackState;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -51,7 +52,6 @@ import android.util.Log;
import android.util.Slog;
import android.view.KeyEvent;
-import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;
@@ -120,6 +120,9 @@ public class MediaFocusControl implements OnFinished {
mHasRemotePlayback = false;
mMainRemoteIsActive = false;
+
+ PlayerRecord.setMediaFocusControl(this);
+
postReevaluateRemote();
}
@@ -251,7 +254,7 @@ public class MediaFocusControl implements OnFinished {
currentUser);
if (DEBUG_RC) { Log.d(TAG, " > enabled list: " + enabledNotifListeners); }
synchronized(mAudioFocusLock) {
- synchronized(mRCStack) {
+ synchronized(mPRStack) {
// check whether the "enable" status of each RCD with a notification listener
// has changed
final String[] enabledComponents;
@@ -263,7 +266,7 @@ public class MediaFocusControl implements OnFinished {
final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
while (displayIterator.hasNext()) {
final DisplayInfoForServer di =
- (DisplayInfoForServer) displayIterator.next();
+ displayIterator.next();
if (di.mClientNotifListComp != null) {
boolean wasEnabled = di.mEnabled;
di.mEnabled = isComponentInStringArray(di.mClientNotifListComp,
@@ -330,6 +333,7 @@ public class MediaFocusControl implements OnFinished {
private static final int MSG_RCC_UPDATE_METADATA = 9;
private static final int MSG_RCDISPLAY_INIT_INFO = 10;
private static final int MSG_REEVALUATE_RCD = 11;
+ private static final int MSG_UNREGISTER_MEDIABUTTONINTENT = 12;
// sendMsg() flags
/** If the msg is already queued, replace it with this one. */
@@ -369,7 +373,7 @@ public class MediaFocusControl implements OnFinished {
case MSG_RCDISPLAY_UPDATE:
// msg.obj is guaranteed to be non null
- onRcDisplayUpdate( (RemoteControlStackEntry) msg.obj, msg.arg1);
+ onRcDisplayUpdate( (PlayerRecord) msg.obj, msg.arg1);
break;
case MSG_REEVALUATE_REMOTE:
@@ -389,7 +393,7 @@ public class MediaFocusControl implements OnFinished {
case MSG_RCC_NEW_PLAYBACK_STATE:
onNewPlaybackStateForRcc(msg.arg1 /* rccId */,
msg.arg2 /* state */,
- (RccPlaybackState)msg.obj /* newState */);
+ (PlayerRecord.RccPlaybackState)msg.obj /* newState */);
break;
case MSG_RCC_SEEK_REQUEST:
@@ -415,6 +419,10 @@ public class MediaFocusControl implements OnFinished {
case MSG_REEVALUATE_RCD:
onReevaluateRemoteControlDisplays();
break;
+
+ case MSG_UNREGISTER_MEDIABUTTONINTENT:
+ unregisterMediaButtonIntent( (PendingIntent) msg.obj );
+ break;
}
}
}
@@ -463,10 +471,6 @@ public class MediaFocusControl implements OnFinished {
final FocusRequester exFocusOwner = mFocusStack.pop();
exFocusOwner.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS);
exFocusOwner.release();
- // clear RCD
- synchronized(mRCStack) {
- clearRemoteControlDisplay_syncAfRcs();
- }
}
}
}
@@ -527,10 +531,6 @@ public class MediaFocusControl implements OnFinished {
if (signal) {
// notify the new top of the stack it gained focus
notifyTopOfAudioFocusStack();
- // there's a new top of the stack, let the remote control know
- synchronized(mRCStack) {
- checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
- }
}
} else {
// focus is abandoned by a client that's not at the top of the stack,
@@ -539,7 +539,7 @@ public class MediaFocusControl implements OnFinished {
// evaluated it, traversal order doesn't matter here)
Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
while(stackIterator.hasNext()) {
- FocusRequester fr = (FocusRequester)stackIterator.next();
+ FocusRequester fr = stackIterator.next();
if(fr.hasSameClient(clientToRemove)) {
Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for "
+ clientToRemove);
@@ -563,7 +563,7 @@ public class MediaFocusControl implements OnFinished {
// evaluated it, traversal order doesn't matter here)
Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
while(stackIterator.hasNext()) {
- FocusRequester fr = (FocusRequester)stackIterator.next();
+ FocusRequester fr = stackIterator.next();
if(fr.hasSameBinder(cb)) {
Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for " + cb);
stackIterator.remove();
@@ -574,10 +574,6 @@ public class MediaFocusControl implements OnFinished {
// we removed an entry at the top of the stack:
// notify the new top of the stack it gained focus.
notifyTopOfAudioFocusStack();
- // there's a new top of the stack, let the remote control know
- synchronized(mRCStack) {
- checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
- }
}
}
@@ -686,10 +682,6 @@ public class MediaFocusControl implements OnFinished {
mFocusStack.push(new FocusRequester(mainStreamType, focusChangeHint, fd, cb,
clientId, afdh, callingPackageName, Binder.getCallingUid()));
- // there's a new top of the stack, let the remote control know
- synchronized(mRCStack) {
- checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
- }
}//synchronized(mAudioFocusLock)
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
@@ -751,7 +743,7 @@ public class MediaFocusControl implements OnFinished {
}
// event filtering for telephony
synchronized(mRingingLock) {
- synchronized(mRCStack) {
+ synchronized(mPRStack) {
if ((mMediaReceiverForCalls != null) &&
(mIsRinging || (mAudioService.getMode() == AudioSystem.MODE_IN_CALL))) {
dispatchMediaKeyEventForCalls(keyEvent, needWakeLock);
@@ -804,15 +796,15 @@ public class MediaFocusControl implements OnFinished {
}
Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
- synchronized(mRCStack) {
- if (!mRCStack.empty()) {
+ synchronized(mPRStack) {
+ if (!mPRStack.empty()) {
// send the intent that was registered by the client
try {
- mRCStack.peek().mMediaIntent.send(mContext,
+ mPRStack.peek().getMediaButtonIntent().send(mContext,
needWakeLock ? WAKELOCK_RELEASE_ON_FINISHED : 0 /*code*/,
keyIntent, this, mEventHandler);
} catch (CanceledException e) {
- Log.e(TAG, "Error sending pending intent " + mRCStack.peek());
+ Log.e(TAG, "Error sending pending intent " + mPRStack.peek());
e.printStackTrace();
}
} else {
@@ -931,33 +923,11 @@ public class MediaFocusControl implements OnFinished {
}
}
- protected static boolean isMediaKeyCode(int keyCode) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_MUTE:
- case KeyEvent.KEYCODE_HEADSETHOOK:
- case KeyEvent.KEYCODE_MEDIA_PLAY:
- case KeyEvent.KEYCODE_MEDIA_PAUSE:
- case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
- case KeyEvent.KEYCODE_MEDIA_STOP:
- case KeyEvent.KEYCODE_MEDIA_NEXT:
- case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
- case KeyEvent.KEYCODE_MEDIA_REWIND:
- case KeyEvent.KEYCODE_MEDIA_RECORD:
- case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
- case KeyEvent.KEYCODE_MEDIA_CLOSE:
- case KeyEvent.KEYCODE_MEDIA_EJECT:
- case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK:
- return true;
- default:
- return false;
- }
- }
-
private static boolean isValidMediaKeyEvent(KeyEvent keyEvent) {
if (keyEvent == null) {
return false;
}
- return MediaFocusControl.isMediaKeyCode(keyEvent.getKeyCode());
+ return KeyEvent.isMediaKey(keyEvent.getKeyCode());
}
/**
@@ -1044,7 +1014,7 @@ public class MediaFocusControl implements OnFinished {
};
/**
- * Synchronization on mCurrentRcLock always inside a block synchronized on mRCStack
+ * Synchronization on mCurrentRcLock always inside a block synchronized on mPRStack
*/
private final Object mCurrentRcLock = new Object();
/**
@@ -1073,51 +1043,6 @@ public class MediaFocusControl implements OnFinished {
*/
private int mCurrentRcClientGen = 0;
- /**
- * Inner class to monitor remote control client deaths, and remove the client for the
- * remote control stack if necessary.
- */
- private class RcClientDeathHandler implements IBinder.DeathRecipient {
- final private IBinder mCb; // To be notified of client's death
- final private PendingIntent mMediaIntent;
-
- RcClientDeathHandler(IBinder cb, PendingIntent pi) {
- mCb = cb;
- mMediaIntent = pi;
- }
-
- public void binderDied() {
- Log.w(TAG, " RemoteControlClient died");
- // remote control client died, make sure the displays don't use it anymore
- // by setting its remote control client to null
- registerRemoteControlClient(mMediaIntent, null/*rcClient*/, null/*ignored*/);
- // the dead client was maybe handling remote playback, reevaluate
- postReevaluateRemote();
- }
-
- public IBinder getBinder() {
- return mCb;
- }
- }
-
- /**
- * A global counter for RemoteControlClient identifiers
- */
- private static int sLastRccId = 0;
-
- private class RemotePlaybackState {
- int mRccId;
- int mVolume;
- int mVolumeMax;
- int mVolumeHandling;
-
- private RemotePlaybackState(int id, int vol, int volMax) {
- mRccId = id;
- mVolume = vol;
- mVolumeMax = volMax;
- mVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
- }
- }
/**
* Internal cache for the playback information of the RemoteControlClient whose volume gets to
@@ -1138,178 +1063,11 @@ public class MediaFocusControl implements OnFinished {
*/
private boolean mHasRemotePlayback;
- private static class RccPlaybackState {
- public int mState;
- public long mPositionMs;
- public float mSpeed;
-
- public RccPlaybackState(int state, long positionMs, float speed) {
- mState = state;
- mPositionMs = positionMs;
- mSpeed = speed;
- }
-
- public void reset() {
- mState = RemoteControlClient.PLAYSTATE_STOPPED;
- mPositionMs = RemoteControlClient.PLAYBACK_POSITION_INVALID;
- mSpeed = RemoteControlClient.PLAYBACK_SPEED_1X;
- }
-
- @Override
- public String toString() {
- return stateToString() + ", " + posToString() + ", " + mSpeed + "X";
- }
-
- private String posToString() {
- if (mPositionMs == RemoteControlClient.PLAYBACK_POSITION_INVALID) {
- return "PLAYBACK_POSITION_INVALID";
- } else if (mPositionMs == RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) {
- return "PLAYBACK_POSITION_ALWAYS_UNKNOWN";
- } else {
- return (String.valueOf(mPositionMs) + "ms");
- }
- }
-
- private String stateToString() {
- switch (mState) {
- case RemoteControlClient.PLAYSTATE_NONE:
- return "PLAYSTATE_NONE";
- case RemoteControlClient.PLAYSTATE_STOPPED:
- return "PLAYSTATE_STOPPED";
- case RemoteControlClient.PLAYSTATE_PAUSED:
- return "PLAYSTATE_PAUSED";
- case RemoteControlClient.PLAYSTATE_PLAYING:
- return "PLAYSTATE_PLAYING";
- case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
- return "PLAYSTATE_FAST_FORWARDING";
- case RemoteControlClient.PLAYSTATE_REWINDING:
- return "PLAYSTATE_REWINDING";
- case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
- return "PLAYSTATE_SKIPPING_FORWARDS";
- case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
- return "PLAYSTATE_SKIPPING_BACKWARDS";
- case RemoteControlClient.PLAYSTATE_BUFFERING:
- return "PLAYSTATE_BUFFERING";
- case RemoteControlClient.PLAYSTATE_ERROR:
- return "PLAYSTATE_ERROR";
- default:
- return "[invalid playstate]";
- }
- }
- }
-
- protected static class RemoteControlStackEntry implements DeathRecipient {
- public int mRccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
- final public MediaFocusControl mController;
- /**
- * The target for the ACTION_MEDIA_BUTTON events.
- * Always non null.
- */
- final public PendingIntent mMediaIntent;
- /**
- * The registered media button event receiver.
- * Always non null.
- */
- final public ComponentName mReceiverComponent;
- public IBinder mToken;
- public String mCallingPackageName;
- public int mCallingUid;
- /**
- * Provides access to the information to display on the remote control.
- * May be null (when a media button event receiver is registered,
- * but no remote control client has been registered) */
- public IRemoteControlClient mRcClient;
- public RcClientDeathHandler mRcClientDeathHandler;
- /**
- * Information only used for non-local playback
- */
- public int mPlaybackType;
- public int mPlaybackVolume;
- public int mPlaybackVolumeMax;
- public int mPlaybackVolumeHandling;
- public int mPlaybackStream;
- public RccPlaybackState mPlaybackState;
- public IRemoteVolumeObserver mRemoteVolumeObs;
-
- public void resetPlaybackInfo() {
- mPlaybackType = RemoteControlClient.PLAYBACK_TYPE_LOCAL;
- mPlaybackVolume = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
- mPlaybackVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
- mPlaybackVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
- mPlaybackStream = AudioManager.STREAM_MUSIC;
- mPlaybackState.reset();
- mRemoteVolumeObs = null;
- }
-
- /** precondition: mediaIntent != null */
- public RemoteControlStackEntry(MediaFocusControl controller, PendingIntent mediaIntent,
- ComponentName eventReceiver, IBinder token) {
- mController = controller;
- mMediaIntent = mediaIntent;
- mReceiverComponent = eventReceiver;
- mToken = token;
- mCallingUid = -1;
- mRcClient = null;
- mRccId = ++sLastRccId;
- mPlaybackState = new RccPlaybackState(
- RemoteControlClient.PLAYSTATE_STOPPED,
- RemoteControlClient.PLAYBACK_POSITION_INVALID,
- RemoteControlClient.PLAYBACK_SPEED_1X);
-
- resetPlaybackInfo();
- if (mToken != null) {
- try {
- mToken.linkToDeath(this, 0);
- } catch (RemoteException e) {
- mController.mEventHandler.post(new Runnable() {
- @Override public void run() {
- mController.unregisterMediaButtonIntent(mMediaIntent);
- }
- });
- }
- }
- }
-
- public void unlinkToRcClientDeath() {
- if ((mRcClientDeathHandler != null) && (mRcClientDeathHandler.mCb != null)) {
- try {
- mRcClientDeathHandler.mCb.unlinkToDeath(mRcClientDeathHandler, 0);
- mRcClientDeathHandler = null;
- } catch (java.util.NoSuchElementException e) {
- // not much we can do here
- Log.e(TAG, "Encountered " + e + " in unlinkToRcClientDeath()");
- e.printStackTrace();
- }
- }
- }
-
- public void destroy() {
- unlinkToRcClientDeath();
- if (mToken != null) {
- mToken.unlinkToDeath(this, 0);
- mToken = null;
- }
- }
-
- @Override
- public void binderDied() {
- mController.unregisterMediaButtonIntent(mMediaIntent);
- }
-
- @Override
- protected void finalize() throws Throwable {
- destroy(); // unlink exception handled inside method
- super.finalize();
- }
- }
-
/**
- * The stack of remote control event receivers.
- * Code sections and methods that modify the remote control event receiver stack are
- * synchronized on mRCStack, but also BEFORE on mFocusLock as any change in either
- * stack, audio focus or RC, can lead to a change in the remote control display
+ * The stack of remote control event receivers.
+ * All read and write operations on mPRStack are synchronized.
*/
- private final Stack<RemoteControlStackEntry> mRCStack = new Stack<RemoteControlStackEntry>();
+ private final Stack<PlayerRecord> mPRStack = new Stack<PlayerRecord>();
/**
* The component the telephony package can register so telephony calls have priority to
@@ -1323,17 +1081,10 @@ public class MediaFocusControl implements OnFinished {
*/
private void dumpRCStack(PrintWriter pw) {
pw.println("\nRemote Control stack entries (last is top of stack):");
- synchronized(mRCStack) {
- Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+ synchronized(mPRStack) {
+ Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
while(stackIterator.hasNext()) {
- RemoteControlStackEntry rcse = stackIterator.next();
- pw.println(" pi: " + rcse.mMediaIntent +
- " -- pack: " + rcse.mCallingPackageName +
- " -- ercvr: " + rcse.mReceiverComponent +
- " -- client: " + rcse.mRcClient +
- " -- uid: " + rcse.mCallingUid +
- " -- type: " + rcse.mPlaybackType +
- " state: " + rcse.mPlaybackState);
+ stackIterator.next().dump(pw, true);
}
}
}
@@ -1345,18 +1096,10 @@ public class MediaFocusControl implements OnFinished {
*/
private void dumpRCCStack(PrintWriter pw) {
pw.println("\nRemote Control Client stack entries (last is top of stack):");
- synchronized(mRCStack) {
- Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+ synchronized(mPRStack) {
+ Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
while(stackIterator.hasNext()) {
- RemoteControlStackEntry rcse = stackIterator.next();
- pw.println(" uid: " + rcse.mCallingUid +
- " -- id: " + rcse.mRccId +
- " -- type: " + rcse.mPlaybackType +
- " -- state: " + rcse.mPlaybackState +
- " -- vol handling: " + rcse.mPlaybackVolumeHandling +
- " -- vol: " + rcse.mPlaybackVolume +
- " -- volMax: " + rcse.mPlaybackVolumeMax +
- " -- volObs: " + rcse.mRemoteVolumeObs);
+ stackIterator.next().dump(pw, false);
}
synchronized(mCurrentRcLock) {
pw.println("\nCurrent remote control generation ID = " + mCurrentRcClientGen);
@@ -1381,10 +1124,10 @@ public class MediaFocusControl implements OnFinished {
*/
private void dumpRCDList(PrintWriter pw) {
pw.println("\nRemote Control Display list entries:");
- synchronized(mRCStack) {
+ synchronized(mPRStack) {
final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
while (displayIterator.hasNext()) {
- final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
+ final DisplayInfoForServer di = displayIterator.next();
pw.println(" IRCD: " + di.mRcDisplay +
" -- w:" + di.mArtworkExpectedWidth +
" -- h:" + di.mArtworkExpectedHeight +
@@ -1400,47 +1143,49 @@ public class MediaFocusControl implements OnFinished {
* Pre-condition: packageName != null
*/
private void cleanupMediaButtonReceiverForPackage(String packageName, boolean removeAll) {
- synchronized(mRCStack) {
- if (mRCStack.empty()) {
+ synchronized(mPRStack) {
+ if (mPRStack.empty()) {
return;
} else {
final PackageManager pm = mContext.getPackageManager();
- RemoteControlStackEntry oldTop = mRCStack.peek();
- Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+ PlayerRecord oldTop = mPRStack.peek();
+ Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
// iterate over the stack entries
// (using an iterator on the stack so we can safely remove an entry after having
// evaluated it, traversal order doesn't matter here)
while(stackIterator.hasNext()) {
- RemoteControlStackEntry rcse = (RemoteControlStackEntry)stackIterator.next();
- if (removeAll && packageName.equals(rcse.mMediaIntent.getCreatorPackage())) {
+ PlayerRecord prse = stackIterator.next();
+ if (removeAll
+ && packageName.equals(prse.getMediaButtonIntent().getCreatorPackage()))
+ {
// a stack entry is from the package being removed, remove it from the stack
stackIterator.remove();
- rcse.destroy();
- } else if (rcse.mReceiverComponent != null) {
+ prse.destroy();
+ } else if (prse.getMediaButtonReceiver() != null) {
try {
// Check to see if this receiver still exists.
- pm.getReceiverInfo(rcse.mReceiverComponent, 0);
+ pm.getReceiverInfo(prse.getMediaButtonReceiver(), 0);
} catch (PackageManager.NameNotFoundException e) {
// Not found -- remove it!
stackIterator.remove();
- rcse.destroy();
+ prse.destroy();
}
}
}
- if (mRCStack.empty()) {
+ if (mPRStack.empty()) {
// no saved media button receiver
mEventHandler.sendMessage(
mEventHandler.obtainMessage(MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0,
null));
- } else if (oldTop != mRCStack.peek()) {
+ } else if (oldTop != mPRStack.peek()) {
// the top of the stack has changed, save it in the system settings
// by posting a message to persist it; only do this however if it has
// a concrete component name (is not a transient registration)
- RemoteControlStackEntry rcse = mRCStack.peek();
- if (rcse.mReceiverComponent != null) {
+ PlayerRecord prse = mPRStack.peek();
+ if (prse.getMediaButtonReceiver() != null) {
mEventHandler.sendMessage(
mEventHandler.obtainMessage(MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0,
- rcse.mReceiverComponent));
+ prse.getMediaButtonReceiver()));
}
}
}
@@ -1474,28 +1219,28 @@ public class MediaFocusControl implements OnFinished {
/**
* Helper function:
* Set the new remote control receiver at the top of the RC focus stack.
- * Called synchronized on mAudioFocusLock, then mRCStack
+ * Called synchronized on mPRStack
* precondition: mediaIntent != null
- * @return true if mRCStack was changed, false otherwise
+ * @return true if mPRStack was changed, false otherwise
*/
- private boolean pushMediaButtonReceiver_syncAfRcs(PendingIntent mediaIntent,
+ private boolean pushMediaButtonReceiver_syncPrs(PendingIntent mediaIntent,
ComponentName target, IBinder token) {
// already at top of stack?
- if (!mRCStack.empty() && mRCStack.peek().mMediaIntent.equals(mediaIntent)) {
+ if (!mPRStack.empty() && mPRStack.peek().hasMatchingMediaButtonIntent(mediaIntent)) {
return false;
}
if (mAppOps.noteOp(AppOpsManager.OP_TAKE_MEDIA_BUTTONS, Binder.getCallingUid(),
mediaIntent.getCreatorPackage()) != AppOpsManager.MODE_ALLOWED) {
return false;
}
- RemoteControlStackEntry rcse = null;
+ PlayerRecord prse = null;
boolean wasInsideStack = false;
try {
- for (int index = mRCStack.size()-1; index >= 0; index--) {
- rcse = mRCStack.elementAt(index);
- if(rcse.mMediaIntent.equals(mediaIntent)) {
+ for (int index = mPRStack.size()-1; index >= 0; index--) {
+ prse = mPRStack.elementAt(index);
+ if(prse.hasMatchingMediaButtonIntent(mediaIntent)) {
// ok to remove element while traversing the stack since we're leaving the loop
- mRCStack.removeElementAt(index);
+ mPRStack.removeElementAt(index);
wasInsideStack = true;
break;
}
@@ -1505,9 +1250,9 @@ public class MediaFocusControl implements OnFinished {
Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
}
if (!wasInsideStack) {
- rcse = new RemoteControlStackEntry(this, mediaIntent, target, token);
+ prse = new PlayerRecord(mediaIntent, target, token);
}
- mRCStack.push(rcse); // rcse is never null
+ mPRStack.push(prse); // prse is never null
// post message to persist the default media button receiver
if (target != null) {
@@ -1522,17 +1267,17 @@ public class MediaFocusControl implements OnFinished {
/**
* Helper function:
* Remove the remote control receiver from the RC focus stack.
- * Called synchronized on mAudioFocusLock, then mRCStack
+ * Called synchronized on mPRStack
* precondition: pi != null
*/
- private void removeMediaButtonReceiver_syncAfRcs(PendingIntent pi) {
+ private void removeMediaButtonReceiver_syncPrs(PendingIntent pi) {
try {
- for (int index = mRCStack.size()-1; index >= 0; index--) {
- final RemoteControlStackEntry rcse = mRCStack.elementAt(index);
- if (rcse.mMediaIntent.equals(pi)) {
- rcse.destroy();
+ for (int index = mPRStack.size()-1; index >= 0; index--) {
+ final PlayerRecord prse = mPRStack.elementAt(index);
+ if (prse.hasMatchingMediaButtonIntent(pi)) {
+ prse.destroy();
// ok to remove element while traversing the stack since we're leaving the loop
- mRCStack.removeElementAt(index);
+ mPRStack.removeElementAt(index);
break;
}
}
@@ -1544,10 +1289,10 @@ public class MediaFocusControl implements OnFinished {
/**
* Helper function:
- * Called synchronized on mRCStack
+ * Called synchronized on mPRStack
*/
private boolean isCurrentRcController(PendingIntent pi) {
- if (!mRCStack.empty() && mRCStack.peek().mMediaIntent.equals(pi)) {
+ if (!mPRStack.empty() && mPRStack.peek().hasMatchingMediaButtonIntent(pi)) {
return true;
}
return false;
@@ -1568,7 +1313,7 @@ public class MediaFocusControl implements OnFinished {
*/
private void setNewRcClientOnDisplays_syncRcsCurrc(int newClientGeneration,
PendingIntent newMediaIntent, boolean clearing) {
- synchronized(mRCStack) {
+ synchronized(mPRStack) {
if (mRcDisplays.size() > 0) {
final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
while (displayIterator.hasNext()) {
@@ -1592,12 +1337,12 @@ public class MediaFocusControl implements OnFinished {
private void setNewRcClientGenerationOnClients_syncRcsCurrc(int newClientGeneration) {
// (using an iterator on the stack so we can safely remove an entry if needed,
// traversal order doesn't matter here as we update all entries)
- Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+ Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
while(stackIterator.hasNext()) {
- RemoteControlStackEntry se = stackIterator.next();
- if ((se != null) && (se.mRcClient != null)) {
+ PlayerRecord se = stackIterator.next();
+ if ((se != null) && (se.getRcc() != null)) {
try {
- se.mRcClient.setCurrentClientGenerationId(newClientGeneration);
+ se.getRcc().setCurrentClientGenerationId(newClientGeneration);
} catch (RemoteException e) {
Log.w(TAG, "Dead client in setNewRcClientGenerationOnClients_syncRcsCurrc()",e);
stackIterator.remove();
@@ -1629,7 +1374,7 @@ public class MediaFocusControl implements OnFinished {
private void onRcDisplayClear() {
if (DEBUG_RC) Log.i(TAG, "Clear remote control display");
- synchronized(mRCStack) {
+ synchronized(mPRStack) {
synchronized(mCurrentRcLock) {
mCurrentRcClientGen++;
// synchronously update the displays and clients with the new client generation
@@ -1642,17 +1387,17 @@ public class MediaFocusControl implements OnFinished {
/**
* Called when processing MSG_RCDISPLAY_UPDATE event
*/
- private void onRcDisplayUpdate(RemoteControlStackEntry rcse, int flags /* USED ?*/) {
- synchronized(mRCStack) {
+ private void onRcDisplayUpdate(PlayerRecord prse, int flags /* USED ?*/) {
+ synchronized(mPRStack) {
synchronized(mCurrentRcLock) {
- if ((mCurrentRcClient != null) && (mCurrentRcClient.equals(rcse.mRcClient))) {
+ if ((mCurrentRcClient != null) && (mCurrentRcClient.equals(prse.getRcc()))) {
if (DEBUG_RC) Log.i(TAG, "Display/update remote control ");
mCurrentRcClientGen++;
// synchronously update the displays and clients with
// the new client generation
setNewRcClient_syncRcsCurrc(mCurrentRcClientGen,
- rcse.mMediaIntent /*newMediaIntent*/,
+ prse.getMediaButtonIntent() /*newMediaIntent*/,
false /*clearing*/);
// tell the current client that it needs to send info
@@ -1678,7 +1423,7 @@ public class MediaFocusControl implements OnFinished {
* a single RemoteControlDisplay, NOT all of them, as with MSG_RCDISPLAY_UPDATE.
*/
private void onRcDisplayInitInfo(IRemoteControlDisplay newRcd, int w, int h) {
- synchronized(mRCStack) {
+ synchronized(mPRStack) {
synchronized(mCurrentRcLock) {
if (mCurrentRcClient != null) {
if (DEBUG_RC) { Log.i(TAG, "Init RCD with current info"); }
@@ -1705,9 +1450,9 @@ public class MediaFocusControl implements OnFinished {
/**
* Helper function:
- * Called synchronized on mRCStack
+ * Called synchronized on mPRStack
*/
- private void clearRemoteControlDisplay_syncAfRcs() {
+ private void clearRemoteControlDisplay_syncPrs() {
synchronized(mCurrentRcLock) {
mCurrentRcClient = null;
}
@@ -1717,93 +1462,56 @@ public class MediaFocusControl implements OnFinished {
/**
* Helper function for code readability: only to be called from
- * checkUpdateRemoteControlDisplay_syncAfRcs() which checks the preconditions for
+ * checkUpdateRemoteControlDisplay_syncPrs() which checks the preconditions for
* this method.
* Preconditions:
- * - called synchronized mAudioFocusLock then on mRCStack
- * - mRCStack.isEmpty() is false
+ * - called synchronized on mPRStack
+ * - mPRStack.isEmpty() is false
*/
- private void updateRemoteControlDisplay_syncAfRcs(int infoChangedFlags) {
- RemoteControlStackEntry rcse = mRCStack.peek();
+ private void updateRemoteControlDisplay_syncPrs(int infoChangedFlags) {
+ PlayerRecord prse = mPRStack.peek();
int infoFlagsAboutToBeUsed = infoChangedFlags;
// this is where we enforce opt-in for information display on the remote controls
// with the new AudioManager.registerRemoteControlClient() API
- if (rcse.mRcClient == null) {
+ if (prse.getRcc() == null) {
//Log.w(TAG, "Can't update remote control display with null remote control client");
- clearRemoteControlDisplay_syncAfRcs();
+ clearRemoteControlDisplay_syncPrs();
return;
}
synchronized(mCurrentRcLock) {
- if (!rcse.mRcClient.equals(mCurrentRcClient)) {
+ if (!prse.getRcc().equals(mCurrentRcClient)) {
// new RC client, assume every type of information shall be queried
infoFlagsAboutToBeUsed = RC_INFO_ALL;
}
- mCurrentRcClient = rcse.mRcClient;
- mCurrentRcClientIntent = rcse.mMediaIntent;
+ mCurrentRcClient = prse.getRcc();
+ mCurrentRcClientIntent = prse.getMediaButtonIntent();
}
// will cause onRcDisplayUpdate() to be called in AudioService's handler thread
mEventHandler.sendMessage( mEventHandler.obtainMessage(MSG_RCDISPLAY_UPDATE,
- infoFlagsAboutToBeUsed /* arg1 */, 0, rcse /* obj, != null */) );
+ infoFlagsAboutToBeUsed /* arg1 */, 0, prse /* obj, != null */) );
}
/**
* Helper function:
- * Called synchronized on mAudioFocusLock, then mRCStack
+ * Called synchronized on mPRStack
* Check whether the remote control display should be updated, triggers the update if required
* @param infoChangedFlags the flags corresponding to the remote control client information
* that has changed, if applicable (checking for the update conditions might trigger a
* clear, rather than an update event).
*/
- private void checkUpdateRemoteControlDisplay_syncAfRcs(int infoChangedFlags) {
+ private void checkUpdateRemoteControlDisplay_syncPrs(int infoChangedFlags) {
// determine whether the remote control display should be refreshed
- // if either stack is empty, there is a mismatch, so clear the RC display
- if (mRCStack.isEmpty() || mFocusStack.isEmpty()) {
- clearRemoteControlDisplay_syncAfRcs();
- return;
- }
-
- // determine which entry in the AudioFocus stack to consider, and compare against the
- // top of the stack for the media button event receivers : simply using the top of the
- // stack would make the entry disappear from the RemoteControlDisplay in conditions such as
- // notifications playing during music playback.
- // Crawl the AudioFocus stack from the top until an entry is found with the following
- // characteristics:
- // - focus gain on STREAM_MUSIC stream
- // - non-transient focus gain on a stream other than music
- FocusRequester af = null;
- try {
- for (int index = mFocusStack.size()-1; index >= 0; index--) {
- FocusRequester fr = mFocusStack.elementAt(index);
- if ((fr.getStreamType() == AudioManager.STREAM_MUSIC)
- || (fr.getGainRequest() == AudioManager.AUDIOFOCUS_GAIN)) {
- af = fr;
- break;
- }
- }
- } catch (ArrayIndexOutOfBoundsException e) {
- Log.e(TAG, "Wrong index accessing audio focus stack when updating RCD: " + e);
- af = null;
- }
- if (af == null) {
- clearRemoteControlDisplay_syncAfRcs();
+ // if the player record stack is empty, there is nothing to display, so clear the RC display
+ if (mPRStack.isEmpty()) {
+ clearRemoteControlDisplay_syncPrs();
return;
}
- // if the audio focus and RC owners belong to different packages, there is a mismatch, clear
- if (!af.hasSamePackage(mRCStack.peek().mCallingPackageName)) {
- clearRemoteControlDisplay_syncAfRcs();
- return;
- }
- // if the audio focus didn't originate from the same Uid as the one in which the remote
- // control information will be retrieved, clear
- if (!af.hasSameUid(mRCStack.peek().mCallingUid)) {
- clearRemoteControlDisplay_syncAfRcs();
- return;
- }
+ // this is where more rules for refresh go
// refresh conditions were verified: update the remote controls
- // ok to call: synchronized mAudioFocusLock then on mRCStack, mRCStack is not empty
- updateRemoteControlDisplay_syncAfRcs(infoChangedFlags);
+ // ok to call: synchronized on mPRStack, mPRStack is not empty
+ updateRemoteControlDisplay_syncPrs(infoChangedFlags);
}
/**
@@ -1819,35 +1527,33 @@ public class MediaFocusControl implements OnFinished {
private void onPromoteRcc(int rccId) {
if (DEBUG_RC) { Log.d(TAG, "Promoting RCC " + rccId); }
- synchronized(mAudioFocusLock) {
- synchronized(mRCStack) {
- // ignore if given RCC ID is already at top of remote control stack
- if (!mRCStack.isEmpty() && (mRCStack.peek().mRccId == rccId)) {
- return;
- }
- int indexToPromote = -1;
- try {
- for (int index = mRCStack.size()-1; index >= 0; index--) {
- final RemoteControlStackEntry rcse = mRCStack.elementAt(index);
- if (rcse.mRccId == rccId) {
- indexToPromote = index;
- break;
- }
- }
- if (indexToPromote >= 0) {
- if (DEBUG_RC) { Log.d(TAG, " moving RCC from index " + indexToPromote
- + " to " + (mRCStack.size()-1)); }
- final RemoteControlStackEntry rcse = mRCStack.remove(indexToPromote);
- mRCStack.push(rcse);
- // the RC stack changed, reevaluate the display
- checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
+ synchronized(mPRStack) {
+ // ignore if given RCC ID is already at top of remote control stack
+ if (!mPRStack.isEmpty() && (mPRStack.peek().getRccId() == rccId)) {
+ return;
+ }
+ int indexToPromote = -1;
+ try {
+ for (int index = mPRStack.size()-1; index >= 0; index--) {
+ final PlayerRecord prse = mPRStack.elementAt(index);
+ if (prse.getRccId() == rccId) {
+ indexToPromote = index;
+ break;
}
- } catch (ArrayIndexOutOfBoundsException e) {
- // not expected to happen, indicates improper concurrent modification
- Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
}
- }//synchronized(mRCStack)
- }//synchronized(mAudioFocusLock)
+ if (indexToPromote >= 0) {
+ if (DEBUG_RC) { Log.d(TAG, " moving RCC from index " + indexToPromote
+ + " to " + (mPRStack.size()-1)); }
+ final PlayerRecord prse = mPRStack.remove(indexToPromote);
+ mPRStack.push(prse);
+ // the RC stack changed, reevaluate the display
+ checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // not expected to happen, indicates improper concurrent modification
+ Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
+ }
+ }//synchronized(mPRStack)
}
/**
@@ -1858,12 +1564,10 @@ public class MediaFocusControl implements OnFinished {
IBinder token) {
Log.i(TAG, " Remote Control registerMediaButtonIntent() for " + mediaIntent);
- synchronized(mAudioFocusLock) {
- synchronized(mRCStack) {
- if (pushMediaButtonReceiver_syncAfRcs(mediaIntent, eventReceiver, token)) {
- // new RC client, assume every type of information shall be queried
- checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
- }
+ synchronized(mPRStack) {
+ if (pushMediaButtonReceiver_syncPrs(mediaIntent, eventReceiver, token)) {
+ // new RC client, assume every type of information shall be queried
+ checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
}
}
}
@@ -1876,18 +1580,22 @@ public class MediaFocusControl implements OnFinished {
{
Log.i(TAG, " Remote Control unregisterMediaButtonIntent() for " + mediaIntent);
- synchronized(mAudioFocusLock) {
- synchronized(mRCStack) {
- boolean topOfStackWillChange = isCurrentRcController(mediaIntent);
- removeMediaButtonReceiver_syncAfRcs(mediaIntent);
- if (topOfStackWillChange) {
- // current RC client will change, assume every type of info needs to be queried
- checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
- }
+ synchronized(mPRStack) {
+ boolean topOfStackWillChange = isCurrentRcController(mediaIntent);
+ removeMediaButtonReceiver_syncPrs(mediaIntent);
+ if (topOfStackWillChange) {
+ // current RC client will change, assume every type of info needs to be queried
+ checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
}
}
}
+ protected void unregisterMediaButtonIntentAsync(final PendingIntent mediaIntent) {
+ mEventHandler.sendMessage(
+ mEventHandler.obtainMessage(MSG_UNREGISTER_MEDIABUTTONINTENT, 0, 0,
+ mediaIntent));
+ }
+
/**
* see AudioManager.registerMediaButtonEventReceiverForCalls(ComponentName c)
* precondition: c != null
@@ -1898,7 +1606,7 @@ public class MediaFocusControl implements OnFinished {
Log.e(TAG, "Invalid permissions to register media button receiver for calls");
return;
}
- synchronized(mRCStack) {
+ synchronized(mPRStack) {
mMediaReceiverForCalls = c;
}
}
@@ -1912,14 +1620,14 @@ public class MediaFocusControl implements OnFinished {
Log.e(TAG, "Invalid permissions to unregister media button receiver for calls");
return;
}
- synchronized(mRCStack) {
+ synchronized(mPRStack) {
mMediaReceiverForCalls = null;
}
}
/**
* see AudioManager.registerRemoteControlClient(ComponentName eventReceiver, ...)
- * @return the unique ID of the RemoteControlStackEntry associated with the RemoteControlClient
+ * @return the unique ID of the PlayerRecord associated with the RemoteControlClient
* Note: using this method with rcClient == null is a way to "disable" the IRemoteControlClient
* without modifying the RC stack, but while still causing the display to refresh (will
* become blank as a result of this)
@@ -1928,61 +1636,40 @@ public class MediaFocusControl implements OnFinished {
IRemoteControlClient rcClient, String callingPackageName) {
if (DEBUG_RC) Log.i(TAG, "Register remote control client rcClient="+rcClient);
int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
- synchronized(mAudioFocusLock) {
- synchronized(mRCStack) {
- // store the new display information
- try {
- for (int index = mRCStack.size()-1; index >= 0; index--) {
- final RemoteControlStackEntry rcse = mRCStack.elementAt(index);
- if(rcse.mMediaIntent.equals(mediaIntent)) {
- // already had a remote control client?
- if (rcse.mRcClientDeathHandler != null) {
- // stop monitoring the old client's death
- rcse.unlinkToRcClientDeath();
- }
- // save the new remote control client
- rcse.mRcClient = rcClient;
- rcse.mCallingPackageName = callingPackageName;
- rcse.mCallingUid = Binder.getCallingUid();
- if (rcClient == null) {
- // here rcse.mRcClientDeathHandler is null;
- rcse.resetPlaybackInfo();
- break;
- }
- rccId = rcse.mRccId;
+ synchronized(mPRStack) {
+ // store the new display information
+ try {
+ for (int index = mPRStack.size()-1; index >= 0; index--) {
+ final PlayerRecord prse = mPRStack.elementAt(index);
+ if(prse.hasMatchingMediaButtonIntent(mediaIntent)) {
+ prse.resetControllerInfoForRcc(rcClient, callingPackageName,
+ Binder.getCallingUid());
- // there is a new (non-null) client:
- // 1/ give the new client the displays (if any)
- if (mRcDisplays.size() > 0) {
- plugRemoteControlDisplaysIntoClient_syncRcStack(rcse.mRcClient);
- }
- // 2/ monitor the new client's death
- IBinder b = rcse.mRcClient.asBinder();
- RcClientDeathHandler rcdh =
- new RcClientDeathHandler(b, rcse.mMediaIntent);
- try {
- b.linkToDeath(rcdh, 0);
- } catch (RemoteException e) {
- // remote control client is DOA, disqualify it
- Log.w(TAG, "registerRemoteControlClient() has a dead client " + b);
- rcse.mRcClient = null;
- }
- rcse.mRcClientDeathHandler = rcdh;
+ if (rcClient == null) {
break;
}
- }//for
- } catch (ArrayIndexOutOfBoundsException e) {
- // not expected to happen, indicates improper concurrent modification
- Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
- }
- // if the eventReceiver is at the top of the stack
- // then check for potential refresh of the remote controls
- if (isCurrentRcController(mediaIntent)) {
- checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
- }
- }//synchronized(mRCStack)
- }//synchronized(mAudioFocusLock)
+ rccId = prse.getRccId();
+
+ // there is a new (non-null) client:
+ // give the new client the displays (if any)
+ if (mRcDisplays.size() > 0) {
+ plugRemoteControlDisplaysIntoClient_syncPrs(prse.getRcc());
+ }
+ break;
+ }
+ }//for
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // not expected to happen, indicates improper concurrent modification
+ Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
+ }
+
+ // if the eventReceiver is at the top of the stack
+ // then check for potential refresh of the remote controls
+ if (isCurrentRcController(mediaIntent)) {
+ checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
+ }
+ }//synchronized(mPRStack)
return rccId;
}
@@ -1993,33 +1680,27 @@ public class MediaFocusControl implements OnFinished {
protected void unregisterRemoteControlClient(PendingIntent mediaIntent,
IRemoteControlClient rcClient) {
if (DEBUG_RC) Log.i(TAG, "Unregister remote control client rcClient="+rcClient);
- synchronized(mAudioFocusLock) {
- synchronized(mRCStack) {
- boolean topRccChange = false;
- try {
- for (int index = mRCStack.size()-1; index >= 0; index--) {
- final RemoteControlStackEntry rcse = mRCStack.elementAt(index);
- if ((rcse.mMediaIntent.equals(mediaIntent))
- && rcClient.equals(rcse.mRcClient)) {
- // we found the IRemoteControlClient to unregister
- // stop monitoring its death
- rcse.unlinkToRcClientDeath();
- // reset the client-related fields
- rcse.mRcClient = null;
- rcse.mCallingPackageName = null;
- topRccChange = (index == mRCStack.size()-1);
- // there can only be one matching RCC in the RC stack, we're done
- break;
- }
+ synchronized(mPRStack) {
+ boolean topRccChange = false;
+ try {
+ for (int index = mPRStack.size()-1; index >= 0; index--) {
+ final PlayerRecord prse = mPRStack.elementAt(index);
+ if ((prse.hasMatchingMediaButtonIntent(mediaIntent))
+ && rcClient.equals(prse.getRcc())) {
+ // we found the IRemoteControlClient to unregister
+ prse.resetControllerInfoForNoRcc();
+ topRccChange = (index == mPRStack.size()-1);
+ // there can only be one matching RCC in the RC stack, we're done
+ break;
}
- } catch (ArrayIndexOutOfBoundsException e) {
- // not expected to happen, indicates improper concurrent modification
- Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
- }
- if (topRccChange) {
- // no more RCC for the RCD, check for potential refresh of the remote controls
- checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
}
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // not expected to happen, indicates improper concurrent modification
+ Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
+ }
+ if (topRccChange) {
+ // no more RCC for the RCD, check for potential refresh of the remote controls
+ checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
}
}
}
@@ -2071,12 +1752,12 @@ public class MediaFocusControl implements OnFinished {
}
public void binderDied() {
- synchronized(mRCStack) {
+ synchronized(mPRStack) {
Log.w(TAG, "RemoteControl: display " + mRcDisplay + " died");
// remove the display from the list
final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
while (displayIterator.hasNext()) {
- final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
+ final DisplayInfoForServer di = displayIterator.next();
if (di.mRcDisplay == mRcDisplay) {
if (DEBUG_RC) Log.w(TAG, " RCD removed from list");
displayIterator.remove();
@@ -2089,7 +1770,7 @@ public class MediaFocusControl implements OnFinished {
/**
* The remote control displays.
- * Access synchronized on mRCStack
+ * Access synchronized on mPRStack
*/
private ArrayList<DisplayInfoForServer> mRcDisplays = new ArrayList<DisplayInfoForServer>(1);
@@ -2097,10 +1778,10 @@ public class MediaFocusControl implements OnFinished {
* Plug each registered display into the specified client
* @param rcc, guaranteed non null
*/
- private void plugRemoteControlDisplaysIntoClient_syncRcStack(IRemoteControlClient rcc) {
+ private void plugRemoteControlDisplaysIntoClient_syncPrs(IRemoteControlClient rcc) {
final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
while (displayIterator.hasNext()) {
- final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
+ final DisplayInfoForServer di = displayIterator.next();
try {
rcc.plugRemoteControlDisplay(di.mRcDisplay, di.mArtworkExpectedWidth,
di.mArtworkExpectedHeight);
@@ -2117,12 +1798,12 @@ public class MediaFocusControl implements OnFinished {
boolean enabled) {
// let all the remote control clients know whether the given display is enabled
// (so the remote control stack traversal order doesn't matter).
- final Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+ final Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
while(stackIterator.hasNext()) {
- RemoteControlStackEntry rcse = stackIterator.next();
- if(rcse.mRcClient != null) {
+ PlayerRecord prse = stackIterator.next();
+ if(prse.getRcc() != null) {
try {
- rcse.mRcClient.enableRemoteControlDisplay(rcd, enabled);
+ prse.getRcc().enableRemoteControlDisplay(rcd, enabled);
} catch (RemoteException e) {
Log.e(TAG, "Error connecting RCD to client: ", e);
}
@@ -2138,7 +1819,7 @@ public class MediaFocusControl implements OnFinished {
private boolean rcDisplayIsPluggedIn_syncRcStack(IRemoteControlDisplay rcd) {
final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
while (displayIterator.hasNext()) {
- final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
+ final DisplayInfoForServer di = displayIterator.next();
if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
return true;
}
@@ -2163,7 +1844,7 @@ public class MediaFocusControl implements OnFinished {
ComponentName listenerComp) {
if (DEBUG_RC) Log.d(TAG, ">>> registerRemoteControlDisplay("+rcd+")");
synchronized(mAudioFocusLock) {
- synchronized(mRCStack) {
+ synchronized(mPRStack) {
if ((rcd == null) || rcDisplayIsPluggedIn_syncRcStack(rcd)) {
return;
}
@@ -2179,12 +1860,12 @@ public class MediaFocusControl implements OnFinished {
// let all the remote control clients know there is a new display (so the remote
// control stack traversal order doesn't matter).
- Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+ Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
while(stackIterator.hasNext()) {
- RemoteControlStackEntry rcse = stackIterator.next();
- if(rcse.mRcClient != null) {
+ PlayerRecord prse = stackIterator.next();
+ if(prse.getRcc() != null) {
try {
- rcse.mRcClient.plugRemoteControlDisplay(rcd, w, h);
+ prse.getRcc().plugRemoteControlDisplay(rcd, w, h);
} catch (RemoteException e) {
Log.e(TAG, "Error connecting RCD to client: ", e);
}
@@ -2209,7 +1890,7 @@ public class MediaFocusControl implements OnFinished {
*/
protected void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) {
if (DEBUG_RC) Log.d(TAG, "<<< unregisterRemoteControlDisplay("+rcd+")");
- synchronized(mRCStack) {
+ synchronized(mPRStack) {
if (rcd == null) {
return;
}
@@ -2217,7 +1898,7 @@ public class MediaFocusControl implements OnFinished {
boolean displayWasPluggedIn = false;
final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
while (displayIterator.hasNext() && !displayWasPluggedIn) {
- final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
+ final DisplayInfoForServer di = displayIterator.next();
if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
displayWasPluggedIn = true;
di.release();
@@ -2228,12 +1909,12 @@ public class MediaFocusControl implements OnFinished {
if (displayWasPluggedIn) {
// disconnect this remote control display from all the clients, so the remote
// control stack traversal order doesn't matter
- final Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+ final Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
while(stackIterator.hasNext()) {
- final RemoteControlStackEntry rcse = stackIterator.next();
- if(rcse.mRcClient != null) {
+ final PlayerRecord prse = stackIterator.next();
+ if(prse.getRcc() != null) {
try {
- rcse.mRcClient.unplugRemoteControlDisplay(rcd);
+ prse.getRcc().unplugRemoteControlDisplay(rcd);
} catch (RemoteException e) {
Log.e(TAG, "Error disconnecting remote control display to client: ", e);
}
@@ -2255,11 +1936,11 @@ public class MediaFocusControl implements OnFinished {
* display doesn't need to receive artwork.
*/
protected void remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h) {
- synchronized(mRCStack) {
+ synchronized(mPRStack) {
final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
boolean artworkSizeUpdate = false;
while (displayIterator.hasNext() && !artworkSizeUpdate) {
- final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
+ final DisplayInfoForServer di = displayIterator.next();
if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) {
di.mArtworkExpectedWidth = w;
@@ -2271,12 +1952,12 @@ public class MediaFocusControl implements OnFinished {
if (artworkSizeUpdate) {
// RCD is currently plugged in and its artwork size has changed, notify all RCCs,
// stack traversal order doesn't matter
- final Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+ final Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
while(stackIterator.hasNext()) {
- final RemoteControlStackEntry rcse = stackIterator.next();
- if(rcse.mRcClient != null) {
+ final PlayerRecord prse = stackIterator.next();
+ if(prse.getRcc() != null) {
try {
- rcse.mRcClient.setBitmapSizeForDisplay(rcd, w, h);
+ prse.getRcc().setBitmapSizeForDisplay(rcd, w, h);
} catch (RemoteException e) {
Log.e(TAG, "Error setting bitmap size for RCD on RCC: ", e);
}
@@ -2300,13 +1981,13 @@ public class MediaFocusControl implements OnFinished {
*/
protected void remoteControlDisplayWantsPlaybackPositionSync(IRemoteControlDisplay rcd,
boolean wantsSync) {
- synchronized(mRCStack) {
+ synchronized(mPRStack) {
boolean rcdRegistered = false;
// store the information about this display
// (display stack traversal order doesn't matter).
final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
while (displayIterator.hasNext()) {
- final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
+ final DisplayInfoForServer di = displayIterator.next();
if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
di.mWantsPositionSync = wantsSync;
rcdRegistered = true;
@@ -2318,12 +1999,12 @@ public class MediaFocusControl implements OnFinished {
}
// notify all current RemoteControlClients
// (stack traversal order doesn't matter as we notify all RCCs)
- final Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+ final Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
while (stackIterator.hasNext()) {
- final RemoteControlStackEntry rcse = stackIterator.next();
- if (rcse.mRcClient != null) {
+ final PlayerRecord prse = stackIterator.next();
+ if (prse.getRcc() != null) {
try {
- rcse.mRcClient.setWantsSyncForDisplay(rcd, wantsSync);
+ prse.getRcc().setWantsSyncForDisplay(rcd, wantsSync);
} catch (RemoteException e) {
Log.e(TAG, "Error setting position sync flag for RCD on RCC: ", e);
}
@@ -2334,7 +2015,7 @@ public class MediaFocusControl implements OnFinished {
protected void setRemoteControlClientPlaybackPosition(int generationId, long timeMs) {
// ignore position change requests if invalid generation ID
- synchronized(mRCStack) {
+ synchronized(mPRStack) {
synchronized(mCurrentRcLock) {
if (mCurrentRcClientGen != generationId) {
return;
@@ -2349,7 +2030,7 @@ public class MediaFocusControl implements OnFinished {
private void onSetRemoteControlClientPlaybackPosition(int generationId, long timeMs) {
if(DEBUG_RC) Log.d(TAG, "onSetRemoteControlClientPlaybackPosition(genId=" + generationId +
", timeMs=" + timeMs + ")");
- synchronized(mRCStack) {
+ synchronized(mPRStack) {
synchronized(mCurrentRcLock) {
if ((mCurrentRcClient != null) && (mCurrentRcClientGen == generationId)) {
// tell the current client to seek to the requested location
@@ -2372,7 +2053,7 @@ public class MediaFocusControl implements OnFinished {
private void onUpdateRemoteControlClientMetadata(int genId, int key, Rating value) {
if(DEBUG_RC) Log.d(TAG, "onUpdateRemoteControlClientMetadata(genId=" + genId +
", what=" + key + ",rating=" + value + ")");
- synchronized(mRCStack) {
+ synchronized(mPRStack) {
synchronized(mCurrentRcLock) {
if ((mCurrentRcClient != null) && (mCurrentRcClientGen == genId)) {
try {
@@ -2403,20 +2084,20 @@ public class MediaFocusControl implements OnFinished {
private void onNewPlaybackInfoForRcc(int rccId, int key, int value) {
if(DEBUG_RC) Log.d(TAG, "onNewPlaybackInfoForRcc(id=" + rccId +
", what=" + key + ",val=" + value + ")");
- synchronized(mRCStack) {
+ synchronized(mPRStack) {
// iterating from top of stack as playback information changes are more likely
// on entries at the top of the remote control stack
try {
- for (int index = mRCStack.size()-1; index >= 0; index--) {
- final RemoteControlStackEntry rcse = mRCStack.elementAt(index);
- if (rcse.mRccId == rccId) {
+ for (int index = mPRStack.size()-1; index >= 0; index--) {
+ final PlayerRecord prse = mPRStack.elementAt(index);
+ if (prse.getRccId() == rccId) {
switch (key) {
case RemoteControlClient.PLAYBACKINFO_PLAYBACK_TYPE:
- rcse.mPlaybackType = value;
+ prse.mPlaybackType = value;
postReevaluateRemote();
break;
case RemoteControlClient.PLAYBACKINFO_VOLUME:
- rcse.mPlaybackVolume = value;
+ prse.mPlaybackVolume = value;
synchronized (mMainRemote) {
if (rccId == mMainRemote.mRccId) {
mMainRemote.mVolume = value;
@@ -2425,7 +2106,7 @@ public class MediaFocusControl implements OnFinished {
}
break;
case RemoteControlClient.PLAYBACKINFO_VOLUME_MAX:
- rcse.mPlaybackVolumeMax = value;
+ prse.mPlaybackVolumeMax = value;
synchronized (mMainRemote) {
if (rccId == mMainRemote.mRccId) {
mMainRemote.mVolumeMax = value;
@@ -2434,7 +2115,7 @@ public class MediaFocusControl implements OnFinished {
}
break;
case RemoteControlClient.PLAYBACKINFO_VOLUME_HANDLING:
- rcse.mPlaybackVolumeHandling = value;
+ prse.mPlaybackVolumeHandling = value;
synchronized (mMainRemote) {
if (rccId == mMainRemote.mRccId) {
mMainRemote.mVolumeHandling = value;
@@ -2443,7 +2124,7 @@ public class MediaFocusControl implements OnFinished {
}
break;
case RemoteControlClient.PLAYBACKINFO_USES_STREAM:
- rcse.mPlaybackStream = value;
+ prse.mPlaybackStream = value;
break;
default:
Log.e(TAG, "unhandled key " + key + " for RCC " + rccId);
@@ -2454,7 +2135,7 @@ public class MediaFocusControl implements OnFinished {
}//for
} catch (ArrayIndexOutOfBoundsException e) {
// not expected to happen, indicates improper concurrent modification
- Log.e(TAG, "Wrong index mRCStack on onNewPlaybackInfoForRcc, lock error? ", e);
+ Log.e(TAG, "Wrong index mPRStack on onNewPlaybackInfoForRcc, lock error? ", e);
}
}
}
@@ -2462,20 +2143,21 @@ public class MediaFocusControl implements OnFinished {
protected void setPlaybackStateForRcc(int rccId, int state, long timeMs, float speed) {
sendMsg(mEventHandler, MSG_RCC_NEW_PLAYBACK_STATE, SENDMSG_QUEUE,
rccId /* arg1 */, state /* arg2 */,
- new RccPlaybackState(state, timeMs, speed) /* obj */, 0 /* delay */);
+ new PlayerRecord.RccPlaybackState(state, timeMs, speed) /* obj */, 0 /* delay */);
}
- private void onNewPlaybackStateForRcc(int rccId, int state, RccPlaybackState newState) {
+ private void onNewPlaybackStateForRcc(int rccId, int state,
+ PlayerRecord.RccPlaybackState newState) {
if(DEBUG_RC) Log.d(TAG, "onNewPlaybackStateForRcc(id=" + rccId + ", state=" + state
+ ", time=" + newState.mPositionMs + ", speed=" + newState.mSpeed + ")");
- synchronized(mRCStack) {
+ synchronized(mPRStack) {
// iterating from top of stack as playback information changes are more likely
// on entries at the top of the remote control stack
try {
- for (int index = mRCStack.size()-1; index >= 0; index--) {
- final RemoteControlStackEntry rcse = mRCStack.elementAt(index);
- if (rcse.mRccId == rccId) {
- rcse.mPlaybackState = newState;
+ for (int index = mPRStack.size()-1; index >= 0; index--) {
+ final PlayerRecord prse = mPRStack.elementAt(index);
+ if (prse.getRccId() == rccId) {
+ prse.mPlaybackState = newState;
synchronized (mMainRemote) {
if (rccId == mMainRemote.mRccId) {
mMainRemoteIsActive = isPlaystateActive(state);
@@ -2492,7 +2174,7 @@ public class MediaFocusControl implements OnFinished {
}//for
} catch (ArrayIndexOutOfBoundsException e) {
// not expected to happen, indicates improper concurrent modification
- Log.e(TAG, "Wrong index on mRCStack in onNewPlaybackStateForRcc, lock error? ", e);
+ Log.e(TAG, "Wrong index on mPRStack in onNewPlaybackStateForRcc, lock error? ", e);
}
}
}
@@ -2504,15 +2186,15 @@ public class MediaFocusControl implements OnFinished {
// handler for MSG_RCC_NEW_VOLUME_OBS
private void onRegisterVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo) {
- synchronized(mRCStack) {
+ synchronized(mPRStack) {
// The stack traversal order doesn't matter because there is only one stack entry
// with this RCC ID, but the matching ID is more likely at the top of the stack, so
// start iterating from the top.
try {
- for (int index = mRCStack.size()-1; index >= 0; index--) {
- final RemoteControlStackEntry rcse = mRCStack.elementAt(index);
- if (rcse.mRccId == rccId) {
- rcse.mRemoteVolumeObs = rvo;
+ for (int index = mPRStack.size()-1; index >= 0; index--) {
+ final PlayerRecord prse = mPRStack.elementAt(index);
+ if (prse.getRccId() == rccId) {
+ prse.mRemoteVolumeObs = rvo;
break;
}
}
@@ -2530,21 +2212,21 @@ public class MediaFocusControl implements OnFinished {
* @return false if no remote playing is currently playing
*/
protected boolean checkUpdateRemoteStateIfActive(int streamType) {
- synchronized(mRCStack) {
+ synchronized(mPRStack) {
// iterating from top of stack as active playback is more likely on entries at the top
try {
- for (int index = mRCStack.size()-1; index >= 0; index--) {
- final RemoteControlStackEntry rcse = mRCStack.elementAt(index);
- if ((rcse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE)
- && isPlaystateActive(rcse.mPlaybackState.mState)
- && (rcse.mPlaybackStream == streamType)) {
+ for (int index = mPRStack.size()-1; index >= 0; index--) {
+ final PlayerRecord prse = mPRStack.elementAt(index);
+ if ((prse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE)
+ && isPlaystateActive(prse.mPlaybackState.mState)
+ && (prse.mPlaybackStream == streamType)) {
if (DEBUG_RC) Log.d(TAG, "remote playback active on stream " + streamType
- + ", vol =" + rcse.mPlaybackVolume);
+ + ", vol =" + prse.mPlaybackVolume);
synchronized (mMainRemote) {
- mMainRemote.mRccId = rcse.mRccId;
- mMainRemote.mVolume = rcse.mPlaybackVolume;
- mMainRemote.mVolumeMax = rcse.mPlaybackVolumeMax;
- mMainRemote.mVolumeHandling = rcse.mPlaybackVolumeHandling;
+ mMainRemote.mRccId = prse.getRccId();
+ mMainRemote.mVolume = prse.mPlaybackVolume;
+ mMainRemote.mVolumeMax = prse.mPlaybackVolumeMax;
+ mMainRemote.mVolumeHandling = prse.mPlaybackVolumeHandling;
mMainRemoteIsActive = true;
}
return true;
@@ -2611,16 +2293,16 @@ public class MediaFocusControl implements OnFinished {
return;
}
IRemoteVolumeObserver rvo = null;
- synchronized (mRCStack) {
+ synchronized (mPRStack) {
// The stack traversal order doesn't matter because there is only one stack entry
// with this RCC ID, but the matching ID is more likely at the top of the stack, so
// start iterating from the top.
try {
- for (int index = mRCStack.size()-1; index >= 0; index--) {
- final RemoteControlStackEntry rcse = mRCStack.elementAt(index);
+ for (int index = mPRStack.size()-1; index >= 0; index--) {
+ final PlayerRecord prse = mPRStack.elementAt(index);
//FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate?
- if (rcse.mRccId == rccId) {
- rvo = rcse.mRemoteVolumeObs;
+ if (prse.getRccId() == rccId) {
+ rvo = prse.mRemoteVolumeObs;
break;
}
}
@@ -2666,16 +2348,16 @@ public class MediaFocusControl implements OnFinished {
rccId = mMainRemote.mRccId;
}
IRemoteVolumeObserver rvo = null;
- synchronized (mRCStack) {
+ synchronized (mPRStack) {
// The stack traversal order doesn't matter because there is only one stack entry
// with this RCC ID, but the matching ID is more likely at the top of the stack, so
// start iterating from the top.
try {
- for (int index = mRCStack.size()-1; index >= 0; index--) {
- final RemoteControlStackEntry rcse = mRCStack.elementAt(index);
+ for (int index = mPRStack.size()-1; index >= 0; index--) {
+ final PlayerRecord prse = mPRStack.elementAt(index);
//FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate?
- if (rcse.mRccId == rccId) {
- rvo = rcse.mRemoteVolumeObs;
+ if (prse.getRccId() == rccId) {
+ rvo = prse.mRemoteVolumeObs;
break;
}
}
@@ -2698,7 +2380,7 @@ public class MediaFocusControl implements OnFinished {
* have their volume controlled. In this implementation this is only to reset whether
* VolumePanel should display remote volumes
*/
- private void postReevaluateRemote() {
+ protected void postReevaluateRemote() {
sendMsg(mEventHandler, MSG_REEVALUATE_REMOTE, SENDMSG_QUEUE, 0, 0, null, 0);
}
@@ -2706,13 +2388,13 @@ public class MediaFocusControl implements OnFinished {
if (DEBUG_VOL) { Log.w(TAG, "onReevaluateRemote()"); }
// is there a registered RemoteControlClient that is handling remote playback
boolean hasRemotePlayback = false;
- synchronized (mRCStack) {
+ synchronized (mPRStack) {
// iteration stops when PLAYBACK_TYPE_REMOTE is found, so remote control stack
// traversal order doesn't matter
- Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+ Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
while(stackIterator.hasNext()) {
- RemoteControlStackEntry rcse = stackIterator.next();
- if (rcse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE) {
+ PlayerRecord prse = stackIterator.next();
+ if (prse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE) {
hasRemotePlayback = true;
break;
}
diff --git a/media/java/android/media/MediaHTTPConnection.java b/media/java/android/media/MediaHTTPConnection.java
new file mode 100644
index 0000000..eb91668
--- /dev/null
+++ b/media/java/android/media/MediaHTTPConnection.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2013 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.os.IBinder;
+import android.os.StrictMode;
+import android.util.Log;
+
+import java.io.BufferedInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.net.CookieHandler;
+import java.net.CookieManager;
+import java.net.URL;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.util.HashMap;
+import java.util.Map;
+
+/** @hide */
+public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
+ private static final String TAG = "MediaHTTPConnection";
+ private static final boolean VERBOSE = false;
+
+ private long mCurrentOffset = -1;
+ private URL mURL = null;
+ private Map<String, String> mHeaders = null;
+ private HttpURLConnection mConnection = null;
+ private long mTotalSize = -1;
+ private InputStream mInputStream = null;
+
+ public MediaHTTPConnection() {
+ if (CookieHandler.getDefault() == null) {
+ CookieHandler.setDefault(new CookieManager());
+ }
+
+ native_setup();
+ }
+
+ @Override
+ public IBinder connect(String uri, String headers) {
+ if (VERBOSE) {
+ Log.d(TAG, "connect: uri=" + uri + ", headers=" + headers);
+ }
+
+ try {
+ disconnect();
+ mURL = new URL(uri);
+ mHeaders = convertHeaderStringToMap(headers);
+ } catch (MalformedURLException e) {
+ return null;
+ }
+
+ return native_getIMemory();
+ }
+
+ private Map<String, String> convertHeaderStringToMap(String headers) {
+ HashMap<String, String> map = new HashMap<String, String>();
+
+ String[] pairs = headers.split("\r\n");
+ for (String pair : pairs) {
+ int colonPos = pair.indexOf(":");
+ if (colonPos >= 0) {
+ String key = pair.substring(0, colonPos);
+ String val = pair.substring(colonPos + 1);
+
+ map.put(key, val);
+ }
+ }
+
+ return map;
+ }
+
+ @Override
+ public void disconnect() {
+ teardownConnection();
+ mHeaders = null;
+ mURL = null;
+ }
+
+ private void teardownConnection() {
+ if (mConnection != null) {
+ mInputStream = null;
+
+ mConnection.disconnect();
+ mConnection = null;
+
+ mCurrentOffset = -1;
+ }
+ }
+
+ private void seekTo(long offset) throws IOException {
+ teardownConnection();
+
+ try {
+ mConnection = (HttpURLConnection)mURL.openConnection();
+
+ if (mHeaders != null) {
+ for (Map.Entry<String, String> entry : mHeaders.entrySet()) {
+ mConnection.setRequestProperty(
+ entry.getKey(), entry.getValue());
+ }
+ }
+
+ if (offset > 0) {
+ mConnection.setRequestProperty(
+ "Range", "bytes=" + offset + "-");
+ }
+
+ int response = mConnection.getResponseCode();
+ // remember the current, possibly redirected URL
+ mURL = mConnection.getURL();
+
+ if (response == HttpURLConnection.HTTP_PARTIAL) {
+ // Partial content, we cannot just use getContentLength
+ // because what we want is not just the length of the range
+ // returned but the size of the full content if available.
+
+ String contentRange =
+ mConnection.getHeaderField("Content-Range");
+
+ mTotalSize = -1;
+ if (contentRange != null) {
+ // format is "bytes xxx-yyy/zzz
+ // where "zzz" is the total number of bytes of the
+ // content or '*' if unknown.
+
+ int lastSlashPos = contentRange.lastIndexOf('/');
+ if (lastSlashPos >= 0) {
+ String total =
+ contentRange.substring(lastSlashPos + 1);
+
+ try {
+ mTotalSize = Long.parseLong(total);
+ } catch (NumberFormatException e) {
+ }
+ }
+ }
+ } else if (response != HttpURLConnection.HTTP_OK) {
+ throw new IOException();
+ } else {
+ mTotalSize = mConnection.getContentLength();
+ }
+
+ if (offset > 0 && response != HttpURLConnection.HTTP_PARTIAL) {
+ // Some servers simply ignore "Range" requests and serve
+ // data from the start of the content.
+ throw new IOException();
+ }
+
+ mInputStream =
+ new BufferedInputStream(mConnection.getInputStream());
+
+ mCurrentOffset = offset;
+ } catch (IOException e) {
+ mTotalSize = -1;
+ mInputStream = null;
+ mConnection = null;
+ mCurrentOffset = -1;
+
+ throw e;
+ }
+ }
+
+ @Override
+ public int readAt(long offset, int size) {
+ return native_readAt(offset, size);
+ }
+
+ private int readAt(long offset, byte[] data, int size) {
+ StrictMode.ThreadPolicy policy =
+ new StrictMode.ThreadPolicy.Builder().permitAll().build();
+
+ StrictMode.setThreadPolicy(policy);
+
+ try {
+ if (offset != mCurrentOffset) {
+ seekTo(offset);
+ }
+
+ int n = mInputStream.read(data, 0, size);
+
+ if (n == -1) {
+ // InputStream signals EOS using a -1 result, our semantics
+ // are to return a 0-length read.
+ n = 0;
+ }
+
+ mCurrentOffset += n;
+
+ if (VERBOSE) {
+ Log.d(TAG, "readAt " + offset + " / " + size + " => " + n);
+ }
+
+ return n;
+ } catch (IOException e) {
+ if (VERBOSE) {
+ Log.d(TAG, "readAt " + offset + " / " + size + " => -1");
+ }
+ return -1;
+ } catch (Exception e) {
+ if (VERBOSE) {
+ Log.d(TAG, "unknown exception " + e);
+ Log.d(TAG, "readAt " + offset + " / " + size + " => -1");
+ }
+ return -1;
+ }
+ }
+
+ @Override
+ public long getSize() {
+ if (mConnection == null) {
+ try {
+ seekTo(0);
+ } catch (IOException e) {
+ return -1;
+ }
+ }
+
+ return mTotalSize;
+ }
+
+ @Override
+ public String getMIMEType() {
+ if (mConnection == null) {
+ try {
+ seekTo(0);
+ } catch (IOException e) {
+ return "application/octet-stream";
+ }
+ }
+
+ return mConnection.getContentType();
+ }
+
+ @Override
+ public String getUri() {
+ return mURL.toString();
+ }
+
+ @Override
+ protected void finalize() {
+ native_finalize();
+ }
+
+ private static native final void native_init();
+ private native final void native_setup();
+ private native final void native_finalize();
+
+ private native final IBinder native_getIMemory();
+ private native final int native_readAt(long offset, int size);
+
+ static {
+ System.loadLibrary("media_jni");
+ native_init();
+ }
+
+ private long mNativeContext;
+
+}
diff --git a/media/java/android/media/MediaHTTPService.java b/media/java/android/media/MediaHTTPService.java
new file mode 100644
index 0000000..3b4703d
--- /dev/null
+++ b/media/java/android/media/MediaHTTPService.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2013 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.os.Binder;
+import android.os.IBinder;
+import android.util.Log;
+
+/** @hide */
+public class MediaHTTPService extends IMediaHTTPService.Stub {
+ private static final String TAG = "MediaHTTPService";
+
+ public MediaHTTPService() {
+ }
+
+ public IMediaHTTPConnection makeHTTPConnection() {
+ return new MediaHTTPConnection();
+ }
+
+ /* package private */static IBinder createHttpServiceBinderIfNecessary(
+ String path) {
+ if (path.startsWith("http://")
+ || path.startsWith("https://")
+ || path.startsWith("widevine://")) {
+ return (new MediaHTTPService()).asBinder();
+ }
+
+ return null;
+ }
+}
diff --git a/media/java/android/media/MediaMetadataEditor.java b/media/java/android/media/MediaMetadataEditor.java
index 373ba11..3bfdb5a 100644
--- a/media/java/android/media/MediaMetadataEditor.java
+++ b/media/java/android/media/MediaMetadataEditor.java
@@ -18,7 +18,6 @@ package android.media;
import android.graphics.Bitmap;
import android.os.Bundle;
-import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import android.util.SparseIntArray;
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index db27d09..9a69c06 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -21,6 +21,7 @@ import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.graphics.Bitmap;
import android.net.Uri;
+import android.os.IBinder;
import java.io.FileDescriptor;
import java.io.FileInputStream;
@@ -100,11 +101,16 @@ public class MediaMetadataRetriever
values[i] = entry.getValue();
++i;
}
- _setDataSource(uri, keys, values);
+
+ _setDataSource(
+ MediaHTTPService.createHttpServiceBinderIfNecessary(uri),
+ uri,
+ keys,
+ values);
}
private native void _setDataSource(
- String uri, String[] keys, String[] values)
+ IBinder httpServiceBinder, String uri, String[] keys, String[] values)
throws IllegalArgumentException;
/**
diff --git a/media/java/android/media/MediaMuxer.java b/media/java/android/media/MediaMuxer.java
index e5c97e7..f518ab2 100644
--- a/media/java/android/media/MediaMuxer.java
+++ b/media/java/android/media/MediaMuxer.java
@@ -17,13 +17,11 @@
package android.media;
import android.media.MediaCodec.BufferInfo;
-
import dalvik.system.CloseGuard;
-import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.util.Map;
@@ -79,6 +77,7 @@ final public class MediaMuxer {
private OutputFormat() {}
/** MPEG4 media file format*/
public static final int MUXER_OUTPUT_MPEG_4 = 0;
+ public static final int MUXER_OUTPUT_WEBM = 1;
};
// All the native functions are listed here.
@@ -120,20 +119,22 @@ final public class MediaMuxer {
if (path == null) {
throw new IllegalArgumentException("path must not be null");
}
- if (format != OutputFormat.MUXER_OUTPUT_MPEG_4) {
+ if (format != OutputFormat.MUXER_OUTPUT_MPEG_4 &&
+ format != OutputFormat.MUXER_OUTPUT_WEBM) {
throw new IllegalArgumentException("format is invalid");
}
- FileOutputStream fos = null;
+ // Use RandomAccessFile so we can open the file with RW access;
+ // RW access allows the native writer to memory map the output file.
+ RandomAccessFile file = null;
try {
- File file = new File(path);
- fos = new FileOutputStream(file);
- FileDescriptor fd = fos.getFD();
+ file = new RandomAccessFile(path, "rws");
+ FileDescriptor fd = file.getFD();
mNativeObject = nativeSetup(fd, format);
mState = MUXER_STATE_INITIALIZED;
mCloseGuard.open("release");
} finally {
- if (fos != null) {
- fos.close();
+ if (file != null) {
+ file.close();
}
}
}
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 41ba5d6..1b92410 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -16,36 +16,38 @@
package android.media;
+import android.app.ActivityThread;
+import android.app.AppOpsManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.AssetFileDescriptor;
-import android.net.Proxy;
-import android.net.ProxyProperties;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
import android.os.Parcelable;
-import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
-import android.graphics.Bitmap;
import android.graphics.SurfaceTexture;
import android.media.AudioManager;
import android.media.MediaFormat;
import android.media.MediaTimeProvider;
-import android.media.MediaTimeProvider.OnMediaTimeListener;
import android.media.SubtitleController;
import android.media.SubtitleData;
+import com.android.internal.app.IAppOpsService;
+
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
@@ -580,6 +582,8 @@ public class MediaPlayer implements SubtitleController.Listener
private PowerManager.WakeLock mWakeLock = null;
private boolean mScreenOnWhilePlaying;
private boolean mStayAwake;
+ private final IAppOpsService mAppOps;
+ private int mStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE;
/**
* Default constructor. Consider using one of the create() methods for
@@ -603,6 +607,8 @@ public class MediaPlayer implements SubtitleController.Listener
mOutOfBandSubtitleTracks = new Vector<SubtitleTrack>();
mOpenSubtitleSources = new Vector<InputStream>();
mInbandSubtitleTracks = new SubtitleTrack[0];
+ IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
+ mAppOps = IAppOpsService.Stub.asInterface(b);
/* Native setup requires a weak reference to our object.
* It's easier to create it here than in C++.
@@ -884,8 +890,6 @@ public class MediaPlayer implements SubtitleController.Listener
*/
public void setDataSource(Context context, Uri uri, Map<String, String> headers)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
- disableProxyListener();
-
String scheme = uri.getScheme();
if(scheme == null || scheme.equals("file")) {
setDataSource(uri.getPath());
@@ -919,11 +923,6 @@ public class MediaPlayer implements SubtitleController.Listener
Log.d(TAG, "Couldn't open file on client side, trying server side");
setDataSource(uri.toString(), headers);
-
- if (scheme.equalsIgnoreCase("http")
- || scheme.equalsIgnoreCase("https")) {
- setupProxyListener(context);
- }
}
/**
@@ -974,8 +973,6 @@ public class MediaPlayer implements SubtitleController.Listener
private void setDataSource(String path, String[] keys, String[] values)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
- disableProxyListener();
-
final Uri uri = Uri.parse(path);
if ("file".equals(uri.getScheme())) {
path = uri.getPath();
@@ -992,8 +989,18 @@ public class MediaPlayer implements SubtitleController.Listener
}
}
- private native void _setDataSource(
+ private void _setDataSource(
String path, String[] keys, String[] values)
+ throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
+ nativeSetDataSource(
+ MediaHTTPService.createHttpServiceBinderIfNecessary(path),
+ path,
+ keys,
+ values);
+ }
+
+ private native void nativeSetDataSource(
+ IBinder httpServiceBinder, String path, String[] keys, String[] values)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
/**
@@ -1021,7 +1028,6 @@ public class MediaPlayer implements SubtitleController.Listener
*/
public void setDataSource(FileDescriptor fd, long offset, long length)
throws IOException, IllegalArgumentException, IllegalStateException {
- disableProxyListener();
_setDataSource(fd, offset, length);
}
@@ -1059,13 +1065,35 @@ public class MediaPlayer implements SubtitleController.Listener
*
* @throws IllegalStateException if it is called in an invalid state
*/
- public void start() throws IllegalStateException {
+ public void start() throws IllegalStateException {
+ if (isRestricted()) {
+ _setVolume(0, 0);
+ }
stayAwake(true);
_start();
}
private native void _start() throws IllegalStateException;
+ private boolean isRestricted() {
+ try {
+ final int mode = mAppOps.checkAudioOperation(AppOpsManager.OP_PLAY_AUDIO,
+ getAudioStreamType(), Process.myUid(), ActivityThread.currentPackageName());
+ return mode != AppOpsManager.MODE_ALLOWED;
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ private int getAudioStreamType() {
+ if (mStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
+ mStreamType = _getAudioStreamType();
+ }
+ return mStreamType;
+ }
+
+ private native int _getAudioStreamType() throws IllegalStateException;
+
/**
* Stops playback after playback has been stopped or paused.
*
@@ -1393,8 +1421,6 @@ public class MediaPlayer implements SubtitleController.Listener
if (mEventHandler != null) {
mEventHandler.removeCallbacksAndMessages(null);
}
-
- disableProxyListener();
}
private native void _reset();
@@ -1408,7 +1434,12 @@ public class MediaPlayer implements SubtitleController.Listener
* @param streamtype the audio stream type
* @see android.media.AudioManager
*/
- public native void setAudioStreamType(int streamtype);
+ public void setAudioStreamType(int streamtype) {
+ _setAudioStreamType(streamtype);
+ mStreamType = streamtype;
+ }
+
+ private native void _setAudioStreamType(int streamtype);
/**
* Sets the player to be looping or non-looping.
@@ -1441,7 +1472,14 @@ public class MediaPlayer implements SubtitleController.Listener
* The single parameter form below is preferred if the channel volumes don't need
* to be set independently.
*/
- public native void setVolume(float leftVolume, float rightVolume);
+ public void setVolume(float leftVolume, float rightVolume) {
+ if (isRestricted()) {
+ return;
+ }
+ _setVolume(leftVolume, rightVolume);
+ }
+
+ private native void _setVolume(float leftVolume, float rightVolume);
/**
* Similar, excepts sets volume of all channels to same value.
@@ -1506,7 +1544,14 @@ public class MediaPlayer implements SubtitleController.Listener
* 0 < x <= R -> level = 10^(72*(x-R)/20/R)
* @param level send level scalar
*/
- public native void setAuxEffectSendLevel(float level);
+ public void setAuxEffectSendLevel(float level) {
+ if (isRestricted()) {
+ return;
+ }
+ _setAuxEffectSendLevel(level);
+ }
+
+ private native void _setAuxEffectSendLevel(float level);
/*
* @param request Parcel destinated to the media player. The
@@ -2742,59 +2787,6 @@ public class MediaPlayer implements SubtitleController.Listener
mode == VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING);
}
- private Context mProxyContext = null;
- private ProxyReceiver mProxyReceiver = null;
-
- private void setupProxyListener(Context context) {
- IntentFilter filter = new IntentFilter();
- filter.addAction(Proxy.PROXY_CHANGE_ACTION);
- mProxyReceiver = new ProxyReceiver();
- mProxyContext = context;
-
- Intent currentProxy =
- context.getApplicationContext().registerReceiver(mProxyReceiver, filter);
-
- if (currentProxy != null) {
- handleProxyBroadcast(currentProxy);
- }
- }
-
- private void disableProxyListener() {
- if (mProxyReceiver == null) {
- return;
- }
-
- Context appContext = mProxyContext.getApplicationContext();
- if (appContext != null) {
- appContext.unregisterReceiver(mProxyReceiver);
- }
-
- mProxyReceiver = null;
- mProxyContext = null;
- }
-
- private void handleProxyBroadcast(Intent intent) {
- ProxyProperties props =
- (ProxyProperties)intent.getExtra(Proxy.EXTRA_PROXY_INFO);
-
- if (props == null || props.getHost() == null) {
- updateProxyConfig(null);
- } else {
- updateProxyConfig(props);
- }
- }
-
- private class ProxyReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(Proxy.PROXY_CHANGE_ACTION)) {
- handleProxyBroadcast(intent);
- }
- }
- }
-
- private native void updateProxyConfig(ProxyProperties props);
-
/** @hide */
static class TimeProvider implements MediaPlayer.OnSeekCompleteListener,
MediaTimeProvider {
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 5a9d577..21e2f4b 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -124,6 +124,18 @@ public class MediaRecorder
public native void setCamera(Camera c);
/**
+ * Gets the surface to record from when using SURFACE video source.
+ * <p>
+ * Should only be called after prepare(). Frames rendered before start()
+ * will be discarded.
+ * </p>
+ * @throws IllegalStateException if it is called before prepare(), after
+ * stop() or is called when VideoSource is not set to SURFACE.
+ * @see android.media.MediaRecorder.VideoSource
+ */
+ public native Surface getSurface();
+
+ /**
* Sets a Surface to show a preview of recorded media (video). Calls this
* before prepare() to make sure that the desirable preview display is
* set. If {@link #setCamera(Camera)} is used and the surface has been
@@ -225,10 +237,23 @@ public class MediaRecorder
*/
private VideoSource() {}
public static final int DEFAULT = 0;
- /** Camera video source */
+ /** Camera video source
+ * <p>
+ * Using android.hardware.Camera as video source.
+ * </p>
+ */
public static final int CAMERA = 1;
- /** @hide */
- public static final int GRALLOC_BUFFER = 2;
+ /** Surface video source
+ * <p>
+ * Using a Surface as video source.
+ * </p><p>
+ * This flag must be used when recording from an
+ * android.hardware.camera2.CameraDevice source.
+ * </p><p>
+ * When using this video source type, use {@link MediaRecorder#getSurface()}
+ * to retrieve the surface created by MediaRecorder.
+ */
+ public static final int SURFACE = 2;
}
/**
@@ -392,8 +417,8 @@ public class MediaRecorder
setParameter("time-lapse-enable=1");
double timeBetweenFrameCapture = 1 / fps;
- int timeBetweenFrameCaptureMs = (int) (1000 * timeBetweenFrameCapture);
- setParameter("time-between-time-lapse-frame-capture=" + timeBetweenFrameCaptureMs);
+ long timeBetweenFrameCaptureUs = (long) (1000000 * timeBetweenFrameCapture);
+ setParameter("time-between-time-lapse-frame-capture=" + timeBetweenFrameCaptureUs);
}
/**
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 53835e2..72f3e1a 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -55,6 +55,7 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
@@ -1400,27 +1401,60 @@ public class MediaScanner
return false;
}
- public static boolean isNoMediaPath(String path) {
- if (path == null) return false;
+ private static HashMap<String,String> mNoMediaPaths = new HashMap<String,String>();
+ private static HashMap<String,String> mMediaPaths = new HashMap<String,String>();
+ /* MediaProvider calls this when a .nomedia file is added or removed */
+ public static void clearMediaPathCache(boolean clearMediaPaths, boolean clearNoMediaPaths) {
+ synchronized (MediaScanner.class) {
+ if (clearMediaPaths) {
+ mMediaPaths.clear();
+ }
+ if (clearNoMediaPaths) {
+ mNoMediaPaths.clear();
+ }
+ }
+ }
+
+ public static boolean isNoMediaPath(String path) {
+ if (path == null) {
+ return false;
+ }
// return true if file or any parent directory has name starting with a dot
- if (path.indexOf("/.") >= 0) return true;
-
- // now check to see if any parent directories have a ".nomedia" file
- // start from 1 so we don't bother checking in the root directory
- int offset = 1;
- while (offset >= 0) {
- int slashIndex = path.indexOf('/', offset);
- if (slashIndex > offset) {
- slashIndex++; // move past slash
- File file = new File(path.substring(0, slashIndex) + ".nomedia");
- if (file.exists()) {
- // we have a .nomedia in one of the parent directories
- return true;
+ if (path.indexOf("/.") >= 0) {
+ return true;
+ }
+
+ int firstSlash = path.lastIndexOf('/');
+ if (firstSlash <= 0) {
+ return false;
+ }
+ String parent = path.substring(0, firstSlash);
+
+ synchronized (MediaScanner.class) {
+ if (mNoMediaPaths.containsKey(parent)) {
+ return true;
+ } else if (!mMediaPaths.containsKey(parent)) {
+ // check to see if any parent directories have a ".nomedia" file
+ // start from 1 so we don't bother checking in the root directory
+ int offset = 1;
+ while (offset >= 0) {
+ int slashIndex = path.indexOf('/', offset);
+ if (slashIndex > offset) {
+ slashIndex++; // move past slash
+ File file = new File(path.substring(0, slashIndex) + ".nomedia");
+ if (file.exists()) {
+ // we have a .nomedia in one of the parent directories
+ mNoMediaPaths.put(parent, "");
+ return true;
+ }
+ }
+ offset = slashIndex;
}
+ mMediaPaths.put(parent, "");
}
- offset = slashIndex;
}
+
return isNoMediaFile(path);
}
diff --git a/media/java/android/media/Metadata.java b/media/java/android/media/Metadata.java
index b566653..eb543b4 100644
--- a/media/java/android/media/Metadata.java
+++ b/media/java/android/media/Metadata.java
@@ -16,7 +16,6 @@
package android.media;
-import android.graphics.Bitmap;
import android.os.Parcel;
import android.util.Log;
diff --git a/media/java/android/media/MiniThumbFile.java b/media/java/android/media/MiniThumbFile.java
index 63b149c..23c3652 100644
--- a/media/java/android/media/MiniThumbFile.java
+++ b/media/java/android/media/MiniThumbFile.java
@@ -16,7 +16,6 @@
package android.media;
-import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Environment;
import android.util.Log;
diff --git a/media/java/android/media/PlayerRecord.java b/media/java/android/media/PlayerRecord.java
new file mode 100644
index 0000000..a79b0ed
--- /dev/null
+++ b/media/java/android/media/PlayerRecord.java
@@ -0,0 +1,354 @@
+/*
+ * 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.app.PendingIntent;
+import android.content.ComponentName;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.PrintWriter;
+
+/**
+ * @hide
+ * Class to handle all the information about a media player, encapsulating information
+ * about its use RemoteControlClient, playback type and volume... The lifecycle of each
+ * instance is managed by android.media.MediaFocusControl, from its addition to the player stack
+ * stack to its release.
+ */
+class PlayerRecord implements DeathRecipient {
+
+ // on purpose not using this classe's name, as it will only be used from MediaFocusControl
+ private static final String TAG = "MediaFocusControl";
+ private static final boolean DEBUG = false;
+
+ /**
+ * A global counter for RemoteControlClient identifiers
+ */
+ private static int sLastRccId = 0;
+
+ public static MediaFocusControl sController;
+
+ /**
+ * The target for the ACTION_MEDIA_BUTTON events.
+ * Always non null. //FIXME verify
+ */
+ final private PendingIntent mMediaIntent;
+ /**
+ * The registered media button event receiver.
+ */
+ final private ComponentName mReceiverComponent;
+
+ private int mRccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
+
+ private IBinder mToken;
+ private String mCallingPackageName;
+ private int mCallingUid;
+ /**
+ * Provides access to the information to display on the remote control.
+ * May be null (when a media button event receiver is registered,
+ * but no remote control client has been registered) */
+ private IRemoteControlClient mRcClient;
+ private RcClientDeathHandler mRcClientDeathHandler;
+ /**
+ * Information only used for non-local playback
+ */
+ //FIXME private?
+ public int mPlaybackType;
+ public int mPlaybackVolume;
+ public int mPlaybackVolumeMax;
+ public int mPlaybackVolumeHandling;
+ public int mPlaybackStream;
+ public RccPlaybackState mPlaybackState;
+ public IRemoteVolumeObserver mRemoteVolumeObs;
+
+
+ protected static class RccPlaybackState {
+ public int mState;
+ public long mPositionMs;
+ public float mSpeed;
+
+ public RccPlaybackState(int state, long positionMs, float speed) {
+ mState = state;
+ mPositionMs = positionMs;
+ mSpeed = speed;
+ }
+
+ public void reset() {
+ mState = RemoteControlClient.PLAYSTATE_STOPPED;
+ mPositionMs = RemoteControlClient.PLAYBACK_POSITION_INVALID;
+ mSpeed = RemoteControlClient.PLAYBACK_SPEED_1X;
+ }
+
+ @Override
+ public String toString() {
+ return stateToString() + ", " + posToString() + ", " + mSpeed + "X";
+ }
+
+ private String posToString() {
+ if (mPositionMs == RemoteControlClient.PLAYBACK_POSITION_INVALID) {
+ return "PLAYBACK_POSITION_INVALID";
+ } else if (mPositionMs == RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) {
+ return "PLAYBACK_POSITION_ALWAYS_UNKNOWN";
+ } else {
+ return (String.valueOf(mPositionMs) + "ms");
+ }
+ }
+
+ private String stateToString() {
+ switch (mState) {
+ case RemoteControlClient.PLAYSTATE_NONE:
+ return "PLAYSTATE_NONE";
+ case RemoteControlClient.PLAYSTATE_STOPPED:
+ return "PLAYSTATE_STOPPED";
+ case RemoteControlClient.PLAYSTATE_PAUSED:
+ return "PLAYSTATE_PAUSED";
+ case RemoteControlClient.PLAYSTATE_PLAYING:
+ return "PLAYSTATE_PLAYING";
+ case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
+ return "PLAYSTATE_FAST_FORWARDING";
+ case RemoteControlClient.PLAYSTATE_REWINDING:
+ return "PLAYSTATE_REWINDING";
+ case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
+ return "PLAYSTATE_SKIPPING_FORWARDS";
+ case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
+ return "PLAYSTATE_SKIPPING_BACKWARDS";
+ case RemoteControlClient.PLAYSTATE_BUFFERING:
+ return "PLAYSTATE_BUFFERING";
+ case RemoteControlClient.PLAYSTATE_ERROR:
+ return "PLAYSTATE_ERROR";
+ default:
+ return "[invalid playstate]";
+ }
+ }
+ }
+
+
+ /**
+ * Inner class to monitor remote control client deaths, and remove the client for the
+ * remote control stack if necessary.
+ */
+ private class RcClientDeathHandler implements IBinder.DeathRecipient {
+ final private IBinder mCb; // To be notified of client's death
+ //FIXME needed?
+ final private PendingIntent mMediaIntent;
+
+ RcClientDeathHandler(IBinder cb, PendingIntent pi) {
+ mCb = cb;
+ mMediaIntent = pi;
+ }
+
+ public void binderDied() {
+ Log.w(TAG, " RemoteControlClient died");
+ // remote control client died, make sure the displays don't use it anymore
+ // by setting its remote control client to null
+ sController.registerRemoteControlClient(mMediaIntent, null/*rcClient*/, null/*ignored*/);
+ // the dead client was maybe handling remote playback, the controller should reevaluate
+ sController.postReevaluateRemote();
+ }
+
+ public IBinder getBinder() {
+ return mCb;
+ }
+ }
+
+
+ protected static class RemotePlaybackState {
+ int mRccId;
+ int mVolume;
+ int mVolumeMax;
+ int mVolumeHandling;
+
+ protected RemotePlaybackState(int id, int vol, int volMax) {
+ mRccId = id;
+ mVolume = vol;
+ mVolumeMax = volMax;
+ mVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
+ }
+ }
+
+
+ void dump(PrintWriter pw, boolean registrationInfo) {
+ if (registrationInfo) {
+ pw.println(" pi: " + mMediaIntent +
+ " -- pack: " + mCallingPackageName +
+ " -- ercvr: " + mReceiverComponent +
+ " -- client: " + mRcClient +
+ " -- uid: " + mCallingUid +
+ " -- type: " + mPlaybackType +
+ " state: " + mPlaybackState);
+ } else {
+ // emphasis on state
+ pw.println(" uid: " + mCallingUid +
+ " -- id: " + mRccId +
+ " -- type: " + mPlaybackType +
+ " -- state: " + mPlaybackState +
+ " -- vol handling: " + mPlaybackVolumeHandling +
+ " -- vol: " + mPlaybackVolume +
+ " -- volMax: " + mPlaybackVolumeMax +
+ " -- volObs: " + mRemoteVolumeObs);
+ }
+ }
+
+
+ static protected void setMediaFocusControl(MediaFocusControl mfc) {
+ sController = mfc;
+ }
+
+ /** precondition: mediaIntent != null */
+ protected PlayerRecord(PendingIntent mediaIntent, ComponentName eventReceiver, IBinder token)
+ {
+ mMediaIntent = mediaIntent;
+ mReceiverComponent = eventReceiver;
+ mToken = token;
+ mCallingUid = -1;
+ mRcClient = null;
+ mRccId = ++sLastRccId;
+ mPlaybackState = new RccPlaybackState(
+ RemoteControlClient.PLAYSTATE_STOPPED,
+ RemoteControlClient.PLAYBACK_POSITION_INVALID,
+ RemoteControlClient.PLAYBACK_SPEED_1X);
+
+ resetPlaybackInfo();
+ if (mToken != null) {
+ try {
+ mToken.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ sController.unregisterMediaButtonIntentAsync(mMediaIntent);
+ }
+ }
+ }
+
+ //---------------------------------------------
+ // Accessors
+ protected int getRccId() {
+ return mRccId;
+ }
+
+ protected IRemoteControlClient getRcc() {
+ return mRcClient;
+ }
+
+ protected ComponentName getMediaButtonReceiver() {
+ return mReceiverComponent;
+ }
+
+ protected PendingIntent getMediaButtonIntent() {
+ return mMediaIntent;
+ }
+
+ // FIXME this is only used when comparing with the audio focus owner calling package name,
+ // accessor to be removed once audio focus and media button owner are dissociated
+ protected String getCallingPackageName() {
+ return mCallingPackageName;
+ }
+
+ // FIXME this is only used when comparing with the audio focus owner calling package name,
+ // accessor to be removed once audio focus and media button owner are dissociated
+ protected int getCallingUid() {
+ return mCallingUid;
+ }
+
+ protected boolean hasMatchingMediaButtonIntent(PendingIntent pi) {
+ return mMediaIntent.equals(pi);
+ }
+
+ //---------------------------------------------
+ // Modify the records stored in the instance
+ protected void resetControllerInfoForRcc(IRemoteControlClient rcClient,
+ String callingPackageName, int uid) {
+ // already had a remote control client?
+ if (mRcClientDeathHandler != null) {
+ // stop monitoring the old client's death
+ unlinkToRcClientDeath();
+ }
+ // save the new remote control client
+ mRcClient = rcClient;
+ mCallingPackageName = callingPackageName;
+ mCallingUid = uid;
+ if (rcClient == null) {
+ // here mcse.mRcClientDeathHandler is null;
+ resetPlaybackInfo();
+ } else {
+ IBinder b = mRcClient.asBinder();
+ RcClientDeathHandler rcdh =
+ new RcClientDeathHandler(b, mMediaIntent);
+ try {
+ b.linkToDeath(rcdh, 0);
+ } catch (RemoteException e) {
+ // remote control client is DOA, disqualify it
+ Log.w(TAG, "registerRemoteControlClient() has a dead client " + b);
+ mRcClient = null;
+ }
+ mRcClientDeathHandler = rcdh;
+ }
+ }
+
+ protected void resetControllerInfoForNoRcc() {
+ // stop monitoring the RCC death
+ unlinkToRcClientDeath();
+ // reset the RCC-related fields
+ mRcClient = null;
+ mCallingPackageName = null;
+ }
+
+ public void resetPlaybackInfo() {
+ mPlaybackType = RemoteControlClient.PLAYBACK_TYPE_LOCAL;
+ mPlaybackVolume = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
+ mPlaybackVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
+ mPlaybackVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
+ mPlaybackStream = AudioManager.STREAM_MUSIC;
+ mPlaybackState.reset();
+ mRemoteVolumeObs = null;
+ }
+
+ //---------------------------------------------
+ public void unlinkToRcClientDeath() {
+ if ((mRcClientDeathHandler != null) && (mRcClientDeathHandler.mCb != null)) {
+ try {
+ mRcClientDeathHandler.mCb.unlinkToDeath(mRcClientDeathHandler, 0);
+ mRcClientDeathHandler = null;
+ } catch (java.util.NoSuchElementException e) {
+ // not much we can do here
+ Log.e(TAG, "Error in unlinkToRcClientDeath()", e);
+ }
+ }
+ }
+
+ // FIXME rename to "release"? (as in FocusRequester class)
+ public void destroy() {
+ unlinkToRcClientDeath();
+ if (mToken != null) {
+ mToken.unlinkToDeath(this, 0);
+ mToken = null;
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ sController.unregisterMediaButtonIntentAsync(mMediaIntent);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ destroy(); // unlink exception handled inside method
+ super.finalize();
+ }
+}
diff --git a/media/java/android/media/Rating.java b/media/java/android/media/Rating.java
index 82c0392..f4fbe2c 100644
--- a/media/java/android/media/Rating.java
+++ b/media/java/android/media/Rating.java
@@ -16,7 +16,6 @@
package android.media;
-import android.graphics.Bitmap;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
@@ -30,8 +29,13 @@ import android.util.Log;
* through one of the factory methods.
*/
public final class Rating implements Parcelable {
-
private final static String TAG = "Rating";
+ /**
+ * Indicates a rating style is not supported. A Rating will never have this
+ * type, but can be used by other classes to indicate they do not support
+ * Rating.
+ */
+ public final static int RATING_NONE = 0;
/**
* A rating style with a single degree of rating, "heart" vs "no heart". Can be used to
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index 0c00aba..c2c61d3 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -24,13 +24,11 @@ import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
-import android.media.MediaMetadataRetriever;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
-import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
diff --git a/media/java/android/media/RemoteController.java b/media/java/android/media/RemoteController.java
index 910b24c..3711585 100644
--- a/media/java/android/media/RemoteController.java
+++ b/media/java/android/media/RemoteController.java
@@ -16,7 +16,6 @@
package android.media;
-import android.Manifest;
import android.app.ActivityManager;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
@@ -29,8 +28,6 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemClock;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -267,7 +264,7 @@ public final class RemoteController
* @throws IllegalArgumentException
*/
public boolean sendMediaKeyEvent(KeyEvent keyEvent) throws IllegalArgumentException {
- if (!MediaFocusControl.isMediaKeyCode(keyEvent.getKeyCode())) {
+ if (!KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
throw new IllegalArgumentException("not a media key event");
}
final PendingIntent pi;
diff --git a/media/java/android/media/ResampleInputStream.java b/media/java/android/media/ResampleInputStream.java
index b025e25..80919f7 100644
--- a/media/java/android/media/ResampleInputStream.java
+++ b/media/java/android/media/ResampleInputStream.java
@@ -16,8 +16,6 @@
package android.media;
-import android.util.Log;
-
import java.io.InputStream;
import java.io.IOException;
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index 8e4004b..e211b99 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -23,7 +23,6 @@ import android.annotation.SdkConstant.SdkConstantType;
import android.app.Activity;
import android.content.ContentUris;
import android.content.Context;
-import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
diff --git a/media/java/android/media/SoundPool.java b/media/java/android/media/SoundPool.java
index fbfc574..14f0c69 100644
--- a/media/java/android/media/SoundPool.java
+++ b/media/java/android/media/SoundPool.java
@@ -18,19 +18,26 @@ package android.media;
import java.io.File;
import java.io.FileDescriptor;
-import java.io.IOException;
import java.lang.ref.WeakReference;
+import android.app.ActivityThread;
+import android.app.AppOpsManager;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemProperties;
import android.util.AndroidRuntimeException;
import android.util.Log;
+import com.android.internal.app.IAppOpsService;
+
/**
* The SoundPool class manages and plays audio resources for applications.
@@ -450,6 +457,8 @@ public class SoundPool {
private SoundPool mProxy;
private final Object mLock;
+ private final int mStreamType;
+ private final IAppOpsService mAppOps;
// SoundPool messages
//
@@ -464,6 +473,9 @@ public class SoundPool {
}
mLock = new Object();
mProxy = proxy;
+ mStreamType = streamType;
+ IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
+ mAppOps = IAppOpsService.Stub.asInterface(b);
}
public int load(String path, int priority)
@@ -523,9 +535,27 @@ public class SoundPool {
public native final boolean unload(int soundID);
- public native final int play(int soundID, float leftVolume, float rightVolume,
+ public final int play(int soundID, float leftVolume, float rightVolume,
+ int priority, int loop, float rate) {
+ if (isRestricted()) {
+ leftVolume = rightVolume = 0;
+ }
+ return _play(soundID, leftVolume, rightVolume, priority, loop, rate);
+ }
+
+ public native final int _play(int soundID, float leftVolume, float rightVolume,
int priority, int loop, float rate);
+ private boolean isRestricted() {
+ try {
+ final int mode = mAppOps.checkAudioOperation(AppOpsManager.OP_PLAY_AUDIO,
+ mStreamType, Process.myUid(), ActivityThread.currentPackageName());
+ return mode != AppOpsManager.MODE_ALLOWED;
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
public native final void pause(int streamID);
public native final void resume(int streamID);
@@ -536,8 +566,14 @@ public class SoundPool {
public native final void stop(int streamID);
- public native final void setVolume(int streamID,
- float leftVolume, float rightVolume);
+ public final void setVolume(int streamID, float leftVolume, float rightVolume) {
+ if (isRestricted()) {
+ return;
+ }
+ _setVolume(streamID, leftVolume, rightVolume);
+ }
+
+ private native final void _setVolume(int streamID, float leftVolume, float rightVolume);
public void setVolume(int streamID, float volume) {
setVolume(streamID, volume, volume);
diff --git a/media/java/android/media/SubtitleData.java b/media/java/android/media/SubtitleData.java
index f552e82..3e6f6f9 100644
--- a/media/java/android/media/SubtitleData.java
+++ b/media/java/android/media/SubtitleData.java
@@ -17,7 +17,6 @@
package android.media;
import android.os.Parcel;
-import android.util.Log;
/**
* @hide
diff --git a/media/java/android/media/ThumbnailUtils.java b/media/java/android/media/ThumbnailUtils.java
index 756638c..daa5fa5 100644
--- a/media/java/android/media/ThumbnailUtils.java
+++ b/media/java/android/media/ThumbnailUtils.java
@@ -17,9 +17,6 @@
package android.media;
import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
@@ -29,15 +26,12 @@ import android.media.MediaMetadataRetriever;
import android.media.MediaFile.MediaFileType;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
-import android.provider.BaseColumns;
import android.provider.MediaStore.Images;
-import android.provider.MediaStore.Images.Thumbnails;
import android.util.Log;
import java.io.FileInputStream;
import java.io.FileDescriptor;
import java.io.IOException;
-import java.io.OutputStream;
/**
* Thumbnail generation routines for media provider.
diff --git a/media/java/android/media/WebVttRenderer.java b/media/java/android/media/WebVttRenderer.java
index 4dec081..1c9730f 100644
--- a/media/java/android/media/WebVttRenderer.java
+++ b/media/java/android/media/WebVttRenderer.java
@@ -558,7 +558,11 @@ class TextTrackCue extends SubtitleTrack.Cue {
}
}
-/** @hide */
+/**
+ * Supporting July 10 2013 draft version
+ *
+ * @hide
+ */
class WebVttParser {
private static final String TAG = "WebVttParser";
private Phase mPhase;
@@ -726,15 +730,15 @@ class WebVttParser {
"has invalid value", e.getMessage(), value);
}
} else if (name.equals("lines")) {
- try {
- int lines = Integer.parseInt(value);
- if (lines >= 0) {
- region.mLines = lines;
- } else {
- log_warning("region setting", name, "is negative", value);
+ if (value.matches(".*[^0-9].*")) {
+ log_warning("lines", name, "contains an invalid character", value);
+ } else {
+ try {
+ region.mLines = Integer.parseInt(value);
+ assert(region.mLines >= 0); // lines contains only digits
+ } catch (NumberFormatException e) {
+ log_warning("region setting", name, "is not numeric", value);
}
- } catch (NumberFormatException e) {
- log_warning("region setting", name, "is not numeric", value);
}
} else if (name.equals("regionanchor") ||
name.equals("viewportanchor")) {
@@ -872,26 +876,23 @@ class WebVttParser {
}
} else if (name.equals("line")) {
try {
- int linePosition;
/* TRICKY: we know that there are no spaces in value */
assert(value.indexOf(' ') < 0);
if (value.endsWith("%")) {
- linePosition = Integer.parseInt(
- value.substring(0, value.length() - 1));
- if (linePosition < 0 || linePosition > 100) {
- log_warning("cue setting", name, "is out of range", value);
- continue;
- }
mCue.mSnapToLines = false;
- mCue.mLinePosition = linePosition;
+ mCue.mLinePosition = parseIntPercentage(value);
+ } else if (value.matches(".*[^0-9].*")) {
+ log_warning("cue setting", name,
+ "contains an invalid character", value);
} else {
mCue.mSnapToLines = true;
mCue.mLinePosition = Integer.parseInt(value);
}
} catch (NumberFormatException e) {
log_warning("cue setting", name,
- "is not numeric or percentage", value);
+ "is not numeric or percentage", value);
}
+ // TODO: add support for optional alignment value [,start|middle|end]
} else if (name.equals("position")) {
try {
mCue.mTextPosition = parseIntPercentage(value);
@@ -1136,11 +1137,15 @@ class WebVttRenderingWidget extends ViewGroup implements SubtitleTrack.Rendering
}
public WebVttRenderingWidget(Context context, AttributeSet attrs) {
- this(context, null, 0);
+ this(context, attrs, 0);
+ }
+
+ public WebVttRenderingWidget(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
}
- public WebVttRenderingWidget(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public WebVttRenderingWidget(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);
@@ -1521,6 +1526,8 @@ class WebVttRenderingWidget extends ViewGroup implements SubtitleTrack.Rendering
if (DEBUG) {
setBackgroundColor(DEBUG_REGION_BACKGROUND);
+ } else {
+ setBackgroundColor(captionStyle.windowColor);
}
}
@@ -1533,6 +1540,8 @@ class WebVttRenderingWidget extends ViewGroup implements SubtitleTrack.Rendering
final CueLayout cueBox = mRegionCueBoxes.get(i);
cueBox.setCaptionStyle(captionStyle, fontSize);
}
+
+ setBackgroundColor(captionStyle.windowColor);
}
/**
diff --git a/media/java/android/media/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java
index cc121a3..9b381cc 100644
--- a/media/java/android/media/audiofx/AudioEffect.java
+++ b/media/java/android/media/audiofx/AudioEffect.java
@@ -22,7 +22,6 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
-import java.io.IOException;
import java.lang.ref.WeakReference;
import java.nio.ByteOrder;
import java.nio.ByteBuffer;
diff --git a/media/java/android/media/audiofx/BassBoost.java b/media/java/android/media/audiofx/BassBoost.java
index 91459ed..a46cc22 100644
--- a/media/java/android/media/audiofx/BassBoost.java
+++ b/media/java/android/media/audiofx/BassBoost.java
@@ -16,16 +16,9 @@
package android.media.audiofx;
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
import android.media.audiofx.AudioEffect;
-import android.os.Bundle;
import android.util.Log;
-import java.nio.ByteOrder;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
import java.util.StringTokenizer;
diff --git a/media/java/android/media/audiofx/EnvironmentalReverb.java b/media/java/android/media/audiofx/EnvironmentalReverb.java
index f1f582e..ef1c4c3 100644
--- a/media/java/android/media/audiofx/EnvironmentalReverb.java
+++ b/media/java/android/media/audiofx/EnvironmentalReverb.java
@@ -16,15 +16,7 @@
package android.media.audiofx;
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
import android.media.audiofx.AudioEffect;
-import android.os.Bundle;
-import android.util.Log;
-
-import java.nio.ByteOrder;
-import java.nio.ByteBuffer;
import java.util.StringTokenizer;
/**
diff --git a/media/java/android/media/audiofx/Equalizer.java b/media/java/android/media/audiofx/Equalizer.java
index 7f38955..7abada0 100644
--- a/media/java/android/media/audiofx/Equalizer.java
+++ b/media/java/android/media/audiofx/Equalizer.java
@@ -16,16 +16,9 @@
package android.media.audiofx;
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
import android.media.audiofx.AudioEffect;
-import android.os.Bundle;
import android.util.Log;
-import java.nio.ByteOrder;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
import java.util.StringTokenizer;
diff --git a/media/java/android/media/audiofx/PresetReverb.java b/media/java/android/media/audiofx/PresetReverb.java
index 7a89ae7..ef91667 100644
--- a/media/java/android/media/audiofx/PresetReverb.java
+++ b/media/java/android/media/audiofx/PresetReverb.java
@@ -16,15 +16,7 @@
package android.media.audiofx;
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
import android.media.audiofx.AudioEffect;
-import android.os.Bundle;
-import android.util.Log;
-
-import java.nio.ByteOrder;
-import java.nio.ByteBuffer;
import java.util.StringTokenizer;
diff --git a/media/java/android/media/audiofx/Virtualizer.java b/media/java/android/media/audiofx/Virtualizer.java
index 68a7b88..6b20006 100644
--- a/media/java/android/media/audiofx/Virtualizer.java
+++ b/media/java/android/media/audiofx/Virtualizer.java
@@ -16,16 +16,9 @@
package android.media.audiofx;
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
import android.media.audiofx.AudioEffect;
-import android.os.Bundle;
import android.util.Log;
-import java.nio.ByteOrder;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
import java.util.StringTokenizer;
diff --git a/media/java/android/media/audiofx/Visualizer.java b/media/java/android/media/audiofx/Visualizer.java
index ff04201..24c74ac 100644
--- a/media/java/android/media/audiofx/Visualizer.java
+++ b/media/java/android/media/audiofx/Visualizer.java
@@ -18,7 +18,6 @@ package android.media.audiofx;
import android.util.Log;
import java.lang.ref.WeakReference;
-import java.io.IOException;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
diff --git a/media/java/android/media/routeprovider/IRouteConnection.aidl b/media/java/android/media/routeprovider/IRouteConnection.aidl
new file mode 100644
index 0000000..15c8039
--- /dev/null
+++ b/media/java/android/media/routeprovider/IRouteConnection.aidl
@@ -0,0 +1,28 @@
+/* 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.routeprovider;
+
+import android.media.session.RouteCommand;
+import android.os.ResultReceiver;
+
+/**
+ * Interface for a specific connected route.
+ * @hide
+ */
+oneway interface IRouteConnection {
+ void onCommand(in RouteCommand command, in ResultReceiver cb);
+ void disconnect();
+} \ No newline at end of file
diff --git a/media/java/android/media/routeprovider/IRouteProvider.aidl b/media/java/android/media/routeprovider/IRouteProvider.aidl
new file mode 100644
index 0000000..c36f6a7
--- /dev/null
+++ b/media/java/android/media/routeprovider/IRouteProvider.aidl
@@ -0,0 +1,36 @@
+/* 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.routeprovider;
+
+import android.media.routeprovider.IRouteConnection;
+import android.media.routeprovider.IRouteProviderCallback;
+import android.media.routeprovider.RouteRequest;
+import android.media.session.RouteInfo;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+
+/**
+ * Interface to an app's RouteProviderService.
+ * @hide
+ */
+oneway interface IRouteProvider {
+ void registerCallback(in IRouteProviderCallback cb);
+ void unregisterCallback(in IRouteProviderCallback cb);
+ void updateDiscoveryRequests(in List<RouteRequest> requests);
+
+ void getAvailableRoutes(in List<RouteRequest> requests, in ResultReceiver cb);
+ void connect(in RouteInfo route, in RouteRequest request, in ResultReceiver cb);
+} \ No newline at end of file
diff --git a/media/java/android/media/routeprovider/IRouteProviderCallback.aidl b/media/java/android/media/routeprovider/IRouteProviderCallback.aidl
new file mode 100644
index 0000000..9185347
--- /dev/null
+++ b/media/java/android/media/routeprovider/IRouteProviderCallback.aidl
@@ -0,0 +1,32 @@
+/* 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.routeprovider;
+
+import android.media.routeprovider.IRouteConnection;
+import android.media.session.RouteEvent;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+
+/**
+ * System's provider callback interface.
+ * @hide
+ */
+oneway interface IRouteProviderCallback {
+ void onRoutesChanged();
+ void onConnectionStateChanged(in IRouteConnection connection, int state);
+ void onConnectionTerminated(in IRouteConnection connection);
+ void onRouteEvent(in RouteEvent event);
+} \ No newline at end of file
diff --git a/media/java/android/media/routeprovider/RouteConnection.java b/media/java/android/media/routeprovider/RouteConnection.java
new file mode 100644
index 0000000..9214ff8
--- /dev/null
+++ b/media/java/android/media/routeprovider/RouteConnection.java
@@ -0,0 +1,164 @@
+/*
+ * 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.routeprovider;
+
+import android.media.routeprovider.IRouteConnection;
+import android.media.session.RouteCommand;
+import android.media.session.RouteEvent;
+import android.media.session.RouteInfo;
+import android.media.session.RouteInterface;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents an ongoing connection between an application and a media route
+ * offered by a media route provider.
+ * <p>
+ * The media route provider should add interfaces to the connection before
+ * returning it to the system in order to receive commands from clients on those
+ * 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.
+ */
+public final class RouteConnection {
+ private static final String TAG = "RouteConnection";
+ private final ConnectionStub mBinder;
+ private final ArrayList<String> mIfaceNames = new ArrayList<String>();
+ private final ArrayMap<String, RouteInterfaceHandler> mIfaces
+ = new ArrayMap<String, RouteInterfaceHandler>();
+ private final RouteProviderService mProvider;
+ private final RouteInfo mRoute;
+
+ private boolean mPublished;
+
+ /**
+ * Create a new connection for the given Provider and Route.
+ *
+ * @param provider The provider this route is associated with.
+ * @param route The route this is a connection to.
+ */
+ public RouteConnection(RouteProviderService provider, RouteInfo route) {
+ if (provider == null) {
+ throw new IllegalArgumentException("provider may not be null.");
+ }
+ if (route == null) {
+ throw new IllegalArgumentException("route may not be null.");
+ }
+ mBinder = new ConnectionStub(this);
+ mProvider = provider;
+ mRoute = route;
+ }
+
+ /**
+ * Add an interface to this route connection. All interfaces must be added
+ * to the connection before the connection is returned to the system.
+ *
+ * @param ifaceName The name of the interface to add
+ * @return The route interface that was registered
+ */
+ public RouteInterfaceHandler addRouteInterface(String ifaceName) {
+ if (TextUtils.isEmpty(ifaceName)) {
+ throw new IllegalArgumentException("The interface's name may not be empty");
+ }
+ if (mPublished) {
+ throw new IllegalStateException(
+ "Connection has already been published to the system.");
+ }
+ RouteInterfaceHandler iface = mIfaces.get(ifaceName);
+ if (iface == null) {
+ iface = new RouteInterfaceHandler(this, ifaceName);
+ mIfaceNames.add(ifaceName);
+ mIfaces.put(ifaceName, iface);
+ } else {
+ Log.w(TAG, "Attempted to add an interface that already exists");
+ }
+ return iface;
+ }
+
+ /**
+ * Get the interface instance for the specified interface name. If the
+ * interface was not added to this connection null will be returned.
+ *
+ * @param ifaceName The name of the interface to get.
+ * @return The route interface with that name or null.
+ */
+ public RouteInterfaceHandler getRouteInterface(String ifaceName) {
+ return mIfaces.get(ifaceName);
+ }
+
+ /**
+ * Close the connection and inform the system that it may no longer be used.
+ */
+ public void shutDown() {
+ mProvider.disconnect(this);
+ }
+
+ /**
+ * @hide
+ */
+ public void sendEvent(String iface, String event, Bundle extras) {
+ RouteEvent e = new RouteEvent(mBinder, iface, event, extras);
+ mProvider.sendRouteEvent(e);
+ }
+
+ /**
+ * @hide
+ */
+ IRouteConnection.Stub getBinder() {
+ return mBinder;
+ }
+
+ /**
+ * @hide
+ */
+ void publish() {
+ mPublished = true;
+ }
+
+ private static class ConnectionStub extends IRouteConnection.Stub {
+ private final WeakReference<RouteConnection> mConnection;
+
+ public ConnectionStub(RouteConnection connection) {
+ mConnection = new WeakReference<RouteConnection>(connection);
+ }
+
+ @Override
+ public void onCommand(RouteCommand command, ResultReceiver cb) {
+ RouteConnection connection = mConnection.get();
+ if (connection != null) {
+ RouteInterfaceHandler iface = connection.mIfaces.get(command.getIface());
+ if (iface != null) {
+ iface.onCommand(command.getEvent(), command.getExtras(), cb);
+ } else if (cb != null) {
+ cb.send(RouteInterface.RESULT_INTERFACE_NOT_SUPPORTED, null);
+ }
+ }
+ }
+
+ @Override
+ public void disconnect() {
+ // TODO
+ }
+ }
+}
diff --git a/media/java/android/media/routeprovider/RouteInterfaceHandler.java b/media/java/android/media/routeprovider/RouteInterfaceHandler.java
new file mode 100644
index 0000000..9693dc6
--- /dev/null
+++ b/media/java/android/media/routeprovider/RouteInterfaceHandler.java
@@ -0,0 +1,245 @@
+/*
+ * 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.routeprovider;
+
+import android.media.session.Route;
+import android.media.session.Session;
+import android.media.session.RouteInterface;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ResultReceiver;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * Represents an interface that an application may use to send requests to a
+ * connected media route.
+ * <p>
+ * A {@link RouteProviderService} may expose multiple interfaces on a
+ * {@link RouteConnection} for a {@link Session} 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
+ * all commands being sent on the interface will be sent to any registered
+ * {@link CommandListener}s.
+ * <p>
+ * An interface instance can only be registered on one {@link RouteConnection}.
+ * To use the same interface on multiple connections a new instance must be
+ * created for each connection.
+ * <p>
+ * 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.
+ */
+public final class RouteInterfaceHandler {
+ private static final String TAG = "RouteInterfaceHandler";
+
+ private final Object mLock = new Object();
+ private final RouteConnection mConnection;
+ private final String mName;
+
+ private ArrayList<MessageHandler> mListeners = new ArrayList<MessageHandler>();
+
+ /**
+ * Create a new RouteInterface for a given connection. This can be used to
+ * send events on the given interface and register listeners for commands
+ * from the connected session.
+ *
+ * @param connection The connection this interface sends events on
+ * @param ifaceName The name of this interface
+ * @hide
+ */
+ public RouteInterfaceHandler(RouteConnection connection, String ifaceName) {
+ if (connection == null) {
+ throw new IllegalArgumentException("connection may not be null");
+ }
+ if (TextUtils.isEmpty(ifaceName)) {
+ throw new IllegalArgumentException("ifaceName can not be empty");
+ }
+ mConnection = connection;
+ mName = ifaceName;
+ }
+
+ /**
+ * Send an event on this interface to the connected session.
+ *
+ * @param event The event to send
+ * @param extras Any extras for the event
+ */
+ public void sendEvent(String event, Bundle extras) {
+ mConnection.sendEvent(mName, event, extras);
+ }
+
+ /**
+ * Send a result from a command to the specified callback. The result codes
+ * in {@link RouteInterface} must be used. More information
+ * about the result, whether successful or an error, should be included in
+ * the extras.
+ *
+ * @param cb The callback to send the result to
+ * @param resultCode The result code for the call
+ * @param extras Any extras to include
+ */
+ public static void sendResult(ResultReceiver cb, int resultCode, Bundle extras) {
+ if (cb != null) {
+ cb.send(resultCode, extras);
+ }
+ }
+
+ /**
+ * Add a listener for this interface. If a handler is specified callbacks
+ * will be performed on the handler's thread, otherwise the callers thread
+ * will be used.
+ *
+ * @param listener The listener to receive calls on.
+ * @param handler The handler whose thread to post calls on or null.
+ */
+ public void addListener(CommandListener listener, Handler handler) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener may not be null");
+ }
+ Looper looper = handler != null ? handler.getLooper() : Looper.myLooper();
+ synchronized (mLock) {
+ if (findIndexOfListenerLocked(listener) != -1) {
+ Log.d(TAG, "Listener is already added, ignoring");
+ return;
+ }
+ mListeners.add(new MessageHandler(looper, listener));
+ }
+ }
+
+ /**
+ * Remove a listener from this interface.
+ *
+ * @param listener The listener to stop receiving commands on.
+ */
+ public void removeListener(CommandListener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener may not be null");
+ }
+ synchronized (mLock) {
+ int index = findIndexOfListenerLocked(listener);
+ if (index != -1) {
+ mListeners.remove(index);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void onCommand(String command, Bundle args, ResultReceiver cb) {
+ synchronized (mLock) {
+ Command cmd = new Command(command, args, cb);
+ for (int i = mListeners.size() - 1; i >= 0; i--) {
+ mListeners.get(i).post(MessageHandler.MSG_COMMAND, cmd);
+ }
+ }
+ }
+
+ /**
+ * Get the interface name.
+ *
+ * @return The name of this interface
+ */
+ public String getName() {
+ return mName;
+ }
+
+ private int findIndexOfListenerLocked(CommandListener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("Callback cannot be null");
+ }
+ for (int i = mListeners.size() - 1; i >= 0; i--) {
+ MessageHandler handler = mListeners.get(i);
+ if (listener == handler.mListener) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Handles commands sent to the interface.
+ * <p>
+ * Register an InterfaceListener using {@link #addListener}.
+ */
+ 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
+ * connected to the route this interface is registered with.
+ *
+ * @param iface The interface the command was received on.
+ * @param command The command or method to invoke.
+ * @param args Any args that were included with the command. May be
+ * null.
+ * @param cb The callback provided to send a response on. May be null.
+ * @return true if the command was handled, false otherwise. If the
+ * command was not handled an error will be sent automatically.
+ * true may be returned if the command will be handled
+ * asynchronously.
+ * @see Route
+ * @see Session
+ */
+ public abstract boolean onCommand(RouteInterfaceHandler iface, String command, Bundle args,
+ ResultReceiver cb);
+ }
+
+ private class MessageHandler extends Handler {
+ private static final int MSG_COMMAND = 1;
+
+ private final CommandListener mListener;
+
+ public MessageHandler(Looper looper, CommandListener listener) {
+ super(looper, null, true /* async */);
+ mListener = listener;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_COMMAND:
+ Command cmd = (Command) msg.obj;
+ if (!mListener.onCommand(RouteInterfaceHandler.this, cmd.command, cmd.args, cmd.cb)) {
+ sendResult(cmd.cb, RouteInterface.RESULT_COMMAND_NOT_SUPPORTED,
+ null);
+ }
+ break;
+ }
+ }
+
+ public void post(int what, Object obj) {
+ obtainMessage(what, obj).sendToTarget();
+ }
+ }
+
+ private final static class Command {
+ public final String command;
+ public final Bundle args;
+ public final ResultReceiver cb;
+
+ public Command(String command, Bundle args, ResultReceiver cb) {
+ this.command = command;
+ this.args = args;
+ this.cb = cb;
+ }
+ }
+}
diff --git a/media/java/android/media/routeprovider/RoutePlaybackControlsHandler.java b/media/java/android/media/routeprovider/RoutePlaybackControlsHandler.java
new file mode 100644
index 0000000..dcef79a
--- /dev/null
+++ b/media/java/android/media/routeprovider/RoutePlaybackControlsHandler.java
@@ -0,0 +1,221 @@
+/*
+ * 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.routeprovider;
+
+import android.media.session.RoutePlaybackControls;
+import android.media.session.RouteInterface;
+import android.media.session.PlaybackState;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.ResultReceiver;
+import android.text.TextUtils;
+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.
+ */
+public final class RoutePlaybackControlsHandler {
+ private static final String TAG = "RoutePlaybackControls";
+
+ private final RouteInterfaceHandler mIface;
+
+ private RoutePlaybackControlsHandler(RouteInterfaceHandler iface) {
+ mIface = iface;
+ }
+
+ /**
+ * Add this interface to the specified route and return a handle for
+ * communicating on the interface.
+ *
+ * @param connection The connection to register this interface on.
+ * @return A handle for communicating on this interface.
+ */
+ public static RoutePlaybackControlsHandler addTo(RouteConnection connection) {
+ if (connection == null) {
+ throw new IllegalArgumentException("connection may not be null");
+ }
+ RouteInterfaceHandler iface = connection
+ .addRouteInterface(RoutePlaybackControls.NAME);
+
+ return new RoutePlaybackControlsHandler(iface);
+ }
+
+ /**
+ * Add a {@link Listener} to this interface. The listener will receive
+ * commands on the caller's thread.
+ *
+ * @param listener The listener to send commands to.
+ */
+ public void addListener(Listener listener) {
+ addListener(listener, null);
+ }
+
+ /**
+ * Add a {@link Listener} to this interface. The listener will receive
+ * updates on the handler's thread. If no handler is specified the caller's
+ * thread will be used instead.
+ *
+ * @param listener The listener to send commands to.
+ * @param handler The handler whose thread calls should be posted on. May be
+ * null.
+ */
+ public void addListener(Listener listener, Handler handler) {
+ mIface.addListener(listener, handler);
+ }
+
+ /**
+ * Remove a {@link Listener} from this interface.
+ *
+ * @param listener The Listener to remove.
+ */
+ public void removeListener(Listener listener) {
+ mIface.removeListener(listener);
+ }
+
+ /**
+ * Publish the current playback state to the system and any controllers.
+ * Valid values are defined in {@link PlaybackState}. TODO create
+ * RoutePlaybackState.
+ *
+ * @param state
+ */
+ public void sendPlaybackChangeEvent(int state) {
+ Bundle extras = new Bundle();
+ extras.putInt(RoutePlaybackControls.KEY_VALUE1, state);
+ mIface.sendEvent(RoutePlaybackControls.EVENT_PLAYSTATE_CHANGE, extras);
+ }
+
+ /**
+ * Command handler for the RoutePlaybackControls interface. You can add a
+ * Listener to the interface using {@link #addListener}.
+ */
+ public static abstract class Listener extends RouteInterfaceHandler.CommandListener {
+
+ @Override
+ public final boolean onCommand(RouteInterfaceHandler iface, String method, Bundle extras,
+ ResultReceiver cb) {
+ if (RoutePlaybackControls.CMD_FAST_FORWARD.equals(method)) {
+ boolean success = fastForward();
+ // TODO specify type of error
+ RouteInterfaceHandler.sendResult(cb, success
+ ? RouteInterface.RESULT_SUCCESS
+ : RouteInterface.RESULT_ERROR, null);
+ return true;
+ } else if (RoutePlaybackControls.CMD_GET_CURRENT_POSITION.equals(method)) {
+ Bundle result = new Bundle();
+ result.putLong(RoutePlaybackControls.KEY_VALUE1, getCurrentPosition());
+ RouteInterfaceHandler.sendResult(cb, RouteInterface.RESULT_SUCCESS,
+ result);
+ return true;
+ } else if (RoutePlaybackControls.CMD_GET_CAPABILITIES.equals(method)) {
+ Bundle result = new Bundle();
+ result.putLong(RoutePlaybackControls.KEY_VALUE1, getCapabilities());
+ RouteInterfaceHandler.sendResult(cb, RouteInterface.RESULT_SUCCESS,
+ result);
+ return true;
+ } else if (RoutePlaybackControls.CMD_PLAY_NOW.equals(method)) {
+ playNow(extras.getString(RoutePlaybackControls.KEY_VALUE1, null), cb);
+ return true;
+ } else if (RoutePlaybackControls.CMD_RESUME.equals(method)) {
+ boolean success = resume();
+ RouteInterfaceHandler.sendResult(cb, success
+ ? RouteInterface.RESULT_SUCCESS
+ : RouteInterface.RESULT_ERROR, null);
+ return true;
+ } else if (RoutePlaybackControls.CMD_PAUSE.equals(method)) {
+ boolean success = pause();
+ RouteInterfaceHandler.sendResult(cb, success
+ ? RouteInterface.RESULT_SUCCESS
+ : RouteInterface.RESULT_ERROR, null);
+ return true;
+ } else {
+ // The command wasn't recognized
+ }
+ return false;
+ }
+
+ /**
+ * Override to handle fast forwarding.
+ *
+ * @return true if the request succeeded, false otherwise
+ */
+ public boolean fastForward() {
+ Log.w(TAG, "fastForward is not supported.");
+ return false;
+ }
+
+ /**
+ * Override to handle getting the current position of playback in
+ * millis.
+ *
+ * @return The current position in millis or -1
+ */
+ public long getCurrentPosition() {
+ Log.w(TAG, "getCurrentPosition is not supported");
+ return -1;
+ }
+
+ /**
+ * Override to handle getting the set of capabilities currently
+ * available.
+ *
+ * @return A bit mask of the supported capabilities
+ */
+ public long getCapabilities() {
+ Log.w(TAG, "getCapabilities is not supported");
+ return 0;
+ }
+
+ /**
+ * Override to handle play now requests.
+ *
+ * @param content The uri of the item to play.
+ * @param cb The callback to send the result to.
+ */
+ public void playNow(String content, ResultReceiver cb) {
+ Log.w(TAG, "playNow is not supported");
+ if (cb != null) {
+ // We do this directly since we don't have a reference to the
+ // iface
+ cb.send(RouteInterface.RESULT_COMMAND_NOT_SUPPORTED, null);
+ }
+ }
+
+ /**
+ * Override to handle resume requests. Return true if the call was
+ * handled, even if it was a no-op.
+ *
+ * @return true if the call was handled.
+ */
+ public boolean resume() {
+ Log.w(TAG, "resume is not supported");
+ return false;
+ }
+
+ /**
+ * Override to handle pause requests. Return true if the call was
+ * handled, even if it was a no-op.
+ *
+ * @return true if the call was handled.
+ */
+ public boolean pause() {
+ Log.w(TAG, "pause is not supported");
+ return false;
+ }
+ }
+}
diff --git a/media/java/android/media/routeprovider/RouteProviderService.java b/media/java/android/media/routeprovider/RouteProviderService.java
new file mode 100644
index 0000000..6ebfb5b
--- /dev/null
+++ b/media/java/android/media/routeprovider/RouteProviderService.java
@@ -0,0 +1,227 @@
+/*
+ * 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.routeprovider;
+
+import android.app.Service;
+import android.content.Intent;
+import android.media.routeprovider.IRouteProvider;
+import android.media.routeprovider.IRouteProviderCallback;
+import android.media.session.RouteEvent;
+import android.media.session.RouteInfo;
+import android.media.session.RouteOptions;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Base class for defining a route provider service.
+ * <p>
+ * A route provider offers media routes which represent destinations to which
+ * applications may connect, control, and send content. This provides a means
+ * for Android applications to interact with a variety of media streaming
+ * devices such as speakers or television sets.
+ * <p>
+ * The system will bind to your provider when an active app is interested in
+ * routes that may be discovered through your provider. After binding, the
+ * system will send updates on which routes to discover through
+ * {@link #updateDiscoveryRequests(List)}. The system will call
+ * {@link #getMatchingRoutes(List)} with a subset of filters when a route is
+ * needed for a specific app.
+ * <p>
+ * TODO add documentation for how the sytem knows an app is interested. Maybe
+ * interface declarations in the manifest.
+ * <p>
+ * The system will only start a provider when an app may discover routes through
+ * it. If your service needs to run at other times you are responsible for
+ * managing its lifecycle.
+ * <p>
+ * Declare your route provider service in your application manifest like this:
+ * <p>
+ *
+ * <pre>
+ * &lt;service android:name=".MyRouteProviderService"
+ * android:label="@string/my_route_provider_service">
+ * &lt;intent-filter>
+ * &lt;action android:name="com.android.media.session.MediaRouteProvider" />
+ * &lt;/intent-filter>
+ * &lt;/service>
+ * </pre>
+ */
+public abstract class RouteProviderService extends Service {
+ private static final String TAG = "RouteProvider";
+ /**
+ * A service that implements a RouteProvider must declare that it handles
+ * this action in its AndroidManifest.
+ */
+ public static final String SERVICE_INTERFACE =
+ "com.android.media.session.MediaRouteProvider";
+
+ /**
+ * @hide
+ */
+ public static final String KEY_ROUTES = "routes";
+ /**
+ * @hide
+ */
+ public static final String KEY_CONNECTION = "connection";
+ /**
+ * @hide
+ */
+ public static final int RESULT_FAILURE = -1;
+ /**
+ * @hide
+ */
+ public static final int RESULT_SUCCESS = 0;
+
+ // The system's callback once it has bound to the service
+ private IRouteProviderCallback mCb;
+
+ /**
+ * If your service overrides onBind it must return super.onBind() in
+ * response to the {@link #SERVICE_INTERFACE} action.
+ */
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (intent != null && RouteProviderService.SERVICE_INTERFACE.equals(intent.getAction())) {
+ return mBinder;
+ }
+ return null;
+ }
+
+ /**
+ * Disconnect the specified RouteConnection. The system will stop sending
+ * commands to this connection.
+ *
+ * @param connection The connection to disconnect.
+ * @hide
+ */
+ public final void disconnect(RouteConnection connection) {
+ if (mCb != null) {
+ try {
+ mCb.onConnectionTerminated(connection.getBinder());
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error in disconnect.", e);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public final void sendRouteEvent(RouteEvent event) {
+ if (mCb != null) {
+ try {
+ mCb.onRouteEvent(event);
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Unable to send MediaRouteEvent to system", e);
+ }
+ }
+ }
+
+ /**
+ * Override to handle updates to the routes that are of interest. Each
+ * {@link RouteRequest} will specify if it is an active or passive request.
+ * Route discovery may perform more aggressive discovery on behalf of active
+ * requests but should use low power discovery methods otherwise.
+ * <p>
+ * A single app may have more than one request. Your provider is responsible
+ * for deciding the set of features that are important for discovery given
+ * the set of requests. If your provider only has one method of discovery it
+ * may simply verify that one or more requests are valid before starting
+ * discovery.
+ *
+ * @param requests The route requests that are currently relevant.
+ */
+ public void updateDiscoveryRequests(List<RouteRequest> requests) {
+ }
+
+ /**
+ * Return a list of matching routes for the given set of requests. Returning
+ * null or an empty list indicates there are no matches. A route is
+ * considered matching if it supports one or more of the
+ * {@link RouteOptions} specified. Each returned {@link RouteInfo}
+ * should include all the requested connections that it supports.
+ *
+ * @param options The set of requests for routes
+ * @return The routes that this caller may connect to using one or more of
+ * the route options.
+ */
+ public abstract List<RouteInfo> getMatchingRoutes(List<RouteRequest> options);
+
+ /**
+ * Handle a request to connect to a specific route with a specific request.
+ * The {@link RouteConnection} must be fully defined before being returned,
+ * though the actual connection to the route may be performed in the
+ * background.
+ *
+ * @param route The route to connect to
+ * @param request The connection request parameters
+ * @return A MediaRouteConnection representing the connection to the route
+ */
+ public abstract RouteConnection connect(RouteInfo route, RouteRequest request);
+
+ private IRouteProvider.Stub mBinder = new IRouteProvider.Stub() {
+
+ @Override
+ public void registerCallback(IRouteProviderCallback cb) throws RemoteException {
+ mCb = cb;
+ }
+
+ @Override
+ public void unregisterCallback(IRouteProviderCallback cb) throws RemoteException {
+ mCb = null;
+ }
+
+ @Override
+ public void updateDiscoveryRequests(List<RouteRequest> requests)
+ throws RemoteException {
+ RouteProviderService.this.updateDiscoveryRequests(requests);
+ }
+
+ @Override
+ public void getAvailableRoutes(List<RouteRequest> requests, ResultReceiver cb)
+ throws RemoteException {
+ List<RouteInfo> routes = RouteProviderService.this.getMatchingRoutes(requests);
+ ArrayList<RouteInfo> routesArray;
+ if (routes instanceof ArrayList) {
+ routesArray = (ArrayList<RouteInfo>) routes;
+ } else {
+ routesArray = new ArrayList<RouteInfo>(routes);
+ }
+ Bundle resultData = new Bundle();
+ resultData.putParcelableArrayList(KEY_ROUTES, routesArray);
+ cb.send(routes == null ? RESULT_FAILURE : RESULT_SUCCESS, resultData);
+ }
+
+ @Override
+ public void connect(RouteInfo route, RouteRequest request, ResultReceiver cb)
+ throws RemoteException {
+ RouteConnection connection = RouteProviderService.this.connect(route, request);
+ Bundle resultData = new Bundle();
+ if (connection != null) {
+ connection.publish();
+ resultData.putBinder(KEY_CONNECTION, connection.getBinder());
+ }
+
+ cb.send(connection == null ? RESULT_FAILURE : RESULT_SUCCESS, resultData);
+ }
+ };
+}
diff --git a/media/java/android/media/routeprovider/RouteRequest.aidl b/media/java/android/media/routeprovider/RouteRequest.aidl
new file mode 100644
index 0000000..7bc5722
--- /dev/null
+++ b/media/java/android/media/routeprovider/RouteRequest.aidl
@@ -0,0 +1,18 @@
+/* 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.
+*/
+
+package android.media.routeprovider;
+
+parcelable RouteRequest;
diff --git a/media/java/android/media/routeprovider/RouteRequest.java b/media/java/android/media/routeprovider/RouteRequest.java
new file mode 100644
index 0000000..9913566
--- /dev/null
+++ b/media/java/android/media/routeprovider/RouteRequest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.routeprovider;
+
+import android.media.session.RouteOptions;
+import android.media.session.SessionInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A request to connect or discover routes with certain capabilities.
+ * <p>
+ * Passed to a {@link RouteProviderService} when a request for discovery or to
+ * connect to a route is made. This identifies the app making the request and
+ * 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.
+ */
+public final class RouteRequest implements Parcelable {
+ private final SessionInfo mSessionInfo;
+ private final RouteOptions mOptions;
+ private final boolean mActive;
+
+ /**
+ * @hide
+ */
+ public RouteRequest(SessionInfo info, RouteOptions connRequest,
+ boolean active) {
+ mSessionInfo = info;
+ mOptions = connRequest;
+ mActive = active;
+ }
+
+ private RouteRequest(Parcel in) {
+ mSessionInfo = SessionInfo.CREATOR.createFromParcel(in);
+ mOptions = RouteOptions.CREATOR.createFromParcel(in);
+ mActive = in.readInt() != 0;
+ }
+
+ /**
+ * Get information about the session making the request.
+ *
+ * @return Info on the session making the request
+ */
+ public SessionInfo getSessionInfo() {
+ return mSessionInfo;
+ }
+
+ /**
+ * Get the connection options, which includes the interfaces and other
+ * connection params the session wants to use with a route.
+ *
+ * @return The connection options
+ */
+ public RouteOptions getConnectionOptions() {
+ return mOptions;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ mSessionInfo.writeToParcel(dest, flags);
+ mOptions.writeToParcel(dest, flags);
+ dest.writeInt(mActive ? 1 : 0);
+ }
+
+ public static final Parcelable.Creator<RouteRequest> CREATOR
+ = new Parcelable.Creator<RouteRequest>() {
+ @Override
+ public RouteRequest createFromParcel(Parcel in) {
+ return new RouteRequest(in);
+ }
+
+ @Override
+ public RouteRequest[] newArray(int size) {
+ return new RouteRequest[size];
+ }
+ };
+}
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl
new file mode 100644
index 0000000..ca77f04
--- /dev/null
+++ b/media/java/android/media/session/ISession.aidl
@@ -0,0 +1,49 @@
+/* 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.session;
+
+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;
+import android.media.session.PlaybackState;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+
+/**
+ * Interface to a MediaSession in the system.
+ * @hide
+ */
+interface ISession {
+ void sendEvent(String event, in Bundle data);
+ ISessionController getController();
+ void setTransportPerformerEnabled();
+ void publish();
+ void destroy();
+
+ // These commands are for setting up and communicating with routes
+ // Returns true if the route was set for this session
+ boolean setRoute(in RouteInfo route);
+ void setRouteOptions(in List<RouteOptions> options);
+ void connectToRoute(in RouteInfo route, in RouteOptions options);
+ void sendRouteCommand(in RouteCommand event, in ResultReceiver cb);
+
+ // These commands are for the TransportPerformer
+ void setMetadata(in MediaMetadata metadata);
+ void setPlaybackState(in PlaybackState state);
+ void setRatingType(int type);
+} \ No newline at end of file
diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl
new file mode 100644
index 0000000..f04cbcc
--- /dev/null
+++ b/media/java/android/media/session/ISessionCallback.aidl
@@ -0,0 +1,47 @@
+/* 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.session;
+
+import android.media.Rating;
+import android.media.session.RouteEvent;
+import android.media.session.RouteInfo;
+import android.media.session.RouteOptions;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+
+/**
+ * @hide
+ */
+oneway interface ISessionCallback {
+ void onCommand(String command, in Bundle extras, in ResultReceiver cb);
+ void onMediaButton(in Intent mediaButtonIntent);
+ void onRequestRouteChange(in RouteInfo route);
+ void onRouteConnected(in RouteInfo route, in RouteOptions options);
+ void onRouteStateChange(int state);
+ void onRouteEvent(in RouteEvent event);
+
+ // These callbacks are for the TransportPerformer
+ void onPlay();
+ void onPause();
+ void onStop();
+ void onNext();
+ void onPrevious();
+ void onFastForward();
+ void onRewind();
+ void onSeekTo(long pos);
+ void onRate(in Rating rating);
+} \ No newline at end of file
diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl
new file mode 100644
index 0000000..e2e046f
--- /dev/null
+++ b/media/java/android/media/session/ISessionController.aidl
@@ -0,0 +1,52 @@
+/* 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.session;
+
+import android.content.Intent;
+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;
+import android.view.KeyEvent;
+
+/**
+ * Interface to a MediaSession in the system.
+ * @hide
+ */
+interface ISessionController {
+ void sendCommand(String command, in Bundle extras, in ResultReceiver cb);
+ void sendMediaButton(in KeyEvent mediaButton);
+ void registerCallbackListener(in ISessionControllerCallback cb);
+ void unregisterCallbackListener(in ISessionControllerCallback cb);
+ boolean isTransportControlEnabled();
+ void showRoutePicker();
+
+ // These commands are for the TransportController
+ void play();
+ void pause();
+ void stop();
+ void next();
+ void previous();
+ void fastForward();
+ void rewind();
+ void seekTo(long pos);
+ void rate(in Rating rating);
+ MediaMetadata getMetadata();
+ PlaybackState getPlaybackState();
+ int getRatingType();
+} \ No newline at end of file
diff --git a/media/java/android/media/session/ISessionControllerCallback.aidl b/media/java/android/media/session/ISessionControllerCallback.aidl
new file mode 100644
index 0000000..bc1ae05
--- /dev/null
+++ b/media/java/android/media/session/ISessionControllerCallback.aidl
@@ -0,0 +1,33 @@
+/* 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.session;
+
+import android.media.session.MediaMetadata;
+import android.media.session.RouteInfo;
+import android.media.session.PlaybackState;
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+oneway interface ISessionControllerCallback {
+ void onEvent(String event, in Bundle extras);
+ void onRouteChanged(in RouteInfo route);
+
+ // These callbacks are for the TransportController
+ void onPlaybackStateChanged(in PlaybackState state);
+ void onMetadataChanged(in MediaMetadata metadata);
+} \ No newline at end of file
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
new file mode 100644
index 0000000..84b9a0f
--- /dev/null
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -0,0 +1,28 @@
+/* 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.session;
+
+import android.media.session.ISession;
+import android.media.session.ISessionCallback;
+import android.os.Bundle;
+
+/**
+ * Interface to the MediaSessionManagerService
+ * @hide
+ */
+interface ISessionManager {
+ ISession createSession(String packageName, in ISessionCallback cb, String tag);
+} \ No newline at end of file
diff --git a/media/java/android/media/session/MediaMetadata.aidl b/media/java/android/media/session/MediaMetadata.aidl
new file mode 100644
index 0000000..4431d9d
--- /dev/null
+++ b/media/java/android/media/session/MediaMetadata.aidl
@@ -0,0 +1,18 @@
+/* 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.
+*/
+
+package android.media.session;
+
+parcelable MediaMetadata;
diff --git a/media/java/android/media/session/MediaMetadata.java b/media/java/android/media/session/MediaMetadata.java
new file mode 100644
index 0000000..e2330f7
--- /dev/null
+++ b/media/java/android/media/session/MediaMetadata.java
@@ -0,0 +1,404 @@
+/*
+ * 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.session;
+
+import android.graphics.Bitmap;
+import android.media.Rating;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
+import android.util.Log;
+
+/**
+ * Contains metadata about an item, such as the title, artist, etc.
+ */
+public final class MediaMetadata implements Parcelable {
+ private static final String TAG = "MediaMetadata";
+
+ /**
+ * The title of the media.
+ */
+ public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
+
+ /**
+ * The artist of the media.
+ */
+ public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
+
+ /**
+ * The duration of the media in ms. A duration of 0 is the default.
+ */
+ public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
+
+ /**
+ * The album title for the media.
+ */
+ public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
+
+ /**
+ * The author of the media.
+ */
+ public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
+
+ /**
+ * The writer of the media.
+ */
+ public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
+
+ /**
+ * The composer of the media.
+ */
+ public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
+
+ /**
+ * The date the media was created or published as TODO determine format.
+ */
+ public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
+
+ /**
+ * The year the media was created or published as a numeric String.
+ */
+ public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
+
+ /**
+ * The genre of the media.
+ */
+ public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
+
+ /**
+ * The track number for the media.
+ */
+ public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+
+ /**
+ * The number of tracks in the media's original source.
+ */
+ public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
+
+ /**
+ * The disc number for the media's original source.
+ */
+ public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+
+ /**
+ * The artist for the album of the media's original source.
+ */
+ public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+
+ /**
+ * The artwork for the media as a {@link Bitmap}.
+ */
+ public static final String METADATA_KEY_ART = "android.media.metadata.ART";
+
+ /**
+ * The artwork for the media as a Uri style String.
+ */
+ public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
+
+ /**
+ * The artwork for the album of the media's original source as a
+ * {@link Bitmap}.
+ */
+ public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
+
+ /**
+ * The artwork for the album of the media's original source as a Uri style
+ * String.
+ */
+ public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
+
+ /**
+ * The user's rating for the media.
+ *
+ * @see Rating
+ */
+ public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
+
+ /**
+ * The overall rating for the media.
+ *
+ * @see Rating
+ */
+ public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
+
+ private static final int METADATA_TYPE_INVALID = -1;
+ private static final int METADATA_TYPE_LONG = 0;
+ private static final int METADATA_TYPE_STRING = 1;
+ private static final int METADATA_TYPE_BITMAP = 2;
+ private static final int METADATA_TYPE_RATING = 3;
+ private static final ArrayMap<String, Integer> METADATA_KEYS_TYPE;
+
+ static {
+ METADATA_KEYS_TYPE = new ArrayMap<String, Integer>();
+ METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_DURATION, METADATA_TYPE_LONG);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_AUTHOR, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_WRITER, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_COMPOSER, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_DATE, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_YEAR, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_TRACK_NUMBER, METADATA_TYPE_LONG);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_NUM_TRACKS, METADATA_TYPE_LONG);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_DISC_NUMBER, METADATA_TYPE_LONG);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ARTIST, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ART_URI, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART, METADATA_TYPE_BITMAP);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART_URI, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_USER_RATING, METADATA_TYPE_RATING);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_RATING, METADATA_TYPE_RATING);
+ }
+ private final Bundle mBundle;
+
+ private MediaMetadata(Bundle bundle) {
+ mBundle = new Bundle(bundle);
+ }
+
+ private MediaMetadata(Parcel in) {
+ mBundle = in.readBundle();
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if no mapping of
+ * the desired type exists for the given key or a null value is explicitly
+ * associated with the key.
+ *
+ * @param key The key the value is stored under
+ * @return a String value, or null
+ */
+ public String getString(String key) {
+ return mBundle.getString(key);
+ }
+
+ /**
+ * Returns the value associated with the given key, or 0L if no long exists
+ * for the given key.
+ *
+ * @param key The key the value is stored under
+ * @return a long value
+ */
+ public long getLong(String key) {
+ return mBundle.getLong(key);
+ }
+
+ /**
+ * Return a {@link Rating} for the given key or null if no rating exists for
+ * the given key.
+ *
+ * @param key The key the value is stored under
+ * @return A {@link Rating} or null
+ */
+ public Rating getRating(String key) {
+ Rating rating = null;
+ try {
+ rating = mBundle.getParcelable(key);
+ } catch (Exception e) {
+ // ignore, value was not a bitmap
+ Log.d(TAG, "Failed to retrieve a key as Rating.", e);
+ }
+ return rating;
+ }
+
+ /**
+ * Return a {@link Bitmap} for the given key or null if no bitmap exists for
+ * the given key.
+ *
+ * @param key The key the value is stored under
+ * @return A {@link Bitmap} or null
+ */
+ public Bitmap getBitmap(String key) {
+ Bitmap bmp = null;
+ try {
+ bmp = mBundle.getParcelable(key);
+ } catch (Exception e) {
+ // ignore, value was not a bitmap
+ Log.d(TAG, "Failed to retrieve a key as Bitmap.", e);
+ }
+ return bmp;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBundle(mBundle);
+ }
+
+ public static final Parcelable.Creator<MediaMetadata> CREATOR
+ = new Parcelable.Creator<MediaMetadata>() {
+ @Override
+ public MediaMetadata createFromParcel(Parcel in) {
+ return new MediaMetadata(in);
+ }
+
+ @Override
+ public MediaMetadata[] newArray(int size) {
+ return new MediaMetadata[size];
+ }
+ };
+
+ /**
+ * Use to build MediaMetadata objects. The system defined metadata keys must
+ * use the appropriate data type.
+ */
+ public static final class Builder {
+ private final Bundle mBundle;
+
+ /**
+ * Create an empty Builder. Any field that should be included in the
+ * {@link MediaMetadata} must be added.
+ */
+ public Builder() {
+ mBundle = new Bundle();
+ }
+
+ /**
+ * Create a Builder using a {@link MediaMetadata} instance to set the
+ * initial values. All fields in the source metadata will be included in
+ * the new metadata. Fields can be overwritten by adding the same key.
+ *
+ * @param source
+ */
+ public Builder(MediaMetadata source) {
+ mBundle = new Bundle(source.mBundle);
+ }
+
+ /**
+ * Put a String value into the metadata. Custom keys may be used, but if
+ * the METADATA_KEYs defined in this class are used they may only be one
+ * of the following:
+ * <ul>
+ * <li>{@link #METADATA_KEY_TITLE}</li>
+ * <li>{@link #METADATA_KEY_ARTIST}</li>
+ * <li>{@link #METADATA_KEY_ALBUM}</li>
+ * <li>{@link #METADATA_KEY_AUTHOR}</li>
+ * <li>{@link #METADATA_KEY_WRITER}</li>
+ * <li>{@link #METADATA_KEY_COMPOSER}</li>
+ * <li>{@link #METADATA_KEY_DATE}</li>
+ * <li>{@link #METADATA_KEY_YEAR}</li>
+ * <li>{@link #METADATA_KEY_GENRE}</li>
+ * <li>{@link #METADATA_KEY_ALBUM_ARTIST}</li>li>
+ * <li>{@link #METADATA_KEY_ART_URI}</li>li>
+ * <li>{@link #METADATA_KEY_ALBUM_ART_URI}</li>
+ * </ul>
+ *
+ * @param key The key for referencing this value
+ * @param value The String value to store
+ * @return The Builder to allow chaining
+ */
+ public Builder putString(String key, String value) {
+ if (METADATA_KEYS_TYPE.containsKey(key)) {
+ if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_STRING) {
+ throw new IllegalArgumentException("The " + key
+ + " key cannot be used to put a String");
+ }
+ }
+ mBundle.putString(key, value);
+ return this;
+ }
+
+ /**
+ * Put a long value into the metadata. Custom keys may be used, but if
+ * the METADATA_KEYs defined in this class are used they may only be one
+ * of the following:
+ * <ul>
+ * <li>{@link #METADATA_KEY_DURATION}</li>
+ * <li>{@link #METADATA_KEY_TRACK_NUMBER}</li>
+ * <li>{@link #METADATA_KEY_NUM_TRACKS}</li>
+ * <li>{@link #METADATA_KEY_DISC_NUMBER}</li>
+ * </ul>
+ *
+ * @param key The key for referencing this value
+ * @param value The String value to store
+ * @return The Builder to allow chaining
+ */
+ public Builder putLong(String key, long value) {
+ if (METADATA_KEYS_TYPE.containsKey(key)) {
+ if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_LONG) {
+ throw new IllegalArgumentException("The " + key
+ + " key cannot be used to put a long");
+ }
+ }
+ mBundle.putLong(key, value);
+ return this;
+ }
+
+ /**
+ * Put a {@link Rating} into the metadata. Custom keys may be used, but
+ * if the METADATA_KEYs defined in this class are used they may only be
+ * one of the following:
+ * <ul>
+ * <li>{@link #METADATA_KEY_RATING}</li>
+ * <li>{@link #METADATA_KEY_USER_RATING}</li>
+ * </ul>
+ *
+ * @param key The key for referencing this value
+ * @param value The String value to store
+ * @return The Builder to allow chaining
+ */
+ public Builder putRating(String key, Rating value) {
+ if (METADATA_KEYS_TYPE.containsKey(key)) {
+ if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_RATING) {
+ throw new IllegalArgumentException("The " + key
+ + " key cannot be used to put a Rating");
+ }
+ }
+ mBundle.putParcelable(key, value);
+ return this;
+ }
+
+ /**
+ * Put a {@link Bitmap} into the metadata. Custom keys may be used, but
+ * if the METADATA_KEYs defined in this class are used they may only be
+ * one of the following:
+ * <ul>
+ * <li>{@link #METADATA_KEY_ART}</li>
+ * <li>{@link #METADATA_KEY_ALBUM_ART}</li>
+ * </ul>
+ *
+ * @param key The key for referencing this value
+ * @param value The Bitmap to store
+ * @return The Builder to allow chaining
+ */
+ public Builder putBitmap(String key, Bitmap value) {
+ if (METADATA_KEYS_TYPE.containsKey(key)) {
+ if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
+ throw new IllegalArgumentException("The " + key
+ + " key cannot be used to put a Bitmap");
+ }
+ }
+ mBundle.putParcelable(key, value);
+ return this;
+ }
+
+ /**
+ * Creates a {@link MediaMetadata} instance with the specified fields.
+ *
+ * @return The new MediaMetadata instance
+ */
+ public MediaMetadata build() {
+ return new MediaMetadata(mBundle);
+ }
+ }
+
+}
diff --git a/media/java/android/media/session/PlaybackState.aidl b/media/java/android/media/session/PlaybackState.aidl
new file mode 100644
index 0000000..0876ebd
--- /dev/null
+++ b/media/java/android/media/session/PlaybackState.aidl
@@ -0,0 +1,18 @@
+/* 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.
+*/
+
+package android.media.session;
+
+parcelable PlaybackState;
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
new file mode 100644
index 0000000..14d9fb1
--- /dev/null
+++ b/media/java/android/media/session/PlaybackState.java
@@ -0,0 +1,358 @@
+/*
+ * 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.session;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Playback state for a {@link Session}. This includes a state like
+ * {@link PlaybackState#PLAYSTATE_PLAYING}, the current playback position,
+ * and the current control capabilities.
+ */
+public final class PlaybackState implements Parcelable {
+ /**
+ * Indicates this performer supports the stop command.
+ *
+ * @see #setActions
+ */
+ public static final long ACTION_STOP = 1 << 0;
+
+ /**
+ * Indicates this performer supports the pause command.
+ *
+ * @see #setActions
+ */
+ public static final long ACTION_PAUSE = 1 << 1;
+
+ /**
+ * Indicates this performer supports the play command.
+ *
+ * @see #setActions
+ */
+ public static final long ACTION_PLAY = 1 << 2;
+
+ /**
+ * Indicates this performer supports the rewind command.
+ *
+ * @see #setActions
+ */
+ public static final long ACTION_REWIND = 1 << 3;
+
+ /**
+ * Indicates this performer supports the previous command.
+ *
+ * @see #setActions
+ */
+ public static final long ACTION_PREVIOUS_ITEM = 1 << 4;
+
+ /**
+ * Indicates this performer supports the next command.
+ *
+ * @see #setActions
+ */
+ public static final long ACTION_NEXT_ITEM = 1 << 5;
+
+ /**
+ * Indicates this performer supports the fast forward command.
+ *
+ * @see #setActions
+ */
+ public static final long ACTION_FASTFORWARD = 1 << 6;
+
+ /**
+ * Indicates this performer supports the set rating command.
+ *
+ * @see #setActions
+ */
+ public static final long ACTION_RATING = 1 << 7;
+
+ /**
+ * Indicates this performer supports the seek to command.
+ *
+ * @see #setActions
+ */
+ public static final long ACTION_SEEK_TO = 1 << 8;
+
+ /**
+ * This is the default playback state and indicates that no media has been
+ * added yet, or the performer has been reset and has no content to play.
+ *
+ * @see #setState
+ */
+ public final static int PLAYSTATE_NONE = 0;
+
+ /**
+ * State indicating this item is currently stopped.
+ *
+ * @see #setState
+ */
+ public final static int PLAYSTATE_STOPPED = 1;
+
+ /**
+ * State indicating this item is currently paused.
+ *
+ * @see #setState
+ */
+ public final static int PLAYSTATE_PAUSED = 2;
+
+ /**
+ * State indicating this item is currently playing.
+ *
+ * @see #setState
+ */
+ public final static int PLAYSTATE_PLAYING = 3;
+
+ /**
+ * State indicating this item is currently fast forwarding.
+ *
+ * @see #setState
+ */
+ public final static int PLAYSTATE_FAST_FORWARDING = 4;
+
+ /**
+ * State indicating this item is currently rewinding.
+ *
+ * @see #setState
+ */
+ public final static int PLAYSTATE_REWINDING = 5;
+
+ /**
+ * State indicating this item is currently buffering and will begin playing
+ * when enough data has buffered.
+ *
+ * @see #setState
+ */
+ public final static int PLAYSTATE_BUFFERING = 6;
+
+ /**
+ * State indicating this item is currently in an error state. The error
+ * message should also be set when entering this state.
+ *
+ * @see #setState
+ */
+ public final static int PLAYSTATE_ERROR = 7;
+
+ /**
+ * State indicating the class doing playback is currently connecting to a
+ * 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.
+ */
+ public final static int PLAYSTATE_CONNECTING = 8;
+
+ private int mState;
+ private long mPosition;
+ private long mBufferPosition;
+ private float mSpeed;
+ private long mCapabilities;
+ private String mErrorMessage;
+
+ /**
+ * Create an empty PlaybackState. At minimum a state and actions should be
+ * set before publishing a PlaybackState.
+ */
+ public PlaybackState() {
+ }
+
+ /**
+ * Create a new PlaybackState from an existing PlaybackState. All fields
+ * will be copied to the new state.
+ *
+ * @param from The PlaybackState to duplicate
+ */
+ public PlaybackState(PlaybackState from) {
+ this.setState(from.getState());
+ this.setPosition(from.getPosition());
+ this.setBufferPosition(from.getBufferPosition());
+ this.setSpeed(from.getSpeed());
+ this.setActions(from.getActions());
+ this.setErrorMessage(from.getErrorMessage());
+ }
+
+ private PlaybackState(Parcel in) {
+ this.setState(in.readInt());
+ this.setPosition(in.readLong());
+ this.setBufferPosition(in.readLong());
+ this.setSpeed(in.readFloat());
+ this.setActions(in.readLong());
+ this.setErrorMessage(in.readString());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(getState());
+ dest.writeLong(getPosition());
+ dest.writeLong(getBufferPosition());
+ dest.writeFloat(getSpeed());
+ dest.writeLong(getActions());
+ dest.writeString(getErrorMessage());
+ }
+
+ /**
+ * Get the current state of playback. One of the following:
+ * <ul>
+ * <li> {@link PlaybackState#PLAYSTATE_NONE}</li>
+ * <li> {@link PlaybackState#PLAYSTATE_STOPPED}</li>
+ * <li> {@link PlaybackState#PLAYSTATE_PLAYING}</li>
+ * <li> {@link PlaybackState#PLAYSTATE_PAUSED}</li>
+ * <li> {@link PlaybackState#PLAYSTATE_FAST_FORWARDING}</li>
+ * <li> {@link PlaybackState#PLAYSTATE_REWINDING}</li>
+ * <li> {@link PlaybackState#PLAYSTATE_BUFFERING}</li>
+ * <li> {@link PlaybackState#PLAYSTATE_ERROR}</li>
+ */
+ public int getState() {
+ return mState;
+ }
+
+ /**
+ * Set the current state of playback. One of the following:
+ * <ul>
+ * <li> {@link PlaybackState#PLAYSTATE_NONE}</li>
+ * <li> {@link PlaybackState#PLAYSTATE_STOPPED}</li>
+ * <li> {@link PlaybackState#PLAYSTATE_PLAYING}</li>
+ * <li> {@link PlaybackState#PLAYSTATE_PAUSED}</li>
+ * <li> {@link PlaybackState#PLAYSTATE_FAST_FORWARDING}</li>
+ * <li> {@link PlaybackState#PLAYSTATE_REWINDING}</li>
+ * <li> {@link PlaybackState#PLAYSTATE_BUFFERING}</li>
+ * <li> {@link PlaybackState#PLAYSTATE_ERROR}</li>
+ */
+ public void setState(int mState) {
+ this.mState = mState;
+ }
+
+ /**
+ * Get the current playback position in ms.
+ */
+ public long getPosition() {
+ return mPosition;
+ }
+
+ /**
+ * Set the current playback position in ms.
+ */
+ public void setPosition(long position) {
+ mPosition = position;
+ }
+
+ /**
+ * Get the current buffer position in ms. This is the farthest playback
+ * point that can be reached from the current position using only buffered
+ * content.
+ */
+ public long getBufferPosition() {
+ return mBufferPosition;
+ }
+
+ /**
+ * Set the current buffer position in ms. This is the farthest playback
+ * point that can be reached from the current position using only buffered
+ * content.
+ */
+ public void setBufferPosition(long bufferPosition) {
+ mBufferPosition = bufferPosition;
+ }
+
+ /**
+ * Get the current playback speed as a multiple of normal playback. This
+ * should be negative when rewinding. A value of 1 means normal playback and
+ * 0 means paused.
+ */
+ public float getSpeed() {
+ return mSpeed;
+ }
+
+ /**
+ * Set the current playback speed as a multiple of normal playback. This
+ * should be negative when rewinding. A value of 1 means normal playback and
+ * 0 means paused.
+ */
+ public void setSpeed(float speed) {
+ mSpeed = speed;
+ }
+
+ /**
+ * Get the current actions available on this session. This should use a
+ * bitmask of the available actions.
+ * <ul>
+ * <li> {@link PlaybackState#ACTION_PREVIOUS_ITEM}</li>
+ * <li> {@link PlaybackState#ACTION_REWIND}</li>
+ * <li> {@link PlaybackState#ACTION_PLAY}</li>
+ * <li> {@link PlaybackState#ACTION_PAUSE}</li>
+ * <li> {@link PlaybackState#ACTION_STOP}</li>
+ * <li> {@link PlaybackState#ACTION_FASTFORWARD}</li>
+ * <li> {@link PlaybackState#ACTION_NEXT_ITEM}</li>
+ * <li> {@link PlaybackState#ACTION_SEEK_TO}</li>
+ * <li> {@link PlaybackState#ACTION_RATING}</li>
+ * </ul>
+ */
+ public long getActions() {
+ return mCapabilities;
+ }
+
+ /**
+ * Set the current capabilities available on this session. This should use a
+ * bitmask of the available capabilities.
+ * <ul>
+ * <li> {@link PlaybackState#ACTION_PREVIOUS_ITEM}</li>
+ * <li> {@link PlaybackState#ACTION_REWIND}</li>
+ * <li> {@link PlaybackState#ACTION_PLAY}</li>
+ * <li> {@link PlaybackState#ACTION_PAUSE}</li>
+ * <li> {@link PlaybackState#ACTION_STOP}</li>
+ * <li> {@link PlaybackState#ACTION_FASTFORWARD}</li>
+ * <li> {@link PlaybackState#ACTION_NEXT_ITEM}</li>
+ * <li> {@link PlaybackState#ACTION_SEEK_TO}</li>
+ * <li> {@link PlaybackState#ACTION_RATING}</li>
+ * </ul>
+ */
+ public void setActions(long capabilities) {
+ mCapabilities = capabilities;
+ }
+
+ /**
+ * Get a user readable error message. This should be set when the state is
+ * {@link PlaybackState#PLAYSTATE_ERROR}.
+ */
+ public String getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ /**
+ * Set a user readable error message. This should be set when the state is
+ * {@link PlaybackState#PLAYSTATE_ERROR}.
+ */
+ public void setErrorMessage(String errorMessage) {
+ mErrorMessage = errorMessage;
+ }
+
+ public static final Parcelable.Creator<PlaybackState> CREATOR
+ = new Parcelable.Creator<PlaybackState>() {
+ @Override
+ public PlaybackState createFromParcel(Parcel in) {
+ return new PlaybackState(in);
+ }
+
+ @Override
+ public PlaybackState[] newArray(int size) {
+ return new PlaybackState[size];
+ }
+ };
+}
diff --git a/media/java/android/media/session/Route.java b/media/java/android/media/session/Route.java
new file mode 100644
index 0000000..c9530a6
--- /dev/null
+++ b/media/java/android/media/session/Route.java
@@ -0,0 +1,99 @@
+/*
+ * 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.session;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Represents a destination which an application has connected to and may send
+ * media content.
+ * <p>
+ * This allows a session owner to interact with a route it has been connected
+ * to. The MediaRoute must be used to get {@link RouteInterface}
+ * instances which can be used to communicate over a specific interface on the
+ * route.
+ */
+public final class Route {
+ private static final String TAG = "Route";
+ private final RouteInfo mInfo;
+ private final Session mSession;
+ private final RouteOptions mOptions;
+
+ /**
+ * @hide
+ */
+ public Route(RouteInfo info, RouteOptions options, Session session) {
+ if (info == null || options == null) {
+ throw new IllegalStateException("Route info was not valid!");
+ }
+ mInfo = info;
+ mOptions = options;
+ mSession = session;
+ }
+
+ /**
+ * Get the {@link RouteInfo} for this route.
+ *
+ * @return The info for this route.
+ */
+ public RouteInfo getRouteInfo() {
+ return mInfo;
+ }
+
+ /**
+ * Get the {@link RouteOptions} that were used to connect this route.
+ *
+ * @return The options used to connect to this route.
+ */
+ public RouteOptions getOptions() {
+ return mOptions;
+ }
+
+ /**
+ * Gets an interface provided by this route. If the interface is not
+ * supported by the route, returns null.
+ *
+ * @see RouteInterface
+ * @param iface The name of the interface to create
+ * @return A {@link RouteInterface} or null if the interface is
+ * not supported.
+ */
+ public RouteInterface getInterface(String iface) {
+ if (TextUtils.isEmpty(iface)) {
+ throw new IllegalArgumentException("iface may not be empty.");
+ }
+ List<String> ifaces = mOptions.getInterfaceNames();
+ if (ifaces != null) {
+ for (int i = ifaces.size() - 1; i >= 0; i--) {
+ if (iface.equals(ifaces.get(i))) {
+ return new RouteInterface(this, iface, mSession);
+ }
+ }
+ }
+ Log.e(TAG, "Interface not supported by route");
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ Session getSession() {
+ return mSession;
+ }
+}
diff --git a/media/java/android/media/session/RouteCommand.aidl b/media/java/android/media/session/RouteCommand.aidl
new file mode 100644
index 0000000..725b308
--- /dev/null
+++ b/media/java/android/media/session/RouteCommand.aidl
@@ -0,0 +1,18 @@
+/* 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.
+*/
+
+package android.media.session;
+
+parcelable RouteCommand;
diff --git a/media/java/android/media/session/RouteCommand.java b/media/java/android/media/session/RouteCommand.java
new file mode 100644
index 0000000..358bc0a
--- /dev/null
+++ b/media/java/android/media/session/RouteCommand.java
@@ -0,0 +1,117 @@
+/*
+ * 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.session;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents a command that an application may send to a route.
+ * <p>
+ * Commands are associated with a specific route and interface supported by that
+ * route and sent through the session. This class isn't used directly by apps.
+ *
+ * @hide
+ */
+public final class RouteCommand implements Parcelable {
+ private final String mRoute;
+ private final String mIface;
+ private final String mEvent;
+ private final Bundle mExtras;
+
+ /**
+ * @param route The id of the route this event is being sent on
+ * @param iface The interface the sender used
+ * @param event The event or command
+ * @param extras Any extras included with the event
+ */
+ public RouteCommand(String route, String iface, String event, Bundle extras) {
+ mRoute = route;
+ mIface = iface;
+ mEvent = event;
+ mExtras = extras;
+ }
+
+ private RouteCommand(Parcel in) {
+ mRoute = in.readString();
+ mIface = in.readString();
+ mEvent = in.readString();
+ mExtras = in.readBundle();
+ }
+
+ /**
+ * Get the id for the route this event was sent on.
+ *
+ * @return The route id this event is using
+ */
+ public String getRouteInfo() {
+ return mRoute;
+ }
+
+ /**
+ * Get the interface this event was sent from
+ *
+ * @return The interface for this event
+ */
+ public String getIface() {
+ return mIface;
+ }
+
+ /**
+ * Get the action/name of the event.
+ *
+ * @return The name of event/command.
+ */
+ public String getEvent() {
+ return mEvent;
+ }
+
+ /**
+ * Get any extras included with the event.
+ *
+ * @return The bundle included with the event or null
+ */
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mRoute);
+ dest.writeString(mIface);
+ dest.writeString(mEvent);
+ dest.writeBundle(mExtras);
+ }
+
+ public static final Parcelable.Creator<RouteCommand> CREATOR
+ = new Parcelable.Creator<RouteCommand>() {
+ @Override
+ public RouteCommand createFromParcel(Parcel in) {
+ return new RouteCommand(in);
+ }
+
+ @Override
+ public RouteCommand[] newArray(int size) {
+ return new RouteCommand[size];
+ }
+ };
+}
diff --git a/media/java/android/media/session/RouteEvent.aidl b/media/java/android/media/session/RouteEvent.aidl
new file mode 100644
index 0000000..6966207
--- /dev/null
+++ b/media/java/android/media/session/RouteEvent.aidl
@@ -0,0 +1,18 @@
+/* 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.
+*/
+
+package android.media.session;
+
+parcelable RouteEvent;
diff --git a/media/java/android/media/session/RouteEvent.java b/media/java/android/media/session/RouteEvent.java
new file mode 100644
index 0000000..918e410
--- /dev/null
+++ b/media/java/android/media/session/RouteEvent.java
@@ -0,0 +1,120 @@
+/*
+ * 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.session;
+
+import android.media.routeprovider.RouteConnection;
+import android.media.routeprovider.RouteProviderService;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents an event that a route provider is sending to a particular
+ * {@link RouteConnection}. Events are associated with a specific interface
+ * supported by the connection and sent through the {@link RouteProviderService}.
+ * This class isn't used directly by apps.
+ *
+ * @hide
+ */
+public class RouteEvent implements Parcelable {
+ private final IBinder mConnection;
+ private final String mIface;
+ private final String mEvent;
+ private final Bundle mExtras;
+
+ /**
+ * @param connection The connection that this event is for
+ * @param iface The interface the sender used
+ * @param event The event or command
+ * @param extras Any extras included with the event
+ */
+ public RouteEvent(IBinder connection, String iface, String event, Bundle extras) {
+ mConnection = connection;
+ mIface = iface;
+ mEvent = event;
+ mExtras = extras;
+ }
+
+ private RouteEvent(Parcel in) {
+ mConnection = in.readStrongBinder();
+ mIface = in.readString();
+ mEvent = in.readString();
+ mExtras = in.readBundle();
+ }
+
+ /**
+ * Get the connection this event was sent on.
+ *
+ * @return The connection this event is using
+ */
+ public IBinder getConnection() {
+ return mConnection;
+ }
+
+ /**
+ * Get the interface this event was sent from
+ *
+ * @return The interface for this event
+ */
+ public String getIface() {
+ return mIface;
+ }
+
+ /**
+ * Get the action/name of the event.
+ *
+ * @return The name of event/command.
+ */
+ public String getEvent() {
+ return mEvent;
+ }
+
+ /**
+ * Get any extras included with the event.
+ *
+ * @return The bundle included with the event or null
+ */
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongBinder(mConnection);
+ dest.writeString(mIface);
+ dest.writeString(mEvent);
+ dest.writeBundle(mExtras);
+ }
+
+ public static final Parcelable.Creator<RouteEvent> CREATOR
+ = new Parcelable.Creator<RouteEvent>() {
+ @Override
+ public RouteEvent createFromParcel(Parcel in) {
+ return new RouteEvent(in);
+ }
+
+ @Override
+ public RouteEvent[] newArray(int size) {
+ return new RouteEvent[size];
+ }
+ };
+}
diff --git a/media/java/android/media/session/RouteInfo.aidl b/media/java/android/media/session/RouteInfo.aidl
new file mode 100644
index 0000000..c5f50c8
--- /dev/null
+++ b/media/java/android/media/session/RouteInfo.aidl
@@ -0,0 +1,18 @@
+/* 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.
+*/
+
+package android.media.session;
+
+parcelable RouteInfo;
diff --git a/media/java/android/media/session/RouteInfo.java b/media/java/android/media/session/RouteInfo.java
new file mode 100644
index 0000000..17df969
--- /dev/null
+++ b/media/java/android/media/session/RouteInfo.java
@@ -0,0 +1,233 @@
+/*
+ * 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.session;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+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.
+ */
+public final class RouteInfo implements Parcelable {
+ private final String mName;
+ private final String mId;
+ private final String mProviderId;
+ private final List<RouteOptions> mOptions;
+
+ private RouteInfo(String id, String name, String providerId,
+ List<RouteOptions> connRequests) {
+ mId = id;
+ mName = name;
+ mProviderId = providerId;
+ mOptions = connRequests;
+ }
+
+ private RouteInfo(Parcel in) {
+ mId = in.readString();
+ mName = in.readString();
+ mProviderId = in.readString();
+ mOptions = new ArrayList<RouteOptions>();
+ in.readTypedList(mOptions, RouteOptions.CREATOR);
+ }
+
+ /**
+ * Get the displayable name of this route.
+ *
+ * @return A short, user readable name for this route
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Get the unique id for this route.
+ *
+ * @return A unique route id.
+ */
+ public String getId() {
+ return mId;
+ }
+
+ /**
+ * Get the package name of this route's provider.
+ *
+ * @return The package name of this route's provider.
+ */
+ public String getProvider() {
+ return mProviderId;
+ }
+
+ /**
+ * Get the set of connections that may be used with this route.
+ *
+ * @return An array of connection requests that may be used to connect
+ */
+ public List<RouteOptions> getConnectionMethods() {
+ return mOptions;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mId);
+ dest.writeString(mName);
+ dest.writeString(mProviderId);
+ dest.writeTypedList(mOptions);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder bob = new StringBuilder();
+ bob.append("RouteInfo: id=").append(mId).append(", name=").append(mName)
+ .append(", provider=").append(mProviderId).append(", options={");
+ for (int i = 0; i < mOptions.size(); i++) {
+ if (i != 0) {
+ bob.append(", ");
+ }
+ bob.append(mOptions.get(i).toString());
+ }
+ bob.append("}");
+ return bob.toString();
+ }
+
+ public static final Parcelable.Creator<RouteInfo> CREATOR
+ = new Parcelable.Creator<RouteInfo>() {
+ @Override
+ public RouteInfo createFromParcel(Parcel in) {
+ return new RouteInfo(in);
+ }
+
+ @Override
+ public RouteInfo[] newArray(int size) {
+ return new RouteInfo[size];
+ }
+ };
+
+ /**
+ * Helper for creating MediaRouteInfos. A route must have a name and an id.
+ * While options are not strictly required the route cannot be connected to
+ * without at least one set of options.
+ */
+ public static final class Builder {
+ private String mName;
+ private String mId;
+ private String mProviderPackage;
+ private ArrayList<RouteOptions> mOptions;
+
+ /**
+ * Copies an existing route info object. TODO Remove once we have
+ * helpers for creating route infos.
+ *
+ * @param from The existing info to copy.
+ */
+ public Builder(RouteInfo from) {
+ mOptions = new ArrayList<RouteOptions>(from.getConnectionMethods());
+ mName = from.mName;
+ mId = from.mId;
+ mProviderPackage = from.mProviderId;
+ }
+
+ public Builder() {
+ mOptions = new ArrayList<RouteOptions>();
+ }
+
+ /**
+ * Set the user visible name for this route.
+ *
+ * @param name The name of the route
+ * @return The builder for easy chaining.
+ */
+ public Builder setName(String name) {
+ mName = name;
+ return this;
+ }
+
+ /**
+ * Set the id of the route. This should be unique to the provider.
+ *
+ * @param id The unique id of the route.
+ * @return The builder for easy chaining.
+ */
+ public Builder setId(String id) {
+ mId = id;
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ public Builder setProviderId(String packageName) {
+ mProviderPackage = packageName;
+ return this;
+ }
+
+ /**
+ * Add a set of {@link RouteOptions} to the route. Multiple options
+ * may be added to the same route.
+ *
+ * @param options The options to add to this route.
+ * @return The builder for easy chaining.
+ */
+ public Builder addRouteOptions(RouteOptions options) {
+ mOptions.add(options);
+ return this;
+ }
+
+ /**
+ * Clear the set of {@link RouteOptions} on the route.
+ *
+ * @return The builder for easy chaining
+ */
+ public Builder clearRouteOptions() {
+ mOptions.clear();
+ return this;
+ }
+
+ /**
+ * Build a new MediaRouteInfo.
+ *
+ * @return A new MediaRouteInfo with the values that were set.
+ */
+ public RouteInfo build() {
+ if (TextUtils.isEmpty(mName)) {
+ throw new IllegalArgumentException("Must set a name before building");
+ }
+ if (TextUtils.isEmpty(mId)) {
+ throw new IllegalArgumentException("Must set an id before building");
+ }
+ return new RouteInfo(mId, mName, mProviderPackage, mOptions);
+ }
+
+ /**
+ * Get the current number of options that have been added to this
+ * builder.
+ *
+ * @return The number of options that have been added.
+ */
+ public int getOptionsSize() {
+ return mOptions.size();
+ }
+ }
+}
diff --git a/media/java/android/media/session/RouteInterface.java b/media/java/android/media/session/RouteInterface.java
new file mode 100644
index 0000000..e9c9fd3
--- /dev/null
+++ b/media/java/android/media/session/RouteInterface.java
@@ -0,0 +1,212 @@
+/*
+ * 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.session;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ResultReceiver;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * A route can support multiple interfaces for a {@link Session} 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
+ * {@link RoutePlaybackControls}, should be used to simplify communication
+ * and reduce errors on that interface.
+ *
+ * @see RoutePlaybackControls for an example
+ */
+public final class RouteInterface {
+ private static final String TAG = "RouteInterface";
+
+ /**
+ * Error indicating the route is currently not connected.
+ */
+ public static final int RESULT_NOT_CONNECTED = -5;
+ /**
+ * Error indicating the session is no longer using the route this command
+ * was sent to.
+ */
+ public static final int RESULT_ROUTE_IS_STALE = -4;
+ /**
+ * Error indicating that the interface does not support the command.
+ */
+ public static final int RESULT_COMMAND_NOT_SUPPORTED = -3;
+ /**
+ * Error indicating that the route does not support the interface.
+ */
+ public static final int RESULT_INTERFACE_NOT_SUPPORTED = -2;
+ /**
+ * Generic error. Extra information about the error may be included in the
+ * result bundle.
+ */
+ public static final int RESULT_ERROR = -1;
+ /**
+ * The command was successful. Extra information may be included in the
+ * result bundle.
+ */
+ public static final int RESULT_SUCCESS = 1;
+
+ private final Route mRoute;
+ private final String mIface;
+ private final Session mSession;
+
+ private final Object mLock = new Object();
+ private final ArrayList<EventHandler> mListeners = new ArrayList<EventHandler>();
+
+ /**
+ * @hide
+ */
+ RouteInterface(Route route, String iface, Session session) {
+ mRoute = route;
+ mIface = iface;
+ mSession = session;
+ mSession.addInterfaceListener(iface, mEventListener);
+ }
+
+ /**
+ * Send a command using this interface.
+ *
+ * @param command The command to send.
+ * @param extras Any extras to include with the command.
+ * @param cb The callback to receive the result on.
+ * @return true if the command was sent, false otherwise.
+ */
+ public boolean sendCommand(String command, Bundle extras, ResultReceiver cb) {
+ RouteCommand cmd = new RouteCommand(mRoute.getRouteInfo().getId(), mIface,
+ command, extras);
+ return mSession.sendRouteCommand(cmd, cb);
+ }
+
+ /**
+ * Add a listener to this interface. Events will be sent on the caller's
+ * thread.
+ *
+ * @param listener The listener to receive events on.
+ */
+ public void addListener(EventListener listener) {
+ addListener(listener, null);
+ }
+
+ /**
+ * Add a listener for this interface. If a handler is specified events will
+ * be performed on the handler's thread, otherwise the caller's thread will
+ * be used.
+ *
+ * @param listener The listener to receive events on
+ * @param handler The handler whose thread to post calls on
+ */
+ public void addListener(EventListener listener, Handler handler) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener may not be null");
+ }
+ if (handler == null) {
+ handler = new Handler();
+ }
+ synchronized (mLock) {
+ if (findIndexOfListenerLocked(listener) != -1) {
+ Log.d(TAG, "Listener is already added, ignoring");
+ return;
+ }
+ mListeners.add(new EventHandler(handler.getLooper(), listener));
+ }
+ }
+
+ /**
+ * Remove a listener from this interface.
+ *
+ * @param listener The listener to stop receiving events on.
+ */
+ public void removeListener(EventListener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener may not be null");
+ }
+ synchronized (mLock) {
+ int index = findIndexOfListenerLocked(listener);
+ if (index != -1) {
+ mListeners.remove(index);
+ }
+ }
+ }
+
+ private int findIndexOfListenerLocked(EventListener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("Callback cannot be null");
+ }
+ for (int i = mListeners.size() - 1; i >= 0; i--) {
+ EventHandler handler = mListeners.get(i);
+ if (listener == handler.mListener) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private EventListener mEventListener = new EventListener() {
+ @Override
+ public void onEvent(String event, Bundle args) {
+ synchronized (mLock) {
+ for (int i = mListeners.size() - 1; i >= 0; i--) {
+ mListeners.get(i).postEvent(event, args);
+ }
+ }
+ }
+
+ };
+
+ /**
+ * An EventListener can be registered by an app with TODO to handle events
+ * sent by the session on a specific interface.
+ */
+ public static abstract class EventListener {
+ /**
+ * This is called when an event is received from the interface. Events
+ * are sent by the session owner and will be delivered to all
+ * controllers that are listening to the interface.
+ *
+ * @param event The event that occurred.
+ * @param args Any extras that were included with the event. May be
+ * null.
+ */
+ public abstract void onEvent(String event, Bundle args);
+ }
+
+ private static final class EventHandler extends Handler {
+
+ private final EventListener mListener;
+
+ public EventHandler(Looper looper, EventListener cb) {
+ super(looper, null, true);
+ mListener = cb;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ mListener.onEvent((String) msg.obj, msg.getData());
+ }
+
+ public void postEvent(String event, Bundle args) {
+ Message msg = obtainMessage(0, event);
+ msg.setData(args);
+ msg.sendToTarget();
+ }
+ }
+}
diff --git a/media/java/android/media/session/RouteOptions.aidl b/media/java/android/media/session/RouteOptions.aidl
new file mode 100644
index 0000000..feaf517
--- /dev/null
+++ b/media/java/android/media/session/RouteOptions.aidl
@@ -0,0 +1,18 @@
+/* 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.
+*/
+
+package android.media.session;
+
+parcelable RouteOptions;
diff --git a/media/java/android/media/session/RouteOptions.java b/media/java/android/media/session/RouteOptions.java
new file mode 100644
index 0000000..5105867
--- /dev/null
+++ b/media/java/android/media/session/RouteOptions.java
@@ -0,0 +1,163 @@
+/*
+ * 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.session;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Specifies options that an application might use when connecting to a route.
+ * This includes things like interfaces, connection parameters, and required
+ * features.
+ * <p>
+ * An application may create several different route options that describe
+ * alternative sets of capabilities that it can use and choose the most
+ * 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.
+ */
+public final class RouteOptions implements Parcelable {
+ private static final String TAG = "RouteOptions";
+
+ private final ArrayList<String> mIfaces;
+ private final Bundle mConnectionParams;
+
+ private RouteOptions(List<String> ifaces, Bundle params) {
+ mIfaces = new ArrayList<String>(ifaces);
+ mConnectionParams = params;
+ }
+
+ private RouteOptions(Parcel in) {
+ mIfaces = new ArrayList<String>();
+ in.readStringList(mIfaces);
+ mConnectionParams = in.readBundle();
+ }
+
+ /**
+ * Get the interfaces this connection wants to use.
+ *
+ * @return The interfaces for this connection
+ */
+ public List<String> getInterfaceNames() {
+ return mIfaces;
+ }
+
+ /**
+ * Get the parameters that will be used for connecting.
+ *
+ * @return The set of connection parameters this connections uses
+ */
+ public Bundle getConnectionParams() {
+ return mConnectionParams;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStringList(mIfaces);
+ dest.writeBundle(mConnectionParams);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder bob = new StringBuilder();
+ bob.append("Options: interfaces={");
+ for (int i = 0; i < mIfaces.size(); i++) {
+ if (i != 0) {
+ bob.append(", ");
+ }
+ bob.append(mIfaces.get(i));
+ }
+ bob.append("}");
+ bob.append(", parameters=");
+ bob.append(mConnectionParams == null ? "null" : mConnectionParams.toString());
+ return bob.toString();
+ }
+
+ public static final Parcelable.Creator<RouteOptions> CREATOR
+ = new Parcelable.Creator<RouteOptions>() {
+ @Override
+ public RouteOptions createFromParcel(Parcel in) {
+ return new RouteOptions(in);
+ }
+
+ @Override
+ public RouteOptions[] newArray(int size) {
+ return new RouteOptions[size];
+ }
+ };
+
+ /**
+ * Builder for creating {@link RouteOptions}.
+ */
+ public final static class Builder {
+ private ArrayList<String> mIfaces = new ArrayList<String>();
+ private Bundle mConnectionParams;
+
+ public Builder() {
+ }
+
+ /**
+ * Add a required interface to the options.
+ *
+ * @param interfaceName The name of the interface to add.
+ * @return The builder to allow chaining commands.
+ */
+ public Builder addInterface(String interfaceName) {
+ if (TextUtils.isEmpty(interfaceName)) {
+ throw new IllegalArgumentException("interfaceName cannot be empty");
+ }
+ if (!mIfaces.contains(interfaceName)) {
+ mIfaces.add(interfaceName);
+ } else {
+ Log.w(TAG, "Attempted to add interface that is already added");
+ }
+ return this;
+ }
+
+ /**
+ * Set the connection parameters to use with the options. TODO replace
+ * with more specific calls once we decide on the standard way to
+ * express parameters.
+ *
+ * @param parameters The parameters to use.
+ * @return The builder to allow chaining commands.
+ */
+ public Builder setParameters(Bundle parameters) {
+ mConnectionParams = parameters;
+ return this;
+ }
+
+ /**
+ * Generate a set of options.
+ *
+ * @return The options with the specified components.
+ */
+ public RouteOptions build() {
+ return new RouteOptions(mIfaces, mConnectionParams);
+ }
+ }
+}
diff --git a/media/java/android/media/session/RoutePlaybackControls.java b/media/java/android/media/session/RoutePlaybackControls.java
new file mode 100644
index 0000000..a3ffb58
--- /dev/null
+++ b/media/java/android/media/session/RoutePlaybackControls.java
@@ -0,0 +1,161 @@
+/*
+ * 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.session;
+
+import android.os.Bundle;
+import android.os.Handler;
+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.
+ */
+public final class RoutePlaybackControls {
+ private static final String TAG = "RoutePlaybackControls";
+ public static final String NAME = "android.media.session.RoutePlaybackControls";
+
+ /** @hide */
+ public static final String KEY_VALUE1 = "value1";
+
+ /** @hide */
+ public static final String CMD_FAST_FORWARD = "fastForward";
+ /** @hide */
+ public static final String CMD_GET_CURRENT_POSITION = "getCurrentPosition";
+ /** @hide */
+ public static final String CMD_GET_CAPABILITIES = "getCapabilities";
+ /** @hide */
+ public static final String CMD_PLAY_NOW = "playNow";
+ /** @hide */
+ public static final String CMD_RESUME = "resume";
+ /** @hide */
+ public static final String CMD_PAUSE = "pause";
+
+ /** @hide */
+ public static final String EVENT_PLAYSTATE_CHANGE = "playstateChange";
+ /** @hide */
+ public static final String EVENT_METADATA_CHANGE = "metadataChange";
+
+ private final RouteInterface mIface;
+
+ private RoutePlaybackControls(RouteInterface iface) {
+ mIface = iface;
+ }
+
+ /**
+ * Get a new MediaRoutePlaybackControls instance for sending commands using
+ * this interface. If the provided route doesn't support this interface null
+ * will be returned.
+ *
+ * @param route The route to send commands to.
+ * @return A MediaRoutePlaybackControls instance or null if not supported.
+ */
+ public static RoutePlaybackControls from(Route route) {
+ RouteInterface iface = route.getInterface(NAME);
+ if (iface != null) {
+ return new RoutePlaybackControls(iface);
+ }
+ return null;
+ }
+
+ /**
+ * Send a resume command to the route.
+ */
+ public void resume() {
+ mIface.sendCommand(CMD_RESUME, null, null);
+ }
+
+ /**
+ * Send a pause command to the route.
+ */
+ public void pause() {
+ mIface.sendCommand(CMD_PAUSE, null, null);
+ }
+
+ /**
+ * Send a fast forward command.
+ */
+ public void fastForward() {
+ Bundle b = new Bundle();
+ mIface.sendCommand(CMD_FAST_FORWARD, b, null);
+ }
+
+ /**
+ * Retrieves the current playback position.
+ *
+ * @param cb The callback to receive the result on.
+ */
+ public void getCurrentPosition(ResultReceiver cb) {
+ mIface.sendCommand(CMD_GET_CURRENT_POSITION, null, cb);
+ }
+
+ public void getCapabilities(ResultReceiver cb) {
+ mIface.sendCommand(CMD_GET_CAPABILITIES, null, cb);
+ }
+
+ public void addListener(Listener listener) {
+ mIface.addListener(listener);
+ }
+
+ public void addListener(Listener listener, Handler handler) {
+ mIface.addListener(listener, handler);
+ }
+
+ public void removeListener(Listener listener) {
+ mIface.removeListener(listener);
+ }
+
+ public void playNow(String content) {
+ Bundle bundle = new Bundle();
+ bundle.putString(KEY_VALUE1, content);
+ mIface.sendCommand(CMD_PLAY_NOW, bundle, null);
+ }
+
+ /**
+ * Register this event listener using {@link #addListener} to receive
+ * RoutePlaybackControl events from a session.
+ */
+ public static abstract class Listener extends RouteInterface.EventListener {
+ @Override
+ public final void onEvent(String event, Bundle args) {
+ if (EVENT_PLAYSTATE_CHANGE.equals(event)) {
+ onPlaybackStateChange(args.getInt(KEY_VALUE1, 0));
+ } else if (EVENT_METADATA_CHANGE.equals(event)) {
+ onMetadataUpdate((MediaMetadata) args.getParcelable(KEY_VALUE1));
+ }
+ }
+
+ /**
+ * Override to handle updates to the playback state. Valid values are in
+ * {@link TransportPerformer}. TODO put playstate values somewhere more
+ * generic.
+ *
+ * @param state
+ */
+ public void onPlaybackStateChange(int state) {
+ }
+
+ /**
+ * Override to handle metadata changes for this session's media. The
+ * default supported fields are those in {@link MediaMetadata}.
+ *
+ * @param metadata
+ */
+ public void onMetadataUpdate(MediaMetadata metadata) {
+ }
+ }
+
+}
diff --git a/media/java/android/media/session/Session.java b/media/java/android/media/session/Session.java
new file mode 100644
index 0000000..8ccd788
--- /dev/null
+++ b/media/java/android/media/session/Session.java
@@ -0,0 +1,666 @@
+/*
+ * 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.session;
+
+import android.content.Intent;
+import android.media.Rating;
+import android.media.session.ISessionController;
+import android.media.session.ISession;
+import android.media.session.ISessionCallback;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Allows interaction with media controllers, media routes, 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.
+ * <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()}. The owner of
+ * the session may also use {@link #getSessionToken()} to allow apps without
+ * this permission to create a {@link SessionController} to interact with this
+ * session.
+ * <p>
+ * To receive commands, media keys, and other events a Callback must be set with
+ * {@link #addCallback(Callback)}.
+ * <p>
+ * When an app is finished performing playback it must call {@link #release()}
+ * to clean up the session and notify any controllers.
+ * <p>
+ * MediaSession objects are thread safe
+ */
+public final class Session {
+ private static final String TAG = "Session";
+
+ 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 String KEY_COMMAND = "command";
+ private static final String KEY_EXTRAS = "extras";
+ private static final String KEY_CALLBACK = "callback";
+
+ private final Object mLock = new Object();
+
+ private final SessionToken mSessionToken;
+ private final ISession mBinder;
+ private final CallbackStub mCbStub;
+
+ private final ArrayList<MessageHandler> mCallbacks = new ArrayList<MessageHandler>();
+ // TODO route interfaces
+ private final ArrayMap<String, RouteInterface.EventListener> mInterfaceListeners
+ = new ArrayMap<String, RouteInterface.EventListener>();
+
+ private TransportPerformer mPerformer;
+ private Route mRoute;
+
+ private boolean mPublished = false;;
+
+ /**
+ * @hide
+ */
+ public Session(ISession binder, CallbackStub cbStub) {
+ mBinder = binder;
+ mCbStub = cbStub;
+ ISessionController controllerBinder = null;
+ try {
+ controllerBinder = mBinder.getController();
+ } catch (RemoteException e) {
+ throw new RuntimeException("Dead object in MediaSessionController constructor: ", e);
+ }
+ mSessionToken = new SessionToken(controllerBinder);
+ }
+
+ /**
+ * Set the callback to receive updates on.
+ *
+ * @param callback The callback object
+ */
+ public void addCallback(Callback callback) {
+ addCallback(callback, null);
+ }
+
+ /**
+ * Add a callback to receive updates for the MediaSession. This includes
+ * events like route updates, media buttons, and focus changes.
+ *
+ * @param callback The callback to receive updates on.
+ * @param handler The handler that events should be posted on.
+ */
+ public void addCallback(Callback callback, Handler handler) {
+ if (callback == null) {
+ throw new IllegalArgumentException("Callback cannot be null");
+ }
+ synchronized (mLock) {
+ if (getHandlerForCallbackLocked(callback) != null) {
+ Log.w(TAG, "Callback is already added, ignoring");
+ return;
+ }
+ if (handler == null) {
+ handler = new Handler();
+ }
+ MessageHandler msgHandler = new MessageHandler(handler.getLooper(), callback);
+ mCallbacks.add(msgHandler);
+ }
+ }
+
+ /**
+ * Remove a callback. It will no longer receive updates.
+ *
+ * @param callback The callback to remove.
+ */
+ public void removeCallback(Callback callback) {
+ synchronized (mLock) {
+ removeCallbackLocked(callback);
+ }
+ }
+
+ /**
+ * Start using a TransportPerformer with this media session. This must be
+ * called before calling publish and cannot be called more than once.
+ * Calling this will allow MediaControllers to retrieve a
+ * TransportController.
+ *
+ * @see TransportController
+ * @return The TransportPerformer created for this session
+ */
+ public TransportPerformer setTransportPerformerEnabled() {
+ if (mPerformer != null) {
+ throw new IllegalStateException("setTransportPerformer can only be called once.");
+ }
+ if (mPublished) {
+ throw new IllegalStateException("setTransportPerformer cannot be called after publish");
+ }
+
+ mPerformer = new TransportPerformer(mBinder);
+ try {
+ mBinder.setTransportPerformerEnabled();
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Failure in setTransportPerformerEnabled.", e);
+ }
+ return mPerformer;
+ }
+
+ /**
+ * Retrieves the TransportPerformer used by this session. If called before
+ * {@link #setTransportPerformerEnabled} null will be returned.
+ *
+ * @return The TransportPerformer associated with this session or null
+ */
+ public TransportPerformer getTransportPerformer() {
+ return mPerformer;
+ }
+
+ /**
+ * Call after you have finished setting up the session. This will make it
+ * available to listeners and begin pushing updates to MediaControllers.
+ * This can only be called once.
+ */
+ public void publish() {
+ if (mPublished) {
+ throw new RuntimeException("publish() may only be called once.");
+ }
+ try {
+ mBinder.publish();
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Failure in publish.", e);
+ }
+ mPublished = true;
+ }
+
+ /**
+ * Send a proprietary event to all MediaControllers listening to this
+ * Session. It's up to the Controller/Session owner to determine the meaning
+ * of any events.
+ *
+ * @param event The name of the event to send
+ * @param extras Any extras included with the event
+ */
+ public void sendEvent(String event, Bundle extras) {
+ if (TextUtils.isEmpty(event)) {
+ throw new IllegalArgumentException("event cannot be null or empty");
+ }
+ try {
+ mBinder.sendEvent(event, extras);
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error sending event", e);
+ }
+ }
+
+ /**
+ * This must be called when an app has finished performing playback. If
+ * playback is expected to start again shortly the session can be left open,
+ * but it must be released if your activity or service is being destroyed.
+ */
+ public void release() {
+ try {
+ mBinder.destroy();
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error releasing session: ", e);
+ }
+ }
+
+ /**
+ * Retrieve a token object that can be used by apps to create a
+ * {@link SessionController} 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() {
+ return mSessionToken;
+ }
+
+ /**
+ * Connect to the current route using the specified request.
+ * <p>
+ * 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.
+ * <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
+ * MediaRouteOptions.
+ *
+ * @param route The route the app is trying to connect to.
+ * @param request The connection request to use.
+ */
+ public void connect(RouteInfo route, RouteOptions request) {
+ if (route == null) {
+ throw new IllegalArgumentException("Must specify the route");
+ }
+ if (request == null) {
+ throw new IllegalArgumentException("Must specify the connection request");
+ }
+ try {
+ mBinder.connectToRoute(route, request);
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error starting connection to route", e);
+ }
+ }
+
+ /**
+ * Disconnect from the current route. After calling you will be switched
+ * back to the default route.
+ *
+ * @param route The route to disconnect from.
+ */
+ public void disconnect(RouteInfo route) {
+ // TODO
+ }
+
+ /**
+ * Set the list of route options your app is interested in connecting to. It
+ * will be used for picking valid routes.
+ *
+ * @param options The set of route options your app may use to connect.
+ */
+ public void setRouteOptions(List<RouteOptions> options) {
+ try {
+ mBinder.setRouteOptions(options);
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error setting route options.", e);
+ }
+ }
+
+ /**
+ * @hide
+ * TODO allow multiple listeners for the same interface, allow removal
+ */
+ public void addInterfaceListener(String iface,
+ RouteInterface.EventListener listener) {
+ mInterfaceListeners.put(iface, listener);
+ }
+
+ /**
+ * @hide
+ */
+ public boolean sendRouteCommand(RouteCommand command, ResultReceiver cb) {
+ try {
+ mBinder.sendRouteCommand(command, cb);
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error sending command to route.", e);
+ return false;
+ }
+ return true;
+ }
+
+ private MessageHandler getHandlerForCallbackLocked(Callback cb) {
+ if (cb == null) {
+ throw new IllegalArgumentException("Callback cannot be null");
+ }
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ MessageHandler handler = mCallbacks.get(i);
+ if (cb == handler.mCallback) {
+ return handler;
+ }
+ }
+ return null;
+ }
+
+ private boolean removeCallbackLocked(Callback cb) {
+ if (cb == null) {
+ throw new IllegalArgumentException("Callback cannot be null");
+ }
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ MessageHandler handler = mCallbacks.get(i);
+ if (cb == handler.mCallback) {
+ mCallbacks.remove(i);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void postCommand(String command, Bundle extras, ResultReceiver resultCb) {
+ Command cmd = new Command(command, extras, resultCb);
+ synchronized (mLock) {
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ mCallbacks.get(i).post(MSG_COMMAND, cmd);
+ }
+ }
+ }
+
+ private void postMediaButton(Intent mediaButtonIntent) {
+ synchronized (mLock) {
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ mCallbacks.get(i).post(MSG_MEDIA_BUTTON, mediaButtonIntent);
+ }
+ }
+ }
+
+ private void postRequestRouteChange(RouteInfo route) {
+ synchronized (mLock) {
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ mCallbacks.get(i).post(MSG_ROUTE_CHANGE, route);
+ }
+ }
+ }
+
+ private void postRouteConnected(RouteInfo route, RouteOptions options) {
+ synchronized (mLock) {
+ mRoute = new Route(route, options, this);
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ mCallbacks.get(i).post(MSG_ROUTE_CONNECTED, mRoute);
+ }
+ }
+ }
+
+ /**
+ * Receives commands or updates from controllers and routes. An app can
+ * specify what commands and buttons it supports by setting them on the
+ * MediaSession (TODO).
+ */
+ public abstract static class Callback {
+
+ public Callback() {
+ }
+
+ /**
+ * Called when a media button is pressed and this session has the
+ * highest priority or a controller sends a media button event to the
+ * session. TODO determine if using Intents identical to the ones
+ * RemoteControlClient receives is useful
+ * <p>
+ * The intent will be of type {@link Intent#ACTION_MEDIA_BUTTON} with a
+ * KeyEvent in {@link Intent#EXTRA_KEY_EVENT}
+ *
+ * @param mediaButtonIntent an intent containing the KeyEvent as an
+ * extra
+ */
+ public void onMediaButton(Intent mediaButtonIntent) {
+ }
+
+ /**
+ * Called when a controller has sent a custom command to this session.
+ * The owner of the session may handle custom commands but is not
+ * required to.
+ *
+ * @param command
+ * @param extras optional
+ */
+ public void onCommand(String command, Bundle extras, ResultReceiver cb) {
+ }
+
+ /**
+ * Called when the user has selected a different route to connect to.
+ * The app is responsible for connecting to the new route and migrating
+ * ongoing playback if necessary.
+ *
+ * @param route
+ */
+ public void onRequestRouteChange(RouteInfo route) {
+ }
+
+ /**
+ * Called when a route has successfully connected. Calls to the route
+ * are now valid.
+ *
+ * @param route The route that was connected
+ */
+ public void onRouteConnected(Route route) {
+ }
+
+ /**
+ * Called when a route was disconnected. Further calls to the route will
+ * fail. If available a reason for being disconnected will be provided.
+ * <p>
+ * Valid reasons are:
+ * <ul>
+ * </ul>
+ *
+ * @param route The route that disconnected
+ * @param reason The reason for the disconnect
+ */
+ public void onRouteDisconnected(Route route, int reason) {
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static class CallbackStub extends ISessionCallback.Stub {
+ private WeakReference<Session> mMediaSession;
+
+ public void setMediaSession(Session session) {
+ mMediaSession = new WeakReference<Session>(session);
+ }
+
+ @Override
+ public void onCommand(String command, Bundle extras, ResultReceiver cb)
+ throws RemoteException {
+ Session session = mMediaSession.get();
+ if (session != null) {
+ session.postCommand(command, extras, cb);
+ }
+ }
+
+ @Override
+ public void onMediaButton(Intent mediaButtonIntent) throws RemoteException {
+ Session session = mMediaSession.get();
+ if (session != null) {
+ session.postMediaButton(mediaButtonIntent);
+ }
+ }
+
+ @Override
+ public void onRequestRouteChange(RouteInfo route) throws RemoteException {
+ Session session = mMediaSession.get();
+ if (session != null) {
+ session.postRequestRouteChange(route);
+ }
+ }
+
+ @Override
+ public void onRouteConnected(RouteInfo route, RouteOptions options) {
+ Session session = mMediaSession.get();
+ if (session != null) {
+ session.postRouteConnected(route, options);
+ }
+ }
+
+ @Override
+ public void onPlay() throws RemoteException {
+ Session session = mMediaSession.get();
+ if (session != null) {
+ TransportPerformer tp = session.getTransportPerformer();
+ if (tp != null) {
+ tp.onPlay();
+ }
+ }
+ }
+
+ @Override
+ public void onPause() throws RemoteException {
+ Session session = mMediaSession.get();
+ if (session != null) {
+ TransportPerformer tp = session.getTransportPerformer();
+ if (tp != null) {
+ tp.onPause();
+ }
+ }
+ }
+
+ @Override
+ public void onStop() throws RemoteException {
+ Session session = mMediaSession.get();
+ if (session != null) {
+ TransportPerformer tp = session.getTransportPerformer();
+ if (tp != null) {
+ tp.onStop();
+ }
+ }
+ }
+
+ @Override
+ public void onNext() throws RemoteException {
+ Session session = mMediaSession.get();
+ if (session != null) {
+ TransportPerformer tp = session.getTransportPerformer();
+ if (tp != null) {
+ tp.onNext();
+ }
+ }
+ }
+
+ @Override
+ public void onPrevious() throws RemoteException {
+ Session session = mMediaSession.get();
+ if (session != null) {
+ TransportPerformer tp = session.getTransportPerformer();
+ if (tp != null) {
+ tp.onPrevious();
+ }
+ }
+ }
+
+ @Override
+ public void onFastForward() throws RemoteException {
+ Session session = mMediaSession.get();
+ if (session != null) {
+ TransportPerformer tp = session.getTransportPerformer();
+ if (tp != null) {
+ tp.onFastForward();
+ }
+ }
+ }
+
+ @Override
+ public void onRewind() throws RemoteException {
+ Session session = mMediaSession.get();
+ if (session != null) {
+ TransportPerformer tp = session.getTransportPerformer();
+ if (tp != null) {
+ tp.onRewind();
+ }
+ }
+ }
+
+ @Override
+ public void onSeekTo(long pos) throws RemoteException {
+ Session session = mMediaSession.get();
+ if (session != null) {
+ TransportPerformer tp = session.getTransportPerformer();
+ if (tp != null) {
+ tp.onSeekTo(pos);
+ }
+ }
+ }
+
+ @Override
+ public void onRate(Rating rating) throws RemoteException {
+ Session session = mMediaSession.get();
+ if (session != null) {
+ TransportPerformer tp = session.getTransportPerformer();
+ if (tp != null) {
+ tp.onRate(rating);
+ }
+ }
+ }
+
+ @Override
+ public void onRouteEvent(RouteEvent event) throws RemoteException {
+ Session session = mMediaSession.get();
+ if (session != null) {
+ RouteInterface.EventListener iface
+ = session.mInterfaceListeners.get(event.getIface());
+ Log.d(TAG, "Received route event on iface " + event.getIface() + ". Listener is "
+ + iface);
+ if (iface != null) {
+ iface.onEvent(event.getEvent(), event.getExtras());
+ }
+ }
+ }
+
+ @Override
+ public void onRouteStateChange(int state) throws RemoteException {
+ // TODO
+
+ }
+
+ }
+
+ private class MessageHandler extends Handler {
+ private Session.Callback mCallback;
+
+ public MessageHandler(Looper looper, Session.Callback callback) {
+ super(looper, null, true);
+ mCallback = callback;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ synchronized (mLock) {
+ if (mCallback == null) {
+ return;
+ }
+ switch (msg.what) {
+ case MSG_MEDIA_BUTTON:
+ mCallback.onMediaButton((Intent) msg.obj);
+ break;
+ case MSG_COMMAND:
+ Command cmd = (Command) msg.obj;
+ mCallback.onCommand(cmd.command, cmd.extras, cmd.stub);
+ break;
+ case MSG_ROUTE_CHANGE:
+ mCallback.onRequestRouteChange((RouteInfo) msg.obj);
+ break;
+ case MSG_ROUTE_CONNECTED:
+ mCallback.onRouteConnected((Route) msg.obj);
+ break;
+ }
+ }
+ }
+
+ public void post(int what, Object obj) {
+ obtainMessage(what, obj).sendToTarget();
+ }
+ }
+
+ private static final class Command {
+ public final String command;
+ public final Bundle extras;
+ public final ResultReceiver stub;
+
+ public Command(String command, Bundle extras, ResultReceiver stub) {
+ this.command = command;
+ this.extras = extras;
+ this.stub = stub;
+ }
+ }
+}
diff --git a/media/java/android/media/session/SessionController.java b/media/java/android/media/session/SessionController.java
new file mode 100644
index 0000000..dc4f7d9
--- /dev/null
+++ b/media/java/android/media/session/SessionController.java
@@ -0,0 +1,365 @@
+/*
+ * 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.session;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * Allows an app to interact with an ongoing media session. Media buttons and
+ * 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
+ * hold the "android.permission.MEDIA_CONTENT_CONTROL" permission or directly if
+ * you have a {@link SessionToken} from the session owner.
+ * <p>
+ * MediaController objects are thread-safe.
+ */
+public final class SessionController {
+ private static final String TAG = "SessionController";
+
+ private static final int MSG_EVENT = 1;
+ private static final int MESSAGE_PLAYBACK_STATE = 2;
+ private static final int MESSAGE_METADATA = 3;
+ private static final int MSG_ROUTE = 4;
+
+ private final ISessionController mSessionBinder;
+
+ private final CallbackStub mCbStub = new CallbackStub(this);
+ private final ArrayList<MessageHandler> mCallbacks = new ArrayList<MessageHandler>();
+ private final Object mLock = new Object();
+
+ private boolean mCbRegistered = false;
+
+ private TransportController mTransportController;
+
+ private SessionController(ISessionController sessionBinder) {
+ mSessionBinder = sessionBinder;
+ }
+
+ /**
+ * @hide
+ */
+ public static SessionController fromBinder(ISessionController sessionBinder) {
+ SessionController controller = new SessionController(sessionBinder);
+ try {
+ controller.mSessionBinder.registerCallbackListener(controller.mCbStub);
+ if (controller.mSessionBinder.isTransportControlEnabled()) {
+ controller.mTransportController = new TransportController(sessionBinder);
+ }
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "MediaController created with expired token", e);
+ controller = null;
+ }
+ return controller;
+ }
+
+ /**
+ * Get a new MediaController for a MediaSessionToken. If successful the
+ * controller returned will be connected to the session that generated the
+ * token.
+ *
+ * @param token The session token to use
+ * @return A controller for the session or null
+ */
+ public static SessionController fromToken(SessionToken token) {
+ return fromBinder(token.getBinder());
+ }
+
+ /**
+ * Get a TransportController if the session supports it. If it is not
+ * supported null will be returned.
+ *
+ * @return A TransportController or null
+ */
+ public TransportController getTransportController() {
+ return mTransportController;
+ }
+
+ /**
+ * Send the specified media button to the session. Only media keys can be
+ * sent using this method.
+ *
+ * @param keycode The media button keycode, such as
+ * {@link KeyEvent#KEYCODE_MEDIA_PLAY}.
+ */
+ public void sendMediaButton(int keycode) {
+ if (!KeyEvent.isMediaKey(keycode)) {
+ throw new IllegalArgumentException("May only send media buttons through "
+ + "sendMediaButton");
+ }
+ // TODO do something better than key down/up events
+ KeyEvent event = new KeyEvent(KeyEvent.ACTION_UP, keycode);
+ try {
+ mSessionBinder.sendMediaButton(event);
+ } catch (RemoteException e) {
+ Log.d(TAG, "Dead object in sendMediaButton", e);
+ }
+ }
+
+ /**
+ * Adds a callback to receive updates from the Session. Updates will be
+ * posted on the caller's thread.
+ *
+ * @param cb The callback object, must not be null
+ */
+ public void addCallback(Callback cb) {
+ addCallback(cb, null);
+ }
+
+ /**
+ * Adds a callback to receive updates from the session. Updates will be
+ * posted on the specified handler's thread.
+ *
+ * @param cb Cannot be null.
+ * @param handler The handler to post updates on. If null the callers thread
+ * will be used
+ */
+ public void addCallback(Callback cb, Handler handler) {
+ if (handler == null) {
+ handler = new Handler();
+ }
+ synchronized (mLock) {
+ addCallbackLocked(cb, handler);
+ }
+ }
+
+ /**
+ * Stop receiving updates on the specified callback. If an update has
+ * already been posted you may still receive it after calling this method.
+ *
+ * @param cb The callback to remove
+ */
+ public void removeCallback(Callback cb) {
+ synchronized (mLock) {
+ removeCallbackLocked(cb);
+ }
+ }
+
+ /**
+ * Sends a generic command to the session. It is up to the session creator
+ * to decide what commands and parameters they will support. As such,
+ * commands should only be sent to sessions that the controller owns.
+ *
+ * @param command The command to send
+ * @param params Any parameters to include with the command
+ * @param cb The callback to receive the result on
+ */
+ public void sendCommand(String command, Bundle params, ResultReceiver cb) {
+ if (TextUtils.isEmpty(command)) {
+ throw new IllegalArgumentException("command cannot be null or empty");
+ }
+ try {
+ mSessionBinder.sendCommand(command, params, cb);
+ } catch (RemoteException e) {
+ Log.d(TAG, "Dead object in sendCommand.", e);
+ }
+ }
+
+ /**
+ * Request that the route picker be shown for this session. This should
+ * generally be called in response to a user action.
+ */
+ public void showRoutePicker() {
+ try {
+ mSessionBinder.showRoutePicker();
+ } catch (RemoteException e) {
+ Log.d(TAG, "Dead object in showRoutePicker", e);
+ }
+ }
+
+ /*
+ * @hide
+ */
+ ISessionController getSessionBinder() {
+ return mSessionBinder;
+ }
+
+ private void addCallbackLocked(Callback cb, Handler handler) {
+ if (cb == null) {
+ throw new IllegalArgumentException("Callback cannot be null");
+ }
+ if (handler == null) {
+ throw new IllegalArgumentException("Handler cannot be null");
+ }
+ if (getHandlerForCallbackLocked(cb) != null) {
+ Log.w(TAG, "Callback is already added, ignoring");
+ return;
+ }
+ MessageHandler holder = new MessageHandler(handler.getLooper(), cb);
+ mCallbacks.add(holder);
+
+ if (!mCbRegistered) {
+ try {
+ mSessionBinder.registerCallbackListener(mCbStub);
+ mCbRegistered = true;
+ } catch (RemoteException e) {
+ Log.d(TAG, "Dead object in registerCallback", e);
+ }
+ }
+ }
+
+ private boolean removeCallbackLocked(Callback cb) {
+ if (cb == null) {
+ throw new IllegalArgumentException("Callback cannot be null");
+ }
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ MessageHandler handler = mCallbacks.get(i);
+ if (cb == handler.mCallback) {
+ mCallbacks.remove(i);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private MessageHandler getHandlerForCallbackLocked(Callback cb) {
+ if (cb == null) {
+ throw new IllegalArgumentException("Callback cannot be null");
+ }
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ MessageHandler handler = mCallbacks.get(i);
+ if (cb == handler.mCallback) {
+ return handler;
+ }
+ }
+ return null;
+ }
+
+ private void postEvent(String event, Bundle extras) {
+ synchronized (mLock) {
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ mCallbacks.get(i).post(MSG_EVENT, event, extras);
+ }
+ }
+ }
+
+ private void postRouteChanged(RouteInfo route) {
+ synchronized (mLock) {
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ mCallbacks.get(i).post(MSG_ROUTE, route, null);
+ }
+ }
+ }
+
+ /**
+ * Callback for receiving updates on from the session. A Callback can be
+ * registered using {@link #addCallback}
+ */
+ public static abstract class Callback {
+ /**
+ * Override to handle custom events sent by the session owner without a
+ * specified interface. Controllers should only handle these for
+ * sessions they own.
+ *
+ * @param event
+ */
+ public void onEvent(String event, Bundle extras) {
+ }
+
+ /**
+ * Override to handle route changes for this session.
+ *
+ * @param route
+ */
+ public void onRouteChanged(RouteInfo route) {
+ }
+ }
+
+ private final static class CallbackStub extends ISessionControllerCallback.Stub {
+ private final WeakReference<SessionController> mController;
+
+ public CallbackStub(SessionController controller) {
+ mController = new WeakReference<SessionController>(controller);
+ }
+
+ @Override
+ public void onEvent(String event, Bundle extras) {
+ SessionController controller = mController.get();
+ if (controller != null) {
+ controller.postEvent(event, extras);
+ }
+ }
+
+ @Override
+ public void onRouteChanged(RouteInfo route) {
+ SessionController controller = mController.get();
+ if (controller != null) {
+ controller.postRouteChanged(route);
+ }
+ }
+
+ @Override
+ public void onPlaybackStateChanged(PlaybackState state) {
+ SessionController controller = mController.get();
+ if (controller != null) {
+ TransportController tc = controller.getTransportController();
+ if (tc != null) {
+ tc.postPlaybackStateChanged(state);
+ }
+ }
+ }
+
+ @Override
+ public void onMetadataChanged(MediaMetadata metadata) {
+ SessionController controller = mController.get();
+ if (controller != null) {
+ TransportController tc = controller.getTransportController();
+ if (tc != null) {
+ tc.postMetadataChanged(metadata);
+ }
+ }
+ }
+
+ }
+
+ private final static class MessageHandler extends Handler {
+ private final SessionController.Callback mCallback;
+
+ public MessageHandler(Looper looper, SessionController.Callback cb) {
+ super(looper, null, true);
+ mCallback = cb;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_EVENT:
+ mCallback.onEvent((String) msg.obj, msg.getData());
+ break;
+ case MSG_ROUTE:
+ mCallback.onRouteChanged((RouteInfo) msg.obj);
+ }
+ }
+
+ public void post(int what, Object obj, Bundle data) {
+ obtainMessage(what, obj).sendToTarget();
+ }
+ }
+
+}
diff --git a/media/java/android/media/session/SessionInfo.java b/media/java/android/media/session/SessionInfo.java
new file mode 100644
index 0000000..22d8ab1
--- /dev/null
+++ b/media/java/android/media/session/SessionInfo.java
@@ -0,0 +1,82 @@
+/*
+ * 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.session;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Information about a media session, including the owner's package name.
+ */
+public final class SessionInfo implements Parcelable {
+ private final String mId;
+ private final String mPackageName;
+
+ /**
+ * @hide
+ */
+ public SessionInfo(String id, String packageName) {
+ mId = id;
+ mPackageName = packageName;
+ }
+
+ private SessionInfo(Parcel in) {
+ mId = in.readString();
+ mPackageName = in.readString();
+ }
+
+ /**
+ * Get the package name of the owner of this session.
+ *
+ * @return The owner's package name
+ */
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Get the unique id for this session.
+ *
+ * @return The id for the session.
+ */
+ public String getId() {
+ return mId;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mId);
+ dest.writeString(mPackageName);
+ }
+
+ public static final Parcelable.Creator<SessionInfo> CREATOR
+ = new Parcelable.Creator<SessionInfo>() {
+ @Override
+ public SessionInfo createFromParcel(Parcel in) {
+ return new SessionInfo(in);
+ }
+
+ @Override
+ public SessionInfo[] newArray(int size) {
+ return new SessionInfo[size];
+ }
+ };
+}
diff --git a/media/java/android/media/session/SessionManager.java b/media/java/android/media/session/SessionManager.java
new file mode 100644
index 0000000..15bf0e3
--- /dev/null
+++ b/media/java/android/media/session/SessionManager.java
@@ -0,0 +1,90 @@
+/*
+ * 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.session;
+
+import android.content.Context;
+import android.media.session.ISessionManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * MediaSessionManager allows the creation and control of MediaSessions in the
+ * system. A MediaSession enables publishing information about ongoing media and
+ * interacting with MediaControllers and MediaRoutes.
+ * <p>
+ * Use <code>Context.getSystemService(Context.MEDIA_SESSION_SERVICE)</code> to
+ * get an instance of this class.
+ * <p>
+ *
+ * @see Session
+ * @see SessionController
+ */
+public final class SessionManager {
+ private static final String TAG = "SessionManager";
+
+ private final ISessionManager mService;
+
+ private Context mContext;
+
+ /**
+ * @hide
+ */
+ public SessionManager(Context context) {
+ // Consider rewriting like DisplayManagerGlobal
+ // Decide if we need context
+ mContext = context;
+ IBinder b = ServiceManager.getService(Context.MEDIA_SESSION_SERVICE);
+ mService = ISessionManager.Stub.asInterface(b);
+ }
+
+ /**
+ * Creates a new session.
+ *
+ * @param tag A short name for debugging purposes
+ * @return a {@link Session} for the new session
+ */
+ public Session createSession(String tag) {
+ try {
+ Session.CallbackStub cbStub = new Session.CallbackStub();
+ Session session = new Session(mService
+ .createSession(mContext.getPackageName(), cbStub, tag), cbStub);
+ cbStub.setMediaSession(session);
+
+ return session;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to create session: ", e);
+ return null;
+ }
+ }
+
+ /**
+ * Get a list of controllers for all ongoing sessions. This requires the
+ * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by
+ * the calling app.
+ *
+ * @return a list of controllers for ongoing sessions
+ */
+ public List<SessionController> getActiveSessions() {
+ // TODO
+ return new ArrayList<SessionController>();
+ }
+}
diff --git a/media/java/android/media/session/SessionToken.aidl b/media/java/android/media/session/SessionToken.aidl
new file mode 100644
index 0000000..db35f85
--- /dev/null
+++ b/media/java/android/media/session/SessionToken.aidl
@@ -0,0 +1,18 @@
+/* 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.
+*/
+
+package android.media.session;
+
+parcelable SessionToken;
diff --git a/media/java/android/media/session/SessionToken.java b/media/java/android/media/session/SessionToken.java
new file mode 100644
index 0000000..59486f6
--- /dev/null
+++ b/media/java/android/media/session/SessionToken.java
@@ -0,0 +1,66 @@
+/*
+ * 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.session;
+
+import android.media.session.ISessionController;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class SessionToken implements Parcelable {
+ private ISessionController mBinder;
+
+ /**
+ * @hide
+ */
+ SessionToken(ISessionController binder) {
+ mBinder = binder;
+ }
+
+ private SessionToken(Parcel in) {
+ mBinder = ISessionController.Stub.asInterface(in.readStrongBinder());
+ }
+
+ /**
+ * @hide
+ */
+ ISessionController getBinder() {
+ return mBinder;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongBinder(mBinder.asBinder());
+ }
+
+ public static final Parcelable.Creator<SessionToken> CREATOR
+ = new Parcelable.Creator<SessionToken>() {
+ @Override
+ public SessionToken createFromParcel(Parcel in) {
+ return new SessionToken(in);
+ }
+
+ @Override
+ public SessionToken[] newArray(int size) {
+ return new SessionToken[size];
+ }
+ };
+}
diff --git a/media/java/android/media/session/TransportController.java b/media/java/android/media/session/TransportController.java
new file mode 100644
index 0000000..9574df6
--- /dev/null
+++ b/media/java/android/media/session/TransportController.java
@@ -0,0 +1,342 @@
+/*
+ * 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.session;
+
+import android.media.Rating;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * Interface for controlling media playback on a session. This allows an app to
+ * request changes in playback, retrieve the current playback state and
+ * metadata, and listen for changes to the playback state and metadata.
+ */
+public final class TransportController {
+ private static final String TAG = "TransportController";
+
+ private final Object mLock = new Object();
+ private final ArrayList<MessageHandler> mListeners = new ArrayList<MessageHandler>();
+ private final ISessionController mBinder;
+
+ /**
+ * @hide
+ */
+ public TransportController(ISessionController binder) {
+ mBinder = binder;
+ }
+
+ /**
+ * Start listening to changes in playback state.
+ */
+ public void addStateListener(TransportStateListener listener) {
+ addStateListener(listener, null);
+ }
+
+ public void addStateListener(TransportStateListener listener, Handler handler) {
+ if (listener == null) {
+ throw new IllegalArgumentException("Listener cannot be null");
+ }
+ synchronized (mLock) {
+ if (getHandlerForListenerLocked(listener) != null) {
+ Log.w(TAG, "Listener is already added, ignoring");
+ return;
+ }
+ if (handler == null) {
+ handler = new Handler();
+ }
+
+ MessageHandler msgHandler = new MessageHandler(handler.getLooper(), listener);
+ mListeners.add(msgHandler);
+ }
+ }
+
+ /**
+ * Stop listening to changes in playback state.
+ */
+ public void removeStateListener(TransportStateListener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("Listener cannot be null");
+ }
+ synchronized (mLock) {
+ removeStateListenerLocked(listener);
+ }
+ }
+
+ /**
+ * Request that the player start its playback at its current position.
+ */
+ public void play() {
+ try {
+ mBinder.play();
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error calling play.", e);
+ }
+ }
+
+ /**
+ * Request that the player pause its playback and stay at its current
+ * position.
+ */
+ public void pause() {
+ try {
+ mBinder.pause();
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error calling pause.", e);
+ }
+ }
+
+ /**
+ * Request that the player stop its playback; it may clear its state in
+ * whatever way is appropriate.
+ */
+ public void stop() {
+ try {
+ mBinder.stop();
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error calling stop.", e);
+ }
+ }
+
+ /**
+ * Move to a new location in the media stream.
+ *
+ * @param pos Position to move to, in milliseconds.
+ */
+ public void seekTo(long pos) {
+ try {
+ mBinder.seekTo(pos);
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error calling seekTo.", e);
+ }
+ }
+
+ /**
+ * Start fast forwarding. If playback is already fast forwarding this may
+ * increase the rate.
+ */
+ public void fastForward() {
+ try {
+ mBinder.fastForward();
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error calling fastForward.", e);
+ }
+ }
+
+ /**
+ * Skip to the next item.
+ */
+ public void next() {
+ try {
+ mBinder.next();
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error calling next.", e);
+ }
+ }
+
+ /**
+ * Start rewinding. If playback is already rewinding this may increase the
+ * rate.
+ */
+ public void rewind() {
+ try {
+ mBinder.rewind();
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error calling rewind.", e);
+ }
+ }
+
+ /**
+ * Skip to the previous item.
+ */
+ public void previous() {
+ try {
+ mBinder.previous();
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error calling previous.", e);
+ }
+ }
+
+ /**
+ * Rate the current content. This will cause the rating to be set for the
+ * current user. The Rating type must match the type returned by
+ * {@link #getRatingType()}.
+ *
+ * @param rating The rating to set for the current content
+ */
+ public void rate(Rating rating) {
+ try {
+ mBinder.rate(rating);
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error calling rate.", e);
+ }
+ }
+
+ /**
+ * Get the rating type supported by the session. One of:
+ * <ul>
+ * <li>{@link Rating#RATING_NONE}</li>
+ * <li>{@link Rating#RATING_HEART}</li>
+ * <li>{@link Rating#RATING_THUMB_UP_DOWN}</li>
+ * <li>{@link Rating#RATING_3_STARS}</li>
+ * <li>{@link Rating#RATING_4_STARS}</li>
+ * <li>{@link Rating#RATING_5_STARS}</li>
+ * <li>{@link Rating#RATING_PERCENTAGE}</li>
+ * </ul>
+ *
+ * @return The supported rating type
+ */
+ public int getRatingType() {
+ try {
+ return mBinder.getRatingType();
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error calling getRatingType.", e);
+ return Rating.RATING_NONE;
+ }
+ }
+
+ /**
+ * Get the current playback state for this session.
+ *
+ * @return The current PlaybackState or null
+ */
+ public PlaybackState getPlaybackState() {
+ try {
+ return mBinder.getPlaybackState();
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error calling getPlaybackState.", e);
+ return null;
+ }
+ }
+
+ /**
+ * Get the current metadata for this session.
+ *
+ * @return The current MediaMetadata or null.
+ */
+ public MediaMetadata getMetadata() {
+ try {
+ return mBinder.getMetadata();
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error calling getMetadata.", e);
+ return null;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public final void postPlaybackStateChanged(PlaybackState state) {
+ synchronized (mLock) {
+ for (int i = mListeners.size() - 1; i >= 0; i--) {
+ mListeners.get(i).post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE, state);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public final void postMetadataChanged(MediaMetadata metadata) {
+ synchronized (mLock) {
+ for (int i = mListeners.size() - 1; i >= 0; i--) {
+ mListeners.get(i).post(MessageHandler.MSG_UPDATE_METADATA,
+ metadata);
+ }
+ }
+ }
+
+ private MessageHandler getHandlerForListenerLocked(TransportStateListener listener) {
+ for (int i = mListeners.size() - 1; i >= 0; i--) {
+ MessageHandler handler = mListeners.get(i);
+ if (listener == handler.mListener) {
+ return handler;
+ }
+ }
+ return null;
+ }
+
+ private boolean removeStateListenerLocked(TransportStateListener listener) {
+ for (int i = mListeners.size() - 1; i >= 0; i--) {
+ if (listener == mListeners.get(i).mListener) {
+ mListeners.remove(i);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Register using {@link #addStateListener} to receive updates when there
+ * are playback changes on the session.
+ */
+ public static abstract class TransportStateListener {
+ private MessageHandler mHandler;
+ /**
+ * Override to handle changes in playback state.
+ *
+ * @param state The new playback state of the session
+ */
+ public void onPlaybackStateChanged(PlaybackState state) {
+ }
+
+ /**
+ * Override to handle changes to the current metadata.
+ *
+ * @see MediaMetadata
+ * @param metadata The current metadata for the session or null
+ */
+ public void onMetadataChanged(MediaMetadata metadata) {
+ }
+
+ private void setHandler(Handler handler) {
+ mHandler = new MessageHandler(handler.getLooper(), this);
+ }
+ }
+
+ private static class MessageHandler extends Handler {
+ private static final int MSG_UPDATE_PLAYBACK_STATE = 1;
+ private static final int MSG_UPDATE_METADATA = 2;
+
+ private TransportStateListener mListener;
+
+ public MessageHandler(Looper looper, TransportStateListener cb) {
+ super(looper, null, true);
+ mListener = cb;
+ }
+
+ public void post(int msg, Object obj) {
+ obtainMessage(msg, obj).sendToTarget();
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_UPDATE_PLAYBACK_STATE:
+ mListener.onPlaybackStateChanged((PlaybackState) msg.obj);
+ break;
+ case MSG_UPDATE_METADATA:
+ mListener.onMetadataChanged((MediaMetadata) msg.obj);
+ break;
+ }
+ }
+ }
+
+}
diff --git a/media/java/android/media/session/TransportPerformer.java b/media/java/android/media/session/TransportPerformer.java
new file mode 100644
index 0000000..eddffd1
--- /dev/null
+++ b/media/java/android/media/session/TransportPerformer.java
@@ -0,0 +1,357 @@
+/*
+ * 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.session;
+
+import android.media.AudioManager;
+import android.media.Rating;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * Allows broadcasting of playback changes.
+ */
+public final class TransportPerformer {
+ private static final String TAG = "TransportPerformer";
+ private final Object mLock = new Object();
+ private final ArrayList<MessageHandler> mListeners = new ArrayList<MessageHandler>();
+
+ private ISession mBinder;
+
+ /**
+ * @hide
+ */
+ public TransportPerformer(ISession binder) {
+ mBinder = binder;
+ }
+
+ /**
+ * Add a listener to receive updates on.
+ *
+ * @param listener The callback object
+ */
+ public void addListener(Listener listener) {
+ addListener(listener, null);
+ }
+
+ /**
+ * Add a listener to receive updates on. The updates will be posted to the
+ * specified handler. If no handler is provided they will be posted to the
+ * caller's thread.
+ *
+ * @param listener The listener to receive updates on
+ * @param handler The handler to post the updates on
+ */
+ public void addListener(Listener listener, Handler handler) {
+ if (listener == null) {
+ throw new IllegalArgumentException("Listener cannot be null");
+ }
+ synchronized (mLock) {
+ if (getHandlerForListenerLocked(listener) != null) {
+ Log.w(TAG, "Listener is already added, ignoring");
+ }
+ if (handler == null) {
+ handler = new Handler();
+ }
+ MessageHandler msgHandler = new MessageHandler(handler.getLooper(), listener);
+ mListeners.add(msgHandler);
+ }
+ }
+
+ /**
+ * Stop receiving updates on the specified handler. If an update has already
+ * been posted you may still receive it after this call returns.
+ *
+ * @param listener The listener to stop receiving updates on
+ */
+ public void removeListener(Listener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("Listener cannot be null");
+ }
+ synchronized (mLock) {
+ removeListenerLocked(listener);
+ }
+ }
+
+ /**
+ * Update the current playback state.
+ *
+ * @param state The current state of playback
+ */
+ public final void setPlaybackState(PlaybackState state) {
+ try {
+ mBinder.setPlaybackState(state);
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Dead object in setPlaybackState.", e);
+ }
+ }
+
+ /**
+ * Update the current metadata. New metadata can be created using
+ * {@link MediaMetadata.Builder}.
+ *
+ * @param metadata The new metadata
+ */
+ public final void setMetadata(MediaMetadata metadata) {
+ try {
+ mBinder.setMetadata(metadata);
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Dead object in setPlaybackState.", e);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public final void onPlay() {
+ post(MessageHandler.MESSAGE_PLAY);
+ }
+
+ /**
+ * @hide
+ */
+ public final void onPause() {
+ post(MessageHandler.MESSAGE_PAUSE);
+ }
+
+ /**
+ * @hide
+ */
+ public final void onStop() {
+ post(MessageHandler.MESSAGE_STOP);
+ }
+
+ /**
+ * @hide
+ */
+ public final void onNext() {
+ post(MessageHandler.MESSAGE_NEXT);
+ }
+
+ /**
+ * @hide
+ */
+ public final void onPrevious() {
+ post(MessageHandler.MESSAGE_PREVIOUS);
+ }
+
+ /**
+ * @hide
+ */
+ public final void onFastForward() {
+ post(MessageHandler.MESSAGE_FAST_FORWARD);
+ }
+
+ /**
+ * @hide
+ */
+ public final void onRewind() {
+ post(MessageHandler.MESSAGE_REWIND);
+ }
+
+ /**
+ * @hide
+ */
+ public final void onSeekTo(long pos) {
+ post(MessageHandler.MESSAGE_SEEK_TO, pos);
+ }
+
+ /**
+ * @hide
+ */
+ public final void onRate(Rating rating) {
+ post(MessageHandler.MESSAGE_RATE, rating);
+ }
+
+ private MessageHandler getHandlerForListenerLocked(Listener listener) {
+ for (int i = mListeners.size() - 1; i >= 0; i--) {
+ MessageHandler handler = mListeners.get(i);
+ if (listener == handler.mListener) {
+ return handler;
+ }
+ }
+ return null;
+ }
+
+ private boolean removeListenerLocked(Listener listener) {
+ for (int i = mListeners.size() - 1; i >= 0; i--) {
+ if (listener == mListeners.get(i).mListener) {
+ mListeners.remove(i);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void post(int what, Object obj) {
+ synchronized (mLock) {
+ for (int i = mListeners.size() - 1; i >= 0; i--) {
+ mListeners.get(i).post(what, obj);
+ }
+ }
+ }
+
+ private void post(int what) {
+ post(what, null);
+ }
+
+ /**
+ * Extend Listener to handle transport controls. Listeners can be registered
+ * using {@link #addListener}.
+ */
+ public static abstract class Listener {
+
+ /**
+ * Override to handle requests to begin playback.
+ */
+ public void onPlay() {
+ }
+
+ /**
+ * Override to handle requests to pause playback.
+ */
+ public void onPause() {
+ }
+
+ /**
+ * Override to handle requests to skip to the next media item.
+ */
+ public void onNext() {
+ }
+
+ /**
+ * Override to handle requests to skip to the previous media item.
+ */
+ public void onPrevious() {
+ }
+
+ /**
+ * Override to handle requests to fast forward.
+ */
+ public void onFastForward() {
+ }
+
+ /**
+ * Override to handle requests to rewind.
+ */
+ public void onRewind() {
+ }
+
+ /**
+ * Override to handle requests to stop playback.
+ */
+ public void onStop() {
+ }
+
+ /**
+ * Override to handle requests to seek to a specific position in ms.
+ *
+ * @param pos New position to move to, in milliseconds.
+ */
+ public void onSeekTo(long pos) {
+ }
+
+ /**
+ * Override to handle the item being rated.
+ *
+ * @param rating
+ */
+ public void onRate(Rating rating) {
+ }
+
+ /**
+ * Report that audio focus has changed on the app. This only happens if
+ * you have indicated you have started playing with
+ * {@link #setPlaybackState}. TODO figure out route focus apis/handling.
+ *
+ * @param focusChange The type of focus change, TBD. The default
+ * implementation will deliver a call to {@link #onPause}
+ * when focus is lost.
+ */
+ public void onRouteFocusChange(int focusChange) {
+ switch (focusChange) {
+ case AudioManager.AUDIOFOCUS_LOSS:
+ onPause();
+ break;
+ }
+ }
+ }
+
+ private class MessageHandler extends Handler {
+ private static final int MESSAGE_PLAY = 1;
+ private static final int MESSAGE_PAUSE = 2;
+ private static final int MESSAGE_STOP = 3;
+ private static final int MESSAGE_NEXT = 4;
+ private static final int MESSAGE_PREVIOUS = 5;
+ private static final int MESSAGE_FAST_FORWARD = 6;
+ private static final int MESSAGE_REWIND = 7;
+ private static final int MESSAGE_SEEK_TO = 8;
+ private static final int MESSAGE_RATE = 9;
+
+ private Listener mListener;
+
+ public MessageHandler(Looper looper, Listener cb) {
+ super(looper);
+ mListener = cb;
+ }
+
+ public void post(int what, Object obj) {
+ obtainMessage(what, obj).sendToTarget();
+ }
+
+ public void post(int what) {
+ post(what, null);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_PLAY:
+ mListener.onPlay();
+ break;
+ case MESSAGE_PAUSE:
+ mListener.onPause();
+ break;
+ case MESSAGE_STOP:
+ mListener.onStop();
+ break;
+ case MESSAGE_NEXT:
+ mListener.onNext();
+ break;
+ case MESSAGE_PREVIOUS:
+ mListener.onPrevious();
+ break;
+ case MESSAGE_FAST_FORWARD:
+ mListener.onFastForward();
+ break;
+ case MESSAGE_REWIND:
+ mListener.onRewind();
+ break;
+ case MESSAGE_SEEK_TO:
+ mListener.onSeekTo((Long) msg.obj);
+ break;
+ case MESSAGE_RATE:
+ mListener.onRate((Rating) msg.obj);
+ break;
+ }
+ }
+ }
+}
diff --git a/media/java/android/media/videoeditor/MediaArtistNativeHelper.java b/media/java/android/media/videoeditor/MediaArtistNativeHelper.java
index 2b0b3e2..2d3de85 100644
--- a/media/java/android/media/videoeditor/MediaArtistNativeHelper.java
+++ b/media/java/android/media/videoeditor/MediaArtistNativeHelper.java
@@ -28,7 +28,6 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
-import android.graphics.Rect;
import android.graphics.Matrix;
import android.media.videoeditor.VideoEditor.ExportProgressListener;
import android.media.videoeditor.VideoEditor.PreviewProgressListener;
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index 9ceefc3..fce3fd0 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -16,21 +16,23 @@
package android.mtp;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.ContentValues;
import android.content.IContentProvider;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.media.MediaScanner;
import android.net.Uri;
-import android.os.Environment;
+import android.os.BatteryManager;
+import android.os.BatteryStats;
import android.os.RemoteException;
import android.provider.MediaStore;
import android.provider.MediaStore.Audio;
import android.provider.MediaStore.Files;
-import android.provider.MediaStore.Images;
import android.provider.MediaStore.MediaColumns;
import android.util.Log;
import android.view.Display;
@@ -115,11 +117,35 @@ public class MtpDatabase {
+ Files.FileColumns.PARENT + "=?";
private final MediaScanner mMediaScanner;
+ private MtpServer mServer;
+
+ // read from native code
+ private int mBatteryLevel;
+ private int mBatteryScale;
static {
System.loadLibrary("media_jni");
}
+ private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
+ mBatteryScale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
+ int newLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
+ if (newLevel != mBatteryLevel) {
+ mBatteryLevel = newLevel;
+ if (mServer != null) {
+ // send device property changed event
+ mServer.sendDevicePropertyChanged(
+ MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL);
+ }
+ }
+ }
+ }
+ };
+
public MtpDatabase(Context context, String volumeName, String storagePath,
String[] subDirectories) {
native_setup();
@@ -173,6 +199,23 @@ public class MtpDatabase {
initDeviceProperties(context);
}
+ public void setServer(MtpServer server) {
+ mServer = server;
+
+ // always unregister before registering
+ try {
+ mContext.unregisterReceiver(mBatteryReceiver);
+ } catch (IllegalArgumentException e) {
+ // wasn't previously registered, ignore
+ }
+
+ // register for battery notifications when we are connected
+ if (server != null) {
+ mContext.registerReceiver(mBatteryReceiver,
+ new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+ }
+ }
+
@Override
protected void finalize() throws Throwable {
try {
@@ -558,6 +601,11 @@ public class MtpDatabase {
MtpConstants.PROPERTY_DURATION,
MtpConstants.PROPERTY_GENRE,
MtpConstants.PROPERTY_COMPOSER,
+ MtpConstants.PROPERTY_AUDIO_WAVE_CODEC,
+ MtpConstants.PROPERTY_BITRATE_TYPE,
+ MtpConstants.PROPERTY_AUDIO_BITRATE,
+ MtpConstants.PROPERTY_NUMBER_OF_CHANNELS,
+ MtpConstants.PROPERTY_SAMPLE_RATE,
};
static final int[] VIDEO_PROPERTIES = {
@@ -665,6 +713,7 @@ public class MtpDatabase {
MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
+ MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL,
};
}
@@ -821,6 +870,8 @@ public class MtpDatabase {
outStringValue[imageSize.length()] = 0;
return MtpConstants.RESPONSE_OK;
+ // DEVICE_PROPERTY_BATTERY_LEVEL is implemented in the JNI code
+
default:
return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
}
diff --git a/media/java/android/mtp/MtpDevice.java b/media/java/android/mtp/MtpDevice.java
index 8310579..72dcaa8 100644
--- a/media/java/android/mtp/MtpDevice.java
+++ b/media/java/android/mtp/MtpDevice.java
@@ -18,8 +18,6 @@ package android.mtp;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
/**
* This class represents an MTP or PTP device connected on the USB host bus. An application can
diff --git a/media/java/android/mtp/MtpPropertyGroup.java b/media/java/android/mtp/MtpPropertyGroup.java
index 48da40f..781988d 100644
--- a/media/java/android/mtp/MtpPropertyGroup.java
+++ b/media/java/android/mtp/MtpPropertyGroup.java
@@ -20,7 +20,6 @@ import android.content.IContentProvider;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
-import android.provider.MediaStore;
import android.provider.MediaStore.Audio;
import android.provider.MediaStore.Files;
import android.provider.MediaStore.Images;
diff --git a/media/java/android/mtp/MtpServer.java b/media/java/android/mtp/MtpServer.java
index 266f78e..3814630 100644
--- a/media/java/android/mtp/MtpServer.java
+++ b/media/java/android/mtp/MtpServer.java
@@ -30,6 +30,7 @@ public class MtpServer implements Runnable {
public MtpServer(MtpDatabase database, boolean usePtp) {
native_setup(database, usePtp);
+ database.setServer(this);
}
public void start() {
@@ -51,6 +52,10 @@ public class MtpServer implements Runnable {
native_send_object_removed(handle);
}
+ public void sendDevicePropertyChanged(int property) {
+ native_send_device_property_changed(property);
+ }
+
public void addStorage(MtpStorage storage) {
native_add_storage(storage);
}
@@ -64,6 +69,7 @@ public class MtpServer implements Runnable {
private native final void native_cleanup();
private native final void native_send_object_added(int handle);
private native final void native_send_object_removed(int handle);
+ private native final void native_send_device_property_changed(int property);
private native final void native_add_storage(MtpStorage storage);
private native final void native_remove_storage(int storageId);
}
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index dea971e..ed98b96 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -8,6 +8,7 @@ LOCAL_SRC_FILES:= \
android_media_MediaCodecList.cpp \
android_media_MediaDrm.cpp \
android_media_MediaExtractor.cpp \
+ android_media_MediaHTTPConnection.cpp \
android_media_MediaMuxer.cpp \
android_media_MediaPlayer.cpp \
android_media_MediaRecorder.cpp \
@@ -37,6 +38,7 @@ LOCAL_SHARED_LIBRARIES := \
libcamera_client \
libmtp \
libusbhost \
+ libjhead \
libexif \
libstagefright_amrnb_common \
@@ -60,8 +62,7 @@ LOCAL_C_INCLUDES += \
$(call include-path-for, libhardware)/hardware \
system/media/camera/include \
$(PV_INCLUDES) \
- $(JNI_H_INCLUDE) \
- $(call include-path-for, corecg graphics)
+ $(JNI_H_INCLUDE)
LOCAL_CFLAGS +=
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index d475eee..716418c 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -86,8 +86,8 @@ public:
void setCpuConsumer(const sp<CpuConsumer>& consumer) { mConsumer = consumer; }
CpuConsumer* getCpuConsumer() { return mConsumer.get(); }
- void setBufferQueue(const sp<BufferQueue>& bq) { mBufferQueue = bq; }
- BufferQueue* getBufferQueue() { return mBufferQueue.get(); }
+ void setProducer(const sp<IGraphicBufferProducer>& producer) { mProducer = producer; }
+ IGraphicBufferProducer* getProducer() { return mProducer.get(); }
void setBufferFormat(int format) { mFormat = format; }
int getBufferFormat() { return mFormat; }
@@ -104,7 +104,7 @@ private:
List<CpuConsumer::LockedBuffer*> mBuffers;
sp<CpuConsumer> mConsumer;
- sp<BufferQueue> mBufferQueue;
+ sp<IGraphicBufferProducer> mProducer;
jobject mWeakThiz;
jclass mClazz;
int mFormat;
@@ -222,7 +222,7 @@ static CpuConsumer* ImageReader_getCpuConsumer(JNIEnv* env, jobject thiz)
return ctx->getCpuConsumer();
}
-static BufferQueue* ImageReader_getBufferQueue(JNIEnv* env, jobject thiz)
+static IGraphicBufferProducer* ImageReader_getProducer(JNIEnv* env, jobject thiz)
{
ALOGV("%s:", __FUNCTION__);
JNIImageReaderContext* const ctx = ImageReader_getContext(env, thiz);
@@ -230,7 +230,7 @@ static BufferQueue* ImageReader_getBufferQueue(JNIEnv* env, jobject thiz)
jniThrowRuntimeException(env, "ImageReaderContext is not initialized");
return NULL;
}
- return ctx->getBufferQueue();
+ return ctx->getProducer();
}
static void ImageReader_setNativeContext(JNIEnv* env,
@@ -386,11 +386,12 @@ static void Image_getLockedBufferInfo(JNIEnv* env, CpuConsumer::LockedBuffer* bu
dataSize = buffer->stride * buffer->height;
break;
case HAL_PIXEL_FORMAT_Y16:
+ bytesPerPixel = 2;
// Single plane, 16bpp, strides are specified in pixels, not in bytes
ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
pData = buffer->data;
- dataSize = buffer->stride * buffer->height * 2;
+ dataSize = buffer->stride * buffer->height * bytesPerPixel;
break;
case HAL_PIXEL_FORMAT_BLOB:
// Used for JPEG data, height must be 1, width == size, single plane.
@@ -402,9 +403,10 @@ static void Image_getLockedBufferInfo(JNIEnv* env, CpuConsumer::LockedBuffer* bu
break;
case HAL_PIXEL_FORMAT_RAW_SENSOR:
// Single plane 16bpp bayer data.
+ bytesPerPixel = 2;
ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
pData = buffer->data;
- dataSize = buffer->width * 2 * buffer->height;
+ dataSize = buffer->stride * buffer->height * bytesPerPixel;
break;
case HAL_PIXEL_FORMAT_RGBA_8888:
case HAL_PIXEL_FORMAT_RGBX_8888:
@@ -613,8 +615,10 @@ static void ImageReader_init(JNIEnv* env, jobject thiz, jobject weakThiz,
nativeFormat = Image_getPixelFormat(env, format);
- sp<BufferQueue> bq = new BufferQueue();
- sp<CpuConsumer> consumer = new CpuConsumer(bq, maxImages,
+ sp<IGraphicBufferProducer> gbProducer;
+ sp<IGraphicBufferConsumer> gbConsumer;
+ BufferQueue::createBufferQueue(&gbProducer, &gbConsumer);
+ sp<CpuConsumer> consumer = new CpuConsumer(gbConsumer, maxImages,
/*controlledByApp*/true);
// TODO: throw dvm exOutOfMemoryError?
if (consumer == NULL) {
@@ -629,7 +633,7 @@ static void ImageReader_init(JNIEnv* env, jobject thiz, jobject weakThiz,
}
sp<JNIImageReaderContext> ctx(new JNIImageReaderContext(env, weakThiz, clazz, maxImages));
ctx->setCpuConsumer(consumer);
- ctx->setBufferQueue(bq);
+ ctx->setProducer(gbProducer);
consumer->setFrameAvailableListener(ctx);
ImageReader_setNativeContext(env, thiz, ctx);
ctx->setBufferFormat(nativeFormat);
@@ -794,14 +798,14 @@ static jobject ImageReader_getSurface(JNIEnv* env, jobject thiz)
{
ALOGV("%s: ", __FUNCTION__);
- BufferQueue* bq = ImageReader_getBufferQueue(env, thiz);
- if (bq == NULL) {
+ IGraphicBufferProducer* gbp = ImageReader_getProducer(env, thiz);
+ if (gbp == NULL) {
jniThrowRuntimeException(env, "CpuConsumer is uninitialized");
return NULL;
}
// Wrap the IGBP in a Java-language Surface.
- return android_view_Surface_createFromIGraphicBufferProducer(env, bq);
+ return android_view_Surface_createFromIGraphicBufferProducer(env, gbp);
}
static jobject Image_createSurfacePlane(JNIEnv* env, jobject thiz, int idx)
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 221ea57..d04b1f8 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -27,6 +27,8 @@
#include "jni.h"
#include "JNIHelp.h"
+#include <cutils/compiler.h>
+
#include <gui/Surface.h>
#include <media/ICrypto.h>
@@ -51,6 +53,10 @@ enum {
DEQUEUE_INFO_OUTPUT_BUFFERS_CHANGED = -3,
};
+enum {
+ EVENT_NOTIFY = 1,
+};
+
struct CryptoErrorCodes {
jint cryptoErrorNoKey;
jint cryptoErrorKeyExpired;
@@ -59,6 +65,7 @@ struct CryptoErrorCodes {
struct fields_t {
jfieldID context;
+ jmethodID postEventFromNativeID;
jfieldID cryptoInfoNumSubSamplesID;
jfieldID cryptoInfoNumBytesOfClearDataID;
jfieldID cryptoInfoNumBytesOfEncryptedDataID;
@@ -75,7 +82,9 @@ JMediaCodec::JMediaCodec(
JNIEnv *env, jobject thiz,
const char *name, bool nameIsType, bool encoder)
: mClass(NULL),
- mObject(NULL) {
+ mObject(NULL),
+ mGeneration(1),
+ mRequestedActivityNotification(false) {
jclass clazz = env->GetObjectClass(thiz);
CHECK(clazz != NULL);
@@ -87,7 +96,7 @@ JMediaCodec::JMediaCodec(
mLooper->start(
false, // runOnCallingThread
- false, // canCallJava
+ true, // canCallJava
PRIORITY_FOREGROUND);
if (nameIsType) {
@@ -101,12 +110,39 @@ status_t JMediaCodec::initCheck() const {
return mCodec != NULL ? OK : NO_INIT;
}
-JMediaCodec::~JMediaCodec() {
+void JMediaCodec::registerSelf() {
+ mLooper->registerHandler(this);
+}
+
+void JMediaCodec::release() {
if (mCodec != NULL) {
mCodec->release();
mCodec.clear();
}
+ if (mLooper != NULL) {
+ mLooper->unregisterHandler(id());
+ mLooper->stop();
+ mLooper.clear();
+ }
+}
+
+JMediaCodec::~JMediaCodec() {
+ if (mCodec != NULL || mLooper != NULL) {
+ /* MediaCodec and looper should have been released explicitly already
+ * in setMediaCodec() (see comments in setMediaCodec()).
+ *
+ * Otherwise JMediaCodec::~JMediaCodec() might be called from within the
+ * message handler, doing release() there risks deadlock as MediaCodec::
+ * release() post synchronous message to the same looper.
+ *
+ * Print a warning and try to proceed with releasing.
+ */
+ ALOGW("try to release MediaCodec from JMediaCodec::~JMediaCodec()...");
+ release();
+ ALOGW("done releasing MediaCodec from JMediaCodec::~JMediaCodec().");
+ }
+
JNIEnv *env = AndroidRuntime::getJNIEnv();
env->DeleteWeakGlobalRef(mObject);
@@ -122,7 +158,8 @@ status_t JMediaCodec::configure(
int flags) {
sp<Surface> client;
if (bufferProducer != NULL) {
- mSurfaceTextureClient = new Surface(bufferProducer, true /* controlledByApp */);
+ mSurfaceTextureClient =
+ new Surface(bufferProducer, true /* controlledByApp */);
} else {
mSurfaceTextureClient.clear();
}
@@ -136,13 +173,32 @@ status_t JMediaCodec::createInputSurface(
}
status_t JMediaCodec::start() {
- return mCodec->start();
+ status_t err = mCodec->start();
+
+ if (err != OK) {
+ return err;
+ }
+
+ mActivityNotification = new AMessage(kWhatActivityNotify, id());
+ mActivityNotification->setInt32("generation", mGeneration);
+
+ requestActivityNotification();
+
+ return err;
}
status_t JMediaCodec::stop() {
mSurfaceTextureClient.clear();
- return mCodec->stop();
+ status_t err = mCodec->stop();
+
+ sp<AMessage> msg = new AMessage(kWhatStopActivityNotifications, id());
+ sp<AMessage> response;
+ msg->postAndAwaitResponse(&response);
+
+ mActivityNotification.clear();
+
+ return err;
}
status_t JMediaCodec::flush() {
@@ -174,7 +230,11 @@ status_t JMediaCodec::queueSecureInputBuffer(
}
status_t JMediaCodec::dequeueInputBuffer(size_t *index, int64_t timeoutUs) {
- return mCodec->dequeueInputBuffer(index, timeoutUs);
+ status_t err = mCodec->dequeueInputBuffer(index, timeoutUs);
+
+ requestActivityNotification();
+
+ return err;
}
status_t JMediaCodec::dequeueOutputBuffer(
@@ -182,9 +242,12 @@ status_t JMediaCodec::dequeueOutputBuffer(
size_t size, offset;
int64_t timeUs;
uint32_t flags;
- status_t err;
- if ((err = mCodec->dequeueOutputBuffer(
- index, &offset, &size, &timeUs, &flags, timeoutUs)) != OK) {
+ status_t err = mCodec->dequeueOutputBuffer(
+ index, &offset, &size, &timeUs, &flags, timeoutUs);
+
+ requestActivityNotification();
+
+ if (err != OK) {
return err;
}
@@ -320,6 +383,67 @@ void JMediaCodec::setVideoScalingMode(int mode) {
}
}
+void JMediaCodec::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatRequestActivityNotifications:
+ {
+ if (mRequestedActivityNotification) {
+ break;
+ }
+
+ mCodec->requestActivityNotification(mActivityNotification);
+ mRequestedActivityNotification = true;
+ break;
+ }
+
+ case kWhatActivityNotify:
+ {
+ {
+ int32_t generation;
+ CHECK(msg->findInt32("generation", &generation));
+
+ if (generation != mGeneration) {
+ // stale
+ break;
+ }
+
+ mRequestedActivityNotification = false;
+ }
+
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ env->CallVoidMethod(
+ mObject,
+ gFields.postEventFromNativeID,
+ EVENT_NOTIFY,
+ 0 /* arg1 */,
+ 0 /* arg2 */,
+ NULL /* obj */);
+
+ break;
+ }
+
+ case kWhatStopActivityNotifications:
+ {
+ uint32_t replyID;
+ CHECK(msg->senderAwaitsResponse(&replyID));
+
+ ++mGeneration;
+ mRequestedActivityNotification = false;
+
+ sp<AMessage> response = new AMessage;
+ response->postReply(replyID);
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void JMediaCodec::requestActivityNotification() {
+ (new AMessage(kWhatRequestActivityNotifications, id()))->post();
+}
+
} // namespace android
////////////////////////////////////////////////////////////////////////////////
@@ -333,6 +457,12 @@ static sp<JMediaCodec> setMediaCodec(
codec->incStrong(thiz);
}
if (old != NULL) {
+ /* release MediaCodec and stop the looper now before decStrong.
+ * otherwise JMediaCodec::~JMediaCodec() could be called from within
+ * its message handler, doing release() from there will deadlock
+ * (as MediaCodec::release() post synchronous message to the same looper)
+ */
+ old->release();
old->decStrong(thiz);
}
env->SetLongField(thiz, gFields.context, (jlong)codec.get());
@@ -610,6 +740,10 @@ static void android_media_MediaCodec_queueSecureInputBuffer(
} else if (numBytesOfClearDataObj != NULL
&& env->GetArrayLength(numBytesOfClearDataObj) < numSubSamples) {
err = -ERANGE;
+ // subSamples array may silently overflow if number of samples are too large. Use
+ // INT32_MAX as maximum allocation size may be less than SIZE_MAX on some platforms
+ } else if ( CC_UNLIKELY(numSubSamples >= INT32_MAX / sizeof(*subSamples)) ) {
+ err = -EINVAL;
} else {
jboolean isCopy;
@@ -888,6 +1022,12 @@ static void android_media_MediaCodec_native_init(JNIEnv *env) {
gFields.context = env->GetFieldID(clazz.get(), "mNativeContext", "J");
CHECK(gFields.context != NULL);
+ gFields.postEventFromNativeID =
+ env->GetMethodID(
+ clazz.get(), "postEventFromNative", "(IIILjava/lang/Object;)V");
+
+ CHECK(gFields.postEventFromNativeID != NULL);
+
clazz.reset(env->FindClass("android/media/MediaCodec$CryptoInfo"));
CHECK(clazz.get() != NULL);
@@ -961,6 +1101,8 @@ static void android_media_MediaCodec_native_setup(
return;
}
+ codec->registerSelf();
+
setMediaCodec(env,thiz, codec);
}
@@ -981,7 +1123,7 @@ static JNINativeMethod gMethods[] = {
(void *)android_media_MediaCodec_createInputSurface },
{ "start", "()V", (void *)android_media_MediaCodec_start },
- { "stop", "()V", (void *)android_media_MediaCodec_stop },
+ { "native_stop", "()V", (void *)android_media_MediaCodec_stop },
{ "flush", "()V", (void *)android_media_MediaCodec_flush },
{ "queueInputBuffer", "(IIIJI)V",
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index 2fbbd72..2f2ea96 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -21,8 +21,8 @@
#include <media/hardware/CryptoAPI.h>
#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/AHandler.h>
#include <utils/Errors.h>
-#include <utils/RefBase.h>
namespace android {
@@ -34,13 +34,16 @@ struct IGraphicBufferProducer;
struct MediaCodec;
class Surface;
-struct JMediaCodec : public RefBase {
+struct JMediaCodec : public AHandler {
JMediaCodec(
JNIEnv *env, jobject thiz,
const char *name, bool nameIsType, bool encoder);
status_t initCheck() const;
+ void registerSelf();
+ void release();
+
status_t configure(
const sp<AMessage> &format,
const sp<IGraphicBufferProducer> &bufferProducer,
@@ -94,7 +97,15 @@ struct JMediaCodec : public RefBase {
protected:
virtual ~JMediaCodec();
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
private:
+ enum {
+ kWhatActivityNotify,
+ kWhatRequestActivityNotifications,
+ kWhatStopActivityNotifications,
+ };
+
jclass mClass;
jweak mObject;
sp<Surface> mSurfaceTextureClient;
@@ -102,6 +113,12 @@ private:
sp<ALooper> mLooper;
sp<MediaCodec> mCodec;
+ sp<AMessage> mActivityNotification;
+ int32_t mGeneration;
+ bool mRequestedActivityNotification;
+
+ void requestActivityNotification();
+
DISALLOW_EVIL_CONSTRUCTORS(JMediaCodec);
};
diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp
index 854ee79..3dbf77b 100644
--- a/media/jni/android_media_MediaExtractor.cpp
+++ b/media/jni/android_media_MediaExtractor.cpp
@@ -26,6 +26,7 @@
#include "jni.h"
#include "JNIHelp.h"
+#include <media/IMediaHTTPService.h>
#include <media/hardware/CryptoAPI.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
@@ -35,6 +36,8 @@
#include <media/stagefright/MetaData.h>
#include <media/stagefright/NuMediaExtractor.h>
+#include "android_util_Binder.h"
+
namespace android {
struct fields_t {
@@ -135,8 +138,10 @@ JMediaExtractor::~JMediaExtractor() {
}
status_t JMediaExtractor::setDataSource(
- const char *path, const KeyedVector<String8, String8> *headers) {
- return mImpl->setDataSource(path, headers);
+ const sp<IMediaHTTPService> &httpService,
+ const char *path,
+ const KeyedVector<String8, String8> *headers) {
+ return mImpl->setDataSource(httpService, path, headers);
}
status_t JMediaExtractor::setDataSource(int fd, off64_t offset, off64_t size) {
@@ -661,7 +666,10 @@ static void android_media_MediaExtractor_native_setup(
static void android_media_MediaExtractor_setDataSource(
JNIEnv *env, jobject thiz,
- jstring pathObj, jobjectArray keysArray, jobjectArray valuesArray) {
+ jobject httpServiceBinderObj,
+ jstring pathObj,
+ jobjectArray keysArray,
+ jobjectArray valuesArray) {
sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
if (extractor == NULL) {
@@ -686,7 +694,13 @@ static void android_media_MediaExtractor_setDataSource(
return;
}
- status_t err = extractor->setDataSource(path, &headers);
+ sp<IMediaHTTPService> httpService;
+ if (httpServiceBinderObj != NULL) {
+ sp<IBinder> binder = ibinderForJavaObject(env, httpServiceBinderObj);
+ httpService = interface_cast<IMediaHTTPService>(binder);
+ }
+
+ status_t err = extractor->setDataSource(httpService, path, &headers);
env->ReleaseStringUTFChars(pathObj, path);
path = NULL;
@@ -839,8 +853,9 @@ static JNINativeMethod gMethods[] = {
{ "native_finalize", "()V",
(void *)android_media_MediaExtractor_native_finalize },
- { "setDataSource", "(Ljava/lang/String;[Ljava/lang/String;"
- "[Ljava/lang/String;)V",
+ { "nativeSetDataSource",
+ "(Landroid/os/IBinder;Ljava/lang/String;[Ljava/lang/String;"
+ "[Ljava/lang/String;)V",
(void *)android_media_MediaExtractor_setDataSource },
{ "setDataSource", "(Ljava/io/FileDescriptor;JJ)V",
diff --git a/media/jni/android_media_MediaExtractor.h b/media/jni/android_media_MediaExtractor.h
index ccbad8c..e5a0c16e 100644
--- a/media/jni/android_media_MediaExtractor.h
+++ b/media/jni/android_media_MediaExtractor.h
@@ -29,6 +29,7 @@
namespace android {
+struct IMediaHTTPService;
struct MetaData;
struct NuMediaExtractor;
@@ -36,6 +37,7 @@ struct JMediaExtractor : public RefBase {
JMediaExtractor(JNIEnv *env, jobject thiz);
status_t setDataSource(
+ const sp<IMediaHTTPService> &httpService,
const char *path,
const KeyedVector<String8, String8> *headers);
diff --git a/media/jni/android_media_MediaHTTPConnection.cpp b/media/jni/android_media_MediaHTTPConnection.cpp
new file mode 100644
index 0000000..0e7d83e
--- /dev/null
+++ b/media/jni/android_media_MediaHTTPConnection.cpp
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2013, 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 "MediaHTTPConnection-JNI"
+#include <utils/Log.h>
+
+#include <binder/MemoryDealer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <nativehelper/ScopedLocalRef.h>
+
+#include "android_media_MediaHTTPConnection.h"
+#include "android_util_Binder.h"
+
+#include "android_runtime/AndroidRuntime.h"
+#include "jni.h"
+#include "JNIHelp.h"
+
+namespace android {
+
+JMediaHTTPConnection::JMediaHTTPConnection(JNIEnv *env, jobject thiz)
+ : mClass(NULL),
+ mObject(NULL),
+ mByteArrayObj(NULL) {
+ jclass clazz = env->GetObjectClass(thiz);
+ CHECK(clazz != NULL);
+
+ mClass = (jclass)env->NewGlobalRef(clazz);
+ mObject = env->NewWeakGlobalRef(thiz);
+
+ mDealer = new MemoryDealer(kBufferSize, "MediaHTTPConnection");
+ mMemory = mDealer->allocate(kBufferSize);
+
+ ScopedLocalRef<jbyteArray> tmp(
+ env, env->NewByteArray(JMediaHTTPConnection::kBufferSize));
+
+ mByteArrayObj = (jbyteArray)env->NewGlobalRef(tmp.get());
+}
+
+JMediaHTTPConnection::~JMediaHTTPConnection() {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+ env->DeleteGlobalRef(mByteArrayObj);
+ mByteArrayObj = NULL;
+ env->DeleteWeakGlobalRef(mObject);
+ mObject = NULL;
+ env->DeleteGlobalRef(mClass);
+ mClass = NULL;
+}
+
+sp<IMemory> JMediaHTTPConnection::getIMemory() {
+ return mMemory;
+}
+
+jbyteArray JMediaHTTPConnection::getByteArrayObj() {
+ return mByteArrayObj;
+}
+
+} // namespace android
+
+using namespace android;
+
+struct fields_t {
+ jfieldID context;
+
+ jmethodID readAtMethodID;
+};
+
+static fields_t gFields;
+
+static sp<JMediaHTTPConnection> setObject(
+ JNIEnv *env, jobject thiz, const sp<JMediaHTTPConnection> &conn) {
+ sp<JMediaHTTPConnection> old =
+ (JMediaHTTPConnection *)env->GetLongField(thiz, gFields.context);
+
+ if (conn != NULL) {
+ conn->incStrong(thiz);
+ }
+ if (old != NULL) {
+ old->decStrong(thiz);
+ }
+ env->SetLongField(thiz, gFields.context, (jlong)conn.get());
+
+ return old;
+}
+
+static sp<JMediaHTTPConnection> getObject(JNIEnv *env, jobject thiz) {
+ return (JMediaHTTPConnection *)env->GetLongField(thiz, gFields.context);
+}
+
+static void android_media_MediaHTTPConnection_native_init(JNIEnv *env) {
+ ScopedLocalRef<jclass> clazz(
+ env, env->FindClass("android/media/MediaHTTPConnection"));
+ CHECK(clazz.get() != NULL);
+
+ gFields.context = env->GetFieldID(clazz.get(), "mNativeContext", "J");
+ CHECK(gFields.context != NULL);
+
+ gFields.readAtMethodID = env->GetMethodID(clazz.get(), "readAt", "(J[BI)I");
+}
+
+static void android_media_MediaHTTPConnection_native_setup(
+ JNIEnv *env, jobject thiz) {
+ sp<JMediaHTTPConnection> conn = new JMediaHTTPConnection(env, thiz);
+
+ setObject(env, thiz, conn);
+}
+
+static void android_media_MediaHTTPConnection_native_finalize(
+ JNIEnv *env, jobject thiz) {
+ setObject(env, thiz, NULL);
+}
+
+static jobject android_media_MediaHTTPConnection_native_getIMemory(
+ JNIEnv *env, jobject thiz) {
+ sp<JMediaHTTPConnection> conn = getObject(env, thiz);
+
+ return javaObjectForIBinder(env, conn->getIMemory()->asBinder());
+}
+
+static jint android_media_MediaHTTPConnection_native_readAt(
+ JNIEnv *env, jobject thiz, jlong offset, jint size) {
+ sp<JMediaHTTPConnection> conn = getObject(env, thiz);
+
+ if (size > JMediaHTTPConnection::kBufferSize) {
+ size = JMediaHTTPConnection::kBufferSize;
+ }
+
+ jbyteArray byteArrayObj = conn->getByteArrayObj();
+
+ jint n = env->CallIntMethod(
+ thiz, gFields.readAtMethodID, offset, byteArrayObj, size);
+
+ if (n > 0) {
+ env->GetByteArrayRegion(
+ byteArrayObj,
+ 0,
+ n,
+ (jbyte *)conn->getIMemory()->pointer());
+ }
+
+ return n;
+}
+
+static JNINativeMethod gMethods[] = {
+ { "native_getIMemory", "()Landroid/os/IBinder;",
+ (void *)android_media_MediaHTTPConnection_native_getIMemory },
+
+ { "native_readAt", "(JI)I",
+ (void *)android_media_MediaHTTPConnection_native_readAt },
+
+ { "native_init", "()V",
+ (void *)android_media_MediaHTTPConnection_native_init },
+
+ { "native_setup", "()V",
+ (void *)android_media_MediaHTTPConnection_native_setup },
+
+ { "native_finalize", "()V",
+ (void *)android_media_MediaHTTPConnection_native_finalize },
+};
+
+int register_android_media_MediaHTTPConnection(JNIEnv *env) {
+ return AndroidRuntime::registerNativeMethods(env,
+ "android/media/MediaHTTPConnection", gMethods, NELEM(gMethods));
+}
+
diff --git a/media/jni/android_media_MediaHTTPConnection.h b/media/jni/android_media_MediaHTTPConnection.h
new file mode 100644
index 0000000..62ff678
--- /dev/null
+++ b/media/jni/android_media_MediaHTTPConnection.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2013, 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.
+ */
+
+#ifndef _ANDROID_MEDIA_MEDIAHTTPCONNECTION_H_
+#define _ANDROID_MEDIA_MEDIAHTTPCONNECTION_H_
+
+#include "jni.h"
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct IMemory;
+struct MemoryDealer;
+
+struct JMediaHTTPConnection : public RefBase {
+ enum {
+ kBufferSize = 32768,
+ };
+
+ JMediaHTTPConnection(JNIEnv *env, jobject thiz);
+
+ sp<IMemory> getIMemory();
+
+ jbyteArray getByteArrayObj();
+
+protected:
+ virtual ~JMediaHTTPConnection();
+
+private:
+ jclass mClass;
+ jweak mObject;
+ jbyteArray mByteArrayObj;
+
+ sp<MemoryDealer> mDealer;
+ sp<IMemory> mMemory;
+
+ DISALLOW_EVIL_CONSTRUCTORS(JMediaHTTPConnection);
+};
+
+} // namespace android
+
+#endif // _ANDROID_MEDIA_MEDIAHTTPCONNECTION_H_
diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp
index 6176f0f..4e42ae3 100644
--- a/media/jni/android_media_MediaMetadataRetriever.cpp
+++ b/media/jni/android_media_MediaMetadataRetriever.cpp
@@ -21,7 +21,8 @@
#include <assert.h>
#include <utils/Log.h>
#include <utils/threads.h>
-#include <core/SkBitmap.h>
+#include <SkBitmap.h>
+#include <media/IMediaHTTPService.h>
#include <media/mediametadataretriever.h>
#include <private/media/VideoFrame.h>
@@ -29,6 +30,7 @@
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
#include "android_media_Utils.h"
+#include "android_util_Binder.h"
using namespace android;
@@ -80,7 +82,7 @@ static void setRetriever(JNIEnv* env, jobject thiz, MediaMetadataRetriever* retr
static void
android_media_MediaMetadataRetriever_setDataSourceAndHeaders(
- JNIEnv *env, jobject thiz, jstring path,
+ JNIEnv *env, jobject thiz, jobject httpServiceBinderObj, jstring path,
jobjectArray keys, jobjectArray values) {
ALOGV("setDataSource");
@@ -122,10 +124,19 @@ android_media_MediaMetadataRetriever_setDataSourceAndHeaders(
env, keys, values, &headersVector)) {
return;
}
+
+ sp<IMediaHTTPService> httpService;
+ if (httpServiceBinderObj != NULL) {
+ sp<IBinder> binder = ibinderForJavaObject(env, httpServiceBinderObj);
+ httpService = interface_cast<IMediaHTTPService>(binder);
+ }
+
process_media_retriever_call(
env,
retriever->setDataSource(
- pathStr.string(), headersVector.size() > 0 ? &headersVector : NULL),
+ httpService,
+ pathStr.string(),
+ headersVector.size() > 0 ? &headersVector : NULL),
"java/lang/RuntimeException",
"setDataSource failed");
@@ -442,7 +453,7 @@ static void android_media_MediaMetadataRetriever_native_setup(JNIEnv *env, jobje
static JNINativeMethod nativeMethods[] = {
{
"_setDataSource",
- "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
+ "(Landroid/os/IBinder;Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
(void *)android_media_MediaMetadataRetriever_setDataSourceAndHeaders
},
diff --git a/media/jni/android_media_MediaMuxer.cpp b/media/jni/android_media_MediaMuxer.cpp
index 3561b06..3fef446f 100644
--- a/media/jni/android_media_MediaMuxer.cpp
+++ b/media/jni/android_media_MediaMuxer.cpp
@@ -164,7 +164,7 @@ static void android_media_MediaMuxer_setOrientationHint(
}
static void android_media_MediaMuxer_setLocation(
- JNIEnv *env, jclass clazz, jint nativeObject, jint latitude, jint longitude) {
+ JNIEnv *env, jclass clazz, jlong nativeObject, jint latitude, jint longitude) {
MediaMuxer* muxer = reinterpret_cast<MediaMuxer *>(nativeObject);
status_t res = muxer->setLocation(latitude, longitude);
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 9d0d5a6..abebd48 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -20,6 +20,7 @@
#include "utils/Log.h"
#include <media/mediaplayer.h>
+#include <media/IMediaHTTPService.h>
#include <media/MediaPlayerInterface.h>
#include <stdio.h>
#include <assert.h>
@@ -45,6 +46,7 @@
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
+#include "android_util_Binder.h"
// ----------------------------------------------------------------------------
using namespace android;
@@ -183,7 +185,7 @@ static void process_media_player_call(JNIEnv *env, jobject thiz, status_t opStat
static void
android_media_MediaPlayer_setDataSourceAndHeaders(
- JNIEnv *env, jobject thiz, jstring path,
+ JNIEnv *env, jobject thiz, jobject httpServiceBinderObj, jstring path,
jobjectArray keys, jobjectArray values) {
sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
@@ -214,8 +216,15 @@ android_media_MediaPlayer_setDataSourceAndHeaders(
return;
}
+ sp<IMediaHTTPService> httpService;
+ if (httpServiceBinderObj != NULL) {
+ sp<IBinder> binder = ibinderForJavaObject(env, httpServiceBinderObj);
+ httpService = interface_cast<IMediaHTTPService>(binder);
+ }
+
status_t opStatus =
mp->setDataSource(
+ httpService,
pathStr,
headersVector.size() > 0? &headersVector : NULL);
@@ -491,6 +500,20 @@ android_media_MediaPlayer_setAudioStreamType(JNIEnv *env, jobject thiz, jint str
process_media_player_call( env, thiz, mp->setAudioStreamType((audio_stream_type_t) streamtype) , NULL, NULL );
}
+static jint
+android_media_MediaPlayer_getAudioStreamType(JNIEnv *env, jobject thiz)
+{
+ sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return 0;
+ }
+ audio_stream_type_t streamtype;
+ process_media_player_call( env, thiz, mp->getAudioStreamType(&streamtype), NULL, NULL );
+ ALOGV("getAudioStreamType: %d (streamtype)", streamtype);
+ return (jint) streamtype;
+}
+
static void
android_media_MediaPlayer_setLooping(JNIEnv *env, jobject thiz, jboolean looping)
{
@@ -726,7 +749,8 @@ static void android_media_MediaPlayer_attachAuxEffect(JNIEnv *env, jobject thiz
}
static jint
-android_media_MediaPlayer_pullBatteryData(JNIEnv *env, jobject thiz, jobject java_reply)
+android_media_MediaPlayer_pullBatteryData(
+ JNIEnv *env, jobject /* thiz */, jobject java_reply)
{
sp<IBinder> binder = defaultServiceManager()->getService(String16("media.player"));
sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(binder);
@@ -806,58 +830,13 @@ android_media_MediaPlayer_setNextMediaPlayer(JNIEnv *env, jobject thiz, jobject
;
}
-static void
-android_media_MediaPlayer_updateProxyConfig(
- JNIEnv *env, jobject thiz, jobject proxyProps)
-{
- ALOGV("updateProxyConfig");
- sp<MediaPlayer> thisplayer = getMediaPlayer(env, thiz);
- if (thisplayer == NULL) {
- return;
- }
-
- if (proxyProps == NULL) {
- thisplayer->updateProxyConfig(
- NULL /* host */, 0 /* port */, NULL /* exclusionList */);
- } else {
- jstring hostObj = (jstring)env->CallObjectMethod(
- proxyProps, fields.proxyConfigGetHost);
-
- const char *host = env->GetStringUTFChars(hostObj, NULL);
-
- int port = env->CallIntMethod(proxyProps, fields.proxyConfigGetPort);
-
- jstring exclusionListObj = (jstring)env->CallObjectMethod(
- proxyProps, fields.proxyConfigGetExclusionList);
-
- if (host != NULL && exclusionListObj != NULL) {
- const char *exclusionList = env->GetStringUTFChars(exclusionListObj, NULL);
-
- if (exclusionList != NULL) {
- thisplayer->updateProxyConfig(host, port, exclusionList);
-
- env->ReleaseStringUTFChars(exclusionListObj, exclusionList);
- exclusionList = NULL;
- } else {
- thisplayer->updateProxyConfig(host, port, "");
- }
- } else if (host != NULL) {
- thisplayer->updateProxyConfig(host, port, "");
- }
-
- if (host != NULL) {
- env->ReleaseStringUTFChars(hostObj, host);
- host = NULL;
- }
- }
-}
-
// ----------------------------------------------------------------------------
static JNINativeMethod gMethods[] = {
{
- "_setDataSource",
- "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
+ "nativeSetDataSource",
+ "(Landroid/os/IBinder;Ljava/lang/String;[Ljava/lang/String;"
+ "[Ljava/lang/String;)V",
(void *)android_media_MediaPlayer_setDataSourceAndHeaders
},
@@ -876,10 +855,11 @@ static JNINativeMethod gMethods[] = {
{"getDuration", "()I", (void *)android_media_MediaPlayer_getDuration},
{"_release", "()V", (void *)android_media_MediaPlayer_release},
{"_reset", "()V", (void *)android_media_MediaPlayer_reset},
- {"setAudioStreamType", "(I)V", (void *)android_media_MediaPlayer_setAudioStreamType},
+ {"_setAudioStreamType", "(I)V", (void *)android_media_MediaPlayer_setAudioStreamType},
+ {"_getAudioStreamType", "()I", (void *)android_media_MediaPlayer_getAudioStreamType},
{"setLooping", "(Z)V", (void *)android_media_MediaPlayer_setLooping},
{"isLooping", "()Z", (void *)android_media_MediaPlayer_isLooping},
- {"setVolume", "(FF)V", (void *)android_media_MediaPlayer_setVolume},
+ {"_setVolume", "(FF)V", (void *)android_media_MediaPlayer_setVolume},
{"native_invoke", "(Landroid/os/Parcel;Landroid/os/Parcel;)I",(void *)android_media_MediaPlayer_invoke},
{"native_setMetadataFilter", "(Landroid/os/Parcel;)I", (void *)android_media_MediaPlayer_setMetadataFilter},
{"native_getMetadata", "(ZZLandroid/os/Parcel;)Z", (void *)android_media_MediaPlayer_getMetadata},
@@ -888,12 +868,11 @@ static JNINativeMethod gMethods[] = {
{"native_finalize", "()V", (void *)android_media_MediaPlayer_native_finalize},
{"getAudioSessionId", "()I", (void *)android_media_MediaPlayer_get_audio_session_id},
{"setAudioSessionId", "(I)V", (void *)android_media_MediaPlayer_set_audio_session_id},
- {"setAuxEffectSendLevel", "(F)V", (void *)android_media_MediaPlayer_setAuxEffectSendLevel},
+ {"_setAuxEffectSendLevel", "(F)V", (void *)android_media_MediaPlayer_setAuxEffectSendLevel},
{"attachAuxEffect", "(I)V", (void *)android_media_MediaPlayer_attachAuxEffect},
{"native_pullBatteryData", "(Landroid/os/Parcel;)I", (void *)android_media_MediaPlayer_pullBatteryData},
{"native_setRetransmitEndpoint", "(Ljava/lang/String;I)I", (void *)android_media_MediaPlayer_setRetransmitEndpoint},
{"setNextMediaPlayer", "(Landroid/media/MediaPlayer;)V", (void *)android_media_MediaPlayer_setNextMediaPlayer},
- {"updateProxyConfig", "(Landroid/net/ProxyProperties;)V", (void *)android_media_MediaPlayer_updateProxyConfig},
};
static const char* const kClassPathName = "android/media/MediaPlayer";
@@ -911,6 +890,7 @@ extern int register_android_media_Drm(JNIEnv *env);
extern int register_android_media_MediaCodec(JNIEnv *env);
extern int register_android_media_MediaExtractor(JNIEnv *env);
extern int register_android_media_MediaCodecList(JNIEnv *env);
+extern int register_android_media_MediaHTTPConnection(JNIEnv *env);
extern int register_android_media_MediaMetadataRetriever(JNIEnv *env);
extern int register_android_media_MediaMuxer(JNIEnv *env);
extern int register_android_media_MediaRecorder(JNIEnv *env);
@@ -922,7 +902,7 @@ extern int register_android_mtp_MtpDatabase(JNIEnv *env);
extern int register_android_mtp_MtpDevice(JNIEnv *env);
extern int register_android_mtp_MtpServer(JNIEnv *env);
-jint JNI_OnLoad(JavaVM* vm, void* reserved)
+jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
JNIEnv* env = NULL;
jint result = -1;
@@ -1018,6 +998,11 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved)
goto bail;
}
+ if (register_android_media_MediaHTTPConnection(env) < 0) {
+ ALOGE("ERROR: MediaHTTPConnection native registration failed");
+ goto bail;
+ }
+
/* success -- return valid version number */
result = JNI_VERSION_1_4;
diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp
index b74d0fb..1685a44 100644
--- a/media/jni/android_media_MediaRecorder.cpp
+++ b/media/jni/android_media_MediaRecorder.cpp
@@ -346,6 +346,26 @@ android_media_MediaRecorder_native_getMaxAmplitude(JNIEnv *env, jobject thiz)
return (jint) result;
}
+static jobject
+android_media_MediaRecorder_getSurface(JNIEnv *env, jobject thiz)
+{
+ ALOGV("getSurface");
+ sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
+
+ sp<IGraphicBufferProducer> bufferProducer = mr->querySurfaceMediaSourceFromMediaServer();
+ if (bufferProducer == NULL) {
+ jniThrowException(
+ env,
+ "java/lang/IllegalStateException",
+ "failed to get surface");
+ return NULL;
+ }
+
+ // Wrap the IGBP in a Java-language Surface.
+ return android_view_Surface_createFromIGraphicBufferProducer(env,
+ bufferProducer);
+}
+
static void
android_media_MediaRecorder_start(JNIEnv *env, jobject thiz)
{
@@ -472,6 +492,7 @@ static JNINativeMethod gMethods[] = {
{"setMaxDuration", "(I)V", (void *)android_media_MediaRecorder_setMaxDuration},
{"setMaxFileSize", "(J)V", (void *)android_media_MediaRecorder_setMaxFileSize},
{"_prepare", "()V", (void *)android_media_MediaRecorder_prepare},
+ {"getSurface", "()Landroid/view/Surface;", (void *)android_media_MediaRecorder_getSurface},
{"getMaxAmplitude", "()I", (void *)android_media_MediaRecorder_native_getMaxAmplitude},
{"start", "()V", (void *)android_media_MediaRecorder_start},
{"stop", "()V", (void *)android_media_MediaRecorder_stop},
diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp
index f1949c0..d781336 100644
--- a/media/jni/android_mtp_MtpDatabase.cpp
+++ b/media/jni/android_mtp_MtpDatabase.cpp
@@ -69,6 +69,8 @@ static jmethodID method_sessionStarted;
static jmethodID method_sessionEnded;
static jfieldID field_context;
+static jfieldID field_batteryLevel;
+static jfieldID field_batteryScale;
// MtpPropertyList fields
static jfieldID field_mCount;
@@ -528,68 +530,75 @@ MtpResponseCode MyMtpDatabase::setObjectPropertyValue(MtpObjectHandle handle,
MtpResponseCode MyMtpDatabase::getDevicePropertyValue(MtpDeviceProperty property,
MtpDataPacket& packet) {
- int type;
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
- if (!getDevicePropertyInfo(property, type))
- return MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
+ if (property == MTP_DEVICE_PROPERTY_BATTERY_LEVEL) {
+ // special case - implemented here instead of Java
+ packet.putUInt8((uint8_t)env->GetIntField(mDatabase, field_batteryLevel));
+ return MTP_RESPONSE_OK;
+ } else {
+ int type;
+
+ if (!getDevicePropertyInfo(property, type))
+ return MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
+
+ jint result = env->CallIntMethod(mDatabase, method_getDeviceProperty,
+ (jint)property, mLongBuffer, mStringBuffer);
+ if (result != MTP_RESPONSE_OK) {
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+ return result;
+ }
- JNIEnv* env = AndroidRuntime::getJNIEnv();
- jint result = env->CallIntMethod(mDatabase, method_getDeviceProperty,
- (jint)property, mLongBuffer, mStringBuffer);
- if (result != MTP_RESPONSE_OK) {
- checkAndClearExceptionFromCallback(env, __FUNCTION__);
- return result;
- }
+ jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0);
+ jlong longValue = longValues[0];
+ env->ReleaseLongArrayElements(mLongBuffer, longValues, 0);
- jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0);
- jlong longValue = longValues[0];
- env->ReleaseLongArrayElements(mLongBuffer, longValues, 0);
+ switch (type) {
+ case MTP_TYPE_INT8:
+ packet.putInt8(longValue);
+ break;
+ case MTP_TYPE_UINT8:
+ packet.putUInt8(longValue);
+ break;
+ case MTP_TYPE_INT16:
+ packet.putInt16(longValue);
+ break;
+ case MTP_TYPE_UINT16:
+ packet.putUInt16(longValue);
+ break;
+ case MTP_TYPE_INT32:
+ packet.putInt32(longValue);
+ break;
+ case MTP_TYPE_UINT32:
+ packet.putUInt32(longValue);
+ break;
+ case MTP_TYPE_INT64:
+ packet.putInt64(longValue);
+ break;
+ case MTP_TYPE_UINT64:
+ packet.putUInt64(longValue);
+ break;
+ case MTP_TYPE_INT128:
+ packet.putInt128(longValue);
+ break;
+ case MTP_TYPE_UINT128:
+ packet.putInt128(longValue);
+ break;
+ case MTP_TYPE_STR:
+ {
+ jchar* str = env->GetCharArrayElements(mStringBuffer, 0);
+ packet.putString(str);
+ env->ReleaseCharArrayElements(mStringBuffer, str, 0);
+ break;
+ }
+ default:
+ ALOGE("unsupported type in getDevicePropertyValue\n");
+ return MTP_RESPONSE_INVALID_DEVICE_PROP_FORMAT;
+ }
- switch (type) {
- case MTP_TYPE_INT8:
- packet.putInt8(longValue);
- break;
- case MTP_TYPE_UINT8:
- packet.putUInt8(longValue);
- break;
- case MTP_TYPE_INT16:
- packet.putInt16(longValue);
- break;
- case MTP_TYPE_UINT16:
- packet.putUInt16(longValue);
- break;
- case MTP_TYPE_INT32:
- packet.putInt32(longValue);
- break;
- case MTP_TYPE_UINT32:
- packet.putUInt32(longValue);
- break;
- case MTP_TYPE_INT64:
- packet.putInt64(longValue);
- break;
- case MTP_TYPE_UINT64:
- packet.putUInt64(longValue);
- break;
- case MTP_TYPE_INT128:
- packet.putInt128(longValue);
- break;
- case MTP_TYPE_UINT128:
- packet.putInt128(longValue);
- break;
- case MTP_TYPE_STR:
- {
- jchar* str = env->GetCharArrayElements(mStringBuffer, 0);
- packet.putString(str);
- env->ReleaseCharArrayElements(mStringBuffer, str, 0);
- break;
- }
- default:
- ALOGE("unsupported type in getDevicePropertyValue\n");
- return MTP_RESPONSE_INVALID_DEVICE_PROP_FORMAT;
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+ return MTP_RESPONSE_OK;
}
-
- checkAndClearExceptionFromCallback(env, __FUNCTION__);
- return MTP_RESPONSE_OK;
}
MtpResponseCode MyMtpDatabase::setDevicePropertyValue(MtpDeviceProperty property,
@@ -925,6 +934,7 @@ static const PropertyTableEntry kDevicePropertyTable[] = {
{ MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER, MTP_TYPE_STR },
{ MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME, MTP_TYPE_STR },
{ MTP_DEVICE_PROPERTY_IMAGE_SIZE, MTP_TYPE_STR },
+ { MTP_DEVICE_PROPERTY_BATTERY_LEVEL, MTP_TYPE_UINT8 },
};
bool MyMtpDatabase::getObjectPropertyInfo(MtpObjectProperty property, int& type) {
@@ -992,6 +1002,22 @@ MtpResponseCode MyMtpDatabase::setObjectReferences(MtpObjectHandle handle,
MtpProperty* MyMtpDatabase::getObjectPropertyDesc(MtpObjectProperty property,
MtpObjectFormat format) {
+ static const int channelEnum[] = {
+ 1, // mono
+ 2, // stereo
+ 3, // 2.1
+ 4, // 3
+ 5, // 3.1
+ 6, // 4
+ 7, // 4.1
+ 8, // 5
+ 9, // 5.1
+ };
+ static const int bitrateEnum[] = {
+ 1, // fixed rate
+ 2, // variable rate
+ };
+
MtpProperty* result = NULL;
switch (property) {
case MTP_PROPERTY_OBJECT_FORMAT:
@@ -1005,6 +1031,7 @@ MtpProperty* MyMtpDatabase::getObjectPropertyDesc(MtpObjectProperty property,
case MTP_PROPERTY_STORAGE_ID:
case MTP_PROPERTY_PARENT_OBJECT:
case MTP_PROPERTY_DURATION:
+ case MTP_PROPERTY_AUDIO_WAVE_CODEC:
result = new MtpProperty(property, MTP_TYPE_UINT32);
break;
case MTP_PROPERTY_OBJECT_SIZE:
@@ -1033,6 +1060,22 @@ MtpProperty* MyMtpDatabase::getObjectPropertyDesc(MtpObjectProperty property,
// We allow renaming files and folders
result = new MtpProperty(property, MTP_TYPE_STR, true);
break;
+ case MTP_PROPERTY_BITRATE_TYPE:
+ result = new MtpProperty(property, MTP_TYPE_UINT16);
+ result->setFormEnum(bitrateEnum, sizeof(bitrateEnum)/sizeof(bitrateEnum[0]));
+ break;
+ case MTP_PROPERTY_AUDIO_BITRATE:
+ result = new MtpProperty(property, MTP_TYPE_UINT32);
+ result->setFormRange(1, 1536000, 1);
+ break;
+ case MTP_PROPERTY_NUMBER_OF_CHANNELS:
+ result = new MtpProperty(property, MTP_TYPE_UINT16);
+ result->setFormEnum(channelEnum, sizeof(channelEnum)/sizeof(channelEnum[0]));
+ break;
+ case MTP_PROPERTY_SAMPLE_RATE:
+ result = new MtpProperty(property, MTP_TYPE_UINT32);
+ result->setFormRange(8000, 48000, 1);
+ break;
}
return result;
@@ -1048,7 +1091,7 @@ MtpProperty* MyMtpDatabase::getDevicePropertyDesc(MtpDeviceProperty property) {
case MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
writable = true;
// fall through
- case MTP_DEVICE_PROPERTY_IMAGE_SIZE:
+ case MTP_DEVICE_PROPERTY_IMAGE_SIZE: {
result = new MtpProperty(property, MTP_TYPE_STR, writable);
// get current value
@@ -1065,6 +1108,12 @@ MtpProperty* MyMtpDatabase::getDevicePropertyDesc(MtpDeviceProperty property) {
ALOGE("unable to read device property, response: %04X", ret);
}
break;
+ }
+ case MTP_DEVICE_PROPERTY_BATTERY_LEVEL:
+ result = new MtpProperty(property, MTP_TYPE_UINT8);
+ result->setFormRange(0, env->GetIntField(mDatabase, field_batteryScale), 1);
+ result->mCurrentValue.u.u8 = (uint8_t)env->GetIntField(mDatabase, field_batteryLevel);
+ break;
}
checkAndClearExceptionFromCallback(env, __FUNCTION__);
@@ -1236,6 +1285,16 @@ int register_android_mtp_MtpDatabase(JNIEnv *env)
ALOGE("Can't find MtpDatabase.mNativeContext");
return -1;
}
+ field_batteryLevel = env->GetFieldID(clazz, "mBatteryLevel", "I");
+ if (field_batteryLevel == NULL) {
+ ALOGE("Can't find MtpDatabase.mBatteryLevel");
+ return -1;
+ }
+ field_batteryScale = env->GetFieldID(clazz, "mBatteryScale", "I");
+ if (field_batteryScale == NULL) {
+ ALOGE("Can't find MtpDatabase.mBatteryScale");
+ return -1;
+ }
// now set up fields for MtpPropertyList class
clazz = env->FindClass("android/mtp/MtpPropertyList");
diff --git a/media/jni/android_mtp_MtpServer.cpp b/media/jni/android_mtp_MtpServer.cpp
index 9d7f1c2..2f90dfe 100644
--- a/media/jni/android_mtp_MtpServer.cpp
+++ b/media/jni/android_mtp_MtpServer.cpp
@@ -118,6 +118,18 @@ android_mtp_MtpServer_send_object_removed(JNIEnv *env, jobject thiz, jint handle
}
static void
+android_mtp_MtpServer_send_device_property_changed(JNIEnv *env, jobject thiz, jint property)
+{
+ Mutex::Autolock autoLock(sMutex);
+
+ MtpServer* server = getMtpServer(env, thiz);
+ if (server)
+ server->sendDevicePropertyChanged(property);
+ else
+ ALOGE("server is null in send_object_removed");
+}
+
+static void
android_mtp_MtpServer_add_storage(JNIEnv *env, jobject thiz, jobject jstorage)
{
Mutex::Autolock autoLock(sMutex);
@@ -174,6 +186,8 @@ static JNINativeMethod gMethods[] = {
{"native_cleanup", "()V", (void *)android_mtp_MtpServer_cleanup},
{"native_send_object_added", "(I)V", (void *)android_mtp_MtpServer_send_object_added},
{"native_send_object_removed", "(I)V", (void *)android_mtp_MtpServer_send_object_removed},
+ {"native_send_device_property_changed", "(I)V",
+ (void *)android_mtp_MtpServer_send_device_property_changed},
{"native_add_storage", "(Landroid/mtp/MtpStorage;)V",
(void *)android_mtp_MtpServer_add_storage},
{"native_remove_storage", "(I)V", (void *)android_mtp_MtpServer_remove_storage},
diff --git a/media/jni/mediaeditor/Android.mk b/media/jni/mediaeditor/Android.mk
index 5d2378d..312c366 100644
--- a/media/jni/mediaeditor/Android.mk
+++ b/media/jni/mediaeditor/Android.mk
@@ -35,7 +35,6 @@ LOCAL_C_INCLUDES += \
$(TOP)/frameworks/base/media/libstagefright/include \
$(TOP)/frameworks/base/media/libstagefright/rtsp \
$(JNI_H_INCLUDE) \
- $(call include-path-for, corecg graphics) \
$(TOP)/frameworks/native/include/media/editor \
$(TOP)/frameworks/base/core/jni/mediaeditor \
$(TOP)/frameworks/av/libvideoeditor/vss/inc \
diff --git a/media/jni/soundpool/android_media_SoundPool_SoundPoolImpl.cpp b/media/jni/soundpool/android_media_SoundPool_SoundPoolImpl.cpp
index 9cc55ab..bda3b6b 100644
--- a/media/jni/soundpool/android_media_SoundPool_SoundPoolImpl.cpp
+++ b/media/jni/soundpool/android_media_SoundPool_SoundPoolImpl.cpp
@@ -229,7 +229,7 @@ static JNINativeMethod gMethods[] = {
"(I)Z",
(void *)android_media_SoundPool_SoundPoolImpl_unload
},
- { "play",
+ { "_play",
"(IFFIIF)I",
(void *)android_media_SoundPool_SoundPoolImpl_play
},
@@ -253,7 +253,7 @@ static JNINativeMethod gMethods[] = {
"(I)V",
(void *)android_media_SoundPool_SoundPoolImpl_stop
},
- { "setVolume",
+ { "_setVolume",
"(IFF)V",
(void *)android_media_SoundPool_SoundPoolImpl_setVolume
},
diff --git a/media/lib/remotedisplay/java/com/android/media/remotedisplay/RemoteDisplay.java b/media/lib/remotedisplay/java/com/android/media/remotedisplay/RemoteDisplay.java
index 6cfc0e8..dc9dd79 100644
--- a/media/lib/remotedisplay/java/com/android/media/remotedisplay/RemoteDisplay.java
+++ b/media/lib/remotedisplay/java/com/android/media/remotedisplay/RemoteDisplay.java
@@ -16,7 +16,6 @@
package com.android.media.remotedisplay;
-import android.media.MediaRouter;
import android.media.RemoteDisplayState.RemoteDisplayInfo;
import android.text.TextUtils;
diff --git a/media/mca/effect/java/android/media/effect/EffectContext.java b/media/mca/effect/java/android/media/effect/EffectContext.java
index ef03229..a11b9c4 100644
--- a/media/mca/effect/java/android/media/effect/EffectContext.java
+++ b/media/mca/effect/java/android/media/effect/EffectContext.java
@@ -19,10 +19,7 @@ package android.media.effect;
import android.filterfw.core.CachedFrameManager;
import android.filterfw.core.FilterContext;
-import android.filterfw.core.FilterFactory;
import android.filterfw.core.GLEnvironment;
-import android.filterfw.core.GLFrame;
-import android.filterfw.core.FrameManager;
import android.opengl.GLES20;
/**
diff --git a/media/mca/effect/java/android/media/effect/EffectFactory.java b/media/mca/effect/java/android/media/effect/EffectFactory.java
index 4330279..f6fcba7 100644
--- a/media/mca/effect/java/android/media/effect/EffectFactory.java
+++ b/media/mca/effect/java/android/media/effect/EffectFactory.java
@@ -18,7 +18,6 @@
package android.media.effect;
import java.lang.reflect.Constructor;
-import java.util.HashMap;
/**
* <p>The EffectFactory class defines the list of available Effects, and provides functionality to
diff --git a/media/mca/effect/java/android/media/effect/FilterEffect.java b/media/mca/effect/java/android/media/effect/FilterEffect.java
index d7c319e..34b3549 100644
--- a/media/mca/effect/java/android/media/effect/FilterEffect.java
+++ b/media/mca/effect/java/android/media/effect/FilterEffect.java
@@ -17,10 +17,7 @@
package android.media.effect;
-import android.filterfw.core.CachedFrameManager;
import android.filterfw.core.FilterContext;
-import android.filterfw.core.FilterFactory;
-import android.filterfw.core.GLEnvironment;
import android.filterfw.core.GLFrame;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
diff --git a/media/mca/effect/java/android/media/effect/FilterGraphEffect.java b/media/mca/effect/java/android/media/effect/FilterGraphEffect.java
index b18bea8..80c695b 100644
--- a/media/mca/effect/java/android/media/effect/FilterGraphEffect.java
+++ b/media/mca/effect/java/android/media/effect/FilterGraphEffect.java
@@ -19,17 +19,13 @@ package android.media.effect;
import android.filterfw.core.Filter;
import android.filterfw.core.FilterGraph;
import android.filterfw.core.GraphRunner;
-import android.filterfw.core.SimpleScheduler;
import android.filterfw.core.SyncRunner;
-import android.media.effect.Effect;
import android.media.effect.FilterEffect;
import android.media.effect.EffectContext;
import android.filterfw.io.GraphIOException;
import android.filterfw.io.GraphReader;
import android.filterfw.io.TextGraphReader;
-import android.util.Log;
-
/**
* Effect subclass for effects based on a single Filter. Subclasses need only invoke the
* constructor with the correct arguments to obtain an Effect implementation.
diff --git a/media/mca/effect/java/android/media/effect/SingleFilterEffect.java b/media/mca/effect/java/android/media/effect/SingleFilterEffect.java
index 6f85861..47900df 100644
--- a/media/mca/effect/java/android/media/effect/SingleFilterEffect.java
+++ b/media/mca/effect/java/android/media/effect/SingleFilterEffect.java
@@ -21,11 +21,8 @@ import android.filterfw.core.Filter;
import android.filterfw.core.FilterFactory;
import android.filterfw.core.FilterFunction;
import android.filterfw.core.Frame;
-import android.media.effect.Effect;
import android.media.effect.EffectContext;
-import android.util.Log;
-
/**
* Effect subclass for effects based on a single Filter. Subclasses need only invoke the
* constructor with the correct arguments to obtain an Effect implementation.
diff --git a/media/mca/effect/java/android/media/effect/SizeChangeEffect.java b/media/mca/effect/java/android/media/effect/SizeChangeEffect.java
index 4d27bae..1bf7d40 100644
--- a/media/mca/effect/java/android/media/effect/SizeChangeEffect.java
+++ b/media/mca/effect/java/android/media/effect/SizeChangeEffect.java
@@ -16,15 +16,9 @@
package android.media.effect;
-import android.filterfw.core.Filter;
-import android.filterfw.core.FilterFactory;
-import android.filterfw.core.FilterFunction;
import android.filterfw.core.Frame;
-import android.media.effect.Effect;
import android.media.effect.EffectContext;
-import android.util.Log;
-
/**
* Effect subclass for effects based on a single Filter with output size differnet
* from input. Subclasses need only invoke the constructor with the correct arguments
diff --git a/media/mca/effect/java/android/media/effect/effects/CropEffect.java b/media/mca/effect/java/android/media/effect/effects/CropEffect.java
index 3e8d78a..7e1c495 100644
--- a/media/mca/effect/java/android/media/effect/effects/CropEffect.java
+++ b/media/mca/effect/java/android/media/effect/effects/CropEffect.java
@@ -19,7 +19,6 @@ package android.media.effect.effects;
import android.media.effect.EffectContext;
import android.media.effect.SizeChangeEffect;
-import android.media.effect.SingleFilterEffect;
import android.filterpacks.imageproc.CropRectFilter;
/**
diff --git a/media/mca/filterfw/java/android/filterfw/FilterFunctionEnvironment.java b/media/mca/filterfw/java/android/filterfw/FilterFunctionEnvironment.java
index 3f36d98..feaf6e8 100644
--- a/media/mca/filterfw/java/android/filterfw/FilterFunctionEnvironment.java
+++ b/media/mca/filterfw/java/android/filterfw/FilterFunctionEnvironment.java
@@ -20,7 +20,6 @@ package android.filterfw;
import android.filterfw.core.Filter;
import android.filterfw.core.FilterFactory;
import android.filterfw.core.FilterFunction;
-import android.filterfw.core.Frame;
import android.filterfw.core.FrameManager;
/**
diff --git a/media/mca/filterfw/java/android/filterfw/core/AsyncRunner.java b/media/mca/filterfw/java/android/filterfw/core/AsyncRunner.java
index 70cbad4..819774a 100644
--- a/media/mca/filterfw/java/android/filterfw/core/AsyncRunner.java
+++ b/media/mca/filterfw/java/android/filterfw/core/AsyncRunner.java
@@ -18,17 +18,9 @@
package android.filterfw.core;
import android.os.AsyncTask;
-import android.os.Handler;
import android.util.Log;
-import java.lang.InterruptedException;
-import java.lang.Runnable;
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.TimeUnit;
-
/**
* @hide
*/
diff --git a/media/mca/filterfw/java/android/filterfw/core/Filter.java b/media/mca/filterfw/java/android/filterfw/core/Filter.java
index 73b009d..062b6ba 100644
--- a/media/mca/filterfw/java/android/filterfw/core/Filter.java
+++ b/media/mca/filterfw/java/android/filterfw/core/Filter.java
@@ -33,7 +33,6 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;
-import java.util.LinkedList;
import java.util.Set;
/**
diff --git a/media/mca/filterfw/java/android/filterfw/core/Frame.java b/media/mca/filterfw/java/android/filterfw/core/Frame.java
index ef8c542..7dd0783 100644
--- a/media/mca/filterfw/java/android/filterfw/core/Frame.java
+++ b/media/mca/filterfw/java/android/filterfw/core/Frame.java
@@ -20,7 +20,6 @@ package android.filterfw.core;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.FrameManager;
import android.graphics.Bitmap;
-import android.util.Log;
import java.nio.ByteBuffer;
diff --git a/media/mca/filterfw/java/android/filterfw/core/NativeFrame.java b/media/mca/filterfw/java/android/filterfw/core/NativeFrame.java
index bfd09ba..7d1553f 100644
--- a/media/mca/filterfw/java/android/filterfw/core/NativeFrame.java
+++ b/media/mca/filterfw/java/android/filterfw/core/NativeFrame.java
@@ -24,8 +24,6 @@ import android.filterfw.core.GLFrame;
import android.filterfw.core.NativeBuffer;
import android.graphics.Bitmap;
-import android.util.Log;
-
import java.nio.ByteBuffer;
/**
diff --git a/media/mca/filterfw/java/android/filterfw/core/OneShotScheduler.java b/media/mca/filterfw/java/android/filterfw/core/OneShotScheduler.java
index dbc8d16..83c475f 100644
--- a/media/mca/filterfw/java/android/filterfw/core/OneShotScheduler.java
+++ b/media/mca/filterfw/java/android/filterfw/core/OneShotScheduler.java
@@ -18,7 +18,6 @@
package android.filterfw.core;
import android.filterfw.core.Filter;
-import android.filterfw.core.Scheduler;
import android.filterfw.core.RoundRobinScheduler;
import android.util.Log;
diff --git a/media/mca/filterfw/java/android/filterfw/core/SerializedFrame.java b/media/mca/filterfw/java/android/filterfw/core/SerializedFrame.java
index f493fd2..35ba04f 100644
--- a/media/mca/filterfw/java/android/filterfw/core/SerializedFrame.java
+++ b/media/mca/filterfw/java/android/filterfw/core/SerializedFrame.java
@@ -20,7 +20,6 @@ package android.filterfw.core;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.FrameManager;
-import android.filterfw.core.NativeBuffer;
import android.filterfw.format.ObjectFormat;
import android.graphics.Bitmap;
@@ -28,10 +27,7 @@ import java.io.InputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
-import java.io.OptionalDataException;
import java.io.OutputStream;
-import java.io.StreamCorruptedException;
-import java.lang.reflect.Constructor;
import java.nio.ByteBuffer;
/**
diff --git a/media/mca/filterfw/java/android/filterfw/core/SimpleFrame.java b/media/mca/filterfw/java/android/filterfw/core/SimpleFrame.java
index 534a30d..7ddd1d4 100644
--- a/media/mca/filterfw/java/android/filterfw/core/SimpleFrame.java
+++ b/media/mca/filterfw/java/android/filterfw/core/SimpleFrame.java
@@ -20,11 +20,9 @@ package android.filterfw.core;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.FrameManager;
-import android.filterfw.core.NativeBuffer;
import android.filterfw.format.ObjectFormat;
import android.graphics.Bitmap;
-import java.lang.reflect.Constructor;
import java.nio.ByteBuffer;
/**
diff --git a/media/mca/filterfw/native/core/gl_env.cpp b/media/mca/filterfw/native/core/gl_env.cpp
index 84dad8c..f092af8 100644
--- a/media/mca/filterfw/native/core/gl_env.cpp
+++ b/media/mca/filterfw/native/core/gl_env.cpp
@@ -162,9 +162,11 @@ bool GLEnv::InitWithNewContext() {
}
// Create dummy surface using a GLConsumer
- sp<BufferQueue> bq = new BufferQueue();
- surfaceTexture_ = new GLConsumer(bq, 0);
- window_ = new Surface(static_cast<sp<IGraphicBufferProducer> >(bq));
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&producer, &consumer);
+ surfaceTexture_ = new GLConsumer(consumer, 0);
+ window_ = new Surface(producer);
surfaces_[0] = SurfaceWindowPair(eglCreateWindowSurface(display(), config, window_.get(), NULL), NULL);
if (CheckEGLError("eglCreateWindowSurface")) return false;
diff --git a/media/mca/filterpacks/java/android/filterpacks/base/CallbackFilter.java b/media/mca/filterpacks/java/android/filterpacks/base/CallbackFilter.java
index 4185343..4a47fa4 100644
--- a/media/mca/filterpacks/java/android/filterpacks/base/CallbackFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/base/CallbackFilter.java
@@ -20,13 +20,8 @@ package android.filterpacks.base;
import android.filterfw.core.Filter;
import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
-import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
import android.filterfw.core.GenerateFinalPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
-import android.filterfw.core.Program;
import android.os.Handler;
import android.os.Looper;
diff --git a/media/mca/filterpacks/java/android/filterpacks/base/FrameBranch.java b/media/mca/filterpacks/java/android/filterpacks/base/FrameBranch.java
index 6b8cbc7..f909c3f 100644
--- a/media/mca/filterpacks/java/android/filterpacks/base/FrameBranch.java
+++ b/media/mca/filterpacks/java/android/filterpacks/base/FrameBranch.java
@@ -22,7 +22,6 @@ import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFinalPort;
-import android.filterfw.core.KeyValueMap;
/**
* @hide
diff --git a/media/mca/filterpacks/java/android/filterpacks/base/FrameFetch.java b/media/mca/filterpacks/java/android/filterpacks/base/FrameFetch.java
index 518b837..87d8f0b 100644
--- a/media/mca/filterpacks/java/android/filterpacks/base/FrameFetch.java
+++ b/media/mca/filterpacks/java/android/filterpacks/base/FrameFetch.java
@@ -24,8 +24,6 @@ import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
import android.filterfw.core.GenerateFinalPort;
-import android.util.Log;
-
/**
* @hide
*/
diff --git a/media/mca/filterpacks/java/android/filterpacks/base/FrameStore.java b/media/mca/filterpacks/java/android/filterpacks/base/FrameStore.java
index 3aadaac..05ac50d 100644
--- a/media/mca/filterpacks/java/android/filterpacks/base/FrameStore.java
+++ b/media/mca/filterpacks/java/android/filterpacks/base/FrameStore.java
@@ -20,7 +20,6 @@ package android.filterpacks.base;
import android.filterfw.core.Filter;
import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
-import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
/**
diff --git a/media/mca/filterpacks/java/android/filterpacks/base/GLTextureSource.java b/media/mca/filterpacks/java/android/filterpacks/base/GLTextureSource.java
index 1776820..af61d9a 100644
--- a/media/mca/filterpacks/java/android/filterpacks/base/GLTextureSource.java
+++ b/media/mca/filterpacks/java/android/filterpacks/base/GLTextureSource.java
@@ -23,11 +23,8 @@ import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
import android.filterfw.core.GLFrame;
-import android.filterfw.core.MutableFrameFormat;
import android.filterfw.format.ImageFormat;
-import java.util.Set;
-
/**
* @hide
*/
diff --git a/media/mca/filterpacks/java/android/filterpacks/base/GLTextureTarget.java b/media/mca/filterpacks/java/android/filterpacks/base/GLTextureTarget.java
index b2285cd..91bb417 100644
--- a/media/mca/filterpacks/java/android/filterpacks/base/GLTextureTarget.java
+++ b/media/mca/filterpacks/java/android/filterpacks/base/GLTextureTarget.java
@@ -23,11 +23,8 @@ import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
import android.filterfw.core.GLFrame;
-import android.filterfw.core.MutableFrameFormat;
import android.filterfw.format.ImageFormat;
-import java.util.Set;
-
/**
* @hide
*/
diff --git a/media/mca/filterpacks/java/android/filterpacks/base/InputStreamSource.java b/media/mca/filterpacks/java/android/filterpacks/base/InputStreamSource.java
index 6c22ee7..0ef9055 100644
--- a/media/mca/filterpacks/java/android/filterpacks/base/InputStreamSource.java
+++ b/media/mca/filterpacks/java/android/filterpacks/base/InputStreamSource.java
@@ -23,7 +23,6 @@ import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
import android.filterfw.core.GenerateFinalPort;
-import android.filterfw.core.KeyValueMap;
import android.filterfw.core.MutableFrameFormat;
import android.filterfw.format.PrimitiveFormat;
diff --git a/media/mca/filterpacks/java/android/filterpacks/base/NullFilter.java b/media/mca/filterpacks/java/android/filterpacks/base/NullFilter.java
index f3e08e4..73434d4 100644
--- a/media/mca/filterpacks/java/android/filterpacks/base/NullFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/base/NullFilter.java
@@ -19,8 +19,6 @@ package android.filterpacks.base;
import android.filterfw.core.Filter;
import android.filterfw.core.FilterContext;
-import android.filterfw.core.Frame;
-import android.filterfw.core.FrameFormat;
/**
* @hide
diff --git a/media/mca/filterpacks/java/android/filterpacks/base/ObjectSource.java b/media/mca/filterpacks/java/android/filterpacks/base/ObjectSource.java
index d511e44..78e2b50 100644
--- a/media/mca/filterpacks/java/android/filterpacks/base/ObjectSource.java
+++ b/media/mca/filterpacks/java/android/filterpacks/base/ObjectSource.java
@@ -17,15 +17,12 @@
package android.filterpacks.base;
-import java.util.Set;
-
import android.filterfw.core.Filter;
import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
import android.filterfw.core.GenerateFinalPort;
-import android.filterfw.core.MutableFrameFormat;
import android.filterfw.format.ObjectFormat;
/**
diff --git a/media/mca/filterpacks/java/android/filterpacks/base/OutputStreamTarget.java b/media/mca/filterpacks/java/android/filterpacks/base/OutputStreamTarget.java
index 3d3d0f1..fd65a9d 100644
--- a/media/mca/filterpacks/java/android/filterpacks/base/OutputStreamTarget.java
+++ b/media/mca/filterpacks/java/android/filterpacks/base/OutputStreamTarget.java
@@ -20,7 +20,6 @@ package android.filterpacks.base;
import android.filterfw.core.Filter;
import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
-import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
import java.io.OutputStream;
diff --git a/media/mca/filterpacks/java/android/filterpacks/base/RetargetFilter.java b/media/mca/filterpacks/java/android/filterpacks/base/RetargetFilter.java
index 254167a..0e988820 100644
--- a/media/mca/filterpacks/java/android/filterpacks/base/RetargetFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/base/RetargetFilter.java
@@ -21,9 +21,7 @@ import android.filterfw.core.Filter;
import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
-import android.filterfw.core.GenerateFieldPort;
import android.filterfw.core.GenerateFinalPort;
-import android.filterfw.core.KeyValueMap;
import android.filterfw.core.MutableFrameFormat;
/**
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/AlphaBlendFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/AlphaBlendFilter.java
index 473369c..c3cc282 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/AlphaBlendFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/AlphaBlendFilter.java
@@ -17,18 +17,9 @@
package android.filterpacks.imageproc;
-import android.filterfw.core.Filter;
import android.filterfw.core.FilterContext;
-import android.filterfw.core.Frame;
-import android.filterfw.core.FrameFormat;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
-import android.filterfw.format.ImageFormat;
-
-import java.util.Set;
/**
* @hide
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/AutoFixFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/AutoFixFilter.java
index c71c1c9..ac83db2 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/AutoFixFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/AutoFixFilter.java
@@ -21,13 +21,10 @@ import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.NativeProgram;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
-import android.util.Log;
-
public class AutoFixFilter extends Filter {
@GenerateFieldPort(name = "tile_size", hasDefault = true)
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapOverlayFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapOverlayFilter.java
index e4bb6cf..92b177c 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapOverlayFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapOverlayFilter.java
@@ -21,17 +21,11 @@ import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
-import android.filterpacks.imageproc.ImageCombineFilter;
import android.graphics.Bitmap;
-import android.util.Log;
-
/**
* @hide
*/
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapSource.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapSource.java
index 978fc94..89e8723 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapSource.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapSource.java
@@ -17,17 +17,11 @@
package android.filterpacks.imageproc;
-import android.content.Context;
import android.filterfw.core.Filter;
import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
-import android.filterfw.core.FrameManager;
-import android.filterfw.core.GenerateFinalPort;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.MutableFrameFormat;
-import android.filterfw.core.NativeFrame;
import android.filterfw.format.ImageFormat;
import android.graphics.Bitmap;
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/BlackWhiteFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/BlackWhiteFilter.java
index 20b02d2..38221b4 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/BlackWhiteFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/BlackWhiteFilter.java
@@ -21,9 +21,6 @@ import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/BlendFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/BlendFilter.java
index 29bc8a3..aff5e9e 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/BlendFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/BlendFilter.java
@@ -16,18 +16,9 @@
package android.filterpacks.imageproc;
-import android.filterfw.core.Filter;
import android.filterfw.core.FilterContext;
-import android.filterfw.core.Frame;
-import android.filterfw.core.FrameFormat;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
-import android.filterfw.format.ImageFormat;
-
-import java.util.Set;
/**
* The filter linearly blends "left" and "right" frames. The blending weight is
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/BrightnessFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/BrightnessFilter.java
index 046e69d..bc62e19 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/BrightnessFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/BrightnessFilter.java
@@ -17,13 +17,8 @@
package android.filterpacks.imageproc;
-import android.filterfw.core.Filter;
import android.filterfw.core.FilterContext;
-import android.filterfw.core.Frame;
-import android.filterfw.core.FrameFormat;
-import android.filterfw.core.KeyValueMap;
import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ColorTemperatureFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ColorTemperatureFilter.java
index 7488980..1d408be 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/ColorTemperatureFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ColorTemperatureFilter.java
@@ -21,13 +21,9 @@ import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
-import android.util.Log;
public class ColorTemperatureFilter extends Filter {
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ContrastFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ContrastFilter.java
index 70e987f..7043c72 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/ContrastFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ContrastFilter.java
@@ -17,18 +17,11 @@
package android.filterpacks.imageproc;
-import android.filterfw.core.Filter;
import android.filterfw.core.FilterContext;
-import android.filterfw.core.Frame;
-import android.filterfw.core.FrameFormat;
-import android.filterfw.core.KeyValueMap;
import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
-import java.util.Set;
-
/**
* @hide
*/
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/CropFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/CropFilter.java
index 5222d9c..0ef323c 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/CropFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/CropFilter.java
@@ -22,19 +22,13 @@ import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.KeyValueMap;
import android.filterfw.core.MutableFrameFormat;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
-import android.filterfw.geometry.Point;
import android.filterfw.geometry.Quad;
import android.filterfw.format.ImageFormat;
import android.filterfw.format.ObjectFormat;
-import android.util.Log;
-
/**
* @hide
*/
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/CropRectFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/CropRectFilter.java
index d423d06..010ee21 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/CropRectFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/CropRectFilter.java
@@ -21,14 +21,9 @@ import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.MutableFrameFormat;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
-import android.util.Log;
/**
* @hide
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/CrossProcessFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/CrossProcessFilter.java
index e0514f8..d565e65 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/CrossProcessFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/CrossProcessFilter.java
@@ -21,15 +21,10 @@ import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
-import android.util.Log;
-
public class CrossProcessFilter extends Filter {
@GenerateFieldPort(name = "tile_size", hasDefault = true)
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/DocumentaryFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/DocumentaryFilter.java
index f93a82c..72745c0 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/DocumentaryFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/DocumentaryFilter.java
@@ -21,9 +21,6 @@ import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/DrawOverlayFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/DrawOverlayFilter.java
index 3f1711e..d10a6ef 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/DrawOverlayFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/DrawOverlayFilter.java
@@ -21,17 +21,11 @@ import android.filterfw.core.Filter;
import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
-import android.filterfw.core.GLFrame;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
-import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.geometry.Quad;
import android.filterfw.format.ImageFormat;
import android.filterfw.format.ObjectFormat;
-import android.opengl.GLES20;
-
/**
* @hide
*/
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/DrawRectFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/DrawRectFilter.java
index 83c9348..b288e6e 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/DrawRectFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/DrawRectFilter.java
@@ -23,9 +23,6 @@ import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
import android.filterfw.core.GLFrame;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
-import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.geometry.Quad;
import android.filterfw.format.ImageFormat;
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/DuotoneFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/DuotoneFilter.java
index d8c88ee..ef82ee9 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/DuotoneFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/DuotoneFilter.java
@@ -21,9 +21,6 @@ import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/FillLightFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/FillLightFilter.java
index fc917a1..c7fb55d 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/FillLightFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/FillLightFilter.java
@@ -21,10 +21,6 @@ import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.GenerateFinalPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/FisheyeFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/FisheyeFilter.java
index 5d7929f..2ff6588 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/FisheyeFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/FisheyeFilter.java
@@ -22,17 +22,11 @@ import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
-import android.util.Log;
-
import java.lang.Math;
-import java.util.Set;
/**
* @hide
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/FixedRotationFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/FixedRotationFilter.java
index 3d319ea..340f308 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/FixedRotationFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/FixedRotationFilter.java
@@ -22,7 +22,6 @@ import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
import android.filterfw.core.MutableFrameFormat;
-import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
import android.filterfw.geometry.Point;
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/FlipFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/FlipFilter.java
index f8b857b..68c760f 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/FlipFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/FlipFilter.java
@@ -21,10 +21,6 @@ import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.MutableFrameFormat;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/GrainFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/GrainFilter.java
index 577243a..528eaa2 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/GrainFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/GrainFilter.java
@@ -21,14 +21,9 @@ import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
-import android.filterfw.geometry.Quad;
-import android.filterfw.geometry.Point;
import java.util.Date;
import java.util.Random;
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageCombineFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageCombineFilter.java
index 858489b..c9a6956 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageCombineFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageCombineFilter.java
@@ -21,16 +21,10 @@ import android.filterfw.core.Filter;
import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
-import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
import java.lang.reflect.Field;
-import java.util.HashSet;
-import java.util.Set;
/**
* @hide
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageEncoder.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageEncoder.java
index a5405cb..e8bf482 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageEncoder.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageEncoder.java
@@ -17,21 +17,16 @@
package android.filterpacks.imageproc;
-import android.content.Context;
import android.filterfw.core.Filter;
import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.KeyValueMap;
import android.filterfw.format.ImageFormat;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
-import android.util.Log;
-
import java.io.OutputStream;
-import java.io.IOException;
/**
* @hide
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageSlicer.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageSlicer.java
index b996eb8..8bf80b2 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageSlicer.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageSlicer.java
@@ -20,9 +20,7 @@ import android.filterfw.core.Filter;
import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
-import android.filterfw.core.FrameManager;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.KeyValueMap;
import android.filterfw.core.MutableFrameFormat;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageStitcher.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageStitcher.java
index 20aba91..5e3d15b 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageStitcher.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageStitcher.java
@@ -20,16 +20,12 @@ import android.filterfw.core.Filter;
import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
-import android.filterfw.core.FrameManager;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.KeyValueMap;
import android.filterfw.core.MutableFrameFormat;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
-import android.util.Log;
-
public class ImageStitcher extends Filter {
@GenerateFieldPort(name = "xSlices")
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/Invert.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/Invert.java
index 400fd5d..881e30f 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/Invert.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/Invert.java
@@ -17,12 +17,8 @@
package android.filterpacks.imageproc;
-import android.filterfw.core.Filter;
import android.filterfw.core.FilterContext;
-import android.filterfw.core.Frame;
-import android.filterfw.core.FrameFormat;
import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/LomoishFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/LomoishFilter.java
index 726ffff..4e53f92 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/LomoishFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/LomoishFilter.java
@@ -21,9 +21,6 @@ import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/NegativeFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/NegativeFilter.java
index 440d6a6..f66fc23 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/NegativeFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/NegativeFilter.java
@@ -21,9 +21,6 @@ import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/PosterizeFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/PosterizeFilter.java
index bc2e553..864d7e2 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/PosterizeFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/PosterizeFilter.java
@@ -21,9 +21,6 @@ import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/RedEyeFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/RedEyeFilter.java
index 8618804..48b2fdf 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/RedEyeFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/RedEyeFilter.java
@@ -21,9 +21,6 @@ import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
@@ -31,8 +28,6 @@ import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
-import android.graphics.PointF;
-import android.util.Log;
/**
* @hide
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ResizeFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ResizeFilter.java
index 411e061..c79c11b 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/ResizeFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ResizeFilter.java
@@ -23,10 +23,7 @@ import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
import android.filterfw.core.GLFrame;
-import android.filterfw.core.KeyValueMap;
import android.filterfw.core.MutableFrameFormat;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/RotateFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/RotateFilter.java
index 3da7939..43d8d6c 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/RotateFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/RotateFilter.java
@@ -22,16 +22,11 @@ import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.MutableFrameFormat;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
import android.filterfw.geometry.Quad;
import android.filterfw.geometry.Point;
-import android.util.Log;
/**
* @hide
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/SaturateFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/SaturateFilter.java
index b83af39..757fac1 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/SaturateFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/SaturateFilter.java
@@ -21,9 +21,6 @@ import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/SepiaFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/SepiaFilter.java
index 7a83fdf..a9f4e2c 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/SepiaFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/SepiaFilter.java
@@ -21,9 +21,6 @@ import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/SharpenFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/SharpenFilter.java
index 256b769..a290996 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/SharpenFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/SharpenFilter.java
@@ -21,15 +21,10 @@ import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
-import java.util.Set;
-
public class SharpenFilter extends Filter {
@GenerateFieldPort(name = "scale", hasDefault = true)
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/SimpleImageFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/SimpleImageFilter.java
index f4fc271..afe92de 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/SimpleImageFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/SimpleImageFilter.java
@@ -21,16 +21,10 @@ import android.filterfw.core.Filter;
import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
-import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
import java.lang.reflect.Field;
-import java.util.HashSet;
-import java.util.Set;
/**
* @hide
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/StraightenFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/StraightenFilter.java
index c9f097d..9db296b 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/StraightenFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/StraightenFilter.java
@@ -22,16 +22,11 @@ import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.MutableFrameFormat;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
import android.filterfw.geometry.Quad;
import android.filterfw.geometry.Point;
-import android.util.Log;
/**
* @hide
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/TintFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/TintFilter.java
index 0da54a5..2b140ba 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/TintFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/TintFilter.java
@@ -21,9 +21,6 @@ import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ToGrayFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToGrayFilter.java
index 00e7bf4..760ce3a 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/ToGrayFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToGrayFilter.java
@@ -17,23 +17,14 @@
package android.filterpacks.imageproc;
-import android.filterfw.core.Filter;
import android.filterfw.core.FilterContext;
-import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.KeyValueMap;
import android.filterfw.core.MutableFrameFormat;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
-import android.util.Log;
-
-import java.lang.reflect.Field;
-
/**
* @hide
*/
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ToPackedGrayFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToPackedGrayFilter.java
index bc4a65e..3c121d0 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/ToPackedGrayFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToPackedGrayFilter.java
@@ -27,8 +27,6 @@ import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
-import android.util.Log;
-
import java.lang.Math;
/**
* @hide
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBAFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBAFilter.java
index ab4814f..f0084fa 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBAFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBAFilter.java
@@ -21,16 +21,11 @@ import android.filterfw.core.Filter;
import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
-import android.filterfw.core.KeyValueMap;
import android.filterfw.core.MutableFrameFormat;
import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
-import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
-import android.util.Log;
-
/**
* @hide
*/
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBFilter.java
index 9258502..bbb0fc3 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBFilter.java
@@ -21,16 +21,11 @@ import android.filterfw.core.Filter;
import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
-import android.filterfw.core.KeyValueMap;
import android.filterfw.core.MutableFrameFormat;
import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
-import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
-import android.util.Log;
-
/**
* @hide
*/
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/VignetteFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/VignetteFilter.java
index 715fec6..249cc6f 100644
--- a/media/mca/filterpacks/java/android/filterpacks/imageproc/VignetteFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/VignetteFilter.java
@@ -21,9 +21,6 @@ import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
diff --git a/media/mca/filterpacks/java/android/filterpacks/text/StringSource.java b/media/mca/filterpacks/java/android/filterpacks/text/StringSource.java
index cc33b89..2fe519f 100644
--- a/media/mca/filterpacks/java/android/filterpacks/text/StringSource.java
+++ b/media/mca/filterpacks/java/android/filterpacks/text/StringSource.java
@@ -22,8 +22,6 @@ import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.MutableFrameFormat;
import android.filterfw.format.ObjectFormat;
/**
diff --git a/media/mca/filterpacks/java/android/filterpacks/ui/SurfaceRenderFilter.java b/media/mca/filterpacks/java/android/filterpacks/ui/SurfaceRenderFilter.java
index a5c1ccb..ba88736 100644
--- a/media/mca/filterpacks/java/android/filterpacks/ui/SurfaceRenderFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/ui/SurfaceRenderFilter.java
@@ -26,19 +26,11 @@ import android.filterfw.core.GenerateFieldPort;
import android.filterfw.core.GenerateFinalPort;
import android.filterfw.core.GLEnvironment;
import android.filterfw.core.GLFrame;
-import android.filterfw.core.KeyValueMap;
import android.filterfw.core.MutableFrameFormat;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
-import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
-import android.view.Surface;
import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-
-import android.graphics.Rect;
import android.util.Log;
diff --git a/media/mca/filterpacks/java/android/filterpacks/ui/SurfaceTargetFilter.java b/media/mca/filterpacks/java/android/filterpacks/ui/SurfaceTargetFilter.java
index 308d168..05cb81b 100644
--- a/media/mca/filterpacks/java/android/filterpacks/ui/SurfaceTargetFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/ui/SurfaceTargetFilter.java
@@ -25,19 +25,11 @@ import android.filterfw.core.GenerateFieldPort;
import android.filterfw.core.GenerateFinalPort;
import android.filterfw.core.GLEnvironment;
import android.filterfw.core.GLFrame;
-import android.filterfw.core.KeyValueMap;
import android.filterfw.core.MutableFrameFormat;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
-import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
import android.view.Surface;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-
-import android.graphics.Rect;
import android.util.Log;
diff --git a/media/mca/filterpacks/java/android/filterpacks/videoproc/BackDropperFilter.java b/media/mca/filterpacks/java/android/filterpacks/videoproc/BackDropperFilter.java
index 78f7f3e..a31ac2c 100644
--- a/media/mca/filterpacks/java/android/filterpacks/videoproc/BackDropperFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/videoproc/BackDropperFilter.java
@@ -24,7 +24,6 @@ import android.filterfw.core.Frame;
import android.filterfw.core.GLFrame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.MutableFrameFormat;
-import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
import android.opengl.GLES20;
@@ -32,7 +31,6 @@ import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.Log;
-import java.lang.ArrayIndexOutOfBoundsException;
import java.lang.Math;
import java.util.Arrays;
import java.nio.ByteBuffer;
diff --git a/media/mca/filterpacks/java/android/filterpacks/videosink/MediaEncoderFilter.java b/media/mca/filterpacks/java/android/filterpacks/videosink/MediaEncoderFilter.java
index 8bb653b..d034051 100644
--- a/media/mca/filterpacks/java/android/filterpacks/videosink/MediaEncoderFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/videosink/MediaEncoderFilter.java
@@ -17,32 +17,23 @@
package android.filterpacks.videosink;
-import android.content.Context;
import android.filterfw.core.Filter;
import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
-import android.filterfw.core.FrameManager;
import android.filterfw.core.GenerateFieldPort;
-import android.filterfw.core.GenerateFinalPort;
import android.filterfw.core.GLFrame;
-import android.filterfw.core.KeyValueMap;
import android.filterfw.core.MutableFrameFormat;
-import android.filterfw.core.NativeFrame;
-import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
import android.filterfw.geometry.Point;
import android.filterfw.geometry.Quad;
-import android.os.ConditionVariable;
import android.media.MediaRecorder;
import android.media.CamcorderProfile;
import android.filterfw.core.GLEnvironment;
import java.io.IOException;
import java.io.FileDescriptor;
-import java.util.List;
-import java.util.Set;
import android.util.Log;
diff --git a/media/mca/filterpacks/java/android/filterpacks/videosink/MediaRecorderStopException.java b/media/mca/filterpacks/java/android/filterpacks/videosink/MediaRecorderStopException.java
index dbf9768..ce7a8c1 100644
--- a/media/mca/filterpacks/java/android/filterpacks/videosink/MediaRecorderStopException.java
+++ b/media/mca/filterpacks/java/android/filterpacks/videosink/MediaRecorderStopException.java
@@ -18,7 +18,6 @@
package android.filterpacks.videosink;
import java.lang.RuntimeException;
-import android.util.Log;
/** @hide **/
public class MediaRecorderStopException extends RuntimeException {
diff --git a/media/mca/filterpacks/java/android/filterpacks/videosrc/CameraSource.java b/media/mca/filterpacks/java/android/filterpacks/videosrc/CameraSource.java
index 2c474ab..d260684 100644
--- a/media/mca/filterpacks/java/android/filterpacks/videosrc/CameraSource.java
+++ b/media/mca/filterpacks/java/android/filterpacks/videosrc/CameraSource.java
@@ -17,29 +17,22 @@
package android.filterpacks.videosrc;
-import android.content.Context;
import android.filterfw.core.Filter;
import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
-import android.filterfw.core.FrameManager;
import android.filterfw.core.GenerateFieldPort;
import android.filterfw.core.GenerateFinalPort;
import android.filterfw.core.GLFrame;
-import android.filterfw.core.KeyValueMap;
import android.filterfw.core.MutableFrameFormat;
-import android.filterfw.core.NativeFrame;
-import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
-import android.os.ConditionVariable;
import android.opengl.Matrix;
import java.io.IOException;
import java.util.List;
-import java.util.Set;
import android.util.Log;
diff --git a/media/mca/filterpacks/java/android/filterpacks/videosrc/MediaSource.java b/media/mca/filterpacks/java/android/filterpacks/videosrc/MediaSource.java
index 0be6c62..53a39a7 100644
--- a/media/mca/filterpacks/java/android/filterpacks/videosrc/MediaSource.java
+++ b/media/mca/filterpacks/java/android/filterpacks/videosrc/MediaSource.java
@@ -23,28 +23,20 @@ import android.filterfw.core.Filter;
import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
-import android.filterfw.core.FrameManager;
import android.filterfw.core.GenerateFieldPort;
import android.filterfw.core.GenerateFinalPort;
import android.filterfw.core.GLFrame;
-import android.filterfw.core.KeyValueMap;
import android.filterfw.core.MutableFrameFormat;
-import android.filterfw.core.NativeFrame;
-import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
import android.graphics.SurfaceTexture;
import android.media.MediaPlayer;
import android.net.Uri;
-import android.os.ConditionVariable;
import android.opengl.Matrix;
import android.view.Surface;
import java.io.IOException;
-import java.io.FileDescriptor;
import java.lang.IllegalArgumentException;
-import java.util.List;
-import java.util.Set;
import android.util.Log;
diff --git a/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureSource.java b/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureSource.java
index 37fa242..6595baa 100644
--- a/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureSource.java
+++ b/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureSource.java
@@ -16,31 +16,20 @@
package android.filterpacks.videosrc;
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
import android.filterfw.core.Filter;
import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
-import android.filterfw.core.FrameManager;
import android.filterfw.core.GenerateFieldPort;
import android.filterfw.core.GenerateFinalPort;
import android.filterfw.core.GLFrame;
-import android.filterfw.core.KeyValueMap;
import android.filterfw.core.MutableFrameFormat;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
import android.graphics.SurfaceTexture;
-import android.media.MediaPlayer;
import android.os.ConditionVariable;
import android.opengl.Matrix;
-import java.io.IOException;
-import java.io.FileDescriptor;
-import java.lang.IllegalArgumentException;
-import java.util.List;
-import java.util.Set;
-
import android.util.Log;
/** <p>A filter that converts textures from a SurfaceTexture object into frames for
diff --git a/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureTarget.java b/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureTarget.java
index b6d9f94..5d03627 100644
--- a/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureTarget.java
+++ b/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureTarget.java
@@ -19,29 +19,19 @@ package android.filterpacks.videosrc;
import android.filterfw.core.Filter;
import android.filterfw.core.FilterContext;
-import android.filterfw.core.FilterSurfaceView;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
import android.filterfw.core.GenerateFinalPort;
import android.filterfw.core.GLEnvironment;
import android.filterfw.core.GLFrame;
-import android.filterfw.core.KeyValueMap;
import android.filterfw.core.MutableFrameFormat;
-import android.filterfw.core.NativeProgram;
-import android.filterfw.core.NativeFrame;
-import android.filterfw.core.Program;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
import android.filterfw.geometry.Quad;
import android.filterfw.geometry.Point;
-import android.view.Surface;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-
-import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.util.Log;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
index ee867ff..d01f4ec 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
@@ -137,6 +137,8 @@ public class MediaNames {
public static final String INVALD_VIDEO_PATH =
"/sdcard/media_api/filepathdoesnotexist" + "/filepathdoesnotexist/temp.3gp";
+ public static final String RECORDED_SURFACE_3GP = "/sdcard/surface.3gp";
+
public static final long RECORDED_TIME = 5000;
public static final long VALID_VIDEO_DURATION = 2000;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaProfileReader.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaProfileReader.java
index abe8b8c..54c8add 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaProfileReader.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaProfileReader.java
@@ -88,7 +88,8 @@ public class MediaProfileReader
if (audioEncoder != MediaRecorder.AudioEncoder.AMR_NB &&
audioEncoder != MediaRecorder.AudioEncoder.AMR_WB &&
audioEncoder != MediaRecorder.AudioEncoder.AAC &&
- audioEncoder != MediaRecorder.AudioEncoder.HE_AAC) {
+ audioEncoder != MediaRecorder.AudioEncoder.HE_AAC &&
+ audioEncoder != MediaRecorder.AudioEncoder.AAC_ELD) {
throw new IllegalArgumentException("Unsupported audio encodeer " + audioEncoder);
}
return audioEncoderMap.get(audioEncoder);
@@ -128,5 +129,6 @@ public class MediaProfileReader
audioEncoderMap.put(MediaRecorder.AudioEncoder.AMR_WB, "amrwb");
audioEncoderMap.put(MediaRecorder.AudioEncoder.AAC, "aac");
audioEncoderMap.put(MediaRecorder.AudioEncoder.HE_AAC, "heaac");
+ audioEncoderMap.put(MediaRecorder.AudioEncoder.AAC_ELD, "aaceld");
}
}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediarecorder/MediaRecorderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediarecorder/MediaRecorderTest.java
index 8e6d5cb..d7069cac 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediarecorder/MediaRecorderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediarecorder/MediaRecorderTest.java
@@ -22,6 +22,10 @@ import com.android.mediaframeworktest.MediaNames;
import java.io.*;
import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Typeface;
import android.hardware.Camera;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
@@ -30,6 +34,7 @@ import android.media.EncoderCapabilities.VideoEncoderCap;
import android.media.EncoderCapabilities.AudioEncoderCap;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;
+import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import com.android.mediaframeworktest.MediaProfileReader;
@@ -41,27 +46,28 @@ import java.util.List;
/**
- * Junit / Instrumentation test case for the media recorder api
- */
+ * Junit / Instrumentation test case for the media recorder api
+ */
public class MediaRecorderTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> {
private String TAG = "MediaRecorderTest";
private int mOutputDuration =0;
private int mOutputVideoWidth = 0;
private int mOutputVideoHeight= 0 ;
-
+
private SurfaceHolder mSurfaceHolder = null;
private MediaRecorder mRecorder;
private int MIN_VIDEO_FPS = 5;
+ private int HIGH_SPEED_FPS = 120;
private static final int CAMERA_ID = 0;
Context mContext;
Camera mCamera;
-
+
public MediaRecorderTest() {
- super("com.android.mediaframeworktest", MediaFrameworkTest.class);
-
+ super(MediaFrameworkTest.class);
+
}
protected void setUp() throws Exception {
@@ -69,8 +75,8 @@ public class MediaRecorderTest extends ActivityInstrumentationTestCase2<MediaFra
mRecorder = new MediaRecorder();
super.setUp();
}
-
- private void recordVideo(int frameRate, int width, int height,
+
+ private void recordVideo(int frameRate, int width, int height,
int videoFormat, int outFormat, String outFile, boolean videoOnly) {
Log.v(TAG,"startPreviewAndPrepareRecording");
try {
@@ -80,7 +86,7 @@ public class MediaRecorderTest extends ActivityInstrumentationTestCase2<MediaFra
}
mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mRecorder.setOutputFormat(outFormat);
- Log.v(TAG, "output format " + outFormat);
+ Log.v(TAG, "output format " + outFormat);
mRecorder.setOutputFile(outFile);
mRecorder.setVideoFrameRate(frameRate);
mRecorder.setVideoSize(width, height);
@@ -105,7 +111,186 @@ public class MediaRecorderTest extends ActivityInstrumentationTestCase2<MediaFra
mRecorder.release();
}
}
-
+
+ private boolean validateGetSurface(boolean useSurface) {
+ Log.v(TAG,"validateGetSurface, useSurface=" + useSurface);
+ MediaRecorder recorder = new MediaRecorder();
+ Surface surface;
+ boolean success = true;
+ try {
+ /* initialization */
+ if (!useSurface) {
+ mCamera = Camera.open(CAMERA_ID);
+ Camera.Parameters parameters = mCamera.getParameters();
+ parameters.setPreviewSize(352, 288);
+ parameters.set("orientation", "portrait");
+ mCamera.setParameters(parameters);
+ mCamera.unlock();
+ recorder.setCamera(mCamera);
+ mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder();
+ recorder.setPreviewDisplay(mSurfaceHolder.getSurface());
+ }
+
+ recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+ int videoSource = useSurface ?
+ MediaRecorder.VideoSource.SURFACE :
+ MediaRecorder.VideoSource.CAMERA;
+ recorder.setVideoSource(videoSource);
+ recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
+ recorder.setOutputFile(MediaNames.RECORDED_SURFACE_3GP);
+ recorder.setVideoFrameRate(30);
+ recorder.setVideoSize(352, 288);
+ recorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
+ recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
+
+ /* Test: getSurface() before prepare()
+ * should throw IllegalStateException
+ */
+ try {
+ surface = recorder.getSurface();
+ throw new Exception("getSurface failed to throw IllegalStateException");
+ } catch (IllegalStateException e) {
+ // OK
+ }
+
+ recorder.prepare();
+
+ /* Test: getSurface() after prepare()
+ * should succeed for surface source
+ * should fail for camera source
+ */
+ try {
+ surface = recorder.getSurface();
+ if (!useSurface) {
+ throw new Exception("getSurface failed to throw IllegalStateException");
+ }
+ } catch (IllegalStateException e) {
+ if (useSurface) {
+ throw new Exception("getSurface failed to throw IllegalStateException");
+ }
+ }
+
+ recorder.start();
+
+ /* Test: getSurface() after start()
+ * should succeed for surface source
+ * should fail for camera source
+ */
+ try {
+ surface = recorder.getSurface();
+ if (!useSurface) {
+ throw new Exception("getSurface failed to throw IllegalStateException");
+ }
+ } catch (IllegalStateException e) {
+ if (useSurface) {
+ throw new Exception("getSurface failed to throw IllegalStateException");
+ }
+ }
+
+ try {
+ recorder.stop();
+ } catch (Exception e) {
+ // stop() could fail if the recording is empty, as we didn't render anything.
+ // ignore any failure in stop, we just want it stopped.
+ }
+
+ /* Test: getSurface() after stop()
+ * should throw IllegalStateException
+ */
+ try {
+ surface = recorder.getSurface();
+ throw new Exception("getSurface failed to throw IllegalStateException");
+ } catch (IllegalStateException e) {
+ // OK
+ }
+ } catch (Exception e) {
+ // fail
+ success = false;
+ }
+
+ try {
+ if (mCamera != null) {
+ mCamera.lock();
+ mCamera.release();
+ mCamera = null;
+ }
+ recorder.release();
+ } catch (Exception e) {
+ success = false;
+ }
+
+ return success;
+ }
+
+ private boolean recordVideoFromSurface(
+ int frameRate, int captureRate, int width, int height,
+ int videoFormat, int outFormat, String outFile, boolean videoOnly) {
+ Log.v(TAG,"recordVideoFromSurface");
+ MediaRecorder recorder = new MediaRecorder();
+ int sleepTime = 33; // normal capture at 33ms / frame
+ try {
+ if (!videoOnly) {
+ recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+ }
+ recorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
+ recorder.setOutputFormat(outFormat);
+ recorder.setOutputFile(outFile);
+ recorder.setVideoFrameRate(frameRate);
+ if (captureRate > 0) {
+ recorder.setCaptureRate(captureRate);
+ sleepTime = 1000 / captureRate;
+ }
+ recorder.setVideoSize(width, height);
+ recorder.setVideoEncoder(videoFormat);
+ if (!videoOnly) {
+ recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
+ }
+ recorder.prepare();
+ Surface surface = recorder.getSurface();
+
+ Paint paint = new Paint();
+ paint.setTextSize(16);
+ paint.setColor(Color.RED);
+ int i;
+
+ /* Test: draw 10 frames at 30fps before start
+ * these should be dropped and not causing malformed stream.
+ */
+ for(i = 0; i < 10; i++) {
+ Canvas canvas = surface.lockCanvas(null);
+ int background = (i * 255 / 99);
+ canvas.drawARGB(255, background, background, background);
+ String text = "Frame #" + i;
+ canvas.drawText(text, 100, 100, paint);
+ surface.unlockCanvasAndPost(canvas);
+ Thread.sleep(sleepTime);
+ }
+
+ Log.v(TAG, "start");
+ recorder.start();
+
+ /* Test: draw another 90 frames at 30fps after start */
+ for(i = 10; i < 100; i++) {
+ Canvas canvas = surface.lockCanvas(null);
+ int background = (i * 255 / 99);
+ canvas.drawARGB(255, background, background, background);
+ String text = "Frame #" + i;
+ canvas.drawText(text, 100, 100, paint);
+ surface.unlockCanvasAndPost(canvas);
+ Thread.sleep(sleepTime);
+ }
+
+ Log.v(TAG, "stop");
+ recorder.stop();
+ recorder.release();
+ } catch (Exception e) {
+ Log.v("record video failed ", e.toString());
+ recorder.release();
+ return false;
+ }
+ return true;
+ }
+
private boolean recordVideoWithPara(VideoEncoderCap videoCap, AudioEncoderCap audioCap, boolean highQuality){
boolean recordSuccess = false;
int videoEncoder = videoCap.mCodec;
@@ -171,7 +356,7 @@ public class MediaRecorderTest extends ActivityInstrumentationTestCase2<MediaFra
return recordSuccess;
}
- private boolean invalidRecordSetting(int frameRate, int width, int height,
+ private boolean invalidRecordSetting(int frameRate, int width, int height,
int videoFormat, int outFormat, String outFile, boolean videoOnly) {
try {
if (!videoOnly) {
@@ -180,7 +365,7 @@ public class MediaRecorderTest extends ActivityInstrumentationTestCase2<MediaFra
}
mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mRecorder.setOutputFormat(outFormat);
- Log.v(TAG, "output format " + outFormat);
+ Log.v(TAG, "output format " + outFormat);
mRecorder.setOutputFile(outFile);
mRecorder.setVideoFrameRate(frameRate);
mRecorder.setVideoSize(width, height);
@@ -208,7 +393,7 @@ public class MediaRecorderTest extends ActivityInstrumentationTestCase2<MediaFra
}
return false;
}
-
+
private void getOutputVideoProperty(String outputFilePath) {
MediaPlayer mediaPlayer = new MediaPlayer();
try {
@@ -223,13 +408,13 @@ public class MediaRecorderTest extends ActivityInstrumentationTestCase2<MediaFra
Thread.sleep(1000);
mOutputVideoHeight = mediaPlayer.getVideoHeight();
mOutputVideoWidth = mediaPlayer.getVideoWidth();
- mediaPlayer.release();
+ mediaPlayer.release();
} catch (Exception e) {
Log.v(TAG, e.toString());
mediaPlayer.release();
- }
+ }
}
-
+
private boolean validateVideo(String filePath, int width, int height) {
boolean validVideo = false;
getOutputVideoProperty(filePath);
@@ -260,7 +445,7 @@ public class MediaRecorderTest extends ActivityInstrumentationTestCase2<MediaFra
int codec = MediaRecorder.VideoEncoder.H263;
int frameRate = MediaProfileReader.getMaxFrameRateForCodec(codec);
recordVideo(frameRate, 352, 288, codec,
- MediaRecorder.OutputFormat.THREE_GPP,
+ MediaRecorder.OutputFormat.THREE_GPP,
MediaNames.RECORDED_PORTRAIT_H263, true);
mCamera.lock();
mCamera.release();
@@ -271,15 +456,15 @@ public class MediaRecorderTest extends ActivityInstrumentationTestCase2<MediaFra
}
assertTrue("PortraitH263", videoRecordedResult);
}
-
+
@LargeTest
- public void testInvalidVideoPath() throws Exception {
+ public void testInvalidVideoPath() throws Exception {
boolean isTestInvalidVideoPathSuccessful = false;
- isTestInvalidVideoPathSuccessful = invalidRecordSetting(15, 176, 144, MediaRecorder.VideoEncoder.H263,
- MediaRecorder.OutputFormat.THREE_GPP, MediaNames.INVALD_VIDEO_PATH, false);
+ isTestInvalidVideoPathSuccessful = invalidRecordSetting(15, 176, 144, MediaRecorder.VideoEncoder.H263,
+ MediaRecorder.OutputFormat.THREE_GPP, MediaNames.INVALD_VIDEO_PATH, false);
assertTrue("Invalid outputFile Path", isTestInvalidVideoPathSuccessful);
}
-
+
@LargeTest
//test cases for the new codec
public void testDeviceSpecificCodec() throws Exception {
@@ -309,4 +494,83 @@ public class MediaRecorderTest extends ActivityInstrumentationTestCase2<MediaFra
assertTrue("testDeviceSpecificCodec", false);
}
}
+
+ // Test MediaRecorder.getSurface() api with surface or camera source
+ public void testGetSurfaceApi() {
+ boolean success = false;
+ int noOfFailure = 0;
+ try {
+ for (int k = 0; k < 2; k++) {
+ success = validateGetSurface(
+ k == 0 ? true : false /* useSurface */);
+ if (!success) {
+ noOfFailure++;
+ }
+ }
+ } catch (Exception e) {
+ Log.v(TAG, e.toString());
+ }
+ assertTrue("testGetSurfaceApi", noOfFailure == 0);
+ }
+
+ // Test recording from surface source with/without audio
+ public void testSurfaceRecording() {
+ boolean success = false;
+ int noOfFailure = 0;
+ try {
+ int codec = MediaRecorder.VideoEncoder.H264;
+ int frameRate = MediaProfileReader.getMaxFrameRateForCodec(codec);
+ for (int k = 0; k < 2; k++) {
+ String filename = "/sdcard/surface_" +
+ (k==0?"video_only":"with_audio") + ".3gp";
+
+ success = recordVideoFromSurface(frameRate, 0, 352, 288, codec,
+ MediaRecorder.OutputFormat.THREE_GPP, filename,
+ k == 0 ? true : false /* videoOnly */);
+ if (success) {
+ success = validateVideo(filename, 352, 288);
+ }
+ if (!success) {
+ noOfFailure++;
+ }
+ }
+ } catch (Exception e) {
+ Log.v(TAG, e.toString());
+ }
+ assertTrue("testSurfaceRecording", noOfFailure == 0);
+ }
+
+ // Test recording from surface source with/without audio
+ public void testSurfaceRecordingTimeLapse() {
+ boolean success = false;
+ int noOfFailure = 0;
+ try {
+ int codec = MediaRecorder.VideoEncoder.H264;
+ int frameRate = MediaProfileReader.getMaxFrameRateForCodec(codec);
+ for (int k = 0; k < 2; k++) {
+ // k==0: time lapse test, set capture rate to MIN_VIDEO_FPS
+ // k==1: slow motion test, set capture rate to HIGH_SPEED_FPS
+ String filename = "/sdcard/surface_" +
+ (k==0 ? "time_lapse" : "slow_motion") + ".3gp";
+
+ // always set videoOnly=false, MediaRecorder should disable
+ // audio automatically with time lapse/slow motion
+ success = recordVideoFromSurface(frameRate,
+ k==0 ? MIN_VIDEO_FPS : HIGH_SPEED_FPS,
+ 352, 288, codec,
+ MediaRecorder.OutputFormat.THREE_GPP,
+ filename, false /* videoOnly */);
+ if (success) {
+ success = validateVideo(filename, 352, 288);
+ }
+ if (!success) {
+ noOfFailure++;
+ }
+ }
+ } catch (Exception e) {
+ Log.v(TAG, e.toString());
+ }
+ assertTrue("testSurfaceRecordingTimeLapse", noOfFailure == 0);
+ }
+
}
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 d157478..89886ef 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -23,6 +23,7 @@ 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;
@@ -151,21 +152,50 @@ public class CameraBinderTest extends AndroidTestCase {
static class DummyCameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
- @Override
- public void onCameraError(int errorCode) {
- }
+ /*
+ * (non-Javadoc)
+ * @see
+ * android.hardware.camera2.ICameraDeviceCallbacks#onCameraError(int,
+ * android.hardware.camera2.CaptureResultExtras)
+ */
+ public void onCameraError(int errorCode, CaptureResultExtras resultExtras)
+ throws RemoteException {
+ // TODO Auto-generated method stub
- @Override
- public void onCameraIdle() {
}
- @Override
- public void onCaptureStarted(int requestId, long timestamp) {
+ /*
+ * (non-Javadoc)
+ * @see
+ * android.hardware.camera2.ICameraDeviceCallbacks#onCaptureStarted(
+ * android.hardware.camera2.CaptureResultExtras, long)
+ */
+ public void onCaptureStarted(CaptureResultExtras resultExtras, long timestamp)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+
}
- @Override
- public void onResultReceived(int frameId, CameraMetadataNative result)
+ /*
+ * (non-Javadoc)
+ * @see
+ * android.hardware.camera2.ICameraDeviceCallbacks#onResultReceived(
+ * android.hardware.camera2.impl.CameraMetadataNative,
+ * android.hardware.camera2.CaptureResultExtras)
+ */
+ public void onResultReceived(CameraMetadataNative result, CaptureResultExtras resultExtras)
throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see android.hardware.camera2.ICameraDeviceCallbacks#onCameraIdle()
+ */
+ public void onCameraIdle() throws RemoteException {
+ // TODO Auto-generated method stub
+
}
}
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 43ebef4..74ce997 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
@@ -21,6 +21,7 @@ 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;
@@ -84,20 +85,50 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
public class DummyCameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
- @Override
- public void onCameraError(int errorCode) {
+ /*
+ * (non-Javadoc)
+ * @see
+ * android.hardware.camera2.ICameraDeviceCallbacks#onCameraError(int,
+ * android.hardware.camera2.CaptureResultExtras)
+ */
+ public void onCameraError(int errorCode, CaptureResultExtras resultExtras)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+
}
- @Override
- public void onCameraIdle() {
+ /*
+ * (non-Javadoc)
+ * @see android.hardware.camera2.ICameraDeviceCallbacks#onCameraIdle()
+ */
+ public void onCameraIdle() throws RemoteException {
+ // TODO Auto-generated method stub
+
}
- @Override
- public void onCaptureStarted(int requestId, long timestamp) {
+ /*
+ * (non-Javadoc)
+ * @see
+ * android.hardware.camera2.ICameraDeviceCallbacks#onCaptureStarted(
+ * android.hardware.camera2.CaptureResultExtras, long)
+ */
+ public void onCaptureStarted(CaptureResultExtras resultExtras, long timestamp)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+
}
- @Override
- public void onResultReceived(int frameId, CameraMetadataNative result) {
+ /*
+ * (non-Javadoc)
+ * @see
+ * android.hardware.camera2.ICameraDeviceCallbacks#onResultReceived(
+ * android.hardware.camera2.impl.CameraMetadataNative,
+ * android.hardware.camera2.CaptureResultExtras)
+ */
+ public void onResultReceived(CameraMetadataNative result, CaptureResultExtras resultExtras)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+
}
}
@@ -139,8 +170,10 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
}
private int submitCameraRequest(CaptureRequest request, boolean streaming) throws Exception {
- int requestId = mCameraUser.submitRequest(request, streaming);
- assertTrue("Request IDs should be non-negative", requestId >= 0);
+ int requestId = mCameraUser.submitRequest(request, streaming, null);
+ assertTrue(
+ "Request IDs should be non-negative (expected: >= 0, actual: " + requestId + ")",
+ requestId >= 0);
return requestId;
}
@@ -250,13 +283,13 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */false);
CaptureRequest request1 = builder.build();
- int status = mCameraUser.submitRequest(request1, /* streaming */false);
+ int status = mCameraUser.submitRequest(request1, /* streaming */false, null);
assertEquals("Expected submitRequest to return BAD_VALUE " +
"since we had 0 surface targets set.", CameraBinderTestUtils.BAD_VALUE, status);
builder.addTarget(mSurface);
CaptureRequest request2 = builder.build();
- status = mCameraUser.submitRequest(request2, /* streaming */false);
+ status = mCameraUser.submitRequest(request2, /* streaming */false, null);
assertEquals("Expected submitRequest to return BAD_VALUE since " +
"the target surface wasn't registered with createStream.",
CameraBinderTestUtils.BAD_VALUE, status);
@@ -290,15 +323,15 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
assertNotSame("Request IDs should be unique for multiple requests", requestId1,
requestIdStreaming);
- int status = mCameraUser.cancelRequest(-1);
+ int status = mCameraUser.cancelRequest(-1, null);
assertEquals("Invalid request IDs should not be cancellable",
CameraBinderTestUtils.BAD_VALUE, status);
- status = mCameraUser.cancelRequest(requestId1);
+ status = mCameraUser.cancelRequest(requestId1, null);
assertEquals("Non-streaming request IDs should not be cancellable",
CameraBinderTestUtils.BAD_VALUE, status);
- status = mCameraUser.cancelRequest(requestIdStreaming);
+ status = mCameraUser.cancelRequest(requestIdStreaming, null);
assertEquals("Streaming request IDs should be cancellable", CameraBinderTestUtils.NO_ERROR,
status);
@@ -337,7 +370,7 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
CameraBinderTestUtils.INVALID_OPERATION, status);
// Test good case, waitUntilIdle when there is no active repeating request
- status = mCameraUser.cancelRequest(requestIdStreaming);
+ status = mCameraUser.cancelRequest(requestIdStreaming, null);
assertEquals(CameraBinderTestUtils.NO_ERROR, status);
status = mCameraUser.waitUntilIdle();
assertEquals(CameraBinderTestUtils.NO_ERROR, status);
@@ -349,16 +382,14 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
CaptureRequest request = createDefaultBuilder(/* needStream */true).build();
// Test both single request and streaming request.
- int requestId1 = submitCameraRequest(request, /* streaming */false);
verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).times(1)).onResultReceived(
- eq(requestId1),
- argThat(matcher));
+ argThat(matcher),
+ any(CaptureResultExtras.class));
- int streamingId = submitCameraRequest(request, /* streaming */true);
verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).atLeast(NUM_CALLBACKS_CHECKED))
.onResultReceived(
- eq(streamingId),
- argThat(matcher));
+ argThat(matcher),
+ any(CaptureResultExtras.class));
}
@SmallTest
@@ -370,13 +401,13 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
// Test both single request and streaming request.
int requestId1 = submitCameraRequest(request, /* streaming */false);
verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).times(1)).onCaptureStarted(
- eq(requestId1),
+ any(CaptureResultExtras.class),
anyLong());
int streamingId = submitCameraRequest(request, /* streaming */true);
verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).atLeast(NUM_CALLBACKS_CHECKED))
.onCaptureStarted(
- eq(streamingId),
+ any(CaptureResultExtras.class),
timestamps.capture());
long timestamp = 0; // All timestamps should be larger than 0.
@@ -399,7 +430,7 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
SystemClock.sleep(WAIT_FOR_WORK_MS);
// Cancel and make sure we eventually quiesce
- status = mCameraUser.cancelRequest(streamingId);
+ status = mCameraUser.cancelRequest(streamingId, null);
verify(mMockCb, timeout(WAIT_FOR_IDLE_TIMEOUT_MS).times(1)).onCameraIdle();
@@ -420,14 +451,14 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
int status;
// Initial flush should work
- status = mCameraUser.flush();
+ status = mCameraUser.flush(null);
assertEquals(CameraBinderTestUtils.NO_ERROR, status);
// Then set up a stream
CaptureRequest request = createDefaultBuilder(/* needStream */true).build();
// Flush should still be a no-op, really
- status = mCameraUser.flush();
+ status = mCameraUser.flush(null);
assertEquals(CameraBinderTestUtils.NO_ERROR, status);
// Submit a few capture requests
@@ -438,7 +469,7 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
int requestId5 = submitCameraRequest(request, /* streaming */false);
// Then flush and wait for idle
- status = mCameraUser.flush();
+ status = mCameraUser.flush(null);
assertEquals(CameraBinderTestUtils.NO_ERROR, status);
verify(mMockCb, timeout(WAIT_FOR_FLUSH_TIMEOUT_MS).times(1)).onCameraIdle();
@@ -450,7 +481,7 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
SystemClock.sleep(WAIT_FOR_WORK_MS);
// Then flush and wait for the idle callback
- status = mCameraUser.flush();
+ status = mCameraUser.flush(null);
assertEquals(CameraBinderTestUtils.NO_ERROR, status);
verify(mMockCb, timeout(WAIT_FOR_FLUSH_TIMEOUT_MS).times(2)).onCameraIdle();
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 3f17aa9..edfa36a 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
@@ -309,7 +309,7 @@ public class CameraMetadataTest extends junit.framework.TestCase {
});
// rational (n) -- in particular rational x 9
- checkKeyGetAndSetArray("android.sensor.calibrationTransform1", Rational[].class,
+ checkKeyGetAndSetArray("android.sensor.calibrationTransform", Rational[].class,
new Rational[] {
new Rational(1, 2), new Rational(3, 4), new Rational(5, 6),
new Rational(7, 8), new Rational(9, 10), new Rational(10, 11),
@@ -552,29 +552,72 @@ public class CameraMetadataTest extends junit.framework.TestCase {
};
int availableFormatTag = CameraMetadataNative.getTag("android.scaler.availableFormats");
- // Write
- mMetadata.set(CameraCharacteristics.SCALER_AVAILABLE_FORMATS, availableFormats);
+ Key<int[]> formatKey = CameraCharacteristics.SCALER_AVAILABLE_FORMATS;
- byte[] availableFormatValues = mMetadata.readValues(availableFormatTag);
+ validateArrayMetadataReadWriteOverride(formatKey, availableFormats,
+ expectedIntValues, availableFormatTag);
- ByteBuffer bf = ByteBuffer.wrap(availableFormatValues).order(ByteOrder.nativeOrder());
+ //
+ // 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");
- assertEquals(expectedIntValues.length * 4, availableFormatValues.length);
- for (int i = 0; i < expectedIntValues.length; ++i) {
- assertEquals(expectedIntValues[i], bf.getInt());
- }
- // Read
- byte[] availableFormatsAsByteArray = new byte[expectedIntValues.length * 4];
- ByteBuffer availableFormatsByteBuffer =
- ByteBuffer.wrap(availableFormatsAsByteArray).order(ByteOrder.nativeOrder());
- for (int value : expectedIntValues) {
- availableFormatsByteBuffer.putInt(value);
- }
- mMetadata.writeValues(availableFormatTag, availableFormatsAsByteArray);
+ Key<int[]> configKey = CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS;
+ validateArrayMetadataReadWriteOverride(configKey, availableStreamConfigs,
+ expectedAvailableStreamConfigs, availableStreamConfigTag);
- int[] resultFormats = mMetadata.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS);
- assertNotNull("result available formats shouldn't be null", resultFormats);
- assertArrayEquals(availableFormats, resultFormats);
+ //
+ // 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)
@@ -639,4 +682,59 @@ public class CameraMetadataTest extends junit.framework.TestCase {
}
}
+
+ /**
+ * 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<T> type = key.getType();
+ if (!type.isArray()) {
+ throw new IllegalArgumentException("This function expects an key with array type");
+ } else if (type != int[].class && type != long[].class) {
+ throw new IllegalArgumentException("This function expects long or int array values");
+ }
+
+ // Write
+ mMetadata.set(key, writeValues);
+
+ byte[] readOutValues = mMetadata.readValues(tag);
+
+ ByteBuffer bf = ByteBuffer.wrap(readOutValues).order(ByteOrder.nativeOrder());
+
+ int readValuesLength = Array.getLength(readValues);
+ int readValuesNumBytes = readValuesLength * 4;
+ if (type == long[].class) {
+ readValuesNumBytes = readValuesLength * 8;
+ }
+
+ assertEquals(readValuesNumBytes, readOutValues.length);
+ for (int i = 0; i < readValuesLength; ++i) {
+ if (type == int[].class) {
+ assertEquals(Array.getInt(readValues, i), bf.getInt());
+ } else if (type == long[].class) {
+ assertEquals(Array.getLong(readValues, i), bf.getLong());
+ }
+ }
+
+ // Read
+ byte[] readOutValuesAsByteArray = new byte[readValuesNumBytes];
+ ByteBuffer readOutValuesByteBuffer =
+ ByteBuffer.wrap(readOutValuesAsByteArray).order(ByteOrder.nativeOrder());
+ for (int i = 0; i < readValuesLength; ++i) {
+ if (type == int[].class) {
+ readOutValuesByteBuffer.putInt(Array.getInt(readValues, i));
+ } else if (type == long[].class) {
+ readOutValuesByteBuffer.putLong(Array.getLong(readValues, i));
+ }
+ }
+ mMetadata.writeValues(tag, readOutValuesAsByteArray);
+
+ T result = mMetadata.get(key);
+ assertNotNull(key.getName() + " result shouldn't be null", result);
+ assertArrayEquals(writeValues, result);
+ }
}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java
index 8c76421..eb1a589 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java
@@ -85,8 +85,8 @@ public class MediaInserterTest extends InstrumentationTestCase {
super.setUp();
mMockProvider = EasyMock.createMock(IContentProvider.class);
mMediaInserter = new MediaInserter(mMockProvider,
- mPackageName, TEST_BUFFER_SIZE);
- mPackageName = getInstrumentation().getContext().getPackageName();
+ mPackageName, TEST_BUFFER_SIZE);
+ mPackageName = getInstrumentation().getContext().getPackageName();
mFilesCounter = 0;
mAudioCounter = 0;
mVideoCounter = 0;
@@ -224,19 +224,19 @@ public class MediaInserterTest extends InstrumentationTestCase {
@SmallTest
public void testInsertContentsWithDifferentSizePerContentType() throws Exception {
EasyMock.expect(mMockProvider.bulkInsert(mPackageName,
- MediaUriMatcher.expectMediaUri(sFilesUri),
+ MediaUriMatcher.expectMediaUri(sFilesUri),
(ContentValues[]) EasyMock.anyObject())).andReturn(1);
EasyMock.expectLastCall().times(1);
EasyMock.expect(mMockProvider.bulkInsert(mPackageName,
- MediaUriMatcher.expectMediaUri(sAudioUri),
+ MediaUriMatcher.expectMediaUri(sAudioUri),
(ContentValues[]) EasyMock.anyObject())).andReturn(1);
EasyMock.expectLastCall().times(2);
EasyMock.expect(mMockProvider.bulkInsert(mPackageName,
- MediaUriMatcher.expectMediaUri(sVideoUri),
+ MediaUriMatcher.expectMediaUri(sVideoUri),
(ContentValues[]) EasyMock.anyObject())).andReturn(1);
EasyMock.expectLastCall().times(3);
EasyMock.expect(mMockProvider.bulkInsert(mPackageName,
- MediaUriMatcher.expectMediaUri(sImagesUri),
+ MediaUriMatcher.expectMediaUri(sImagesUri),
(ContentValues[]) EasyMock.anyObject())).andReturn(1);
EasyMock.expectLastCall().times(4);
EasyMock.replay(mMockProvider);
diff --git a/media/tests/omxjpegdecoder/Android.mk b/media/tests/omxjpegdecoder/Android.mk
index ad874c8..b0bc5d4 100644
--- a/media/tests/omxjpegdecoder/Android.mk
+++ b/media/tests/omxjpegdecoder/Android.mk
@@ -19,7 +19,6 @@ include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
omx_jpeg_decoder.cpp \
jpeg_decoder_bench.cpp \
- SkOmxPixelRef.cpp \
StreamSource.cpp
LOCAL_SHARED_LIBRARIES := \
@@ -34,11 +33,6 @@ LOCAL_SHARED_LIBRARIES := \
LOCAL_C_INCLUDES := \
$(TOP)/external/jpeg \
- $(TOP)/external/skia/include/config \
- $(TOP)/external/skia/include/core \
- $(TOP)/external/skia/include/images \
- $(TOP)/external/skia/include/utils \
- $(TOP)/external/skia/include/effects \
$(TOP)/frameworks/base/media/libstagefright \
$(TOP)/frameworks/base/include/ \
$(TOP)/frameworks/base/ \
diff --git a/media/tests/omxjpegdecoder/SkOmxPixelRef.cpp b/media/tests/omxjpegdecoder/SkOmxPixelRef.cpp
deleted file mode 100644
index a25e854..0000000
--- a/media/tests/omxjpegdecoder/SkOmxPixelRef.cpp
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2009 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.
- */
-
-#include <media/stagefright/foundation/ADebug.h>
-#include <SkBitmap.h>
-
-#include "SkOmxPixelRef.h"
-
-using namespace android;
-
-SkOmxPixelRef::SkOmxPixelRef(SkColorTable* ctable, MediaBuffer* buffer,
- sp<MediaSource> decoder) {
- mBuffer = buffer;
- mDecoder = decoder;
- mSize = buffer->size();
- mCTable = ctable;
- SkSafeRef(mCTable);
-}
-
-SkOmxPixelRef::~SkOmxPixelRef() {
- mBuffer->release();
- CHECK_EQ(mDecoder->stop(), (status_t)OK);
- SkSafeUnref(mCTable);
-}
-
-void* SkOmxPixelRef::onLockPixels(SkColorTable** ct) {
- *ct = mCTable;
- return mBuffer->data();
-}
-
-void SkOmxPixelRef::onUnlockPixels() {
- // nothing to do
-}
diff --git a/media/tests/omxjpegdecoder/SkOmxPixelRef.h b/media/tests/omxjpegdecoder/SkOmxPixelRef.h
deleted file mode 100644
index 374604c..0000000
--- a/media/tests/omxjpegdecoder/SkOmxPixelRef.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2009 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.
- */
-
-#ifndef SKOMXPIXELREF_DEFINED
-#define SKOMXPIXELREF_DEFINED
-
-#include <media/stagefright/MediaBuffer.h>
-#include <media/stagefright/OMXClient.h>
-#include <media/stagefright/OMXCodec.h>
-#include <SkPixelRef.h>
-
-namespace android {
-
-class SkOmxPixelRef : public SkPixelRef {
-public:
- SkOmxPixelRef(SkColorTable* ctable, MediaBuffer* buffer,
- sp<MediaSource> decoder);
- virtual ~SkOmxPixelRef();
-
- //! Return the allocation size for the pixels
- size_t getSize() const { return mSize; }
-
- SK_DECLARE_UNFLATTENABLE_OBJECT()
-protected:
- // overrides from SkPixelRef
- virtual void* onLockPixels(SkColorTable**);
- virtual void onUnlockPixels();
-
-private:
- MediaBuffer* mBuffer;
- sp<MediaSource> mDecoder;
- size_t mSize;
- SkColorTable* mCTable;
-
- typedef SkPixelRef INHERITED;
-};
-
-} // namespace android
-#endif // SKOMXPIXELREF_DEFINED
diff --git a/media/tests/omxjpegdecoder/StreamSource.h b/media/tests/omxjpegdecoder/StreamSource.h
index 6c34cbd..9807385 100644
--- a/media/tests/omxjpegdecoder/StreamSource.h
+++ b/media/tests/omxjpegdecoder/StreamSource.h
@@ -20,7 +20,7 @@
#include <stdio.h>
-#include <core/SkStream.h>
+#include <SkStream.h>
#include <media/stagefright/DataSource.h>
#include <media/stagefright/MediaErrors.h>
#include <utils/threads.h>
diff --git a/media/tests/omxjpegdecoder/omx_jpeg_decoder.cpp b/media/tests/omxjpegdecoder/omx_jpeg_decoder.cpp
index 53f04bc..3dd988e 100644
--- a/media/tests/omxjpegdecoder/omx_jpeg_decoder.cpp
+++ b/media/tests/omxjpegdecoder/omx_jpeg_decoder.cpp
@@ -34,7 +34,6 @@
#include <SkMallocPixelRef.h>
#include "omx_jpeg_decoder.h"
-#include "SkOmxPixelRef.h"
#include "StreamSource.h"
using namespace android;
@@ -158,10 +157,6 @@ bool OmxJpegImageDecoder::decodeSource(sp<MediaSource> decoder,
printf("Duration in decoder->read(): %.1f (msecs). \n",
duration / 1E3 );
- /* Mark the code for now, since we attend to copy buffer to SkBitmap.
- // Install pixelRef to Bitmap.
- installPixelRef(buffer, decoder, bm);*/
-
// Copy pixels from buffer to bm.
// May need to check buffer->rawBytes() == bm->rawBytes().
CHECK_EQ(buffer->size(), bm->getSize());
@@ -172,17 +167,6 @@ bool OmxJpegImageDecoder::decodeSource(sp<MediaSource> decoder,
return true;
}
-void OmxJpegImageDecoder::installPixelRef(MediaBuffer *buffer, sp<MediaSource> decoder,
- SkBitmap* bm) {
-
- // set bm's pixelref based on the data in buffer.
- SkAutoLockPixels alp(*bm);
- SkPixelRef* pr = new SkOmxPixelRef(NULL, buffer, decoder);
- bm->setPixelRef(pr)->unref();
- bm->lockPixels();
- return;
-}
-
void OmxJpegImageDecoder::configBitmapSize(SkBitmap* bm, SkBitmap::Config pref,
int width, int height) {
bm->setConfig(getColorSpaceConfig(pref), width, height, 0, kOpaque_SkAlphaType);
diff --git a/media/tests/omxjpegdecoder/omx_jpeg_decoder.h b/media/tests/omxjpegdecoder/omx_jpeg_decoder.h
index a313877..e431e72 100644
--- a/media/tests/omxjpegdecoder/omx_jpeg_decoder.h
+++ b/media/tests/omxjpegdecoder/omx_jpeg_decoder.h
@@ -49,8 +49,6 @@ private:
sp<MediaSource> getDecoder(OMXClient* client, const sp<MediaSource>& source);
bool decodeSource(sp<MediaSource> decoder, const sp<MediaSource>& source,
SkBitmap* bm);
- void installPixelRef(MediaBuffer* buffer, sp<MediaSource> decoder,
- SkBitmap* bm);
void configBitmapSize(SkBitmap* bm, SkBitmap::Config pref, int width,
int height);
SkBitmap::Config getColorSpaceConfig(SkBitmap::Config pref);
diff --git a/media/tests/players/invoke_mock_media_player.cpp b/media/tests/players/invoke_mock_media_player.cpp
index a94fedb..d1fed7bb 100644
--- a/media/tests/players/invoke_mock_media_player.cpp
+++ b/media/tests/players/invoke_mock_media_player.cpp
@@ -27,6 +27,7 @@
using android::INVALID_OPERATION;
using android::Surface;
using android::IGraphicBufferProducer;
+using android::IMediaHTTPService;
using android::MediaPlayerBase;
using android::OK;
using android::Parcel;
@@ -57,6 +58,7 @@ class Player: public MediaPlayerBase
virtual bool hardwareOutput() {return true;}
virtual status_t setDataSource(
+ const sp<IMediaHTTPService> &httpService,
const char *url,
const KeyedVector<String8, String8> *) {
ALOGV("setDataSource %s", url);