diff options
Diffstat (limited to 'media')
18 files changed, 465 insertions, 259 deletions
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java index 7eb1357..3cbc405 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -1277,7 +1277,8 @@ public class AudioRecord native_enableDeviceCallback(); } mRoutingChangeListeners.put( - listener, new NativeRoutingEventHandlerDelegate(this, listener, handler)); + listener, new NativeRoutingEventHandlerDelegate(this, listener, + handler != null ? handler : new Handler(mInitializationLooper))); } } } diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index 7293c6c..f395cb3 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -2244,7 +2244,8 @@ public class AudioTrack native_enableDeviceCallback(); } mRoutingChangeListeners.put( - listener, new NativeRoutingEventHandlerDelegate(this, listener, handler)); + listener, new NativeRoutingEventHandlerDelegate(this, listener, + handler != null ? handler : new Handler(mInitializationLooper))); } } } diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index aa5d43a..6bf5721 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -57,6 +57,16 @@ public class ExifInterface { public static final String TAG_APERTURE = "FNumber"; /** Type is String. */ public static final String TAG_ISO = "ISOSpeedRatings"; + /** Type is String. */ + public static final String TAG_DATETIME_DIGITIZED = "DateTimeDigitized"; + /** Type is int. */ + public static final String TAG_SUBSEC_TIME = "SubSecTime"; + /** Type is int. */ + public static final String TAG_SUBSEC_TIME_ORIG = "SubSecTimeOriginal"; + /** Type is int. */ + public static final String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized"; + + /** * @hide 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/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 4b1fa13..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); } @@ -549,8 +608,9 @@ public final class TvInputInfo implements Parcelable { private static final String generateInputIdForHdmiDevice( ComponentName name, HdmiDeviceInfo deviceInfo) { // Example of the format : "/HDMI%04X%02X" - String format = String.format("%s%s%%0%sX%%0%sX", DELIMITER_INFO_IN_ID, PREFIX_HDMI_DEVICE, - LENGTH_HDMI_PHYSICAL_ADDRESS, LENGTH_HDMI_DEVICE_ID); + String format = DELIMITER_INFO_IN_ID + PREFIX_HDMI_DEVICE + + "%0" + LENGTH_HDMI_PHYSICAL_ADDRESS + "X" + + "%0" + LENGTH_HDMI_DEVICE_ID + "X"; return name.flattenToShortString() + String.format(format, deviceInfo.getPhysicalAddress(), deviceInfo.getId()); } @@ -564,8 +624,8 @@ public final class TvInputInfo implements Parcelable { */ private static final String generateInputIdForHardware( ComponentName name, TvInputHardwareInfo hardwareInfo) { - return name.flattenToShortString() + String.format("%s%s%d", - DELIMITER_INFO_IN_ID, PREFIX_HARDWARE_DEVICE, hardwareInfo.getDeviceId()); + return name.flattenToShortString() + DELIMITER_INFO_IN_ID + PREFIX_HARDWARE_DEVICE + + hardwareInfo.getDeviceId(); } public static final Parcelable.Creator<TvInputInfo> CREATOR = @@ -590,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/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. diff --git a/media/jni/android_media_ImageWriter.cpp b/media/jni/android_media_ImageWriter.cpp index 294cd84..634ba64 100644 --- a/media/jni/android_media_ImageWriter.cpp +++ b/media/jni/android_media_ImageWriter.cpp @@ -293,7 +293,8 @@ static jlong ImageWriter_init(JNIEnv* env, jobject thiz, jobject weakThiz, jobje res = native_window_set_usage(anw.get(), GRALLOC_USAGE_SW_WRITE_OFTEN); if (res != OK) { ALOGE("%s: Configure usage %08x for format %08x failed: %s (%d)", - __FUNCTION__, GRALLOC_USAGE_SW_WRITE_OFTEN, format, strerror(-res), res); + __FUNCTION__, static_cast<unsigned int>(GRALLOC_USAGE_SW_WRITE_OFTEN), + format, strerror(-res), res); jniThrowRuntimeException(env, "Failed to SW_WRITE_OFTEN configure usage"); return 0; } diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index 93b8ec7..ce7f7e5 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -56,6 +56,7 @@ enum { enum { EVENT_CALLBACK = 1, EVENT_SET_CALLBACK = 2, + EVENT_FRAME_RENDERED = 3, }; static struct CryptoErrorCodes { @@ -226,6 +227,18 @@ void JMediaCodec::deleteJavaObjects(JNIEnv *env) { mByteBufferLimitMethodID = NULL; } +status_t JMediaCodec::enableOnFrameRenderedListener(jboolean enable) { + if (enable) { + if (mOnFrameRenderedNotification == NULL) { + mOnFrameRenderedNotification = new AMessage(kWhatFrameRendered, this); + } + } else { + mOnFrameRenderedNotification.clear(); + } + + return mCodec->setOnFrameRenderedNotification(mOnFrameRenderedNotification); +} + status_t JMediaCodec::setCallback(jobject cb) { if (cb != NULL) { if (mCallbackNotification == NULL) { @@ -728,6 +741,27 @@ void JMediaCodec::handleCallback(const sp<AMessage> &msg) { env->DeleteLocalRef(obj); } +void JMediaCodec::handleFrameRenderedNotification(const sp<AMessage> &msg) { + int32_t arg1 = 0, arg2 = 0; + jobject obj = NULL; + JNIEnv *env = AndroidRuntime::getJNIEnv(); + + sp<AMessage> data; + CHECK(msg->findMessage("data", &data)); + + status_t err = ConvertMessageToMap(env, data, &obj); + if (err != OK) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + env->CallVoidMethod( + mObject, gFields.postEventFromNativeID, + EVENT_FRAME_RENDERED, arg1, arg2, obj); + + env->DeleteLocalRef(obj); +} + void JMediaCodec::onMessageReceived(const sp<AMessage> &msg) { switch (msg->what()) { case kWhatCallbackNotify: @@ -735,6 +769,11 @@ void JMediaCodec::onMessageReceived(const sp<AMessage> &msg) { handleCallback(msg); break; } + case kWhatFrameRendered: + { + handleFrameRenderedNotification(msg); + break; + } default: TRESPASS(); } @@ -848,6 +887,22 @@ static jint throwExceptionAsNecessary( } } +static void android_media_MediaCodec_native_enableOnFrameRenderedListener( + JNIEnv *env, + jobject thiz, + jboolean enabled) { + sp<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == NULL) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return; + } + + status_t err = codec->enableOnFrameRenderedListener(enabled); + + throwExceptionAsNecessary(env, err); +} + static void android_media_MediaCodec_native_setCallback( JNIEnv *env, jobject thiz, @@ -1744,6 +1799,9 @@ static JNINativeMethod gMethods[] = { { "native_setInputSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaCodec_setInputSurface }, + { "native_enableOnFrameRenderedListener", "(Z)V", + (void *)android_media_MediaCodec_native_enableOnFrameRenderedListener }, + { "native_setCallback", "(Landroid/media/MediaCodec$Callback;)V", (void *)android_media_MediaCodec_native_setCallback }, diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h index a4ed67b..6650cf9 100644 --- a/media/jni/android_media_MediaCodec.h +++ b/media/jni/android_media_MediaCodec.h @@ -46,6 +46,8 @@ struct JMediaCodec : public AHandler { void registerSelf(); void release(); + status_t enableOnFrameRenderedListener(jboolean enable); + status_t setCallback(jobject cb); status_t configure( @@ -116,11 +118,11 @@ protected: virtual ~JMediaCodec(); virtual void onMessageReceived(const sp<AMessage> &msg); - void handleCallback(const sp<AMessage> &msg); private: enum { kWhatCallbackNotify, + kWhatFrameRendered, }; jclass mClass; @@ -139,6 +141,7 @@ private: sp<MediaCodec> mCodec; sp<AMessage> mCallbackNotification; + sp<AMessage> mOnFrameRenderedNotification; status_t mInitStatus; @@ -148,6 +151,8 @@ private: void cacheJavaObjects(JNIEnv *env); void deleteJavaObjects(JNIEnv *env); + void handleCallback(const sp<AMessage> &msg); + void handleFrameRenderedNotification(const sp<AMessage> &msg); DISALLOW_EVIL_CONSTRUCTORS(JMediaCodec); }; diff --git a/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp index 96b72a2..fdc586b 100644 --- a/media/jni/audioeffect/android_media_AudioEffect.cpp +++ b/media/jni/audioeffect/android_media_AudioEffect.cpp @@ -92,6 +92,7 @@ static jint translateError(int code) { } } +static Mutex sLock; // ---------------------------------------------------------------------------- static void effectCallback(int event, void* user, void *info) { @@ -182,6 +183,32 @@ effectCallback_Exit: } // ---------------------------------------------------------------------------- + +static sp<AudioEffect> getAudioEffect(JNIEnv* env, jobject thiz) +{ + Mutex::Autolock l(sLock); + AudioEffect* const ae = + (AudioEffect*)env->GetLongField(thiz, fields.fidNativeAudioEffect); + return sp<AudioEffect>(ae); +} + +static sp<AudioEffect> setAudioEffect(JNIEnv* env, jobject thiz, + const sp<AudioEffect>& ae) +{ + Mutex::Autolock l(sLock); + sp<AudioEffect> old = + (AudioEffect*)env->GetLongField(thiz, fields.fidNativeAudioEffect); + if (ae.get()) { + ae->incStrong((void*)setAudioEffect); + } + if (old != 0) { + old->decStrong((void*)setAudioEffect); + } + env->SetLongField(thiz, fields.fidNativeAudioEffect, (jlong)ae.get()); + return old; +} + +// ---------------------------------------------------------------------------- // This function gets some field IDs, which in turn causes class initialization. // It is called from a static block in AudioEffect, which won't run until the // first time an instance of this class is used. @@ -257,7 +284,7 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t ALOGV("android_media_AudioEffect_native_setup"); AudioEffectJniStorage* lpJniStorage = NULL; int lStatus = AUDIOEFFECT_ERROR_NO_MEMORY; - AudioEffect* lpAudioEffect = NULL; + sp<AudioEffect> lpAudioEffect; jint* nId = NULL; const char *typeStr = NULL; const char *uuidStr = NULL; @@ -272,6 +299,8 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t ScopedUtfChars opPackageNameStr(env, opPackageName); + setAudioEffect(env, thiz, 0); + if (type != NULL) { typeStr = env->GetStringUTFChars(type, NULL); if (typeStr == NULL) { // Out of memory @@ -324,7 +353,7 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t &lpJniStorage->mCallbackData, sessionId, 0); - if (lpAudioEffect == NULL) { + if (lpAudioEffect == 0) { ALOGE("Error creating AudioEffect"); goto setup_failure; } @@ -394,7 +423,7 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t env->SetObjectArrayElement(javadesc, 0, jdesc); - env->SetLongField(thiz, fields.fidNativeAudioEffect, (jlong)lpAudioEffect); + setAudioEffect(env, thiz, lpAudioEffect); env->SetLongField(thiz, fields.fidJniData, (jlong)lpJniStorage); @@ -407,12 +436,9 @@ setup_failure: env->ReleasePrimitiveArrayCritical(jId, nId, 0); } - if (lpAudioEffect) { - delete lpAudioEffect; - } - env->SetLongField(thiz, fields.fidNativeAudioEffect, 0); - if (lpJniStorage) { + env->DeleteGlobalRef(lpJniStorage->mCallbackData.audioEffect_class); + env->DeleteGlobalRef(lpJniStorage->mCallbackData.audioEffect_ref); delete lpJniStorage; } env->SetLongField(thiz, fields.fidJniData, 0); @@ -430,20 +456,20 @@ setup_failure: // ---------------------------------------------------------------------------- -static void android_media_AudioEffect_native_finalize(JNIEnv *env, jobject thiz) { - ALOGV("android_media_AudioEffect_native_finalize jobject: %p\n", thiz); - - // delete the AudioEffect object - AudioEffect* lpAudioEffect = (AudioEffect *)env->GetLongField( - thiz, fields.fidNativeAudioEffect); - if (lpAudioEffect) { - ALOGV("deleting AudioEffect: %p\n", lpAudioEffect); - delete lpAudioEffect; +static void android_media_AudioEffect_native_release(JNIEnv *env, jobject thiz) { + sp<AudioEffect> lpAudioEffect = setAudioEffect(env, thiz, 0); + if (lpAudioEffect == 0) { + return; } // delete the JNI data - AudioEffectJniStorage* lpJniStorage = (AudioEffectJniStorage *)env->GetLongField( - thiz, fields.fidJniData); + AudioEffectJniStorage* lpJniStorage = + (AudioEffectJniStorage *)env->GetLongField(thiz, fields.fidJniData); + + // reset the native resources in the Java object so any attempt to access + // them after a call to release fails. + env->SetLongField(thiz, fields.fidJniData, 0); + if (lpJniStorage) { ALOGV("deleting pJniStorage: %p\n", lpJniStorage); delete lpJniStorage; @@ -451,24 +477,16 @@ static void android_media_AudioEffect_native_finalize(JNIEnv *env, jobject thiz } // ---------------------------------------------------------------------------- -static void android_media_AudioEffect_native_release(JNIEnv *env, jobject thiz) { - - // do everything a call to finalize would - android_media_AudioEffect_native_finalize(env, thiz); - // + reset the native resources in the Java object so any attempt to access - // them after a call to release fails. - env->SetLongField(thiz, fields.fidNativeAudioEffect, 0); - env->SetLongField(thiz, fields.fidJniData, 0); +static void android_media_AudioEffect_native_finalize(JNIEnv *env, jobject thiz) { + ALOGV("android_media_AudioEffect_native_finalize jobject: %p\n", thiz); + android_media_AudioEffect_native_release(env, thiz); } static jint android_media_AudioEffect_native_setEnabled(JNIEnv *env, jobject thiz, jboolean enabled) { - // retrieve the AudioEffect object - AudioEffect* lpAudioEffect = (AudioEffect *)env->GetLongField( - thiz, fields.fidNativeAudioEffect); - - if (lpAudioEffect == NULL) { + sp<AudioEffect> lpAudioEffect = getAudioEffect(env, thiz); + if (lpAudioEffect == 0) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioEffect pointer for enable()"); return AUDIOEFFECT_ERROR_NO_INIT; @@ -480,11 +498,8 @@ android_media_AudioEffect_native_setEnabled(JNIEnv *env, jobject thiz, jboolean static jboolean android_media_AudioEffect_native_getEnabled(JNIEnv *env, jobject thiz) { - // retrieve the AudioEffect object - AudioEffect* lpAudioEffect = (AudioEffect *)env->GetLongField( - thiz, fields.fidNativeAudioEffect); - - if (lpAudioEffect == NULL) { + sp<AudioEffect> lpAudioEffect = getAudioEffect(env, thiz); + if (lpAudioEffect == 0) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioEffect pointer for getEnabled()"); return JNI_FALSE; @@ -501,11 +516,8 @@ android_media_AudioEffect_native_getEnabled(JNIEnv *env, jobject thiz) static jboolean android_media_AudioEffect_native_hasControl(JNIEnv *env, jobject thiz) { - // retrieve the AudioEffect object - AudioEffect* lpAudioEffect = (AudioEffect *)env->GetLongField( - thiz, fields.fidNativeAudioEffect); - - if (lpAudioEffect == NULL) { + sp<AudioEffect> lpAudioEffect = getAudioEffect(env, thiz); + if (lpAudioEffect == 0) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioEffect pointer for hasControl()"); return JNI_FALSE; @@ -528,10 +540,8 @@ static jint android_media_AudioEffect_native_setParameter(JNIEnv *env, effect_param_t *p; int voffset; - AudioEffect* lpAudioEffect = (AudioEffect *) env->GetLongField(thiz, - fields.fidNativeAudioEffect); - - if (lpAudioEffect == NULL) { + sp<AudioEffect> lpAudioEffect = getAudioEffect(env, thiz); + if (lpAudioEffect == 0) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioEffect pointer for setParameter()"); return AUDIOEFFECT_ERROR_NO_INIT; @@ -591,10 +601,8 @@ android_media_AudioEffect_native_getParameter(JNIEnv *env, effect_param_t *p; int voffset; - AudioEffect* lpAudioEffect = (AudioEffect *) env->GetLongField(thiz, - fields.fidNativeAudioEffect); - - if (lpAudioEffect == NULL) { + sp<AudioEffect> lpAudioEffect = getAudioEffect(env, thiz); + if (lpAudioEffect == 0) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioEffect pointer for getParameter()"); return AUDIOEFFECT_ERROR_NO_INIT; @@ -657,11 +665,8 @@ static jint android_media_AudioEffect_native_command(JNIEnv *env, jobject thiz, jbyte* pReplyData = NULL; jint lStatus = AUDIOEFFECT_ERROR_BAD_VALUE; - // retrieve the AudioEffect object - AudioEffect* lpAudioEffect = (AudioEffect *) env->GetLongField(thiz, - fields.fidNativeAudioEffect); - - if (lpAudioEffect == NULL) { + sp<AudioEffect> lpAudioEffect = getAudioEffect(env, thiz); + if (lpAudioEffect == 0) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioEffect pointer for setParameter()"); return AUDIOEFFECT_ERROR_NO_INIT; diff --git a/media/jni/audioeffect/android_media_Visualizer.cpp b/media/jni/audioeffect/android_media_Visualizer.cpp index abc681e..6098b4a 100644 --- a/media/jni/audioeffect/android_media_Visualizer.cpp +++ b/media/jni/audioeffect/android_media_Visualizer.cpp @@ -102,14 +102,14 @@ struct visualizer_callback_cookie { }; // ---------------------------------------------------------------------------- -class visualizerJniStorage { +class VisualizerJniStorage { public: visualizer_callback_cookie mCallbackData; - visualizerJniStorage() { + VisualizerJniStorage() { } - ~visualizerJniStorage() { + ~VisualizerJniStorage() { } }; @@ -135,6 +135,7 @@ static jint translateError(int code) { } } +static Mutex sLock; // ---------------------------------------------------------------------------- static void ensureArraySize(JNIEnv *env, jbyteArray *array, uint32_t size) { @@ -227,15 +228,30 @@ static void captureCallback(void* user, } } -static Visualizer *getVisualizer(JNIEnv* env, jobject thiz) +// ---------------------------------------------------------------------------- + +static sp<Visualizer> getVisualizer(JNIEnv* env, jobject thiz) { - Visualizer *v = (Visualizer *)env->GetLongField( - thiz, fields.fidNativeVisualizer); - if (v == NULL) { - jniThrowException(env, "java/lang/IllegalStateException", - "Unable to retrieve Visualizer pointer"); + Mutex::Autolock l(sLock); + Visualizer* const v = + (Visualizer*)env->GetLongField(thiz, fields.fidNativeVisualizer); + return sp<Visualizer>(v); +} + +static sp<Visualizer> setVisualizer(JNIEnv* env, jobject thiz, + const sp<Visualizer>& v) +{ + Mutex::Autolock l(sLock); + sp<Visualizer> old = + (Visualizer*)env->GetLongField(thiz, fields.fidNativeVisualizer); + if (v.get()) { + v->incStrong((void*)setVisualizer); + } + if (old != 0) { + old->decStrong((void*)setVisualizer); } - return v; + env->SetLongField(thiz, fields.fidNativeVisualizer, (jlong)v.get()); + return old; } // ---------------------------------------------------------------------------- @@ -318,7 +334,7 @@ static void android_media_visualizer_effect_callback(int32_t event, void *info) { if ((event == AudioEffect::EVENT_ERROR) && (*((status_t*)info) == DEAD_OBJECT)) { - visualizerJniStorage* lpJniStorage = (visualizerJniStorage*)user; + VisualizerJniStorage* lpJniStorage = (VisualizerJniStorage*)user; visualizer_callback_cookie* callbackInfo = &lpJniStorage->mCallbackData; JNIEnv *env = AndroidRuntime::getJNIEnv(); @@ -336,14 +352,16 @@ android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_th jint sessionId, jintArray jId, jstring opPackageName) { ALOGV("android_media_visualizer_native_setup"); - visualizerJniStorage* lpJniStorage = NULL; + VisualizerJniStorage* lpJniStorage = NULL; int lStatus = VISUALIZER_ERROR_NO_MEMORY; - Visualizer* lpVisualizer = NULL; + sp<Visualizer> lpVisualizer; jint* nId = NULL; ScopedUtfChars opPackageNameStr(env, opPackageName); - lpJniStorage = new visualizerJniStorage(); + setVisualizer(env, thiz, 0); + + lpJniStorage = new VisualizerJniStorage(); if (lpJniStorage == NULL) { ALOGE("setup: Error creating JNI Storage"); goto setup_failure; @@ -371,7 +389,7 @@ android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_th android_media_visualizer_effect_callback, lpJniStorage, sessionId); - if (lpVisualizer == NULL) { + if (lpVisualizer == 0) { ALOGE("Error creating Visualizer"); goto setup_failure; } @@ -392,7 +410,7 @@ android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_th env->ReleasePrimitiveArrayCritical(jId, nId, 0); nId = NULL; - env->SetLongField(thiz, fields.fidNativeVisualizer, (jlong)lpVisualizer); + setVisualizer(env, thiz, lpVisualizer); env->SetLongField(thiz, fields.fidJniData, (jlong)lpJniStorage); @@ -405,12 +423,9 @@ setup_failure: env->ReleasePrimitiveArrayCritical(jId, nId, 0); } - if (lpVisualizer) { - delete lpVisualizer; - } - env->SetLongField(thiz, fields.fidNativeVisualizer, 0); - if (lpJniStorage) { + env->DeleteGlobalRef(lpJniStorage->mCallbackData.visualizer_class); + env->DeleteGlobalRef(lpJniStorage->mCallbackData.visualizer_ref); delete lpJniStorage; } env->SetLongField(thiz, fields.fidJniData, 0); @@ -419,49 +434,44 @@ setup_failure: } // ---------------------------------------------------------------------------- -static void android_media_visualizer_native_finalize(JNIEnv *env, jobject thiz) { - ALOGV("android_media_visualizer_native_finalize jobject: %p\n", thiz); - - // delete the Visualizer object - Visualizer* lpVisualizer = (Visualizer *)env->GetLongField( - thiz, fields.fidNativeVisualizer); - if (lpVisualizer) { - ALOGV("deleting Visualizer: %p\n", lpVisualizer); - delete lpVisualizer; +static void android_media_visualizer_native_release(JNIEnv *env, jobject thiz) { + sp<Visualizer> lpVisualizer = setVisualizer(env, thiz, 0); + if (lpVisualizer == 0) { + return; } // delete the JNI data - visualizerJniStorage* lpJniStorage = (visualizerJniStorage *)env->GetLongField( - thiz, fields.fidJniData); + VisualizerJniStorage* lpJniStorage = + (VisualizerJniStorage *)env->GetLongField(thiz, fields.fidJniData); + + // reset the native resources in the Java object so any attempt to access + // them after a call to release fails. + env->SetLongField(thiz, fields.fidJniData, 0); + if (lpJniStorage) { ALOGV("deleting pJniStorage: %p\n", lpJniStorage); delete lpJniStorage; } } -// ---------------------------------------------------------------------------- -static void android_media_visualizer_native_release(JNIEnv *env, jobject thiz) { - - // do everything a call to finalize would - android_media_visualizer_native_finalize(env, thiz); - // + reset the native resources in the Java object so any attempt to access - // them after a call to release fails. - env->SetLongField(thiz, fields.fidNativeVisualizer, 0); - env->SetLongField(thiz, fields.fidJniData, 0); +static void android_media_visualizer_native_finalize(JNIEnv *env, jobject thiz) { + ALOGV("android_media_visualizer_native_finalize jobject: %p\n", thiz); + android_media_visualizer_native_release(env, thiz); } +// ---------------------------------------------------------------------------- static jint android_media_visualizer_native_setEnabled(JNIEnv *env, jobject thiz, jboolean enabled) { - Visualizer* lpVisualizer = getVisualizer(env, thiz); - if (lpVisualizer == NULL) { + sp<Visualizer> lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == 0) { return VISUALIZER_ERROR_NO_INIT; } jint retVal = translateError(lpVisualizer->setEnabled(enabled)); if (!enabled) { - visualizerJniStorage* lpJniStorage = (visualizerJniStorage *)env->GetLongField( + VisualizerJniStorage* lpJniStorage = (VisualizerJniStorage *)env->GetLongField( thiz, fields.fidJniData); if (NULL != lpJniStorage) @@ -474,8 +484,8 @@ android_media_visualizer_native_setEnabled(JNIEnv *env, jobject thiz, jboolean e static jboolean android_media_visualizer_native_getEnabled(JNIEnv *env, jobject thiz) { - Visualizer* lpVisualizer = getVisualizer(env, thiz); - if (lpVisualizer == NULL) { + sp<Visualizer> lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == 0) { return JNI_FALSE; } @@ -507,8 +517,8 @@ android_media_visualizer_native_getMaxCaptureRate(JNIEnv* /* env */, jobject /* static jint android_media_visualizer_native_setCaptureSize(JNIEnv *env, jobject thiz, jint size) { - Visualizer* lpVisualizer = getVisualizer(env, thiz); - if (lpVisualizer == NULL) { + sp<Visualizer> lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == 0) { return VISUALIZER_ERROR_NO_INIT; } @@ -518,8 +528,8 @@ android_media_visualizer_native_setCaptureSize(JNIEnv *env, jobject thiz, jint s static jint android_media_visualizer_native_getCaptureSize(JNIEnv *env, jobject thiz) { - Visualizer* lpVisualizer = getVisualizer(env, thiz); - if (lpVisualizer == NULL) { + sp<Visualizer> lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == 0) { return -1; } return (jint) lpVisualizer->getCaptureSize(); @@ -528,8 +538,8 @@ android_media_visualizer_native_getCaptureSize(JNIEnv *env, jobject thiz) static jint android_media_visualizer_native_setScalingMode(JNIEnv *env, jobject thiz, jint mode) { - Visualizer* lpVisualizer = getVisualizer(env, thiz); - if (lpVisualizer == NULL) { + sp<Visualizer> lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == 0) { return VISUALIZER_ERROR_NO_INIT; } @@ -539,8 +549,8 @@ android_media_visualizer_native_setScalingMode(JNIEnv *env, jobject thiz, jint m static jint android_media_visualizer_native_getScalingMode(JNIEnv *env, jobject thiz) { - Visualizer* lpVisualizer = getVisualizer(env, thiz); - if (lpVisualizer == NULL) { + sp<Visualizer> lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == 0) { return -1; } return (jint)lpVisualizer->getScalingMode(); @@ -549,8 +559,8 @@ android_media_visualizer_native_getScalingMode(JNIEnv *env, jobject thiz) static jint android_media_visualizer_native_setMeasurementMode(JNIEnv *env, jobject thiz, jint mode) { - Visualizer* lpVisualizer = getVisualizer(env, thiz); - if (lpVisualizer == NULL) { + sp<Visualizer> lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == 0) { return VISUALIZER_ERROR_NO_INIT; } return translateError(lpVisualizer->setMeasurementMode(mode)); @@ -559,8 +569,8 @@ android_media_visualizer_native_setMeasurementMode(JNIEnv *env, jobject thiz, ji static jint android_media_visualizer_native_getMeasurementMode(JNIEnv *env, jobject thiz) { - Visualizer* lpVisualizer = getVisualizer(env, thiz); - if (lpVisualizer == NULL) { + sp<Visualizer> lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == 0) { return MEASUREMENT_MODE_NONE; } return lpVisualizer->getMeasurementMode(); @@ -569,8 +579,8 @@ android_media_visualizer_native_getMeasurementMode(JNIEnv *env, jobject thiz) static jint android_media_visualizer_native_getSamplingRate(JNIEnv *env, jobject thiz) { - Visualizer* lpVisualizer = getVisualizer(env, thiz); - if (lpVisualizer == NULL) { + sp<Visualizer> lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == 0) { return -1; } return (jint) lpVisualizer->getSamplingRate(); @@ -579,8 +589,8 @@ android_media_visualizer_native_getSamplingRate(JNIEnv *env, jobject thiz) static jint android_media_visualizer_native_getWaveForm(JNIEnv *env, jobject thiz, jbyteArray jWaveform) { - Visualizer* lpVisualizer = getVisualizer(env, thiz); - if (lpVisualizer == NULL) { + sp<Visualizer> lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == 0) { return VISUALIZER_ERROR_NO_INIT; } @@ -597,8 +607,8 @@ android_media_visualizer_native_getWaveForm(JNIEnv *env, jobject thiz, jbyteArra static jint android_media_visualizer_native_getFft(JNIEnv *env, jobject thiz, jbyteArray jFft) { - Visualizer* lpVisualizer = getVisualizer(env, thiz); - if (lpVisualizer == NULL) { + sp<Visualizer> lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == 0) { return VISUALIZER_ERROR_NO_INIT; } @@ -616,8 +626,8 @@ android_media_visualizer_native_getFft(JNIEnv *env, jobject thiz, jbyteArray jFf static jint android_media_visualizer_native_getPeakRms(JNIEnv *env, jobject thiz, jobject jPeakRmsObj) { - Visualizer* lpVisualizer = getVisualizer(env, thiz); - if (lpVisualizer == NULL) { + sp<Visualizer> lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == 0) { return VISUALIZER_ERROR_NO_INIT; } int32_t measurements[2]; @@ -635,11 +645,11 @@ android_media_visualizer_native_getPeakRms(JNIEnv *env, jobject thiz, jobject jP static jint android_media_setPeriodicCapture(JNIEnv *env, jobject thiz, jint rate, jboolean jWaveform, jboolean jFft) { - Visualizer* lpVisualizer = getVisualizer(env, thiz); - if (lpVisualizer == NULL) { + sp<Visualizer> lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == 0) { return VISUALIZER_ERROR_NO_INIT; } - visualizerJniStorage* lpJniStorage = (visualizerJniStorage *)env->GetLongField(thiz, + VisualizerJniStorage* lpJniStorage = (VisualizerJniStorage *)env->GetLongField(thiz, fields.fidJniData); if (lpJniStorage == NULL) { return VISUALIZER_ERROR_NO_INIT; diff --git a/media/jni/soundpool/android_media_SoundPool.cpp b/media/jni/soundpool/android_media_SoundPool.cpp index fc4cf05..ab3e340 100644 --- a/media/jni/soundpool/android_media_SoundPool.cpp +++ b/media/jni/soundpool/android_media_SoundPool.cpp @@ -286,7 +286,7 @@ static JNINativeMethod gMethods[] = { "(Ljava/lang/Object;ILjava/lang/Object;)I", (void*)android_media_SoundPool_native_setup }, - { "release", + { "native_release", "()V", (void*)android_media_SoundPool_release } diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java index bcfcbf3..8f7d6ac 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java @@ -60,6 +60,7 @@ public class CodecTest { private static boolean onPrepareSuccess = false; public static boolean onCompleteSuccess = false; public static boolean mPlaybackError = false; + public static boolean mFailedToCompleteWithNoError = true; public static int mMediaInfoUnknownCount = 0; public static int mMediaInfoVideoTrackLaggingCount = 0; public static int mMediaInfoBadInterleavingCount = 0; @@ -801,6 +802,7 @@ public class CodecTest { mMediaInfoNotSeekableCount = 0; mMediaInfoMetdataUpdateCount = 0; mPlaybackError = false; + mFailedToCompleteWithNoError = true; String testResult; initializeMessageLooper(); @@ -843,6 +845,9 @@ public class CodecTest { } catch (Exception e) { Log.v(TAG, "playMediaSamples:" + e.getMessage()); } + // Check if playback state is unknown (neither completed nor erroneous) unless + // it's not interrupted in the middle. If true, that is an exceptional case to investigate. + mFailedToCompleteWithNoError = !(onCompleteSuccess || mPlaybackError); return onCompleteSuccess; } } diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStressTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStressTest.java index e289812..4221f1b 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStressTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStressTest.java @@ -65,6 +65,7 @@ public class MediaPlayerStressTest extends ActivityInstrumentationTestCase2<Medi private int mTotalBadInterleaving = 0; private int mTotalNotSeekable = 0; private int mTotalMetaDataUpdate = 0; + private int mTotalFailedToCompleteWithNoError = 0; //Test result output file private static final String PLAYBACK_RESULT = "PlaybackTestResult.txt"; @@ -78,6 +79,8 @@ public class MediaPlayerStressTest extends ActivityInstrumentationTestCase2<Medi output.write(" Bad Interleaving: " + CodecTest.mMediaInfoBadInterleavingCount); output.write(" Not Seekable: " + CodecTest.mMediaInfoNotSeekableCount); output.write(" Info Meta data update: " + CodecTest.mMediaInfoMetdataUpdateCount); + output.write(" Failed To Complete With No Error: " + + CodecTest.mFailedToCompleteWithNoError); output.write("\n"); } @@ -90,16 +93,21 @@ public class MediaPlayerStressTest extends ActivityInstrumentationTestCase2<Medi output.write("Total Bad Interleaving: " + mTotalBadInterleaving + "\n"); output.write("Total Not Seekable: " + mTotalNotSeekable + "\n"); output.write("Total Info Meta data update: " + mTotalMetaDataUpdate + "\n"); + output.write("Total Failed To Complete With No Error: " + + mTotalFailedToCompleteWithNoError); output.write("\n"); } private void updateTestResult(){ - if (CodecTest.onCompleteSuccess){ + if (CodecTest.onCompleteSuccess) { mTotalComplete++; } - else if (CodecTest.mPlaybackError){ + else if (CodecTest.mPlaybackError) { mTotalPlaybackError++; } + else if (CodecTest.mFailedToCompleteWithNoError) { + mTotalFailedToCompleteWithNoError++; + } mTotalInfoUnknown += CodecTest.mMediaInfoUnknownCount; mTotalVideoTrackLagging += CodecTest.mMediaInfoVideoTrackLaggingCount; mTotalBadInterleaving += CodecTest.mMediaInfoBadInterleavingCount; @@ -149,4 +157,4 @@ public class MediaPlayerStressTest extends ActivityInstrumentationTestCase2<Medi assertTrue("testMediaSamples", testResult); } } -}
\ No newline at end of file +} |
