diff options
Diffstat (limited to 'media/java')
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 “patching” 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 “patching” 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> +<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 -“res/xml/synth_device_info.xml”.</p> +“res/xml/synth_device_info.xml”. The port names that you +declare in this file will be available from PortInfo.getName().</p> <pre class=prettyprint> <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; + @Override public void onCreate() { super.onCreate(); @@ -311,10 +328,12 @@ public class MidiSynthDeviceService extends MidiDeviceService { */ @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. |
