summaryrefslogtreecommitdiffstats
path: root/media/java/android
diff options
context:
space:
mode:
Diffstat (limited to 'media/java/android')
-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
64 files changed, 3116 insertions, 1179 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;