summaryrefslogtreecommitdiffstats
path: root/media/java
diff options
context:
space:
mode:
Diffstat (limited to 'media/java')
-rw-r--r--media/java/android/media/AudioDeviceInfo.java3
-rw-r--r--media/java/android/media/AudioDevicePort.java4
-rw-r--r--media/java/android/media/AudioManager.java82
-rw-r--r--media/java/android/media/AudioMixPort.java5
-rw-r--r--media/java/android/media/AudioPort.java13
-rw-r--r--media/java/android/media/AudioRecord.java15
-rw-r--r--media/java/android/media/AudioTrack.java13
-rw-r--r--media/java/android/media/MediaCodec.java47
-rw-r--r--media/java/android/media/MediaCodecInfo.java2
-rw-r--r--media/java/android/media/SoundPool.java40
-rw-r--r--media/java/android/media/browse/MediaBrowser.java36
-rw-r--r--media/java/android/media/midi/IMidiDeviceServer.aidl1
-rw-r--r--media/java/android/media/midi/MidiDeviceServer.java26
-rw-r--r--media/java/android/media/midi/MidiDeviceService.java4
-rw-r--r--media/java/android/media/midi/MidiManager.java1
-rw-r--r--media/java/android/media/midi/package.html63
-rw-r--r--media/java/android/media/tv/TvInputInfo.java109
-rw-r--r--media/java/android/media/tv/TvInputService.java17
-rw-r--r--media/java/android/mtp/MtpDevice.java3
-rw-r--r--media/java/android/service/media/MediaBrowserService.java50
20 files changed, 345 insertions, 189 deletions
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 431d37e..173d349 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -173,8 +173,7 @@ public final class AudioDeviceInfo {
* @see AudioFormat
*/
public @NonNull int[] getChannelIndexMasks() {
- // TODO: implement
- return new int[0];
+ return mPort.channelIndexMasks();
}
/**
diff --git a/media/java/android/media/AudioDevicePort.java b/media/java/android/media/AudioDevicePort.java
index c078260..aea39a3 100644
--- a/media/java/android/media/AudioDevicePort.java
+++ b/media/java/android/media/AudioDevicePort.java
@@ -37,12 +37,12 @@ public class AudioDevicePort extends AudioPort {
private final String mAddress;
AudioDevicePort(AudioHandle handle, String deviceName,
- int[] samplingRates, int[] channelMasks,
+ int[] samplingRates, int[] channelMasks, int[] channelIndexMasks,
int[] formats, AudioGain[] gains, int type, String address) {
super(handle,
(AudioManager.isInputDevice(type) == true) ?
AudioPort.ROLE_SOURCE : AudioPort.ROLE_SINK,
- deviceName, samplingRates, channelMasks, formats, gains);
+ deviceName, samplingRates, channelMasks, channelIndexMasks, formats, gains);
mType = type;
mAddress = address;
}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 33db9cf..a806440 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -3701,8 +3701,9 @@ public class AudioManager {
* The message sent to apps when the contents of the device list changes if they provide
* a {#link Handler} object to addOnAudioDeviceConnectionListener().
*/
- private final static int MSG_DEVICES_DEVICES_ADDED = 0;
- private final static int MSG_DEVICES_DEVICES_REMOVED = 1;
+ private final static int MSG_DEVICES_CALLBACK_REGISTERED = 0;
+ private final static int MSG_DEVICES_DEVICES_ADDED = 1;
+ private final static int MSG_DEVICES_DEVICES_REMOVED = 2;
/**
* The list of {@link AudioDeviceCallback} objects to receive add/remove notifications.
@@ -3848,8 +3849,10 @@ public class AudioManager {
android.os.Handler handler) {
if (callback != null && !mDeviceCallbacks.containsKey(callback)) {
synchronized (mDeviceCallbacks) {
- mDeviceCallbacks.put(
- callback, new NativeEventHandlerDelegate(callback, handler));
+ NativeEventHandlerDelegate delegate =
+ new NativeEventHandlerDelegate(callback, handler);
+ mDeviceCallbacks.put(callback, delegate);
+ broadcastDeviceListChange(delegate.getHandler());
}
}
}
@@ -3868,49 +3871,60 @@ public class AudioManager {
}
}
+ // Since we need to calculate the changes since THE LAST NOTIFICATION, and not since the
+ // (unpredictable) last time updateAudioPortCache() was called by someone, keep a list
+ // of the ports that exist at the time of the last notification.
+ private ArrayList<AudioDevicePort> mPreviousPorts = new ArrayList<AudioDevicePort>();
+
/**
* Internal method to compute and generate add/remove messages and then send to any
* registered callbacks.
*/
- private void broadcastDeviceListChange() {
+ private void broadcastDeviceListChange(Handler handler) {
int status;
- ArrayList<AudioDevicePort> previous_ports = new ArrayList<AudioDevicePort>();
- status = AudioManager.listPreviousAudioDevicePorts(previous_ports);
- if (status != AudioManager.SUCCESS) {
- return;
- }
-
+ // Get the new current set of ports
ArrayList<AudioDevicePort> current_ports = new ArrayList<AudioDevicePort>();
status = AudioManager.listAudioDevicePorts(current_ports);
if (status != AudioManager.SUCCESS) {
return;
}
- AudioDeviceInfo[] added_devices =
- calcListDeltas(previous_ports, current_ports, GET_DEVICES_ALL);
- AudioDeviceInfo[] removed_devices =
- calcListDeltas(current_ports, previous_ports, GET_DEVICES_ALL);
-
- if (added_devices.length != 0 || removed_devices.length != 0) {
- Collection<NativeEventHandlerDelegate> values;
- synchronized (mDeviceCallbacks) {
- values = mDeviceCallbacks.values();
- }
- for (NativeEventHandlerDelegate delegate : values) {
- Handler handler = delegate.getHandler();
- if (handler != null) {
- if (added_devices.length != 0) {
- handler.sendMessage(
- Message.obtain(handler,MSG_DEVICES_DEVICES_ADDED, added_devices));
- }
- if (removed_devices.length != 0) {
- handler.sendMessage(
- Message.obtain(handler,MSG_DEVICES_DEVICES_REMOVED, removed_devices));
+ if (handler != null) {
+ // This is the callback for the registration, so send the current list
+ AudioDeviceInfo[] deviceList =
+ infoListFromPortList(current_ports, GET_DEVICES_ALL);
+ handler.sendMessage(
+ Message.obtain(handler, MSG_DEVICES_CALLBACK_REGISTERED, deviceList));
+ } else {
+ AudioDeviceInfo[] added_devices =
+ calcListDeltas(mPreviousPorts, current_ports, GET_DEVICES_ALL);
+ AudioDeviceInfo[] removed_devices =
+ calcListDeltas(current_ports, mPreviousPorts, GET_DEVICES_ALL);
+
+ if (added_devices.length != 0 || removed_devices.length != 0) {
+ Collection<NativeEventHandlerDelegate> values;
+ synchronized (mDeviceCallbacks) {
+ values = mDeviceCallbacks.values();
+ }
+ for (NativeEventHandlerDelegate delegate : values) {
+ handler = delegate.getHandler();
+ if (handler != null) {
+ if (added_devices.length != 0) {
+ handler.sendMessage(
+ Message.obtain(handler,MSG_DEVICES_DEVICES_ADDED, added_devices));
+ }
+ if (removed_devices.length != 0) {
+ handler.sendMessage(
+ Message.obtain(handler,MSG_DEVICES_DEVICES_REMOVED,
+ removed_devices));
+ }
}
}
}
}
+
+ mPreviousPorts = current_ports;
}
/**
@@ -3919,7 +3933,7 @@ public class AudioManager {
private class OnAmPortUpdateListener implements AudioManager.OnAudioPortUpdateListener {
static final String TAG = "OnAmPortUpdateListener";
public void onAudioPortListUpdate(AudioPort[] portList) {
- broadcastDeviceListChange();
+ broadcastDeviceListChange(null);
}
/**
@@ -3933,7 +3947,7 @@ public class AudioManager {
* Callback method called when the mediaserver dies
*/
public void onServiceDied() {
- broadcastDeviceListChange();
+ broadcastDeviceListChange(null);
}
}
@@ -3965,8 +3979,8 @@ public class AudioManager {
@Override
public void handleMessage(Message msg) {
switch(msg.what) {
+ case MSG_DEVICES_CALLBACK_REGISTERED:
case MSG_DEVICES_DEVICES_ADDED:
- // call the OnAudioDeviceConnectionListener
if (callback != null) {
callback.onAudioDevicesAdded((AudioDeviceInfo[])msg.obj);
}
diff --git a/media/java/android/media/AudioMixPort.java b/media/java/android/media/AudioMixPort.java
index ab55c8d..ba144bf 100644
--- a/media/java/android/media/AudioMixPort.java
+++ b/media/java/android/media/AudioMixPort.java
@@ -31,9 +31,10 @@ public class AudioMixPort extends AudioPort {
private final int mIoHandle;
AudioMixPort(AudioHandle handle, int ioHandle, int role, String deviceName,
- int[] samplingRates, int[] channelMasks,
+ int[] samplingRates, int[] channelMasks, int[] channelIndexMasks,
int[] formats, AudioGain[] gains) {
- super(handle, role, deviceName, samplingRates, channelMasks, formats, gains);
+ super(handle, role, deviceName, samplingRates, channelMasks, channelIndexMasks,
+ formats, gains);
mIoHandle = ioHandle;
}
diff --git a/media/java/android/media/AudioPort.java b/media/java/android/media/AudioPort.java
index 7328d7a..19bf51d 100644
--- a/media/java/android/media/AudioPort.java
+++ b/media/java/android/media/AudioPort.java
@@ -71,12 +71,13 @@ public class AudioPort {
private final String mName;
private final int[] mSamplingRates;
private final int[] mChannelMasks;
+ private final int[] mChannelIndexMasks;
private final int[] mFormats;
private final AudioGain[] mGains;
private AudioPortConfig mActiveConfig;
AudioPort(AudioHandle handle, int role, String name,
- int[] samplingRates, int[] channelMasks,
+ int[] samplingRates, int[] channelMasks, int[] channelIndexMasks,
int[] formats, AudioGain[] gains) {
mHandle = handle;
@@ -84,6 +85,7 @@ public class AudioPort {
mName = name;
mSamplingRates = samplingRates;
mChannelMasks = channelMasks;
+ mChannelIndexMasks = channelIndexMasks;
mFormats = formats;
mGains = gains;
}
@@ -133,6 +135,15 @@ public class AudioPort {
}
/**
+ * Get the list of supported channel index mask configurations
+ * (e.g 0x0003 means 2 channel, 0x000F means 4 channel....)
+ * Empty array if channel index mask is not relevant for this audio port
+ */
+ public int[] channelIndexMasks() {
+ return mChannelIndexMasks;
+ }
+
+ /**
* Get the list of supported audio format configurations
* (e.g AudioFormat.ENCODING_PCM_16BIT)
* Empty array if format is not relevant for this audio port
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index 3cbc405..974b62e 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -527,10 +527,11 @@ public class AudioRecord
}
/**
- * @return a new {@link AudioRecord} instance initialized with all the parameters set
- * on this <code>Builder</code>
+ * @return a new {@link AudioRecord} instance successfully initialized with all
+ * the parameters set on this <code>Builder</code>.
* @throws UnsupportedOperationException if the parameters set on the <code>Builder</code>
- * were incompatible, or if they are not supported by the device.
+ * were incompatible, or if they are not supported by the device,
+ * or if the device was not available.
*/
public AudioRecord build() throws UnsupportedOperationException {
if (mFormat == null) {
@@ -564,7 +565,13 @@ public class AudioRecord
mBufferSizeInBytes = mFormat.getChannelCount()
* mFormat.getBytesPerSample(mFormat.getEncoding());
}
- return new AudioRecord(mAttributes, mFormat, mBufferSizeInBytes, mSessionId);
+ final AudioRecord record = new AudioRecord(
+ mAttributes, mFormat, mBufferSizeInBytes, mSessionId);
+ if (record.getState() == STATE_UNINITIALIZED) {
+ // release is not necessary
+ throw new UnsupportedOperationException("Cannot create AudioRecord");
+ }
+ return record;
} catch (IllegalArgumentException e) {
throw new UnsupportedOperationException(e.getMessage());
}
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index f395cb3..62810c6 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -661,9 +661,10 @@ public class AudioTrack
/**
* Builds an {@link AudioTrack} instance initialized with all the parameters set
* on this <code>Builder</code>.
- * @return a new {@link AudioTrack} instance.
+ * @return a new successfully initialized {@link AudioTrack} instance.
* @throws UnsupportedOperationException if the parameters set on the <code>Builder</code>
- * were incompatible, or if they are not supported by the device.
+ * were incompatible, or if they are not supported by the device,
+ * or if the device was not available.
*/
public @NonNull AudioTrack build() throws UnsupportedOperationException {
if (mAttributes == null) {
@@ -686,7 +687,13 @@ public class AudioTrack
mBufferSizeInBytes = mFormat.getChannelCount()
* mFormat.getBytesPerSample(mFormat.getEncoding());
}
- return new AudioTrack(mAttributes, mFormat, mBufferSizeInBytes, mMode, mSessionId);
+ final AudioTrack track = new AudioTrack(
+ mAttributes, mFormat, mBufferSizeInBytes, mMode, mSessionId);
+ if (track.getState() == STATE_UNINITIALIZED) {
+ // release is not necessary
+ throw new UnsupportedOperationException("Cannot create AudioTrack");
+ }
+ return track;
} catch (IllegalArgumentException e) {
throw new UnsupportedOperationException(e.getMessage());
}
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index eec4960..a79dd04 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -1455,15 +1455,6 @@ final public class MediaCodec {
@Retention(RetentionPolicy.SOURCE)
public @interface BufferFlag {}
- private static class FrameRenderedInfo {
- public long mPresentationTimeUs;
- public long mNanoTime;
- public FrameRenderedInfo(long presentationTimeUs, long nanoTime) {
- mPresentationTimeUs = presentationTimeUs;
- mNanoTime = nanoTime;
- }
- }
-
private EventHandler mEventHandler;
private EventHandler mOnFrameRenderedHandler;
private EventHandler mCallbackHandler;
@@ -1503,10 +1494,16 @@ final public class MediaCodec {
}
case EVENT_FRAME_RENDERED:
synchronized (mListenerLock) {
- FrameRenderedInfo info = (FrameRenderedInfo)msg.obj;
- if (mOnFrameRenderedListener != null) {
+ Map<String, Object> map = (Map<String, Object>)msg.obj;
+ for (int i = 0; ; ++i) {
+ Object mediaTimeUs = map.get(i + "-media-time-us");
+ Object systemNano = map.get(i + "-system-nano");
+ if (mediaTimeUs == null || systemNano == null
+ || mOnFrameRenderedListener == null) {
+ break;
+ }
mOnFrameRenderedListener.onFrameRendered(
- mCodec, info.mPresentationTimeUs, info.mNanoTime);
+ mCodec, (long)mediaTimeUs, (long)systemNano);
}
break;
}
@@ -2362,26 +2359,9 @@ final public class MediaCodec {
info = mDequeuedOutputInfos.remove(index);
}
}
- // TODO
- // until codec and libgui supports callback, assume frame is rendered within 50 ms
- postRenderedCallback(render, info, 50 /* delayMs */);
releaseOutputBuffer(index, render, false /* updatePTS */, 0 /* dummy */);
}
- private void postRenderedCallback(boolean render, @Nullable BufferInfo info, long delayMs) {
- if (render && info != null) {
- synchronized (mListenerLock) {
- if (mOnFrameRenderedListener != null) {
- FrameRenderedInfo obj = new FrameRenderedInfo(
- info.presentationTimeUs, System.nanoTime() + delayMs * 1000000);
- Message msg = mOnFrameRenderedHandler.obtainMessage(
- EVENT_FRAME_RENDERED, obj);
- mOnFrameRenderedHandler.sendMessageDelayed(msg, delayMs);
- }
- }
- }
- }
-
/**
* If you are done with a buffer, use this call to update its surface timestamp
* and return it to the codec to render it on the output surface. If you
@@ -2440,12 +2420,6 @@ final public class MediaCodec {
info = mDequeuedOutputInfos.remove(index);
}
}
- // TODO
- // until codec and libgui supports callback, assume frame is rendered at the
- // render time or 16 ms from now, whichever is later.
- postRenderedCallback(
- true /* render */, info,
- Math.max(renderTimestampNs - System.nanoTime(), 16666666) / 1000000);
releaseOutputBuffer(
index, true /* render */, true /* updatePTS */, renderTimestampNs);
}
@@ -3049,9 +3023,12 @@ final public class MediaCodec {
} else if (mOnFrameRenderedHandler != null) {
mOnFrameRenderedHandler.removeMessages(EVENT_FRAME_RENDERED);
}
+ native_enableOnFrameRenderedListener(listener != null);
}
}
+ private native void native_enableOnFrameRenderedListener(boolean enable);
+
private EventHandler getEventHandlerOn(
@Nullable Handler handler, @NonNull EventHandler lastHandler) {
if (handler == null) {
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 89d419a..6c26220 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -1382,7 +1382,7 @@ public final class MediaCodecInfo {
// upper limit.
// for now we are keeping the profile specific "width/height
// in macroblocks" limits.
- if (Integer.valueOf(1).equals(map.get("feature-can-swap-width-height"))) {
+ if (map.containsKey("feature-can-swap-width-height")) {
if (widths != null) {
mSmallerDimensionUpperLimit =
Math.min(widths.getUpper(), heights.getUpper());
diff --git a/media/java/android/media/SoundPool.java b/media/java/android/media/SoundPool.java
index 64863c2..1355635 100644
--- a/media/java/android/media/SoundPool.java
+++ b/media/java/android/media/SoundPool.java
@@ -35,6 +35,7 @@ import android.os.ServiceManager;
import android.util.AndroidRuntimeException;
import android.util.Log;
+import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
@@ -125,10 +126,12 @@ public class SoundPool {
private EventHandler mEventHandler;
private SoundPool.OnLoadCompleteListener mOnLoadCompleteListener;
+ private boolean mHasAppOpsPlayAudio;
private final Object mLock;
private final AudioAttributes mAttributes;
private final IAppOpsService mAppOps;
+ private final IAppOpsCallback mAppOpsCallback;
/**
* Constructor. Constructs a SoundPool object with the following
@@ -159,6 +162,24 @@ public class SoundPool {
mAttributes = attributes;
IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
mAppOps = IAppOpsService.Stub.asInterface(b);
+ // initialize mHasAppOpsPlayAudio
+ updateAppOpsPlayAudio();
+ // register a callback to monitor whether the OP_PLAY_AUDIO is still allowed
+ mAppOpsCallback = new IAppOpsCallback.Stub() {
+ public void opChanged(int op, String packageName) {
+ synchronized (mLock) {
+ if (op == AppOpsManager.OP_PLAY_AUDIO) {
+ updateAppOpsPlayAudio();
+ }
+ }
+ }
+ };
+ try {
+ mAppOps.startWatchingMode(AppOpsManager.OP_PLAY_AUDIO,
+ ActivityThread.currentPackageName(), mAppOpsCallback);
+ } catch (RemoteException e) {
+ mHasAppOpsPlayAudio = false;
+ }
}
/**
@@ -168,7 +189,16 @@ public class SoundPool {
* object. The SoundPool can no longer be used and the reference
* should be set to null.
*/
- public native final void release();
+ public final void release() {
+ try {
+ mAppOps.stopWatchingMode(mAppOpsCallback);
+ } catch (RemoteException e) {
+ // nothing to do here, the SoundPool is being released anyway
+ }
+ native_release();
+ }
+
+ private native final void native_release();
protected void finalize() { release(); }
@@ -466,13 +496,17 @@ public class SoundPool {
if ((mAttributes.getAllFlags() & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0) {
return false;
}
+ return !mHasAppOpsPlayAudio;
+ }
+
+ private void updateAppOpsPlayAudio() {
try {
final int mode = mAppOps.checkAudioOperation(AppOpsManager.OP_PLAY_AUDIO,
mAttributes.getUsage(),
Process.myUid(), ActivityThread.currentPackageName());
- return mode != AppOpsManager.MODE_ALLOWED;
+ mHasAppOpsPlayAudio = (mode == AppOpsManager.MODE_ALLOWED);
} catch (RemoteException e) {
- return false;
+ mHasAppOpsPlayAudio = false;
}
}
diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java
index ef8d169..ba867e1 100644
--- a/media/java/android/media/browse/MediaBrowser.java
+++ b/media/java/android/media/browse/MediaBrowser.java
@@ -375,7 +375,7 @@ public final class MediaBrowser {
* @param mediaId The id of the item to retrieve.
* @param cb The callback to receive the result on.
*/
- public void getMediaItem(@NonNull String mediaId, @NonNull final MediaItemCallback cb) {
+ public void getItem(final @NonNull String mediaId, @NonNull final ItemCallback cb) {
if (TextUtils.isEmpty(mediaId)) {
throw new IllegalArgumentException("mediaId is empty.");
}
@@ -387,7 +387,7 @@ public final class MediaBrowser {
mHandler.post(new Runnable() {
@Override
public void run() {
- cb.onError();
+ cb.onError(mediaId);
}
});
return;
@@ -397,15 +397,15 @@ public final class MediaBrowser {
protected void onReceiveResult(int resultCode, Bundle resultData) {
if (resultCode != 0 || resultData == null
|| !resultData.containsKey(MediaBrowserService.KEY_MEDIA_ITEM)) {
- cb.onError();
+ cb.onError(mediaId);
return;
}
Parcelable item = resultData.getParcelable(MediaBrowserService.KEY_MEDIA_ITEM);
if (!(item instanceof MediaItem)) {
- cb.onError();
+ cb.onError(mediaId);
+ return;
}
- cb.onMediaItemLoaded((MediaItem) resultData.getParcelable(
- MediaBrowserService.KEY_MEDIA_ITEM));
+ cb.onItemLoaded((MediaItem)item);
}
};
try {
@@ -415,7 +415,7 @@ public final class MediaBrowser {
mHandler.post(new Runnable() {
@Override
public void run() {
- cb.onError();
+ cb.onError(mediaId);
}
});
}
@@ -728,6 +728,9 @@ public final class MediaBrowser {
public static abstract class SubscriptionCallback {
/**
* Called when the list of children is loaded or updated.
+ *
+ * @param parentId The media id of the parent media item.
+ * @param children The children which were loaded.
*/
public void onChildrenLoaded(@NonNull String parentId,
@NonNull List<MediaItem> children) {
@@ -739,29 +742,32 @@ public final class MediaBrowser {
* If this is called, the subscription remains until {@link MediaBrowser#unsubscribe}
* called, because some errors may heal themselves.
* </p>
+ *
+ * @param parentId The media id of the parent media item whose children could
+ * not be loaded.
*/
- public void onError(@NonNull String id) {
+ public void onError(@NonNull String parentId) {
}
}
/**
- * Callback for receiving the result of {@link #getMediaItem}.
+ * Callback for receiving the result of {@link #getItem}.
*/
- public static abstract class MediaItemCallback {
-
+ public static abstract class ItemCallback {
/**
* Called when the item has been returned by the browser service.
*
* @param item The item that was returned or null if it doesn't exist.
*/
- public void onMediaItemLoaded(MediaItem item) {
+ public void onItemLoaded(MediaItem item) {
}
/**
- * Called when the id doesn't exist or there was an error retrieving the
- * item.
+ * Called when the item doesn't exist or there was an error retrieving it.
+ *
+ * @param itemId The media id of the media item which could not be loaded.
*/
- public void onError() {
+ public void onError(@NonNull String itemId) {
}
}
diff --git a/media/java/android/media/midi/IMidiDeviceServer.aidl b/media/java/android/media/midi/IMidiDeviceServer.aidl
index e30796d..c2cc2b9 100644
--- a/media/java/android/media/midi/IMidiDeviceServer.aidl
+++ b/media/java/android/media/midi/IMidiDeviceServer.aidl
@@ -31,4 +31,5 @@ interface IMidiDeviceServer
void connectPorts(IBinder token, in ParcelFileDescriptor pfd, int outputPortNumber);
MidiDeviceInfo getDeviceInfo();
+ void setDeviceInfo(in MidiDeviceInfo deviceInfo);
}
diff --git a/media/java/android/media/midi/MidiDeviceServer.java b/media/java/android/media/midi/MidiDeviceServer.java
index 1b56e1c..1212b64 100644
--- a/media/java/android/media/midi/MidiDeviceServer.java
+++ b/media/java/android/media/midi/MidiDeviceServer.java
@@ -269,8 +269,20 @@ public final class MidiDeviceServer implements Closeable {
public MidiDeviceInfo getDeviceInfo() {
return mDeviceInfo;
}
+
+ @Override
+ public void setDeviceInfo(MidiDeviceInfo deviceInfo) {
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ throw new SecurityException("setDeviceInfo should only be called by MidiService");
+ }
+ if (mDeviceInfo != null) {
+ throw new IllegalStateException("setDeviceInfo should only be called once");
+ }
+ mDeviceInfo = deviceInfo;
+ }
};
+ // Constructor for MidiManager.createDeviceServer()
/* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers,
int numOutputPorts, Callback callback) {
mMidiManager = midiManager;
@@ -292,6 +304,13 @@ public final class MidiDeviceServer implements Closeable {
mGuard.open("close");
}
+ // Constructor for MidiDeviceService.onCreate()
+ /* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers,
+ MidiDeviceInfo deviceInfo, Callback callback) {
+ this(midiManager, inputPortReceivers, deviceInfo.getOutputPortCount(), callback);
+ mDeviceInfo = deviceInfo;
+ }
+
/* package */ IMidiDeviceServer getBinderInterface() {
return mServer;
}
@@ -300,13 +319,6 @@ public final class MidiDeviceServer implements Closeable {
return mServer.asBinder();
}
- /* package */ void setDeviceInfo(MidiDeviceInfo deviceInfo) {
- if (mDeviceInfo != null) {
- throw new IllegalStateException("setDeviceInfo should only be called once");
- }
- mDeviceInfo = deviceInfo;
- }
-
private void updateDeviceStatus() {
// clear calling identity, since we may be in a Binder call from one of our clients
long identityToken = Binder.clearCallingIdentity();
diff --git a/media/java/android/media/midi/MidiDeviceService.java b/media/java/android/media/midi/MidiDeviceService.java
index d897ad2..388d95b 100644
--- a/media/java/android/media/midi/MidiDeviceService.java
+++ b/media/java/android/media/midi/MidiDeviceService.java
@@ -83,9 +83,7 @@ abstract public class MidiDeviceService extends Service {
if (inputPortReceivers == null) {
inputPortReceivers = new MidiReceiver[0];
}
- server = new MidiDeviceServer(mMidiManager, inputPortReceivers,
- deviceInfo.getOutputPortCount(), mCallback);
- server.setDeviceInfo(deviceInfo);
+ server = new MidiDeviceServer(mMidiManager, inputPortReceivers, deviceInfo, mCallback);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in IMidiManager.getServiceDeviceInfo");
server = null;
diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java
index 0beb9a4..89230fe 100644
--- a/media/java/android/media/midi/MidiManager.java
+++ b/media/java/android/media/midi/MidiManager.java
@@ -318,7 +318,6 @@ public final class MidiManager {
Log.e(TAG, "registerVirtualDevice failed");
return null;
}
- server.setDeviceInfo(deviceInfo);
return server;
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in createVirtualDevice");
diff --git a/media/java/android/media/midi/package.html b/media/java/android/media/midi/package.html
index 8b2bd16..673c4ba 100644
--- a/media/java/android/media/midi/package.html
+++ b/media/java/android/media/midi/package.html
@@ -10,27 +10,28 @@
<p>The Android MIDI package allows users to:</p>
<ul>
- <li> Connect a MIDI keyboard to Android to play a synthesizer or drive music apps.
- <li> Connect alternative MIDI controllers to Android.
- <li> Drive external MIDI synths from Android.
- <li> Drive external peripherals, lights, show control, etc from Android.
- <li> Generate music dynamically from games or music creation apps.
- <li> Generate MIDI messages in one app and send them to a second app.
- <li> Use an Android device running in <em>peripheral mode</em> as a multitouch controller connected to a laptop.
+ <li> Connect a MIDI keyboard to Android to play a synthesizer or drive music apps.</li>
+ <li> Connect alternative MIDI controllers to Android.</li>
+ <li> Drive external MIDI synths from Android.</li>
+ <li> Drive external peripherals, lights, show control, etc from Android.</li>
+ <li> Generate music dynamically from games or music creation apps.</li>
+ <li> Generate MIDI messages in one app and send them to a second app.</li>
+ <li> Use an Android device running in <em>peripheral mode</em> as a multitouch controller
+ connected to a laptop.</li>
</ul>
<h2 id=the_api_features_include>The API features include:</h2>
-
<ul>
<li> Enumeration of currently available devices. Information includes name, vendor,
-capabilities, etc.
- <li> Provide notification when MIDI devices are plugged in or unplugged.
- <li> Support efficient transmission of single or multiple short 1-3 byte MIDI
-messages.
- <li> Support transmission of arbitrary length data for SysEx, etc.
- <li> Timestamps to avoid jitter.
- <li> Support direction connection or &ldquo;patching&rdquo; of devices for lower latency.
+capabilities, etc.</li>
+ <li> Provide notification when MIDI devices are plugged in or unplugged.</li>
+ <li> Support efficient transmission of single or multiple short 1-3 byte MIDI messages.</li>
+ <li> Support transmission of arbitrary length data for SysEx, etc.</li>
+ <li> Timestamps to avoid jitter.</li>
+ <li> Support creation of <em>virtual MIDI devices</em> that can be connected to other devices.
+ An example might be a synthesizer app that can be controlled by a composing app.</li>
+ <li> Support direction connection or &ldquo;patching&rdquo; of devices for lower latency.</li>
</ul>
<h2 id=transports_supported>Transports Supported</h2>
@@ -65,6 +66,14 @@ the MidiService.</p>
<h1 id=writing_a_midi_application>Writing a MIDI Application</h1>
+<h2 id=manifest_feature>Declare Feature in Manifest</h2>
+
+<p>An app that requires the MIDI API should declare that in the AndroidManifest.xml file.
+Then the app will not appear in the Play Store for old devices that do not support the MIDI API.</p>
+
+<pre class=prettyprint>
+&lt;uses-feature android:name="android.software.midi" android:required="true"/>
+</pre>
<h2 id=the_midimanager>The MidiManager</h2>
@@ -132,11 +141,15 @@ String manufacturer = properties
<p>Other properties include PROPERTY_PRODUCT, PROPERTY_NAME,
PROPERTY_SERIAL_NUMBER</p>
-<p>You can get the names of the ports from a PortInfo object.</p>
+<p>You can get the names and types of the ports from a PortInfo object.
+The type will be either TYPE_INPUT or TYPE_OUTPUT.</p>
<pre class=prettyprint>
-PortInfo portInfo = info.getInputPortInfo(i);
-String portName = portInfo.getName();
+MidiDeviceInfo.PortInfo[] portInfos = info.getPorts();
+String portName = portInfos[0].getName();
+if (portInfos[0].getType() == MidiDeviceInfo.PortInfo.TYPE_INPUT) {
+ ...
+}
</pre>
@@ -196,8 +209,9 @@ consistent with the other audio and input timers.</p>
<p>Here we send a message with a timestamp 2 seconds in the future.</p>
<pre class=prettyprint>
+final long NANOS_PER_SECOND = 1000000000L;
long now = System.nanoTime();
-long future = now + (2 * 1000000000);
+long future = now + (2 * NANOS_PER_SECOND);
inputPort.send(buffer, offset, numBytes, future);
</pre>
@@ -263,7 +277,8 @@ AndroidManifest.xml file.</p>
<p>The details of the resource in this example is stored in
-&ldquo;res/xml/synth_device_info.xml&rdquo;.</p>
+&ldquo;res/xml/synth_device_info.xml&rdquo;. The port names that you
+declare in this file will be available from PortInfo.getName().</p>
<pre class=prettyprint>
&lt;devices>
@@ -288,6 +303,8 @@ import android.media.midi.MidiReceiver;
public class MidiSynthDeviceService extends MidiDeviceService {
private static final String TAG = "MidiSynthDeviceService";
private MySynthEngine mSynthEngine = new MySynthEngine();
+ private boolean synthStarted = false;
+
&#64;Override
public void onCreate() {
super.onCreate();
@@ -311,10 +328,12 @@ public class MidiSynthDeviceService extends MidiDeviceService {
*/
&#64;Override
public void onDeviceStatusChanged(MidiDeviceStatus status) {
- if (status.isInputPortOpen(0)) {
+ if (status.isInputPortOpen(0) && !synthStarted) {
mSynthEngine.start();
- } else {
+ synthStarted = true;
+ } else if (!status.isInputPortOpen(0) && synthStarted){
mSynthEngine.stop();
+ synthStarted = false;
}
}
}
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index c537dd6..5d1aa14 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -29,6 +29,7 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.net.Uri;
import android.os.Parcel;
@@ -125,7 +126,9 @@ public final class TvInputInfo implements Parcelable {
private String mSettingsActivity;
private HdmiDeviceInfo mHdmiDeviceInfo;
+ private int mLabelRes;
private String mLabel;
+ private Icon mIcon;
private Uri mIconUri;
private boolean mIsConnectedToHdmiSwitch;
@@ -155,7 +158,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, false, null, null, false);
+ null, TYPE_TUNER, false, 0, null, null, null, false);
}
/**
@@ -165,11 +168,11 @@ public final class TvInputInfo implements Parcelable {
* @param service The ResolveInfo returned from the package manager about this TV input service.
* @param hdmiDeviceInfo The HdmiDeviceInfo for a HDMI CEC logical device.
* @param parentId The ID of this TV input's parent input. {@code null} if none exists.
+ * @param label The label of this TvInputInfo. If it is {@code null} or empty, {@code service}
+ * label will be loaded.
* @param iconUri The {@link android.net.Uri} to load the icon image. See
* {@link android.content.ContentResolver#openInputStream}. If it is {@code null},
* the application icon of {@code service} will be loaded.
- * @param label The label of this TvInputInfo. If it is {@code null} or empty, {@code service}
- * label will be loaded.
* @hide
*/
@SystemApi
@@ -179,7 +182,34 @@ 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, true, label, iconUri, isConnectedToHdmiSwitch);
+ hdmiDeviceInfo), parentId, TYPE_HDMI, true, 0, label, null, iconUri,
+ isConnectedToHdmiSwitch);
+ input.mHdmiDeviceInfo = hdmiDeviceInfo;
+ return input;
+ }
+
+ /**
+ * Create a new instance of the TvInputInfo class, instantiating it from the given Context,
+ * ResolveInfo, and HdmiDeviceInfo.
+ *
+ * @param service The ResolveInfo returned from the package manager about this TV input service.
+ * @param hdmiDeviceInfo The HdmiDeviceInfo for a HDMI CEC logical device.
+ * @param parentId The ID of this TV input's parent input. {@code null} if none exists.
+ * @param labelRes The label resource ID of this TvInputInfo. If it is {@code 0},
+ * {@code service} label will be loaded.
+ * @param icon The {@link android.graphics.drawable.Icon} to load the icon image. If it is
+ * {@code null}, the application icon of {@code service} will be loaded.
+ * @hide
+ */
+ @SystemApi
+ public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service,
+ HdmiDeviceInfo hdmiDeviceInfo, String parentId, int labelRes, Icon icon)
+ throws XmlPullParserException, IOException {
+ boolean isConnectedToHdmiSwitch = (hdmiDeviceInfo.getPhysicalAddress() & 0x0FFF) != 0;
+ TvInputInfo input = createTvInputInfo(context, service, generateInputIdForHdmiDevice(
+ new ComponentName(service.serviceInfo.packageName, service.serviceInfo.name),
+ hdmiDeviceInfo), parentId, TYPE_HDMI, true, labelRes, null, icon, null,
+ isConnectedToHdmiSwitch);
input.mHdmiDeviceInfo = hdmiDeviceInfo;
return input;
}
@@ -190,11 +220,11 @@ public final class TvInputInfo implements Parcelable {
*
* @param service The ResolveInfo returned from the package manager about this TV input service.
* @param hardwareInfo The TvInputHardwareInfo for a TV input hardware device.
+ * @param label The label of this TvInputInfo. If it is {@code null} or empty, {@code service}
+ * label will be loaded.
* @param iconUri The {@link android.net.Uri} to load the icon image. See
* {@link android.content.ContentResolver#openInputStream}. If it is {@code null},
* the application icon of {@code service} will be loaded.
- * @param label The label of this TvInputInfo. If it is {@code null} or empty, {@code service}
- * label will be loaded.
* @hide
*/
@SystemApi
@@ -204,12 +234,34 @@ 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, true, label, iconUri, false);
+ hardwareInfo), null, inputType, true, 0, label, null, iconUri, false);
+ }
+
+ /**
+ * Create a new instance of the TvInputInfo class, instantiating it from the given Context,
+ * ResolveInfo, and TvInputHardwareInfo.
+ *
+ * @param service The ResolveInfo returned from the package manager about this TV input service.
+ * @param hardwareInfo The TvInputHardwareInfo for a TV input hardware device.
+ * @param labelRes The label resource ID of this TvInputInfo. If it is {@code 0},
+ * {@code service} label will be loaded.
+ * @param icon The {@link android.graphics.drawable.Icon} to load the icon image. If it is
+ * {@code null}, the application icon of {@code service} will be loaded.
+ * @hide
+ */
+ @SystemApi
+ public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service,
+ TvInputHardwareInfo hardwareInfo, int labelRes, Icon icon)
+ throws XmlPullParserException, IOException {
+ int inputType = sHardwareTypeToTvInputType.get(hardwareInfo.getType(), TYPE_TUNER);
+ return createTvInputInfo(context, service, generateInputIdForHardware(
+ new ComponentName(service.serviceInfo.packageName, service.serviceInfo.name),
+ hardwareInfo), null, inputType, true, labelRes, null, icon, null, false);
}
- private static TvInputInfo createTvInputInfo(Context context, ResolveInfo service,
- String id, String parentId, int inputType, boolean isHardwareInput, String label,
- Uri iconUri, boolean isConnectedToHdmiSwitch)
+ private static TvInputInfo createTvInputInfo(Context context, ResolveInfo service, String id,
+ String parentId, int inputType, boolean isHardwareInput, int labelRes, String label,
+ Icon icon, Uri iconUri, boolean isConnectedToHdmiSwitch)
throws XmlPullParserException, IOException {
ServiceInfo si = service.serviceInfo;
PackageManager pm = context.getPackageManager();
@@ -254,7 +306,9 @@ public final class TvInputInfo implements Parcelable {
}
sa.recycle();
+ input.mLabelRes = labelRes;
input.mLabel = label;
+ input.mIcon = icon;
input.mIconUri = iconUri;
input.mIsConnectedToHdmiSwitch = isConnectedToHdmiSwitch;
return input;
@@ -426,11 +480,13 @@ public final class TvInputInfo implements Parcelable {
* a label, its name is returned.
*/
public CharSequence loadLabel(@NonNull Context context) {
- if (TextUtils.isEmpty(mLabel)) {
- return mService.loadLabel(context.getPackageManager());
- } else {
+ if (mLabelRes != 0) {
+ return context.getPackageManager().getText(mService.serviceInfo.packageName, mLabelRes,
+ null);
+ } else if (!TextUtils.isEmpty(mLabel)) {
return mLabel;
}
+ return mService.loadLabel(context.getPackageManager());
}
/**
@@ -454,19 +510,20 @@ public final class TvInputInfo implements Parcelable {
* application's icon is returned. If it's unavailable too, {@code null} is returned.
*/
public Drawable loadIcon(@NonNull Context context) {
- if (mIconUri == null) {
- return loadServiceIcon(context);
- }
- try (InputStream is = context.getContentResolver().openInputStream(mIconUri)) {
- Drawable drawable = Drawable.createFromStream(is, null);
- if (drawable == null) {
- return loadServiceIcon(context);
+ if (mIcon != null) {
+ return mIcon.loadDrawable(context);
+ } else if (mIconUri != null) {
+ try (InputStream is = context.getContentResolver().openInputStream(mIconUri)) {
+ Drawable drawable = Drawable.createFromStream(is, null);
+ if (drawable != null) {
+ return drawable;
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Loading the default icon due to a failure on loading " + mIconUri, e);
+ // Falls back.
}
- return drawable;
- } catch (IOException e) {
- Log.w(TAG, "Loading the default icon due to a failure on loading " + mIconUri, e);
- return loadServiceIcon(context);
}
+ return loadServiceIcon(context);
}
@Override
@@ -516,7 +573,9 @@ public final class TvInputInfo implements Parcelable {
dest.writeInt(mType);
dest.writeByte(mIsHardwareInput ? (byte) 1 : 0);
dest.writeParcelable(mHdmiDeviceInfo, flags);
+ dest.writeParcelable(mIcon, flags);
dest.writeParcelable(mIconUri, flags);
+ dest.writeInt(mLabelRes);
dest.writeString(mLabel);
dest.writeByte(mIsConnectedToHdmiSwitch ? (byte) 1 : 0);
}
@@ -591,7 +650,9 @@ public final class TvInputInfo implements Parcelable {
mType = in.readInt();
mIsHardwareInput = in.readByte() == 1 ? true : false;
mHdmiDeviceInfo = in.readParcelable(null);
+ mIcon = in.readParcelable(null);
mIconUri = in.readParcelable(null);
+ mLabelRes = in.readInt();
mLabel = in.readString();
mIsConnectedToHdmiSwitch = in.readByte() == 1 ? true : false;
}
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 50a215c..f52ccc9 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -434,9 +434,12 @@ public abstract class TvInputService extends Service {
}
/**
- * Informs the application that the video is now available for watching. This is primarily
- * used to signal the application to unblock the screen. The TV input service must call this
- * method as soon as the content rendered onto its surface gets ready for viewing.
+ * Informs the application that the video is now available for watching. Video is blocked
+ * until this method is called.
+ *
+ * <p>The TV input service must call this method as soon as the content rendered onto its
+ * surface is ready for viewing. This method must be called each time {@link #onTune(Uri)}
+ * is called.
*
* @see #notifyVideoUnavailable
*/
@@ -761,9 +764,11 @@ public abstract class TvInputService extends Service {
public abstract void onSetStreamVolume(float volume);
/**
- * 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.
+ * Tunes to a given channel.
+ *
+ * <p>No video will be displayed until {@link #notifyVideoAvailable()} is 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} if the tuning was successful, {@code false} otherwise.
diff --git a/media/java/android/mtp/MtpDevice.java b/media/java/android/mtp/MtpDevice.java
index 72dcaa8..a68361b 100644
--- a/media/java/android/mtp/MtpDevice.java
+++ b/media/java/android/mtp/MtpDevice.java
@@ -132,7 +132,8 @@ public final class MtpDevice {
*
* @param storageId the storage unit to query
* @param format the format of the object to return, or zero for all formats
- * @param objectHandle the parent object to query, or zero for the storage root
+ * @param objectHandle the parent object to query, -1 for the storage root,
+ * or zero for all objects
* @return the object handles
*/
public int[] getObjectHandles(int storageId, int format, int objectHandle) {
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index 41156cb..1bb99ff 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -76,7 +76,7 @@ public abstract class MediaBrowserService extends Service {
public static final String SERVICE_INTERFACE = "android.media.browse.MediaBrowserService";
/**
- * A key for passing the MediaItem to the ResultReceiver in getMediaItem.
+ * A key for passing the MediaItem to the ResultReceiver in getItem.
*
* @hide
*/
@@ -109,6 +109,7 @@ public abstract class MediaBrowserService extends Service {
* be thrown.
*
* @see MediaBrowserService#onLoadChildren
+ * @see MediaBrowserService#onGetMediaItem
*/
public class Result<T> {
private Object mDebug;
@@ -279,20 +280,7 @@ public abstract class MediaBrowserService extends Service {
mHandler.post(new Runnable() {
@Override
public void run() {
- final Result<MediaBrowser.MediaItem> result
- = new Result<MediaBrowser.MediaItem>(mediaId) {
- @Override
- void onResultSent(MediaBrowser.MediaItem item) {
- Bundle bundle = new Bundle();
- bundle.putParcelable(KEY_MEDIA_ITEM, item);
- receiver.send(0, bundle);
- }
- };
- try {
- MediaBrowserService.this.getMediaItem(mediaId, result);
- } catch (UnsupportedOperationException e) {
- receiver.send(-1, null);
- }
+ performLoadItem(mediaId, receiver);
}
});
}
@@ -357,8 +345,7 @@ public abstract class MediaBrowserService extends Service {
@NonNull Result<List<MediaBrowser.MediaItem>> result);
/**
- * Called to get a specific media item. The mediaId should be the same id
- * that would be returned for this item when it is in a list of child items.
+ * Called to get information about a specific media item.
* <p>
* Implementations must call {@link Result#sendResult result.sendResult}. If
* loading the item will be an expensive operation {@link Result#detach
@@ -366,17 +353,15 @@ public abstract class MediaBrowserService extends Service {
* then {@link Result#sendResult result.sendResult} called when the item has
* been loaded.
* <p>
- * The default implementation throws an exception.
+ * The default implementation sends a null result.
*
- * @param mediaId The id for the specific
+ * @param itemId The id for the specific
* {@link android.media.browse.MediaBrowser.MediaItem}.
* @param result The Result to send the item to, or null if the id is
* invalid.
- * @throws UnsupportedOperationException
*/
- public void getMediaItem(String mediaId, Result<MediaBrowser.MediaItem> result)
- throws UnsupportedOperationException {
- throw new UnsupportedOperationException("getMediaItem is not supported.");
+ public void onLoadItem(String itemId, Result<MediaBrowser.MediaItem> result) {
+ result.sendResult(null);
}
/**
@@ -515,6 +500,25 @@ public abstract class MediaBrowserService extends Service {
}
}
+ private void performLoadItem(String itemId, final ResultReceiver receiver) {
+ final Result<MediaBrowser.MediaItem> result =
+ new Result<MediaBrowser.MediaItem>(itemId) {
+ @Override
+ void onResultSent(MediaBrowser.MediaItem item) {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(KEY_MEDIA_ITEM, item);
+ receiver.send(0, bundle);
+ }
+ };
+
+ MediaBrowserService.this.onLoadItem(itemId, result);
+
+ if (!result.isDone()) {
+ throw new IllegalStateException("onLoadItem must call detach() or sendResult()"
+ + " before returning for id=" + itemId);
+ }
+ }
+
/**
* Contains information that the browser service needs to send to the client
* when first connected.