summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
Diffstat (limited to 'media')
-rw-r--r--media/java/android/media/AudioAttributes.java7
-rw-r--r--media/java/android/media/AudioDevicesManager.java1
-rw-r--r--media/java/android/media/AudioManager.java328
-rw-r--r--media/java/android/media/AudioManagerInternal.java10
-rw-r--r--media/java/android/media/AudioPort.java1
-rw-r--r--media/java/android/media/AudioPortEventHandler.java2
-rw-r--r--media/java/android/media/AudioRecord.java5
-rw-r--r--media/java/android/media/AudioTrack.java3
-rw-r--r--media/java/android/media/IAudioService.aidl34
-rw-r--r--media/java/android/media/IVolumeController.aidl2
-rw-r--r--media/java/android/media/Image.java92
-rw-r--r--media/java/android/media/ImageReader.java172
-rw-r--r--media/java/android/media/ImageUtils.java120
-rw-r--r--media/java/android/media/ImageWriter.java798
-rw-r--r--media/java/android/media/MediaCodec.java1
-rw-r--r--media/java/android/media/MediaCodecInfo.java7
-rw-r--r--media/java/android/media/MediaCodecList.java2
-rw-r--r--media/java/android/media/MediaDescription.java11
-rw-r--r--media/java/android/media/MediaDrm.java37
-rw-r--r--media/java/android/media/MediaFormat.java23
-rw-r--r--media/java/android/media/MediaHTTPService.java2
-rw-r--r--media/java/android/media/MediaMetadata.java1
-rw-r--r--media/java/android/media/MediaPlayer.java74
-rw-r--r--media/java/android/media/MediaRecorder.java11
-rw-r--r--media/java/android/media/RemoteControlClient.java1
-rw-r--r--media/java/android/media/SoundPool.java591
-rw-r--r--media/java/android/media/TtmlRenderer.java12
-rw-r--r--media/java/android/media/Utils.java2
-rw-r--r--media/java/android/media/VolumePolicy.aidl19
-rw-r--r--media/java/android/media/VolumePolicy.java83
-rw-r--r--media/java/android/media/WebVttRenderer.java4
-rw-r--r--media/java/android/media/audiofx/AcousticEchoCanceler.java3
-rw-r--r--media/java/android/media/audiofx/AutomaticGainControl.java3
-rw-r--r--media/java/android/media/audiofx/NoiseSuppressor.java3
-rw-r--r--media/java/android/media/audiopolicy/AudioPolicyConfig.java4
-rw-r--r--media/java/android/media/midi/IMidiDeviceListener.aidl (renamed from media/java/android/media/midi/IMidiListener.aidl)4
-rw-r--r--media/java/android/media/midi/IMidiDeviceServer.aidl8
-rw-r--r--media/java/android/media/midi/IMidiManager.aidl27
-rw-r--r--media/java/android/media/midi/MidiDevice.java122
-rw-r--r--media/java/android/media/midi/MidiDeviceInfo.java165
-rw-r--r--media/java/android/media/midi/MidiDeviceServer.java385
-rw-r--r--media/java/android/media/midi/MidiDeviceService.java136
-rw-r--r--media/java/android/media/midi/MidiDeviceStatus.aidl19
-rw-r--r--media/java/android/media/midi/MidiDeviceStatus.java137
-rw-r--r--media/java/android/media/midi/MidiDispatcher.java84
-rw-r--r--media/java/android/media/midi/MidiInputPort.java132
-rw-r--r--media/java/android/media/midi/MidiManager.java137
-rw-r--r--media/java/android/media/midi/MidiOutputPort.java128
-rw-r--r--media/java/android/media/midi/MidiPortImpl.java (renamed from media/java/android/media/midi/MidiPort.java)54
-rw-r--r--media/java/android/media/midi/MidiReceiver.java60
-rw-r--r--media/java/android/media/midi/MidiSender.java9
-rw-r--r--media/java/android/media/projection/MediaProjection.java1
-rw-r--r--media/java/android/media/projection/MediaProjectionManager.java1
-rw-r--r--media/java/android/media/routing/MediaRouteSelector.java1
-rw-r--r--media/java/android/media/session/MediaSession.java1
-rw-r--r--media/java/android/media/session/MediaSessionLegacyHelper.java11
-rw-r--r--media/java/android/media/session/MediaSessionManager.java1
-rw-r--r--media/java/android/media/session/PlaybackState.java2
-rw-r--r--media/java/android/media/tv/TvContract.java88
-rw-r--r--media/java/android/media/tv/TvInputInfo.java33
-rw-r--r--media/java/android/media/tv/TvInputService.java44
-rw-r--r--media/java/android/media/tv/TvTrackInfo.java34
-rwxr-xr-xmedia/java/android/mtp/MtpDatabase.java1
-rw-r--r--media/java/android/service/media/MediaBrowserService.java1
-rw-r--r--media/jni/Android.mk1
-rw-r--r--media/jni/android_media_ImageReader.cpp86
-rw-r--r--media/jni/android_media_ImageWriter.cpp1014
-rw-r--r--media/jni/android_media_MediaCodec.cpp2
-rw-r--r--media/jni/android_media_MediaDrm.cpp67
-rw-r--r--media/jni/android_media_MediaMetadataRetriever.cpp8
-rw-r--r--media/jni/android_media_MediaPlayer.cpp20
-rw-r--r--media/jni/soundpool/Android.mk2
-rw-r--r--media/jni/soundpool/SoundPool.cpp93
-rw-r--r--media/jni/soundpool/SoundPool.h8
-rw-r--r--media/jni/soundpool/android_media_SoundPool.cpp (renamed from media/jni/soundpool/android_media_SoundPool_SoundPoolImpl.cpp)90
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java31
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java20
77 files changed, 4387 insertions, 1350 deletions
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 97919a9..4526839 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -27,7 +27,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.Objects;
import java.util.Set;
@@ -280,6 +279,7 @@ public final class AudioAttributes implements Parcelable {
* Internal use only
* @return a combined mask of all flags
*/
+ @SystemApi
public int getAllFlags() {
return (mFlags & FLAG_ALL);
}
@@ -542,14 +542,15 @@ public final class AudioAttributes implements Parcelable {
/**
* @hide
* Same as {@link #setCapturePreset(int)} but authorizes the use of HOTWORD,
- * REMOTE_SUBMIX and FM_TUNER.
+ * REMOTE_SUBMIX and RADIO_TUNER.
* @param preset
* @return the same Builder instance.
*/
+ @SystemApi
public Builder setInternalCapturePreset(int preset) {
if ((preset == MediaRecorder.AudioSource.HOTWORD)
|| (preset == MediaRecorder.AudioSource.REMOTE_SUBMIX)
- || (preset == MediaRecorder.AudioSource.FM_TUNER)) {
+ || (preset == MediaRecorder.AudioSource.RADIO_TUNER)) {
mSource = preset;
} else {
setCapturePreset(preset);
diff --git a/media/java/android/media/AudioDevicesManager.java b/media/java/android/media/AudioDevicesManager.java
index bce2100..ee11eef 100644
--- a/media/java/android/media/AudioDevicesManager.java
+++ b/media/java/android/media/AudioDevicesManager.java
@@ -22,7 +22,6 @@ import android.util.Slog;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
-import android.content.Context;
/** @hide
* API candidate
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 9876995..28941b9 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -26,9 +26,7 @@ import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.media.RemoteController.OnClientUpdateListener;
import android.media.audiopolicy.AudioPolicy;
-import android.media.audiopolicy.AudioPolicyConfig;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.MediaSessionLegacyHelper;
@@ -58,12 +56,10 @@ import java.util.Iterator;
*/
public class AudioManager {
- private final Context mContext;
+ private final Context mApplicationContext;
private long mVolumeKeyUpTime;
- private final boolean mUseMasterVolume;
private final boolean mUseVolumeKeySounds;
private final boolean mUseFixedVolume;
- private final Binder mToken = new Binder();
private static String TAG = "AudioManager";
private static final AudioPortEventHandler sAudioPortEventHandler = new AudioPortEventHandler();
@@ -149,17 +145,6 @@ public class AudioManager {
"android.media.STREAM_MUTE_CHANGED_ACTION";
/**
- * @hide Broadcast intent when the master volume changes.
- * Includes the new volume
- *
- * @see #EXTRA_MASTER_VOLUME_VALUE
- * @see #EXTRA_PREV_MASTER_VOLUME_VALUE
- */
- @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String MASTER_VOLUME_CHANGED_ACTION =
- "android.media.MASTER_VOLUME_CHANGED_ACTION";
-
- /**
* @hide Broadcast intent when the master mute state changes.
* Includes the the new volume
*
@@ -211,20 +196,6 @@ public class AudioManager {
"android.media.EXTRA_PREV_VOLUME_STREAM_VALUE";
/**
- * @hide The new master volume value for the master volume changed intent.
- * Value is integer between 0 and 100 inclusive.
- */
- public static final String EXTRA_MASTER_VOLUME_VALUE =
- "android.media.EXTRA_MASTER_VOLUME_VALUE";
-
- /**
- * @hide The previous master volume value for the master volume changed intent.
- * Value is integer between 0 and 100 inclusive.
- */
- public static final String EXTRA_PREV_MASTER_VOLUME_VALUE =
- "android.media.EXTRA_PREV_MASTER_VOLUME_VALUE";
-
- /**
* @hide The new master volume mute state for the master mute changed intent.
* Value is boolean
*/
@@ -259,7 +230,7 @@ public class AudioManager {
"android.intent.action.HEADSET_PLUG";
/**
- * Broadcast Action: A sticky broadcast indicating an HMDI cable was plugged or unplugged
+ * Broadcast Action: A sticky broadcast indicating an HDMI cable was plugged or unplugged.
*
* The intent will have the following extra values: {@link #EXTRA_AUDIO_PLUG_STATE},
* {@link #EXTRA_MAX_CHANNEL_COUNT}, {@link #EXTRA_ENCODINGS}.
@@ -467,6 +438,12 @@ public class AudioManager {
*/
public static final int FLAG_SHOW_VIBRATE_HINT = 1 << 11;
+ /**
+ * Adjusting the volume due to a hardware key press.
+ * @hide
+ */
+ public static final int FLAG_FROM_KEY = 1 << 12;
+
private static final String[] FLAG_NAMES = {
"FLAG_SHOW_UI",
"FLAG_ALLOW_RINGER_MODES",
@@ -480,6 +457,7 @@ public class AudioManager {
"FLAG_ACTIVE_MEDIA_ONLY",
"FLAG_SHOW_UI_WARNINGS",
"FLAG_SHOW_VIBRATE_HINT",
+ "FLAG_FROM_KEY",
};
/** @hide */
@@ -604,12 +582,10 @@ public class AudioManager {
* @hide
*/
public AudioManager(Context context) {
- mContext = context;
- mUseMasterVolume = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_useMasterVolume);
- mUseVolumeKeySounds = mContext.getResources().getBoolean(
+ mApplicationContext = context;
+ mUseVolumeKeySounds = mApplicationContext.getResources().getBoolean(
com.android.internal.R.bool.config_useVolumeKeySounds);
- mUseFixedVolume = mContext.getResources().getBoolean(
+ mUseFixedVolume = mApplicationContext.getResources().getBoolean(
com.android.internal.R.bool.config_useFixedVolume);
sAudioPortEventHandler.init();
}
@@ -648,7 +624,7 @@ public class AudioManager {
* or {@link KeyEvent#KEYCODE_MEDIA_AUDIO_TRACK}.
*/
public void dispatchMediaKeyEvent(KeyEvent keyEvent) {
- MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
+ MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mApplicationContext);
helper.sendMediaButtonEvent(keyEvent, false);
}
@@ -668,12 +644,8 @@ public class AudioManager {
* The user has hit another key during the delay (e.g., 300ms)
* since the last volume key up, so cancel any sounds.
*/
- if (mUseMasterVolume) {
- adjustMasterVolume(ADJUST_SAME, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
- } else {
- adjustSuggestedStreamVolume(ADJUST_SAME,
- stream, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
- }
+ adjustSuggestedStreamVolume(ADJUST_SAME,
+ stream, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
}
}
@@ -689,26 +661,17 @@ public class AudioManager {
* Adjust the volume in on key down since it is more
* responsive to the user.
*/
- int flags = FLAG_SHOW_UI | FLAG_VIBRATE;
-
- if (mUseMasterVolume) {
- adjustMasterVolume(
- keyCode == KeyEvent.KEYCODE_VOLUME_UP
- ? ADJUST_RAISE
- : ADJUST_LOWER,
- flags);
- } else {
- adjustSuggestedStreamVolume(
- keyCode == KeyEvent.KEYCODE_VOLUME_UP
- ? ADJUST_RAISE
- : ADJUST_LOWER,
- stream,
- flags);
- }
+ adjustSuggestedStreamVolume(
+ keyCode == KeyEvent.KEYCODE_VOLUME_UP
+ ? ADJUST_RAISE
+ : ADJUST_LOWER,
+ stream,
+ FLAG_SHOW_UI | FLAG_VIBRATE);
break;
case KeyEvent.KEYCODE_VOLUME_MUTE:
if (event.getRepeatCount() == 0) {
- MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(event, false);
+ MediaSessionLegacyHelper.getHelper(mApplicationContext)
+ .sendVolumeKeyEvent(event, false);
}
break;
}
@@ -727,20 +690,16 @@ public class AudioManager {
* sound to play when a user holds down volume down to mute.
*/
if (mUseVolumeKeySounds) {
- if (mUseMasterVolume) {
- adjustMasterVolume(ADJUST_SAME, FLAG_PLAY_SOUND);
- } else {
- int flags = FLAG_PLAY_SOUND;
- adjustSuggestedStreamVolume(
- ADJUST_SAME,
- stream,
- flags);
- }
+ adjustSuggestedStreamVolume(
+ ADJUST_SAME,
+ stream,
+ FLAG_PLAY_SOUND);
}
mVolumeKeyUpTime = SystemClock.uptimeMillis();
break;
case KeyEvent.KEYCODE_VOLUME_MUTE:
- MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(event, false);
+ MediaSessionLegacyHelper.getHelper(mApplicationContext)
+ .sendVolumeKeyEvent(event, false);
break;
}
}
@@ -784,12 +743,8 @@ public class AudioManager {
public void adjustStreamVolume(int streamType, int direction, int flags) {
IAudioService service = getService();
try {
- if (mUseMasterVolume) {
- service.adjustMasterVolume(direction, flags, mContext.getOpPackageName());
- } else {
- service.adjustStreamVolume(streamType, direction, flags,
- mContext.getOpPackageName());
- }
+ service.adjustStreamVolume(streamType, direction, flags,
+ mApplicationContext.getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Dead object in adjustStreamVolume", e);
}
@@ -819,17 +774,8 @@ public class AudioManager {
* @see #isVolumeFixed()
*/
public void adjustVolume(int direction, int flags) {
- IAudioService service = getService();
- try {
- if (mUseMasterVolume) {
- service.adjustMasterVolume(direction, flags, mContext.getOpPackageName());
- } else {
- MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
- helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, flags);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in adjustVolume", e);
- }
+ MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mApplicationContext);
+ helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, flags);
}
/**
@@ -857,34 +803,17 @@ public class AudioManager {
* @see #isVolumeFixed()
*/
public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags) {
- IAudioService service = getService();
- try {
- if (mUseMasterVolume) {
- service.adjustMasterVolume(direction, flags, mContext.getOpPackageName());
- } else {
- MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
- helper.sendAdjustVolumeBy(suggestedStreamType, direction, flags);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in adjustSuggestedStreamVolume", e);
- }
+ MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mApplicationContext);
+ helper.sendAdjustVolumeBy(suggestedStreamType, direction, flags);
}
- /**
- * Adjusts the master volume for the device's audio amplifier.
- * <p>
- *
- * @param steps The number of volume steps to adjust. A positive
- * value will raise the volume.
- * @param flags One or more flags.
- * @hide
- */
- public void adjustMasterVolume(int steps, int flags) {
+ /** @hide */
+ public void setMasterMute(boolean mute, int flags) {
IAudioService service = getService();
try {
- service.adjustMasterVolume(steps, flags, mContext.getOpPackageName());
+ service.setMasterMute(mute, flags, mApplicationContext.getOpPackageName());
} catch (RemoteException e) {
- Log.e(TAG, "Dead object in adjustMasterVolume", e);
+ Log.e(TAG, "Dead object in setMasterMute", e);
}
}
@@ -936,11 +865,7 @@ public class AudioManager {
public int getStreamMaxVolume(int streamType) {
IAudioService service = getService();
try {
- if (mUseMasterVolume) {
- return service.getMasterMaxVolume();
- } else {
- return service.getStreamMaxVolume(streamType);
- }
+ return service.getStreamMaxVolume(streamType);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in getStreamMaxVolume", e);
return 0;
@@ -948,6 +873,24 @@ public class AudioManager {
}
/**
+ * Returns the minimum volume index for a particular stream.
+ *
+ * @param streamType The stream type whose minimum volume index is returned.
+ * @return The minimum valid volume index for the stream.
+ * @see #getStreamVolume(int)
+ * @hide
+ */
+ public int getStreamMinVolume(int streamType) {
+ IAudioService service = getService();
+ try {
+ return service.getStreamMinVolume(streamType);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in getStreamMinVolume", e);
+ return 0;
+ }
+ }
+
+ /**
* Returns the current volume index for a particular stream.
*
* @param streamType The stream type whose volume index is returned.
@@ -958,11 +901,7 @@ public class AudioManager {
public int getStreamVolume(int streamType) {
IAudioService service = getService();
try {
- if (mUseMasterVolume) {
- return service.getMasterVolume();
- } else {
- return service.getStreamVolume(streamType);
- }
+ return service.getStreamVolume(streamType);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in getStreamVolume", e);
return 0;
@@ -977,11 +916,7 @@ public class AudioManager {
public int getLastAudibleStreamVolume(int streamType) {
IAudioService service = getService();
try {
- if (mUseMasterVolume) {
- return service.getLastAudibleMasterVolume();
- } else {
- return service.getLastAudibleStreamVolume(streamType);
- }
+ return service.getLastAudibleStreamVolume(streamType);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in getLastAudibleStreamVolume", e);
return 0;
@@ -994,12 +929,12 @@ public class AudioManager {
* It is assumed that this stream type is also tied to ringer mode changes.
* @hide
*/
- public int getMasterStreamType() {
+ public int getUiSoundsStreamType() {
IAudioService service = getService();
try {
- return service.getMasterStreamType();
+ return service.getUiSoundsStreamType();
} catch (RemoteException e) {
- Log.e(TAG, "Dead object in getMasterStreamType", e);
+ Log.e(TAG, "Dead object in getUiSoundsStreamType", e);
return STREAM_RING;
}
}
@@ -1023,7 +958,7 @@ public class AudioManager {
}
IAudioService service = getService();
try {
- service.setRingerModeExternal(ringerMode, mContext.getOpPackageName());
+ service.setRingerModeExternal(ringerMode, mApplicationContext.getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Dead object in setRingerMode", e);
}
@@ -1044,82 +979,13 @@ public class AudioManager {
public void setStreamVolume(int streamType, int index, int flags) {
IAudioService service = getService();
try {
- if (mUseMasterVolume) {
- service.setMasterVolume(index, flags, mContext.getOpPackageName());
- } else {
- service.setStreamVolume(streamType, index, flags, mContext.getOpPackageName());
- }
+ service.setStreamVolume(streamType, index, flags, mApplicationContext.getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Dead object in setStreamVolume", e);
}
}
/**
- * Returns the maximum volume index for master volume.
- *
- * @hide
- */
- public int getMasterMaxVolume() {
- IAudioService service = getService();
- try {
- return service.getMasterMaxVolume();
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in getMasterMaxVolume", e);
- return 0;
- }
- }
-
- /**
- * Returns the current volume index for master volume.
- *
- * @return The current volume index for master volume.
- * @hide
- */
- public int getMasterVolume() {
- IAudioService service = getService();
- try {
- return service.getMasterVolume();
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in getMasterVolume", e);
- return 0;
- }
- }
-
- /**
- * Get last audible volume before master volume was muted.
- *
- * @hide
- */
- public int getLastAudibleMasterVolume() {
- IAudioService service = getService();
- try {
- return service.getLastAudibleMasterVolume();
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in getLastAudibleMasterVolume", e);
- return 0;
- }
- }
-
- /**
- * Sets the volume index for master volume.
- *
- * @param index The volume index to set. See
- * {@link #getMasterMaxVolume()} for the largest valid value.
- * @param flags One or more flags.
- * @see #getMasterMaxVolume()
- * @see #getMasterVolume()
- * @hide
- */
- public void setMasterVolume(int index, int flags) {
- IAudioService service = getService();
- try {
- service.setMasterVolume(index, flags, mContext.getOpPackageName());
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in setMasterVolume", e);
- }
- }
-
- /**
* Solo or unsolo a particular stream.
* <p>
* Do not use. This method has been deprecated and is now a no-op.
@@ -1190,11 +1056,7 @@ public class AudioManager {
public boolean isStreamMute(int streamType) {
IAudioService service = getService();
try {
- if (mUseMasterVolume) {
- return service.isMasterMute();
- } else {
- return service.isStreamMute(streamType);
- }
+ return service.isStreamMute(streamType);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in isStreamMute", e);
return false;
@@ -1224,9 +1086,6 @@ public class AudioManager {
* @hide
*/
public void forceVolumeControlStream(int streamType) {
- if (mUseMasterVolume) {
- return;
- }
IAudioService service = getService();
try {
service.forceVolumeControlStream(streamType, mICallBack);
@@ -1433,7 +1292,7 @@ public class AudioManager {
* @see #startBluetoothSco()
*/
public boolean isBluetoothScoAvailableOffCall() {
- return mContext.getResources().getBoolean(
+ return mApplicationContext.getResources().getBoolean(
com.android.internal.R.bool.config_bluetooth_sco_off_call);
}
@@ -1485,7 +1344,8 @@ public class AudioManager {
public void startBluetoothSco(){
IAudioService service = getService();
try {
- service.startBluetoothSco(mICallBack, mContext.getApplicationInfo().targetSdkVersion);
+ service.startBluetoothSco(mICallBack,
+ mApplicationContext.getApplicationInfo().targetSdkVersion);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in startBluetoothSco", e);
}
@@ -1633,7 +1493,7 @@ public class AudioManager {
public void setMicrophoneMute(boolean on){
IAudioService service = getService();
try {
- service.setMicrophoneMute(on, mContext.getOpPackageName());
+ service.setMicrophoneMute(on, mApplicationContext.getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Dead object in setMicrophoneMute", e);
}
@@ -1666,7 +1526,7 @@ public class AudioManager {
public void setMode(int mode) {
IAudioService service = getService();
try {
- service.setMode(mode, mICallBack);
+ service.setMode(mode, mICallBack, mApplicationContext.getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Dead object in setMode", e);
}
@@ -2064,7 +1924,7 @@ public class AudioManager {
* Settings has an in memory cache, so this is fast.
*/
private boolean querySoundEffectsEnabled(int user) {
- return Settings.System.getIntForUser(mContext.getContentResolver(),
+ return Settings.System.getIntForUser(mApplicationContext.getContentResolver(),
Settings.System.SOUND_EFFECTS_ENABLED, 0, user) != 0;
}
@@ -2476,7 +2336,7 @@ public class AudioManager {
try {
status = service.requestAudioFocus(requestAttributes, durationHint, mICallBack,
mAudioFocusDispatcher, getIdForAudioFocusListener(l),
- mContext.getOpPackageName() /* package name */, flags,
+ mApplicationContext.getOpPackageName() /* package name */, flags,
ap != null ? ap.cb() : null);
} catch (RemoteException e) {
Log.e(TAG, "Can't call requestAudioFocus() on AudioService:", e);
@@ -2501,7 +2361,7 @@ public class AudioManager {
.setInternalLegacyStreamType(streamType).build(),
durationHint, mICallBack, null,
AudioSystem.IN_VOICE_COMM_FOCUS_ID,
- mContext.getOpPackageName(),
+ mApplicationContext.getOpPackageName(),
AUDIOFOCUS_FLAG_LOCK,
null /* policy token */);
} catch (RemoteException e) {
@@ -2570,7 +2430,7 @@ public class AudioManager {
if (eventReceiver == null) {
return;
}
- if (!eventReceiver.getPackageName().equals(mContext.getPackageName())) {
+ if (!eventReceiver.getPackageName().equals(mApplicationContext.getPackageName())) {
Log.e(TAG, "registerMediaButtonEventReceiver() error: " +
"receiver and context package names don't match");
return;
@@ -2579,7 +2439,7 @@ public class AudioManager {
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
// the associated intent will be handled by the component being registered
mediaButtonIntent.setComponent(eventReceiver);
- PendingIntent pi = PendingIntent.getBroadcast(mContext,
+ PendingIntent pi = PendingIntent.getBroadcast(mApplicationContext,
0/*requestCode, ignored*/, mediaButtonIntent, 0/*flags*/);
registerMediaButtonIntent(pi, eventReceiver);
}
@@ -2613,8 +2473,8 @@ public class AudioManager {
Log.e(TAG, "Cannot call registerMediaButtonIntent() with a null parameter");
return;
}
- MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
- helper.addMediaButtonListener(pi, eventReceiver, mContext);
+ MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mApplicationContext);
+ helper.addMediaButtonListener(pi, eventReceiver, mApplicationContext);
}
/**
@@ -2632,7 +2492,7 @@ public class AudioManager {
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
// the associated intent will be handled by the component being registered
mediaButtonIntent.setComponent(eventReceiver);
- PendingIntent pi = PendingIntent.getBroadcast(mContext,
+ PendingIntent pi = PendingIntent.getBroadcast(mApplicationContext,
0/*requestCode, ignored*/, mediaButtonIntent, 0/*flags*/);
unregisterMediaButtonIntent(pi);
}
@@ -2655,7 +2515,7 @@ public class AudioManager {
* @hide
*/
public void unregisterMediaButtonIntent(PendingIntent pi) {
- MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
+ MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mApplicationContext);
helper.removeMediaButtonListener(pi);
}
@@ -2672,7 +2532,7 @@ public class AudioManager {
if ((rcClient == null) || (rcClient.getRcMediaIntent() == null)) {
return;
}
- rcClient.registerWithSession(MediaSessionLegacyHelper.getHelper(mContext));
+ rcClient.registerWithSession(MediaSessionLegacyHelper.getHelper(mApplicationContext));
}
/**
@@ -2687,7 +2547,7 @@ public class AudioManager {
if ((rcClient == null) || (rcClient.getRcMediaIntent() == null)) {
return;
}
- rcClient.unregisterWithSession(MediaSessionLegacyHelper.getHelper(mContext));
+ rcClient.unregisterWithSession(MediaSessionLegacyHelper.getHelper(mApplicationContext));
}
/**
@@ -2695,7 +2555,7 @@ public class AudioManager {
* metadata updates and playback state information from applications using
* {@link RemoteControlClient}, and control their playback.
* <p>
- * Registration requires the {@link OnClientUpdateListener} listener to be
+ * Registration requires the {@link RemoteController.OnClientUpdateListener} listener to be
* one of the enabled notification listeners (see
* {@link android.service.notification.NotificationListenerService}).
*
@@ -3205,7 +3065,8 @@ public class AudioManager {
public void setWiredDeviceConnectionState(int type, int state, String address, String name) {
IAudioService service = getService();
try {
- service.setWiredDeviceConnectionState(type, state, address, name);
+ service.setWiredDeviceConnectionState(type, state, address, name,
+ mApplicationContext.getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Dead object in setWiredDeviceConnectionState "+e);
}
@@ -3230,9 +3091,8 @@ public class AudioManager {
delay = service.setBluetoothA2dpDeviceConnectionState(device, state, profile);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in setBluetoothA2dpDeviceConnectionState "+e);
- } finally {
- return delay;
}
+ return delay;
}
/** {@hide} */
@@ -3349,7 +3209,7 @@ public class AudioManager {
*/
public void disableSafeMediaVolume() {
try {
- getService().disableSafeMediaVolume();
+ getService().disableSafeMediaVolume(mApplicationContext.getOpPackageName());
} catch (RemoteException e) {
Log.w(TAG, "Error disabling safe media volume", e);
}
@@ -3361,7 +3221,7 @@ public class AudioManager {
*/
public void setRingerModeInternal(int ringerMode) {
try {
- getService().setRingerModeInternal(ringerMode, mContext.getOpPackageName());
+ getService().setRingerModeInternal(ringerMode, mApplicationContext.getOpPackageName());
} catch (RemoteException e) {
Log.w(TAG, "Error calling setRingerModeInternal", e);
}
@@ -3381,6 +3241,18 @@ public class AudioManager {
}
/**
+ * Only useful for volume controllers.
+ * @hide
+ */
+ public void setVolumePolicy(VolumePolicy policy) {
+ try {
+ getService().setVolumePolicy(policy);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error calling setVolumePolicy", e);
+ }
+ }
+
+ /**
* Set Hdmi Cec system audio mode.
*
* @param on whether to be on system audio mode
diff --git a/media/java/android/media/AudioManagerInternal.java b/media/java/android/media/AudioManagerInternal.java
index ef5710c..abb4257 100644
--- a/media/java/android/media/AudioManagerInternal.java
+++ b/media/java/android/media/AudioManagerInternal.java
@@ -27,8 +27,7 @@ import com.android.server.LocalServices;
public abstract class AudioManagerInternal {
public abstract void adjustSuggestedStreamVolumeForUid(int streamType, int direction,
- int flags,
- String callingPackage, int uid);
+ int flags, String callingPackage, int uid);
public abstract void adjustStreamVolumeForUid(int streamType, int direction, int flags,
String callingPackage, int uid);
@@ -36,9 +35,6 @@ public abstract class AudioManagerInternal {
public abstract void setStreamVolumeForUid(int streamType, int direction, int flags,
String callingPackage, int uid);
- public abstract void adjustMasterVolumeForUid(int steps, int flags, String callingPackage,
- int uid);
-
public abstract void setRingerModeDelegate(RingerModeDelegate delegate);
public abstract int getRingerModeInternal();
@@ -50,10 +46,10 @@ public abstract class AudioManagerInternal {
public interface RingerModeDelegate {
/** Called when external ringer mode is evaluated, returns the new internal ringer mode */
int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller,
- int ringerModeInternal);
+ int ringerModeInternal, VolumePolicy policy);
/** Called when internal ringer mode is evaluated, returns the new external ringer mode */
int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew, String caller,
- int ringerModeExternal);
+ int ringerModeExternal, VolumePolicy policy);
}
}
diff --git a/media/java/android/media/AudioPort.java b/media/java/android/media/AudioPort.java
index b046791..88e784a 100644
--- a/media/java/android/media/AudioPort.java
+++ b/media/java/android/media/AudioPort.java
@@ -15,7 +15,6 @@
*/
package android.media;
-import android.util.Slog;
/**
* An audio port is a node of the audio framework or hardware that can be connected to or
diff --git a/media/java/android/media/AudioPortEventHandler.java b/media/java/android/media/AudioPortEventHandler.java
index ba2a59d..c05fd77 100644
--- a/media/java/android/media/AudioPortEventHandler.java
+++ b/media/java/android/media/AudioPortEventHandler.java
@@ -19,8 +19,6 @@ package android.media;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.util.Log;
-
import java.util.ArrayList;
import java.lang.ref.WeakReference;
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index de10ef9..259fe37 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -20,6 +20,7 @@ import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.util.Iterator;
+import android.annotation.SystemApi;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -238,7 +239,6 @@ public class AudioRecord
/**
* @hide
- * CANDIDATE FOR PUBLIC API
* Class constructor with {@link AudioAttributes} and {@link AudioFormat}.
* @param attributes a non-null {@link AudioAttributes} instance. Use
* {@link AudioAttributes.Builder#setCapturePreset(int)} for configuring the capture
@@ -257,6 +257,7 @@ public class AudioRecord
* construction.
* @throws IllegalArgumentException
*/
+ @SystemApi
public AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
int sessionId) throws IllegalArgumentException {
mRecordingState = RECORDSTATE_STOPPED;
@@ -376,7 +377,7 @@ public class AudioRecord
// audio source
if ( (audioSource < MediaRecorder.AudioSource.DEFAULT) ||
((audioSource > MediaRecorder.getAudioSourceMax()) &&
- (audioSource != MediaRecorder.AudioSource.FM_TUNER) &&
+ (audioSource != MediaRecorder.AudioSource.RADIO_TUNER) &&
(audioSource != MediaRecorder.AudioSource.HOTWORD)) ) {
throw new IllegalArgumentException("Invalid audio source.");
}
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index cd78234..93e2cbe 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -21,9 +21,6 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.nio.NioUtils;
-import java.util.Iterator;
-import java.util.Set;
-
import android.annotation.IntDef;
import android.app.ActivityThread;
import android.app.AppOpsManager;
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 17f5b59..8e96218 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -29,6 +29,7 @@ import android.media.IRemoteVolumeObserver;
import android.media.IRingtonePlayer;
import android.media.IVolumeController;
import android.media.Rating;
+import android.media.VolumePolicy;
import android.media.audiopolicy.AudioPolicyConfig;
import android.media.audiopolicy.IAudioPolicyCallback;
import android.net.Uri;
@@ -40,36 +41,30 @@ import android.view.KeyEvent;
interface IAudioService {
void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
- String callingPackage);
+ String callingPackage, String caller);
void adjustStreamVolume(int streamType, int direction, int flags, String callingPackage);
- void adjustMasterVolume(int direction, int flags, String callingPackage);
-
void setStreamVolume(int streamType, int index, int flags, String callingPackage);
oneway void setRemoteStreamVolume(int index);
- void setMasterVolume(int index, int flags, String callingPackage);
-
boolean isStreamMute(int streamType);
void forceRemoteSubmixFullVolume(boolean startForcing, IBinder cb);
boolean isMasterMute();
+ void setMasterMute(boolean mute, int flags, String callingPackage);
+
int getStreamVolume(int streamType);
- int getMasterVolume();
+ int getStreamMinVolume(int streamType);
int getStreamMaxVolume(int streamType);
- int getMasterMaxVolume();
-
int getLastAudibleStreamVolume(int streamType);
- int getLastAudibleMasterVolume();
-
void setMicrophoneMute(boolean on, String callingPackage);
void setRingerModeExternal(int ringerMode, String caller);
@@ -88,7 +83,7 @@ interface IAudioService {
boolean shouldVibrate(int vibrateType);
- void setMode(int mode, IBinder cb);
+ void setMode(int mode, IBinder cb, String callingPackage);
int getMode();
@@ -187,9 +182,11 @@ interface IAudioService {
void setRingtonePlayer(IRingtonePlayer player);
IRingtonePlayer getRingtonePlayer();
- int getMasterStreamType();
+ int getUiSoundsStreamType();
+
+ void setWiredDeviceConnectionState(int type, int state, String address, String name,
+ String caller);
- void setWiredDeviceConnectionState(int type, int state, String address, String name);
int setBluetoothA2dpDeviceConnectionState(in BluetoothDevice device, int state, int profile);
AudioRoutesInfo startWatchingRoutes(in IAudioRoutesObserver observer);
@@ -204,15 +201,18 @@ interface IAudioService {
boolean isStreamAffectedByMute(int streamType);
- void disableSafeMediaVolume();
+ void disableSafeMediaVolume(String callingPackage);
int setHdmiSystemAudioSupported(boolean on);
boolean isHdmiSystemAudioSupported();
- String registerAudioPolicy(in AudioPolicyConfig policyConfig,
- in IAudioPolicyCallback pcb, boolean hasFocusListener);
+ String registerAudioPolicy(in AudioPolicyConfig policyConfig,
+ in IAudioPolicyCallback pcb, boolean hasFocusListener);
+
oneway void unregisterAudioPolicyAsync(in IAudioPolicyCallback pcb);
- int setFocusPropertiesForPolicy(int duckingBehavior, in IAudioPolicyCallback pcb);
+ int setFocusPropertiesForPolicy(int duckingBehavior, in IAudioPolicyCallback pcb);
+
+ void setVolumePolicy(in VolumePolicy policy);
}
diff --git a/media/java/android/media/IVolumeController.aidl b/media/java/android/media/IVolumeController.aidl
index e3593a6..90ac416 100644
--- a/media/java/android/media/IVolumeController.aidl
+++ b/media/java/android/media/IVolumeController.aidl
@@ -27,8 +27,6 @@ oneway interface IVolumeController {
void volumeChanged(int streamType, int flags);
- void masterVolumeChanged(int flags);
-
void masterMuteChanged(int flags);
void setLayoutDirection(int layoutDirection);
diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java
index 53ab264..9d07492 100644
--- a/media/java/android/media/Image.java
+++ b/media/java/android/media/Image.java
@@ -115,14 +115,49 @@ public abstract class Image implements AutoCloseable {
/**
* Get the timestamp associated with this frame.
* <p>
- * The timestamp is measured in nanoseconds, and is monotonically
- * increasing. However, the zero point and whether the timestamp can be
- * compared against other sources of time or images depend on the source of
- * this image.
+ * The timestamp is measured in nanoseconds, and is normally monotonically
+ * increasing. However, the behavior of the timestamp depends on the source
+ * of this image. See {@link android.hardware.Camera Camera},
+ * {@link android.hardware.camera2.CameraDevice CameraDevice}, {@link MediaPlayer} and
+ * {@link MediaCodec} for more details.
* </p>
*/
public abstract long getTimestamp();
+ /**
+ * Set the timestamp associated with this frame.
+ * <p>
+ * The timestamp is measured in nanoseconds, and is normally monotonically
+ * increasing. However, However, the behavior of the timestamp depends on
+ * the destination of this image. See {@link android.hardware.Camera Camera}
+ * , {@link android.hardware.camera2.CameraDevice CameraDevice},
+ * {@link MediaPlayer} and {@link MediaCodec} for more details.
+ * </p>
+ * <p>
+ * For images dequeued from {@link ImageWriter} via
+ * {@link ImageWriter#dequeueInputImage()}, it's up to the application to
+ * set the timestamps correctly before sending them back to the
+ * {@link ImageWriter}, or the timestamp will be generated automatically when
+ * {@link ImageWriter#queueInputImage queueInputImage()} is called.
+ * </p>
+ *
+ * @param timestamp The timestamp to be set for this image.
+ */
+ public void setTimestamp(long timestamp) {
+ return;
+ }
+
+ /**
+ * <p>Check if the image is opaque.</p>
+ *
+ * <p>The pixel data of opaque images are not accessible to the application,
+ * and therefore {@link #getPlanes} will return an empty array for an opaque image.
+ * </p>
+ */
+ public boolean isOpaque() {
+ return false;
+ }
+
private Rect mCropRect;
/**
@@ -155,7 +190,10 @@ public abstract class Image implements AutoCloseable {
/**
* Get the array of pixel planes for this Image. The number of planes is
- * determined by the format of the Image.
+ * determined by the format of the Image. The application will get an
+ * empty array if the image is opaque because the opaque image pixel data
+ * is not directly accessible. The application can check if an image is
+ * opaque by calling {@link Image#isOpaque}.
*/
public abstract Plane[] getPlanes();
@@ -164,14 +202,54 @@ public abstract class Image implements AutoCloseable {
* <p>
* After calling this method, calling any methods on this {@code Image} will
* result in an {@link IllegalStateException}, and attempting to read from
- * {@link ByteBuffer ByteBuffers} returned by an earlier
- * {@link Plane#getBuffer} call will have undefined behavior.
+ * or write to {@link ByteBuffer ByteBuffers} returned by an earlier
+ * {@link Plane#getBuffer} call will have undefined behavior. If the image
+ * was obtained from {@link ImageWriter} via
+ * {@link ImageWriter#dequeueInputImage()}, after calling this method, any
+ * image data filled by the application will be lost and the image will be
+ * returned to {@link ImageWriter} for reuse. Images given to
+ * {@link ImageWriter#queueInputImage queueInputImage()} are automatically
+ * closed.
* </p>
*/
@Override
public abstract void close();
/**
+ * <p>
+ * Check if the image can be attached to a new owner (e.g. {@link ImageWriter}).
+ * </p>
+ * <p>
+ * This is a package private method that is only used internally.
+ * </p>
+ *
+ * @return true if the image is attachable to a new owner, false if the image is still attached
+ * to its current owner, or the image is a stand-alone image and is not attachable to
+ * a new owner.
+ */
+ boolean isAttachable() {
+ return false;
+ }
+
+ /**
+ * <p>
+ * Get the owner of the {@link Image}.
+ * </p>
+ * <p>
+ * The owner of an {@link Image} could be {@link ImageReader}, {@link ImageWriter},
+ * {@link MediaCodec} etc. This method returns the owner that produces this image, or null
+ * if the image is stand-alone image or the owner is unknown.
+ * </p>
+ * <p>
+ * This is a package private method that is only used internally.
+ * </p>
+ *
+ * @return The owner of the Image.
+ */
+ Object getOwner() {
+ return null;
+ }
+ /**
* <p>A single color plane of image data.</p>
*
* <p>The number and meaning of the planes in an Image are determined by the
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index 8d6a588..b2f7a20 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -27,6 +27,7 @@ import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.NioUtils;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* <p>The ImageReader class allows direct application access to image data
@@ -34,7 +35,7 @@ import java.nio.NioUtils;
*
* <p>Several Android media API classes accept Surface objects as targets to
* render to, including {@link MediaPlayer}, {@link MediaCodec},
- * {@link android.hardware.camera2.CameraDevice}, and
+ * {@link android.hardware.camera2.CameraDevice}, {@link ImageWriter} 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>
@@ -97,10 +98,60 @@ public class ImageReader implements AutoCloseable {
* @see Image
*/
public static ImageReader newInstance(int width, int height, int format, int maxImages) {
+ if (format == PixelFormat.OPAQUE) {
+ throw new IllegalArgumentException("To obtain an opaque ImageReader, please use"
+ + " newOpaqueInstance rather than newInstance");
+ }
return new ImageReader(width, height, format, maxImages);
}
/**
+ * <p>
+ * Create a new opaque reader for images of the desired size.
+ * </p>
+ * <p>
+ * An opaque {@link ImageReader} produces images that are not directly
+ * accessible by the application. The application can still acquire images
+ * from an opaque image reader, and send them to the
+ * {@link android.hardware.camera2.CameraDevice camera} for reprocessing via
+ * {@link ImageWriter} interface. However, the {@link Image#getPlanes()
+ * getPlanes()} will return an empty array for opaque images. The
+ * application can check if an existing reader is an opaque reader by
+ * calling {@link #isOpaque()}.
+ * </p>
+ * <p>
+ * The {@code maxImages} parameter determines the maximum number of
+ * {@link Image} objects that can be be acquired from the
+ * {@code ImageReader} simultaneously. Requesting more buffers will use up
+ * more memory, so it is important to use only the minimum number necessary.
+ * </p>
+ * <p>
+ * The valid sizes and formats depend on the source of the image data.
+ * </p>
+ * <p>
+ * Opaque ImageReaders are more efficient to use when application access to
+ * image data is not necessary, comparing to ImageReaders using a non-opaque
+ * format such as {@link ImageFormat#YUV_420_888 YUV_420_888}.
+ * </p>
+ *
+ * @param width The default width in pixels of the Images that this reader
+ * will produce.
+ * @param height The default height in pixels of the Images that this reader
+ * will produce.
+ * @param maxImages The maximum number of images the user will want to
+ * access simultaneously. This should be as small as possible to
+ * limit memory use. Once maxImages Images are obtained by the
+ * user, one of them has to be released before a new Image will
+ * become available for access through
+ * {@link #acquireLatestImage()} or {@link #acquireNextImage()}.
+ * Must be greater than 0.
+ * @see Image
+ */
+ public static ImageReader newOpaqueInstance(int width, int height, int maxImages) {
+ return new ImageReader(width, height, PixelFormat.OPAQUE, maxImages);
+ }
+
+ /**
* @hide
*/
protected ImageReader(int width, int height, int format, int maxImages) {
@@ -197,6 +248,23 @@ public class ImageReader implements AutoCloseable {
}
/**
+ * <p>
+ * Check if the {@link ImageReader} is an opaque reader.
+ * </p>
+ * <p>
+ * An opaque image reader produces opaque images, see {@link Image#isOpaque}
+ * for more details.
+ * </p>
+ *
+ * @return true if the ImageReader is opaque.
+ * @see Image#isOpaque
+ * @see ImageReader#newOpaqueInstance
+ */
+ public boolean isOpaque() {
+ return mFormat == PixelFormat.OPAQUE;
+ }
+
+ /**
* <p>Get a {@link Surface} that can be used to produce {@link Image Images} for this
* {@code ImageReader}.</p>
*
@@ -443,6 +511,7 @@ public class ImageReader implements AutoCloseable {
@Override
public void close() {
setOnImageAvailableListener(null, null);
+ if (mSurface != null) mSurface.release();
nativeClose();
}
@@ -456,6 +525,58 @@ public class ImageReader implements AutoCloseable {
}
/**
+ * <p>
+ * Remove the ownership of this image from the ImageReader.
+ * </p>
+ * <p>
+ * After this call, the ImageReader no longer owns this image, and the image
+ * ownership can be transfered to another entity like {@link ImageWriter}
+ * via {@link ImageWriter#queueInputImage}. It's up to the new owner to
+ * release the resources held by this image. For example, if the ownership
+ * of this image is transfered to an {@link ImageWriter}, the image will be
+ * freed by the ImageWriter after the image data consumption is done.
+ * </p>
+ * <p>
+ * This method can be used to achieve zero buffer copy for use cases like
+ * {@link android.hardware.camera2.CameraDevice Camera2 API} OPAQUE and YUV
+ * reprocessing, where the application can select an output image from
+ * {@link ImageReader} and transfer this image directly to
+ * {@link ImageWriter}, where this image can be consumed by camera directly.
+ * For OPAQUE reprocessing, this is the only way to send input buffers to
+ * the {@link android.hardware.camera2.CameraDevice camera} for
+ * reprocessing.
+ * </p>
+ * <p>
+ * This is a package private method that is only used internally.
+ * </p>
+ *
+ * @param image The image to be detached from this ImageReader.
+ * @throws IllegalStateException If the ImageReader or image have been
+ * closed, or the has been detached, or has not yet been
+ * acquired.
+ */
+ void detachImage(Image image) {
+ if (image == null) {
+ throw new IllegalArgumentException("input image must not be null");
+ }
+ if (!isImageOwnedbyMe(image)) {
+ throw new IllegalArgumentException("Trying to detach an image that is not owned by"
+ + " this ImageReader");
+ }
+
+ SurfaceImage si = (SurfaceImage) image;
+ if (!si.isImageValid()) {
+ throw new IllegalStateException("Image is no longer valid");
+ }
+ if (si.isAttachable()) {
+ throw new IllegalStateException("Image was already detached from this ImageReader");
+ }
+
+ nativeDetachImage(image);
+ si.setDetached(true);
+ }
+
+ /**
* Only a subset of the formats defined in
* {@link android.graphics.ImageFormat ImageFormat} and
* {@link android.graphics.PixelFormat PixelFormat} are supported by
@@ -483,13 +604,25 @@ public class ImageReader implements AutoCloseable {
case ImageFormat.Y16:
case ImageFormat.RAW_SENSOR:
case ImageFormat.RAW10:
+ case ImageFormat.DEPTH16:
+ case ImageFormat.DEPTH_POINT_CLOUD:
return 1;
+ case PixelFormat.OPAQUE:
+ return 0;
default:
throw new UnsupportedOperationException(
String.format("Invalid format specified %d", mFormat));
}
}
+ private boolean isImageOwnedbyMe(Image image) {
+ if (!(image instanceof SurfaceImage)) {
+ return false;
+ }
+ SurfaceImage si = (SurfaceImage) image;
+ return si.getReader() == this;
+ }
+
/**
* Called from Native code when an Event happens.
*
@@ -558,7 +691,11 @@ public class ImageReader implements AutoCloseable {
@Override
public void close() {
if (mIsImageValid) {
- ImageReader.this.releaseImage(this);
+ if (!mIsDetached.get()) {
+ // For detached images, the new owner is responsible for
+ // releasing the resources
+ ImageReader.this.releaseImage(this);
+ }
}
}
@@ -611,6 +748,15 @@ public class ImageReader implements AutoCloseable {
}
@Override
+ public void setTimestamp(long timestampNs) {
+ if (mIsImageValid) {
+ mTimestamp = timestampNs;
+ } else {
+ throw new IllegalStateException("Image is already released");
+ }
+ }
+
+ @Override
public Plane[] getPlanes() {
if (mIsImageValid) {
// Shallow copy is fine.
@@ -621,6 +767,11 @@ public class ImageReader implements AutoCloseable {
}
@Override
+ public boolean isOpaque() {
+ return mFormat == PixelFormat.OPAQUE;
+ }
+
+ @Override
protected final void finalize() throws Throwable {
try {
close();
@@ -629,6 +780,20 @@ public class ImageReader implements AutoCloseable {
}
}
+ @Override
+ boolean isAttachable() {
+ return mIsDetached.get();
+ }
+
+ @Override
+ ImageReader getOwner() {
+ return ImageReader.this;
+ }
+
+ private void setDetached(boolean detached) {
+ mIsDetached.getAndSet(detached);
+ }
+
private void setImageValid(boolean isValid) {
mIsImageValid = isValid;
}
@@ -731,6 +896,8 @@ public class ImageReader implements AutoCloseable {
private boolean mIsImageValid;
private int mHeight = -1;
private int mWidth = -1;
+ // If this image is detached from the ImageReader.
+ private AtomicBoolean mIsDetached = new AtomicBoolean(false);
private synchronized native ByteBuffer nativeImageGetBuffer(int idx, int readerFormat);
private synchronized native SurfacePlane nativeCreatePlane(int idx, int readerFormat);
@@ -743,6 +910,7 @@ public class ImageReader implements AutoCloseable {
private synchronized native void nativeClose();
private synchronized native void nativeReleaseImage(Image i);
private synchronized native Surface nativeGetSurface();
+ private synchronized native void nativeDetachImage(Image i);
/**
* @return A return code {@code ACQUIRE_*}
diff --git a/media/java/android/media/ImageUtils.java b/media/java/android/media/ImageUtils.java
new file mode 100644
index 0000000..89313bf
--- /dev/null
+++ b/media/java/android/media/ImageUtils.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2015 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.graphics.ImageFormat;
+import android.graphics.PixelFormat;
+import android.media.Image.Plane;
+import android.util.Size;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Package private utility class for hosting commonly used Image related methods.
+ */
+class ImageUtils {
+
+ /**
+ * Only a subset of the formats defined in
+ * {@link android.graphics.ImageFormat ImageFormat} and
+ * {@link android.graphics.PixelFormat PixelFormat} are supported by
+ * ImageReader. When reading RGB data from a surface, the formats defined in
+ * {@link android.graphics.PixelFormat PixelFormat} can be used; when
+ * reading YUV, JPEG or raw sensor data (for example, from the camera or video
+ * decoder), formats from {@link android.graphics.ImageFormat ImageFormat}
+ * are used.
+ */
+ public static int getNumPlanesForFormat(int format) {
+ switch (format) {
+ case ImageFormat.YV12:
+ case ImageFormat.YUV_420_888:
+ case ImageFormat.NV21:
+ return 3;
+ case ImageFormat.NV16:
+ return 2;
+ case PixelFormat.RGB_565:
+ case PixelFormat.RGBA_8888:
+ case PixelFormat.RGBX_8888:
+ case PixelFormat.RGB_888:
+ case ImageFormat.JPEG:
+ case ImageFormat.YUY2:
+ case ImageFormat.Y8:
+ case ImageFormat.Y16:
+ case ImageFormat.RAW_SENSOR:
+ case ImageFormat.RAW10:
+ return 1;
+ case PixelFormat.OPAQUE:
+ return 0;
+ default:
+ throw new UnsupportedOperationException(
+ String.format("Invalid format specified %d", format));
+ }
+ }
+
+ /**
+ * <p>
+ * Copy source image data to destination Image.
+ * </p>
+ * <p>
+ * Only support the copy between two non-opaque images with same properties
+ * (format, size, etc.). The data from the source image will be copied to
+ * the byteBuffers from the destination Image starting from position zero,
+ * and the destination image will be rewound to zero after copy is done.
+ * </p>
+ *
+ * @param src The source image to be copied from.
+ * @param dst The destination image to be copied to.
+ * @throws IllegalArgumentException If the source and destination images
+ * have different format, or one of the images is not copyable.
+ */
+ public static void imageCopy(Image src, Image dst) {
+ if (src == null || dst == null) {
+ throw new IllegalArgumentException("Images should be non-null");
+ }
+ if (src.getFormat() != dst.getFormat()) {
+ throw new IllegalArgumentException("Src and dst images should have the same format");
+ }
+ if (src.isOpaque() || dst.isOpaque()) {
+ throw new IllegalArgumentException("Opaque image is not copyable");
+ }
+ if (!(dst.getOwner() instanceof ImageWriter)) {
+ throw new IllegalArgumentException("Destination image is not from ImageWriter. Only"
+ + " the images from ImageWriter are writable");
+ }
+ Size srcSize = new Size(src.getWidth(), src.getHeight());
+ Size dstSize = new Size(dst.getWidth(), dst.getHeight());
+ if (!srcSize.equals(dstSize)) {
+ throw new IllegalArgumentException("source image size " + srcSize + " is different"
+ + " with " + "destination image size " + dstSize);
+ }
+
+ Plane[] srcPlanes = src.getPlanes();
+ Plane[] dstPlanes = dst.getPlanes();
+ ByteBuffer srcBuffer = null;
+ ByteBuffer dstBuffer = null;
+ for (int i = 0; i < srcPlanes.length; i++) {
+ srcBuffer = srcPlanes[i].getBuffer();
+ int srcPos = srcBuffer.position();
+ srcBuffer.rewind();
+ dstBuffer = dstPlanes[i].getBuffer();
+ dstBuffer.rewind();
+ dstBuffer.put(srcBuffer);
+ srcBuffer.position(srcPos);
+ dstBuffer.rewind();
+ }
+ }
+}
diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java
new file mode 100644
index 0000000..20389a3
--- /dev/null
+++ b/media/java/android/media/ImageWriter.java
@@ -0,0 +1,798 @@
+/*
+ * Copyright 2015 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.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.view.Surface;
+
+import java.lang.ref.WeakReference;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.NioUtils;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * <p>
+ * The ImageWriter class allows an application to produce Image data into a
+ * {@link android.view.Surface}, and have it be consumed by another component like
+ * {@link android.hardware.camera2.CameraDevice CameraDevice}.
+ * </p>
+ * <p>
+ * Several Android API classes can provide input {@link android.view.Surface
+ * Surface} objects for ImageWriter to produce data into, including
+ * {@link MediaCodec MediaCodec} (encoder),
+ * {@link android.hardware.camera2.CameraDevice CameraDevice} (reprocessing
+ * input), {@link ImageReader}, etc.
+ * </p>
+ * <p>
+ * The input Image data is encapsulated in {@link Image} objects. To produce
+ * Image data into a destination {@link android.view.Surface Surface}, the
+ * application can get an input Image via {@link #dequeueInputImage} then write
+ * Image data into it. Multiple such {@link Image} objects can be dequeued at
+ * the same time and queued back in any order, up to the number specified by the
+ * {@code maxImages} constructor parameter.
+ * </p>
+ * <p>
+ * If the application already has an Image from {@link ImageReader}, the
+ * application can directly queue this Image into ImageWriter (via
+ * {@link #queueInputImage}), potentially with zero buffer copies. For the opaque
+ * Images produced by an opaque ImageReader (created by
+ * {@link ImageReader#newOpaqueInstance}), this is the only way to send Image
+ * data to ImageWriter, as the Image data aren't accessible by the application.
+ * </p>
+ * Once new input Images are queued into an ImageWriter, it's up to the downstream
+ * components (e.g. {@link ImageReader} or
+ * {@link android.hardware.camera2.CameraDevice}) to consume the Images. If the
+ * downstream components cannot consume the Images at least as fast as the
+ * ImageWriter production rate, the {@link #dequeueInputImage} call will eventually
+ * block and the application will have to drop input frames. </p>
+ */
+public class ImageWriter implements AutoCloseable {
+ private final Object mListenerLock = new Object();
+ private ImageListener mListener;
+ private ListenerHandler mListenerHandler;
+ private long mNativeContext;
+
+ // Field below is used by native code, do not access or modify.
+ private int mWriterFormat;
+
+ private final int mMaxImages;
+ // Keep track of the currently attached Image; or an attached Image that is
+ // released will be removed from this list.
+ private List<Image> mAttachedImages = new ArrayList<Image>();
+ private List<Image> mDequeuedImages = new ArrayList<Image>();
+
+ /**
+ * <p>
+ * Create a new ImageWriter.
+ * </p>
+ * <p>
+ * The {@code maxImages} parameter determines the maximum number of
+ * {@link Image} objects that can be be dequeued from the
+ * {@code ImageWriter} simultaneously. Requesting more buffers will use up
+ * more memory, so it is important to use only the minimum number necessary.
+ * </p>
+ * <p>
+ * The input Image size and format depend on the Surface that is provided by
+ * the downstream consumer end-point.
+ * </p>
+ *
+ * @param surface The destination Surface this writer produces Image data
+ * into.
+ * @param maxImages The maximum number of Images the user will want to
+ * access simultaneously for producing Image data. This should be
+ * as small as possible to limit memory use. Once maxImages
+ * Images are dequeued by the user, one of them has to be queued
+ * back before a new Image can be dequeued for access via
+ * {@link #dequeueInputImage()}.
+ * @return a new ImageWriter instance.
+ */
+ public static ImageWriter newInstance(Surface surface, int maxImages) {
+ return new ImageWriter(surface, maxImages);
+ }
+
+ /**
+ * @hide
+ */
+ protected ImageWriter(Surface surface, int maxImages) {
+ if (surface == null || maxImages < 1) {
+ throw new IllegalArgumentException("Illegal input argument: surface " + surface
+ + ", maxImages: " + maxImages);
+ }
+
+ mMaxImages = maxImages;
+ // Note that the underlying BufferQueue is working in synchronous mode
+ // to avoid dropping any buffers.
+ mNativeContext = nativeInit(new WeakReference<ImageWriter>(this), surface, maxImages);
+ }
+
+ /**
+ * <p>
+ * Maximum number of Images that can be dequeued from the ImageWriter
+ * simultaneously (for example, with {@link #dequeueInputImage()}).
+ * </p>
+ * <p>
+ * An Image is considered dequeued after it's returned by
+ * {@link #dequeueInputImage()} from ImageWriter, and until the Image is
+ * sent back to ImageWriter via {@link #queueInputImage}, or
+ * {@link Image#close()}.
+ * </p>
+ * <p>
+ * Attempting to dequeue more than {@code maxImages} concurrently will
+ * result in the {@link #dequeueInputImage()} function throwing an
+ * {@link IllegalStateException}.
+ * </p>
+ *
+ * @return Maximum number of Images that can be dequeued from this
+ * ImageWriter.
+ * @see #dequeueInputImage
+ * @see #queueInputImage
+ * @see Image#close
+ */
+ public int getMaxImages() {
+ return mMaxImages;
+ }
+
+ /**
+ * <p>
+ * Dequeue the next available input Image for the application to produce
+ * data into.
+ * </p>
+ * <p>
+ * This method requests a new input Image from ImageWriter. The application
+ * owns this Image after this call. Once the application fills the Image
+ * data, it is expected to return this Image back to ImageWriter for
+ * downstream consumer components (e.g.
+ * {@link android.hardware.camera2.CameraDevice}) to consume. The Image can
+ * be returned to ImageWriter via {@link #queueInputImage} or
+ * {@link Image#close()}.
+ * </p>
+ * <p>
+ * This call will block if all available input images have been filled by
+ * the application and the downstream consumer has not yet consumed any.
+ * When an Image is consumed by the downstream consumer, an
+ * {@link ImageListener#onInputImageReleased} callback will be fired, which
+ * indicates that there is one input Image available. It is recommended to
+ * dequeue next Image only after this callback is fired, in the steady state.
+ * </p>
+ *
+ * @return The next available input Image from this ImageWriter.
+ * @throws IllegalStateException if {@code maxImages} Images are currently
+ * dequeued.
+ * @see #queueInputImage
+ * @see Image#close
+ */
+ public Image dequeueInputImage() {
+ if (mDequeuedImages.size() >= mMaxImages) {
+ throw new IllegalStateException("Already dequeued max number of Images " + mMaxImages);
+ }
+ WriterSurfaceImage newImage = new WriterSurfaceImage(this);
+ nativeDequeueInputImage(mNativeContext, newImage);
+ mDequeuedImages.add(newImage);
+ newImage.setImageValid(true);
+ return newImage;
+ }
+
+ /**
+ * <p>
+ * Queue an input {@link Image} back to ImageWriter for the downstream
+ * consumer to access.
+ * </p>
+ * <p>
+ * The input {@link Image} could be from ImageReader (acquired via
+ * {@link ImageReader#acquireNextImage} or
+ * {@link ImageReader#acquireLatestImage}), or from this ImageWriter
+ * (acquired via {@link #dequeueInputImage}). In the former case, the Image
+ * data will be moved to this ImageWriter. Note that the Image properties
+ * (size, format, strides, etc.) must be the same as the properties of the
+ * images dequeued from this ImageWriter, or this method will throw an
+ * {@link IllegalArgumentException}. In the latter case, the application has
+ * filled the input image with data. This method then passes the filled
+ * buffer to the downstream consumer. In both cases, it's up to the caller
+ * to ensure that the Image timestamp (in nanoseconds) is correctly set, as
+ * the downstream component may want to use it to indicate the Image data
+ * capture time.
+ * </p>
+ * <p>
+ * Passing in a non-opaque Image may result in a memory copy, which also
+ * requires a free input Image from this ImageWriter as the destination. In
+ * this case, this call will block, as {@link #dequeueInputImage} does, if
+ * there are no free Images available. To be safe, the application should ensure
+ * that there is at least one free Image available in this ImageWriter before calling
+ * this method.
+ * </p>
+ * <p>
+ * After this call, the input Image is no longer valid for further access,
+ * as if the Image is {@link Image#close closed}. Attempting to access the
+ * {@link ByteBuffer ByteBuffers} returned by an earlier
+ * {@link Image.Plane#getBuffer Plane#getBuffer} call will result in an
+ * {@link IllegalStateException}.
+ * </p>
+ *
+ * @param image The Image to be queued back to ImageWriter for future
+ * consumption.
+ * @see #dequeueInputImage()
+ */
+ public void queueInputImage(Image image) {
+ if (image == null) {
+ throw new IllegalArgumentException("image shouldn't be null");
+ }
+ boolean ownedByMe = isImageOwnedByMe(image);
+ if (ownedByMe && !(((WriterSurfaceImage) image).isImageValid())) {
+ throw new IllegalStateException("Image from ImageWriter is invalid");
+ }
+
+ // For images from other components, need to detach first, then attach.
+ if (!ownedByMe) {
+ if (!(image.getOwner() instanceof ImageReader)) {
+ throw new IllegalArgumentException("Only images from ImageReader can be queued to"
+ + " ImageWriter, other image source is not supported yet!");
+ }
+
+ ImageReader prevOwner = (ImageReader) image.getOwner();
+ // Only do the image attach for opaque images for now. Do the image
+ // copy for other formats. TODO: use attach for other formats to
+ // improve the performance, and fall back to copy when attach/detach fails.
+ if (image.isOpaque()) {
+ prevOwner.detachImage(image);
+ attachInputImage(image);
+ } else {
+ Image inputImage = dequeueInputImage();
+ inputImage.setTimestamp(image.getTimestamp());
+ inputImage.setCropRect(image.getCropRect());
+ ImageUtils.imageCopy(image, inputImage);
+ image.close();
+ image = inputImage;
+ ownedByMe = true;
+ }
+ }
+
+ Rect crop = image.getCropRect();
+ nativeQueueInputImage(mNativeContext, image, image.getTimestamp(), crop.left, crop.top,
+ crop.right, crop.bottom);
+
+ /**
+ * Only remove and cleanup the Images that are owned by this
+ * ImageWriter. Images detached from other owners are only
+ * temporarily owned by this ImageWriter and will be detached immediately
+ * after they are released by downstream consumers, so there is no need to
+ * keep track of them in mDequeuedImages.
+ */
+ if (ownedByMe) {
+ mDequeuedImages.remove(image);
+ WriterSurfaceImage wi = (WriterSurfaceImage) image;
+ wi.clearSurfacePlanes();
+ wi.setImageValid(false);
+ } else {
+ // This clears the native reference held by the original owner. When
+ // this Image is detached later by this ImageWriter, the native
+ // memory won't be leaked.
+ image.close();
+ }
+ }
+
+ /**
+ * ImageWriter callback interface, used to to asynchronously notify the
+ * application of various ImageWriter events.
+ */
+ public interface ImageListener {
+ /**
+ * <p>
+ * Callback that is called when an input Image is released back to
+ * ImageWriter after the data consumption.
+ * </p>
+ * <p>
+ * The client can use this callback to indicate either an input Image is
+ * available to fill data into, or the input Image is returned and freed
+ * if it was attached from other components (e.g. an
+ * {@link ImageReader}). For the latter case, the ownership of the Image
+ * will be automatically removed by ImageWriter right before this
+ * callback is fired.
+ * </p>
+ *
+ * @param writer the ImageWriter the callback is associated with.
+ * @see ImageWriter
+ * @see Image
+ */
+ // TODO: the semantics is confusion, does't tell which buffer is
+ // released if an application is doing queueInputImage with a mix of
+ // buffers from dequeueInputImage and from an ImageReader. see b/19872821
+ void onInputImageReleased(ImageWriter writer);
+ }
+
+ /**
+ * Register a listener to be invoked when an input Image is returned to
+ * the ImageWriter.
+ *
+ * @param listener The listener that will be run.
+ * @param handler The handler on which the listener should be invoked, or
+ * null if the listener should be invoked on the calling thread's
+ * looper.
+ * @throws IllegalArgumentException If no handler specified and the calling
+ * thread has no looper.
+ */
+ public void setImageListener(ImageListener listener, Handler handler) {
+ synchronized (mListenerLock) {
+ if (listener != null) {
+ Looper looper = handler != null ? handler.getLooper() : Looper.myLooper();
+ if (looper == null) {
+ throw new IllegalArgumentException(
+ "handler is null but the current thread is not a looper");
+ }
+ if (mListenerHandler == null || mListenerHandler.getLooper() != looper) {
+ mListenerHandler = new ListenerHandler(looper);
+ }
+ mListener = listener;
+ } else {
+ mListener = null;
+ mListenerHandler = null;
+ }
+ }
+ }
+
+ /**
+ * Free up all the resources associated with this ImageWriter.
+ * <p>
+ * After calling this method, this ImageWriter cannot be used. Calling any
+ * methods on this ImageWriter and Images previously provided by
+ * {@link #dequeueInputImage()} will result in an
+ * {@link IllegalStateException}, and attempting to write into
+ * {@link ByteBuffer ByteBuffers} returned by an earlier
+ * {@link Image.Plane#getBuffer Plane#getBuffer} call will have undefined
+ * behavior.
+ * </p>
+ */
+ @Override
+ public void close() {
+ setImageListener(null, null);
+ for (Image image : mDequeuedImages) {
+ image.close();
+ }
+ mDequeuedImages.clear();
+ nativeClose(mNativeContext);
+ mNativeContext = 0;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Get the ImageWriter format.
+ * <p>
+ * This format may be different than the Image format returned by
+ * {@link Image#getFormat()}
+ * </p>
+ *
+ * @return The ImageWriter format.
+ */
+ int getFormat() {
+ return mWriterFormat;
+ }
+
+
+ /**
+ * <p>
+ * Attach input Image to this ImageWriter.
+ * </p>
+ * <p>
+ * When an Image is from an opaque source (e.g. an opaque ImageReader created
+ * by {@link ImageReader#newOpaqueInstance}), or the source Image is so large
+ * that copying its data is too expensive, this method can be used to
+ * migrate the source Image into ImageWriter without a data copy. The source
+ * Image must be detached from its previous owner already, or this call will
+ * throw an {@link IllegalStateException}.
+ * </p>
+ * <p>
+ * After this call, the ImageWriter takes ownership of this Image.
+ * This ownership will be automatically removed from this writer after the
+ * consumer releases this Image, that is, after
+ * {@link ImageListener#onInputImageReleased}. The caller is
+ * responsible for closing this Image through {@link Image#close()} to free up
+ * the resources held by this Image.
+ * </p>
+ *
+ * @param image The source Image to be attached and queued into this
+ * ImageWriter for downstream consumer to use.
+ * @throws IllegalStateException if the Image is not detached from its
+ * previous owner, or the Image is already attached to this
+ * ImageWriter, or the source Image is invalid.
+ */
+ private void attachInputImage(Image image) {
+ if (image == null) {
+ throw new IllegalArgumentException("image shouldn't be null");
+ }
+ if (isImageOwnedByMe(image)) {
+ throw new IllegalArgumentException(
+ "Can not attach an image that is owned ImageWriter already");
+ }
+ /**
+ * Throw ISE if the image is not attachable, which means that it is
+ * either owned by other entity now, or completely non-attachable (some
+ * stand-alone images are not backed by native gralloc buffer, thus not
+ * attachable).
+ */
+ if (!image.isAttachable()) {
+ throw new IllegalStateException("Image was not detached from last owner, or image "
+ + " is not detachable");
+ }
+ if (mAttachedImages.contains(image)) {
+ throw new IllegalStateException("Image was already attached to ImageWritter");
+ }
+
+ // TODO: what if attach failed, throw RTE or detach a slot then attach?
+ // need do some cleanup to make sure no orphaned
+ // buffer caused leak.
+ nativeAttachImage(mNativeContext, image);
+ mAttachedImages.add(image);
+ }
+
+ /**
+ * This custom handler runs asynchronously so callbacks don't get queued
+ * behind UI messages.
+ */
+ private final class ListenerHandler extends Handler {
+ public ListenerHandler(Looper looper) {
+ super(looper, null, true /* async */);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ ImageListener listener;
+ synchronized (mListenerLock) {
+ listener = mListener;
+ }
+ // TODO: detach Image from ImageWriter and remove the Image from
+ // mAttachedImage list.
+ if (listener != null) {
+ listener.onInputImageReleased(ImageWriter.this);
+ }
+ }
+ }
+
+ /**
+ * Called from Native code when an Event happens. This may be called from an
+ * arbitrary Binder thread, so access to the ImageWriter must be
+ * synchronized appropriately.
+ */
+ private static void postEventFromNative(Object selfRef) {
+ @SuppressWarnings("unchecked")
+ WeakReference<ImageWriter> weakSelf = (WeakReference<ImageWriter>) selfRef;
+ final ImageWriter iw = weakSelf.get();
+ if (iw == null) {
+ return;
+ }
+
+ final Handler handler;
+ synchronized (iw.mListenerLock) {
+ handler = iw.mListenerHandler;
+ }
+ if (handler != null) {
+ handler.sendEmptyMessage(0);
+ }
+ }
+
+ /**
+ * <p>
+ * Abort the Images that were dequeued from this ImageWriter, and return
+ * them to this writer for reuse.
+ * </p>
+ * <p>
+ * This method is used for the cases where the application dequeued the
+ * Image, may have filled the data, but does not want the downstream
+ * component to consume it. The Image will be returned to this ImageWriter
+ * for reuse after this call, and the ImageWriter will immediately have an
+ * Image available to be dequeued. This aborted Image will be invisible to
+ * the downstream consumer, as if nothing happened.
+ * </p>
+ *
+ * @param image The Image to be aborted.
+ * @see #dequeueInputImage()
+ * @see Image#close()
+ */
+ private void abortImage(Image image) {
+ if (image == null) {
+ throw new IllegalArgumentException("image shouldn't be null");
+ }
+
+ if (!mDequeuedImages.contains(image)) {
+ throw new IllegalStateException("It is illegal to abort some image that is not"
+ + " dequeued yet");
+ }
+
+ WriterSurfaceImage wi = (WriterSurfaceImage) image;
+
+ if (!wi.isImageValid()) {
+ throw new IllegalStateException("Image is invalid");
+ }
+
+ /**
+ * We only need abort Images that are owned and dequeued by ImageWriter.
+ * For attached Images, no need to abort, as there are only two cases:
+ * attached + queued successfully, and attach failed. Neither of the
+ * cases need abort.
+ */
+ cancelImage(mNativeContext,image);
+ mDequeuedImages.remove(image);
+ wi.clearSurfacePlanes();
+ wi.setImageValid(false);
+ }
+
+ private boolean isImageOwnedByMe(Image image) {
+ if (!(image instanceof WriterSurfaceImage)) {
+ return false;
+ }
+ WriterSurfaceImage wi = (WriterSurfaceImage) image;
+ if (wi.getOwner() != this) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private static class WriterSurfaceImage extends android.media.Image {
+ private ImageWriter mOwner;
+ private AtomicBoolean mIsImageValid = new AtomicBoolean(false);
+ // This field is used by native code, do not access or modify.
+ private long mNativeBuffer;
+ private int mNativeFenceFd = -1;
+ private SurfacePlane[] mPlanes;
+ private int mHeight = -1;
+ private int mWidth = -1;
+ private int mFormat = -1;
+ // When this default timestamp is used, timestamp for the input Image
+ // will be generated automatically when queueInputBuffer is called.
+ private final long DEFAULT_TIMESTAMP = Long.MIN_VALUE;
+ private long mTimestamp = DEFAULT_TIMESTAMP;
+
+ public WriterSurfaceImage(ImageWriter writer) {
+ mOwner = writer;
+ }
+
+ @Override
+ public int getFormat() {
+ if (!mIsImageValid.get()) {
+ throw new IllegalStateException("Image is already released");
+ }
+ if (mFormat == -1) {
+ mFormat = nativeGetFormat();
+ }
+ return mFormat;
+ }
+
+ @Override
+ public int getWidth() {
+ if (!mIsImageValid.get()) {
+ throw new IllegalStateException("Image is already released");
+ }
+
+ if (mWidth == -1) {
+ mWidth = nativeGetWidth();
+ }
+
+ return mWidth;
+ }
+
+ @Override
+ public int getHeight() {
+ if (!mIsImageValid.get()) {
+ throw new IllegalStateException("Image is already released");
+ }
+
+ if (mHeight == -1) {
+ mHeight = nativeGetHeight();
+ }
+
+ return mHeight;
+ }
+
+ @Override
+ public long getTimestamp() {
+ if (!mIsImageValid.get()) {
+ throw new IllegalStateException("Image is already released");
+ }
+
+ return mTimestamp;
+ }
+
+ @Override
+ public void setTimestamp(long timestamp) {
+ if (!mIsImageValid.get()) {
+ throw new IllegalStateException("Image is already released");
+ }
+
+ mTimestamp = timestamp;
+ }
+
+ @Override
+ public boolean isOpaque() {
+ if (!mIsImageValid.get()) {
+ throw new IllegalStateException("Image is already released");
+ }
+
+ return getFormat() == PixelFormat.OPAQUE;
+ }
+
+ @Override
+ public Plane[] getPlanes() {
+ if (!mIsImageValid.get()) {
+ throw new IllegalStateException("Image is already released");
+ }
+
+ if (mPlanes == null) {
+ int numPlanes = ImageUtils.getNumPlanesForFormat(getFormat());
+ mPlanes = nativeCreatePlanes(numPlanes, getOwner().getFormat());
+ }
+
+ return mPlanes.clone();
+ }
+
+ @Override
+ boolean isAttachable() {
+ if (!mIsImageValid.get()) {
+ throw new IllegalStateException("Image is already released");
+ }
+ // Don't allow Image to be detached from ImageWriter for now, as no
+ // detach API is exposed.
+ return false;
+ }
+
+ @Override
+ ImageWriter getOwner() {
+ return mOwner;
+ }
+
+ @Override
+ public void close() {
+ if (mIsImageValid.get()) {
+ getOwner().abortImage(this);
+ }
+ }
+
+ @Override
+ protected final void finalize() throws Throwable {
+ try {
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private boolean isImageValid() {
+ return mIsImageValid.get();
+ }
+
+ private void setImageValid(boolean isValid) {
+ mIsImageValid.getAndSet(isValid);
+ }
+
+ private void clearSurfacePlanes() {
+ if (mIsImageValid.get()) {
+ for (int i = 0; i < mPlanes.length; i++) {
+ if (mPlanes[i] != null) {
+ mPlanes[i].clearBuffer();
+ mPlanes[i] = null;
+ }
+ }
+ }
+ }
+
+ private class SurfacePlane extends android.media.Image.Plane {
+ private ByteBuffer mBuffer;
+ final private int mPixelStride;
+ final private int mRowStride;
+
+ // SurfacePlane instance is created by native code when a new
+ // SurfaceImage is created
+ private SurfacePlane(int rowStride, int pixelStride, ByteBuffer buffer) {
+ mRowStride = rowStride;
+ mPixelStride = pixelStride;
+ mBuffer = buffer;
+ /**
+ * Set the byteBuffer order according to host endianness (native
+ * order), otherwise, the byteBuffer order defaults to
+ * ByteOrder.BIG_ENDIAN.
+ */
+ mBuffer.order(ByteOrder.nativeOrder());
+ }
+
+ @Override
+ public int getRowStride() {
+ if (WriterSurfaceImage.this.isImageValid() == false) {
+ throw new IllegalStateException("Image is already released");
+ }
+ return mRowStride;
+ }
+
+ @Override
+ public int getPixelStride() {
+ if (WriterSurfaceImage.this.isImageValid() == false) {
+ throw new IllegalStateException("Image is already released");
+ }
+ return mPixelStride;
+ }
+
+ @Override
+ public ByteBuffer getBuffer() {
+ if (WriterSurfaceImage.this.isImageValid() == false) {
+ throw new IllegalStateException("Image is already released");
+ }
+
+ return mBuffer;
+ }
+
+ private void clearBuffer() {
+ // Need null check first, as the getBuffer() may not be called
+ // before an Image is closed.
+ if (mBuffer == null) {
+ return;
+ }
+
+ if (mBuffer.isDirect()) {
+ NioUtils.freeDirectBuffer(mBuffer);
+ }
+ mBuffer = null;
+ }
+
+ }
+
+ // this will create the SurfacePlane object and fill the information
+ private synchronized native SurfacePlane[] nativeCreatePlanes(int numPlanes, int writerFmt);
+
+ private synchronized native int nativeGetWidth();
+
+ private synchronized native int nativeGetHeight();
+
+ private synchronized native int nativeGetFormat();
+ }
+
+ // Native implemented ImageWriter methods.
+ private synchronized native long nativeInit(Object weakSelf, Surface surface, int maxImgs);
+
+ private synchronized native void nativeClose(long nativeCtx);
+
+ private synchronized native void nativeAttachImage(long nativeCtx, Image image);
+
+ private synchronized native void nativeDequeueInputImage(long nativeCtx, Image wi);
+
+ private synchronized native void nativeQueueInputImage(long nativeCtx, Image image,
+ long timestampNs, int left, int top, int right, int bottom);
+
+ private synchronized native void cancelImage(long nativeCtx, Image image);
+
+ /**
+ * We use a class initializer to allow the native code to cache some field
+ * offsets.
+ */
+ private static native void nativeClassInit();
+
+ static {
+ System.loadLibrary("media_jni");
+ nativeClassInit();
+ }
+}
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 8985b52..a7f33fa 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -19,7 +19,6 @@ package android.media;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.media.Image;
-import android.media.Image.Plane;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import android.media.MediaCrypto;
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 6ac854f..ebf73da 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -24,15 +24,12 @@ import android.util.Size;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import static android.media.Utils.intersectSortedDistinctRanges;
import static android.media.Utils.sortDistinctRanges;
-import static com.android.internal.util.Preconditions.checkArgumentPositive;
-import static com.android.internal.util.Preconditions.checkNotNull;
/**
* Provides information about a given media codec available on the device. You can
@@ -1975,7 +1972,7 @@ public final class MediaCodecInfo {
(Integer)map.get(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL);
if (complexity == null) {
complexity = flacComplexity;
- } else if (flacComplexity != null && complexity != flacComplexity) {
+ } else if (flacComplexity != null && !complexity.equals(flacComplexity)) {
throw new IllegalArgumentException(
"conflicting values for complexity and " +
"flac-compression-level");
@@ -1988,7 +1985,7 @@ public final class MediaCodecInfo {
Integer aacProfile = (Integer)map.get(MediaFormat.KEY_AAC_PROFILE);
if (profile == null) {
profile = aacProfile;
- } else if (aacProfile != null && aacProfile != profile) {
+ } else if (aacProfile != null && !aacProfile.equals(profile)) {
throw new IllegalArgumentException(
"conflicting values for profile and aac-profile");
}
diff --git a/media/java/android/media/MediaCodecList.java b/media/java/android/media/MediaCodecList.java
index bb848d9..7fd0186 100644
--- a/media/java/android/media/MediaCodecList.java
+++ b/media/java/android/media/MediaCodecList.java
@@ -19,8 +19,6 @@ package android.media;
import android.util.Log;
import android.media.MediaCodecInfo;
-import android.media.MediaCodecInfo.CodecCapabilities;
-
import java.util.ArrayList;
import java.util.Arrays;
diff --git a/media/java/android/media/MediaDescription.java b/media/java/android/media/MediaDescription.java
index 4399c0d..ddbffc2 100644
--- a/media/java/android/media/MediaDescription.java
+++ b/media/java/android/media/MediaDescription.java
@@ -1,22 +1,11 @@
package android.media;
-import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.ContentResolver;
-import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.RectF;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
-import android.os.CancellationSignal;
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.Size;
/**
* A simple set of metadata for a media item suitable for display. This can be
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 78a5abe..069f7ff 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -21,8 +21,6 @@ import java.util.UUID;
import java.util.HashMap;
import java.util.List;
import android.annotation.SystemApi;
-import android.os.Binder;
-import android.os.Debug;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -84,6 +82,10 @@ import android.util.Log;
* encrypted content, the samples returned from the extractor remain encrypted, they
* are only decrypted when the samples are delivered to the decoder.
* <p>
+ * MediaDrm methods throw {@link java.lang.IllegalStateException}
+ * when a method is called on a MediaDrm object that is in an invalid or inoperable
+ * state. This is typically due to incorrect application API usage, but may also
+ * be due to an unrecoverable failure in the DRM plugin or security hardware.
* <a name="Callbacks"></a>
* <h3>Callbacks</h3>
* <p>Applications should register for informational events in order
@@ -256,6 +258,9 @@ public final class MediaDrm {
* This event type indicates that the app needs to request a certificate from
* the provisioning server. The request message data is obtained using
* {@link #getProvisionRequest}
+ *
+ * @deprecated Handle provisioning via {@link android.media.NotProvisionedException}
+ * instead.
*/
public static final int EVENT_PROVISION_REQUIRED = 1;
@@ -277,6 +282,12 @@ public final class MediaDrm {
*/
public static final int EVENT_VENDOR_DEFINED = 4;
+ /**
+ * This event indicates that a session opened by the app has been reclaimed by the resource
+ * manager.
+ */
+ public static final int EVENT_SESSION_RECLAIMED = 5;
+
private static final int DRM_EVENT = 200;
private class EventHandler extends Handler
@@ -376,11 +387,27 @@ public final class MediaDrm {
public static final int KEY_TYPE_RELEASE = 3;
/**
+ * Key request type is initial license request
+ */
+ public static final int REQUEST_TYPE_INITIAL = 0;
+
+ /**
+ * Key request type is license renewal
+ */
+ public static final int REQUEST_TYPE_RENEWAL = 1;
+
+ /**
+ * Key request type is license release
+ */
+ public static final int REQUEST_TYPE_RELEASE = 2;
+
+ /**
* Contains the opaque data an app uses to request keys from a license server
*/
public final static class KeyRequest {
private byte[] mData;
private String mDefaultUrl;
+ private int mRequestType;
KeyRequest() {}
@@ -395,6 +422,11 @@ public final class MediaDrm {
* server URL from other sources.
*/
public String getDefaultUrl() { return mDefaultUrl; }
+
+ /**
+ * Get the type of the request
+ */
+ public int getRequestType() { return mRequestType; }
};
/**
@@ -453,7 +485,6 @@ public final class MediaDrm {
* reprovisioning is required
* @throws DeniedByServerException if the response indicates that the
* server rejected the request
- * @throws ResourceBusyException if required resources are in use
*/
public native byte[] provideKeyResponse(byte[] scope, byte[] response)
throws NotProvisionedException, DeniedByServerException;
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 4356a3e..0c1c7e9 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -420,6 +420,25 @@ public final class MediaFormat {
public static final String KEY_QUALITY = "quality";
/**
+ * A key describing the desired codec priority.
+ * <p>
+ * The associated value is an integer. Higher value means lower priority.
+ * <p>
+ * Currently, only two levels are supported:<br>
+ * 0: realtime priority - meaning that the codec shall support the given
+ * performance configuration (e.g. framerate) at realtime. This should
+ * only be used by media playback, capture, and possibly by realtime
+ * communication scenarios if best effort performance is not suitable.<br>
+ * 1: non-realtime priority (best effort).
+ * <p>
+ * This is a hint used at codec configuration and resource planning - to understand
+ * the realtime requirements of the application; however, due to the nature of
+ * media components, performance is not guaranteed.
+ *
+ */
+ public static final String KEY_PRIORITY = "priority";
+
+ /**
* A key describing the desired profile to be used by an encoder.
* Constants are declared in {@link MediaCodecInfo.CodecProfileLevel}.
* This key is only supported for codecs that specify a profile.
@@ -587,14 +606,14 @@ public final class MediaFormat {
* Sets the value of an integer key.
*/
public final void setInteger(String name, int value) {
- mMap.put(name, new Integer(value));
+ mMap.put(name, Integer.valueOf(value));
}
/**
* Sets the value of a long key.
*/
public final void setLong(String name, long value) {
- mMap.put(name, new Long(value));
+ mMap.put(name, Long.valueOf(value));
}
/**
diff --git a/media/java/android/media/MediaHTTPService.java b/media/java/android/media/MediaHTTPService.java
index 3b4703d..2348ab7 100644
--- a/media/java/android/media/MediaHTTPService.java
+++ b/media/java/android/media/MediaHTTPService.java
@@ -16,9 +16,7 @@
package android.media;
-import android.os.Binder;
import android.os.IBinder;
-import android.util.Log;
/** @hide */
public class MediaHTTPService extends IMediaHTTPService.Stub {
diff --git a/media/java/android/media/MediaMetadata.java b/media/java/android/media/MediaMetadata.java
index 754da0e..39bcef5 100644
--- a/media/java/android/media/MediaMetadata.java
+++ b/media/java/android/media/MediaMetadata.java
@@ -30,7 +30,6 @@ import android.util.ArrayMap;
import android.util.Log;
import android.util.SparseArray;
-import java.util.ArrayList;
import java.util.Set;
/**
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index fc372eb..d77fcd8 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -16,14 +16,11 @@
package android.media;
+import android.annotation.IntDef;
import android.app.ActivityThread;
import android.app.AppOpsManager;
-import android.app.Application;
-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.Uri;
import android.os.Handler;
@@ -64,8 +61,9 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStream;
import java.lang.Runnable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.net.InetSocketAddress;
import java.util.Map;
import java.util.Scanner;
@@ -477,6 +475,11 @@ import java.lang.ref.WeakReference;
* <td>{} </p></td>
* <td>This method can be called in any state and calling it does not change
* the object state. </p></td></tr>
+ * <tr><td>setPlaybackRate</p></td>
+ * <td>any </p></td>
+ * <td>{} </p></td>
+ * <td>This method can be called in any state and calling it does not change
+ * the object state. </p></td></tr>
* <tr><td>setVolume </p></td>
* <td>{Idle, Initialized, Stopped, Prepared, Started, Paused,
* PlaybackCompleted}</p></td>
@@ -1324,6 +1327,59 @@ public class MediaPlayer implements SubtitleController.Listener
public native boolean isPlaying();
/**
+ * Specifies resampling as audio mode for variable rate playback, i.e.,
+ * resample the waveform based on the requested playback rate to get
+ * a new waveform, and play back the new waveform at the original sampling
+ * frequency.
+ * When rate is larger than 1.0, pitch becomes higher.
+ * When rate is smaller than 1.0, pitch becomes lower.
+ */
+ public static final int PLAYBACK_RATE_AUDIO_MODE_RESAMPLE = 0;
+
+ /**
+ * Specifies time stretching as audio mode for variable rate playback.
+ * Time stretching changes the duration of the audio samples without
+ * affecting its pitch.
+ * FIXME: implement time strectching.
+ * @hide
+ */
+ public static final int PLAYBACK_RATE_AUDIO_MODE_STRETCH = 1;
+
+ /** @hide */
+ @IntDef(
+ value = {
+ PLAYBACK_RATE_AUDIO_MODE_RESAMPLE,
+ PLAYBACK_RATE_AUDIO_MODE_STRETCH })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PlaybackRateAudioMode {}
+
+ /**
+ * Sets playback rate and audio mode.
+ *
+ * <p> The supported audio modes are:
+ * <ul>
+ * <li> {@link #PLAYBACK_RATE_AUDIO_MODE_RESAMPLE}
+ * </ul>
+ *
+ * @param rate the ratio between desired playback rate and normal one.
+ * @param audioMode audio playback mode. Must be one of the supported
+ * audio modes.
+ *
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized.
+ * @throws IllegalArgumentException if audioMode is not supported.
+ */
+ public void setPlaybackRate(float rate, @PlaybackRateAudioMode int audioMode) {
+ if (!isAudioPlaybackModeSupported(audioMode)) {
+ final String msg = "Audio playback mode " + audioMode + " is not supported";
+ throw new IllegalArgumentException(msg);
+ }
+ _setPlaybackRate(rate);
+ }
+
+ private native void _setPlaybackRate(float rate) throws IllegalStateException;
+
+ /**
* Seeks to specified time position.
*
* @param msec the offset in milliseconds from the start to seek to
@@ -3083,6 +3139,14 @@ public class MediaPlayer implements SubtitleController.Listener
mode == VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING);
}
+ /*
+ * Test whether a given audio playback mode is supported.
+ * TODO query supported AudioPlaybackMode from player.
+ */
+ private boolean isAudioPlaybackModeSupported(int mode) {
+ return (mode == PLAYBACK_RATE_AUDIO_MODE_RESAMPLE);
+ }
+
/** @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 97b3f63..58c86f2 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -16,6 +16,7 @@
package android.media;
+import android.annotation.SystemApi;
import android.app.ActivityThread;
import android.hardware.Camera;
import android.os.Handler;
@@ -222,12 +223,11 @@ public class MediaRecorder
public static final int REMOTE_SUBMIX = 8;
/**
- * Audio source for FM, which is used to capture current FM tuner output by FMRadio app.
- * There are two use cases, one is for record FM stream for later listening, another is
- * for FM indirect mode(the routing except FM to headset(headphone) device routing).
+ * Audio source for capturing broadcast radio tuner output.
* @hide
*/
- public static final int FM_TUNER = 1998;
+ @SystemApi
+ public static final int RADIO_TUNER = 1998;
/**
* Audio source for preemptible, low-priority software hotword detection
@@ -240,7 +240,8 @@ public class MediaRecorder
* This is a hidden audio source.
* @hide
*/
- protected static final int HOTWORD = 1999;
+ @SystemApi
+ public static final int HOTWORD = 1999;
}
/**
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index 1b6536f..c9a86d8 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -27,7 +27,6 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.os.ServiceManager;
import android.os.SystemClock;
import android.util.Log;
diff --git a/media/java/android/media/SoundPool.java b/media/java/android/media/SoundPool.java
index db6b38b..88d979e 100644
--- a/media/java/android/media/SoundPool.java
+++ b/media/java/android/media/SoundPool.java
@@ -32,7 +32,6 @@ 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;
@@ -112,7 +111,24 @@ import com.android.internal.app.IAppOpsService;
* resumes.</p>
*/
public class SoundPool {
- private final SoundPoolDelegate mImpl;
+ static { System.loadLibrary("soundpool"); }
+
+ // SoundPool messages
+ //
+ // must match SoundPool.h
+ private static final int SAMPLE_LOADED = 1;
+
+ private final static String TAG = "SoundPool";
+ private final static boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private long mNativeContext; // accessed by native methods
+
+ private EventHandler mEventHandler;
+ private SoundPool.OnLoadCompleteListener mOnLoadCompleteListener;
+
+ private final Object mLock;
+ private final AudioAttributes mAttributes;
+ private final IAppOpsService mAppOps;
/**
* Constructor. Constructs a SoundPool object with the following
@@ -135,68 +151,26 @@ public class SoundPool {
}
private SoundPool(int maxStreams, AudioAttributes attributes) {
- if (SystemProperties.getBoolean("config.disable_media", false)) {
- mImpl = new SoundPoolStub();
- } else {
- mImpl = new SoundPoolImpl(this, maxStreams, attributes);
+ // do native setup
+ if (native_setup(new WeakReference<SoundPool>(this), maxStreams, attributes) != 0) {
+ throw new RuntimeException("Native setup failed");
}
+ mLock = new Object();
+ mAttributes = attributes;
+ IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
+ mAppOps = IAppOpsService.Stub.asInterface(b);
}
/**
- * Builder class for {@link SoundPool} objects.
+ * Release the SoundPool resources.
+ *
+ * Release all memory and native resources used by the SoundPool
+ * object. The SoundPool can no longer be used and the reference
+ * should be set to null.
*/
- public static class Builder {
- private int mMaxStreams = 1;
- private AudioAttributes mAudioAttributes;
+ public native final void release();
- /**
- * Constructs a new Builder with the defaults format values.
- * If not provided, the maximum number of streams is 1 (see {@link #setMaxStreams(int)} to
- * change it), and the audio attributes have a usage value of
- * {@link AudioAttributes#USAGE_MEDIA} (see {@link #setAudioAttributes(AudioAttributes)} to
- * change them).
- */
- public Builder() {
- }
-
- /**
- * Sets the maximum of number of simultaneous streams that can be played simultaneously.
- * @param maxStreams a value equal to 1 or greater.
- * @return the same Builder instance
- * @throws IllegalArgumentException
- */
- public Builder setMaxStreams(int maxStreams) throws IllegalArgumentException {
- if (maxStreams <= 0) {
- throw new IllegalArgumentException(
- "Strictly positive value required for the maximum number of streams");
- }
- mMaxStreams = maxStreams;
- return this;
- }
-
- /**
- * Sets the {@link AudioAttributes}. For examples, game applications will use attributes
- * built with usage information set to {@link AudioAttributes#USAGE_GAME}.
- * @param attributes a non-null
- * @return
- */
- public Builder setAudioAttributes(AudioAttributes attributes)
- throws IllegalArgumentException {
- if (attributes == null) {
- throw new IllegalArgumentException("Invalid null AudioAttributes");
- }
- mAudioAttributes = attributes;
- return this;
- }
-
- public SoundPool build() {
- if (mAudioAttributes == null) {
- mAudioAttributes = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_MEDIA).build();
- }
- return new SoundPool(mMaxStreams, mAudioAttributes);
- }
- }
+ protected void finalize() { release(); }
/**
* Load the sound from the specified path.
@@ -207,7 +181,19 @@ public class SoundPool {
* @return a sound ID. This value can be used to play or unload the sound.
*/
public int load(String path, int priority) {
- return mImpl.load(path, priority);
+ int id = 0;
+ try {
+ File f = new File(path);
+ ParcelFileDescriptor fd = ParcelFileDescriptor.open(f,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+ if (fd != null) {
+ id = _load(fd.getFileDescriptor(), 0, f.length(), priority);
+ fd.close();
+ }
+ } catch (java.io.IOException e) {
+ Log.e(TAG, "error loading " + path);
+ }
+ return id;
}
/**
@@ -226,7 +212,17 @@ public class SoundPool {
* @return a sound ID. This value can be used to play or unload the sound.
*/
public int load(Context context, int resId, int priority) {
- return mImpl.load(context, resId, priority);
+ AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId);
+ int id = 0;
+ if (afd != null) {
+ id = _load(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength(), priority);
+ try {
+ afd.close();
+ } catch (java.io.IOException ex) {
+ //Log.d(TAG, "close failed:", ex);
+ }
+ }
+ return id;
}
/**
@@ -238,7 +234,15 @@ public class SoundPool {
* @return a sound ID. This value can be used to play or unload the sound.
*/
public int load(AssetFileDescriptor afd, int priority) {
- return mImpl.load(afd, priority);
+ if (afd != null) {
+ long len = afd.getLength();
+ if (len < 0) {
+ throw new AndroidRuntimeException("no length for fd");
+ }
+ return _load(afd.getFileDescriptor(), afd.getStartOffset(), len, priority);
+ } else {
+ return 0;
+ }
}
/**
@@ -256,7 +260,7 @@ public class SoundPool {
* @return a sound ID. This value can be used to play or unload the sound.
*/
public int load(FileDescriptor fd, long offset, long length, int priority) {
- return mImpl.load(fd, offset, length, priority);
+ return _load(fd, offset, length, priority);
}
/**
@@ -269,9 +273,7 @@ public class SoundPool {
* @param soundID a soundID returned by the load() function
* @return true if just unloaded, false if previously unloaded
*/
- public final boolean unload(int soundID) {
- return mImpl.unload(soundID);
- }
+ public native final boolean unload(int soundID);
/**
* Play a sound from a sound ID.
@@ -299,8 +301,10 @@ public class SoundPool {
*/
public final int play(int soundID, float leftVolume, float rightVolume,
int priority, int loop, float rate) {
- return mImpl.play(
- soundID, leftVolume, rightVolume, priority, loop, rate);
+ if (isRestricted()) {
+ leftVolume = rightVolume = 0;
+ }
+ return _play(soundID, leftVolume, rightVolume, priority, loop, rate);
}
/**
@@ -314,9 +318,7 @@ public class SoundPool {
*
* @param streamID a streamID returned by the play() function
*/
- public final void pause(int streamID) {
- mImpl.pause(streamID);
- }
+ public native final void pause(int streamID);
/**
* Resume a playback stream.
@@ -328,9 +330,7 @@ public class SoundPool {
*
* @param streamID a streamID returned by the play() function
*/
- public final void resume(int streamID) {
- mImpl.resume(streamID);
- }
+ public native final void resume(int streamID);
/**
* Pause all active streams.
@@ -340,9 +340,7 @@ public class SoundPool {
* are playing. It also sets a flag so that any streams that
* are playing can be resumed by calling autoResume().
*/
- public final void autoPause() {
- mImpl.autoPause();
- }
+ public native final void autoPause();
/**
* Resume all previously active streams.
@@ -350,9 +348,7 @@ public class SoundPool {
* Automatically resumes all streams that were paused in previous
* calls to autoPause().
*/
- public final void autoResume() {
- mImpl.autoResume();
- }
+ public native final void autoResume();
/**
* Stop a playback stream.
@@ -365,9 +361,7 @@ public class SoundPool {
*
* @param streamID a streamID returned by the play() function
*/
- public final void stop(int streamID) {
- mImpl.stop(streamID);
- }
+ public native final void stop(int streamID);
/**
* Set stream volume.
@@ -381,9 +375,11 @@ public class SoundPool {
* @param leftVolume left volume value (range = 0.0 to 1.0)
* @param rightVolume right volume value (range = 0.0 to 1.0)
*/
- public final void setVolume(int streamID,
- float leftVolume, float rightVolume) {
- mImpl.setVolume(streamID, leftVolume, rightVolume);
+ public final void setVolume(int streamID, float leftVolume, float rightVolume) {
+ if (isRestricted()) {
+ return;
+ }
+ _setVolume(streamID, leftVolume, rightVolume);
}
/**
@@ -404,9 +400,7 @@ public class SoundPool {
*
* @param streamID a streamID returned by the play() function
*/
- public final void setPriority(int streamID, int priority) {
- mImpl.setPriority(streamID, priority);
- }
+ public native final void setPriority(int streamID, int priority);
/**
* Set loop mode.
@@ -419,9 +413,7 @@ public class SoundPool {
* @param streamID a streamID returned by the play() function
* @param loop loop mode (0 = no loop, -1 = loop forever)
*/
- public final void setLoop(int streamID, int loop) {
- mImpl.setLoop(streamID, loop);
- }
+ public native final void setLoop(int streamID, int loop);
/**
* Change playback rate.
@@ -435,9 +427,7 @@ public class SoundPool {
* @param streamID a streamID returned by the play() function
* @param rate playback rate (1.0 = normal playback, range 0.5 to 2.0)
*/
- public final void setRate(int streamID, float rate) {
- mImpl.setRate(streamID, rate);
- }
+ public native final void setRate(int streamID, float rate);
public interface OnLoadCompleteListener {
/**
@@ -454,356 +444,137 @@ public class SoundPool {
* Sets the callback hook for the OnLoadCompleteListener.
*/
public void setOnLoadCompleteListener(OnLoadCompleteListener listener) {
- mImpl.setOnLoadCompleteListener(listener);
- }
-
- /**
- * Release the SoundPool resources.
- *
- * Release all memory and native resources used by the SoundPool
- * object. The SoundPool can no longer be used and the reference
- * should be set to null.
- */
- public final void release() {
- mImpl.release();
- }
-
- /**
- * Interface for SoundPool implementations.
- * SoundPool is statically referenced and unconditionally called from all
- * over the framework, so we can't simply omit the class or make it throw
- * runtime exceptions, as doing so would break the framework. Instead we
- * now select either a real or no-op impl object based on whether media is
- * enabled.
- *
- * @hide
- */
- /* package */ interface SoundPoolDelegate {
- public int load(String path, int priority);
- public int load(Context context, int resId, int priority);
- public int load(AssetFileDescriptor afd, int priority);
- public int load(
- FileDescriptor fd, long offset, long length, int priority);
- public boolean unload(int soundID);
- public int play(
- int soundID, float leftVolume, float rightVolume,
- int priority, int loop, float rate);
- public void pause(int streamID);
- public void resume(int streamID);
- public void autoPause();
- public void autoResume();
- public void stop(int streamID);
- public void setVolume(int streamID, float leftVolume, float rightVolume);
- public void setVolume(int streamID, float volume);
- public void setPriority(int streamID, int priority);
- public void setLoop(int streamID, int loop);
- public void setRate(int streamID, float rate);
- public void setOnLoadCompleteListener(OnLoadCompleteListener listener);
- public void release();
- }
-
-
- /**
- * Real implementation of the delegate interface. This was formerly the
- * body of SoundPool itself.
- */
- /* package */ static class SoundPoolImpl implements SoundPoolDelegate {
- static { System.loadLibrary("soundpool"); }
-
- private final static String TAG = "SoundPool";
- private final static boolean DEBUG = false;
-
- private long mNativeContext; // accessed by native methods
-
- private EventHandler mEventHandler;
- private SoundPool.OnLoadCompleteListener mOnLoadCompleteListener;
- private SoundPool mProxy;
-
- private final Object mLock;
- private final AudioAttributes mAttributes;
- private final IAppOpsService mAppOps;
-
- // SoundPool messages
- //
- // must match SoundPool.h
- private static final int SAMPLE_LOADED = 1;
-
- public SoundPoolImpl(SoundPool proxy, int maxStreams, AudioAttributes attr) {
-
- // do native setup
- if (native_setup(new WeakReference(this), maxStreams, attr) != 0) {
- throw new RuntimeException("Native setup failed");
- }
- mLock = new Object();
- mProxy = proxy;
- mAttributes = attr;
- IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
- mAppOps = IAppOpsService.Stub.asInterface(b);
- }
-
- public int load(String path, int priority)
- {
- int id = 0;
- try {
- File f = new File(path);
- ParcelFileDescriptor fd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
- if (fd != null) {
- id = _load(fd.getFileDescriptor(), 0, f.length(), priority);
- fd.close();
- }
- } catch (java.io.IOException e) {
- Log.e(TAG, "error loading " + path);
- }
- return id;
- }
-
- @Override
- public int load(Context context, int resId, int priority) {
- AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId);
- int id = 0;
- if (afd != null) {
- id = _load(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength(), priority);
- try {
- afd.close();
- } catch (java.io.IOException ex) {
- //Log.d(TAG, "close failed:", ex);
- }
- }
- return id;
- }
-
- @Override
- public int load(AssetFileDescriptor afd, int priority) {
- if (afd != null) {
- long len = afd.getLength();
- if (len < 0) {
- throw new AndroidRuntimeException("no length for fd");
+ synchronized(mLock) {
+ if (listener != null) {
+ // setup message handler
+ Looper looper;
+ if ((looper = Looper.myLooper()) != null) {
+ mEventHandler = new EventHandler(looper);
+ } else if ((looper = Looper.getMainLooper()) != null) {
+ mEventHandler = new EventHandler(looper);
+ } else {
+ mEventHandler = null;
}
- return _load(afd.getFileDescriptor(), afd.getStartOffset(), len, priority);
} else {
- return 0;
+ mEventHandler = null;
}
+ mOnLoadCompleteListener = listener;
}
+ }
- @Override
- public int load(FileDescriptor fd, long offset, long length, int priority) {
- return _load(fd, offset, length, priority);
- }
-
- private native final int _load(FileDescriptor fd, long offset, long length, int priority);
-
- @Override
- public native final boolean unload(int soundID);
-
- @Override
- 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);
+ private boolean isRestricted() {
+ if ((mAttributes.getFlags() & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0) {
+ return false;
}
-
- public native final int _play(int soundID, float leftVolume, float rightVolume,
- int priority, int loop, float rate);
-
- private boolean isRestricted() {
- if ((mAttributes.getFlags() & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0) {
- return false;
- }
- try {
- final int mode = mAppOps.checkAudioOperation(AppOpsManager.OP_PLAY_AUDIO,
- mAttributes.getUsage(),
- Process.myUid(), ActivityThread.currentPackageName());
- return mode != AppOpsManager.MODE_ALLOWED;
- } catch (RemoteException e) {
- return false;
- }
+ try {
+ final int mode = mAppOps.checkAudioOperation(AppOpsManager.OP_PLAY_AUDIO,
+ mAttributes.getUsage(),
+ Process.myUid(), ActivityThread.currentPackageName());
+ return mode != AppOpsManager.MODE_ALLOWED;
+ } catch (RemoteException e) {
+ return false;
}
+ }
- @Override
- public native final void pause(int streamID);
+ private native final int _load(FileDescriptor fd, long offset, long length, int priority);
- @Override
- public native final void resume(int streamID);
+ private native final int native_setup(Object weakRef, int maxStreams,
+ Object/*AudioAttributes*/ attributes);
- @Override
- public native final void autoPause();
+ private native final int _play(int soundID, float leftVolume, float rightVolume,
+ int priority, int loop, float rate);
- @Override
- public native final void autoResume();
+ private native final void _setVolume(int streamID, float leftVolume, float rightVolume);
- @Override
- public native final void stop(int streamID);
+ // post event from native code to message handler
+ @SuppressWarnings("unchecked")
+ private static void postEventFromNative(Object ref, int msg, int arg1, int arg2, Object obj) {
+ SoundPool soundPool = ((WeakReference<SoundPool>) ref).get();
+ if (soundPool == null)
+ return;
- @Override
- public final void setVolume(int streamID, float leftVolume, float rightVolume) {
- if (isRestricted()) {
- return;
- }
- _setVolume(streamID, leftVolume, rightVolume);
+ if (soundPool.mEventHandler != null) {
+ Message m = soundPool.mEventHandler.obtainMessage(msg, arg1, arg2, obj);
+ soundPool.mEventHandler.sendMessage(m);
}
+ }
- private native final void _setVolume(int streamID, float leftVolume, float rightVolume);
-
- @Override
- public void setVolume(int streamID, float volume) {
- setVolume(streamID, volume, volume);
+ private final class EventHandler extends Handler {
+ public EventHandler(Looper looper) {
+ super(looper);
}
@Override
- public native final void setPriority(int streamID, int priority);
-
- @Override
- public native final void setLoop(int streamID, int loop);
-
- @Override
- public native final void setRate(int streamID, float rate);
-
- @Override
- public void setOnLoadCompleteListener(SoundPool.OnLoadCompleteListener listener)
- {
- synchronized(mLock) {
- if (listener != null) {
- // setup message handler
- Looper looper;
- if ((looper = Looper.myLooper()) != null) {
- mEventHandler = new EventHandler(mProxy, looper);
- } else if ((looper = Looper.getMainLooper()) != null) {
- mEventHandler = new EventHandler(mProxy, looper);
- } else {
- mEventHandler = null;
+ public void handleMessage(Message msg) {
+ switch(msg.what) {
+ case SAMPLE_LOADED:
+ if (DEBUG) Log.d(TAG, "Sample " + msg.arg1 + " loaded");
+ synchronized(mLock) {
+ if (mOnLoadCompleteListener != null) {
+ mOnLoadCompleteListener.onLoadComplete(SoundPool.this, msg.arg1, msg.arg2);
}
- } else {
- mEventHandler = null;
}
- mOnLoadCompleteListener = listener;
- }
- }
-
- private class EventHandler extends Handler
- {
- private SoundPool mSoundPool;
-
- public EventHandler(SoundPool soundPool, Looper looper) {
- super(looper);
- mSoundPool = soundPool;
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch(msg.what) {
- case SAMPLE_LOADED:
- if (DEBUG) Log.d(TAG, "Sample " + msg.arg1 + " loaded");
- synchronized(mLock) {
- if (mOnLoadCompleteListener != null) {
- mOnLoadCompleteListener.onLoadComplete(mSoundPool, msg.arg1, msg.arg2);
- }
- }
- break;
- default:
- Log.e(TAG, "Unknown message type " + msg.what);
- return;
- }
- }
- }
-
- // post event from native code to message handler
- private static void postEventFromNative(Object weakRef, int msg, int arg1, int arg2, Object obj)
- {
- SoundPoolImpl soundPoolImpl = (SoundPoolImpl)((WeakReference)weakRef).get();
- if (soundPoolImpl == null)
+ break;
+ default:
+ Log.e(TAG, "Unknown message type " + msg.what);
return;
-
- if (soundPoolImpl.mEventHandler != null) {
- Message m = soundPoolImpl.mEventHandler.obtainMessage(msg, arg1, arg2, obj);
- soundPoolImpl.mEventHandler.sendMessage(m);
}
}
-
- public native final void release();
-
- private native final int native_setup(Object weakRef, int maxStreams,
- Object/*AudioAttributes*/ attributes);
-
- protected void finalize() { release(); }
}
/**
- * No-op implementation of SoundPool.
- * Used when media is disabled by the system.
- * @hide
+ * Builder class for {@link SoundPool} objects.
*/
- /* package */ static class SoundPoolStub implements SoundPoolDelegate {
- public SoundPoolStub() { }
-
- public int load(String path, int priority) {
- return 0;
- }
-
- @Override
- public int load(Context context, int resId, int priority) {
- return 0;
- }
-
- @Override
- public int load(AssetFileDescriptor afd, int priority) {
- return 0;
- }
-
- @Override
- public int load(FileDescriptor fd, long offset, long length, int priority) {
- return 0;
- }
+ public static class Builder {
+ private int mMaxStreams = 1;
+ private AudioAttributes mAudioAttributes;
- @Override
- public final boolean unload(int soundID) {
- return true;
+ /**
+ * Constructs a new Builder with the defaults format values.
+ * If not provided, the maximum number of streams is 1 (see {@link #setMaxStreams(int)} to
+ * change it), and the audio attributes have a usage value of
+ * {@link AudioAttributes#USAGE_MEDIA} (see {@link #setAudioAttributes(AudioAttributes)} to
+ * change them).
+ */
+ public Builder() {
}
- @Override
- public final int play(int soundID, float leftVolume, float rightVolume,
- int priority, int loop, float rate) {
- return 0;
+ /**
+ * Sets the maximum of number of simultaneous streams that can be played simultaneously.
+ * @param maxStreams a value equal to 1 or greater.
+ * @return the same Builder instance
+ * @throws IllegalArgumentException
+ */
+ public Builder setMaxStreams(int maxStreams) throws IllegalArgumentException {
+ if (maxStreams <= 0) {
+ throw new IllegalArgumentException(
+ "Strictly positive value required for the maximum number of streams");
+ }
+ mMaxStreams = maxStreams;
+ return this;
}
- @Override
- public final void pause(int streamID) { }
-
- @Override
- public final void resume(int streamID) { }
-
- @Override
- public final void autoPause() { }
-
- @Override
- public final void autoResume() { }
-
- @Override
- public final void stop(int streamID) { }
-
- @Override
- public final void setVolume(int streamID,
- float leftVolume, float rightVolume) { }
-
- @Override
- public void setVolume(int streamID, float volume) {
+ /**
+ * Sets the {@link AudioAttributes}. For examples, game applications will use attributes
+ * built with usage information set to {@link AudioAttributes#USAGE_GAME}.
+ * @param attributes a non-null
+ * @return
+ */
+ public Builder setAudioAttributes(AudioAttributes attributes)
+ throws IllegalArgumentException {
+ if (attributes == null) {
+ throw new IllegalArgumentException("Invalid null AudioAttributes");
+ }
+ mAudioAttributes = attributes;
+ return this;
}
- @Override
- public final void setPriority(int streamID, int priority) { }
-
- @Override
- public final void setLoop(int streamID, int loop) { }
-
- @Override
- public final void setRate(int streamID, float rate) { }
-
- @Override
- public void setOnLoadCompleteListener(SoundPool.OnLoadCompleteListener listener) {
+ public SoundPool build() {
+ if (mAudioAttributes == null) {
+ mAudioAttributes = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_MEDIA).build();
+ }
+ return new SoundPool(mMaxStreams, mAudioAttributes);
}
-
- @Override
- public final void release() { }
}
}
diff --git a/media/java/android/media/TtmlRenderer.java b/media/java/android/media/TtmlRenderer.java
index 75133c9..9d587b9 100644
--- a/media/java/android/media/TtmlRenderer.java
+++ b/media/java/android/media/TtmlRenderer.java
@@ -17,27 +17,15 @@
package android.media;
import android.content.Context;
-import android.graphics.Color;
-import android.media.SubtitleTrack.RenderingWidget.OnChangedListener;
-import android.text.Layout.Alignment;
-import android.text.SpannableStringBuilder;
import android.text.TextUtils;
-import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
-import android.view.ViewGroup;
-import android.view.View.MeasureSpec;
-import android.view.ViewGroup.LayoutParams;
import android.view.accessibility.CaptioningManager;
-import android.view.accessibility.CaptioningManager.CaptionStyle;
-import android.view.accessibility.CaptioningManager.CaptioningChangeListener;
import android.widget.LinearLayout;
import android.widget.TextView;
-import com.android.internal.widget.SubtitleView;
-
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
diff --git a/media/java/android/media/Utils.java b/media/java/android/media/Utils.java
index df0daaf..9e01e65 100644
--- a/media/java/android/media/Utils.java
+++ b/media/java/android/media/Utils.java
@@ -26,8 +26,6 @@ import java.util.Arrays;
import java.util.Comparator;
import java.util.Vector;
-import static com.android.internal.util.Preconditions.checkNotNull;
-
// package private
class Utils {
private static final String TAG = "Utils";
diff --git a/media/java/android/media/VolumePolicy.aidl b/media/java/android/media/VolumePolicy.aidl
new file mode 100644
index 0000000..371f798
--- /dev/null
+++ b/media/java/android/media/VolumePolicy.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2015 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;
+
+parcelable VolumePolicy;
diff --git a/media/java/android/media/VolumePolicy.java b/media/java/android/media/VolumePolicy.java
new file mode 100644
index 0000000..2d3376a
--- /dev/null
+++ b/media/java/android/media/VolumePolicy.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2015 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.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public final class VolumePolicy implements Parcelable {
+ public static final VolumePolicy DEFAULT = new VolumePolicy(false, false, true, 400);
+
+ /** Allow volume adjustments lower from vibrate to enter ringer mode = silent */
+ public final boolean volumeDownToEnterSilent;
+
+ /** Allow volume adjustments higher to exit ringer mode = silent */
+ public final boolean volumeUpToExitSilent;
+
+ /** Automatically enter do not disturb when ringer mode = silent */
+ public final boolean doNotDisturbWhenSilent;
+
+ /** Only allow volume adjustment from vibrate to silent after this
+ number of milliseconds since an adjustment from normal to vibrate. */
+ public final int vibrateToSilentDebounce;
+
+ public VolumePolicy(boolean volumeDownToEnterSilent, boolean volumeUpToExitSilent,
+ boolean doNotDisturbWhenSilent, int vibrateToSilentDebounce) {
+ this.volumeDownToEnterSilent = volumeDownToEnterSilent;
+ this.volumeUpToExitSilent = volumeUpToExitSilent;
+ this.doNotDisturbWhenSilent = doNotDisturbWhenSilent;
+ this.vibrateToSilentDebounce = vibrateToSilentDebounce;
+ }
+
+ @Override
+ public String toString() {
+ return "VolumePolicy[volumeDownToEnterSilent=" + volumeDownToEnterSilent
+ + ",volumeUpToExitSilent=" + volumeUpToExitSilent
+ + ",doNotDisturbWhenSilent=" + doNotDisturbWhenSilent
+ + ",vibrateToSilentDebounce=" + vibrateToSilentDebounce + "]";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(volumeDownToEnterSilent ? 1 : 0);
+ dest.writeInt(volumeUpToExitSilent ? 1 : 0);
+ dest.writeInt(doNotDisturbWhenSilent ? 1 : 0);
+ dest.writeInt(vibrateToSilentDebounce);
+ }
+
+ public static final Parcelable.Creator<VolumePolicy> CREATOR
+ = new Parcelable.Creator<VolumePolicy>() {
+ @Override
+ public VolumePolicy createFromParcel(Parcel p) {
+ return new VolumePolicy(p.readInt() != 0,
+ p.readInt() != 0,
+ p.readInt() != 0,
+ p.readInt());
+ }
+
+ @Override
+ public VolumePolicy[] newArray(int size) {
+ return new VolumePolicy[size];
+ }
+ };
+} \ No newline at end of file
diff --git a/media/java/android/media/WebVttRenderer.java b/media/java/android/media/WebVttRenderer.java
index 69e0ea6..91c53fa 100644
--- a/media/java/android/media/WebVttRenderer.java
+++ b/media/java/android/media/WebVttRenderer.java
@@ -433,7 +433,9 @@ class TextTrackCue extends SubtitleTrack.Cue {
mRegionId.equals(cue.mRegionId) &&
mSnapToLines == cue.mSnapToLines &&
mAutoLinePosition == cue.mAutoLinePosition &&
- (mAutoLinePosition || mLinePosition == cue.mLinePosition) &&
+ (mAutoLinePosition ||
+ ((mLinePosition != null && mLinePosition.equals(cue.mLinePosition)) ||
+ (mLinePosition == null && cue.mLinePosition == null))) &&
mTextPosition == cue.mTextPosition &&
mSize == cue.mSize &&
mAlignment == cue.mAlignment &&
diff --git a/media/java/android/media/audiofx/AcousticEchoCanceler.java b/media/java/android/media/audiofx/AcousticEchoCanceler.java
index 4b59c88..f5f98ef 100644
--- a/media/java/android/media/audiofx/AcousticEchoCanceler.java
+++ b/media/java/android/media/audiofx/AcousticEchoCanceler.java
@@ -68,9 +68,8 @@ public class AcousticEchoCanceler extends AudioEffect {
Log.w(TAG, "not enough resources");
} catch (RuntimeException e) {
Log.w(TAG, "not enough memory");
- } finally {
- return aec;
}
+ return aec;
}
/**
diff --git a/media/java/android/media/audiofx/AutomaticGainControl.java b/media/java/android/media/audiofx/AutomaticGainControl.java
index 83eb4e9..4a6b1f3 100644
--- a/media/java/android/media/audiofx/AutomaticGainControl.java
+++ b/media/java/android/media/audiofx/AutomaticGainControl.java
@@ -68,9 +68,8 @@ public class AutomaticGainControl extends AudioEffect {
Log.w(TAG, "not enough resources");
} catch (RuntimeException e) {
Log.w(TAG, "not enough memory");
- } finally {
- return agc;
}
+ return agc;
}
/**
diff --git a/media/java/android/media/audiofx/NoiseSuppressor.java b/media/java/android/media/audiofx/NoiseSuppressor.java
index 0ea42ab..bca990f 100644
--- a/media/java/android/media/audiofx/NoiseSuppressor.java
+++ b/media/java/android/media/audiofx/NoiseSuppressor.java
@@ -70,9 +70,8 @@ public class NoiseSuppressor extends AudioEffect {
Log.w(TAG, "not enough resources");
} catch (RuntimeException e) {
Log.w(TAG, "not enough memory");
- } finally {
- return ns;
}
+ return ns;
}
/**
diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
index 019309d..917e07b 100644
--- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
@@ -16,12 +16,8 @@
package android.media.audiopolicy;
-import android.media.AudioAttributes;
import android.media.AudioFormat;
-import android.media.AudioManager;
import android.media.audiopolicy.AudioMixingRule.AttributeMatchCriterion;
-import android.os.Binder;
-import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
diff --git a/media/java/android/media/midi/IMidiListener.aidl b/media/java/android/media/midi/IMidiDeviceListener.aidl
index a4129e9..31c66e3 100644
--- a/media/java/android/media/midi/IMidiListener.aidl
+++ b/media/java/android/media/midi/IMidiDeviceListener.aidl
@@ -17,10 +17,12 @@
package android.media.midi;
import android.media.midi.MidiDeviceInfo;
+import android.media.midi.MidiDeviceStatus;
/** @hide */
-oneway interface IMidiListener
+oneway interface IMidiDeviceListener
{
void onDeviceAdded(in MidiDeviceInfo device);
void onDeviceRemoved(in MidiDeviceInfo device);
+ void onDeviceStatusChanged(in MidiDeviceStatus status);
}
diff --git a/media/java/android/media/midi/IMidiDeviceServer.aidl b/media/java/android/media/midi/IMidiDeviceServer.aidl
index 71914ad..642078a 100644
--- a/media/java/android/media/midi/IMidiDeviceServer.aidl
+++ b/media/java/android/media/midi/IMidiDeviceServer.aidl
@@ -21,6 +21,10 @@ import android.os.ParcelFileDescriptor;
/** @hide */
interface IMidiDeviceServer
{
- ParcelFileDescriptor openInputPort(int portNumber);
- ParcelFileDescriptor openOutputPort(int portNumber);
+ ParcelFileDescriptor openInputPort(IBinder token, int portNumber);
+ ParcelFileDescriptor openOutputPort(IBinder token, int portNumber);
+ void closePort(IBinder token);
+
+ // connects the input port pfd to the specified output port
+ void connectPorts(IBinder token, in ParcelFileDescriptor pfd, int outputPortNumber);
}
diff --git a/media/java/android/media/midi/IMidiManager.aidl b/media/java/android/media/midi/IMidiManager.aidl
index bba35f5..74c63b4 100644
--- a/media/java/android/media/midi/IMidiManager.aidl
+++ b/media/java/android/media/midi/IMidiManager.aidl
@@ -16,9 +16,10 @@
package android.media.midi;
+import android.media.midi.IMidiDeviceListener;
import android.media.midi.IMidiDeviceServer;
-import android.media.midi.IMidiListener;
import android.media.midi.MidiDeviceInfo;
+import android.media.midi.MidiDeviceStatus;
import android.os.Bundle;
import android.os.IBinder;
@@ -28,14 +29,28 @@ interface IMidiManager
MidiDeviceInfo[] getDeviceList();
// for device creation & removal notifications
- void registerListener(IBinder token, in IMidiListener listener);
- void unregisterListener(IBinder token, in IMidiListener listener);
+ void registerListener(IBinder token, in IMidiDeviceListener listener);
+ void unregisterListener(IBinder token, in IMidiDeviceListener listener);
- // for communicating with MIDI devices
+ // for opening built-in MIDI devices
IMidiDeviceServer openDevice(IBinder token, in MidiDeviceInfo device);
- // for implementing virtual MIDI devices
+ // for registering built-in MIDI devices
MidiDeviceInfo registerDeviceServer(in IMidiDeviceServer server, int numInputPorts,
- int numOutputPorts, in Bundle properties, boolean isPrivate, int type);
+ int numOutputPorts, in String[] inputPortNames, in String[] outputPortNames,
+ in Bundle properties, int type);
+
+ // for unregistering built-in MIDI devices
void unregisterDeviceServer(in IMidiDeviceServer server);
+
+ // used by MidiDeviceService to access the MidiDeviceInfo that was created based on its
+ // manifest's meta-data
+ MidiDeviceInfo getServiceDeviceInfo(String packageName, String className);
+
+ // used for client's to retrieve a device's MidiDeviceStatus
+ MidiDeviceStatus getDeviceStatus(in MidiDeviceInfo deviceInfo);
+
+ // used by MIDI devices to report their status
+ // the token is used by MidiService for death notification
+ void setDeviceStatus(IBinder token, in MidiDeviceStatus status);
}
diff --git a/media/java/android/media/midi/MidiDevice.java b/media/java/android/media/midi/MidiDevice.java
index 36710fd..569f7c6 100644
--- a/media/java/android/media/midi/MidiDevice.java
+++ b/media/java/android/media/midi/MidiDevice.java
@@ -16,36 +16,70 @@
package android.media.midi;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.os.Binder;
+import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
+import dalvik.system.CloseGuard;
+
+import libcore.io.IoUtils;
+
+import java.io.Closeable;
import java.io.IOException;
-import java.util.ArrayList;
/**
- * This class is used for sending and receiving data to and from an MIDI device
+ * This class is used for sending and receiving data to and from a MIDI device
* Instances of this class are created by {@link MidiManager#openDevice}.
*
* CANDIDATE FOR PUBLIC API
* @hide
*/
-public final class MidiDevice {
+public final class MidiDevice implements Closeable {
private static final String TAG = "MidiDevice";
private final MidiDeviceInfo mDeviceInfo;
- private final IMidiDeviceServer mServer;
+ private final IMidiDeviceServer mDeviceServer;
+ private Context mContext;
+ private ServiceConnection mServiceConnection;
- /**
- * MidiDevice should only be instantiated by MidiManager
- * @hide
- */
- public MidiDevice(MidiDeviceInfo deviceInfo, IMidiDeviceServer server) {
+
+ private final CloseGuard mGuard = CloseGuard.get();
+
+ public class MidiConnection implements Closeable {
+ private final IBinder mToken;
+ private final MidiInputPort mInputPort;
+
+ MidiConnection(IBinder token, MidiInputPort inputPort) {
+ mToken = token;
+ mInputPort = inputPort;
+ }
+
+ @Override
+ public void close() throws IOException {
+ try {
+ mDeviceServer.closePort(mToken);
+ IoUtils.closeQuietly(mInputPort);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in MidiConnection.close");
+ }
+ }
+ }
+
+ /* package */ MidiDevice(MidiDeviceInfo deviceInfo, IMidiDeviceServer server) {
+ this(deviceInfo, server, null, null);
+ }
+
+ /* package */ MidiDevice(MidiDeviceInfo deviceInfo, IMidiDeviceServer server,
+ Context context, ServiceConnection serviceConnection) {
mDeviceInfo = deviceInfo;
- mServer = server;
+ mDeviceServer = server;
+ mContext = context;
+ mServiceConnection = serviceConnection;
+ mGuard.open("close");
}
/**
@@ -65,11 +99,12 @@ public final class MidiDevice {
*/
public MidiInputPort openInputPort(int portNumber) {
try {
- ParcelFileDescriptor pfd = mServer.openInputPort(portNumber);
+ IBinder token = new Binder();
+ ParcelFileDescriptor pfd = mDeviceServer.openInputPort(token, portNumber);
if (pfd == null) {
return null;
}
- return new MidiInputPort(pfd, portNumber);
+ return new MidiInputPort(mDeviceServer, token, pfd, portNumber);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in openInputPort");
return null;
@@ -84,17 +119,70 @@ public final class MidiDevice {
*/
public MidiOutputPort openOutputPort(int portNumber) {
try {
- ParcelFileDescriptor pfd = mServer.openOutputPort(portNumber);
+ IBinder token = new Binder();
+ ParcelFileDescriptor pfd = mDeviceServer.openOutputPort(token, portNumber);
if (pfd == null) {
return null;
}
- return new MidiOutputPort(pfd, portNumber);
+ return new MidiOutputPort(mDeviceServer, token, pfd, portNumber);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in openOutputPort");
return null;
}
}
+ /**
+ * Connects the supplied {@link MidiInputPort} to the output port of this device
+ * with the specified port number. Once the connection is made, the MidiInput port instance
+ * can no longer receive data via its {@link MidiReciever.receive} method.
+ * This method returns a {@link #MidiConnection} object, which can be used to close the connection
+ * @param inputPort the inputPort to connect
+ * @param outputPortNumber the port number of the output port to connect inputPort to.
+ * @return {@link #MidiConnection} object if the connection is successful, or null in case of failure
+ */
+ public MidiConnection connectPorts(MidiInputPort inputPort, int outputPortNumber) {
+ if (outputPortNumber < 0 || outputPortNumber >= mDeviceInfo.getOutputPortCount()) {
+ throw new IllegalArgumentException("outputPortNumber out of range");
+ }
+
+ ParcelFileDescriptor pfd = inputPort.claimFileDescriptor();
+ if (pfd == null) {
+ return null;
+ }
+ try {
+ IBinder token = new Binder();
+ mDeviceServer.connectPorts(token, pfd, outputPortNumber);
+ // close our copy of the file descriptor
+ IoUtils.closeQuietly(pfd);
+ return new MidiConnection(token, inputPort);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in connectPorts");
+ return null;
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ synchronized (mGuard) {
+ mGuard.close();
+ if (mContext != null && mServiceConnection != null) {
+ mContext.unbindService(mServiceConnection);
+ mContext = null;
+ mServiceConnection = null;
+ }
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ mGuard.warnIfOpen();
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
@Override
public String toString() {
return ("MidiDevice: " + mDeviceInfo.toString());
diff --git a/media/java/android/media/midi/MidiDeviceInfo.java b/media/java/android/media/midi/MidiDeviceInfo.java
index fd35052..7201e25 100644
--- a/media/java/android/media/midi/MidiDeviceInfo.java
+++ b/media/java/android/media/midi/MidiDeviceInfo.java
@@ -27,11 +27,8 @@ import android.os.Parcelable;
*
* This class is just an immutable object to encapsulate the MIDI device description.
* Use the MidiDevice class to actually communicate with devices.
- *
- * CANDIDATE FOR PUBLIC API
- * @hide
*/
-public class MidiDeviceInfo implements Parcelable {
+public final class MidiDeviceInfo implements Parcelable {
private static final String TAG = "MidiDeviceInfo";
@@ -45,11 +42,17 @@ public class MidiDeviceInfo implements Parcelable {
*/
public static final int TYPE_VIRTUAL = 2;
- private final int mType; // USB or virtual
- private final int mId; // unique ID generated by MidiService
- private final int mInputPortCount;
- private final int mOutputPortCount;
- private final Bundle mProperties;
+ /**
+ * Constant representing Bluetooth MIDI devices for {@link #getType}
+ */
+ public static final int TYPE_BLUETOOTH = 3;
+
+ /**
+ * Bundle key for the device's user visible name property.
+ * Used with the {@link android.os.Bundle} returned by {@link #getProperties}.
+ * For USB devices, this is a concatenation of the manufacturer and product names.
+ */
+ public static final String PROPERTY_NAME = "name";
/**
* Bundle key for the device's manufacturer name property.
@@ -59,11 +62,11 @@ public class MidiDeviceInfo implements Parcelable {
public static final String PROPERTY_MANUFACTURER = "manufacturer";
/**
- * Bundle key for the device's model name property.
+ * Bundle key for the device's product name property.
* Used with the {@link android.os.Bundle} returned by {@link #getProperties}
* Matches the USB device product name string for USB MIDI devices.
*/
- public static final String PROPERTY_MODEL = "model";
+ public static final String PROPERTY_PRODUCT = "product";
/**
* Bundle key for the device's serial number property.
@@ -80,9 +83,18 @@ public class MidiDeviceInfo implements Parcelable {
public static final String PROPERTY_USB_DEVICE = "usb_device";
/**
+ * Bundle key for the device's {@link android.bluetooth.BluetoothDevice}.
+ * Only set for Bluetooth MIDI devices.
+ * Used with the {@link android.os.Bundle} returned by {@link #getProperties}
+ */
+ public static final String PROPERTY_BLUETOOTH_DEVICE = "bluetooth_device";
+
+ /**
* Bundle key for the device's ALSA card number.
* Only set for USB MIDI devices.
* Used with the {@link android.os.Bundle} returned by {@link #getProperties}
+ *
+ * @hide
*/
public static final String PROPERTY_ALSA_CARD = "alsa_card";
@@ -90,20 +102,101 @@ public class MidiDeviceInfo implements Parcelable {
* Bundle key for the device's ALSA device number.
* Only set for USB MIDI devices.
* Used with the {@link android.os.Bundle} returned by {@link #getProperties}
+ *
+ * @hide
*/
public static final String PROPERTY_ALSA_DEVICE = "alsa_device";
/**
+ * {@link android.content.pm.ServiceInfo} for the service hosting the device implementation.
+ * Only set for Virtual MIDI devices.
+ * Used with the {@link android.os.Bundle} returned by {@link #getProperties}
+ *
+ * @hide
+ */
+ public static final String PROPERTY_SERVICE_INFO = "service_info";
+
+ /**
+ * Contains information about an input or output port.
+ */
+ public static final class PortInfo {
+ /**
+ * Port type for input ports
+ */
+ public static final int TYPE_INPUT = 1;
+
+ /**
+ * Port type for output ports
+ */
+ public static final int TYPE_OUTPUT = 2;
+
+ private final int mPortType;
+ private final int mPortNumber;
+ private final String mName;
+
+ PortInfo(int type, int portNumber, String name) {
+ mPortType = type;
+ mPortNumber = portNumber;
+ mName = (name == null ? "" : name);
+ }
+
+ /**
+ * Returns the port type of the port (either {@link #TYPE_INPUT} or {@link #TYPE_OUTPUT})
+ * @return the port type
+ */
+ public int getType() {
+ return mPortType;
+ }
+
+ /**
+ * Returns the port number of the port
+ * @return the port number
+ */
+ public int getPortNumber() {
+ return mPortNumber;
+ }
+
+ /**
+ * Returns the name of the port, or empty string if the port has no name
+ * @return the port name
+ */
+ public String getName() {
+ return mName;
+ }
+ }
+
+ private final int mType; // USB or virtual
+ private final int mId; // unique ID generated by MidiService
+ private final int mInputPortCount;
+ private final int mOutputPortCount;
+ private final String[] mInputPortNames;
+ private final String[] mOutputPortNames;
+ private final Bundle mProperties;
+ private final boolean mIsPrivate;
+
+ /**
* MidiDeviceInfo should only be instantiated by MidiService implementation
* @hide
*/
public MidiDeviceInfo(int type, int id, int numInputPorts, int numOutputPorts,
- Bundle properties) {
+ String[] inputPortNames, String[] outputPortNames, Bundle properties,
+ boolean isPrivate) {
mType = type;
mId = id;
mInputPortCount = numInputPorts;
mOutputPortCount = numOutputPorts;
+ if (inputPortNames == null) {
+ mInputPortNames = new String[numInputPorts];
+ } else {
+ mInputPortNames = inputPortNames;
+ }
+ if (outputPortNames == null) {
+ mOutputPortNames = new String[numOutputPorts];
+ } else {
+ mOutputPortNames = outputPortNames;
+ }
mProperties = properties;
+ mIsPrivate = isPrivate;
}
/**
@@ -144,6 +237,32 @@ public class MidiDeviceInfo implements Parcelable {
}
/**
+ * Returns information about an input port.
+ *
+ * @param portNumber the number of the input port
+ * @return the input port's information object
+ */
+ public PortInfo getInputPortInfo(int portNumber) {
+ if (portNumber < 0 || portNumber >= mInputPortCount) {
+ throw new IllegalArgumentException("portNumber out of range");
+ }
+ return new PortInfo(PortInfo.TYPE_INPUT, portNumber, mInputPortNames[portNumber]);
+ }
+
+ /**
+ * Returns information about an output port.
+ *
+ * @param portNumber the number of the output port
+ * @return the output port's information object
+ */
+ public PortInfo getOutputPortInfo(int portNumber) {
+ if (portNumber < 0 || portNumber >= mOutputPortCount) {
+ throw new IllegalArgumentException("portNumber out of range");
+ }
+ return new PortInfo(PortInfo.TYPE_OUTPUT, portNumber, mOutputPortNames[portNumber]);
+ }
+
+ /**
* Returns the {@link android.os.Bundle} containing the device's properties.
*
* @return the device's properties
@@ -152,6 +271,16 @@ public class MidiDeviceInfo implements Parcelable {
return mProperties;
}
+ /**
+ * Returns true if the device is private. Private devices are only visible and accessible
+ * to clients with the same UID as the application that is hosting the device.
+ *
+ * @return true if the device is private
+ */
+ public boolean isPrivate() {
+ return mIsPrivate;
+ }
+
@Override
public boolean equals(Object o) {
if (o instanceof MidiDeviceInfo) {
@@ -171,7 +300,8 @@ public class MidiDeviceInfo implements Parcelable {
return ("MidiDeviceInfo[mType=" + mType +
",mInputPortCount=" + mInputPortCount +
",mOutputPortCount=" + mOutputPortCount +
- ",mProperties=" + mProperties);
+ ",mProperties=" + mProperties +
+ ",mIsPrivate=" + mIsPrivate);
}
public static final Parcelable.Creator<MidiDeviceInfo> CREATOR =
@@ -181,8 +311,12 @@ public class MidiDeviceInfo implements Parcelable {
int id = in.readInt();
int inputPorts = in.readInt();
int outputPorts = in.readInt();
+ String[] inputPortNames = in.createStringArray();
+ String[] outputPortNames = in.createStringArray();
Bundle properties = in.readBundle();
- return new MidiDeviceInfo(type, id, inputPorts, outputPorts, properties);
+ boolean isPrivate = (in.readInt() == 1);
+ return new MidiDeviceInfo(type, id, inputPorts, outputPorts,
+ inputPortNames, outputPortNames, properties, isPrivate);
}
public MidiDeviceInfo[] newArray(int size) {
@@ -199,6 +333,9 @@ public class MidiDeviceInfo implements Parcelable {
parcel.writeInt(mId);
parcel.writeInt(mInputPortCount);
parcel.writeInt(mOutputPortCount);
+ parcel.writeStringArray(mInputPortNames);
+ parcel.writeStringArray(mOutputPortNames);
parcel.writeBundle(mProperties);
+ parcel.writeInt(mIsPrivate ? 1 : 0);
}
}
diff --git a/media/java/android/media/midi/MidiDeviceServer.java b/media/java/android/media/midi/MidiDeviceServer.java
index 3317baa..d27351f 100644
--- a/media/java/android/media/midi/MidiDeviceServer.java
+++ b/media/java/android/media/midi/MidiDeviceServer.java
@@ -16,21 +16,26 @@
package android.media.midi;
+import android.os.Binder;
+import android.os.IBinder;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
import android.os.RemoteException;
import android.system.OsConstants;
import android.util.Log;
+import dalvik.system.CloseGuard;
+
+import libcore.io.IoUtils;
+
import java.io.Closeable;
import java.io.IOException;
-import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
- * This class is used to provide the implemention of MIDI device.
- * Applications may call {@link MidiManager#createDeviceServer}
- * to create an instance of this class to implement a virtual MIDI device.
+ * Internal class used for providing an implementation for a MIDI device.
*
- * CANDIDATE FOR PUBLIC API
* @hide
*/
public final class MidiDeviceServer implements Closeable {
@@ -40,64 +45,129 @@ public final class MidiDeviceServer implements Closeable {
// MidiDeviceInfo for the device implemented by this server
private MidiDeviceInfo mDeviceInfo;
- private int mInputPortCount;
- private int mOutputPortCount;
+ private final int mInputPortCount;
+ private final int mOutputPortCount;
+
+ // MidiReceivers for receiving data on our input ports
+ private final MidiReceiver[] mInputPortReceivers;
+
+ // MidiDispatchers for sending data on our output ports
+ private MidiDispatcher[] mOutputPortDispatchers;
+
+ // MidiOutputPorts for clients connected to our input ports
+ private final MidiOutputPort[] mInputPortOutputPorts;
+
+ // List of all MidiInputPorts we created
+ private final CopyOnWriteArrayList<MidiInputPort> mInputPorts
+ = new CopyOnWriteArrayList<MidiInputPort>();
+
+
+ // for reporting device status
+ private final IBinder mDeviceStatusToken = new Binder();
+ private final boolean[] mInputPortOpen;
+ private final int[] mOutputPortOpenCount;
+
+ private final CloseGuard mGuard = CloseGuard.get();
+ private boolean mIsClosed;
+
+ private final Callback mCallback;
+
+ public interface Callback {
+ /**
+ * Called to notify when an our device status has changed
+ * @param server the {@link MidiDeviceServer} that changed
+ * @param status the {@link MidiDeviceStatus} for the device
+ */
+ public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status);
+ }
+
+ abstract private class PortClient implements IBinder.DeathRecipient {
+ final IBinder mToken;
- // output ports for receiving messages from our clients
- // we can have only one per port number
- private MidiOutputPort[] mInputPortSenders;
+ PortClient(IBinder token) {
+ mToken = token;
- // receivers attached to our input ports
- private ArrayList<MidiReceiver>[] mInputPortReceivers;
+ try {
+ token.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ close();
+ }
+ }
- // input ports for sending messages to our clients
- // we can have multiple outputs per port number
- private ArrayList<MidiInputPort>[] mOutputPortReceivers;
+ abstract void close();
- // subclass of MidiInputPort for passing to clients
- // that notifies us when the connection has failed
- private class ServerInputPort extends MidiInputPort {
- ServerInputPort(ParcelFileDescriptor pfd, int portNumber) {
- super(pfd, portNumber);
+ @Override
+ public void binderDied() {
+ close();
+ }
+ }
+
+ private class InputPortClient extends PortClient {
+ private final MidiOutputPort mOutputPort;
+
+ InputPortClient(IBinder token, MidiOutputPort outputPort) {
+ super(token);
+ mOutputPort = outputPort;
}
@Override
- public void onIOException() {
- synchronized (mOutputPortReceivers) {
- mOutputPortReceivers[getPortNumber()].clear();
+ void close() {
+ mToken.unlinkToDeath(this, 0);
+ synchronized (mInputPortOutputPorts) {
+ int portNumber = mOutputPort.getPortNumber();
+ mInputPortOutputPorts[portNumber] = null;
+ mInputPortOpen[portNumber] = false;
+ updateDeviceStatus();
}
+ IoUtils.closeQuietly(mOutputPort);
}
}
- // subclass of MidiOutputPort for passing to clients
- // that notifies us when the connection has failed
- private class ServerOutputPort extends MidiOutputPort {
- ServerOutputPort(ParcelFileDescriptor pfd, int portNumber) {
- super(pfd, portNumber);
+ private class OutputPortClient extends PortClient {
+ private final MidiInputPort mInputPort;
+
+ OutputPortClient(IBinder token, MidiInputPort inputPort) {
+ super(token);
+ mInputPort = inputPort;
}
@Override
- public void onIOException() {
- synchronized (mInputPortSenders) {
- mInputPortSenders[getPortNumber()] = null;
- }
+ void close() {
+ mToken.unlinkToDeath(this, 0);
+ int portNumber = mInputPort.getPortNumber();
+ MidiDispatcher dispatcher = mOutputPortDispatchers[portNumber];
+ synchronized (dispatcher) {
+ dispatcher.getSender().disconnect(mInputPort);
+ int openCount = dispatcher.getReceiverCount();
+ mOutputPortOpenCount[portNumber] = openCount;
+ updateDeviceStatus();
+ }
+
+ mInputPorts.remove(mInputPort);
+ IoUtils.closeQuietly(mInputPort);
}
}
+ private final HashMap<IBinder, PortClient> mPortClients = new HashMap<IBinder, PortClient>();
+
// Binder interface stub for receiving connection requests from clients
private final IMidiDeviceServer mServer = new IMidiDeviceServer.Stub() {
@Override
- public ParcelFileDescriptor openInputPort(int portNumber) {
+ public ParcelFileDescriptor openInputPort(IBinder token, int portNumber) {
+ if (mDeviceInfo.isPrivate()) {
+ if (Binder.getCallingUid() != Process.myUid()) {
+ throw new SecurityException("Can't access private device from different UID");
+ }
+ }
+
if (portNumber < 0 || portNumber >= mInputPortCount) {
Log.e(TAG, "portNumber out of range in openInputPort: " + portNumber);
return null;
}
- ParcelFileDescriptor result = null;
-
- synchronized (mInputPortSenders) {
- if (mInputPortSenders[portNumber] != null) {
+ synchronized (mInputPortOutputPorts) {
+ if (mInputPortOutputPorts[portNumber] != null) {
Log.d(TAG, "port " + portNumber + " already open");
return null;
}
@@ -105,47 +175,102 @@ public final class MidiDeviceServer implements Closeable {
try {
ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
OsConstants.SOCK_SEQPACKET);
- MidiOutputPort newOutputPort = new ServerOutputPort(pair[0], portNumber);
- mInputPortSenders[portNumber] = newOutputPort;
- result = pair[1];
-
- ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumber];
- synchronized (receivers) {
- for (int i = 0; i < receivers.size(); i++) {
- newOutputPort.connect(receivers.get(i));
- }
+ MidiOutputPort outputPort = new MidiOutputPort(pair[0], portNumber);
+ mInputPortOutputPorts[portNumber] = outputPort;
+ outputPort.connect(mInputPortReceivers[portNumber]);
+ InputPortClient client = new InputPortClient(token, outputPort);
+ synchronized (mPortClients) {
+ mPortClients.put(token, client);
}
+ mInputPortOpen[portNumber] = true;
+ updateDeviceStatus();
+ return pair[1];
} catch (IOException e) {
Log.e(TAG, "unable to create ParcelFileDescriptors in openInputPort");
return null;
}
}
-
- return result;
}
@Override
- public ParcelFileDescriptor openOutputPort(int portNumber) {
+ public ParcelFileDescriptor openOutputPort(IBinder token, int portNumber) {
+ if (mDeviceInfo.isPrivate()) {
+ if (Binder.getCallingUid() != Process.myUid()) {
+ throw new SecurityException("Can't access private device from different UID");
+ }
+ }
+
if (portNumber < 0 || portNumber >= mOutputPortCount) {
Log.e(TAG, "portNumber out of range in openOutputPort: " + portNumber);
return null;
}
- synchronized (mOutputPortReceivers) {
- try {
- ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
- OsConstants.SOCK_SEQPACKET);
- mOutputPortReceivers[portNumber].add(new ServerInputPort(pair[0], portNumber));
- return pair[1];
- } catch (IOException e) {
- Log.e(TAG, "unable to create ParcelFileDescriptors in openOutputPort");
- return null;
+
+ try {
+ ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
+ OsConstants.SOCK_SEQPACKET);
+ MidiInputPort inputPort = new MidiInputPort(pair[0], portNumber);
+ MidiDispatcher dispatcher = mOutputPortDispatchers[portNumber];
+ synchronized (dispatcher) {
+ dispatcher.getSender().connect(inputPort);
+ int openCount = dispatcher.getReceiverCount();
+ mOutputPortOpenCount[portNumber] = openCount;
+ updateDeviceStatus();
+ }
+
+ mInputPorts.add(inputPort);
+ OutputPortClient client = new OutputPortClient(token, inputPort);
+ synchronized (mPortClients) {
+ mPortClients.put(token, client);
+ }
+ return pair[1];
+ } catch (IOException e) {
+ Log.e(TAG, "unable to create ParcelFileDescriptors in openOutputPort");
+ return null;
+ }
+ }
+
+ @Override
+ public void closePort(IBinder token) {
+ synchronized (mPortClients) {
+ PortClient client = mPortClients.remove(token);
+ if (client != null) {
+ client.close();
}
}
}
+
+ @Override
+ public void connectPorts(IBinder token, ParcelFileDescriptor pfd,
+ int outputPortNumber) {
+ MidiInputPort inputPort = new MidiInputPort(pfd, outputPortNumber);
+ mOutputPortDispatchers[outputPortNumber].getSender().connect(inputPort);
+ mInputPorts.add(inputPort);
+ OutputPortClient client = new OutputPortClient(token, inputPort);
+ synchronized (mPortClients) {
+ mPortClients.put(token, client);
+ }
+ }
};
- /* package */ MidiDeviceServer(IMidiManager midiManager) {
+ /* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers,
+ int numOutputPorts, Callback callback) {
mMidiManager = midiManager;
+ mInputPortReceivers = inputPortReceivers;
+ mInputPortCount = inputPortReceivers.length;
+ mOutputPortCount = numOutputPorts;
+ mCallback = callback;
+
+ mInputPortOutputPorts = new MidiOutputPort[mInputPortCount];
+
+ mOutputPortDispatchers = new MidiDispatcher[numOutputPorts];
+ for (int i = 0; i < numOutputPorts; i++) {
+ mOutputPortDispatchers[i] = new MidiDispatcher();
+ }
+
+ mInputPortOpen = new boolean[mInputPortCount];
+ mOutputPortOpenCount = new int[numOutputPorts];
+
+ mGuard.open("close");
}
/* package */ IMidiDeviceServer getBinderInterface() {
@@ -157,112 +282,70 @@ public final class MidiDeviceServer implements Closeable {
throw new IllegalStateException("setDeviceInfo should only be called once");
}
mDeviceInfo = deviceInfo;
- mInputPortCount = deviceInfo.getInputPortCount();
- mOutputPortCount = deviceInfo.getOutputPortCount();
- mInputPortSenders = new MidiOutputPort[mInputPortCount];
+ }
- mInputPortReceivers = new ArrayList[mInputPortCount];
- for (int i = 0; i < mInputPortCount; i++) {
- mInputPortReceivers[i] = new ArrayList<MidiReceiver>();
- }
+ private void updateDeviceStatus() {
+ // clear calling identity, since we may be in a Binder call from one of our clients
+ long identityToken = Binder.clearCallingIdentity();
- mOutputPortReceivers = new ArrayList[mOutputPortCount];
- for (int i = 0; i < mOutputPortCount; i++) {
- mOutputPortReceivers[i] = new ArrayList<MidiInputPort>();
+ MidiDeviceStatus status = new MidiDeviceStatus(mDeviceInfo, mInputPortOpen,
+ mOutputPortOpenCount);
+ if (mCallback != null) {
+ mCallback.onDeviceStatusChanged(this, status);
}
- }
-
- @Override
- public void close() throws IOException {
try {
- // FIXME - close input and output ports too?
- mMidiManager.unregisterDeviceServer(mServer);
+ mMidiManager.setDeviceStatus(mDeviceStatusToken, status);
} catch (RemoteException e) {
- Log.e(TAG, "RemoteException in unregisterDeviceServer");
+ Log.e(TAG, "RemoteException in updateDeviceStatus");
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
}
}
- /**
- * Returns a {@link MidiDeviceInfo} object, which describes this device.
- *
- * @return the {@link MidiDeviceInfo} object
- */
- public MidiDeviceInfo getInfo() {
- return mDeviceInfo;
- }
-
- /**
- * Called to open a {@link MidiSender} to allow receiving MIDI messages
- * on the device's input port for the specified port number.
- *
- * @param portNumber the number of the input port
- * @return the {@link MidiSender}
- */
- public MidiSender openInputPortSender(int portNumber) {
- if (portNumber < 0 || portNumber >= mDeviceInfo.getInputPortCount()) {
- throw new IllegalArgumentException("portNumber " + portNumber + " out of range");
- }
- final int portNumberF = portNumber;
- return new MidiSender() {
-
- @Override
- public void connect(MidiReceiver receiver) {
- // We always synchronize on mInputPortSenders before receivers if we need to
- // synchronize on both.
- synchronized (mInputPortSenders) {
- ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumberF];
- synchronized (receivers) {
- receivers.add(receiver);
- MidiOutputPort outputPort = mInputPortSenders[portNumberF];
- if (outputPort != null) {
- outputPort.connect(receiver);
- }
- }
+ @Override
+ public void close() throws IOException {
+ synchronized (mGuard) {
+ if (mIsClosed) return;
+ mGuard.close();
+
+ for (int i = 0; i < mInputPortCount; i++) {
+ MidiOutputPort outputPort = mInputPortOutputPorts[i];
+ if (outputPort != null) {
+ IoUtils.closeQuietly(outputPort);
+ mInputPortOutputPorts[i] = null;
}
}
-
- @Override
- public void disconnect(MidiReceiver receiver) {
- // We always synchronize on mInputPortSenders before receivers if we need to
- // synchronize on both.
- synchronized (mInputPortSenders) {
- ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumberF];
- synchronized (receivers) {
- receivers.remove(receiver);
- MidiOutputPort outputPort = mInputPortSenders[portNumberF];
- if (outputPort != null) {
- outputPort.disconnect(receiver);
- }
- }
- }
+ for (MidiInputPort inputPort : mInputPorts) {
+ IoUtils.closeQuietly(inputPort);
+ }
+ mInputPorts.clear();
+ try {
+ mMidiManager.unregisterDeviceServer(mServer);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in unregisterDeviceServer");
}
- };
+ mIsClosed = true;
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ mGuard.warnIfOpen();
+ close();
+ } finally {
+ super.finalize();
+ }
}
/**
- * Called to open a {@link MidiReceiver} to allow sending MIDI messages
- * on the virtual device's output port for the specified port number.
- *
- * @param portNumber the number of the output port
- * @return the {@link MidiReceiver}
+ * Returns an array of {@link MidiReceiver} for the device's output ports.
+ * Clients can use these receivers to send data out the device's output ports.
+ * @return array of MidiReceivers
*/
- public MidiReceiver openOutputPortReceiver(int portNumber) {
- if (portNumber < 0 || portNumber >= mDeviceInfo.getOutputPortCount()) {
- throw new IllegalArgumentException("portNumber " + portNumber + " out of range");
- }
- final int portNumberF = portNumber;
- return new MidiReceiver() {
-
- @Override
- public void post(byte[] msg, int offset, int count, long timestamp) throws IOException {
- ArrayList<MidiInputPort> receivers = mOutputPortReceivers[portNumberF];
- synchronized (receivers) {
- for (int i = 0; i < receivers.size(); i++) {
- // FIXME catch errors and remove dead ones
- receivers.get(i).post(msg, offset, count, timestamp);
- }
- }
- }
- };
+ public MidiReceiver[] getOutputPortReceivers() {
+ MidiReceiver[] receivers = new MidiReceiver[mOutputPortCount];
+ System.arraycopy(mOutputPortDispatchers, 0, receivers, 0, mOutputPortCount);
+ return receivers;
}
}
diff --git a/media/java/android/media/midi/MidiDeviceService.java b/media/java/android/media/midi/MidiDeviceService.java
new file mode 100644
index 0000000..8b1de3e
--- /dev/null
+++ b/media/java/android/media/midi/MidiDeviceService.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2015 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.midi;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+/**
+ * A service that implements a virtual MIDI device.
+ * Subclasses must implement the {@link #onGetInputPortReceivers} method to provide a
+ * list of {@link MidiReceiver}s to receive data sent to the device's input ports.
+ * Similarly, subclasses can call {@link #getOutputPortReceivers} to fetch a list
+ * of {@link MidiReceiver}s for sending data out the output ports.
+ *
+ * <p>To extend this class, you must declare the service in your manifest file with
+ * an intent filter with the {@link #SERVICE_INTERFACE} action
+ * and meta-data to describe the virtual device.
+ For example:</p>
+ * <pre>
+ * &lt;service android:name=".VirtualDeviceService"
+ * android:label="&#64;string/service_name">
+ * &lt;intent-filter>
+ * &lt;action android:name="android.media.midi.MidiDeviceService" />
+ * &lt;/intent-filter>
+ * &lt;meta-data android:name="android.media.midi.MidiDeviceService"
+ android:resource="@xml/device_info" />
+ * &lt;/service></pre>
+ */
+abstract public class MidiDeviceService extends Service {
+ private static final String TAG = "MidiDeviceService";
+
+ public static final String SERVICE_INTERFACE = "android.media.midi.MidiDeviceService";
+
+ private IMidiManager mMidiManager;
+ private MidiDeviceServer mServer;
+ private MidiDeviceInfo mDeviceInfo;
+
+ private final MidiDeviceServer.Callback mCallback = new MidiDeviceServer.Callback() {
+ @Override
+ public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status) {
+ MidiDeviceService.this.onDeviceStatusChanged(status);
+ }
+ };
+
+ @Override
+ public void onCreate() {
+ mMidiManager = IMidiManager.Stub.asInterface(
+ ServiceManager.getService(Context.MIDI_SERVICE));
+ MidiDeviceServer server;
+ try {
+ MidiDeviceInfo deviceInfo = mMidiManager.getServiceDeviceInfo(getPackageName(),
+ this.getClass().getName());
+ if (deviceInfo == null) {
+ Log.e(TAG, "Could not find MidiDeviceInfo for MidiDeviceService " + this);
+ return;
+ }
+ mDeviceInfo = deviceInfo;
+ MidiReceiver[] inputPortReceivers = onGetInputPortReceivers();
+ if (inputPortReceivers == null) {
+ inputPortReceivers = new MidiReceiver[0];
+ }
+ server = new MidiDeviceServer(mMidiManager, inputPortReceivers,
+ deviceInfo.getOutputPortCount(), mCallback);
+ server.setDeviceInfo(deviceInfo);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in IMidiManager.getServiceDeviceInfo");
+ server = null;
+ }
+ mServer = server;
+ }
+
+ /**
+ * Returns an array of {@link MidiReceiver} for the device's input ports.
+ * Subclasses must override this to provide the receivers which will receive
+ * data sent to the device's input ports. An empty array or null should be returned if
+ * the device has no input ports.
+ * @return array of MidiReceivers
+ */
+ abstract public MidiReceiver[] onGetInputPortReceivers();
+
+ /**
+ * Returns an array of {@link MidiReceiver} for the device's output ports.
+ * These can be used to send data out the device's output ports.
+ * @return array of MidiReceivers
+ */
+ public final MidiReceiver[] getOutputPortReceivers() {
+ if (mServer == null) {
+ return null;
+ } else {
+ return mServer.getOutputPortReceivers();
+ }
+ }
+
+ /**
+ * returns the {@link MidiDeviceInfo} instance for this service
+ * @return our MidiDeviceInfo
+ */
+ public final MidiDeviceInfo getDeviceInfo() {
+ return mDeviceInfo;
+ }
+
+ /**
+ * Called to notify when an our {@link MidiDeviceStatus} has changed
+ * @param status the number of the port that was opened
+ */
+ public void onDeviceStatusChanged(MidiDeviceStatus status) {
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (SERVICE_INTERFACE.equals(intent.getAction()) && mServer != null) {
+ return mServer.getBinderInterface().asBinder();
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/media/java/android/media/midi/MidiDeviceStatus.aidl b/media/java/android/media/midi/MidiDeviceStatus.aidl
new file mode 100644
index 0000000..1a848c0
--- /dev/null
+++ b/media/java/android/media/midi/MidiDeviceStatus.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2015, 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.midi;
+
+parcelable MidiDeviceStatus;
diff --git a/media/java/android/media/midi/MidiDeviceStatus.java b/media/java/android/media/midi/MidiDeviceStatus.java
new file mode 100644
index 0000000..7522dcf
--- /dev/null
+++ b/media/java/android/media/midi/MidiDeviceStatus.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2015 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.midi;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This is an immutable class that describes the current status of a MIDI device's ports.
+ */
+public final class MidiDeviceStatus implements Parcelable {
+
+ private static final String TAG = "MidiDeviceStatus";
+
+ private final MidiDeviceInfo mDeviceInfo;
+ // true if input ports are open
+ private final boolean mInputPortOpen[];
+ // open counts for output ports
+ private final int mOutputPortOpenCount[];
+
+ /**
+ * @hide
+ */
+ public MidiDeviceStatus(MidiDeviceInfo deviceInfo, boolean inputPortOpen[],
+ int outputPortOpenCount[]) {
+ // MidiDeviceInfo is immutable so we can share references
+ mDeviceInfo = deviceInfo;
+
+ // make copies of the arrays
+ mInputPortOpen = new boolean[inputPortOpen.length];
+ System.arraycopy(inputPortOpen, 0, mInputPortOpen, 0, inputPortOpen.length);
+ mOutputPortOpenCount = new int[outputPortOpenCount.length];
+ System.arraycopy(outputPortOpenCount, 0, mOutputPortOpenCount, 0,
+ outputPortOpenCount.length);
+ }
+
+ /**
+ * Creates a MidiDeviceStatus with zero for all port open counts
+ * @hide
+ */
+ public MidiDeviceStatus(MidiDeviceInfo deviceInfo) {
+ mDeviceInfo = deviceInfo;
+ mInputPortOpen = new boolean[deviceInfo.getInputPortCount()];
+ mOutputPortOpenCount = new int[deviceInfo.getOutputPortCount()];
+ }
+
+ /**
+ * Returns the {@link MidiDeviceInfo} of the device.
+ *
+ * @return the device info
+ */
+ public MidiDeviceInfo getDeviceInfo() {
+ return mDeviceInfo;
+ }
+
+ /**
+ * Returns true if an input port is open.
+ *
+ * @param portNumber the input port's port number
+ * @return input port open status
+ */
+ public boolean isInputPortOpen(int portNumber) {
+ return mInputPortOpen[portNumber];
+ }
+
+ /**
+ * Returns the open count for an output port.
+ *
+ * @param portNumber the output port's port number
+ * @return output port open count
+ */
+ public int getOutputPortOpenCount(int portNumber) {
+ return mOutputPortOpenCount[portNumber];
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder(mDeviceInfo.toString());
+ int inputPortCount = mDeviceInfo.getInputPortCount();
+ int outputPortCount = mDeviceInfo.getOutputPortCount();
+ builder.append(" mInputPortOpen=[");
+ for (int i = 0; i < inputPortCount; i++) {
+ builder.append(mInputPortOpen[i]);
+ if (i < inputPortCount -1) {
+ builder.append(",");
+ }
+ }
+ builder.append("] mOutputPortOpenCount=[");
+ for (int i = 0; i < outputPortCount; i++) {
+ builder.append(mOutputPortOpenCount[i]);
+ if (i < outputPortCount -1) {
+ builder.append(",");
+ }
+ }
+ builder.append("]");
+ return builder.toString();
+ }
+
+ public static final Parcelable.Creator<MidiDeviceStatus> CREATOR =
+ new Parcelable.Creator<MidiDeviceStatus>() {
+ public MidiDeviceStatus createFromParcel(Parcel in) {
+ ClassLoader classLoader = MidiDeviceInfo.class.getClassLoader();
+ MidiDeviceInfo deviceInfo = in.readParcelable(classLoader);
+ boolean[] inputPortOpen = in.createBooleanArray();
+ int[] outputPortOpenCount = in.createIntArray();
+ return new MidiDeviceStatus(deviceInfo, inputPortOpen, outputPortOpenCount);
+ }
+
+ public MidiDeviceStatus[] newArray(int size) {
+ return new MidiDeviceStatus[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeParcelable(mDeviceInfo, flags);
+ parcel.writeBooleanArray(mInputPortOpen);
+ parcel.writeIntArray(mOutputPortOpenCount);
+ }
+}
diff --git a/media/java/android/media/midi/MidiDispatcher.java b/media/java/android/media/midi/MidiDispatcher.java
new file mode 100644
index 0000000..0868346
--- /dev/null
+++ b/media/java/android/media/midi/MidiDispatcher.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2015 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.midi;
+
+import java.io.IOException;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Utility class for dispatching MIDI data to a list of {@link MidiReceiver}s.
+ * This class subclasses {@link MidiReceiver} and dispatches any data it receives
+ * to its receiver list. Any receivers that throw an exception upon receiving data will
+ * be automatically removed from the receiver list, but no IOException will be returned
+ * from the dispatcher's {@link #onReceive} in that case.
+ *
+ * @hide
+ */
+public final class MidiDispatcher extends MidiReceiver {
+
+ private final CopyOnWriteArrayList<MidiReceiver> mReceivers
+ = new CopyOnWriteArrayList<MidiReceiver>();
+
+ private final MidiSender mSender = new MidiSender() {
+ /**
+ * Called to connect a {@link MidiReceiver} to the sender
+ *
+ * @param receiver the receiver to connect
+ */
+ public void connect(MidiReceiver receiver) {
+ mReceivers.add(receiver);
+ }
+
+ /**
+ * Called to disconnect a {@link MidiReceiver} from the sender
+ *
+ * @param receiver the receiver to disconnect
+ */
+ public void disconnect(MidiReceiver receiver) {
+ mReceivers.remove(receiver);
+ }
+ };
+
+ /**
+ * Returns the number of {@link MidiReceiver}s this dispatcher contains.
+ * @return the number of receivers
+ */
+ public int getReceiverCount() {
+ return mReceivers.size();
+ }
+
+ /**
+ * Returns a {@link MidiSender} which is used to add and remove {@link MidiReceiver}s
+ * to the dispatcher's receiver list.
+ * @return the dispatcher's MidiSender
+ */
+ public MidiSender getSender() {
+ return mSender;
+ }
+
+ @Override
+ public void onReceive(byte[] msg, int offset, int count, long timestamp) throws IOException {
+ for (MidiReceiver receiver : mReceivers) {
+ try {
+ receiver.sendWithTimestamp(msg, offset, count, timestamp);
+ } catch (IOException e) {
+ // if the receiver fails we remove the receiver but do not propagate the exception
+ mReceivers.remove(receiver);
+ }
+ }
+ }
+}
diff --git a/media/java/android/media/midi/MidiInputPort.java b/media/java/android/media/midi/MidiInputPort.java
index 730d364..1d3b37a 100644
--- a/media/java/android/media/midi/MidiInputPort.java
+++ b/media/java/android/media/midi/MidiInputPort.java
@@ -16,65 +16,131 @@
package android.media.midi;
+import android.os.IBinder;
import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Log;
+
+import dalvik.system.CloseGuard;
import libcore.io.IoUtils;
+import java.io.Closeable;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* This class is used for sending data to a port on a MIDI device
- *
- * CANDIDATE FOR PUBLIC API
- * @hide
*/
-public class MidiInputPort extends MidiPort implements MidiReceiver {
+public final class MidiInputPort extends MidiReceiver implements Closeable {
+ private static final String TAG = "MidiInputPort";
+
+ private IMidiDeviceServer mDeviceServer;
+ private final IBinder mToken;
+ private final int mPortNumber;
+ private ParcelFileDescriptor mParcelFileDescriptor;
+ private FileOutputStream mOutputStream;
+
+ private final CloseGuard mGuard = CloseGuard.get();
+ private boolean mIsClosed;
- private final FileOutputStream mOutputStream;
+ // buffer to use for sending data out our output stream
+ private final byte[] mBuffer = new byte[MidiPortImpl.MAX_PACKET_SIZE];
- // buffer to use for sending messages out our output stream
- private final byte[] mBuffer = new byte[MAX_PACKET_SIZE];
+ /* package */ MidiInputPort(IMidiDeviceServer server, IBinder token,
+ ParcelFileDescriptor pfd, int portNumber) {
+ mDeviceServer = server;
+ mToken = token;
+ mParcelFileDescriptor = pfd;
+ mPortNumber = portNumber;
+ mOutputStream = new FileOutputStream(pfd.getFileDescriptor());
+ mGuard.open("close");
+ }
- /* package */ MidiInputPort(ParcelFileDescriptor pfd, int portNumber) {
- super(portNumber);
- mOutputStream = new ParcelFileDescriptor.AutoCloseOutputStream(pfd);
+ /* package */ MidiInputPort(ParcelFileDescriptor pfd, int portNumber) {
+ this(null, null, pfd, portNumber);
}
/**
- * Writes a MIDI message to the input port
+ * Returns the port number of this port
*
- * @param msg byte array containing the message
- * @param offset offset of first byte of the message in msg byte array
- * @param count size of the message in bytes
- * @param timestamp future time to post the message (based on
- * {@link java.lang.System#nanoTime}
+ * @return the port's port number
*/
- public void post(byte[] msg, int offset, int count, long timestamp) throws IOException {
- assert(offset >= 0 && count >= 0 && offset + count <= msg.length);
+ public final int getPortNumber() {
+ return mPortNumber;
+ }
+
+ @Override
+ public void onReceive(byte[] msg, int offset, int count, long timestamp) throws IOException {
+ if (offset < 0 || count < 0 || offset + count > msg.length) {
+ throw new IllegalArgumentException("offset or count out of range");
+ }
+ if (count > MidiPortImpl.MAX_PACKET_DATA_SIZE) {
+ throw new IllegalArgumentException("count exceeds max message size");
+ }
synchronized (mBuffer) {
- try {
- while (count > 0) {
- int length = packMessage(msg, offset, count, timestamp, mBuffer);
- mOutputStream.write(mBuffer, 0, length);
- int sent = getMessageSize(mBuffer, length);
- assert(sent >= 0 && sent <= length);
-
- offset += sent;
- count -= sent;
- }
- } catch (IOException e) {
+ if (mOutputStream == null) {
+ throw new IOException("MidiInputPort is closed");
+ }
+ int length = MidiPortImpl.packMessage(msg, offset, count, timestamp, mBuffer);
+ mOutputStream.write(mBuffer, 0, length);
+ }
+ }
+
+ // used by MidiDevice.connectInputPort() to connect our socket directly to another device
+ /* package */ ParcelFileDescriptor claimFileDescriptor() {
+ synchronized (mBuffer) {
+ ParcelFileDescriptor pfd = mParcelFileDescriptor;
+ if (pfd != null) {
IoUtils.closeQuietly(mOutputStream);
- // report I/O failure
- onIOException();
- throw e;
+ mParcelFileDescriptor = null;
+ mOutputStream = null;
}
+ return pfd;
}
}
@Override
+ public int getMaxMessageSize() {
+ return MidiPortImpl.MAX_PACKET_DATA_SIZE;
+ }
+
+ @Override
public void close() throws IOException {
- mOutputStream.close();
+ synchronized (mGuard) {
+ if (mIsClosed) return;
+ mGuard.close();
+ synchronized (mBuffer) {
+ if (mParcelFileDescriptor != null) {
+ mParcelFileDescriptor.close();
+ mParcelFileDescriptor = null;
+ }
+ if (mOutputStream != null) {
+ mOutputStream.close();
+ mOutputStream = null;
+ }
+ }
+ if (mDeviceServer != null) {
+ try {
+ mDeviceServer.closePort(mToken);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in MidiInputPort.close()");
+ }
+ }
+ mIsClosed = true;
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ mGuard.warnIfOpen();
+ // not safe to make binder calls from finalize()
+ mDeviceServer = null;
+ close();
+ } finally {
+ super.finalize();
+ }
}
}
diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java
index 410120d..1b98ca5 100644
--- a/media/java/android/media/midi/MidiManager.java
+++ b/media/java/android/media/midi/MidiManager.java
@@ -16,7 +16,11 @@
package android.media.midi;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ServiceInfo;
import android.os.Binder;
import android.os.IBinder;
import android.os.Bundle;
@@ -24,7 +28,6 @@ import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
-import java.io.IOException;
import java.util.HashMap;
/**
@@ -39,7 +42,7 @@ import java.util.HashMap;
* CANDIDATE FOR PUBLIC API
* @hide
*/
-public class MidiManager {
+public final class MidiManager {
private static final String TAG = "MidiManager";
private final Context mContext;
@@ -50,7 +53,7 @@ public class MidiManager {
new HashMap<DeviceCallback,DeviceListener>();
// Binder stub for receiving device notifications from MidiService
- private class DeviceListener extends IMidiListener.Stub {
+ private class DeviceListener extends IMidiDeviceListener.Stub {
private final DeviceCallback mCallback;
private final Handler mHandler;
@@ -59,6 +62,7 @@ public class MidiManager {
mHandler = handler;
}
+ @Override
public void onDeviceAdded(MidiDeviceInfo device) {
if (mHandler != null) {
final MidiDeviceInfo deviceF = device;
@@ -72,6 +76,7 @@ public class MidiManager {
}
}
+ @Override
public void onDeviceRemoved(MidiDeviceInfo device) {
if (mHandler != null) {
final MidiDeviceInfo deviceF = device;
@@ -84,6 +89,20 @@ public class MidiManager {
mCallback.onDeviceRemoved(device);
}
}
+
+ @Override
+ public void onDeviceStatusChanged(MidiDeviceStatus status) {
+ if (mHandler != null) {
+ final MidiDeviceStatus statusF = status;
+ mHandler.post(new Runnable() {
+ @Override public void run() {
+ mCallback.onDeviceStatusChanged(statusF);
+ }
+ });
+ } else {
+ mCallback.onDeviceStatusChanged(status);
+ }
+ }
}
/**
@@ -105,6 +124,27 @@ public class MidiManager {
*/
public void onDeviceRemoved(MidiDeviceInfo device) {
}
+
+ /**
+ * Called to notify when the status of a MIDI device has changed
+ *
+ * @param device a {@link MidiDeviceStatus} for the changed device
+ */
+ public void onDeviceStatusChanged(MidiDeviceStatus status) {
+ }
+ }
+
+ /**
+ * Callback class used for receiving the results of {@link #openDevice}
+ */
+ abstract public static class DeviceOpenCallback {
+ /**
+ * Called to respond to a {@link #openDevice} request
+ *
+ * @param deviceInfo the {@link MidiDeviceInfo} for the device to open
+ * @param device a {@link MidiDevice} for opened device, or null if opening failed
+ */
+ abstract public void onDeviceOpened(MidiDeviceInfo deviceInfo, MidiDevice device);
}
/**
@@ -164,33 +204,87 @@ public class MidiManager {
}
}
+ private void sendOpenDeviceResponse(final MidiDeviceInfo deviceInfo, final MidiDevice device,
+ final DeviceOpenCallback callback, Handler handler) {
+ if (handler != null) {
+ handler.post(new Runnable() {
+ @Override public void run() {
+ callback.onDeviceOpened(deviceInfo, device);
+ }
+ });
+ } else {
+ callback.onDeviceOpened(deviceInfo, device);
+ }
+ }
+
/**
* Opens a MIDI device for reading and writing.
*
* @param deviceInfo a {@link android.media.midi.MidiDeviceInfo} to open
- * @return a {@link MidiDevice} object for the device
+ * @param callback a {@link #DeviceOpenCallback} to be called to receive the result
+ * @param handler the {@link android.os.Handler Handler} that will be used for delivering
+ * the result. If handler is null, then the thread used for the
+ * callback is unspecified.
*/
- public MidiDevice openDevice(MidiDeviceInfo deviceInfo) {
+ public void openDevice(MidiDeviceInfo deviceInfo, DeviceOpenCallback callback,
+ Handler handler) {
+ MidiDevice device = null;
try {
IMidiDeviceServer server = mService.openDevice(mToken, deviceInfo);
if (server == null) {
- Log.e(TAG, "could not open device " + deviceInfo);
- return null;
+ ServiceInfo serviceInfo = (ServiceInfo)deviceInfo.getProperties().getParcelable(
+ MidiDeviceInfo.PROPERTY_SERVICE_INFO);
+ if (serviceInfo == null) {
+ Log.e(TAG, "no ServiceInfo for " + deviceInfo);
+ } else {
+ Intent intent = new Intent(MidiDeviceService.SERVICE_INTERFACE);
+ intent.setComponent(new ComponentName(serviceInfo.packageName,
+ serviceInfo.name));
+ final MidiDeviceInfo deviceInfoF = deviceInfo;
+ final DeviceOpenCallback callbackF = callback;
+ final Handler handlerF = handler;
+ if (mContext.bindService(intent,
+ new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder binder) {
+ IMidiDeviceServer server =
+ IMidiDeviceServer.Stub.asInterface(binder);
+ MidiDevice device = new MidiDevice(deviceInfoF, server, mContext, this);
+ sendOpenDeviceResponse(deviceInfoF, device, callbackF, handlerF);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ // FIXME - anything to do here?
+ }
+ },
+ Context.BIND_AUTO_CREATE))
+ {
+ // return immediately to avoid calling sendOpenDeviceResponse below
+ return;
+ } else {
+ Log.e(TAG, "Unable to bind service: " + intent);
+ }
+ }
+ } else {
+ device = new MidiDevice(deviceInfo, server);
}
- return new MidiDevice(deviceInfo, server);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in openDevice");
}
- return null;
+ sendOpenDeviceResponse(deviceInfo, device, callback, handler);
}
/** @hide */
- public MidiDeviceServer createDeviceServer(int numInputPorts, int numOutputPorts,
- Bundle properties, boolean isPrivate, int type) {
+ public MidiDeviceServer createDeviceServer(MidiReceiver[] inputPortReceivers,
+ int numOutputPorts, String[] inputPortNames, String[] outputPortNames,
+ Bundle properties, int type, MidiDeviceServer.Callback callback) {
try {
- MidiDeviceServer server = new MidiDeviceServer(mService);
+ MidiDeviceServer server = new MidiDeviceServer(mService, inputPortReceivers,
+ numOutputPorts, callback);
MidiDeviceInfo deviceInfo = mService.registerDeviceServer(server.getBinderInterface(),
- numInputPorts, numOutputPorts, properties, isPrivate, type);
+ inputPortReceivers.length, numOutputPorts, inputPortNames, outputPortNames,
+ properties, type);
if (deviceInfo == null) {
Log.e(TAG, "registerVirtualDevice failed");
return null;
@@ -202,21 +296,4 @@ public class MidiManager {
return null;
}
}
-
- /**
- * Creates a new MIDI virtual device.
- *
- * @param numInputPorts number of input ports for the virtual device
- * @param numOutputPorts number of output ports for the virtual device
- * @param properties a {@link android.os.Bundle} containing properties describing the device
- * @param isPrivate true if this device should only be visible and accessible to apps
- * with the same UID as the caller
- * @return a {@link MidiDeviceServer} object to locally represent the device
- */
- public MidiDeviceServer createDeviceServer(int numInputPorts, int numOutputPorts,
- Bundle properties, boolean isPrivate) {
- return createDeviceServer(numInputPorts, numOutputPorts, properties,
- isPrivate, MidiDeviceInfo.TYPE_VIRTUAL);
- }
-
}
diff --git a/media/java/android/media/midi/MidiOutputPort.java b/media/java/android/media/midi/MidiOutputPort.java
index 83ddeeb..b8ed36f 100644
--- a/media/java/android/media/midi/MidiOutputPort.java
+++ b/media/java/android/media/midi/MidiOutputPort.java
@@ -16,38 +16,40 @@
package android.media.midi;
+import android.os.IBinder;
import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
import android.util.Log;
+import dalvik.system.CloseGuard;
+
import libcore.io.IoUtils;
+import java.io.Closeable;
import java.io.FileInputStream;
import java.io.IOException;
-import java.util.ArrayList;
/**
* This class is used for receiving data from a port on a MIDI device
- *
- * CANDIDATE FOR PUBLIC API
- * @hide
*/
-public class MidiOutputPort extends MidiPort implements MidiSender {
+public final class MidiOutputPort extends MidiSender implements Closeable {
private static final String TAG = "MidiOutputPort";
+ private IMidiDeviceServer mDeviceServer;
+ private final IBinder mToken;
+ private final int mPortNumber;
private final FileInputStream mInputStream;
+ private final MidiDispatcher mDispatcher = new MidiDispatcher();
- // array of receiver lists, indexed by port number
- private final ArrayList<MidiReceiver> mReceivers = new ArrayList<MidiReceiver>();
-
- private int mReceiverCount; // total number of receivers for all ports
+ private final CloseGuard mGuard = CloseGuard.get();
+ private boolean mIsClosed;
// This thread reads MIDI events from a socket and distributes them to the list of
// MidiReceivers attached to this device.
private final Thread mThread = new Thread() {
@Override
public void run() {
- byte[] buffer = new byte[MAX_PACKET_SIZE];
- ArrayList<MidiReceiver> deadReceivers = new ArrayList<MidiReceiver>();
+ byte[] buffer = new byte[MidiPortImpl.MAX_PACKET_SIZE];
try {
while (true) {
@@ -55,81 +57,85 @@ public class MidiOutputPort extends MidiPort implements MidiSender {
int count = mInputStream.read(buffer);
if (count < 0) {
break;
+ // FIXME - inform receivers here?
}
- int offset = getMessageOffset(buffer, count);
- int size = getMessageSize(buffer, count);
- long timestamp = getMessageTimeStamp(buffer, count);
-
- synchronized (mReceivers) {
- for (int i = 0; i < mReceivers.size(); i++) {
- MidiReceiver receiver = mReceivers.get(i);
- try {
- receiver.post(buffer, offset, size, timestamp);
- } catch (IOException e) {
- Log.e(TAG, "post failed");
- deadReceivers.add(receiver);
- }
- }
- // remove any receivers that failed
- if (deadReceivers.size() > 0) {
- for (MidiReceiver receiver: deadReceivers) {
- mReceivers.remove(receiver);
- mReceiverCount--;
- }
- deadReceivers.clear();
- }
- // exit if we have no receivers left
- if (mReceiverCount == 0) {
- break;
- }
- }
+ int offset = MidiPortImpl.getMessageOffset(buffer, count);
+ int size = MidiPortImpl.getMessageSize(buffer, count);
+ long timestamp = MidiPortImpl.getMessageTimeStamp(buffer, count);
+
+ // dispatch to all our receivers
+ mDispatcher.sendWithTimestamp(buffer, offset, size, timestamp);
}
} catch (IOException e) {
- // report I/O failure
+ // FIXME report I/O failure?
Log.e(TAG, "read failed");
} finally {
IoUtils.closeQuietly(mInputStream);
- onIOException();
}
}
};
- /* package */ MidiOutputPort(ParcelFileDescriptor pfd, int portNumber) {
- super(portNumber);
+ /* package */ MidiOutputPort(IMidiDeviceServer server, IBinder token,
+ ParcelFileDescriptor pfd, int portNumber) {
+ mDeviceServer = server;
+ mToken = token;
+ mPortNumber = portNumber;
mInputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+ mThread.start();
+ mGuard.open("close");
+ }
+
+ /* package */ MidiOutputPort(ParcelFileDescriptor pfd, int portNumber) {
+ this(null, null, pfd, portNumber);
}
/**
- * Connects a {@link MidiReceiver} to the output port to allow receiving
- * MIDI messages from the port.
+ * Returns the port number of this port
*
- * @param receiver the receiver to connect
+ * @return the port's port number
*/
+ public final int getPortNumber() {
+ return mPortNumber;
+ }
+
+ @Override
public void connect(MidiReceiver receiver) {
- synchronized (mReceivers) {
- mReceivers.add(receiver);
- if (mReceiverCount++ == 0) {
- mThread.start();
- }
- }
+ mDispatcher.getSender().connect(receiver);
}
- /**
- * Disconnects a {@link MidiReceiver} from the output port.
- *
- * @param receiver the receiver to connect
- */
+ @Override
public void disconnect(MidiReceiver receiver) {
- synchronized (mReceivers) {
- if (mReceivers.remove(receiver)) {
- mReceiverCount--;
+ mDispatcher.getSender().disconnect(receiver);
+ }
+
+ @Override
+ public void close() throws IOException {
+ synchronized (mGuard) {
+ if (mIsClosed) return;
+
+ mGuard.close();
+ mInputStream.close();
+ if (mDeviceServer != null) {
+ try {
+ mDeviceServer.closePort(mToken);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in MidiOutputPort.close()");
+ }
}
+ mIsClosed = true;
}
}
@Override
- public void close() throws IOException {
- mInputStream.close();
+ protected void finalize() throws Throwable {
+ try {
+ mGuard.warnIfOpen();
+ // not safe to make binder calls from finalize()
+ mDeviceServer = null;
+ close();
+ } finally {
+ super.finalize();
+ }
}
}
diff --git a/media/java/android/media/midi/MidiPort.java b/media/java/android/media/midi/MidiPortImpl.java
index 4d3c91d..5795045 100644
--- a/media/java/android/media/midi/MidiPort.java
+++ b/media/java/android/media/midi/MidiPortImpl.java
@@ -16,33 +16,20 @@
package android.media.midi;
-import android.util.Log;
-
-import java.io.Closeable;
-
/**
- * This class represents a MIDI input or output port.
- * Base class for {@link MidiInputPort} and {@link MidiOutputPort}
- *
- * CANDIDATE FOR PUBLIC API
- * @hide
+ * This class contains utilities for socket communication between a
+ * MidiInputPort and MidiOutputPort
*/
-abstract public class MidiPort implements Closeable {
+/* package */ class MidiPortImpl {
private static final String TAG = "MidiPort";
- private final int mPortNumber;
-
/**
* Maximum size of a packet that can pass through our ParcelFileDescriptor.
- * For internal use only. Implementation details may change in the future.
- * @hide
*/
public static final int MAX_PACKET_SIZE = 1024;
/**
* size of message timestamp in bytes
- * For internal use only. Implementation details may change in the future.
- * @hide
*/
private static final int TIMESTAMP_SIZE = 8;
@@ -51,29 +38,6 @@ abstract public class MidiPort implements Closeable {
*/
public static final int MAX_PACKET_DATA_SIZE = MAX_PACKET_SIZE - TIMESTAMP_SIZE;
-
- /* package */ MidiPort(int portNumber) {
- mPortNumber = portNumber;
- }
-
- /**
- * Returns the port number of this port
- *
- * @return the port's port number
- */
- public final int getPortNumber() {
- return mPortNumber;
- }
-
- /**
- * Called when an IOExeption occurs while sending or receiving data.
- * Subclasses can override to be notified of such errors
- *
- * @hide
- */
- public void onIOException() {
- }
-
/**
* Utility function for packing a MIDI message to be sent through our ParcelFileDescriptor
*
@@ -82,9 +46,6 @@ abstract public class MidiPort implements Closeable {
* timestamp is message timestamp to pack
* dest is buffer to pack into
* returns size of packed message
- *
- * For internal use only. Implementation details may change in the future.
- * @hide
*/
public static int packMessage(byte[] message, int offset, int size, long timestamp,
byte[] dest) {
@@ -106,9 +67,6 @@ abstract public class MidiPort implements Closeable {
/**
* Utility function for unpacking a MIDI message received from our ParcelFileDescriptor
* returns the offset of the MIDI message in packed buffer
- *
- * For internal use only. Implementation details may change in the future.
- * @hide
*/
public static int getMessageOffset(byte[] buffer, int bufferLength) {
// message is at the beginning
@@ -118,9 +76,6 @@ abstract public class MidiPort implements Closeable {
/**
* Utility function for unpacking a MIDI message received from our ParcelFileDescriptor
* returns size of MIDI data in packed buffer
- *
- * For internal use only. Implementation details may change in the future.
- * @hide
*/
public static int getMessageSize(byte[] buffer, int bufferLength) {
// message length is total buffer length minus size of the timestamp
@@ -130,9 +85,6 @@ abstract public class MidiPort implements Closeable {
/**
* Utility function for unpacking a MIDI message received from our ParcelFileDescriptor
* unpacks timestamp from packed buffer
- *
- * For internal use only. Implementation details may change in the future.
- * @hide
*/
public static long getMessageTimeStamp(byte[] buffer, int bufferLength) {
// timestamp is at end of the packet
diff --git a/media/java/android/media/midi/MidiReceiver.java b/media/java/android/media/midi/MidiReceiver.java
index 64c0c07..6f4c266 100644
--- a/media/java/android/media/midi/MidiReceiver.java
+++ b/media/java/android/media/midi/MidiReceiver.java
@@ -20,25 +20,69 @@ import java.io.IOException;
/**
* Interface for sending and receiving data to and from a MIDI device.
- *
- * CANDIDATE FOR PUBLIC API
- * @hide
*/
-public interface MidiReceiver {
+abstract public class MidiReceiver {
/**
* Called to pass MIDI data to the receiver.
+ * May fail if count exceeds {@link #getMaxMessageSize}.
*
* NOTE: the msg array parameter is only valid within the context of this call.
* The msg bytes should be copied by the receiver rather than retaining a reference
* to this parameter.
* Also, modifying the contents of the msg array parameter may result in other receivers
- * in the same application receiving incorrect values in their post() method.
+ * in the same application receiving incorrect values in their {link #onReceive} method.
+ *
+ * @param msg a byte array containing the MIDI data
+ * @param offset the offset of the first byte of the data in the array to be processed
+ * @param count the number of bytes of MIDI data in the array to be processed
+ * @param timestamp the timestamp of the message (based on {@link java.lang.System#nanoTime}
+ * @throws IOException
+ */
+ abstract public void onReceive(byte[] msg, int offset, int count, long timestamp)
+ throws IOException;
+
+ /**
+ * Returns the maximum size of a message this receiver can receive.
+ * Defaults to {@link java.lang.Integer#MAX_VALUE} unless overridden.
+ * @return maximum message size
+ */
+ public int getMaxMessageSize() {
+ return Integer.MAX_VALUE;
+ }
+
+ /**
+ * Called to send MIDI data to the receiver
+ * Data will get split into multiple calls to {@link #onReceive} if count exceeds
+ * {@link #getMaxMessageSize}.
+ *
+ * @param msg a byte array containing the MIDI data
+ * @param offset the offset of the first byte of the data in the array to be sent
+ * @param count the number of bytes of MIDI data in the array to be sent
+ * @throws IOException
+ */
+ public void send(byte[] msg, int offset, int count) throws IOException {
+ sendWithTimestamp(msg, offset, count, System.nanoTime());
+ }
+
+ /**
+ * Called to send MIDI data to the receiver to be handled at a specified time in the future
+ * Data will get split into multiple calls to {@link #onReceive} if count exceeds
+ * {@link #getMaxMessageSize}.
*
* @param msg a byte array containing the MIDI data
- * @param offset the offset of the first byte of the data in the byte array
- * @param count the number of bytes of MIDI data in the array
+ * @param offset the offset of the first byte of the data in the array to be sent
+ * @param count the number of bytes of MIDI data in the array to be sent
* @param timestamp the timestamp of the message (based on {@link java.lang.System#nanoTime}
* @throws IOException
*/
- public void post(byte[] msg, int offset, int count, long timestamp) throws IOException;
+ public void sendWithTimestamp(byte[] msg, int offset, int count, long timestamp)
+ throws IOException {
+ int messageSize = getMaxMessageSize();
+ while (count > 0) {
+ int length = (count > messageSize ? messageSize : count);
+ onReceive(msg, offset, length, timestamp);
+ offset += length;
+ count -= length;
+ }
+ }
}
diff --git a/media/java/android/media/midi/MidiSender.java b/media/java/android/media/midi/MidiSender.java
index 4550476..f64fc3c 100644
--- a/media/java/android/media/midi/MidiSender.java
+++ b/media/java/android/media/midi/MidiSender.java
@@ -19,22 +19,19 @@ package android.media.midi;
/**
* Interface provided by a device to allow attaching
* MidiReceivers to a MIDI device.
- *
- * CANDIDATE FOR PUBLIC API
- * @hide
*/
-public interface MidiSender {
+abstract public class MidiSender {
/**
* Called to connect a {@link MidiReceiver} to the sender
*
* @param receiver the receiver to connect
*/
- public void connect(MidiReceiver receiver);
+ abstract public void connect(MidiReceiver receiver);
/**
* Called to disconnect a {@link MidiReceiver} from the sender
*
* @param receiver the receiver to disconnect
*/
- public void disconnect(MidiReceiver receiver);
+ abstract public void disconnect(MidiReceiver receiver);
}
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index a6bde1d..e757f09 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -25,7 +25,6 @@ import android.media.AudioRecord;
import android.media.projection.IMediaProjection;
import android.media.projection.IMediaProjectionCallback;
import android.os.Handler;
-import android.os.Looper;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index a1cfc35..f4a548b 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -22,7 +22,6 @@ import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.media.projection.IMediaProjection;
-import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
diff --git a/media/java/android/media/routing/MediaRouteSelector.java b/media/java/android/media/routing/MediaRouteSelector.java
index 26a9b1c..0bfc796 100644
--- a/media/java/android/media/routing/MediaRouteSelector.java
+++ b/media/java/android/media/routing/MediaRouteSelector.java
@@ -19,7 +19,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.media.routing.MediaRouter.RouteFeatures;
import android.os.Bundle;
-import android.os.IInterface;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 57c291d..cc602c9 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -30,7 +30,6 @@ import android.media.MediaMetadata;
import android.media.Rating;
import android.media.VolumeProvider;
import android.media.routing.MediaRouter;
-import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java
index 4ea22f9..c61d7ad 100644
--- a/media/java/android/media/session/MediaSessionLegacyHelper.java
+++ b/media/java/android/media/session/MediaSessionLegacyHelper.java
@@ -30,12 +30,9 @@ import android.media.MediaMetadata;
import android.media.MediaMetadataEditor;
import android.media.MediaMetadataRetriever;
import android.media.Rating;
-import android.media.RemoteControlClient;
-import android.media.RemoteControlClient.MetadataEditor;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
-import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
import android.view.KeyEvent;
@@ -200,17 +197,17 @@ public class MediaSessionLegacyHelper {
break;
}
if (down || up) {
- int flags;
+ int flags = AudioManager.FLAG_FROM_KEY;
if (musicOnly) {
// This flag is used when the screen is off to only affect
// active media
- flags = AudioManager.FLAG_ACTIVE_MEDIA_ONLY;
+ flags |= AudioManager.FLAG_ACTIVE_MEDIA_ONLY;
} else {
// These flags are consistent with the home screen
if (up) {
- flags = AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE;
+ flags |= AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE;
} else {
- flags = AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE;
+ flags |= AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE;
}
}
if (direction != 0) {
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index b4fff8f..6ac0efb 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -29,7 +29,6 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
-import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.view.KeyEvent;
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 54d0acd..6807e7f 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -23,8 +23,6 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
import android.text.TextUtils;
-import android.util.Log;
-
import java.util.ArrayList;
import java.util.List;
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index bc9722e..936762c 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -733,6 +733,50 @@ public final class TvContract {
public static final String COLUMN_INTERNAL_PROVIDER_DATA = "internal_provider_data";
/**
+ * Internal integer flag used by individual TV input services.
+ * <p>
+ * This is internal to the provider that inserted it, and should not be decoded by other
+ * apps.
+ * </p><p>
+ * Type: INTEGER
+ * </p>
+ */
+ public static final String COLUMN_INTERNAL_PROVIDER_FLAG1 = "internal_provider_flag1";
+
+ /**
+ * Internal integer flag used by individual TV input services.
+ * <p>
+ * This is internal to the provider that inserted it, and should not be decoded by other
+ * apps.
+ * </p><p>
+ * Type: INTEGER
+ * </p>
+ */
+ public static final String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2";
+
+ /**
+ * Internal integer flag used by individual TV input services.
+ * <p>
+ * This is internal to the provider that inserted it, and should not be decoded by other
+ * apps.
+ * </p><p>
+ * Type: INTEGER
+ * </p>
+ */
+ public static final String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3";
+
+ /**
+ * Internal integer flag used by individual TV input services.
+ * <p>
+ * This is internal to the provider that inserted it, and should not be decoded by other
+ * apps.
+ * </p><p>
+ * Type: INTEGER
+ * </p>
+ */
+ public static final String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4";
+
+ /**
* The version number of this row entry used by TV input services.
* <p>
* This is best used by sync adapters to identify the rows to update. The number can be
@@ -1010,6 +1054,50 @@ public final class TvContract {
public static final String COLUMN_INTERNAL_PROVIDER_DATA = "internal_provider_data";
/**
+ * Internal integer flag used by individual TV input services.
+ * <p>
+ * This is internal to the provider that inserted it, and should not be decoded by other
+ * apps.
+ * </p><p>
+ * Type: INTEGER
+ * </p>
+ */
+ public static final String COLUMN_INTERNAL_PROVIDER_FLAG1 = "internal_provider_flag1";
+
+ /**
+ * Internal integer flag used by individual TV input services.
+ * <p>
+ * This is internal to the provider that inserted it, and should not be decoded by other
+ * apps.
+ * </p><p>
+ * Type: INTEGER
+ * </p>
+ */
+ public static final String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2";
+
+ /**
+ * Internal integer flag used by individual TV input services.
+ * <p>
+ * This is internal to the provider that inserted it, and should not be decoded by other
+ * apps.
+ * </p><p>
+ * Type: INTEGER
+ * </p>
+ */
+ public static final String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3";
+
+ /**
+ * Internal integer flag used by individual TV input services.
+ * <p>
+ * This is internal to the provider that inserted it, and should not be decoded by other
+ * apps.
+ * </p><p>
+ * Type: INTEGER
+ * </p>
+ */
+ public static final String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4";
+
+ /**
* The version number of this row entry used by TV input services.
* <p>
* This is best used by sync adapters to identify the rows to update. The number can be
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index b9e99d2..5c1193f 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -116,12 +116,13 @@ public final class TvInputInfo implements Parcelable {
private final ResolveInfo mService;
private final String mId;
private final String mParentId;
+ private final int mType;
+ private final boolean mIsHardwareInput;
// Attributes from XML meta data.
private String mSetupActivity;
private String mSettingsActivity;
- private int mType = TYPE_TUNER;
private HdmiDeviceInfo mHdmiDeviceInfo;
private String mLabel;
private Uri mIconUri;
@@ -153,7 +154,7 @@ public final class TvInputInfo implements Parcelable {
throws XmlPullParserException, IOException {
return createTvInputInfo(context, service, generateInputIdForComponentName(
new ComponentName(service.serviceInfo.packageName, service.serviceInfo.name)),
- null, TYPE_TUNER, null, null, false);
+ null, TYPE_TUNER, false, null, null, false);
}
/**
@@ -177,7 +178,7 @@ public final class TvInputInfo implements Parcelable {
boolean isConnectedToHdmiSwitch = (hdmiDeviceInfo.getPhysicalAddress() & 0x0FFF) != 0;
TvInputInfo input = createTvInputInfo(context, service, generateInputIdForHdmiDevice(
new ComponentName(service.serviceInfo.packageName, service.serviceInfo.name),
- hdmiDeviceInfo), parentId, TYPE_HDMI, label, iconUri, isConnectedToHdmiSwitch);
+ hdmiDeviceInfo), parentId, TYPE_HDMI, true, label, iconUri, isConnectedToHdmiSwitch);
input.mHdmiDeviceInfo = hdmiDeviceInfo;
return input;
}
@@ -202,12 +203,12 @@ public final class TvInputInfo implements Parcelable {
int inputType = sHardwareTypeToTvInputType.get(hardwareInfo.getType(), TYPE_TUNER);
return createTvInputInfo(context, service, generateInputIdForHardware(
new ComponentName(service.serviceInfo.packageName, service.serviceInfo.name),
- hardwareInfo), null, inputType, label, iconUri, false);
+ hardwareInfo), null, inputType, true, label, iconUri, false);
}
private static TvInputInfo createTvInputInfo(Context context, ResolveInfo service,
- String id, String parentId, int inputType, String label, Uri iconUri,
- boolean isConnectedToHdmiSwitch)
+ String id, String parentId, int inputType, boolean isHardwareInput, String label,
+ Uri iconUri, boolean isConnectedToHdmiSwitch)
throws XmlPullParserException, IOException {
ServiceInfo si = service.serviceInfo;
PackageManager pm = context.getPackageManager();
@@ -233,7 +234,7 @@ public final class TvInputInfo implements Parcelable {
"Meta-data does not start with tv-input-service tag in " + si.name);
}
- TvInputInfo input = new TvInputInfo(service, id, parentId, inputType);
+ TvInputInfo input = new TvInputInfo(service, id, parentId, inputType, isHardwareInput);
TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.TvInputService);
input.mSetupActivity = sa.getString(
@@ -272,12 +273,16 @@ public final class TvInputInfo implements Parcelable {
* @param id ID of this TV input. Should be generated via generateInputId*().
* @param parentId ID of this TV input's parent input. {@code null} if none exists.
* @param type The type of this TV input service.
+ * @param isHardwareInput {@code true} if this TV input represents a hardware device.
+ * {@code false} otherwise.
*/
- private TvInputInfo(ResolveInfo service, String id, String parentId, int type) {
+ private TvInputInfo(ResolveInfo service, String id, String parentId, int type,
+ boolean isHardwareInput) {
mService = service;
mId = id;
mParentId = parentId;
mType = type;
+ mIsHardwareInput = isHardwareInput;
}
/**
@@ -381,6 +386,16 @@ public final class TvInputInfo implements Parcelable {
}
/**
+ * Returns {@code true} if this TV input represents a hardware device. (e.g. built-in tuner,
+ * HDMI1) {@code false} otherwise.
+ * @hide
+ */
+ @SystemApi
+ public boolean isHardwareInput() {
+ return mIsHardwareInput;
+ }
+
+ /**
* Returns {@code true}, if a CEC device for this TV input is connected to an HDMI switch, i.e.,
* the device isn't directly connected to a HDMI port.
* @hide
@@ -499,6 +514,7 @@ public final class TvInputInfo implements Parcelable {
dest.writeString(mSetupActivity);
dest.writeString(mSettingsActivity);
dest.writeInt(mType);
+ dest.writeByte(mIsHardwareInput ? (byte) 1 : 0);
dest.writeParcelable(mHdmiDeviceInfo, flags);
dest.writeParcelable(mIconUri, flags);
dest.writeString(mLabel);
@@ -572,6 +588,7 @@ public final class TvInputInfo implements Parcelable {
mSetupActivity = in.readString();
mSettingsActivity = in.readString();
mType = in.readInt();
+ mIsHardwareInput = in.readByte() == 1 ? true : false;
mHdmiDeviceInfo = in.readParcelable(null);
mIconUri = in.readParcelable(null);
mLabel = in.readString();
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index f552a51..8ed383a 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -48,7 +48,6 @@ import android.view.WindowManager;
import android.view.accessibility.CaptioningManager;
import android.widget.FrameLayout;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import java.util.ArrayList;
@@ -242,7 +241,7 @@ public abstract class TvInputService extends Service {
final Handler mHandler;
private WindowManager.LayoutParams mWindowParams;
private Surface mSurface;
- private Context mContext;
+ private final Context mContext;
private FrameLayout mOverlayViewContainer;
private View mOverlayView;
private OverlayViewCleanUpTask mOverlayViewCleanUpTask;
@@ -250,11 +249,11 @@ public abstract class TvInputService extends Service {
private IBinder mWindowToken;
private Rect mOverlayFrame;
- private Object mLock = new Object();
+ private final Object mLock = new Object();
// @GuardedBy("mLock")
private ITvInputSessionCallback mSessionCallback;
// @GuardedBy("mLock")
- private List<Runnable> mPendingActions = new ArrayList<>();
+ private final List<Runnable> mPendingActions = new ArrayList<>();
/**
* Creates a new Session.
@@ -615,16 +614,17 @@ public abstract class TvInputService extends Service {
public void onSetMain(boolean isMain) {
}
- /**
- * Sets the {@link Surface} for the current input session on which the TV input renders video.
- * <p>
- * When {@code setSurface(null)} is called, the implementation should stop using the Surface
- * object previously given and release any references to it.
- *
- * @param surface possibly {@code null} {@link Surface} an application passes to this TV input
- * session.
- * @return {@code true} if the surface was set, {@code false} otherwise.
- */
+ /**
+ * Sets the {@link Surface} for the current input session on which the TV input renders
+ * video.
+ * <p>
+ * When {@code setSurface(null)} is called, the implementation should stop using the Surface
+ * object previously given and release any references to it.
+ *
+ * @param surface possibly {@code null} {@link Surface} an application passes to this TV
+ * input session.
+ * @return {@code true} if the surface was set, {@code false} otherwise.
+ */
public abstract boolean onSetSurface(Surface surface);
/**
@@ -663,11 +663,11 @@ public abstract class TvInputService extends Service {
/**
* Tunes to a given channel. When the video is available, {@link #notifyVideoAvailable()}
- * should be called. Also, {@link #notifyVideoUnavailable(int)} should be called when the
- * TV input cannot continue playing the given channel.
+ * should be called. Also, {@link #notifyVideoUnavailable(int)} should be called when the TV
+ * input cannot continue playing the given channel.
*
* @param channelUri The URI of the channel.
- * @return {@code true} the tuning was successful, {@code false} otherwise.
+ * @return {@code true} if the tuning was successful, {@code false} otherwise.
*/
public abstract boolean onTune(Uri channelUri);
@@ -676,7 +676,7 @@ public abstract class TvInputService extends Service {
*
* @param channelUri The URI of the channel.
* @param params The extra parameters from other applications.
- * @return {@code true} the tuning was successful, {@code false} otherwise.
+ * @return {@code true} if the tuning was successful, {@code false} otherwise.
* @hide
*/
@SystemApi
@@ -712,10 +712,10 @@ public abstract class TvInputService extends Service {
}
/**
- * Select a given track.
+ * Selects a given track.
* <p>
* If this is done successfully, the implementation should call {@link #notifyTrackSelected}
- * to help applications maintain the selcted track lists.
+ * to help applications maintain the up-to-date list of the selected tracks.
* </p>
*
* @param trackId The ID of the track to select. {@code null} means to unselect the current
@@ -723,6 +723,7 @@ public abstract class TvInputService extends Service {
* @param type The type of the track to select. The type can be
* {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or
* {@link TvTrackInfo#TYPE_SUBTITLE}.
+ * @return {@code true} if the track selection was successful, {@code false} otherwise.
* @see #notifyTrackSelected
*/
public boolean onSelectTrack(int type, String trackId) {
@@ -1230,6 +1231,8 @@ public abstract class TvInputService extends Service {
args.arg2 = mProxySession;
args.arg3 = mProxySessionCallback;
args.arg4 = session.getToken();
+ session.tune(TvContract.buildChannelUriForPassthroughInput(
+ getHardwareInputId()));
} else {
args.arg1 = null;
args.arg2 = null;
@@ -1239,7 +1242,6 @@ public abstract class TvInputService extends Service {
}
mServiceHandler.obtainMessage(ServiceHandler.DO_NOTIFY_SESSION_CREATED, args)
.sendToTarget();
- session.tune(TvContract.buildChannelUriForPassthroughInput(getHardwareInputId()));
}
@Override
diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java
index e0aacd6..0284171 100644
--- a/media/java/android/media/tv/TvTrackInfo.java
+++ b/media/java/android/media/tv/TvTrackInfo.java
@@ -42,6 +42,7 @@ public final class TvTrackInfo implements Parcelable {
private final int mType;
private final String mId;
private final String mLanguage;
+ private final String mDescription;
private final int mAudioChannelCount;
private final int mAudioSampleRate;
private final int mVideoWidth;
@@ -49,12 +50,13 @@ public final class TvTrackInfo implements Parcelable {
private final float mVideoFrameRate;
private final Bundle mExtra;
- private TvTrackInfo(int type, String id, String language, int audioChannelCount,
- int audioSampleRate, int videoWidth, int videoHeight, float videoFrameRate,
- Bundle extra) {
+ private TvTrackInfo(int type, String id, String language, String description,
+ int audioChannelCount, int audioSampleRate, int videoWidth, int videoHeight,
+ float videoFrameRate, Bundle extra) {
mType = type;
mId = id;
mLanguage = language;
+ mDescription = description;
mAudioChannelCount = audioChannelCount;
mAudioSampleRate = audioSampleRate;
mVideoWidth = videoWidth;
@@ -67,6 +69,7 @@ public final class TvTrackInfo implements Parcelable {
mType = in.readInt();
mId = in.readString();
mLanguage = in.readString();
+ mDescription = in.readString();
mAudioChannelCount = in.readInt();
mAudioSampleRate = in.readInt();
mVideoWidth = in.readInt();
@@ -99,6 +102,13 @@ public final class TvTrackInfo implements Parcelable {
}
/**
+ * Returns a user readable description for the current track.
+ */
+ public final String getDescription() {
+ return mDescription;
+ }
+
+ /**
* Returns the audio channel count. Valid only for {@link #TYPE_AUDIO} tracks.
*/
public final int getAudioChannelCount() {
@@ -174,6 +184,7 @@ public final class TvTrackInfo implements Parcelable {
dest.writeInt(mType);
dest.writeString(mId);
dest.writeString(mLanguage);
+ dest.writeString(mDescription);
dest.writeInt(mAudioChannelCount);
dest.writeInt(mAudioSampleRate);
dest.writeInt(mVideoWidth);
@@ -202,6 +213,7 @@ public final class TvTrackInfo implements Parcelable {
private final String mId;
private final int mType;
private String mLanguage;
+ private String mDescription;
private int mAudioChannelCount;
private int mAudioSampleRate;
private int mVideoWidth;
@@ -241,6 +253,16 @@ public final class TvTrackInfo implements Parcelable {
}
/**
+ * Sets a user readable description for the current track.
+ *
+ * @param description The user readable description.
+ */
+ public final Builder setDescription(String description) {
+ mDescription = description;
+ return this;
+ }
+
+ /**
* Sets the audio channel count. Valid only for {@link #TYPE_AUDIO} tracks.
*
* @param audioChannelCount The audio channel count.
@@ -325,8 +347,8 @@ public final class TvTrackInfo implements Parcelable {
* @return The new {@link TvTrackInfo} instance
*/
public TvTrackInfo build() {
- return new TvTrackInfo(mType, mId, mLanguage, mAudioChannelCount, mAudioSampleRate,
- mVideoWidth, mVideoHeight, mVideoFrameRate, mExtra);
+ return new TvTrackInfo(mType, mId, mLanguage, mDescription, mAudioChannelCount,
+ mAudioSampleRate, mVideoWidth, mVideoHeight, mVideoFrameRate, mExtra);
}
}
-} \ No newline at end of file
+}
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index 5d9355a..3541fba 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -28,7 +28,6 @@ import android.database.sqlite.SQLiteDatabase;
import android.media.MediaScanner;
import android.net.Uri;
import android.os.BatteryManager;
-import android.os.BatteryStats;
import android.os.RemoteException;
import android.provider.MediaStore;
import android.provider.MediaStore.Audio;
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index 8287344..41156cb 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -16,7 +16,6 @@
package android.service.media;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index 4ebbe26..dae57a8 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -2,6 +2,7 @@ LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
+ android_media_ImageWriter.cpp \
android_media_ImageReader.cpp \
android_media_MediaCrypto.cpp \
android_media_MediaCodec.cpp \
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index cf69b8f..9fc7e8e 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -95,6 +95,9 @@ public:
void setBufferFormat(int format) { mFormat = format; }
int getBufferFormat() { return mFormat; }
+ void setBufferDataspace(android_dataspace dataSpace) { mDataSpace = dataSpace; }
+ android_dataspace getBufferDataspace() { return mDataSpace; }
+
void setBufferWidth(int width) { mWidth = width; }
int getBufferWidth() { return mWidth; }
@@ -111,6 +114,7 @@ private:
jobject mWeakThiz;
jclass mClazz;
int mFormat;
+ android_dataspace mDataSpace;
int mWidth;
int mHeight;
};
@@ -263,29 +267,6 @@ static void Image_setBuffer(JNIEnv* env, jobject thiz,
env->SetLongField(thiz, gSurfaceImageClassInfo.mLockedBuffer, reinterpret_cast<jlong>(buffer));
}
-// Some formats like JPEG defined with different values between android.graphics.ImageFormat and
-// graphics.h, need convert to the one defined in graphics.h here.
-static int Image_getPixelFormat(JNIEnv* env, int format)
-{
- int jpegFormat;
- jfieldID fid;
-
- ALOGV("%s: format = 0x%x", __FUNCTION__, format);
-
- jclass imageFormatClazz = env->FindClass("android/graphics/ImageFormat");
- ALOG_ASSERT(imageFormatClazz != NULL);
-
- fid = env->GetStaticFieldID(imageFormatClazz, "JPEG", "I");
- jpegFormat = env->GetStaticIntField(imageFormatClazz, fid);
-
- // Translate the JPEG to BLOB for camera purpose.
- if (format == jpegFormat) {
- format = HAL_PIXEL_FORMAT_BLOB;
- }
-
- return format;
-}
-
static uint32_t Image_getJpegSize(CpuConsumer::LockedBuffer* buffer, bool usingRGBAOverride)
{
ALOG_ASSERT(buffer != NULL, "Input buffer is NULL!!!");
@@ -483,7 +464,7 @@ static void Image_getLockedBufferInfo(JNIEnv* env, CpuConsumer::LockedBuffer* bu
}
static jint Image_imageGetPixelStride(JNIEnv* env, CpuConsumer::LockedBuffer* buffer, int idx,
- int32_t readerFormat)
+ int32_t halReaderFormat)
{
ALOGV("%s: buffer index: %d", __FUNCTION__, idx);
ALOG_ASSERT((idx < IMAGE_READER_MAX_NUM_PLANES) && (idx >= 0), "Index is out of range:%d", idx);
@@ -493,7 +474,7 @@ static jint Image_imageGetPixelStride(JNIEnv* env, CpuConsumer::LockedBuffer* bu
int32_t fmt = buffer->flexFormat;
- fmt = applyFormatOverrides(fmt, readerFormat);
+ fmt = applyFormatOverrides(fmt, halReaderFormat);
switch (fmt) {
case HAL_PIXEL_FORMAT_YCbCr_420_888:
@@ -543,7 +524,7 @@ static jint Image_imageGetPixelStride(JNIEnv* env, CpuConsumer::LockedBuffer* bu
}
static jint Image_imageGetRowStride(JNIEnv* env, CpuConsumer::LockedBuffer* buffer, int idx,
- int32_t readerFormat)
+ int32_t halReaderFormat)
{
ALOGV("%s: buffer index: %d", __FUNCTION__, idx);
ALOG_ASSERT((idx < IMAGE_READER_MAX_NUM_PLANES) && (idx >= 0));
@@ -553,7 +534,7 @@ static jint Image_imageGetRowStride(JNIEnv* env, CpuConsumer::LockedBuffer* buff
int32_t fmt = buffer->flexFormat;
- fmt = applyFormatOverrides(fmt, readerFormat);
+ fmt = applyFormatOverrides(fmt, halReaderFormat);
switch (fmt) {
case HAL_PIXEL_FORMAT_YCbCr_420_888:
@@ -682,11 +663,16 @@ static void ImageReader_init(JNIEnv* env, jobject thiz, jobject weakThiz,
{
status_t res;
int nativeFormat;
+ android_dataspace nativeDataspace;
ALOGV("%s: width:%d, height: %d, format: 0x%x, maxImages:%d",
__FUNCTION__, width, height, format, maxImages);
- nativeFormat = Image_getPixelFormat(env, format);
+ PublicFormat publicFormat = static_cast<PublicFormat>(format);
+ nativeFormat = android_view_Surface_mapPublicFormatToHalFormat(
+ publicFormat);
+ nativeDataspace = android_view_Surface_mapPublicFormatToHalDataspace(
+ publicFormat);
sp<IGraphicBufferProducer> gbProducer;
sp<IGraphicBufferConsumer> gbConsumer;
@@ -710,10 +696,11 @@ static void ImageReader_init(JNIEnv* env, jobject thiz, jobject weakThiz,
consumer->setFrameAvailableListener(ctx);
ImageReader_setNativeContext(env, thiz, ctx);
ctx->setBufferFormat(nativeFormat);
+ ctx->setBufferDataspace(nativeDataspace);
ctx->setBufferWidth(width);
ctx->setBufferHeight(height);
- // Set the width/height/format to the CpuConsumer
+ // Set the width/height/format/dataspace to the CpuConsumer
res = consumer->setDefaultBufferSize(width, height);
if (res != OK) {
jniThrowException(env, "java/lang/IllegalStateException",
@@ -725,6 +712,12 @@ static void ImageReader_init(JNIEnv* env, jobject thiz, jobject weakThiz,
jniThrowException(env, "java/lang/IllegalStateException",
"Failed to set CpuConsumer buffer format");
}
+ res = consumer->setDefaultBufferDataSpace(nativeDataspace);
+ if (res != OK) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Failed to set CpuConsumer buffer dataSpace");
+ }
+
}
static void ImageReader_close(JNIEnv* env, jobject thiz)
@@ -867,6 +860,25 @@ static jint ImageReader_imageSetup(JNIEnv* env, jobject thiz,
return ACQUIRE_SUCCESS;
}
+static void ImageReader_detachImage(JNIEnv* env, jobject thiz, jobject image) {
+ ALOGV("%s:", __FUNCTION__);
+ JNIImageReaderContext* ctx = ImageReader_getContext(env, thiz);
+ if (ctx == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", "ImageReader was already closed");
+ return;
+ }
+
+ // CpuConsumer* consumer = ctx->getCpuConsumer();
+ CpuConsumer::LockedBuffer* buffer = Image_getLockedBuffer(env, image);
+ if (!buffer) {
+ ALOGW("Image already released!!!");
+ return;
+ }
+
+ // TODO: need implement
+ jniThrowRuntimeException(env, "nativeDetachImage is not implemented yet!!!");
+}
+
static jobject ImageReader_getSurface(JNIEnv* env, jobject thiz)
{
ALOGV("%s: ", __FUNCTION__);
@@ -884,6 +896,8 @@ static jobject ImageReader_getSurface(JNIEnv* env, jobject thiz)
static jobject Image_createSurfacePlane(JNIEnv* env, jobject thiz, int idx, int readerFormat)
{
int rowStride, pixelStride;
+ PublicFormat publicReaderFormat = static_cast<PublicFormat>(readerFormat);
+
ALOGV("%s: buffer index: %d", __FUNCTION__, idx);
CpuConsumer::LockedBuffer* buffer = Image_getLockedBuffer(env, thiz);
@@ -893,10 +907,11 @@ static jobject Image_createSurfacePlane(JNIEnv* env, jobject thiz, int idx, int
jniThrowException(env, "java/lang/IllegalStateException", "Image was released");
}
- readerFormat = Image_getPixelFormat(env, readerFormat);
+ int halReaderFormat = android_view_Surface_mapPublicFormatToHalFormat(
+ publicReaderFormat);
- rowStride = Image_imageGetRowStride(env, buffer, idx, readerFormat);
- pixelStride = Image_imageGetPixelStride(env, buffer, idx, readerFormat);
+ rowStride = Image_imageGetRowStride(env, buffer, idx, halReaderFormat);
+ pixelStride = Image_imageGetPixelStride(env, buffer, idx, halReaderFormat);
jobject surfPlaneObj = env->NewObject(gSurfacePlaneClassInfo.clazz,
gSurfacePlaneClassInfo.ctor, thiz, idx, rowStride, pixelStride);
@@ -909,6 +924,7 @@ static jobject Image_getByteBuffer(JNIEnv* env, jobject thiz, int idx, int reade
uint8_t *base = NULL;
uint32_t size = 0;
jobject byteBuffer;
+ PublicFormat readerPublicFormat = static_cast<PublicFormat>(readerFormat);
ALOGV("%s: buffer index: %d", __FUNCTION__, idx);
@@ -918,10 +934,11 @@ static jobject Image_getByteBuffer(JNIEnv* env, jobject thiz, int idx, int reade
jniThrowException(env, "java/lang/IllegalStateException", "Image was released");
}
- readerFormat = Image_getPixelFormat(env, readerFormat);
+ int readerHalFormat = android_view_Surface_mapPublicFormatToHalFormat(
+ readerPublicFormat);
// Create byteBuffer from native buffer
- Image_getLockedBufferInfo(env, buffer, idx, &base, &size, readerFormat);
+ Image_getLockedBufferInfo(env, buffer, idx, &base, &size, readerHalFormat);
if (size > static_cast<uint32_t>(INT32_MAX)) {
// Byte buffer have 'int capacity', so check the range
@@ -963,6 +980,7 @@ static JNINativeMethod gImageReaderMethods[] = {
{"nativeReleaseImage", "(Landroid/media/Image;)V", (void*)ImageReader_imageRelease },
{"nativeImageSetup", "(Landroid/media/Image;)I", (void*)ImageReader_imageSetup },
{"nativeGetSurface", "()Landroid/view/Surface;", (void*)ImageReader_getSurface },
+ {"nativeDetachImage", "(Landroid/media/Image;)V", (void*)ImageReader_detachImage },
};
static JNINativeMethod gImageMethods[] = {
diff --git a/media/jni/android_media_ImageWriter.cpp b/media/jni/android_media_ImageWriter.cpp
new file mode 100644
index 0000000..d10df3e9
--- /dev/null
+++ b/media/jni/android_media_ImageWriter.cpp
@@ -0,0 +1,1014 @@
+/*
+ * Copyright 2015 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 "ImageWriter_JNI"
+#include <utils/Log.h>
+#include <utils/String8.h>
+
+#include <gui/IProducerListener.h>
+#include <gui/Surface.h>
+#include <gui/CpuConsumer.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/android_view_Surface.h>
+#include <camera3.h>
+
+#include <jni.h>
+#include <JNIHelp.h>
+
+#include <stdint.h>
+#include <inttypes.h>
+
+#define ALIGN(x, mask) ( ((x) + (mask) - 1) & ~((mask) - 1) )
+
+#define IMAGE_BUFFER_JNI_ID "mNativeBuffer"
+
+// ----------------------------------------------------------------------------
+
+using namespace android;
+
+enum {
+ IMAGE_WRITER_MAX_NUM_PLANES = 3,
+};
+
+static struct {
+ jmethodID postEventFromNative;
+ jfieldID mWriterFormat;
+} gImageWriterClassInfo;
+
+static struct {
+ jfieldID mNativeBuffer;
+ jfieldID mNativeFenceFd;
+ jfieldID mPlanes;
+} gSurfaceImageClassInfo;
+
+static struct {
+ jclass clazz;
+ jmethodID ctor;
+} gSurfacePlaneClassInfo;
+
+typedef CpuConsumer::LockedBuffer LockedImage;
+
+// ----------------------------------------------------------------------------
+
+class JNIImageWriterContext : public BnProducerListener {
+public:
+ JNIImageWriterContext(JNIEnv* env, jobject weakThiz, jclass clazz);
+
+ virtual ~JNIImageWriterContext();
+
+ // Implementation of IProducerListener, used to notify the ImageWriter that the consumer
+ // has returned a buffer and it is ready for ImageWriter to dequeue.
+ virtual void onBufferReleased();
+
+ void setProducer(const sp<ANativeWindow>& producer) { mProducer = producer; }
+ ANativeWindow* getProducer() { return mProducer.get(); }
+
+ void setBufferFormat(int format) { mFormat = format; }
+ int getBufferFormat() { return mFormat; }
+
+ void setBufferWidth(int width) { mWidth = width; }
+ int getBufferWidth() { return mWidth; }
+
+ void setBufferHeight(int height) { mHeight = height; }
+ int getBufferHeight() { return mHeight; }
+
+private:
+ static JNIEnv* getJNIEnv(bool* needsDetach);
+ static void detachJNI();
+
+ sp<ANativeWindow> mProducer;
+ jobject mWeakThiz;
+ jclass mClazz;
+ int mFormat;
+ int mWidth;
+ int mHeight;
+};
+
+JNIImageWriterContext::JNIImageWriterContext(JNIEnv* env, jobject weakThiz, jclass clazz) :
+ mWeakThiz(env->NewGlobalRef(weakThiz)),
+ mClazz((jclass)env->NewGlobalRef(clazz)),
+ mFormat(0),
+ mWidth(-1),
+ mHeight(-1) {
+}
+
+JNIImageWriterContext::~JNIImageWriterContext() {
+ ALOGV("%s", __FUNCTION__);
+ bool needsDetach = false;
+ JNIEnv* env = getJNIEnv(&needsDetach);
+ if (env != NULL) {
+ env->DeleteGlobalRef(mWeakThiz);
+ env->DeleteGlobalRef(mClazz);
+ } else {
+ ALOGW("leaking JNI object references");
+ }
+ if (needsDetach) {
+ detachJNI();
+ }
+
+ mProducer.clear();
+}
+
+JNIEnv* JNIImageWriterContext::getJNIEnv(bool* needsDetach) {
+ ALOGV("%s", __FUNCTION__);
+ LOG_ALWAYS_FATAL_IF(needsDetach == NULL, "needsDetach is null!!!");
+ *needsDetach = false;
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ if (env == NULL) {
+ JavaVMAttachArgs args = {JNI_VERSION_1_4, NULL, NULL};
+ JavaVM* vm = AndroidRuntime::getJavaVM();
+ int result = vm->AttachCurrentThread(&env, (void*) &args);
+ if (result != JNI_OK) {
+ ALOGE("thread attach failed: %#x", result);
+ return NULL;
+ }
+ *needsDetach = true;
+ }
+ return env;
+}
+
+void JNIImageWriterContext::detachJNI() {
+ ALOGV("%s", __FUNCTION__);
+ JavaVM* vm = AndroidRuntime::getJavaVM();
+ int result = vm->DetachCurrentThread();
+ if (result != JNI_OK) {
+ ALOGE("thread detach failed: %#x", result);
+ }
+}
+
+void JNIImageWriterContext::onBufferReleased() {
+ ALOGV("%s: buffer released", __FUNCTION__);
+ bool needsDetach = false;
+ JNIEnv* env = getJNIEnv(&needsDetach);
+ if (env != NULL) {
+ env->CallStaticVoidMethod(mClazz, gImageWriterClassInfo.postEventFromNative, mWeakThiz);
+ } else {
+ ALOGW("onBufferReleased event will not posted");
+ }
+ if (needsDetach) {
+ detachJNI();
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+extern "C" {
+
+// -------------------------------Private method declarations--------------
+
+static bool isWritable(int format);
+static bool isPossiblyYUV(PixelFormat format);
+static void Image_setNativeContext(JNIEnv* env, jobject thiz,
+ sp<GraphicBuffer> buffer, int fenceFd);
+static void Image_getNativeContext(JNIEnv* env, jobject thiz,
+ GraphicBuffer** buffer, int* fenceFd);
+static void Image_unlockIfLocked(JNIEnv* env, jobject thiz);
+
+// --------------------------ImageWriter methods---------------------------------------
+
+static void ImageWriter_classInit(JNIEnv* env, jclass clazz) {
+ ALOGV("%s:", __FUNCTION__);
+ jclass imageClazz = env->FindClass("android/media/ImageWriter$WriterSurfaceImage");
+ LOG_ALWAYS_FATAL_IF(imageClazz == NULL,
+ "can't find android/media/ImageWriter$WriterSurfaceImage");
+ gSurfaceImageClassInfo.mNativeBuffer = env->GetFieldID(
+ imageClazz, IMAGE_BUFFER_JNI_ID, "J");
+ LOG_ALWAYS_FATAL_IF(gSurfaceImageClassInfo.mNativeBuffer == NULL,
+ "can't find android/media/ImageWriter$WriterSurfaceImage.%s", IMAGE_BUFFER_JNI_ID);
+
+ gSurfaceImageClassInfo.mNativeFenceFd = env->GetFieldID(
+ imageClazz, "mNativeFenceFd", "I");
+ LOG_ALWAYS_FATAL_IF(gSurfaceImageClassInfo.mNativeFenceFd == NULL,
+ "can't find android/media/ImageWriter$WriterSurfaceImage.mNativeFenceFd");
+
+ gSurfaceImageClassInfo.mPlanes = env->GetFieldID(
+ imageClazz, "mPlanes", "[Landroid/media/ImageWriter$WriterSurfaceImage$SurfacePlane;");
+ LOG_ALWAYS_FATAL_IF(gSurfaceImageClassInfo.mPlanes == NULL,
+ "can't find android/media/ImageWriter$WriterSurfaceImage.mPlanes");
+
+ gImageWriterClassInfo.postEventFromNative = env->GetStaticMethodID(
+ clazz, "postEventFromNative", "(Ljava/lang/Object;)V");
+ LOG_ALWAYS_FATAL_IF(gImageWriterClassInfo.postEventFromNative == NULL,
+ "can't find android/media/ImageWriter.postEventFromNative");
+
+ gImageWriterClassInfo.mWriterFormat = env->GetFieldID(
+ clazz, "mWriterFormat", "I");
+ LOG_ALWAYS_FATAL_IF(gImageWriterClassInfo.mWriterFormat == NULL,
+ "can't find android/media/ImageWriter.mWriterFormat");
+
+ jclass planeClazz = env->FindClass("android/media/ImageWriter$WriterSurfaceImage$SurfacePlane");
+ LOG_ALWAYS_FATAL_IF(planeClazz == NULL, "Can not find SurfacePlane class");
+ // FindClass only gives a local reference of jclass object.
+ gSurfacePlaneClassInfo.clazz = (jclass) env->NewGlobalRef(planeClazz);
+ gSurfacePlaneClassInfo.ctor = env->GetMethodID(gSurfacePlaneClassInfo.clazz, "<init>",
+ "(Landroid/media/ImageWriter$WriterSurfaceImage;IILjava/nio/ByteBuffer;)V");
+ LOG_ALWAYS_FATAL_IF(gSurfacePlaneClassInfo.ctor == NULL,
+ "Can not find SurfacePlane constructor");
+}
+
+static jlong ImageWriter_init(JNIEnv* env, jobject thiz, jobject weakThiz, jobject jsurface,
+ jint maxImages) {
+ status_t res;
+
+ ALOGV("%s: maxImages:%d", __FUNCTION__, maxImages);
+
+ sp<Surface> surface(android_view_Surface_getSurface(env, jsurface));
+ if (surface == NULL) {
+ jniThrowException(env,
+ "java/lang/IllegalArgumentException",
+ "The surface has been released");
+ return 0;
+ }
+ sp<IGraphicBufferProducer> bufferProducer = surface->getIGraphicBufferProducer();
+
+ jclass clazz = env->GetObjectClass(thiz);
+ if (clazz == NULL) {
+ jniThrowRuntimeException(env, "Can't find android/graphics/ImageWriter");
+ return 0;
+ }
+ sp<JNIImageWriterContext> ctx(new JNIImageWriterContext(env, weakThiz, clazz));
+
+ sp<Surface> producer = new Surface(bufferProducer, /*controlledByApp*/false);
+ ctx->setProducer(producer);
+ /**
+ * NATIVE_WINDOW_API_CPU isn't a good choice here, as it makes the bufferQueue not connectable
+ * after disconnect. MEDIA or CAMERA are treated the same internally. The producer listener
+ * will be cleared after disconnect call.
+ */
+ producer->connect(/*api*/NATIVE_WINDOW_API_CAMERA, /*listener*/ctx);
+ jlong nativeCtx = reinterpret_cast<jlong>(ctx.get());
+
+ // Get the dimension and format of the producer.
+ sp<ANativeWindow> anw = producer;
+ int32_t width, height, format;
+ if ((res = anw->query(anw.get(), NATIVE_WINDOW_WIDTH, &width)) != OK) {
+ ALOGE("%s: Query Surface width failed: %s (%d)", __FUNCTION__, strerror(-res), res);
+ jniThrowRuntimeException(env, "Failed to query Surface width");
+ return 0;
+ }
+ ctx->setBufferWidth(width);
+
+ if ((res = anw->query(anw.get(), NATIVE_WINDOW_HEIGHT, &height)) != OK) {
+ ALOGE("%s: Query Surface height failed: %s (%d)", __FUNCTION__, strerror(-res), res);
+ jniThrowRuntimeException(env, "Failed to query Surface height");
+ return 0;
+ }
+ ctx->setBufferHeight(height);
+
+ if ((res = anw->query(anw.get(), NATIVE_WINDOW_FORMAT, &format)) != OK) {
+ ALOGE("%s: Query Surface format failed: %s (%d)", __FUNCTION__, strerror(-res), res);
+ jniThrowRuntimeException(env, "Failed to query Surface format");
+ return 0;
+ }
+ ctx->setBufferFormat(format);
+ env->SetIntField(thiz, gImageWriterClassInfo.mWriterFormat, reinterpret_cast<jint>(format));
+
+
+ if (isWritable(format)) {
+ res = native_window_set_usage(anw.get(), GRALLOC_USAGE_SW_WRITE_OFTEN);
+ if (res != OK) {
+ ALOGE("%s: Configure usage %08x for format %08x failed: %s (%d)",
+ __FUNCTION__, GRALLOC_USAGE_SW_WRITE_OFTEN, format, strerror(-res), res);
+ jniThrowRuntimeException(env, "Failed to SW_WRITE_OFTEN configure usage");
+ return 0;
+ }
+ }
+
+ int minUndequeuedBufferCount = 0;
+ res = anw->query(anw.get(),
+ NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &minUndequeuedBufferCount);
+ if (res != OK) {
+ ALOGE("%s: Query producer undequeued buffer count failed: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ jniThrowRuntimeException(env, "Query producer undequeued buffer count failed");
+ return 0;
+ }
+
+ size_t totalBufferCount = maxImages + minUndequeuedBufferCount;
+ res = native_window_set_buffer_count(anw.get(), totalBufferCount);
+ if (res != OK) {
+ ALOGE("%s: Set buffer count failed: %s (%d)", __FUNCTION__, strerror(-res), res);
+ jniThrowRuntimeException(env, "Set buffer count failed");
+ return 0;
+ }
+
+ if (ctx != 0) {
+ ctx->incStrong((void*)ImageWriter_init);
+ }
+ return nativeCtx;
+}
+
+static void ImageWriter_dequeueImage(JNIEnv* env, jobject thiz, jlong nativeCtx, jobject image) {
+ ALOGV("%s", __FUNCTION__);
+ JNIImageWriterContext* const ctx = reinterpret_cast<JNIImageWriterContext *>(nativeCtx);
+ if (ctx == NULL || thiz == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "ImageWriterContext is not initialized");
+ return;
+ }
+
+ sp<ANativeWindow> anw = ctx->getProducer();
+ android_native_buffer_t *anb = NULL;
+ int fenceFd = -1;
+ status_t res = anw->dequeueBuffer(anw.get(), &anb, &fenceFd);
+ if (res != OK) {
+ // TODO: handle different error cases here.
+ ALOGE("%s: Set buffer count failed: %s (%d)", __FUNCTION__, strerror(-res), res);
+ jniThrowRuntimeException(env, "dequeue buffer failed");
+ return;
+ }
+ // New GraphicBuffer object doesn't own the handle, thus the native buffer
+ // won't be freed when this object is destroyed.
+ sp<GraphicBuffer> buffer(new GraphicBuffer(anb, /*keepOwnership*/false));
+
+ // Note that:
+ // 1. No need to lock buffer now, will only lock it when the first getPlanes() is called.
+ // 2. Fence will be saved to mNativeFenceFd, and will consumed by lock/queue/cancel buffer
+ // later.
+ // 3. need use lockAsync here, as it will handle the dequeued fence for us automatically.
+
+ // Finally, set the native info into image object.
+ Image_setNativeContext(env, image, buffer, fenceFd);
+}
+
+static void ImageWriter_close(JNIEnv* env, jobject thiz, jlong nativeCtx) {
+ ALOGV("%s:", __FUNCTION__);
+ JNIImageWriterContext* const ctx = reinterpret_cast<JNIImageWriterContext *>(nativeCtx);
+ if (ctx == NULL || thiz == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "ImageWriterContext is not initialized");
+ return;
+ }
+
+ ANativeWindow* producer = ctx->getProducer();
+ if (producer != NULL) {
+ /**
+ * NATIVE_WINDOW_API_CPU isn't a good choice here, as it makes the bufferQueue not
+ * connectable after disconnect. MEDIA or CAMERA are treated the same internally.
+ * The producer listener will be cleared after disconnect call.
+ */
+ status_t res = native_window_api_disconnect(producer, /*api*/NATIVE_WINDOW_API_CAMERA);
+ /**
+ * This is not an error. if client calling process dies, the window will
+ * also die and all calls to it will return DEAD_OBJECT, thus it's already
+ * "disconnected"
+ */
+ if (res == DEAD_OBJECT) {
+ ALOGW("%s: While disconnecting ImageWriter from native window, the"
+ " native window died already", __FUNCTION__);
+ } else if (res != OK) {
+ ALOGE("%s: native window disconnect failed: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ jniThrowRuntimeException(env, "Native window disconnect failed");
+ return;
+ }
+ }
+
+ ctx->decStrong((void*)ImageWriter_init);
+}
+
+static void ImageWriter_cancelImage(JNIEnv* env, jobject thiz, jlong nativeCtx, jobject image) {
+ ALOGV("%s", __FUNCTION__);
+ JNIImageWriterContext* const ctx = reinterpret_cast<JNIImageWriterContext *>(nativeCtx);
+ if (ctx == NULL || thiz == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "ImageWriterContext is not initialized");
+ return;
+ }
+
+ sp<ANativeWindow> anw = ctx->getProducer();
+
+ GraphicBuffer *buffer = NULL;
+ int fenceFd = -1;
+ Image_getNativeContext(env, image, &buffer, &fenceFd);
+ if (buffer == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Image is not initialized");
+ return;
+ }
+
+ // Unlock the image if it was locked
+ Image_unlockIfLocked(env, image);
+
+ anw->cancelBuffer(anw.get(), buffer, fenceFd);
+
+ Image_setNativeContext(env, image, NULL, -1);
+}
+
+static void ImageWriter_queueImage(JNIEnv* env, jobject thiz, jlong nativeCtx, jobject image,
+ jlong timestampNs, jint left, jint top, jint right, jint bottom) {
+ ALOGV("%s", __FUNCTION__);
+ JNIImageWriterContext* const ctx = reinterpret_cast<JNIImageWriterContext *>(nativeCtx);
+ if (ctx == NULL || thiz == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "ImageWriterContext is not initialized");
+ return;
+ }
+
+ status_t res = OK;
+ sp<ANativeWindow> anw = ctx->getProducer();
+
+ GraphicBuffer *buffer = NULL;
+ int fenceFd = -1;
+ Image_getNativeContext(env, image, &buffer, &fenceFd);
+ if (buffer == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Image is not initialized");
+ return;
+ }
+
+ // Unlock image if it was locked.
+ Image_unlockIfLocked(env, image);
+
+ // Set timestamp
+ ALOGV("timestamp to be queued: %" PRId64, timestampNs);
+ res = native_window_set_buffers_timestamp(anw.get(), timestampNs);
+ if (res != OK) {
+ jniThrowRuntimeException(env, "Set timestamp failed");
+ return;
+ }
+
+ // Set crop
+ android_native_rect_t cropRect;
+ cropRect.left = left;
+ cropRect.top = top;
+ cropRect.right = right;
+ cropRect.bottom = bottom;
+ res = native_window_set_crop(anw.get(), &cropRect);
+ if (res != OK) {
+ jniThrowRuntimeException(env, "Set crop rect failed");
+ return;
+ }
+
+ // Finally, queue input buffer
+ res = anw->queueBuffer(anw.get(), buffer, fenceFd);
+ if (res != OK) {
+ jniThrowRuntimeException(env, "Queue input buffer failed");
+ return;
+ }
+
+ Image_setNativeContext(env, image, NULL, -1);
+}
+
+static void ImageWriter_attachImage(JNIEnv* env, jobject thiz, jlong nativeCtx, jobject image) {
+ ALOGV("%s", __FUNCTION__);
+ JNIImageWriterContext* const ctx = reinterpret_cast<JNIImageWriterContext *>(nativeCtx);
+ if (ctx == NULL || thiz == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "ImageWriterContext is not initialized");
+ return;
+ }
+
+ sp<ANativeWindow> anw = ctx->getProducer();
+
+ GraphicBuffer *buffer = NULL;
+ int fenceFd = -1;
+ Image_getNativeContext(env, image, &buffer, &fenceFd);
+ if (buffer == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Image is not initialized");
+ return;
+ }
+
+ // TODO: need implement
+ jniThrowRuntimeException(env, "nativeAttachImage is not implement yet!!!");
+}
+
+// --------------------------Image methods---------------------------------------
+
+static void Image_getNativeContext(JNIEnv* env, jobject thiz,
+ GraphicBuffer** buffer, int* fenceFd) {
+ ALOGV("%s", __FUNCTION__);
+ if (buffer != NULL) {
+ GraphicBuffer *gb = reinterpret_cast<GraphicBuffer *>
+ (env->GetLongField(thiz, gSurfaceImageClassInfo.mNativeBuffer));
+ *buffer = gb;
+ }
+
+ if (fenceFd != NULL) {
+ *fenceFd = reinterpret_cast<jint>(env->GetIntField(
+ thiz, gSurfaceImageClassInfo.mNativeFenceFd));
+ }
+}
+
+static void Image_setNativeContext(JNIEnv* env, jobject thiz,
+ sp<GraphicBuffer> buffer, int fenceFd) {
+ ALOGV("%s:", __FUNCTION__);
+ GraphicBuffer* p = NULL;
+ Image_getNativeContext(env, thiz, &p, /*fenceFd*/NULL);
+ if (buffer != 0) {
+ buffer->incStrong((void*)Image_setNativeContext);
+ }
+ if (p) {
+ p->decStrong((void*)Image_setNativeContext);
+ }
+ env->SetLongField(thiz, gSurfaceImageClassInfo.mNativeBuffer,
+ reinterpret_cast<jlong>(buffer.get()));
+
+ env->SetIntField(thiz, gSurfaceImageClassInfo.mNativeFenceFd, reinterpret_cast<jint>(fenceFd));
+}
+
+static void Image_unlockIfLocked(JNIEnv* env, jobject thiz) {
+ ALOGV("%s", __FUNCTION__);
+ GraphicBuffer* buffer;
+ Image_getNativeContext(env, thiz, &buffer, NULL);
+ if (buffer == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Image is not initialized");
+ return;
+ }
+
+ // Is locked?
+ bool isLocked = false;
+ jobject planes = env->GetObjectField(thiz, gSurfaceImageClassInfo.mPlanes);
+ isLocked = (planes != NULL);
+ if (isLocked) {
+ // no need to use fence here, as we it will be consumed by either concel or queue buffer.
+ status_t res = buffer->unlock();
+ if (res != OK) {
+ jniThrowRuntimeException(env, "unlock buffer failed");
+ }
+ ALOGV("Successfully unlocked the image");
+ }
+}
+
+static jint Image_getWidth(JNIEnv* env, jobject thiz) {
+ ALOGV("%s", __FUNCTION__);
+ GraphicBuffer* buffer;
+ Image_getNativeContext(env, thiz, &buffer, NULL);
+ if (buffer == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Image is not initialized");
+ return -1;
+ }
+
+ return buffer->getWidth();
+}
+
+static jint Image_getHeight(JNIEnv* env, jobject thiz) {
+ ALOGV("%s", __FUNCTION__);
+ GraphicBuffer* buffer;
+ Image_getNativeContext(env, thiz, &buffer, NULL);
+ if (buffer == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Image is not initialized");
+ return -1;
+ }
+
+ return buffer->getHeight();
+}
+
+// Some formats like JPEG defined with different values between android.graphics.ImageFormat and
+// graphics.h, need convert to the one defined in graphics.h here.
+static int Image_getPixelFormat(JNIEnv* env, int format) {
+ int jpegFormat;
+ jfieldID fid;
+
+ ALOGV("%s: format = 0x%x", __FUNCTION__, format);
+
+ jclass imageFormatClazz = env->FindClass("android/graphics/ImageFormat");
+ ALOG_ASSERT(imageFormatClazz != NULL);
+
+ fid = env->GetStaticFieldID(imageFormatClazz, "JPEG", "I");
+ jpegFormat = env->GetStaticIntField(imageFormatClazz, fid);
+
+ // Translate the JPEG to BLOB for camera purpose.
+ if (format == jpegFormat) {
+ format = HAL_PIXEL_FORMAT_BLOB;
+ }
+
+ return format;
+}
+
+static jint Image_getFormat(JNIEnv* env, jobject thiz) {
+ ALOGV("%s", __FUNCTION__);
+ GraphicBuffer* buffer;
+ Image_getNativeContext(env, thiz, &buffer, NULL);
+ if (buffer == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Image is not initialized");
+ return 0;
+ }
+
+ return Image_getPixelFormat(env, buffer->getPixelFormat());
+}
+
+static void Image_setFenceFd(JNIEnv* env, jobject thiz, int fenceFd) {
+ ALOGV("%s:", __FUNCTION__);
+ env->SetIntField(thiz, gSurfaceImageClassInfo.mNativeFenceFd, reinterpret_cast<jint>(fenceFd));
+}
+
+static void Image_getLockedImage(JNIEnv* env, jobject thiz, LockedImage *image) {
+ ALOGV("%s", __FUNCTION__);
+ GraphicBuffer* buffer;
+ int fenceFd = -1;
+ Image_getNativeContext(env, thiz, &buffer, &fenceFd);
+ if (buffer == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Image is not initialized");
+ return;
+ }
+
+ void* pData = NULL;
+ android_ycbcr ycbcr = android_ycbcr();
+ status_t res;
+ int format = Image_getFormat(env, thiz);
+ int flexFormat = format;
+ if (isPossiblyYUV(format)) {
+ // ImageWriter doesn't use crop by itself, app sets it, use the no crop version.
+ res = buffer->lockAsyncYCbCr(GRALLOC_USAGE_SW_WRITE_OFTEN, &ycbcr, fenceFd);
+ // Clear the fenceFd as it is already consumed by lock call.
+ Image_setFenceFd(env, thiz, /*fenceFd*/-1);
+ if (res != OK) {
+ jniThrowRuntimeException(env, "lockAsyncYCbCr failed for YUV buffer");
+ return;
+ }
+ pData = ycbcr.y;
+ flexFormat = HAL_PIXEL_FORMAT_YCbCr_420_888;
+ }
+
+ // lockAsyncYCbCr for YUV is unsuccessful.
+ if (pData == NULL) {
+ res = buffer->lockAsync(GRALLOC_USAGE_SW_WRITE_OFTEN, &pData, fenceFd);
+ if (res != OK) {
+ jniThrowRuntimeException(env, "lockAsync failed");
+ return;
+ }
+ }
+
+ image->data = reinterpret_cast<uint8_t*>(pData);
+ image->width = buffer->getWidth();
+ image->height = buffer->getHeight();
+ image->format = format;
+ image->flexFormat = flexFormat;
+ image->stride = (ycbcr.y != NULL) ? static_cast<uint32_t>(ycbcr.ystride) : buffer->getStride();
+
+ image->dataCb = reinterpret_cast<uint8_t*>(ycbcr.cb);
+ image->dataCr = reinterpret_cast<uint8_t*>(ycbcr.cr);
+ image->chromaStride = static_cast<uint32_t>(ycbcr.cstride);
+ image->chromaStep = static_cast<uint32_t>(ycbcr.chroma_step);
+ ALOGV("Successfully locked the image");
+ // crop, transform, scalingMode, timestamp, and frameNumber should be set by producer,
+ // and we don't set them here.
+}
+
+static bool usingRGBAToJpegOverride(int32_t bufferFormat, int32_t writerCtxFormat) {
+ return writerCtxFormat == HAL_PIXEL_FORMAT_BLOB && bufferFormat == HAL_PIXEL_FORMAT_RGBA_8888;
+}
+
+static int32_t applyFormatOverrides(int32_t bufferFormat, int32_t writerCtxFormat)
+{
+ // Using HAL_PIXEL_FORMAT_RGBA_8888 gralloc buffers containing JPEGs to get around SW
+ // write limitations for some platforms (b/17379185).
+ if (usingRGBAToJpegOverride(bufferFormat, writerCtxFormat)) {
+ return HAL_PIXEL_FORMAT_BLOB;
+ }
+ return bufferFormat;
+}
+
+static uint32_t Image_getJpegSize(LockedImage* buffer, bool usingRGBAOverride) {
+ ALOGV("%s", __FUNCTION__);
+ ALOG_ASSERT(buffer != NULL, "Input buffer is NULL!!!");
+ uint32_t size = 0;
+ uint32_t width = buffer->width;
+ uint8_t* jpegBuffer = buffer->data;
+
+ if (usingRGBAOverride) {
+ width = (buffer->width + buffer->stride * (buffer->height - 1)) * 4;
+ }
+
+ // First check for JPEG transport header at the end of the buffer
+ uint8_t* header = jpegBuffer + (width - sizeof(struct camera3_jpeg_blob));
+ struct camera3_jpeg_blob *blob = (struct camera3_jpeg_blob*)(header);
+ if (blob->jpeg_blob_id == CAMERA3_JPEG_BLOB_ID) {
+ size = blob->jpeg_size;
+ ALOGV("%s: Jpeg size = %d", __FUNCTION__, size);
+ }
+
+ // failed to find size, default to whole buffer
+ if (size == 0) {
+ /*
+ * This is a problem because not including the JPEG header
+ * means that in certain rare situations a regular JPEG blob
+ * will be misidentified as having a header, in which case
+ * we will get a garbage size value.
+ */
+ ALOGW("%s: No JPEG header detected, defaulting to size=width=%d",
+ __FUNCTION__, width);
+ size = width;
+ }
+
+ return size;
+}
+
+static void Image_getLockedImageInfo(JNIEnv* env, LockedImage* buffer, int idx,
+ int32_t writerFormat, uint8_t **base, uint32_t *size, int *pixelStride, int *rowStride) {
+ ALOGV("%s", __FUNCTION__);
+ ALOG_ASSERT(buffer != NULL, "Input buffer is NULL!!!");
+ ALOG_ASSERT(base != NULL, "base is NULL!!!");
+ ALOG_ASSERT(size != NULL, "size is NULL!!!");
+ ALOG_ASSERT(pixelStride != NULL, "pixelStride is NULL!!!");
+ ALOG_ASSERT(rowStride != NULL, "rowStride is NULL!!!");
+ ALOG_ASSERT((idx < IMAGE_WRITER_MAX_NUM_PLANES) && (idx >= 0));
+
+ ALOGV("%s: buffer: %p", __FUNCTION__, buffer);
+
+ uint32_t dataSize, ySize, cSize, cStride;
+ uint32_t pStride = 0, rStride = 0;
+ uint8_t *cb, *cr;
+ uint8_t *pData = NULL;
+ int bytesPerPixel = 0;
+
+ dataSize = ySize = cSize = cStride = 0;
+ int32_t fmt = buffer->flexFormat;
+
+ bool usingRGBAOverride = usingRGBAToJpegOverride(fmt, writerFormat);
+ fmt = applyFormatOverrides(fmt, writerFormat);
+ switch (fmt) {
+ case HAL_PIXEL_FORMAT_YCbCr_420_888:
+ pData =
+ (idx == 0) ?
+ buffer->data :
+ (idx == 1) ?
+ buffer->dataCb :
+ buffer->dataCr;
+ // only map until last pixel
+ if (idx == 0) {
+ pStride = 1;
+ rStride = buffer->stride;
+ dataSize = buffer->stride * (buffer->height - 1) + buffer->width;
+ } else {
+ pStride = buffer->chromaStep;
+ rStride = buffer->chromaStride;
+ dataSize = buffer->chromaStride * (buffer->height / 2 - 1) +
+ buffer->chromaStep * (buffer->width / 2 - 1) + 1;
+ }
+ break;
+ // NV21
+ case HAL_PIXEL_FORMAT_YCrCb_420_SP:
+ cr = buffer->data + (buffer->stride * buffer->height);
+ cb = cr + 1;
+ // only map until last pixel
+ ySize = buffer->width * (buffer->height - 1) + buffer->width;
+ cSize = buffer->width * (buffer->height / 2 - 1) + buffer->width - 1;
+
+ pData =
+ (idx == 0) ?
+ buffer->data :
+ (idx == 1) ?
+ cb:
+ cr;
+
+ dataSize = (idx == 0) ? ySize : cSize;
+ pStride = (idx == 0) ? 1 : 2;
+ rStride = buffer->width;
+ break;
+ case HAL_PIXEL_FORMAT_YV12:
+ // Y and C stride need to be 16 pixel aligned.
+ LOG_ALWAYS_FATAL_IF(buffer->stride % 16,
+ "Stride is not 16 pixel aligned %d", buffer->stride);
+
+ ySize = buffer->stride * buffer->height;
+ cStride = ALIGN(buffer->stride / 2, 16);
+ cr = buffer->data + ySize;
+ cSize = cStride * buffer->height / 2;
+ cb = cr + cSize;
+
+ pData =
+ (idx == 0) ?
+ buffer->data :
+ (idx == 1) ?
+ cb :
+ cr;
+ dataSize = (idx == 0) ? ySize : cSize;
+ pStride = 1;
+ rStride = (idx == 0) ? buffer->stride : ALIGN(buffer->stride / 2, 16);
+ break;
+ case HAL_PIXEL_FORMAT_Y8:
+ // Single plane, 8bpp.
+ ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
+
+ pData = buffer->data;
+ dataSize = buffer->stride * buffer->height;
+ pStride = 1;
+ rStride = buffer->stride;
+ break;
+ case HAL_PIXEL_FORMAT_Y16:
+ bytesPerPixel = 2;
+ // Single plane, 16bpp, strides are specified in pixels, not in bytes
+ ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
+
+ pData = buffer->data;
+ dataSize = buffer->stride * buffer->height * bytesPerPixel;
+ pStride = bytesPerPixel;
+ rStride = buffer->stride * 2;
+ break;
+ case HAL_PIXEL_FORMAT_BLOB:
+ // Used for JPEG data, height must be 1, width == size, single plane.
+ ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
+ ALOG_ASSERT(buffer->height == 1, "JPEG should has height value %d", buffer->height);
+
+ pData = buffer->data;
+ dataSize = Image_getJpegSize(buffer, usingRGBAOverride);
+ pStride = bytesPerPixel;
+ rowStride = 0;
+ break;
+ case HAL_PIXEL_FORMAT_RAW16:
+ // Single plane 16bpp bayer data.
+ bytesPerPixel = 2;
+ ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
+ pData = buffer->data;
+ dataSize = buffer->stride * buffer->height * bytesPerPixel;
+ pStride = bytesPerPixel;
+ rStride = buffer->stride * 2;
+ break;
+ case HAL_PIXEL_FORMAT_RAW10:
+ // Single plane 10bpp bayer data.
+ ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
+ LOG_ALWAYS_FATAL_IF(buffer->width % 4,
+ "Width is not multiple of 4 %d", buffer->width);
+ LOG_ALWAYS_FATAL_IF(buffer->height % 2,
+ "Height is not even %d", buffer->height);
+ LOG_ALWAYS_FATAL_IF(buffer->stride < (buffer->width * 10 / 8),
+ "stride (%d) should be at least %d",
+ buffer->stride, buffer->width * 10 / 8);
+ pData = buffer->data;
+ dataSize = buffer->stride * buffer->height;
+ pStride = 0;
+ rStride = buffer->stride;
+ break;
+ case HAL_PIXEL_FORMAT_RGBA_8888:
+ case HAL_PIXEL_FORMAT_RGBX_8888:
+ // Single plane, 32bpp.
+ bytesPerPixel = 4;
+ ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
+ pData = buffer->data;
+ dataSize = buffer->stride * buffer->height * bytesPerPixel;
+ pStride = bytesPerPixel;
+ rStride = buffer->stride * 4;
+ break;
+ case HAL_PIXEL_FORMAT_RGB_565:
+ // Single plane, 16bpp.
+ bytesPerPixel = 2;
+ ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
+ pData = buffer->data;
+ dataSize = buffer->stride * buffer->height * bytesPerPixel;
+ pStride = bytesPerPixel;
+ rStride = buffer->stride * 2;
+ break;
+ case HAL_PIXEL_FORMAT_RGB_888:
+ // Single plane, 24bpp.
+ bytesPerPixel = 3;
+ ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
+ pData = buffer->data;
+ dataSize = buffer->stride * buffer->height * bytesPerPixel;
+ pStride = bytesPerPixel;
+ rStride = buffer->stride * 3;
+ break;
+ default:
+ jniThrowExceptionFmt(env, "java/lang/UnsupportedOperationException",
+ "Pixel format: 0x%x is unsupported", fmt);
+ break;
+ }
+
+ *base = pData;
+ *size = dataSize;
+ *pixelStride = pStride;
+ *rowStride = rStride;
+}
+
+static jobjectArray Image_createSurfacePlanes(JNIEnv* env, jobject thiz,
+ int numPlanes, int writerFormat) {
+ ALOGV("%s: create SurfacePlane array with size %d", __FUNCTION__, numPlanes);
+ int rowStride, pixelStride;
+ uint8_t *pData;
+ uint32_t dataSize;
+ jobject byteBuffer;
+
+ int format = Image_getFormat(env, thiz);
+ if (!isWritable(format) && numPlanes > 0) {
+ String8 msg;
+ msg.appendFormat("Format 0x%x is opaque, thus not writable, the number of planes (%d)"
+ " must be 0", format, numPlanes);
+ jniThrowException(env, "java/lang/IllegalArgumentException", msg.string());
+ return NULL;
+ }
+
+ jobjectArray surfacePlanes = env->NewObjectArray(numPlanes, gSurfacePlaneClassInfo.clazz,
+ /*initial_element*/NULL);
+ if (surfacePlanes == NULL) {
+ jniThrowRuntimeException(env, "Failed to create SurfacePlane arrays,"
+ " probably out of memory");
+ return NULL;
+ }
+
+ // Buildup buffer info: rowStride, pixelStride and byteBuffers.
+ LockedImage lockedImg = LockedImage();
+ Image_getLockedImage(env, thiz, &lockedImg);
+
+ // Create all SurfacePlanes
+ writerFormat = Image_getPixelFormat(env, writerFormat);
+ for (int i = 0; i < numPlanes; i++) {
+ Image_getLockedImageInfo(env, &lockedImg, i, writerFormat,
+ &pData, &dataSize, &pixelStride, &rowStride);
+ byteBuffer = env->NewDirectByteBuffer(pData, dataSize);
+ if ((byteBuffer == NULL) && (env->ExceptionCheck() == false)) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Failed to allocate ByteBuffer");
+ return NULL;
+ }
+
+ // Finally, create this SurfacePlane.
+ jobject surfacePlane = env->NewObject(gSurfacePlaneClassInfo.clazz,
+ gSurfacePlaneClassInfo.ctor, thiz, rowStride, pixelStride, byteBuffer);
+ env->SetObjectArrayElement(surfacePlanes, i, surfacePlane);
+ }
+
+ return surfacePlanes;
+}
+
+// -------------------------------Private convenience methods--------------------
+
+// Check if buffer with this format is writable. Generally speaking, the opaque formats
+// like IMPLEMENTATION_DEFINED is not writable, as the actual buffer formats and layouts
+// are unknown to frameworks.
+static bool isWritable(int format) {
+ // Assume all other formats are writable.
+ return !(format == HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED ||
+ format == HAL_PIXEL_FORMAT_RAW_OPAQUE);
+}
+
+static bool isPossiblyYUV(PixelFormat format) {
+ switch (static_cast<int>(format)) {
+ case HAL_PIXEL_FORMAT_RGBA_8888:
+ case HAL_PIXEL_FORMAT_RGBX_8888:
+ case HAL_PIXEL_FORMAT_RGB_888:
+ case HAL_PIXEL_FORMAT_RGB_565:
+ case HAL_PIXEL_FORMAT_BGRA_8888:
+ case HAL_PIXEL_FORMAT_Y8:
+ case HAL_PIXEL_FORMAT_Y16:
+ case HAL_PIXEL_FORMAT_RAW16:
+ case HAL_PIXEL_FORMAT_RAW10:
+ case HAL_PIXEL_FORMAT_RAW_OPAQUE:
+ case HAL_PIXEL_FORMAT_BLOB:
+ case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
+ return false;
+
+ case HAL_PIXEL_FORMAT_YV12:
+ case HAL_PIXEL_FORMAT_YCbCr_420_888:
+ case HAL_PIXEL_FORMAT_YCbCr_422_SP:
+ case HAL_PIXEL_FORMAT_YCrCb_420_SP:
+ case HAL_PIXEL_FORMAT_YCbCr_422_I:
+ default:
+ return true;
+ }
+}
+
+} // extern "C"
+
+// ----------------------------------------------------------------------------
+
+static JNINativeMethod gImageWriterMethods[] = {
+ {"nativeClassInit", "()V", (void*)ImageWriter_classInit },
+ {"nativeInit", "(Ljava/lang/Object;Landroid/view/Surface;I)J",
+ (void*)ImageWriter_init },
+ {"nativeClose", "(J)V", (void*)ImageWriter_close },
+ {"nativeAttachImage", "(JLandroid/media/Image;)V", (void*)ImageWriter_attachImage },
+ {"nativeDequeueInputImage", "(JLandroid/media/Image;)V", (void*)ImageWriter_dequeueImage },
+ {"nativeQueueInputImage", "(JLandroid/media/Image;JIIII)V", (void*)ImageWriter_queueImage },
+ {"cancelImage", "(JLandroid/media/Image;)V", (void*)ImageWriter_cancelImage },
+};
+
+static JNINativeMethod gImageMethods[] = {
+ {"nativeCreatePlanes", "(II)[Landroid/media/ImageWriter$WriterSurfaceImage$SurfacePlane;",
+ (void*)Image_createSurfacePlanes },
+ {"nativeGetWidth", "()I", (void*)Image_getWidth },
+ {"nativeGetHeight", "()I", (void*)Image_getHeight },
+ {"nativeGetFormat", "()I", (void*)Image_getFormat },
+};
+
+int register_android_media_ImageWriter(JNIEnv *env) {
+
+ int ret1 = AndroidRuntime::registerNativeMethods(env,
+ "android/media/ImageWriter", gImageWriterMethods, NELEM(gImageWriterMethods));
+
+ int ret2 = AndroidRuntime::registerNativeMethods(env,
+ "android/media/ImageWriter$WriterSurfaceImage", gImageMethods, NELEM(gImageMethods));
+
+ return (ret1 || ret2);
+}
+
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 1cf589d..16758d0 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -215,7 +215,7 @@ void JMediaCodec::deleteJavaObjects(JNIEnv *env) {
status_t JMediaCodec::setCallback(jobject cb) {
if (cb != NULL) {
if (mCallbackNotification == NULL) {
- mCallbackNotification = new AMessage(kWhatCallbackNotify, id());
+ mCallbackNotification = new AMessage(kWhatCallbackNotify, this);
}
} else {
mCallbackNotification.clear();
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index d9de7a9..96d7133 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -59,6 +59,7 @@ namespace android {
struct RequestFields {
jfieldID data;
jfieldID defaultUrl;
+ jfieldID requestType;
};
struct ArrayListFields {
@@ -92,6 +93,7 @@ struct EventTypes {
jint kEventKeyRequired;
jint kEventKeyExpired;
jint kEventVendorDefined;
+ jint kEventSessionReclaimed;
} gEventTypes;
struct KeyTypes {
@@ -100,6 +102,12 @@ struct KeyTypes {
jint kKeyTypeRelease;
} gKeyTypes;
+struct KeyRequestTypes {
+ jint kKeyRequestTypeInitial;
+ jint kKeyRequestTypeRenewal;
+ jint kKeyRequestTypeRelease;
+} gKeyRequestTypes;
+
struct CertificateTypes {
jint kCertificateTypeNone;
jint kCertificateTypeX509;
@@ -181,7 +189,7 @@ void JNIDrmListener::notify(DrmPlugin::EventType eventType, int extra,
jint jeventType;
// translate DrmPlugin event types into their java equivalents
- switch(eventType) {
+ switch (eventType) {
case DrmPlugin::kDrmPluginEventProvisionRequired:
jeventType = gEventTypes.kEventProvisionRequired;
break;
@@ -194,6 +202,9 @@ void JNIDrmListener::notify(DrmPlugin::EventType eventType, int extra,
case DrmPlugin::kDrmPluginEventVendorDefined:
jeventType = gEventTypes.kEventVendorDefined;
break;
+ case DrmPlugin::kDrmPluginEventSessionReclaimed:
+ jeventType = gEventTypes.kEventSessionReclaimed;
+ break;
default:
ALOGE("Invalid event DrmPlugin::EventType %d, ignored", (int)eventType);
return;
@@ -232,7 +243,7 @@ static bool throwExceptionAsNecessary(
const char *drmMessage = NULL;
- switch(err) {
+ switch (err) {
case ERROR_DRM_UNKNOWN:
drmMessage = "General DRM error";
break;
@@ -438,9 +449,11 @@ static String8 JStringToString8(JNIEnv *env, jstring const &jstr) {
Entry e = s.next();
*/
-static KeyedVector<String8, String8> HashMapToKeyedVector(JNIEnv *env, jobject &hashMap) {
+static KeyedVector<String8, String8> HashMapToKeyedVector(
+ JNIEnv *env, jobject &hashMap, bool* pIsOK) {
jclass clazz = gFields.stringClassId;
KeyedVector<String8, String8> keyedVector;
+ *pIsOK = true;
jobject entrySet = env->CallObjectMethod(hashMap, gFields.hashmap.entrySet);
if (entrySet) {
@@ -451,16 +464,22 @@ static KeyedVector<String8, String8> HashMapToKeyedVector(JNIEnv *env, jobject &
jobject entry = env->CallObjectMethod(iterator, gFields.iterator.next);
if (entry) {
jobject obj = env->CallObjectMethod(entry, gFields.entry.getKey);
- if (!env->IsInstanceOf(obj, clazz)) {
+ if (obj == NULL || !env->IsInstanceOf(obj, clazz)) {
jniThrowException(env, "java/lang/IllegalArgumentException",
"HashMap key is not a String");
+ env->DeleteLocalRef(entry);
+ *pIsOK = false;
+ break;
}
jstring jkey = static_cast<jstring>(obj);
obj = env->CallObjectMethod(entry, gFields.entry.getValue);
- if (!env->IsInstanceOf(obj, clazz)) {
+ if (obj == NULL || !env->IsInstanceOf(obj, clazz)) {
jniThrowException(env, "java/lang/IllegalArgumentException",
"HashMap value is not a String");
+ env->DeleteLocalRef(entry);
+ *pIsOK = false;
+ break;
}
jstring jvalue = static_cast<jstring>(obj);
@@ -565,6 +584,8 @@ static void android_media_MediaDrm_native_init(JNIEnv *env) {
gEventTypes.kEventKeyExpired = env->GetStaticIntField(clazz, field);
GET_STATIC_FIELD_ID(field, clazz, "EVENT_VENDOR_DEFINED", "I");
gEventTypes.kEventVendorDefined = env->GetStaticIntField(clazz, field);
+ GET_STATIC_FIELD_ID(field, clazz, "EVENT_SESSION_RECLAIMED", "I");
+ gEventTypes.kEventSessionReclaimed = env->GetStaticIntField(clazz, field);
GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_STREAMING", "I");
gKeyTypes.kKeyTypeStreaming = env->GetStaticIntField(clazz, field);
@@ -573,6 +594,13 @@ static void android_media_MediaDrm_native_init(JNIEnv *env) {
GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_RELEASE", "I");
gKeyTypes.kKeyTypeRelease = env->GetStaticIntField(clazz, field);
+ GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_INITIAL", "I");
+ gKeyRequestTypes.kKeyRequestTypeInitial = env->GetStaticIntField(clazz, field);
+ GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_RENEWAL", "I");
+ gKeyRequestTypes.kKeyRequestTypeRenewal = env->GetStaticIntField(clazz, field);
+ GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_RELEASE", "I");
+ gKeyRequestTypes.kKeyRequestTypeRelease = env->GetStaticIntField(clazz, field);
+
GET_STATIC_FIELD_ID(field, clazz, "CERTIFICATE_TYPE_NONE", "I");
gCertificateTypes.kCertificateTypeNone = env->GetStaticIntField(clazz, field);
GET_STATIC_FIELD_ID(field, clazz, "CERTIFICATE_TYPE_X509", "I");
@@ -581,6 +609,7 @@ static void android_media_MediaDrm_native_init(JNIEnv *env) {
FIND_CLASS(clazz, "android/media/MediaDrm$KeyRequest");
GET_FIELD_ID(gFields.keyRequest.data, clazz, "mData", "[B");
GET_FIELD_ID(gFields.keyRequest.defaultUrl, clazz, "mDefaultUrl", "Ljava/lang/String;");
+ GET_FIELD_ID(gFields.keyRequest.requestType, clazz, "mRequestType", "I");
FIND_CLASS(clazz, "android/media/MediaDrm$ProvisionRequest");
GET_FIELD_ID(gFields.provisionRequest.data, clazz, "mData", "[B");
@@ -763,14 +792,19 @@ static jobject android_media_MediaDrm_getKeyRequest(
KeyedVector<String8, String8> optParams;
if (joptParams != NULL) {
- optParams = HashMapToKeyedVector(env, joptParams);
+ bool isOK;
+ optParams = HashMapToKeyedVector(env, joptParams, &isOK);
+ if (!isOK) {
+ return NULL;
+ }
}
Vector<uint8_t> request;
String8 defaultUrl;
+ DrmPlugin::KeyRequestType keyRequestType;
status_t err = drm->getKeyRequest(sessionId, initData, mimeType,
- keyType, optParams, request, defaultUrl);
+ keyType, optParams, request, defaultUrl, &keyRequestType);
if (throwExceptionAsNecessary(env, err, "Failed to get key request")) {
return NULL;
@@ -789,6 +823,25 @@ static jobject android_media_MediaDrm_getKeyRequest(
jstring jdefaultUrl = env->NewStringUTF(defaultUrl.string());
env->SetObjectField(keyObj, gFields.keyRequest.defaultUrl, jdefaultUrl);
+
+ switch (keyRequestType) {
+ case DrmPlugin::kKeyRequestType_Initial:
+ env->SetIntField(keyObj, gFields.keyRequest.requestType,
+ gKeyRequestTypes.kKeyRequestTypeInitial);
+ break;
+ case DrmPlugin::kKeyRequestType_Renewal:
+ env->SetIntField(keyObj, gFields.keyRequest.requestType,
+ gKeyRequestTypes.kKeyRequestTypeRenewal);
+ break;
+ case DrmPlugin::kKeyRequestType_Release:
+ env->SetIntField(keyObj, gFields.keyRequest.requestType,
+ gKeyRequestTypes.kKeyRequestTypeRelease);
+ break;
+ case DrmPlugin::kKeyRequestType_Unknown:
+ throwStateException(env, "DRM plugin failure: unknown key request type",
+ ERROR_DRM_UNKNOWN);
+ break;
+ }
}
return keyObj;
diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp
index 93138fa..2f6bbf4 100644
--- a/media/jni/android_media_MediaMetadataRetriever.cpp
+++ b/media/jni/android_media_MediaMetadataRetriever.cpp
@@ -40,7 +40,6 @@ using namespace android;
struct fields_t {
jfieldID context;
jclass bitmapClazz; // Must be a global ref
- jfieldID nativeBitmap;
jmethodID createBitmapMethod;
jmethodID createScaledBitmapMethod;
jclass configClazz; // Must be a global ref
@@ -282,8 +281,7 @@ static jobject android_media_MediaMetadataRetriever_getFrameAtTime(JNIEnv *env,
return NULL;
}
- SkBitmap *bitmap =
- (SkBitmap *) env->GetLongField(jBitmap, fields.nativeBitmap);
+ SkBitmap *bitmap = GraphicsJNI::getSkBitmap(env, jBitmap);
bitmap->lockPixels();
rotate((uint16_t*)bitmap->getPixels(),
@@ -421,10 +419,6 @@ static void android_media_MediaMetadataRetriever_native_init(JNIEnv *env)
if (fields.createScaledBitmapMethod == NULL) {
return;
}
- fields.nativeBitmap = env->GetFieldID(fields.bitmapClazz, "mNativeBitmap", "J");
- if (fields.nativeBitmap == NULL) {
- return;
- }
jclass configClazz = env->FindClass("android/graphics/Bitmap$Config");
if (configClazz == NULL) {
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 820de5b..b748f3a 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -402,6 +402,18 @@ android_media_MediaPlayer_isPlaying(JNIEnv *env, jobject thiz)
}
static void
+android_media_MediaPlayer_setPlaybackRate(JNIEnv *env, jobject thiz, jfloat rate)
+{
+ sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+ ALOGV("setPlaybackRate: %f", rate);
+ process_media_player_call(env, thiz, mp->setPlaybackRate(rate), NULL, NULL);
+}
+
+static void
android_media_MediaPlayer_seekTo(JNIEnv *env, jobject thiz, jint msec)
{
sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
@@ -867,6 +879,7 @@ static JNINativeMethod gMethods[] = {
{"_stop", "()V", (void *)android_media_MediaPlayer_stop},
{"getVideoWidth", "()I", (void *)android_media_MediaPlayer_getVideoWidth},
{"getVideoHeight", "()I", (void *)android_media_MediaPlayer_getVideoHeight},
+ {"_setPlaybackRate", "(F)V", (void *)android_media_MediaPlayer_setPlaybackRate},
{"seekTo", "(I)V", (void *)android_media_MediaPlayer_seekTo},
{"_pause", "()V", (void *)android_media_MediaPlayer_pause},
{"isPlaying", "()Z", (void *)android_media_MediaPlayer_isPlaying},
@@ -901,8 +914,8 @@ static int register_android_media_MediaPlayer(JNIEnv *env)
return AndroidRuntime::registerNativeMethods(env,
"android/media/MediaPlayer", gMethods, NELEM(gMethods));
}
-
extern int register_android_media_ImageReader(JNIEnv *env);
+extern int register_android_media_ImageWriter(JNIEnv *env);
extern int register_android_media_Crypto(JNIEnv *env);
extern int register_android_media_Drm(JNIEnv *env);
extern int register_android_media_MediaCodec(JNIEnv *env);
@@ -931,6 +944,11 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
}
assert(env != NULL);
+ if (register_android_media_ImageWriter(env) != JNI_OK) {
+ ALOGE("ERROR: ImageWriter native registration failed");
+ goto bail;
+ }
+
if (register_android_media_ImageReader(env) < 0) {
ALOGE("ERROR: ImageReader native registration failed");
goto bail;
diff --git a/media/jni/soundpool/Android.mk b/media/jni/soundpool/Android.mk
index 71ab013..2476056 100644
--- a/media/jni/soundpool/Android.mk
+++ b/media/jni/soundpool/Android.mk
@@ -2,7 +2,7 @@ LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
- android_media_SoundPool_SoundPoolImpl.cpp \
+ android_media_SoundPool.cpp \
SoundPool.cpp \
SoundPoolThread.cpp
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
index a73209b..10233f3 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -256,7 +256,7 @@ int SoundPool::play(int sampleID, float leftVolume, float rightVolume,
dump();
// allocate a channel
- channel = allocateChannel_l(priority);
+ channel = allocateChannel_l(priority, sampleID);
// no channel allocated - return 0
if (!channel) {
@@ -271,13 +271,25 @@ int SoundPool::play(int sampleID, float leftVolume, float rightVolume,
return channelID;
}
-SoundChannel* SoundPool::allocateChannel_l(int priority)
+SoundChannel* SoundPool::allocateChannel_l(int priority, int sampleID)
{
List<SoundChannel*>::iterator iter;
SoundChannel* channel = NULL;
- // allocate a channel
+ // check if channel for given sampleID still available
if (!mChannels.empty()) {
+ for (iter = mChannels.begin(); iter != mChannels.end(); ++iter) {
+ if (sampleID == (*iter)->getPrevSampleID() && (*iter)->state() == SoundChannel::IDLE) {
+ channel = *iter;
+ mChannels.erase(iter);
+ ALOGV("Allocated recycled channel for same sampleID");
+ break;
+ }
+ }
+ }
+
+ // allocate any channel
+ if (!channel && !mChannels.empty()) {
iter = mChannels.begin();
if (priority >= (*iter)->priority()) {
channel = *iter;
@@ -626,7 +638,7 @@ status_t Sample::doLoad()
goto error;
}
- if ((numChannels < 1) || (numChannels > 2)) {
+ if ((numChannels < 1) || (numChannels > 8)) {
ALOGE("Sample channel count (%d) out of range", numChannels);
status = BAD_VALUE;
goto error;
@@ -648,6 +660,7 @@ error:
void SoundChannel::init(SoundPool* soundPool)
{
mSoundPool = soundPool;
+ mPrevSampleID = -1;
}
// call with sound pool lock held
@@ -656,7 +669,7 @@ void SoundChannel::play(const sp<Sample>& sample, int nextChannelID, float leftV
{
sp<AudioTrack> oldTrack;
sp<AudioTrack> newTrack;
- status_t status;
+ status_t status = NO_ERROR;
{ // scope for the lock
Mutex::Autolock lock(&mLock);
@@ -689,8 +702,10 @@ void SoundChannel::play(const sp<Sample>& sample, int nextChannelID, float leftV
size_t frameCount = 0;
if (loop) {
- frameCount = sample->size()/numChannels/
- ((sample->format() == AUDIO_FORMAT_PCM_16_BIT) ? sizeof(int16_t) : sizeof(uint8_t));
+ const audio_format_t format = sample->format();
+ const size_t frameSize = audio_is_linear_pcm(format)
+ ? numChannels * audio_bytes_per_sample(format) : 1;
+ frameCount = sample->size() / frameSize;
}
#ifndef USE_SHARED_MEM_BUFFER
@@ -701,38 +716,43 @@ void SoundChannel::play(const sp<Sample>& sample, int nextChannelID, float leftV
}
#endif
- // mToggle toggles each time a track is started on a given channel.
- // The toggle is concatenated with the SoundChannel address and passed to AudioTrack
- // as callback user data. This enables the detection of callbacks received from the old
- // audio track while the new one is being started and avoids processing them with
- // wrong audio audio buffer size (mAudioBufferSize)
- unsigned long toggle = mToggle ^ 1;
- void *userData = (void *)((unsigned long)this | toggle);
- audio_channel_mask_t channelMask = audio_channel_out_mask_from_count(numChannels);
-
- // do not create a new audio track if current track is compatible with sample parameters
-#ifdef USE_SHARED_MEM_BUFFER
- newTrack = new AudioTrack(streamType, sampleRate, sample->format(),
- channelMask, sample->getIMemory(), AUDIO_OUTPUT_FLAG_FAST, callback, userData);
-#else
- uint32_t bufferFrames = (totalFrames + (kDefaultBufferCount - 1)) / kDefaultBufferCount;
- newTrack = new AudioTrack(streamType, sampleRate, sample->format(),
- channelMask, frameCount, AUDIO_OUTPUT_FLAG_FAST, callback, userData,
- bufferFrames);
-#endif
- oldTrack = mAudioTrack;
- status = newTrack->initCheck();
- if (status != NO_ERROR) {
- ALOGE("Error creating AudioTrack");
- goto exit;
+ if (!mAudioTrack.get() || mPrevSampleID != sample->sampleID()) {
+ // mToggle toggles each time a track is started on a given channel.
+ // The toggle is concatenated with the SoundChannel address and passed to AudioTrack
+ // as callback user data. This enables the detection of callbacks received from the old
+ // audio track while the new one is being started and avoids processing them with
+ // wrong audio audio buffer size (mAudioBufferSize)
+ unsigned long toggle = mToggle ^ 1;
+ void *userData = (void *)((unsigned long)this | toggle);
+ audio_channel_mask_t channelMask = audio_channel_out_mask_from_count(numChannels);
+
+ // do not create a new audio track if current track is compatible with sample parameters
+ #ifdef USE_SHARED_MEM_BUFFER
+ newTrack = new AudioTrack(streamType, sampleRate, sample->format(),
+ channelMask, sample->getIMemory(), AUDIO_OUTPUT_FLAG_FAST, callback, userData);
+ #else
+ uint32_t bufferFrames = (totalFrames + (kDefaultBufferCount - 1)) / kDefaultBufferCount;
+ newTrack = new AudioTrack(streamType, sampleRate, sample->format(),
+ channelMask, frameCount, AUDIO_OUTPUT_FLAG_FAST, callback, userData,
+ bufferFrames);
+ #endif
+ oldTrack = mAudioTrack;
+ status = newTrack->initCheck();
+ if (status != NO_ERROR) {
+ ALOGE("Error creating AudioTrack");
+ goto exit;
+ }
+ // From now on, AudioTrack callbacks received with previous toggle value will be ignored.
+ mToggle = toggle;
+ mAudioTrack = newTrack;
+ ALOGV("using new track %p for sample %d", newTrack.get(), sample->sampleID());
+ } else {
+ newTrack = mAudioTrack;
+ newTrack->setSampleRate(sampleRate);
+ ALOGV("reusing track %p for sample %d", mAudioTrack.get(), sample->sampleID());
}
- ALOGV("setVolume %p", newTrack.get());
newTrack->setVolume(leftVolume, rightVolume);
newTrack->setLoop(0, frameCount, loop);
-
- // From now on, AudioTrack callbacks received with previous toggle value will be ignored.
- mToggle = toggle;
- mAudioTrack = newTrack;
mPos = 0;
mSample = sample;
mChannelID = nextChannelID;
@@ -875,6 +895,7 @@ bool SoundChannel::doStop_l()
setVolume_l(0, 0);
ALOGV("stop");
mAudioTrack->stop();
+ mPrevSampleID = mSample->sampleID();
mSample.clear();
mState = IDLE;
mPriority = IDLE_PRIORITY;
diff --git a/media/jni/soundpool/SoundPool.h b/media/jni/soundpool/SoundPool.h
index 9d9cbdf..4aacf53 100644
--- a/media/jni/soundpool/SoundPool.h
+++ b/media/jni/soundpool/SoundPool.h
@@ -72,8 +72,8 @@ private:
volatile int32_t mRefCount;
uint16_t mSampleID;
uint16_t mSampleRate;
- uint8_t mState : 3;
- uint8_t mNumChannels : 2;
+ uint8_t mState;
+ uint8_t mNumChannels;
audio_format_t mFormat;
int mFd;
int64_t mOffset;
@@ -136,6 +136,7 @@ public:
void nextEvent();
int nextChannelID() { return mNextEvent.channelID(); }
void dump();
+ int getPrevSampleID(void) { return mPrevSampleID; }
private:
static void callback(int event, void* user, void *info);
@@ -152,6 +153,7 @@ private:
int mAudioBufferSize;
unsigned long mToggle;
bool mAutoPaused;
+ int mPrevSampleID;
};
// application object for managing a pool of sounds
@@ -193,7 +195,7 @@ private:
sp<Sample> findSample(int sampleID) { return mSamples.valueFor(sampleID); }
SoundChannel* findChannel (int channelID);
SoundChannel* findNextChannel (int channelID);
- SoundChannel* allocateChannel_l(int priority);
+ SoundChannel* allocateChannel_l(int priority, int sampleID);
void moveToFront_l(SoundChannel* channel);
void notify(SoundPoolEvent event);
void dump();
diff --git a/media/jni/soundpool/android_media_SoundPool_SoundPoolImpl.cpp b/media/jni/soundpool/android_media_SoundPool.cpp
index b2333f8..fc4cf05 100644
--- a/media/jni/soundpool/android_media_SoundPool_SoundPoolImpl.cpp
+++ b/media/jni/soundpool/android_media_SoundPool.cpp
@@ -47,10 +47,10 @@ static audio_attributes_fields_t javaAudioAttrFields;
// ----------------------------------------------------------------------------
static jint
-android_media_SoundPool_SoundPoolImpl_load_FD(JNIEnv *env, jobject thiz, jobject fileDescriptor,
+android_media_SoundPool_load_FD(JNIEnv *env, jobject thiz, jobject fileDescriptor,
jlong offset, jlong length, jint priority)
{
- ALOGV("android_media_SoundPool_SoundPoolImpl_load_FD");
+ ALOGV("android_media_SoundPool_load_FD");
SoundPool *ap = MusterSoundPool(env, thiz);
if (ap == NULL) return 0;
return (jint) ap->load(jniGetFDFromFileDescriptor(env, fileDescriptor),
@@ -58,104 +58,104 @@ android_media_SoundPool_SoundPoolImpl_load_FD(JNIEnv *env, jobject thiz, jobject
}
static jboolean
-android_media_SoundPool_SoundPoolImpl_unload(JNIEnv *env, jobject thiz, jint sampleID) {
- ALOGV("android_media_SoundPool_SoundPoolImpl_unload\n");
+android_media_SoundPool_unload(JNIEnv *env, jobject thiz, jint sampleID) {
+ ALOGV("android_media_SoundPool_unload\n");
SoundPool *ap = MusterSoundPool(env, thiz);
if (ap == NULL) return JNI_FALSE;
return ap->unload(sampleID) ? JNI_TRUE : JNI_FALSE;
}
static jint
-android_media_SoundPool_SoundPoolImpl_play(JNIEnv *env, jobject thiz, jint sampleID,
+android_media_SoundPool_play(JNIEnv *env, jobject thiz, jint sampleID,
jfloat leftVolume, jfloat rightVolume, jint priority, jint loop,
jfloat rate)
{
- ALOGV("android_media_SoundPool_SoundPoolImpl_play\n");
+ ALOGV("android_media_SoundPool_play\n");
SoundPool *ap = MusterSoundPool(env, thiz);
if (ap == NULL) return 0;
return (jint) ap->play(sampleID, leftVolume, rightVolume, priority, loop, rate);
}
static void
-android_media_SoundPool_SoundPoolImpl_pause(JNIEnv *env, jobject thiz, jint channelID)
+android_media_SoundPool_pause(JNIEnv *env, jobject thiz, jint channelID)
{
- ALOGV("android_media_SoundPool_SoundPoolImpl_pause");
+ ALOGV("android_media_SoundPool_pause");
SoundPool *ap = MusterSoundPool(env, thiz);
if (ap == NULL) return;
ap->pause(channelID);
}
static void
-android_media_SoundPool_SoundPoolImpl_resume(JNIEnv *env, jobject thiz, jint channelID)
+android_media_SoundPool_resume(JNIEnv *env, jobject thiz, jint channelID)
{
- ALOGV("android_media_SoundPool_SoundPoolImpl_resume");
+ ALOGV("android_media_SoundPool_resume");
SoundPool *ap = MusterSoundPool(env, thiz);
if (ap == NULL) return;
ap->resume(channelID);
}
static void
-android_media_SoundPool_SoundPoolImpl_autoPause(JNIEnv *env, jobject thiz)
+android_media_SoundPool_autoPause(JNIEnv *env, jobject thiz)
{
- ALOGV("android_media_SoundPool_SoundPoolImpl_autoPause");
+ ALOGV("android_media_SoundPool_autoPause");
SoundPool *ap = MusterSoundPool(env, thiz);
if (ap == NULL) return;
ap->autoPause();
}
static void
-android_media_SoundPool_SoundPoolImpl_autoResume(JNIEnv *env, jobject thiz)
+android_media_SoundPool_autoResume(JNIEnv *env, jobject thiz)
{
- ALOGV("android_media_SoundPool_SoundPoolImpl_autoResume");
+ ALOGV("android_media_SoundPool_autoResume");
SoundPool *ap = MusterSoundPool(env, thiz);
if (ap == NULL) return;
ap->autoResume();
}
static void
-android_media_SoundPool_SoundPoolImpl_stop(JNIEnv *env, jobject thiz, jint channelID)
+android_media_SoundPool_stop(JNIEnv *env, jobject thiz, jint channelID)
{
- ALOGV("android_media_SoundPool_SoundPoolImpl_stop");
+ ALOGV("android_media_SoundPool_stop");
SoundPool *ap = MusterSoundPool(env, thiz);
if (ap == NULL) return;
ap->stop(channelID);
}
static void
-android_media_SoundPool_SoundPoolImpl_setVolume(JNIEnv *env, jobject thiz, jint channelID,
+android_media_SoundPool_setVolume(JNIEnv *env, jobject thiz, jint channelID,
jfloat leftVolume, jfloat rightVolume)
{
- ALOGV("android_media_SoundPool_SoundPoolImpl_setVolume");
+ ALOGV("android_media_SoundPool_setVolume");
SoundPool *ap = MusterSoundPool(env, thiz);
if (ap == NULL) return;
ap->setVolume(channelID, (float) leftVolume, (float) rightVolume);
}
static void
-android_media_SoundPool_SoundPoolImpl_setPriority(JNIEnv *env, jobject thiz, jint channelID,
+android_media_SoundPool_setPriority(JNIEnv *env, jobject thiz, jint channelID,
jint priority)
{
- ALOGV("android_media_SoundPool_SoundPoolImpl_setPriority");
+ ALOGV("android_media_SoundPool_setPriority");
SoundPool *ap = MusterSoundPool(env, thiz);
if (ap == NULL) return;
ap->setPriority(channelID, (int) priority);
}
static void
-android_media_SoundPool_SoundPoolImpl_setLoop(JNIEnv *env, jobject thiz, jint channelID,
+android_media_SoundPool_setLoop(JNIEnv *env, jobject thiz, jint channelID,
int loop)
{
- ALOGV("android_media_SoundPool_SoundPoolImpl_setLoop");
+ ALOGV("android_media_SoundPool_setLoop");
SoundPool *ap = MusterSoundPool(env, thiz);
if (ap == NULL) return;
ap->setLoop(channelID, loop);
}
static void
-android_media_SoundPool_SoundPoolImpl_setRate(JNIEnv *env, jobject thiz, jint channelID,
+android_media_SoundPool_setRate(JNIEnv *env, jobject thiz, jint channelID,
jfloat rate)
{
- ALOGV("android_media_SoundPool_SoundPoolImpl_setRate");
+ ALOGV("android_media_SoundPool_setRate");
SoundPool *ap = MusterSoundPool(env, thiz);
if (ap == NULL) return;
ap->setRate(channelID, (float) rate);
@@ -169,7 +169,7 @@ static void android_media_callback(SoundPoolEvent event, SoundPool* soundPool, v
}
static jint
-android_media_SoundPool_SoundPoolImpl_native_setup(JNIEnv *env, jobject thiz, jobject weakRef,
+android_media_SoundPool_native_setup(JNIEnv *env, jobject thiz, jobject weakRef,
jint maxChannels, jobject jaa)
{
if (jaa == 0) {
@@ -191,7 +191,7 @@ android_media_SoundPool_SoundPoolImpl_native_setup(JNIEnv *env, jobject thiz, jo
(audio_content_type_t) env->GetIntField(jaa, javaAudioAttrFields.fieldContentType);
paa->flags = env->GetIntField(jaa, javaAudioAttrFields.fieldFlags);
- ALOGV("android_media_SoundPool_SoundPoolImpl_native_setup");
+ ALOGV("android_media_SoundPool_native_setup");
SoundPool *ap = new SoundPool(maxChannels, paa);
if (ap == NULL) {
return -1;
@@ -211,9 +211,9 @@ android_media_SoundPool_SoundPoolImpl_native_setup(JNIEnv *env, jobject thiz, jo
}
static void
-android_media_SoundPool_SoundPoolImpl_release(JNIEnv *env, jobject thiz)
+android_media_SoundPool_release(JNIEnv *env, jobject thiz)
{
- ALOGV("android_media_SoundPool_SoundPoolImpl_release");
+ ALOGV("android_media_SoundPool_release");
SoundPool *ap = MusterSoundPool(env, thiz);
if (ap != NULL) {
@@ -236,63 +236,63 @@ android_media_SoundPool_SoundPoolImpl_release(JNIEnv *env, jobject thiz)
static JNINativeMethod gMethods[] = {
{ "_load",
"(Ljava/io/FileDescriptor;JJI)I",
- (void *)android_media_SoundPool_SoundPoolImpl_load_FD
+ (void *)android_media_SoundPool_load_FD
},
{ "unload",
"(I)Z",
- (void *)android_media_SoundPool_SoundPoolImpl_unload
+ (void *)android_media_SoundPool_unload
},
{ "_play",
"(IFFIIF)I",
- (void *)android_media_SoundPool_SoundPoolImpl_play
+ (void *)android_media_SoundPool_play
},
{ "pause",
"(I)V",
- (void *)android_media_SoundPool_SoundPoolImpl_pause
+ (void *)android_media_SoundPool_pause
},
{ "resume",
"(I)V",
- (void *)android_media_SoundPool_SoundPoolImpl_resume
+ (void *)android_media_SoundPool_resume
},
{ "autoPause",
"()V",
- (void *)android_media_SoundPool_SoundPoolImpl_autoPause
+ (void *)android_media_SoundPool_autoPause
},
{ "autoResume",
"()V",
- (void *)android_media_SoundPool_SoundPoolImpl_autoResume
+ (void *)android_media_SoundPool_autoResume
},
{ "stop",
"(I)V",
- (void *)android_media_SoundPool_SoundPoolImpl_stop
+ (void *)android_media_SoundPool_stop
},
{ "_setVolume",
"(IFF)V",
- (void *)android_media_SoundPool_SoundPoolImpl_setVolume
+ (void *)android_media_SoundPool_setVolume
},
{ "setPriority",
"(II)V",
- (void *)android_media_SoundPool_SoundPoolImpl_setPriority
+ (void *)android_media_SoundPool_setPriority
},
{ "setLoop",
"(II)V",
- (void *)android_media_SoundPool_SoundPoolImpl_setLoop
+ (void *)android_media_SoundPool_setLoop
},
{ "setRate",
"(IF)V",
- (void *)android_media_SoundPool_SoundPoolImpl_setRate
+ (void *)android_media_SoundPool_setRate
},
{ "native_setup",
"(Ljava/lang/Object;ILjava/lang/Object;)I",
- (void*)android_media_SoundPool_SoundPoolImpl_native_setup
+ (void*)android_media_SoundPool_native_setup
},
{ "release",
"()V",
- (void*)android_media_SoundPool_SoundPoolImpl_release
+ (void*)android_media_SoundPool_release
}
};
-static const char* const kClassPathName = "android/media/SoundPool$SoundPoolImpl";
+static const char* const kClassPathName = "android/media/SoundPool";
jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
@@ -314,14 +314,14 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
fields.mNativeContext = env->GetFieldID(clazz, "mNativeContext", "J");
if (fields.mNativeContext == NULL) {
- ALOGE("Can't find SoundPoolImpl.mNativeContext");
+ ALOGE("Can't find SoundPool.mNativeContext");
return result;
}
fields.mPostEvent = env->GetStaticMethodID(clazz, "postEventFromNative",
"(Ljava/lang/Object;IIILjava/lang/Object;)V");
if (fields.mPostEvent == NULL) {
- ALOGE("Can't find android/media/SoundPoolImpl.postEventFromNative");
+ ALOGE("Can't find android/media/SoundPool.postEventFromNative");
return result;
}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index cc50c43..3bb5f01 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -20,8 +20,6 @@ import android.hardware.CameraInfo;
import android.hardware.ICamera;
import android.hardware.ICameraClient;
import android.hardware.ICameraServiceListener;
-import android.hardware.IProCameraCallbacks;
-import android.hardware.IProCameraUser;
import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.ICameraDeviceUser;
import android.hardware.camera2.impl.CameraMetadataNative;
@@ -181,30 +179,6 @@ public class CameraBinderTest extends AndroidTestCase {
}
}
- static class DummyProCameraCallbacks extends DummyBase implements IProCameraCallbacks {
- }
-
- @SmallTest
- public void testConnectPro() throws Exception {
- for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) {
-
- IProCameraCallbacks dummyCallbacks = new DummyProCameraCallbacks();
-
- String clientPackageName = getContext().getPackageName();
-
- BinderHolder holder = new BinderHolder();
- CameraBinderDecorator.newInstance(mUtils.getCameraService())
- .connectPro(dummyCallbacks, cameraId,
- clientPackageName, CameraBinderTestUtils.USE_CALLING_UID, holder);
- IProCameraUser cameraUser = IProCameraUser.Stub.asInterface(holder.getBinder());
- assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);
-
- Log.v(TAG, String.format("Camera %s connected", cameraId));
-
- cameraUser.disconnect();
- }
- }
-
@SmallTest
public void testConnectLegacy() throws Exception {
final int CAMERA_HAL_API_VERSION_1_0 = 0x100;
@@ -316,6 +290,11 @@ public class CameraBinderTest extends AndroidTestCase {
throws RemoteException {
Log.v(TAG, String.format("Camera %d has status changed to 0x%x", cameraId, status));
}
+ public void onTorchStatusChanged(int status, String cameraId)
+ throws RemoteException {
+ Log.v(TAG, String.format("Camera %s has torch status changed to 0x%x",
+ cameraId, status));
+ }
}
/**
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
index 3cae19d..e05e1fc 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
@@ -25,6 +25,7 @@ import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.ICameraDeviceUser;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.CaptureResultExtras;
+import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.utils.BinderHolder;
import android.media.Image;
import android.media.ImageReader;
@@ -67,6 +68,7 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
private CameraBinderTestUtils mUtils;
private ICameraDeviceCallbacks.Stub mMockCb;
private Surface mSurface;
+ private OutputConfiguration mOutputConfiguration;
private HandlerThread mHandlerThread;
private Handler mHandler;
ImageReader mImageReader;
@@ -147,6 +149,7 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
MAX_NUM_IMAGES);
mImageReader.setOnImageAvailableListener(new ImageDropperListener(), mHandler);
mSurface = mImageReader.getSurface();
+ mOutputConfiguration = new OutputConfiguration(mSurface);
}
private CaptureRequest.Builder createDefaultBuilder(boolean needStream) throws Exception {
@@ -161,8 +164,7 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
assertFalse(request.isEmpty());
assertFalse(metadata.isEmpty());
if (needStream) {
- int streamId = mCameraUser.createStream(/* ignored */10, /* ignored */20,
- /* ignored */30, mSurface);
+ int streamId = mCameraUser.createStream(mOutputConfiguration);
assertEquals(0, streamId);
request.addTarget(mSurface);
}
@@ -235,12 +237,11 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
@SmallTest
public void testCreateStream() throws Exception {
- int streamId = mCameraUser.createStream(/* ignored */10, /* ignored */20, /* ignored */30,
- mSurface);
+ int streamId = mCameraUser.createStream(mOutputConfiguration);
assertEquals(0, streamId);
assertEquals(CameraBinderTestUtils.ALREADY_EXISTS,
- mCameraUser.createStream(/* ignored */0, /* ignored */0, /* ignored */0, mSurface));
+ mCameraUser.createStream(mOutputConfiguration));
assertEquals(CameraBinderTestUtils.NO_ERROR, mCameraUser.deleteStream(streamId));
}
@@ -257,20 +258,19 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
public void testCreateStreamTwo() throws Exception {
// Create first stream
- int streamId = mCameraUser.createStream(/* ignored */0, /* ignored */0, /* ignored */0,
- mSurface);
+ int streamId = mCameraUser.createStream(mOutputConfiguration);
assertEquals(0, streamId);
assertEquals(CameraBinderTestUtils.ALREADY_EXISTS,
- mCameraUser.createStream(/* ignored */0, /* ignored */0, /* ignored */0, mSurface));
+ mCameraUser.createStream(mOutputConfiguration));
// Create second stream with a different surface.
SurfaceTexture surfaceTexture = new SurfaceTexture(/* ignored */0);
surfaceTexture.setDefaultBufferSize(640, 480);
Surface surface2 = new Surface(surfaceTexture);
+ OutputConfiguration output2 = new OutputConfiguration(surface2);
- int streamId2 = mCameraUser.createStream(/* ignored */0, /* ignored */0, /* ignored */0,
- surface2);
+ int streamId2 = mCameraUser.createStream(output2);
assertEquals(1, streamId2);
// Clean up streams