diff options
Diffstat (limited to 'media')
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 -> level = 0 * 0 < x <= R -> 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> + * <service android:name=".MyRouteProviderService" + * android:label="@string/my_route_provider_service"> + * <intent-filter> + * <action android:name="com.android.media.session.MediaRouteProvider" /> + * </intent-filter> + * </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); |