diff options
Diffstat (limited to 'media')
177 files changed, 5697 insertions, 1404 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..4bd5a80 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) || @@ -4093,18 +4086,31 @@ public class AudioService extends IAudioService.Stub { } else if (action.equals(Intent.ACTION_USB_AUDIO_ACCESSORY_PLUG) || 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); + + //TODO(pmclean) - Ignore for now the hasPlayback & hasCapture flags since we + // still need to call setWiredDeviceConnectionState() on disconnect (when we + // don't have card/device files to parse for this info). We will need to store + // that info here when we get smarter about multiple USB card/devices. + // Playback Device 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); setWiredDeviceConnectionState(device, state, params); - } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { + + // Capture Device + device = action.equals(Intent.ACTION_USB_AUDIO_ACCESSORY_PLUG) ? + AudioSystem.DEVICE_IN_USB_ACCESSORY : 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; synchronized (mScoClients) { @@ -4209,7 +4215,7 @@ public class AudioService extends IAudioService.Stub { mStreamStates[AudioSystem.STREAM_MUSIC], 0); } } - } + } // end class AudioServiceBroadcastReceiver //========================================================================================== // RemoteControlDisplay / RemoteControlClient / Remote info @@ -4587,43 +4593,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..57bc171 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. @@ -145,6 +159,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 +247,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 +302,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 +355,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); } @@ -814,6 +856,9 @@ public class AudioTrack * {@link #ERROR_INVALID_OPERATION} */ public int setStereoVolume(float leftVolume, float rightVolume) { + if (isRestricted()) { + return SUCCESS; + } if (mState == STATE_UNINITIALIZED) { return ERROR_INVALID_OPERATION; } @@ -839,8 +884,12 @@ public class AudioTrack /** - * Similar, except set volume of all channels to same value. - * @hide + * Sets the specified output volume values on all channels of this track. The value is clamped + * to the ({@link #getMinVolume()}, {@link #getMaxVolume()}) interval if outside this range. + * @param volume output attenuation for all channels. A value of 0.0f is silence, + * a value of 1.0f is no attenuation. + * @return error code or success, see {@link #SUCCESS}, + * {@link #ERROR_INVALID_OPERATION} */ public int setVolume(float volume) { return setStereoVolume(volume, volume); @@ -983,13 +1032,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 +1141,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 +1199,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}. @@ -1192,9 +1321,12 @@ public class AudioTrack * * @param level send level scalar * @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; } @@ -1205,8 +1337,8 @@ public class AudioTrack if (level > getMaxVolume()) { level = getMaxVolume(); } - native_setAuxEffectSendLevel(level); - return SUCCESS; + int err = native_setAuxEffectSendLevel(level); + return err == 0 ? SUCCESS : ERROR; } //--------------------------------------------------------- @@ -1319,7 +1451,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 +1467,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 +1508,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 9bf48ce..c016d08 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; import android.content.Context; 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..f688d81 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; } } } @@ -464,7 +472,7 @@ public class MediaFocusControl implements OnFinished { exFocusOwner.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS); exFocusOwner.release(); // clear RCD - synchronized(mRCStack) { + synchronized(mPRStack) { clearRemoteControlDisplay_syncAfRcs(); } } @@ -528,7 +536,7 @@ public class MediaFocusControl implements OnFinished { // 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) { + synchronized(mPRStack) { checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); } } @@ -539,7 +547,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 +571,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(); @@ -575,7 +583,7 @@ public class MediaFocusControl implements OnFinished { // 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) { + synchronized(mPRStack) { checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); } } @@ -687,7 +695,7 @@ public class MediaFocusControl implements OnFinished { clientId, afdh, callingPackageName, Binder.getCallingUid())); // there's a new top of the stack, let the remote control know - synchronized(mRCStack) { + synchronized(mPRStack) { checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); } }//synchronized(mAudioFocusLock) @@ -751,7 +759,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 +812,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 +939,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 +1030,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 +1059,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 +1079,13 @@ 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 + * synchronized on mPRStack, 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 */ - 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 +1099,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 +1114,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 +1142,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 +1161,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 +1237,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 mAudioFocusLock, then 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, 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 +1268,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 +1285,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 mAudioFocusLock, then mPRStack * precondition: pi != null */ private void removeMediaButtonReceiver_syncAfRcs(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 +1307,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 +1331,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 +1355,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 +1392,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 +1405,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 +1441,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,7 +1468,7 @@ public class MediaFocusControl implements OnFinished { /** * Helper function: - * Called synchronized on mRCStack + * Called synchronized on mPRStack */ private void clearRemoteControlDisplay_syncAfRcs() { synchronized(mCurrentRcLock) { @@ -1720,35 +1483,35 @@ public class MediaFocusControl implements OnFinished { * checkUpdateRemoteControlDisplay_syncAfRcs() which checks the preconditions for * this method. * Preconditions: - * - called synchronized mAudioFocusLock then on mRCStack - * - mRCStack.isEmpty() is false + * - called synchronized mAudioFocusLock then on mPRStack + * - mPRStack.isEmpty() is false */ private void updateRemoteControlDisplay_syncAfRcs(int infoChangedFlags) { - RemoteControlStackEntry rcse = mRCStack.peek(); + 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(); 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 mAudioFocusLock, then 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 @@ -1757,7 +1520,7 @@ public class MediaFocusControl implements OnFinished { private void checkUpdateRemoteControlDisplay_syncAfRcs(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()) { + if (mPRStack.isEmpty() || mFocusStack.isEmpty()) { clearRemoteControlDisplay_syncAfRcs(); return; } @@ -1790,19 +1553,19 @@ public class MediaFocusControl implements OnFinished { } // if the audio focus and RC owners belong to different packages, there is a mismatch, clear - if (!af.hasSamePackage(mRCStack.peek().mCallingPackageName)) { + if (!af.hasSamePackage(mPRStack.peek().getCallingPackageName())) { 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)) { + if (!af.hasSameUid(mPRStack.peek().getCallingUid())) { clearRemoteControlDisplay_syncAfRcs(); return; } // refresh conditions were verified: update the remote controls - // ok to call: synchronized mAudioFocusLock then on mRCStack, mRCStack is not empty + // ok to call: synchronized mAudioFocusLock then on mPRStack, mPRStack is not empty updateRemoteControlDisplay_syncAfRcs(infoChangedFlags); } @@ -1820,25 +1583,25 @@ public class MediaFocusControl implements OnFinished { private void onPromoteRcc(int rccId) { if (DEBUG_RC) { Log.d(TAG, "Promoting RCC " + rccId); } synchronized(mAudioFocusLock) { - synchronized(mRCStack) { + synchronized(mPRStack) { // ignore if given RCC ID is already at top of remote control stack - if (!mRCStack.isEmpty() && (mRCStack.peek().mRccId == rccId)) { + if (!mPRStack.isEmpty() && (mPRStack.peek().getRccId() == 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) { + for (int index = mPRStack.size()-1; index >= 0; index--) { + final PlayerRecord prse = mPRStack.elementAt(index); + if (prse.getRccId() == 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); + + " to " + (mPRStack.size()-1)); } + final PlayerRecord prse = mPRStack.remove(indexToPromote); + mPRStack.push(prse); // the RC stack changed, reevaluate the display checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); } @@ -1846,7 +1609,7 @@ public class MediaFocusControl implements OnFinished { // not expected to happen, indicates improper concurrent modification Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e); } - }//synchronized(mRCStack) + }//synchronized(mPRStack) }//synchronized(mAudioFocusLock) } @@ -1859,7 +1622,7 @@ public class MediaFocusControl implements OnFinished { Log.i(TAG, " Remote Control registerMediaButtonIntent() for " + mediaIntent); synchronized(mAudioFocusLock) { - synchronized(mRCStack) { + synchronized(mPRStack) { if (pushMediaButtonReceiver_syncAfRcs(mediaIntent, eventReceiver, token)) { // new RC client, assume every type of information shall be queried checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); @@ -1877,7 +1640,7 @@ public class MediaFocusControl implements OnFinished { Log.i(TAG, " Remote Control unregisterMediaButtonIntent() for " + mediaIntent); synchronized(mAudioFocusLock) { - synchronized(mRCStack) { + synchronized(mPRStack) { boolean topOfStackWillChange = isCurrentRcController(mediaIntent); removeMediaButtonReceiver_syncAfRcs(mediaIntent); if (topOfStackWillChange) { @@ -1888,6 +1651,12 @@ public class MediaFocusControl implements OnFinished { } } + 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 +1667,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 +1681,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) @@ -1929,45 +1698,26 @@ public class MediaFocusControl implements OnFinished { if (DEBUG_RC) Log.i(TAG, "Register remote control client rcClient="+rcClient); int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED; synchronized(mAudioFocusLock) { - synchronized(mRCStack) { + synchronized(mPRStack) { // 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(); + 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()); + if (rcClient == null) { - // here rcse.mRcClientDeathHandler is null; - rcse.resetPlaybackInfo(); break; } - rccId = rcse.mRccId; + + rccId = prse.getRccId(); // there is a new (non-null) client: - // 1/ give the new client the displays (if any) + // give the new client the displays (if any) if (mRcDisplays.size() > 0) { - plugRemoteControlDisplaysIntoClient_syncRcStack(rcse.mRcClient); + plugRemoteControlDisplaysIntoClient_syncRcStack(prse.getRcc()); } - // 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; break; } }//for @@ -1981,7 +1731,7 @@ public class MediaFocusControl implements OnFinished { if (isCurrentRcController(mediaIntent)) { checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); } - }//synchronized(mRCStack) + }//synchronized(mPRStack) }//synchronized(mAudioFocusLock) return rccId; } @@ -1994,20 +1744,16 @@ public class MediaFocusControl implements OnFinished { IRemoteControlClient rcClient) { if (DEBUG_RC) Log.i(TAG, "Unregister remote control client rcClient="+rcClient); synchronized(mAudioFocusLock) { - synchronized(mRCStack) { + synchronized(mPRStack) { 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)) { + 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 - // stop monitoring its death - rcse.unlinkToRcClientDeath(); - // reset the client-related fields - rcse.mRcClient = null; - rcse.mCallingPackageName = null; - topRccChange = (index == mRCStack.size()-1); + prse.resetControllerInfoForNoRcc(); + topRccChange = (index == mPRStack.size()-1); // there can only be one matching RCC in the RC stack, we're done break; } @@ -2071,12 +1817,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 +1835,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); @@ -2100,7 +1846,7 @@ public class MediaFocusControl implements OnFinished { private void plugRemoteControlDisplaysIntoClient_syncRcStack(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 +1863,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 +1884,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 +1909,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 +1925,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 +1955,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 +1963,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 +1974,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 +2001,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 +2017,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 +2046,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 +2064,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 +2080,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 +2095,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 +2118,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 +2149,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 +2171,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 +2180,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 +2189,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 +2200,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 +2208,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 +2239,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 +2251,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 +2277,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 +2358,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 +2413,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 +2445,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 +2453,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..58d3520 100644 --- a/media/java/android/media/WebVttRenderer.java +++ b/media/java/android/media/WebVttRenderer.java @@ -1136,11 +1136,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 defStyle) { - super(context, attrs, defStyle); + public WebVttRenderingWidget(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + 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 +1525,8 @@ class WebVttRenderingWidget extends ViewGroup implements SubtitleTrack.Rendering if (DEBUG) { setBackgroundColor(DEBUG_REGION_BACKGROUND); + } else { + setBackgroundColor(captionStyle.windowColor); } } @@ -1533,6 +1539,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/session/IMediaController.aidl b/media/java/android/media/session/IMediaController.aidl new file mode 100644 index 0000000..d34e973 --- /dev/null +++ b/media/java/android/media/session/IMediaController.aidl @@ -0,0 +1,51 @@ +/* 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.IMediaControllerCallback; +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 IMediaController { + void sendCommand(String command, in Bundle extras, in ResultReceiver cb); + void sendMediaButton(in KeyEvent mediaButton); + void registerCallbackListener(in IMediaControllerCallback cb); + void unregisterCallbackListener(in IMediaControllerCallback cb); + boolean isTransportControlEnabled(); + + // 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/IMediaControllerCallback.aidl b/media/java/android/media/session/IMediaControllerCallback.aidl new file mode 100644 index 0000000..3651f1b --- /dev/null +++ b/media/java/android/media/session/IMediaControllerCallback.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.session; + +import android.media.session.MediaMetadata; +import android.media.session.PlaybackState; +import android.os.Bundle; + +/** + * @hide + */ +oneway interface IMediaControllerCallback { + void onEvent(String event, in Bundle extras); + void onRouteChanged(in Bundle 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/IMediaSession.aidl b/media/java/android/media/session/IMediaSession.aidl new file mode 100644 index 0000000..aed7641 --- /dev/null +++ b/media/java/android/media/session/IMediaSession.aidl @@ -0,0 +1,41 @@ +/* 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.IMediaController; +import android.media.session.MediaMetadata; +import android.media.session.PlaybackState; +import android.os.Bundle; + +/** + * Interface to a MediaSession in the system. + * @hide + */ +interface IMediaSession { + void sendEvent(String event, in Bundle data); + IMediaController getMediaController(); + void setTransportPerformerEnabled(); + void setRouteState(in Bundle routeState); + void setRoute(in Bundle mediaRouteDescriptor); + List<String> getSupportedInterfaces(); + void publish(); + void destroy(); + + // 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/IMediaSessionCallback.aidl b/media/java/android/media/session/IMediaSessionCallback.aidl new file mode 100644 index 0000000..7c183e0 --- /dev/null +++ b/media/java/android/media/session/IMediaSessionCallback.aidl @@ -0,0 +1,41 @@ +/* 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.content.Intent; +import android.os.Bundle; +import android.os.ResultReceiver; + +/** + * @hide + */ +oneway interface IMediaSessionCallback { + void onCommand(String command, in Bundle extras, in ResultReceiver cb); + void onMediaButton(in Intent mediaRequestIntent); + void onRequestRouteChange(in Bundle route); + + // 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/IMediaSessionManager.aidl b/media/java/android/media/session/IMediaSessionManager.aidl new file mode 100644 index 0000000..0b4328e --- /dev/null +++ b/media/java/android/media/session/IMediaSessionManager.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.IMediaSession; +import android.media.session.IMediaSessionCallback; +import android.os.Bundle; + +/** + * Interface to the MediaSessionManagerService + * @hide + */ +interface IMediaSessionManager { + IMediaSession createSession(String packageName, in IMediaSessionCallback cb, String tag); +}
\ No newline at end of file diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java new file mode 100644 index 0000000..afd8b11 --- /dev/null +++ b/media/java/android/media/session/MediaController.java @@ -0,0 +1,353 @@ +/* + * 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 MediaSessionManager} if you + * hold the "android.permission.MEDIA_CONTENT_CONTROL" permission or directly if + * you have a {@link MediaSessionToken} from the session owner. + * <p> + * MediaController objects are thread-safe. + */ +public final class MediaController { + private static final String TAG = "MediaController"; + + 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 IMediaController 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 MediaController(IMediaController sessionBinder) { + mSessionBinder = sessionBinder; + } + + /** + * @hide + */ + public static MediaController fromBinder(IMediaController sessionBinder) { + MediaController controller = new MediaController(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 MediaController fromToken(MediaSessionToken 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); + } + } + + /* + * @hide + */ + IMediaController 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(Bundle routeDescriptor) { + synchronized (mLock) { + for (int i = mCallbacks.size() - 1; i >= 0; i--) { + mCallbacks.get(i).post(MSG_ROUTE, null, routeDescriptor); + } + } + } + + /** + * 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(Bundle route) { + } + } + + private final static class CallbackStub extends IMediaControllerCallback.Stub { + private final WeakReference<MediaController> mController; + + public CallbackStub(MediaController controller) { + mController = new WeakReference<MediaController>(controller); + } + + @Override + public void onEvent(String event, Bundle extras) { + MediaController controller = mController.get(); + if (controller != null) { + controller.postEvent(event, extras); + } + } + + @Override + public void onRouteChanged(Bundle mediaRouteDescriptor) { + MediaController controller = mController.get(); + if (controller != null) { + controller.postRouteChanged(mediaRouteDescriptor); + } + } + + @Override + public void onPlaybackStateChanged(PlaybackState state) { + MediaController controller = mController.get(); + if (controller != null) { + TransportController tc = controller.getTransportController(); + if (tc != null) { + tc.postPlaybackStateChanged(state); + } + } + } + + @Override + public void onMetadataChanged(MediaMetadata metadata) { + MediaController 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 MediaController.Callback mCallback; + + public MessageHandler(Looper looper, MediaController.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(msg.getData()); + } + } + + public void post(int what, Object obj, Bundle data) { + obtainMessage(what, obj).sendToTarget(); + } + } + +} 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/MediaSession.java b/media/java/android/media/session/MediaSession.java new file mode 100644 index 0000000..23c3035 --- /dev/null +++ b/media/java/android/media/session/MediaSession.java @@ -0,0 +1,537 @@ +/* + * 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.IMediaController; +import android.media.session.IMediaSession; +import android.media.session.IMediaSessionCallback; +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; + +/** + * 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 MediaSessionManager#createSession(String)}. Once a session is created + * apps that have the MEDIA_CONTENT_CONTROL permission can interact with the + * session through {@link MediaSessionManager#getActiveSessions()}. The owner of + * the session may also use {@link #getSessionToken()} to allow apps without + * this permission to create a {@link MediaController} to interact with this + * session. + * <p> + * To receive commands, media keys, and other events a Callback must be set with + * {@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 MediaSession { + private static final String TAG = "MediaSession"; + + 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 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 MediaSessionToken mSessionToken; + private final IMediaSession mBinder; + private final CallbackStub mCbStub; + + private final ArrayList<MessageHandler> mCallbacks = new ArrayList<MessageHandler>(); + // TODO route interfaces + private final ArrayMap<String, RouteInterface.Stub> mInterfaces + = new ArrayMap<String, RouteInterface.Stub>(); + + private TransportPerformer mPerformer; + + private boolean mPublished = false;; + + /** + * @hide + */ + public MediaSession(IMediaSession binder, CallbackStub cbStub) { + mBinder = binder; + mCbStub = cbStub; + IMediaController controllerBinder = null; + try { + controllerBinder = mBinder.getMediaController(); + } catch (RemoteException e) { + throw new RuntimeException("Dead object in MediaSessionController constructor: ", e); + } + mSessionToken = new MediaSessionToken(controllerBinder); + } + + /** + * Set the callback to receive updates on. + * + * @param callback The callback object + */ + public void addCallback(Callback callback) { + addCallback(callback, null); + } + + 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); + } + } + + 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; + } + + /** + * Add an interface that can be used by MediaSessions. TODO make this a + * route provider api + * + * @see RouteInterface + * @param iface The interface to add + * @hide + */ + public void addInterface(RouteInterface.Stub iface) { + if (iface == null) { + throw new IllegalArgumentException("Stub cannot be null"); + } + String name = iface.getName(); + if (TextUtils.isEmpty(name)) { + throw new IllegalArgumentException("Stub must return a valid name"); + } + if (mInterfaces.containsKey(iface)) { + throw new IllegalArgumentException("Interface is already added"); + } + synchronized (mLock) { + mInterfaces.put(iface.getName(), iface); + } + } + + /** + * 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 MediaController} for interacting with this session. The owner of + * the session is responsible for deciding how to distribute these tokens. + * + * @return A token that can be used to create a MediaController for this + * session + */ + public MediaSessionToken getSessionToken() { + return mSessionToken; + } + + 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(Bundle mediaRouteDescriptor) { + synchronized (mLock) { + for (int i = mCallbacks.size() - 1; i >= 0; i--) { + mCallbacks.get(i).post(MSG_ROUTE_CHANGE, mediaRouteDescriptor); + } + } + } + + /** + * 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 descriptor + */ + public void onRequestRouteChange(Bundle descriptor) { + } + } + + /** + * @hide + */ + public static class CallbackStub extends IMediaSessionCallback.Stub { + private WeakReference<MediaSession> mMediaSession; + + public void setMediaSession(MediaSession session) { + mMediaSession = new WeakReference<MediaSession>(session); + } + + @Override + public void onCommand(String command, Bundle extras, ResultReceiver cb) + throws RemoteException { + MediaSession session = mMediaSession.get(); + if (session != null) { + session.postCommand(command, extras, cb); + } + } + + @Override + public void onMediaButton(Intent mediaButtonIntent) throws RemoteException { + MediaSession session = mMediaSession.get(); + if (session != null) { + session.postMediaButton(mediaButtonIntent); + } + } + + @Override + public void onRequestRouteChange(Bundle mediaRouteDescriptor) throws RemoteException { + MediaSession session = mMediaSession.get(); + if (session != null) { + session.postRequestRouteChange(mediaRouteDescriptor); + } + } + + @Override + public void onPlay() throws RemoteException { + MediaSession session = mMediaSession.get(); + if (session != null) { + TransportPerformer tp = session.getTransportPerformer(); + if (tp != null) { + tp.onPlay(); + } + } + } + + @Override + public void onPause() throws RemoteException { + MediaSession session = mMediaSession.get(); + if (session != null) { + TransportPerformer tp = session.getTransportPerformer(); + if (tp != null) { + tp.onPause(); + } + } + } + + @Override + public void onStop() throws RemoteException { + MediaSession session = mMediaSession.get(); + if (session != null) { + TransportPerformer tp = session.getTransportPerformer(); + if (tp != null) { + tp.onStop(); + } + } + } + + @Override + public void onNext() throws RemoteException { + MediaSession session = mMediaSession.get(); + if (session != null) { + TransportPerformer tp = session.getTransportPerformer(); + if (tp != null) { + tp.onNext(); + } + } + } + + @Override + public void onPrevious() throws RemoteException { + MediaSession session = mMediaSession.get(); + if (session != null) { + TransportPerformer tp = session.getTransportPerformer(); + if (tp != null) { + tp.onPrevious(); + } + } + } + + @Override + public void onFastForward() throws RemoteException { + MediaSession session = mMediaSession.get(); + if (session != null) { + TransportPerformer tp = session.getTransportPerformer(); + if (tp != null) { + tp.onFastForward(); + } + } + } + + @Override + public void onRewind() throws RemoteException { + MediaSession session = mMediaSession.get(); + if (session != null) { + TransportPerformer tp = session.getTransportPerformer(); + if (tp != null) { + tp.onRewind(); + } + } + } + + @Override + public void onSeekTo(long pos) throws RemoteException { + MediaSession session = mMediaSession.get(); + if (session != null) { + TransportPerformer tp = session.getTransportPerformer(); + if (tp != null) { + tp.onSeekTo(pos); + } + } + } + + @Override + public void onRate(Rating rating) throws RemoteException { + MediaSession session = mMediaSession.get(); + if (session != null) { + TransportPerformer tp = session.getTransportPerformer(); + if (tp != null) { + tp.onRate(rating); + } + } + } + + } + + private class MessageHandler extends Handler { + private MediaSession.Callback mCallback; + + public MessageHandler(Looper looper, MediaSession.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((Bundle) msg.obj); + break; + } + } + msg.recycle(); + } + + 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/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java new file mode 100644 index 0000000..e3f2d9c --- /dev/null +++ b/media/java/android/media/session/MediaSessionManager.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.IMediaSessionManager; +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 MediaSession + * @see MediaController + */ +public final class MediaSessionManager { + private static final String TAG = "MediaSessionManager"; + + private final IMediaSessionManager mService; + + private Context mContext; + + /** + * @hide + */ + public MediaSessionManager(Context context) { + // Consider rewriting like DisplayManagerGlobal + // Decide if we need context + mContext = context; + IBinder b = ServiceManager.getService(Context.MEDIA_SESSION_SERVICE); + mService = IMediaSessionManager.Stub.asInterface(b); + } + + /** + * Creates a new session. + * + * @param tag A short name for debugging purposes + * @return a {@link MediaSession} for the new session + */ + public MediaSession createSession(String tag) { + try { + MediaSession.CallbackStub cbStub = new MediaSession.CallbackStub(); + MediaSession session = new MediaSession(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<MediaController> getActiveSessions() { + // TODO + return new ArrayList<MediaController>(); + } +} diff --git a/media/java/android/media/session/MediaSessionToken.aidl b/media/java/android/media/session/MediaSessionToken.aidl new file mode 100644 index 0000000..5812682 --- /dev/null +++ b/media/java/android/media/session/MediaSessionToken.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 MediaSessionToken; diff --git a/media/java/android/media/session/MediaSessionToken.java b/media/java/android/media/session/MediaSessionToken.java new file mode 100644 index 0000000..dbb4964 --- /dev/null +++ b/media/java/android/media/session/MediaSessionToken.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.IMediaController; +import android.os.Parcel; +import android.os.Parcelable; + +public class MediaSessionToken implements Parcelable { + private IMediaController mBinder; + + /** + * @hide + */ + MediaSessionToken(IMediaController binder) { + mBinder = binder; + } + + private MediaSessionToken(Parcel in) { + mBinder = IMediaController.Stub.asInterface(in.readStrongBinder()); + } + + /** + * @hide + */ + IMediaController 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<MediaSessionToken> CREATOR + = new Parcelable.Creator<MediaSessionToken>() { + @Override + public MediaSessionToken createFromParcel(Parcel in) { + return new MediaSessionToken(in); + } + + @Override + public MediaSessionToken[] newArray(int size) { + return new MediaSessionToken[size]; + } + }; +} 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..b3506b3 --- /dev/null +++ b/media/java/android/media/session/PlaybackState.java @@ -0,0 +1,351 @@ +/* + * 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.RemoteControlClient; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Playback state for a {@link MediaSession}. 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; + + 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/RouteInterface.java b/media/java/android/media/session/RouteInterface.java new file mode 100644 index 0000000..2391f27 --- /dev/null +++ b/media/java/android/media/session/RouteInterface.java @@ -0,0 +1,187 @@ +/* + * 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.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.Parcelable; +import android.os.ResultReceiver; + +/** + * Routes can support multiple interfaces for MediaSessions to interact with. To + * add a standard interface you should implement that interface's RouteInterface + * Stub and register it with the session. The set of supported commands is + * dependent on the specific interface's implementation. + * <p> + * A MediaInterface can be registered by calling TODO. Once added an interface + * will be used by Sessions to decide how they communicate with a session and + * cannot be removed, so all interfaces that you plan to support should be added + * when the route is created. + * + * @see RouteTransportControls + */ +public final class RouteInterface { + private static final String TAG = "MediaInterface"; + + private static final String KEY_RESULT = "result"; + + private final MediaController mController; + private final String mIface; + + /** + * @hide + */ + RouteInterface(MediaController controller, String iface) { + mController = controller; + mIface = iface; + } + + public void sendCommand(String command, Bundle params, ResultReceiver cb) { + // TODO + } + + public void addListener(EventListener listener) { + addListener(listener, null); + } + + public void addListener(EventListener listener, Handler handler) { + // TODO See MediaController for add/remove pattern + } + + public void removeListener(EventListener listener) { + // TODO + } + + // TODO decide on list of supported types + private static Bundle writeResultToBundle(Object v) { + Bundle b = new Bundle(); + if (v == null) { + // Don't send anything if null + } else if (v instanceof String) { + b.putString(KEY_RESULT, (String) v); + } else if (v instanceof Integer) { + b.putInt(KEY_RESULT, (Integer) v); + } else if (v instanceof Bundle) { + // Must be before Parcelable + b.putBundle(KEY_RESULT, (Bundle) v); + } else if (v instanceof Parcelable) { + b.putParcelable(KEY_RESULT, (Parcelable) v); + } else if (v instanceof Short) { + b.putShort(KEY_RESULT, (Short) v); + } else if (v instanceof Long) { + b.putLong(KEY_RESULT, (Long) v); + } else if (v instanceof Float) { + b.putFloat(KEY_RESULT, (Float) v); + } else if (v instanceof Double) { + b.putDouble(KEY_RESULT, (Double) v); + } else if (v instanceof Boolean) { + b.putBoolean(KEY_RESULT, (Boolean) v); + } else if (v instanceof CharSequence) { + // Must be after String + b.putCharSequence(KEY_RESULT, (CharSequence) v); + } else if (v instanceof boolean[]) { + b.putBooleanArray(KEY_RESULT, (boolean[]) v); + } else if (v instanceof byte[]) { + b.putByteArray(KEY_RESULT, (byte[]) v); + } else if (v instanceof String[]) { + b.putStringArray(KEY_RESULT, (String[]) v); + } else if (v instanceof CharSequence[]) { + // Must be after String[] and before Object[] + b.putCharSequenceArray(KEY_RESULT, (CharSequence[]) v); + } else if (v instanceof IBinder) { + b.putBinder(KEY_RESULT, (IBinder) v); + } else if (v instanceof Parcelable[]) { + b.putParcelableArray(KEY_RESULT, (Parcelable[]) v); + } else if (v instanceof int[]) { + b.putIntArray(KEY_RESULT, (int[]) v); + } else if (v instanceof long[]) { + b.putLongArray(KEY_RESULT, (long[]) v); + } else if (v instanceof Byte) { + b.putByte(KEY_RESULT, (Byte) v); + } + return b; + } + + public abstract static class Stub { + + /** + * The name of an interface should be a fully qualified name to prevent + * namespace collisions. Example: "com.myproject.MyPlaybackInterface" + * + * @return The name of this interface + */ + public abstract String getName(); + + /** + * This is called when a command is received that matches the interface + * you registered. Commands can come from any app with a MediaController + * reference to the session. + * + * @see MediaController + * @see MediaSession + * @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. + */ + public abstract void onCommand(String command, Bundle args, ResultReceiver cb); + + public final void sendEvent(MediaSession session, String event, Bundle extras) { + // TODO + } + } + + /** + * 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 RouteInterface.EventListener mListener; + + public EventHandler(Looper looper, RouteInterface.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/RouteTransportControls.java b/media/java/android/media/session/RouteTransportControls.java new file mode 100644 index 0000000..665fd10 --- /dev/null +++ b/media/java/android/media/session/RouteTransportControls.java @@ -0,0 +1,230 @@ +/* + * 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.RemoteControlClient; +import android.os.Bundle; +import android.os.Handler; +import android.os.ResultReceiver; +import android.text.TextUtils; +import android.util.Log; + +/** + * A standard media control interface for Routes. Routes can support multiple + * interfaces for MediaSessions to interact with. TODO rewrite for routes + */ +public final class RouteTransportControls { + private static final String TAG = "RouteTransportControls"; + public static final String NAME = "android.media.session.RouteTransportControls"; + + private static final String KEY_VALUE1 = "value1"; + + private static final String METHOD_FAST_FORWARD = "fastForward"; + private static final String METHOD_GET_CURRENT_POSITION = "getCurrentPosition"; + private static final String METHOD_GET_CAPABILITIES = "getCapabilities"; + + private static final String EVENT_PLAYSTATE_CHANGE = "playstateChange"; + private static final String EVENT_METADATA_CHANGE = "metadataChange"; + + private final MediaController mController; + private final RouteInterface mIface; + + private RouteTransportControls(RouteInterface iface, MediaController controller) { + mIface = iface; + mController = controller; + } + + public static RouteTransportControls from(MediaController controller) { +// MediaInterface iface = controller.getInterface(NAME); +// if (iface != null) { +// return new RouteTransportControls(iface, controller); +// } + return null; + } + + /** + * Send a play command to the route. TODO rename resume() and use messaging + * protocol, not KeyEvent + */ + public void play() { + // TODO + } + + /** + * Send a pause command to the session. + */ + public void pause() { + // TODO + } + + /** + * Set the rate at which to fastforward. Valid values are in the range [0,1] + * with actual rates depending on the implementation. + * + * @param rate + */ + public void fastForward(float rate) { + if (rate < 0 || rate > 1) { + throw new IllegalArgumentException("Rate must be between 0 and 1 inclusive"); + } + Bundle b = new Bundle(); + b.putFloat(KEY_VALUE1, rate); + mIface.sendCommand(METHOD_FAST_FORWARD, b, null); + } + + public void getCurrentPosition(ResultReceiver cb) { + mIface.sendCommand(METHOD_GET_CURRENT_POSITION, null, cb); + } + + public void getCapabilities(ResultReceiver cb) { + mIface.sendCommand(METHOD_GET_CAPABILITIES, null, cb); + } + + public void addListener(Listener listener) { + mIface.addListener(listener.mListener); + } + + public void addListener(Listener listener, Handler handler) { + mIface.addListener(listener.mListener, handler); + } + + public void removeListener(Listener listener) { + mIface.removeListener(listener.mListener); + } + + public static abstract class Stub extends RouteInterface.Stub { + private final MediaSession mSession; + + public Stub(MediaSession session) { + mSession = session; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public void onCommand(String method, Bundle extras, ResultReceiver cb) { + if (TextUtils.isEmpty(method)) { + return; + } + Bundle result; + if (METHOD_FAST_FORWARD.equals(method)) { + fastForward(extras.getFloat(KEY_VALUE1, -1)); + } else if (METHOD_GET_CURRENT_POSITION.equals(method)) { + if (cb != null) { + result = new Bundle(); + result.putLong(KEY_VALUE1, getCurrentPosition()); + cb.send(0, result); + } + } else if (METHOD_GET_CAPABILITIES.equals(method)) { + if (cb != null) { + result = new Bundle(); + result.putLong(KEY_VALUE1, getCapabilities()); + cb.send(0, result); + } + } + } + + /** + * Override to handle fast forwarding. Valid values are [0,1] inclusive. + * The interpretation of the rate is up to the implementation. If no + * rate was included with the command a rate of -1 will be used by + * default. + * + * @param rate The rate at which to fast forward as a multiplier + */ + public void fastForward(float rate) { + Log.w(TAG, "fastForward is not supported."); + } + + /** + * 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; + } + + /** + * Publish the current playback state to the system and any controllers. + * Valid values are defined in {@link RemoteControlClient}. TODO move + * play states somewhere else. + * + * @param state + */ + public final void updatePlaybackState(int state) { + Bundle extras = new Bundle(); + extras.putInt(KEY_VALUE1, state); + sendEvent(mSession, EVENT_PLAYSTATE_CHANGE, extras); + } + } + + /** + * Register this event listener using TODO to receive + * TransportControlInterface events from a session. + * + * @see RouteInterface.EventListener + */ + public static abstract class Listener { + + private RouteInterface.EventListener mListener = new RouteInterface.EventListener() { + @Override + public final void onEvent(String event, Bundle args) { + if (EVENT_PLAYSTATE_CHANGE.equals(event)) { + onPlaybackStateChange(args.getInt(KEY_VALUE1)); + } else if (EVENT_METADATA_CHANGE.equals(event)) { + onMetadataUpdate(args); + } + } + }; + + /** + * 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(Bundle metadata) { + } + } + +} diff --git a/media/java/android/media/session/TransportController.java b/media/java/android/media/session/TransportController.java new file mode 100644 index 0000000..15b11f3 --- /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 IMediaController mBinder; + + /** + * @hide + */ + public TransportController(IMediaController 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..b96db20 --- /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 IMediaSession mBinder; + + /** + * @hide + */ + public TransportPerformer(IMediaSession 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..fcd425e 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, @@ -613,8 +613,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 +631,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 +796,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 0cfd2ff..fd69cad 100644 --- a/media/jni/android_media_MediaRecorder.cpp +++ b/media/jni/android_media_MediaRecorder.cpp @@ -344,6 +344,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) { @@ -470,6 +490,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 7df56f4..0e55228 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, @@ -924,6 +933,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) { @@ -991,6 +1001,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: @@ -1004,6 +1030,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: @@ -1032,6 +1059,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; @@ -1047,7 +1090,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 @@ -1064,6 +1107,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__); @@ -1235,6 +1284,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/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); |