diff options
Diffstat (limited to 'media')
38 files changed, 582 insertions, 388 deletions
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java index ee6d661..c29ec0d 100644 --- a/media/java/android/media/AudioFormat.java +++ b/media/java/android/media/AudioFormat.java @@ -18,16 +18,19 @@ package android.media; import android.annotation.IntDef; import android.annotation.NonNull; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; /** - * The <code>AudioFormat</code> class is used to access a number of audio format and + * The {@link AudioFormat} class is used to access a number of audio format and * channel configuration constants. They are for instance used * in {@link AudioTrack} and {@link AudioRecord}, as valid values in individual parameters of * constructors like {@link AudioTrack#AudioTrack(int, int, int, int, int, int)}, where the fourth * parameter is one of the <code>AudioFormat.ENCODING_*</code> constants. + * The <code>AudioFormat</code> constants are also used in {@link MediaFormat} to specify + * audio related values commonly used in media, such as for {@link MediaFormat#KEY_CHANNEL_MASK}. * <p>The {@link AudioFormat.Builder} class can be used to create instances of * the <code>AudioFormat</code> format class. * Refer to @@ -39,6 +42,9 @@ import java.util.Arrays; * <li><a href="#encoding">encoding</a> * <li><a href="#channelMask">channel masks</a> * </ol> + * <p>Closely associated with the <code>AudioFormat</code> is the notion of an + * <a href="#audioFrame">audio frame</a>, which is used throughout the documentation + * to represent the minimum size complete unit of audio data. * * <h4 id="sampleRate">Sample rate</h4> * <p>Expressed in Hz, the sample rate in an <code>AudioFormat</code> instance expresses the number @@ -48,10 +54,69 @@ import java.util.Arrays; * can be played on a device operating at a sample rate of 48000Hz; the sample rate conversion is * automatically handled by the platform, it will not play at 6x speed. * + * <p>As of API {@link android.os.Build.VERSION_CODES#MNC}, + * sample rates up to 192kHz are supported + * for <code>AudioRecord</code> and <code>AudioTrack</code>, with sample rate conversion + * performed as needed. + * To improve efficiency and avoid lossy conversions, it is recommended to match the sample rate + * for <code>AudioRecord</code> and <code>AudioTrack</code> to the endpoint device + * sample rate, and limit the sample rate to no more than 48kHz unless there are special + * device capabilities that warrant a higher rate. + * * <h4 id="encoding">Encoding</h4> - * <p>For PCM audio, audio encoding is used to describe the bit representation of an audio data - * sample; for example, the size as 8 bit, 16 bit, and the representation as integer or float. - * <br>For compressed formats, audio encoding is used to describe the compression scheme being used. + * <p>Audio encoding is used to describe the bit representation of audio data, which can be + * either linear PCM or compressed audio, such as AC3 or DTS. + * <p>For linear PCM, the audio encoding describes the sample size, 8 bits, 16 bits, or 32 bits, + * and the sample representation, integer or float. + * <ul> + * <li> {@link #ENCODING_PCM_8BIT}: The audio sample is a 8 bit unsigned integer in the + * range [0, 255], with a 128 offset for zero. This is typically stored as a Java byte in a + * byte array or ByteBuffer. Since the Java byte is <em>signed</em>, + * be careful with math operations and conversions as the most significant bit is inverted. + * </li> + * <li> {@link #ENCODING_PCM_16BIT}: The audio sample is a 16 bit signed integer + * typically stored as a Java short in a short array, but when the short + * is stored in a ByteBuffer, it is native endian (as compared to the default Java big endian). + * The short has full range from [-32768, 32767], + * and is sometimes interpreted as fixed point Q.15 data. + * </li> + * <li> {@link #ENCODING_PCM_FLOAT}: Introduced in + * API {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this encoding specifies that + * the audio sample is a 32 bit IEEE single precision float. The sample can be + * manipulated as a Java float in a float array, though within a ByteBuffer + * it is stored in native endian byte order. + * The nominal range of <code>ENCODING_PCM_FLOAT</code> audio data is [-1.0, 1.0]. + * It is implementation dependent whether the positive maximum of 1.0 is included + * in the interval. Values outside of the nominal range are clamped before + * sending to the endpoint device. Beware that + * the handling of NaN is undefined; subnormals may be treated as zero; and + * infinities are generally clamped just like other values for <code>AudioTrack</code> + * – try to avoid infinities because they can easily generate a NaN. + * <br> + * To achieve higher audio bit depth than a signed 16 bit integer short, + * it is recommended to use <code>ENCODING_PCM_FLOAT</code> for audio capture, processing, + * and playback. + * Floats are efficiently manipulated by modern CPUs, + * have greater precision than 24 bit signed integers, + * and have greater dynamic range than 32 bit signed integers. + * <code>AudioRecord</code> as of API {@link android.os.Build.VERSION_CODES#MNC} and + * <code>AudioTrack</code> as of API {@link android.os.Build.VERSION_CODES#LOLLIPOP} + * support <code>ENCODING_PCM_FLOAT</code>. + * </li> + * </ul> + * <p>For compressed audio, the encoding specifies the method of compression, + * for example {@link #ENCODING_AC3} and {@link #ENCODING_DTS}. The compressed + * audio data is typically stored as bytes in + * a byte array or ByteBuffer. When a compressed audio encoding is specified + * for an <code>AudioTrack</code>, it creates a direct (non-mixed) track + * for output to an endpoint (such as HDMI) capable of decoding the compressed audio. + * For (most) other endpoints, which are not capable of decoding such compressed audio, + * you will need to decode the data first, typically by creating a {@link MediaCodec}. + * Alternatively, one may use {@link MediaPlayer} for playback of compressed + * audio files or streams. + * <p>When compressed audio is sent out through a direct <code>AudioTrack</code>, + * it need not be written in exact multiples of the audio access unit; + * this differs from <code>MediaCodec</code> input buffers. * * <h4 id="channelMask">Channel mask</h4> * <p>Channel masks are used in <code>AudioTrack</code> and <code>AudioRecord</code> to describe @@ -127,6 +192,22 @@ import java.util.Arrays; * about position it corresponds to, in which case the channel index mask is <code>0xC</code>. * Multichannel <code>AudioRecord</code> sessions should use channel index masks. * </ul> + * <h4 id="audioFrame">Audio Frame</h4> + * <p>For linear PCM, an audio frame consists of a set of samples captured at the same time, + * whose count and + * channel association are given by the <a href="#channelMask">channel mask</a>, + * and whose sample contents are specified by the <a href="#encoding">encoding</a>. + * For example, a stereo 16 bit PCM frame consists of + * two 16 bit linear PCM samples, with a frame size of 4 bytes. + * For compressed audio, an audio frame may alternately + * refer to an access unit of compressed data bytes that is logically grouped together for + * decoding and bitstream access (e.g. {@link MediaCodec}), + * or a single byte of compressed data (e.g. {@link AudioTrack#getBufferSizeInFrames() + * AudioTrack.getBufferSizeInFrames()}), + * or the linear PCM frame result from decoding the compressed data + * (e.g.{@link AudioTrack#getPlaybackHeadPosition() + * AudioTrack.getPlaybackHeadPosition()}), + * depending on the context where audio frame is used. */ public class AudioFormat { diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 316ccf6..33db9cf 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -38,7 +38,6 @@ import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.RemoteException; -import android.os.SystemProperties; import android.os.SystemClock; import android.os.ServiceManager; import android.provider.Settings; @@ -69,16 +68,6 @@ public class AudioManager { private static final AudioPortEventHandler sAudioPortEventHandler = new AudioPortEventHandler(); /** - * System properties for whether the default microphone and speaker paths support - * near-ultrasound frequencies (range of 18 - 21 kHz). - */ - private static final String SYSTEM_PROPERTY_MIC_NEAR_ULTRASOUND = - "persist.audio.mic.ultrasound"; - private static final String SYSTEM_PROPERTY_SPEAKER_NEAR_ULTRASOUND = - "persist.audio.spkr.ultrasound"; - private static final String DEFAULT_RESULT_FALSE_STRING = "false"; - - /** * Broadcast intent, a hint for applications that audio is about to become * 'noisy' due to a change in audio outputs. For example, this intent may * be sent when a wired headset is unplugged, or when an A2DP audio @@ -3213,11 +3202,13 @@ public class AudioManager { int outputFramesPerBuffer = AudioSystem.getPrimaryOutputFrameCount(); return outputFramesPerBuffer > 0 ? Integer.toString(outputFramesPerBuffer) : null; } else if (PROPERTY_SUPPORT_MIC_NEAR_ULTRASOUND.equals(key)) { - return SystemProperties.get(SYSTEM_PROPERTY_MIC_NEAR_ULTRASOUND, - DEFAULT_RESULT_FALSE_STRING); + // Will throw a RuntimeException Resources.NotFoundException if this config value is + // not found. + return String.valueOf(getContext().getResources().getBoolean( + com.android.internal.R.bool.config_supportMicNearUltrasound)); } else if (PROPERTY_SUPPORT_SPEAKER_NEAR_ULTRASOUND.equals(key)) { - return SystemProperties.get(SYSTEM_PROPERTY_SPEAKER_NEAR_ULTRASOUND, - DEFAULT_RESULT_FALSE_STRING); + return String.valueOf(getContext().getResources().getBoolean( + com.android.internal.R.bool.config_supportSpeakerNearUltrasound)); } else { // null or unknown key return null; 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 f76189c..f395cb3 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -1990,9 +1990,24 @@ public class AudioTrack * The dead object error code is not returned if some data was successfully transferred. * In this case, the error is returned at the next write(). */ - public int write(ByteBuffer audioData, int sizeInBytes, + public int write(@NonNull ByteBuffer audioData, int sizeInBytes, @WriteMode int writeMode, long timestamp) { + if (mState == STATE_UNINITIALIZED) { + Log.e(TAG, "AudioTrack.write() called in invalid state STATE_UNINITIALIZED"); + return ERROR_INVALID_OPERATION; + } + + if ((writeMode != WRITE_BLOCKING) && (writeMode != WRITE_NON_BLOCKING)) { + Log.e(TAG, "AudioTrack.write() called with invalid blocking mode"); + return ERROR_BAD_VALUE; + } + + if (mDataLoadMode != MODE_STREAM) { + Log.e(TAG, "AudioTrack.write() with timestamp called for non-streaming mode track"); + return ERROR_INVALID_OPERATION; + } + if ((mAttributes.getFlags() & AudioAttributes.FLAG_HW_AV_SYNC) == 0) { Log.d(TAG, "AudioTrack.write() called on a regular AudioTrack. Ignoring pts..."); return write(audioData, sizeInBytes, writeMode); @@ -2229,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/ClosedCaptionRenderer.java b/media/java/android/media/ClosedCaptionRenderer.java index e3680e9..8403c1c 100644 --- a/media/java/android/media/ClosedCaptionRenderer.java +++ b/media/java/android/media/ClosedCaptionRenderer.java @@ -1044,42 +1044,26 @@ class CCParser { } /** - * @hide - * - * MutableBackgroundColorSpan - * - * This is a mutable version of BackgroundSpan to facilitate text - * rendering with edge styles. + * Mutable version of BackgroundSpan to facilitate text rendering with edge + * styles. * + * @hide */ -class MutableBackgroundColorSpan extends CharacterStyle - implements UpdateAppearance, ParcelableSpan { +class MutableBackgroundColorSpan extends CharacterStyle implements UpdateAppearance { private int mColor; public MutableBackgroundColorSpan(int color) { mColor = color; } - public MutableBackgroundColorSpan(Parcel src) { - mColor = src.readInt(); - } + public void setBackgroundColor(int color) { mColor = color; } + public int getBackgroundColor() { return mColor; } - @Override - public int getSpanTypeId() { - return TextUtils.BACKGROUND_COLOR_SPAN; - } - @Override - public int describeContents() { - return 0; - } - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mColor); - } + @Override public void updateDrawState(TextPaint ds) { ds.bgColor = mColor; diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index 9db35fc..6bf5721 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -57,6 +57,21 @@ 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 + */ + public static final String TAG_SUBSECTIME = "SubSecTime"; /** * The altitude (in meters) based on the reference in TAG_GPS_ALTITUDE_REF. @@ -346,7 +361,7 @@ public class ExifInterface { } /** - * Returns number of milliseconds since Jan. 1, 1970, midnight. + * Returns number of milliseconds since Jan. 1, 1970, midnight local time. * Returns -1 if the date time information if not available. * @hide */ @@ -356,9 +371,24 @@ public class ExifInterface { ParsePosition pos = new ParsePosition(0); try { + // The exif field is in local time. Parsing it as if it is UTC will yield time + // since 1/1/1970 local time Date datetime = sFormatter.parse(dateTimeString, pos); if (datetime == null) return -1; - return datetime.getTime(); + long msecs = datetime.getTime(); + + String subSecs = mAttributes.get(TAG_SUBSECTIME); + if (subSecs != null) { + try { + long sub = Long.valueOf(subSecs); + while (sub > 1000) { + sub /= 10; + } + msecs += sub; + } catch (NumberFormatException e) { + } + } + return msecs; } catch (IllegalArgumentException ex) { return -1; } @@ -375,7 +405,6 @@ public class ExifInterface { if (date == null || time == null) return -1; String dateTimeString = date + ' ' + time; - if (dateTimeString == null) return -1; ParsePosition pos = new ParsePosition(0); try { diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java index e18e9a3..045216b 100644 --- a/media/java/android/media/Image.java +++ b/media/java/android/media/Image.java @@ -228,7 +228,9 @@ public abstract class Image implements AutoCloseable { if (cropRect != null) { cropRect = new Rect(cropRect); // make a copy - cropRect.intersect(0, 0, getWidth(), getHeight()); + if (!cropRect.intersect(0, 0, getWidth(), getHeight())) { + cropRect.setEmpty(); + } } mCropRect = cropRect; } diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index 0e67daa..b2fa0ac 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -106,9 +106,6 @@ public final class MediaFormat { public static final String MIMETYPE_AUDIO_FLAC = "audio/flac"; public static final String MIMETYPE_AUDIO_MSGSM = "audio/gsm"; public static final String MIMETYPE_AUDIO_AC3 = "audio/ac3"; - /** - * @hide - */ public static final String MIMETYPE_AUDIO_EAC3 = "audio/eac3"; /** diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index 668f80a..13714d3 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -77,7 +77,6 @@ import java.util.Map; import java.util.Scanner; import java.util.Set; import java.util.Vector; -import java.util.concurrent.atomic.AtomicBoolean; import java.lang.ref.WeakReference; /** @@ -624,9 +623,6 @@ public class MediaPlayer implements SubtitleController.Listener private int mUsage = -1; private boolean mBypassInterruptionPolicy; - // use AtomicBoolean instead of boolean so we can use the same member both as a flag and a lock. - private AtomicBoolean mPreparing = new AtomicBoolean(); - /** * Default constructor. Consider using one of the create() methods for * synchronously instantiating a MediaPlayer from a Uri or resource. @@ -1166,10 +1162,6 @@ public class MediaPlayer implements SubtitleController.Listener * @throws IllegalStateException if it is called in an invalid state */ public void prepare() throws IOException, IllegalStateException { - // The synchronous version of prepare also recieves a MEDIA_PREPARED message. - synchronized (mPreparing) { - mPreparing.set(true); - } _prepare(); scanInternalSubtitleTracks(); } @@ -1186,14 +1178,7 @@ public class MediaPlayer implements SubtitleController.Listener * * @throws IllegalStateException if it is called in an invalid state */ - public void prepareAsync() throws IllegalStateException { - synchronized (mPreparing) { - mPreparing.set(true); - } - _prepareAsync(); - } - - private native void _prepareAsync() throws IllegalStateException; + public native void prepareAsync() throws IllegalStateException; /** * Starts or resumes playback. If playback had previously been paused, @@ -1244,9 +1229,6 @@ public class MediaPlayer implements SubtitleController.Listener * initialized. */ public void stop() throws IllegalStateException { - synchronized (mPreparing) { - mPreparing.set(false); - } stayAwake(false); _stop(); } @@ -1676,9 +1658,6 @@ public class MediaPlayer implements SubtitleController.Listener * at the same time. */ public void release() { - synchronized (mPreparing) { - mPreparing.set(false); - } stayAwake(false); updateSurfaceScreenOn(); mOnPreparedListener = null; @@ -1705,9 +1684,6 @@ public class MediaPlayer implements SubtitleController.Listener * data source and calling prepare(). */ public void reset() { - synchronized (mPreparing) { - mPreparing.set(false); - } mSelectedSubtitleTrackIndex = -1; synchronized(mOpenSubtitleSources) { for (final InputStream is: mOpenSubtitleSources) { @@ -2828,11 +2804,15 @@ public class MediaPlayer implements SubtitleController.Listener } switch(msg.what) { case MEDIA_PREPARED: - synchronized (mPreparing) { - if (mPreparing.get()) { - scanInternalSubtitleTracks(); - mPreparing.set(false); - } + try { + scanInternalSubtitleTracks(); + } catch (RuntimeException e) { + // send error message instead of crashing; + // send error message instead of inlining a call to onError + // to avoid code duplication. + Message msg2 = obtainMessage( + MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null); + sendMessage(msg2); } if (mOnPreparedListener != null) mOnPreparedListener.onPrepared(mMediaPlayer); @@ -2908,7 +2888,13 @@ public class MediaPlayer implements SubtitleController.Listener Log.i(TAG, "Info (" + msg.arg1 + "," + msg.arg2 + ")"); break; case MEDIA_INFO_METADATA_UPDATE: - scanInternalSubtitleTracks(); + try { + scanInternalSubtitleTracks(); + } catch (RuntimeException e) { + Message msg2 = obtainMessage( + MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null); + sendMessage(msg2); + } // fall through case MEDIA_INFO_EXTERNAL_METADATA_UPDATE: diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java index 8441541..166ff38 100644 --- a/media/java/android/media/Ringtone.java +++ b/media/java/android/media/Ringtone.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.res.AssetFileDescriptor; import android.content.res.Resources.NotFoundException; import android.database.Cursor; +import android.media.MediaPlayer.OnCompletionListener; import android.net.Uri; import android.os.Binder; import android.os.RemoteException; @@ -29,6 +30,7 @@ import android.provider.Settings; import android.util.Log; import java.io.IOException; +import java.util.ArrayList; /** * Ringtone provides a quick method for playing a ringtone, notification, or @@ -49,6 +51,9 @@ public class Ringtone { MediaStore.Audio.Media.TITLE }; + // keep references on active Ringtones until stopped or completion listener called. + private static final ArrayList<Ringtone> sActiveRingtones = new ArrayList<Ringtone>(); + private final Context mContext; private final AudioManager mAudioManager; @@ -62,6 +67,7 @@ public class Ringtone { private final Binder mRemoteToken; private MediaPlayer mLocalPlayer; + private final MyOnCompletionListener mCompletionListener = new MyOnCompletionListener(); private Uri mUri; private String mTitle; @@ -247,7 +253,7 @@ public class Ringtone { // (typically because ringer mode is silent). if (mAudioManager.getStreamVolume( AudioAttributes.toLegacyStreamType(mAudioAttributes)) != 0) { - mLocalPlayer.start(); + startLocalPlayer(); } } else if (mAllowRemote && (mRemotePlayer != null)) { final Uri canonicalUri = mUri.getCanonicalUri(); @@ -285,7 +291,21 @@ public class Ringtone { mLocalPlayer.reset(); mLocalPlayer.release(); mLocalPlayer = null; + synchronized (sActiveRingtones) { + sActiveRingtones.remove(this); + } + } + } + + private void startLocalPlayer() { + if (mLocalPlayer == null) { + return; } + synchronized (sActiveRingtones) { + sActiveRingtones.add(this); + } + mLocalPlayer.setOnCompletionListener(mCompletionListener); + mLocalPlayer.start(); } /** @@ -330,7 +350,7 @@ public class Ringtone { } mLocalPlayer.setAudioAttributes(mAudioAttributes); mLocalPlayer.prepare(); - mLocalPlayer.start(); + startLocalPlayer(); afd.close(); return true; } else { @@ -352,4 +372,20 @@ public class Ringtone { void setTitle(String title) { mTitle = title; } + + @Override + protected void finalize() { + if (mLocalPlayer != null) { + mLocalPlayer.release(); + } + } + + class MyOnCompletionListener implements MediaPlayer.OnCompletionListener { + public void onCompletion(MediaPlayer mp) + { + synchronized (sActiveRingtones) { + sActiveRingtones.remove(Ringtone.this); + } + } + } } diff --git a/media/java/android/media/Utils.java b/media/java/android/media/Utils.java index 9e01e65..2cd3c43 100644 --- a/media/java/android/media/Utils.java +++ b/media/java/android/media/Utils.java @@ -209,6 +209,9 @@ class Utils { } static int parseIntSafely(Object o, int fallback) { + if (o == null) { + return fallback; + } try { String s = (String)o; return Integer.parseInt(s); diff --git a/media/java/android/media/midi/IMidiDeviceOpenCallback.aidl b/media/java/android/media/midi/IMidiDeviceOpenCallback.aidl new file mode 100644 index 0000000..6e3dbbf --- /dev/null +++ b/media/java/android/media/midi/IMidiDeviceOpenCallback.aidl @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.midi; + +import android.media.midi.IMidiDeviceServer; + +/** @hide */ +oneway interface IMidiDeviceOpenCallback +{ + void onDeviceOpened(in IMidiDeviceServer server, IBinder token); +} diff --git a/media/java/android/media/midi/IMidiDeviceServer.aidl b/media/java/android/media/midi/IMidiDeviceServer.aidl index 96d12fd..e30796d 100644 --- a/media/java/android/media/midi/IMidiDeviceServer.aidl +++ b/media/java/android/media/midi/IMidiDeviceServer.aidl @@ -25,6 +25,7 @@ interface IMidiDeviceServer ParcelFileDescriptor openInputPort(IBinder token, int portNumber); ParcelFileDescriptor openOutputPort(IBinder token, int portNumber); void closePort(IBinder token); + void closeDevice(); // connects the input port pfd to the specified output port void connectPorts(IBinder token, in ParcelFileDescriptor pfd, int outputPortNumber); diff --git a/media/java/android/media/midi/IMidiManager.aidl b/media/java/android/media/midi/IMidiManager.aidl index fcd4aff..d3c8e0a 100644 --- a/media/java/android/media/midi/IMidiManager.aidl +++ b/media/java/android/media/midi/IMidiManager.aidl @@ -16,7 +16,10 @@ package android.media.midi; +import android.bluetooth.BluetoothDevice; import android.media.midi.IMidiDeviceListener; +import android.media.midi.IMidiDeviceOpenCallback; +import android.media.midi.IMidiDeviceServer; import android.media.midi.IMidiDeviceServer; import android.media.midi.MidiDeviceInfo; import android.media.midi.MidiDeviceStatus; @@ -29,11 +32,13 @@ interface IMidiManager MidiDeviceInfo[] getDevices(); // for device creation & removal notifications - void registerListener(IBinder token, in IMidiDeviceListener listener); - void unregisterListener(IBinder token, in IMidiDeviceListener listener); + void registerListener(IBinder clientToken, in IMidiDeviceListener listener); + void unregisterListener(IBinder clientToken, in IMidiDeviceListener listener); - // for opening built-in MIDI devices - IMidiDeviceServer openDevice(IBinder token, in MidiDeviceInfo device); + void openDevice(IBinder clientToken, in MidiDeviceInfo device, in IMidiDeviceOpenCallback callback); + void openBluetoothDevice(IBinder clientToken, in BluetoothDevice bluetoothDevice, + in IMidiDeviceOpenCallback callback); + void closeDevice(IBinder clientToken, IBinder deviceToken); // for registering built-in MIDI devices MidiDeviceInfo registerDeviceServer(in IMidiDeviceServer server, int numInputPorts, @@ -52,5 +57,5 @@ interface IMidiManager // used by MIDI devices to report their status // the token is used by MidiService for death notification - void setDeviceStatus(IBinder token, in MidiDeviceStatus status); + void setDeviceStatus(in IMidiDeviceServer server, in MidiDeviceStatus status); } diff --git a/media/java/android/media/midi/MidiDevice.java b/media/java/android/media/midi/MidiDevice.java index 6526adc..7998a92 100644 --- a/media/java/android/media/midi/MidiDevice.java +++ b/media/java/android/media/midi/MidiDevice.java @@ -16,8 +16,6 @@ package android.media.midi; -import android.content.Context; -import android.content.ServiceConnection; import android.os.Binder; import android.os.IBinder; import android.os.ParcelFileDescriptor; @@ -40,9 +38,9 @@ public final class MidiDevice implements Closeable { private final MidiDeviceInfo mDeviceInfo; private final IMidiDeviceServer mDeviceServer; - private Context mContext; - private ServiceConnection mServiceConnection; - + private final IMidiManager mMidiManager; + private final IBinder mClientToken; + private final IBinder mDeviceToken; private final CloseGuard mGuard = CloseGuard.get(); @@ -71,16 +69,13 @@ public final class MidiDevice implements Closeable { } } - /* package */ MidiDevice(MidiDeviceInfo deviceInfo, IMidiDeviceServer server) { - this(deviceInfo, server, null, null); - } - /* package */ MidiDevice(MidiDeviceInfo deviceInfo, IMidiDeviceServer server, - Context context, ServiceConnection serviceConnection) { + IMidiManager midiManager, IBinder clientToken, IBinder deviceToken) { mDeviceInfo = deviceInfo; mDeviceServer = server; - mContext = context; - mServiceConnection = serviceConnection; + mMidiManager = midiManager; + mClientToken = clientToken; + mDeviceToken = deviceToken; mGuard.open("close"); } @@ -170,10 +165,10 @@ public final class MidiDevice implements Closeable { public void close() throws IOException { synchronized (mGuard) { mGuard.close(); - if (mContext != null && mServiceConnection != null) { - mContext.unbindService(mServiceConnection); - mContext = null; - mServiceConnection = null; + try { + mMidiManager.closeDevice(mClientToken, mDeviceToken); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in closeDevice"); } } } diff --git a/media/java/android/media/midi/MidiDeviceServer.java b/media/java/android/media/midi/MidiDeviceServer.java index a316a44..1b56e1c 100644 --- a/media/java/android/media/midi/MidiDeviceServer.java +++ b/media/java/android/media/midi/MidiDeviceServer.java @@ -65,7 +65,6 @@ public final class MidiDeviceServer implements Closeable { // for reporting device status - private final IBinder mDeviceStatusToken = new Binder(); private final boolean[] mInputPortOpen; private final int[] mOutputPortOpenCount; @@ -81,6 +80,11 @@ public final class MidiDeviceServer implements Closeable { * @param status the {@link MidiDeviceStatus} for the device */ public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status); + + /** + * Called to notify when the device is closed + */ + public void onClose(); } abstract private class PortClient implements IBinder.DeathRecipient { @@ -242,6 +246,14 @@ public final class MidiDeviceServer implements Closeable { } @Override + public void closeDevice() { + if (mCallback != null) { + mCallback.onClose(); + } + IoUtils.closeQuietly(MidiDeviceServer.this); + } + + @Override public void connectPorts(IBinder token, ParcelFileDescriptor pfd, int outputPortNumber) { MidiInputPort inputPort = new MidiInputPort(pfd, outputPortNumber); @@ -305,7 +317,7 @@ public final class MidiDeviceServer implements Closeable { mCallback.onDeviceStatusChanged(this, status); } try { - mMidiManager.setDeviceStatus(mDeviceStatusToken, status); + mMidiManager.setDeviceStatus(mServer, status); } catch (RemoteException e) { Log.e(TAG, "RemoteException in updateDeviceStatus"); } finally { diff --git a/media/java/android/media/midi/MidiDeviceService.java b/media/java/android/media/midi/MidiDeviceService.java index ce12a4f..d897ad2 100644 --- a/media/java/android/media/midi/MidiDeviceService.java +++ b/media/java/android/media/midi/MidiDeviceService.java @@ -59,6 +59,11 @@ abstract public class MidiDeviceService extends Service { public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status) { MidiDeviceService.this.onDeviceStatusChanged(status); } + + @Override + public void onClose() { + MidiDeviceService.this.onClose(); + } }; @Override @@ -125,6 +130,12 @@ abstract public class MidiDeviceService extends Service { public void onDeviceStatusChanged(MidiDeviceStatus status) { } + /** + * Called to notify when our device has been closed by all its clients + */ + public void onClose() { + } + @Override public IBinder onBind(Intent intent) { if (SERVICE_INTERFACE.equals(intent.getAction()) && mServer != null) { diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java index d19cf36..0beb9a4 100644 --- a/media/java/android/media/midi/MidiManager.java +++ b/media/java/android/media/midi/MidiManager.java @@ -17,11 +17,6 @@ package android.media.midi; import android.bluetooth.BluetoothDevice; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.pm.ServiceInfo; import android.os.Binder; import android.os.IBinder; import android.os.Bundle; @@ -52,16 +47,17 @@ public final class MidiManager { /** * BluetoothMidiService package name + * @hide */ - private static final String BLUETOOTH_MIDI_SERVICE_PACKAGE = "com.android.bluetoothmidiservice"; + public static final String BLUETOOTH_MIDI_SERVICE_PACKAGE = "com.android.bluetoothmidiservice"; /** * BluetoothMidiService class name + * @hide */ - private static final String BLUETOOTH_MIDI_SERVICE_CLASS = + public static final String BLUETOOTH_MIDI_SERVICE_CLASS = "com.android.bluetoothmidiservice.BluetoothMidiService"; - private final Context mContext; private final IMidiManager mService; private final IBinder mToken = new Binder(); @@ -166,8 +162,7 @@ public final class MidiManager { /** * @hide */ - public MidiManager(Context context, IMidiManager service) { - mContext = context; + public MidiManager(IMidiManager service) { mService = service; } @@ -237,7 +232,7 @@ public final class MidiManager { * Opens a MIDI device for reading and writing. * * @param deviceInfo a {@link android.media.midi.MidiDeviceInfo} to open - * @param listener a {@link MidiManager.OnDeviceOpenedListener} to be called + * @param listener a {@link MidiManager.OnDeviceOpenedListener} to be called * to receive the result * @param handler the {@link android.os.Handler Handler} that will be used for delivering * the result. If handler is null, then the thread used for the @@ -245,52 +240,28 @@ public final class MidiManager { */ public void openDevice(MidiDeviceInfo deviceInfo, OnDeviceOpenedListener listener, Handler handler) { - MidiDevice device = null; - try { - IMidiDeviceServer server = mService.openDevice(mToken, deviceInfo); - if (server == null) { - ServiceInfo serviceInfo = (ServiceInfo)deviceInfo.getProperties().getParcelable( - MidiDeviceInfo.PROPERTY_SERVICE_INFO); - if (serviceInfo == null) { - Log.e(TAG, "no ServiceInfo for " + deviceInfo); - } else { - Intent intent = new Intent(MidiDeviceService.SERVICE_INTERFACE); - intent.setComponent(new ComponentName(serviceInfo.packageName, - serviceInfo.name)); - final MidiDeviceInfo deviceInfoF = deviceInfo; - final OnDeviceOpenedListener listenerF = listener; - final Handler handlerF = handler; - if (mContext.bindService(intent, - new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder binder) { - IMidiDeviceServer server = - IMidiDeviceServer.Stub.asInterface(binder); - MidiDevice device = new MidiDevice(deviceInfoF, server, mContext, - this); - sendOpenDeviceResponse(device, listenerF, handlerF); - } + final MidiDeviceInfo deviceInfoF = deviceInfo; + final OnDeviceOpenedListener listenerF = listener; + final Handler handlerF = handler; - @Override - public void onServiceDisconnected(ComponentName name) { - // FIXME - anything to do here? - } - }, - Context.BIND_AUTO_CREATE)) - { - // return immediately to avoid calling sendOpenDeviceResponse below - return; - } else { - Log.e(TAG, "Unable to bind service: " + intent); - } + IMidiDeviceOpenCallback callback = new IMidiDeviceOpenCallback.Stub() { + @Override + public void onDeviceOpened(IMidiDeviceServer server, IBinder deviceToken) { + MidiDevice device; + if (server != null) { + device = new MidiDevice(deviceInfoF, server, mService, mToken, deviceToken); + } else { + device = null; } - } else { - device = new MidiDevice(deviceInfo, server); + sendOpenDeviceResponse(device, listenerF, handlerF); } + }; + + try { + mService.openDevice(mToken, deviceInfo, callback); } catch (RemoteException e) { Log.e(TAG, "RemoteException in openDevice"); } - sendOpenDeviceResponse(device, listener, handler); } /** @@ -303,38 +274,33 @@ public final class MidiManager { * the result. If handler is null, then the thread used for the * listener is unspecified. */ - public void openBluetoothDevice(final BluetoothDevice bluetoothDevice, - final OnDeviceOpenedListener listener, final Handler handler) { - Intent intent = new Intent(BLUETOOTH_MIDI_SERVICE_INTENT); - intent.setComponent(new ComponentName(BLUETOOTH_MIDI_SERVICE_PACKAGE, - BLUETOOTH_MIDI_SERVICE_CLASS)); - intent.putExtra("device", bluetoothDevice); - if (!mContext.bindService(intent, - new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder binder) { - IMidiDeviceServer server = - IMidiDeviceServer.Stub.asInterface(binder); + public void openBluetoothDevice(BluetoothDevice bluetoothDevice, + OnDeviceOpenedListener listener, Handler handler) { + final OnDeviceOpenedListener listenerF = listener; + final Handler handlerF = handler; + + IMidiDeviceOpenCallback callback = new IMidiDeviceOpenCallback.Stub() { + @Override + public void onDeviceOpened(IMidiDeviceServer server, IBinder deviceToken) { + MidiDevice device = null; + if (server != null) { try { // fetch MidiDeviceInfo from the server MidiDeviceInfo deviceInfo = server.getDeviceInfo(); - MidiDevice device = new MidiDevice(deviceInfo, server, mContext, this); - sendOpenDeviceResponse(device, listener, handler); + device = new MidiDevice(deviceInfo, server, mService, mToken, deviceToken); + sendOpenDeviceResponse(device, listenerF, handlerF); } catch (RemoteException e) { - Log.e(TAG, "remote exception in onServiceConnected"); - sendOpenDeviceResponse(null, listener, handler); + Log.e(TAG, "remote exception in getDeviceInfo()"); } } + sendOpenDeviceResponse(device, listenerF, handlerF); + } + }; - @Override - public void onServiceDisconnected(ComponentName name) { - // FIXME - anything to do here? - } - }, - Context.BIND_AUTO_CREATE)) - { - Log.e(TAG, "Unable to bind service: " + intent); - sendOpenDeviceResponse(null, listener, handler); + try { + mService.openBluetoothDevice(mToken, bluetoothDevice, callback); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in openDevice"); } } diff --git a/media/java/android/media/midi/package.html b/media/java/android/media/midi/package.html index 8323bdf..673c4ba 100644 --- a/media/java/android/media/midi/package.html +++ b/media/java/android/media/midi/package.html @@ -255,7 +255,8 @@ messages.</p> <p>An app can provide a MIDI Service that can be used by other apps. For example, -an app can provide a custom synthesizer that other apps can send messages to. </p> +an app can provide a custom synthesizer that other apps can send messages to. +The service must be guarded with permission "android.permission.BIND_MIDI_DEVICE_SERVICE".</p> <h2 id=manifest_files>Manifest Files</h2> @@ -264,7 +265,8 @@ an app can provide a custom synthesizer that other apps can send messages to. </ AndroidManifest.xml file.</p> <pre class=prettyprint> -<service android:name="<strong>MySynthDeviceService</strong>"> +<service android:name="<strong>MySynthDeviceService</strong>" + android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE"> <intent-filter> <action android:name="android.media.midi.MidiDeviceService" /> </intent-filter> diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java index 2acee04..b1a51a5 100644 --- a/media/java/android/media/session/MediaController.java +++ b/media/java/android/media/session/MediaController.java @@ -1018,7 +1018,9 @@ public final class MediaController { } public void post(int what, Object obj, Bundle data) { - obtainMessage(what, obj).sendToTarget(); + Message msg = obtainMessage(what, obj); + msg.setData(data); + msg.sendToTarget(); } } diff --git a/media/java/android/media/tv/DvbDeviceInfo.java b/media/java/android/media/tv/DvbDeviceInfo.java index 1885a34..e07f3a6 100644 --- a/media/java/android/media/tv/DvbDeviceInfo.java +++ b/media/java/android/media/tv/DvbDeviceInfo.java @@ -16,14 +16,10 @@ package android.media.tv; -import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; -import android.media.tv.TvInputManager; import android.util.Log; -import java.lang.IllegalArgumentException; - /** * Simple container for information about DVB device. * Not for third-party developers. diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java index fed0ddf..5ad1bce 100644 --- a/media/java/android/media/tv/ITvInputSessionWrapper.java +++ b/media/java/android/media/tv/ITvInputSessionWrapper.java @@ -285,8 +285,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand @Override public void timeShiftSeekTo(long timeMs) { - mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_TIME_SHIFT_SEEK_TO, - Long.valueOf(timeMs))); + mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_TIME_SHIFT_SEEK_TO, timeMs)); } @Override @@ -298,7 +297,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand @Override public void timeShiftEnablePositionTracking(boolean enable) { mCaller.executeOrSendMessage(mCaller.obtainMessageO( - DO_TIME_SHIFT_ENABLE_POSITION_TRACKING, Boolean.valueOf(enable))); + DO_TIME_SHIFT_ENABLE_POSITION_TRACKING, enable)); } private final class TvInputEventReceiver extends InputEventReceiver { diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java index f5a6f2b..91b1a49 100644 --- a/media/java/android/media/tv/TvContract.java +++ b/media/java/android/media/tv/TvContract.java @@ -485,8 +485,7 @@ public final class TvContract { /** The video resolution for ultra high-definition. */ public static final String VIDEO_RESOLUTION_UHD = "VIDEO_RESOLUTION_UHD"; - private static final Map<String, String> VIDEO_FORMAT_TO_RESOLUTION_MAP = - new HashMap<String, String>(); + private static final Map<String, String> VIDEO_FORMAT_TO_RESOLUTION_MAP = new HashMap<>(); static { VIDEO_FORMAT_TO_RESOLUTION_MAP.put(VIDEO_FORMAT_480I, VIDEO_RESOLUTION_SD); @@ -1128,7 +1127,7 @@ public final class TvContract { /** The genre for Tech/Science. */ public static final String TECH_SCIENCE = "TECH_SCIENCE"; - private static final ArraySet<String> CANONICAL_GENRES = new ArraySet<String>(); + private static final ArraySet<String> CANONICAL_GENRES = new ArraySet<>(); static { CANONICAL_GENRES.add(FAMILY_KIDS); CANONICAL_GENRES.add(SPORTS); diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java index 46d33b4..c537dd6 100644 --- a/media/java/android/media/tv/TvInputInfo.java +++ b/media/java/android/media/tv/TvInputInfo.java @@ -549,8 +549,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 +565,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 = @@ -626,7 +627,7 @@ public final class TvInputInfo implements Parcelable { public static Set<String> getHiddenTvInputIds(Context context, int userId) { String hiddenIdsString = Settings.Secure.getStringForUser( context.getContentResolver(), Settings.Secure.TV_INPUT_HIDDEN_INPUTS, userId); - Set<String> set = new HashSet<String>(); + Set<String> set = new HashSet<>(); if (TextUtils.isEmpty(hiddenIdsString)) { return set; } @@ -648,7 +649,7 @@ public final class TvInputInfo implements Parcelable { public static Map<String, String> getCustomLabels(Context context, int userId) { String labelsString = Settings.Secure.getStringForUser( context.getContentResolver(), Settings.Secure.TV_INPUT_CUSTOM_LABELS, userId); - Map<String, String> map = new HashMap<String, String>(); + Map<String, String> map = new HashMap<>(); if (TextUtils.isEmpty(labelsString)) { return map; } diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 7fc19f1..01de898 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -221,16 +221,15 @@ public final class TvInputManager { private final Object mLock = new Object(); // @GuardedBy("mLock") - private final List<TvInputCallbackRecord> mCallbackRecords = - new LinkedList<TvInputCallbackRecord>(); + private final List<TvInputCallbackRecord> mCallbackRecords = new LinkedList<>(); // A mapping from TV input ID to the state of corresponding input. // @GuardedBy("mLock") - private final Map<String, Integer> mStateMap = new ArrayMap<String, Integer>(); + private final Map<String, Integer> mStateMap = new ArrayMap<>(); // A mapping from the sequence number of a session to its SessionCallbackRecord. private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap = - new SparseArray<SessionCallbackRecord>(); + new SparseArray<>(); // A sequence number for the next session to be created. Should be protected by a lock // {@code mSessionCallbackRecordMap}. @@ -238,8 +237,6 @@ public final class TvInputManager { private final ITvInputClient mClient; - private final ITvInputManagerCallback mManagerCallback; - private final int mUserId; /** @@ -879,7 +876,7 @@ public final class TvInputManager { } } }; - mManagerCallback = new ITvInputManagerCallback.Stub() { + ITvInputManagerCallback managerCallback = new ITvInputManagerCallback.Stub() { @Override public void onInputStateChanged(String inputId, int state) { synchronized (mLock) { @@ -921,7 +918,7 @@ public final class TvInputManager { }; try { if (mService != null) { - mService.registerCallback(mManagerCallback, mUserId); + mService.registerCallback(managerCallback, mUserId); List<TvInputInfo> infos = mService.getTvInputList(mUserId); synchronized (mLock) { for (TvInputInfo info : infos) { @@ -985,7 +982,7 @@ public final class TvInputManager { Log.w(TAG, "Unrecognized input ID: " + inputId); return INPUT_STATE_DISCONNECTED; } - return state.intValue(); + return state; } } @@ -1076,7 +1073,7 @@ public final class TvInputManager { @SystemApi public List<TvContentRating> getBlockedRatings() { try { - List<TvContentRating> ratings = new ArrayList<TvContentRating>(); + List<TvContentRating> ratings = new ArrayList<>(); for (String rating : mService.getBlockedRatings(mUserId)) { ratings.add(TvContentRating.unflattenFromString(rating)); } @@ -1334,8 +1331,8 @@ public final class TvInputManager { // protect pending input events and the input channel. private final InputEventHandler mHandler = new InputEventHandler(Looper.getMainLooper()); - private final Pool<PendingEvent> mPendingEventPool = new SimplePool<PendingEvent>(20); - private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<PendingEvent>(20); + private final Pool<PendingEvent> mPendingEventPool = new SimplePool<>(20); + private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20); private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap; private IBinder mToken; @@ -1344,11 +1341,11 @@ public final class TvInputManager { private final Object mMetadataLock = new Object(); // @GuardedBy("mMetadataLock") - private final List<TvTrackInfo> mAudioTracks = new ArrayList<TvTrackInfo>(); + private final List<TvTrackInfo> mAudioTracks = new ArrayList<>(); // @GuardedBy("mMetadataLock") - private final List<TvTrackInfo> mVideoTracks = new ArrayList<TvTrackInfo>(); + private final List<TvTrackInfo> mVideoTracks = new ArrayList<>(); // @GuardedBy("mMetadataLock") - private final List<TvTrackInfo> mSubtitleTracks = new ArrayList<TvTrackInfo>(); + private final List<TvTrackInfo> mSubtitleTracks = new ArrayList<>(); // @GuardedBy("mMetadataLock") private String mSelectedAudioTrackId; // @GuardedBy("mMetadataLock") @@ -1589,17 +1586,17 @@ public final class TvInputManager { if (mAudioTracks == null) { return null; } - return new ArrayList<TvTrackInfo>(mAudioTracks); + return new ArrayList<>(mAudioTracks); } else if (type == TvTrackInfo.TYPE_VIDEO) { if (mVideoTracks == null) { return null; } - return new ArrayList<TvTrackInfo>(mVideoTracks); + return new ArrayList<>(mVideoTracks); } else if (type == TvTrackInfo.TYPE_SUBTITLE) { if (mSubtitleTracks == null) { return null; } - return new ArrayList<TvTrackInfo>(mSubtitleTracks); + return new ArrayList<>(mSubtitleTracks); } } throw new IllegalArgumentException("invalid type: " + type); diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 4b84090..50a215c 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -27,7 +27,6 @@ import android.graphics.PixelFormat; import android.graphics.Rect; import android.hardware.hdmi.HdmiDeviceInfo; import android.media.PlaybackParams; -import android.media.tv.TvInputService.HardwareSession; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; @@ -98,7 +97,7 @@ public abstract class TvInputService extends Service { */ private final Handler mServiceHandler = new ServiceHandler(); private final RemoteCallbackList<ITvInputServiceCallback> mCallbacks = - new RemoteCallbackList<ITvInputServiceCallback>(); + new RemoteCallbackList<>(); private TvInputManager mTvInputManager; @@ -341,9 +340,13 @@ public abstract class TvInputService extends Service { } /** - * Notifies the channel of the session is retuned by TV input. + * Informs the application that the current channel is re-tuned for some reason and the + * session now displays the content from a new channel. This is used to handle special cases + * such as when the current channel becomes unavailable, it is necessary to send the user to + * a certain channel or the user changes channel in some other way (e.g. by using a + * dedicated remote). * - * @param channelUri The URI of a channel. + * @param channelUri The URI of the new channel. */ public void notifyChannelRetuned(final Uri channelUri) { executeOrPostRunnable(new Runnable() { @@ -374,7 +377,7 @@ public abstract class TvInputService extends Service { * @throws IllegalArgumentException if {@code tracks} contains redundant tracks. */ public void notifyTracksChanged(final List<TvTrackInfo> tracks) { - Set<String> trackIdSet = new HashSet<String>(); + Set<String> trackIdSet = new HashSet<>(); for (TvTrackInfo track : tracks) { String trackId = track.getId(); if (trackIdSet.contains(trackId)) { @@ -933,6 +936,10 @@ public abstract class TvInputService extends Service { * Returns {@link TvInputManager#TIME_SHIFT_INVALID_TIME} if the position is unknown at the * moment. * + * <p>Note that the current playback position should be equal to or greater than the start + * playback position reported by {@link #onTimeShiftGetStartPosition}. Failure to notifying + * the correct current position might lead to bad user experience. + * * @see #onTimeShiftResume * @see #onTimeShiftPause * @see #onTimeShiftSeekTo @@ -1396,6 +1403,12 @@ public abstract class TvInputService extends Service { notifyTimeShiftStartPositionChanged(startPositionMs); } long currentPositionMs = onTimeShiftGetCurrentPosition(); + if (currentPositionMs < mStartPositionMs) { + Log.w(TAG, "Current position (" + currentPositionMs + ") cannot be earlier than" + + " start position (" + mStartPositionMs + "). Reset to the start " + + "position."); + currentPositionMs = mStartPositionMs; + } if (mCurrentPositionMs != currentPositionMs) { mCurrentPositionMs = currentPositionMs; notifyTimeShiftCurrentPositionChanged(currentPositionMs); diff --git a/media/jni/Android.mk b/media/jni/Android.mk index 51d0140..79557bc 100644 --- a/media/jni/Android.mk +++ b/media/jni/Android.mk @@ -69,7 +69,7 @@ LOCAL_C_INCLUDES += \ $(PV_INCLUDES) \ $(JNI_H_INCLUDE) -LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code +LOCAL_CFLAGS += -Wall -Werror -Wno-error=deprecated-declarations -Wunused -Wunreachable-code LOCAL_MODULE:= libmedia_jni diff --git a/media/jni/android_media_MediaCrypto.cpp b/media/jni/android_media_MediaCrypto.cpp index a9accb0..d7968d2 100644 --- a/media/jni/android_media_MediaCrypto.cpp +++ b/media/jni/android_media_MediaCrypto.cpp @@ -301,17 +301,19 @@ static void android_media_MediaCrypto_setMediaDrmSession( status_t err = crypto->setMediaDrmSession(sessionId); - String8 msg("setMediaDrmSession failed"); - if (err == ERROR_DRM_SESSION_NOT_OPENED) { - msg += ": session not opened"; - } else if (err == ERROR_UNSUPPORTED) { - msg += ": not supported by this crypto scheme"; - } else if (err == NO_INIT) { - msg += ": crypto plugin not initialized"; - } else if (err != OK) { - msg.appendFormat(": general failure (%d)", err); + if (err != OK) { + String8 msg("setMediaDrmSession failed"); + if (err == ERROR_DRM_SESSION_NOT_OPENED) { + msg += ": session not opened"; + } else if (err == ERROR_UNSUPPORTED) { + msg += ": not supported by this crypto scheme"; + } else if (err == NO_INIT) { + msg += ": crypto plugin not initialized"; + } else { + msg.appendFormat(": general failure (%d)", err); + } + jniThrowException(env, "android/media/MediaCryptoException", msg.string()); } - jniThrowException(env, "android/media/MediaCryptoException", msg.string()); } static JNINativeMethod gMethods[] = { diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index 9c67278..d8041f4 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -1045,7 +1045,7 @@ static JNINativeMethod gMethods[] = { {"_setDataSource", "(Landroid/media/MediaDataSource;)V",(void *)android_media_MediaPlayer_setDataSourceCallback }, {"_setVideoSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaPlayer_setVideoSurface}, {"_prepare", "()V", (void *)android_media_MediaPlayer_prepare}, - {"_prepareAsync", "()V", (void *)android_media_MediaPlayer_prepareAsync}, + {"prepareAsync", "()V", (void *)android_media_MediaPlayer_prepareAsync}, {"_start", "()V", (void *)android_media_MediaPlayer_start}, {"_stop", "()V", (void *)android_media_MediaPlayer_stop}, {"getVideoWidth", "()I", (void *)android_media_MediaPlayer_getVideoWidth}, 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.mk b/media/jni/soundpool/Android.mk index 2476056..2bc41b5 100644 --- a/media/jni/soundpool/Android.mk +++ b/media/jni/soundpool/Android.mk @@ -18,6 +18,6 @@ LOCAL_SHARED_LIBRARIES := \ LOCAL_MODULE:= libsoundpool -LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code +LOCAL_CFLAGS += -Wall -Werror -Wno-error=deprecated-declarations -Wunused -Wunreachable-code include $(BUILD_SHARED_LIBRARY) diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp index 8038cdf..a705bcc 100644 --- a/media/jni/soundpool/SoundPool.cpp +++ b/media/jni/soundpool/SoundPool.cpp @@ -753,6 +753,7 @@ void SoundChannel::play(const sp<Sample>& sample, int nextChannelID, float leftV status = newTrack->initCheck(); if (status != NO_ERROR) { ALOGE("Error creating AudioTrack"); + // newTrack goes out of scope, so reference count drops to zero goto exit; } // From now on, AudioTrack callbacks received with previous toggle value will be ignored. diff --git a/media/packages/BluetoothMidiService/AndroidManifest.xml b/media/packages/BluetoothMidiService/AndroidManifest.xml index 15aa581..b0b389a 100644 --- a/media/packages/BluetoothMidiService/AndroidManifest.xml +++ b/media/packages/BluetoothMidiService/AndroidManifest.xml @@ -8,7 +8,8 @@ <application android:label="@string/app_name"> - <service android:name="BluetoothMidiService"> + <service android:name="BluetoothMidiService" + android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE"> <intent-filter> <action android:name="android.media.midi.BluetoothMidiService" /> </intent-filter> diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java index 60c6570..e6d59e4 100644 --- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java +++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java @@ -24,10 +24,11 @@ import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothProfile; import android.content.Context; -import android.media.midi.MidiReceiver; -import android.media.midi.MidiManager; -import android.media.midi.MidiDeviceServer; import android.media.midi.MidiDeviceInfo; +import android.media.midi.MidiDeviceServer; +import android.media.midi.MidiDeviceStatus; +import android.media.midi.MidiManager; +import android.media.midi.MidiReceiver; import android.os.Bundle; import android.os.IBinder; import android.util.Log; @@ -81,6 +82,18 @@ public final class BluetoothMidiDevice { private final BluetoothPacketDecoder mPacketDecoder = new BluetoothPacketDecoder(MAX_PACKET_SIZE); + private final MidiDeviceServer.Callback mDeviceServerCallback + = new MidiDeviceServer.Callback() { + @Override + public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status) { + } + + @Override + public void onClose() { + close(); + } + }; + private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, @@ -213,7 +226,7 @@ public final class BluetoothMidiDevice { inputPortReceivers[0] = mEventScheduler.getReceiver(); mDeviceServer = mMidiManager.createDeviceServer(inputPortReceivers, 1, - null, null, properties, MidiDeviceInfo.TYPE_BLUETOOTH, null); + null, null, properties, MidiDeviceInfo.TYPE_BLUETOOTH, mDeviceServerCallback); mOutputReceiver = mDeviceServer.getOutputPortReceivers()[0]; @@ -248,11 +261,12 @@ public final class BluetoothMidiDevice { private void close() { synchronized (mBluetoothDevice) { - mEventScheduler.close(); + mEventScheduler.close(); + mService.deviceClosed(mBluetoothDevice); + if (mDeviceServer != null) { IoUtils.closeQuietly(mDeviceServer); mDeviceServer = null; - mService.deviceClosed(mBluetoothDevice); } if (mBluetoothGatt != null) { mBluetoothGatt.close(); 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 66ed933..bcfcbf3 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java @@ -823,13 +823,20 @@ public class CodecTest { duration = mMediaPlayer.getDuration(); // start to play mMediaPlayer.start(); - waittime = duration - mMediaPlayer.getCurrentPosition(); - synchronized(onCompletion){ - try { - onCompletion.wait(waittime + buffertime); - }catch (Exception e) { - Log.v(TAG, "playMediaSamples are interrupted"); - return false; + if (duration < 0) { + Log.w(TAG, filePath + " has unknown duration, waiting until playback completes"); + while (mMediaPlayer.isPlaying()) { + SystemClock.sleep(1000); + } + } else { + waittime = duration - mMediaPlayer.getCurrentPosition(); + synchronized(onCompletion){ + try { + onCompletion.wait(waittime + buffertime); + } catch (Exception e) { + Log.v(TAG, "playMediaSamples are interrupted"); + return false; + } } } terminateMessageLooper(); diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java index 29c3c75..cd0e587 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java @@ -731,27 +731,31 @@ public class CameraMetadataTest extends junit.framework.TestCase { @SmallTest public void testReadWriteHighSpeedVideoConfiguration() { - // int32 x 4 x 1 + // int32 x 5 x 1 checkKeyMarshal("android.control.availableHighSpeedVideoConfigurations", new HighSpeedVideoConfiguration( - /*width*/1000, /*height*/255, /*fpsMin*/30, /*fpsMax*/200), + /*width*/1000, /*height*/255, /*fpsMin*/30, /*fpsMax*/200, + /*batchSizeMax*/8), /* width, height, fpsMin, fpsMax */ - toByteArray(1000, 255, 30, 200)); + toByteArray(1000, 255, 30, 200, 8)); - // int32 x 4 x 3 + // int32 x 5 x 3 checkKeyMarshal("android.control.availableHighSpeedVideoConfigurations", new HighSpeedVideoConfiguration[] { new HighSpeedVideoConfiguration( - /*width*/1280, /*height*/720, /*fpsMin*/60, /*fpsMax*/120), + /*width*/1280, /*height*/720, /*fpsMin*/60, /*fpsMax*/120, + /*batchSizeMax*/8), new HighSpeedVideoConfiguration( - /*width*/123, /*height*/456, /*fpsMin*/1, /*fpsMax*/200), + /*width*/123, /*height*/456, /*fpsMin*/1, /*fpsMax*/200, + /*batchSizeMax*/4), new HighSpeedVideoConfiguration( - /*width*/4096, /*height*/2592, /*fpsMin*/30, /*fpsMax*/60) + /*width*/4096, /*height*/2592, /*fpsMin*/30, /*fpsMax*/60, + /*batchSizeMax*/2) }, toByteArray( - 1280, 720, 60, 120, - 123, 456, 1, 200, - 4096, 2592, 30, 60 + 1280, 720, 60, 120, 8, + 123, 456, 1, 200, 4, + 4096, 2592, 30, 60, 2 )); } diff --git a/media/tests/audiotests/Android.mk b/media/tests/audiotests/Android.mk index 794e7f22..3507434 100644 --- a/media/tests/audiotests/Android.mk +++ b/media/tests/audiotests/Android.mk @@ -18,6 +18,6 @@ LOCAL_SHARED_LIBRARIES := \ LOCAL_MODULE_TAGS := tests -LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code +LOCAL_CFLAGS += -Wall -Werror -Wno-error=deprecated-declarations -Wunused -Wunreachable-code include $(BUILD_EXECUTABLE) |
