diff options
Diffstat (limited to 'media/java/android')
23 files changed, 792 insertions, 412 deletions
diff --git a/media/java/android/media/AmrInputStream.java b/media/java/android/media/AmrInputStream.java index bc68472..8b7eee2 100644 --- a/media/java/android/media/AmrInputStream.java +++ b/media/java/android/media/AmrInputStream.java @@ -44,7 +44,7 @@ public final class AmrInputStream extends InputStream private int mGae; // result amr stream - private byte[] mBuf = new byte[SAMPLES_PER_FRAME * 2]; + private final byte[] mBuf = new byte[SAMPLES_PER_FRAME * 2]; private int mBufIn = 0; private int mBufOut = 0; diff --git a/media/java/android/media/AsyncPlayer.java b/media/java/android/media/AsyncPlayer.java index 09aec2e..804528e 100644 --- a/media/java/android/media/AsyncPlayer.java +++ b/media/java/android/media/AsyncPlayer.java @@ -49,7 +49,7 @@ public class AsyncPlayer { } } - private LinkedList<Command> mCmdQueue = new LinkedList(); + private final LinkedList<Command> mCmdQueue = new LinkedList(); private void startSound(Command cmd) { // Preparing can be slow, so if there is something else diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java index 8990fe5..49f498e 100644 --- a/media/java/android/media/AudioFormat.java +++ b/media/java/android/media/AudioFormat.java @@ -31,10 +31,11 @@ public class AudioFormat { public static final int ENCODING_INVALID = 0; /** Default audio data format */ public static final int ENCODING_DEFAULT = 1; + // These two values must be kept in sync with JNI code for AudioTrack, AudioRecord /** Audio data format: PCM 16 bit per sample. Guaranteed to be supported by devices. */ - public static final int ENCODING_PCM_16BIT = 2; // accessed by native code + public static final int ENCODING_PCM_16BIT = 2; /** Audio data format: PCM 8 bit per sample. Not guaranteed to be supported by devices. */ - public static final int ENCODING_PCM_8BIT = 3; // accessed by native code + public static final int ENCODING_PCM_8BIT = 3; /** Invalid audio channel configuration */ /** @deprecated use CHANNEL_INVALID instead */ diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index a0881a7..78eb89f 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -22,8 +22,6 @@ import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.database.ContentObserver; -import android.graphics.Bitmap; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -37,7 +35,6 @@ import android.util.Log; import android.view.KeyEvent; import android.view.VolumePanel; -import java.util.Iterator; import java.util.HashMap; /** @@ -49,11 +46,9 @@ import java.util.HashMap; public class AudioManager { private final Context mContext; - private final Handler mHandler; private long mVolumeKeyUpTime; private int mVolumeControlStream = -1; private static String TAG = "AudioManager"; - private static boolean localLOGV = false; /** * Broadcast intent, a hint for applications that audio is about to become @@ -359,7 +354,6 @@ public class AudioManager { */ public AudioManager(Context context) { mContext = context; - mHandler = new Handler(context.getMainLooper()); } private static IAudioService getService() @@ -1515,7 +1509,7 @@ public class AudioManager { * Map to convert focus event listener IDs, as used in the AudioService audio focus stack, * to actual listener objects. */ - private HashMap<String, OnAudioFocusChangeListener> mAudioFocusIdListenerMap = + private final HashMap<String, OnAudioFocusChangeListener> mAudioFocusIdListenerMap = new HashMap<String, OnAudioFocusChangeListener>(); /** * Lock to prevent concurrent changes to the list of focus listeners for this AudioManager @@ -1530,7 +1524,7 @@ public class AudioManager { /** * Handler for audio focus events coming from the audio service. */ - private FocusEventHandlerDelegate mAudioFocusEventHandlerDelegate = + private final FocusEventHandlerDelegate mAudioFocusEventHandlerDelegate = new FocusEventHandlerDelegate(); /** @@ -1569,7 +1563,7 @@ public class AudioManager { } } - private IAudioFocusDispatcher mAudioFocusDispatcher = new IAudioFocusDispatcher.Stub() { + private final IAudioFocusDispatcher mAudioFocusDispatcher = new IAudioFocusDispatcher.Stub() { public void dispatchAudioFocusChange(int focusChange, String id) { Message m = mAudioFocusEventHandlerDelegate.getHandler().obtainMessage(focusChange, id); @@ -1655,11 +1649,46 @@ public class AudioManager { mAudioFocusDispatcher, getIdForAudioFocusListener(l), mContext.getPackageName() /* package name */); } catch (RemoteException e) { - Log.e(TAG, "Can't call requestAudioFocus() from AudioService due to "+e); + Log.e(TAG, "Can't call requestAudioFocus() on AudioService due to "+e); } return status; } + /** + * @hide + * Used internally by telephony package to request audio focus. Will cause the focus request + * to be associated with the "voice communication" identifier only used in AudioService + * to identify this use case. + * @param streamType use STREAM_RING for focus requests when ringing, VOICE_CALL for + * the establishment of the call + * @param durationHint the type of focus request. AUDIOFOCUS_GAIN_TRANSIENT is recommended so + * media applications resume after a call + */ + public void requestAudioFocusForCall(int streamType, int durationHint) { + IAudioService service = getService(); + try { + service.requestAudioFocus(streamType, durationHint, mICallBack, null, + AudioService.IN_VOICE_COMM_FOCUS_ID, + "system" /* dump-friendly package name */); + } catch (RemoteException e) { + Log.e(TAG, "Can't call requestAudioFocusForCall() on AudioService due to "+e); + } + } + + /** + * @hide + * Used internally by telephony package to abandon audio focus, typically after a call or + * when ringing ends and the call is rejected or not answered. + * Should match one or more calls to {@link #requestAudioFocusForCall(int, int)}. + */ + public void abandonAudioFocusForCall() { + IAudioService service = getService(); + try { + service.abandonAudioFocus(null, AudioService.IN_VOICE_COMM_FOCUS_ID); + } catch (RemoteException e) { + Log.e(TAG, "Can't call abandonAudioFocusForCall() on AudioService due to "+e); + } + } /** * Abandon audio focus. Causes the previous focus owner, if any, to receive focus. @@ -1674,7 +1703,7 @@ public class AudioManager { status = service.abandonAudioFocus(mAudioFocusDispatcher, getIdForAudioFocusListener(l)); } catch (RemoteException e) { - Log.e(TAG, "Can't call abandonAudioFocus() from AudioService due to "+e); + Log.e(TAG, "Can't call abandonAudioFocus() on AudioService due to "+e); } return status; } @@ -1926,7 +1955,7 @@ public class AudioManager { /** * {@hide} */ - private IBinder mICallBack = new Binder(); + private final IBinder mICallBack = new Binder(); /** * Checks whether the phone is in silent mode, with or without vibrate. diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java index 855e831..5cc24c0 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -161,7 +161,7 @@ public class AudioRecord /** * Lock to make sure mRecordingState updates are reflecting the actual state of the object. */ - private Object mRecordingStateLock = new Object(); + private final Object mRecordingStateLock = new Object(); /** * The listener the AudioRecord notifies when the record position reaches a marker * or for periodic updates during the progression of the record head. diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index 37aacab..13e3982 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -102,8 +102,6 @@ public class AudioService extends IAudioService.Stub { private VolumePanel mVolumePanel; // sendMsg() flags - /** Used when a message should be shared across all stream types. */ - private static final int SHARED_MSG = -1; /** If the msg is already queued, replace it with this one. */ private static final int SENDMSG_REPLACE = 0; /** If the msg is already queued, ignore this one and leave the old. */ @@ -112,7 +110,7 @@ public class AudioService extends IAudioService.Stub { private static final int SENDMSG_QUEUE = 2; // AudioHandler message.whats - private static final int MSG_SET_SYSTEM_VOLUME = 0; + private static final int MSG_SET_DEVICE_VOLUME = 0; private static final int MSG_PERSIST_VOLUME = 1; private static final int MSG_PERSIST_RINGER_MODE = 3; private static final int MSG_PERSIST_VIBRATE_SETTING = 4; @@ -126,6 +124,13 @@ public class AudioService extends IAudioService.Stub { private static final int MSG_BT_HEADSET_CNCT_FAILED = 12; private static final int MSG_RCDISPLAY_CLEAR = 13; private static final int MSG_RCDISPLAY_UPDATE = 14; + private static final int MSG_SET_ALL_VOLUMES = 15; + + + // flags for MSG_PERSIST_VOLUME indicating if current and/or last audible volume should be + // persisted + private static final int PERSIST_CURRENT = 0x1; + private static final int PERSIST_LAST_AUDIBLE = 0x2; private static final int BTA2DP_DOCK_TIMEOUT_MILLIS = 8000; // Timeout for connection to bluetooth headset service @@ -141,11 +146,12 @@ public class AudioService extends IAudioService.Stub { private SettingsObserver mSettingsObserver; private int mMode; - private Object mSettingsLock = new Object(); + // protects mRingerMode + private final Object mSettingsLock = new Object(); private boolean mMediaServerOk; private SoundPool mSoundPool; - private Object mSoundEffectsLock = new Object(); + private final Object mSoundEffectsLock = new Object(); private static final int NUM_SOUNDPOOL_CHANNELS = 4; private static final int SOUND_EFFECT_VOLUME = 1000; @@ -162,7 +168,7 @@ public class AudioService extends IAudioService.Stub { /* Sound effect file name mapping sound effect id (AudioManager.FX_xxx) to * file index in SOUND_EFFECT_FILES[] (first column) and indicating if effect * uses soundpool (second column) */ - private int[][] SOUND_EFFECT_FILES_MAP = new int[][] { + private final int[][] SOUND_EFFECT_FILES_MAP = new int[][] { {0, -1}, // FX_KEY_CLICK {0, -1}, // FX_FOCUS_NAVIGATION_UP {0, -1}, // FX_FOCUS_NAVIGATION_DOWN @@ -175,7 +181,7 @@ public class AudioService extends IAudioService.Stub { }; /** @hide Maximum volume index values for audio streams */ - private int[] MAX_STREAM_VOLUME = new int[] { + private final int[] MAX_STREAM_VOLUME = new int[] { 5, // STREAM_VOICE_CALL 7, // STREAM_SYSTEM 7, // STREAM_RING @@ -191,7 +197,7 @@ public class AudioService extends IAudioService.Stub { * of another stream: This avoids multiplying the volume settings for hidden * stream types that follow other stream behavior for volume settings * NOTE: do not create loops in aliases! */ - private int[] STREAM_VOLUME_ALIAS = new int[] { + private final int[] STREAM_VOLUME_ALIAS = new int[] { AudioSystem.STREAM_VOICE_CALL, // STREAM_VOICE_CALL AudioSystem.STREAM_SYSTEM, // STREAM_SYSTEM AudioSystem.STREAM_RING, // STREAM_RING @@ -204,19 +210,19 @@ public class AudioService extends IAudioService.Stub { AudioSystem.STREAM_MUSIC // STREAM_TTS }; - private AudioSystem.ErrorCallback mAudioSystemCallback = new AudioSystem.ErrorCallback() { + private final AudioSystem.ErrorCallback mAudioSystemCallback = new AudioSystem.ErrorCallback() { public void onError(int error) { switch (error) { case AudioSystem.AUDIO_STATUS_SERVER_DIED: if (mMediaServerOk) { - sendMsg(mAudioHandler, MSG_MEDIA_SERVER_DIED, SHARED_MSG, SENDMSG_NOOP, 0, 0, + sendMsg(mAudioHandler, MSG_MEDIA_SERVER_DIED, SENDMSG_NOOP, 0, 0, null, 1500); mMediaServerOk = false; } break; case AudioSystem.AUDIO_STATUS_OK: if (!mMediaServerOk) { - sendMsg(mAudioHandler, MSG_MEDIA_SERVER_STARTED, SHARED_MSG, SENDMSG_NOOP, 0, 0, + sendMsg(mAudioHandler, MSG_MEDIA_SERVER_STARTED, SENDMSG_NOOP, 0, 0, null, 0); mMediaServerOk = true; } @@ -232,6 +238,7 @@ public class AudioService extends IAudioService.Stub { * {@link AudioManager#RINGER_MODE_SILENT}, or * {@link AudioManager#RINGER_MODE_VIBRATE}. */ + // protected by mSettingsLock private int mRingerMode; /** @see System#MODE_RINGER_STREAMS_AFFECTED */ @@ -263,17 +270,17 @@ public class AudioService extends IAudioService.Stub { private boolean mIsRinging = false; // Devices currently connected - private HashMap <Integer, String> mConnectedDevices = new HashMap <Integer, String>(); + private final HashMap <Integer, String> mConnectedDevices = new HashMap <Integer, String>(); // Forced device usage for communications private int mForcedUseForComm; // List of binder death handlers for setMode() client processes. // The last process to have called setMode() is at the top of the list. - private ArrayList <SetModeDeathHandler> mSetModeDeathHandlers = new ArrayList <SetModeDeathHandler>(); + private final ArrayList <SetModeDeathHandler> mSetModeDeathHandlers = new ArrayList <SetModeDeathHandler>(); // List of clients having issued a SCO start request - private ArrayList <ScoClient> mScoClients = new ArrayList <ScoClient>(); + private final ArrayList <ScoClient> mScoClients = new ArrayList <ScoClient>(); // BluetoothHeadset API to control SCO connection private BluetoothHeadset mBluetoothHeadset; @@ -323,6 +330,7 @@ public class AudioService extends IAudioService.Stub { // Keyguard manager proxy private KeyguardManager mKeyguardManager; + /////////////////////////////////////////////////////////////////////////// // Construction /////////////////////////////////////////////////////////////////////////// @@ -425,12 +433,17 @@ public class AudioService extends IAudioService.Stub { // Correct stream index values for streams with aliases for (int i = 0; i < numStreamTypes; i++) { + int device = getDeviceForStream(i); if (STREAM_VOLUME_ALIAS[i] != i) { - int index = rescaleIndex(streams[i].mIndex, STREAM_VOLUME_ALIAS[i], i); - streams[i].mIndex = streams[i].getValidIndex(index); - setStreamVolumeIndex(i, index); - index = rescaleIndex(streams[i].mLastAudibleIndex, STREAM_VOLUME_ALIAS[i], i); - streams[i].mLastAudibleIndex = streams[i].getValidIndex(index); + int index = rescaleIndex(streams[i].getIndex(device, false /* lastAudible */), + STREAM_VOLUME_ALIAS[i], + i); + streams[i].mIndex.put(device, streams[i].getValidIndex(index)); + streams[i].applyDeviceVolume(device); + index = rescaleIndex(streams[i].getIndex(device, true /* lastAudible */), + STREAM_VOLUME_ALIAS[i], + i); + streams[i].mLastAudibleIndex.put(device, streams[i].getValidIndex(index)); } } } @@ -438,12 +451,15 @@ public class AudioService extends IAudioService.Stub { private void readPersistedSettings() { final ContentResolver cr = mContentResolver; - mRingerMode = System.getInt(cr, System.MODE_RINGER, AudioManager.RINGER_MODE_NORMAL); + int ringerMode = System.getInt(cr, System.MODE_RINGER, AudioManager.RINGER_MODE_NORMAL); // sanity check in case the settings are restored from a device with incompatible // ringer modes - if (!AudioManager.isValidRingerMode(mRingerMode)) { - mRingerMode = AudioManager.RINGER_MODE_NORMAL; - System.putInt(cr, System.MODE_RINGER, mRingerMode); + if (!AudioManager.isValidRingerMode(ringerMode)) { + ringerMode = AudioManager.RINGER_MODE_NORMAL; + System.putInt(cr, System.MODE_RINGER, ringerMode); + } + synchronized(mSettingsLock) { + mRingerMode = ringerMode; } mVibrateSetting = System.getInt(cr, System.VIBRATE_ON, 0); @@ -469,7 +485,7 @@ public class AudioService extends IAudioService.Stub { // Each stream will read its own persisted settings // Broadcast the sticky intent - broadcastRingerMode(); + broadcastRingerMode(ringerMode); // Broadcast vibrate settings broadcastVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER); @@ -479,10 +495,6 @@ public class AudioService extends IAudioService.Stub { restoreMediaButtonReceiver(); } - private void setStreamVolumeIndex(int stream, int index) { - AudioSystem.setStreamVolumeIndex(stream, (index + 5)/10); - } - private int rescaleIndex(int index, int srcStream, int dstStream) { return (index * mStreamStates[dstStream].getMaxIndex() + mStreamStates[srcStream].getMaxIndex() / 2) / mStreamStates[srcStream].getMaxIndex(); } @@ -526,7 +538,11 @@ public class AudioService extends IAudioService.Stub { // checkForRingerModeChange() in place of STREAM_RING or STREAM_NOTIFICATION) int streamTypeAlias = STREAM_VOLUME_ALIAS[streamType]; VolumeStreamState streamState = mStreamStates[streamTypeAlias]; - final int oldIndex = (streamState.muteCount() != 0) ? streamState.mLastAudibleIndex : streamState.mIndex; + + final int device = getDeviceForStream(streamTypeAlias); + // get last audible index if stream is muted, current index otherwise + final int oldIndex = streamState.getIndex(device, + (streamState.muteCount() != 0) /* lastAudible */); boolean adjustVolume = true; // If either the client forces allowing ringer modes for this adjustment, @@ -534,8 +550,9 @@ public class AudioService extends IAudioService.Stub { if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) || streamTypeAlias == AudioSystem.STREAM_RING || (!mVoiceCapable && streamTypeAlias == AudioSystem.STREAM_MUSIC)) { + int ringerMode = getRingerMode(); // do not vibrate if already in vibrate mode - if (mRingerMode == AudioManager.RINGER_MODE_VIBRATE) { + if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) { flags &= ~AudioManager.FLAG_VIBRATE; } // Check if the ringer mode changes with this volume adjustment. If @@ -554,22 +571,32 @@ public class AudioService extends IAudioService.Stub { if (STREAM_VOLUME_ALIAS[i] == streamTypeAlias) { VolumeStreamState s = mStreamStates[i]; - s.adjustLastAudibleIndex(direction); + s.adjustLastAudibleIndex(direction, device); // Post a persist volume msg - sendMsg(mAudioHandler, MSG_PERSIST_VOLUME, i, - SENDMSG_REPLACE, 0, 1, s, PERSIST_DELAY); + sendMsg(mAudioHandler, + MSG_PERSIST_VOLUME, + SENDMSG_REPLACE, + PERSIST_LAST_AUDIBLE, + device, + s, + PERSIST_DELAY); } } } - index = streamState.mLastAudibleIndex; + index = streamState.getIndex(device, true /* lastAudible */); } else { - if (adjustVolume && streamState.adjustIndex(direction)) { + if (adjustVolume && streamState.adjustIndex(direction, device)) { // Post message to set system volume (it in turn will post a message // to persist). Do not change volume if stream is muted. - sendMsg(mAudioHandler, MSG_SET_SYSTEM_VOLUME, streamTypeAlias, SENDMSG_NOOP, 0, 0, - streamState, 0); + sendMsg(mAudioHandler, + MSG_SET_DEVICE_VOLUME, + SENDMSG_NOOP, + device, + 0, + streamState, + 0); } - index = streamState.mIndex; + index = streamState.getIndex(device, false /* lastAudible */); } sendVolumeUpdate(streamType, oldIndex, index, flags); @@ -580,29 +607,35 @@ public class AudioService extends IAudioService.Stub { ensureValidStreamType(streamType); VolumeStreamState streamState = mStreamStates[STREAM_VOLUME_ALIAS[streamType]]; - final int oldIndex = (streamState.muteCount() != 0) ? streamState.mLastAudibleIndex : streamState.mIndex; + final int device = getDeviceForStream(streamType); + // get last audible index if stream is muted, current index otherwise + final int oldIndex = streamState.getIndex(device, + (streamState.muteCount() != 0) /* lastAudible */); // setting ring or notifications volume to 0 on voice capable devices enters silent mode if (mVoiceCapable && (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) || (STREAM_VOLUME_ALIAS[streamType] == AudioSystem.STREAM_RING))) { - int newRingerMode = mRingerMode; + int newRingerMode; if (index == 0) { newRingerMode = System.getInt(mContentResolver, System.VIBRATE_IN_SILENT, 1) == 1 ? AudioManager.RINGER_MODE_VIBRATE : AudioManager.RINGER_MODE_SILENT; - setStreamVolumeInt(STREAM_VOLUME_ALIAS[streamType], index, false, true); + setStreamVolumeInt(STREAM_VOLUME_ALIAS[streamType], + index, + device, + false, + true); } else { newRingerMode = AudioManager.RINGER_MODE_NORMAL; } - if (newRingerMode != mRingerMode) { - setRingerMode(newRingerMode); - } + setRingerMode(newRingerMode); } index = rescaleIndex(index * 10, streamType, STREAM_VOLUME_ALIAS[streamType]); - setStreamVolumeInt(STREAM_VOLUME_ALIAS[streamType], index, false, true); - - index = (streamState.muteCount() != 0) ? streamState.mLastAudibleIndex : streamState.mIndex; + setStreamVolumeInt(STREAM_VOLUME_ALIAS[streamType], index, device, false, true); + // get last audible index if stream is muted, current index otherwise + index = streamState.getIndex(device, + (streamState.muteCount() != 0) /* lastAudible */); sendVolumeUpdate(streamType, oldIndex, index, flags); } @@ -630,28 +663,43 @@ public class AudioService extends IAudioService.Stub { * * @param streamType Type of the stream * @param index Desired volume index of the stream + * @param device the device whose volume must be changed * @param force If true, set the volume even if the desired volume is same * as the current volume. * @param lastAudible If true, stores new index as last audible one */ - private void setStreamVolumeInt(int streamType, int index, boolean force, boolean lastAudible) { + private void setStreamVolumeInt(int streamType, + int index, + int device, + boolean force, + boolean lastAudible) { VolumeStreamState streamState = mStreamStates[streamType]; // If stream is muted, set last audible index only if (streamState.muteCount() != 0) { // Do not allow last audible index to be 0 if (index != 0) { - streamState.setLastAudibleIndex(index); + streamState.setLastAudibleIndex(index, device); // Post a persist volume msg - sendMsg(mAudioHandler, MSG_PERSIST_VOLUME, streamType, - SENDMSG_REPLACE, 0, 1, streamState, PERSIST_DELAY); + sendMsg(mAudioHandler, + MSG_PERSIST_VOLUME, + SENDMSG_REPLACE, + PERSIST_LAST_AUDIBLE, + device, + streamState, + PERSIST_DELAY); } } else { - if (streamState.setIndex(index, lastAudible) || force) { + if (streamState.setIndex(index, device, lastAudible) || force) { // Post message to set system volume (it in turn will post a message // to persist). - sendMsg(mAudioHandler, MSG_SET_SYSTEM_VOLUME, streamType, SENDMSG_NOOP, 0, 0, - streamState, 0); + sendMsg(mAudioHandler, + MSG_SET_DEVICE_VOLUME, + SENDMSG_NOOP, + device, + 0, + streamState, + 0); } } } @@ -680,7 +728,8 @@ public class AudioService extends IAudioService.Stub { /** @see AudioManager#getStreamVolume(int) */ public int getStreamVolume(int streamType) { ensureValidStreamType(streamType); - return (mStreamStates[streamType].mIndex + 5) / 10; + int device = getDeviceForStream(streamType); + return (mStreamStates[streamType].getIndex(device, false /* lastAudible */) + 5) / 10; } /** @see AudioManager#getStreamMaxVolume(int) */ @@ -693,27 +742,37 @@ public class AudioService extends IAudioService.Stub { /** Get last audible volume before stream was muted. */ public int getLastAudibleStreamVolume(int streamType) { ensureValidStreamType(streamType); - return (mStreamStates[streamType].mLastAudibleIndex + 5) / 10; + int device = getDeviceForStream(streamType); + return (mStreamStates[streamType].getIndex(device, true /* lastAudible */) + 5) / 10; } /** @see AudioManager#getRingerMode() */ public int getRingerMode() { - return mRingerMode; + synchronized(mSettingsLock) { + return mRingerMode; + } + } + + private void ensureValidRingerMode(int ringerMode) { + if (!AudioManager.isValidRingerMode(ringerMode)) { + throw new IllegalArgumentException("Bad ringer mode " + ringerMode); + } } /** @see AudioManager#setRingerMode(int) */ public void setRingerMode(int ringerMode) { - synchronized (mSettingsLock) { - if (ringerMode != mRingerMode) { - setRingerModeInt(ringerMode, true); - // Send sticky broadcast - broadcastRingerMode(); - } + ensureValidRingerMode(ringerMode); + if (ringerMode != getRingerMode()) { + setRingerModeInt(ringerMode, true); + // Send sticky broadcast + broadcastRingerMode(ringerMode); } } private void setRingerModeInt(int ringerMode, boolean persist) { - mRingerMode = ringerMode; + synchronized(mSettingsLock) { + mRingerMode = ringerMode; + } // Mute stream if not previously muted by ringer mode and ringer mode // is not RINGER_MODE_NORMAL and stream is affected by ringer mode. @@ -723,20 +782,27 @@ public class AudioService extends IAudioService.Stub { for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { if (isStreamMutedByRingerMode(streamType)) { if (!isStreamAffectedByRingerMode(streamType) || - mRingerMode == AudioManager.RINGER_MODE_NORMAL) { + ringerMode == AudioManager.RINGER_MODE_NORMAL) { // ring and notifications volume should never be 0 when not silenced // on voice capable devices if (mVoiceCapable && - STREAM_VOLUME_ALIAS[streamType] == AudioSystem.STREAM_RING && - mStreamStates[streamType].mLastAudibleIndex == 0) { - mStreamStates[streamType].mLastAudibleIndex = 10; + STREAM_VOLUME_ALIAS[streamType] == AudioSystem.STREAM_RING) { + + Set set = mStreamStates[streamType].mLastAudibleIndex.entrySet(); + Iterator i = set.iterator(); + while (i.hasNext()) { + Map.Entry entry = (Map.Entry)i.next(); + if ((Integer)entry.getValue() == 0) { + entry.setValue(10); + } + } } mStreamStates[streamType].mute(null, false); mRingerModeMutedStreams &= ~(1 << streamType); } } else { if (isStreamAffectedByRingerMode(streamType) && - mRingerMode != AudioManager.RINGER_MODE_NORMAL) { + ringerMode != AudioManager.RINGER_MODE_NORMAL) { mStreamStates[streamType].mute(null, true); mRingerModeMutedStreams |= (1 << streamType); } @@ -745,7 +811,7 @@ public class AudioService extends IAudioService.Stub { // Post a persist ringer mode msg if (persist) { - sendMsg(mAudioHandler, MSG_PERSIST_RINGER_MODE, SHARED_MSG, + sendMsg(mAudioHandler, MSG_PERSIST_RINGER_MODE, SENDMSG_REPLACE, 0, 0, null, PERSIST_DELAY); } } @@ -756,10 +822,10 @@ public class AudioService extends IAudioService.Stub { switch (getVibrateSetting(vibrateType)) { case AudioManager.VIBRATE_SETTING_ON: - return mRingerMode != AudioManager.RINGER_MODE_SILENT; + return getRingerMode() != AudioManager.RINGER_MODE_SILENT; case AudioManager.VIBRATE_SETTING_ONLY_SILENT: - return mRingerMode == AudioManager.RINGER_MODE_VIBRATE; + return getRingerMode() == AudioManager.RINGER_MODE_VIBRATE; case AudioManager.VIBRATE_SETTING_OFF: // return false, even for incoming calls @@ -785,7 +851,7 @@ public class AudioService extends IAudioService.Stub { // Post message to set ringer mode (it in turn will post a message // to persist) - sendMsg(mAudioHandler, MSG_PERSIST_VIBRATE_SETTING, SHARED_MSG, SENDMSG_NOOP, 0, 0, + sendMsg(mAudioHandler, MSG_PERSIST_VIBRATE_SETTING, SENDMSG_NOOP, 0, 0, null, 0); } @@ -926,8 +992,6 @@ public class AudioService extends IAudioService.Stub { if (mode != mMode) { status = AudioSystem.setPhoneState(mode); if (status == AudioSystem.AUDIO_STATUS_OK) { - // automatically handle audio focus for mode changes - handleFocusForCalls(mMode, mode, cb); mMode = mode; } else { if (hdlr != null) { @@ -951,46 +1015,13 @@ public class AudioService extends IAudioService.Stub { } } int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE); - int index = mStreamStates[STREAM_VOLUME_ALIAS[streamType]].mIndex; - setStreamVolumeInt(STREAM_VOLUME_ALIAS[streamType], index, true, false); + int device = getDeviceForStream(streamType); + int index = mStreamStates[STREAM_VOLUME_ALIAS[streamType]].getIndex(device, false); + setStreamVolumeInt(STREAM_VOLUME_ALIAS[streamType], index, device, true, false); } return newModeOwnerPid; } - /** pre-condition: oldMode != newMode */ - private void handleFocusForCalls(int oldMode, int newMode, IBinder cb) { - // if ringing - if (newMode == AudioSystem.MODE_RINGTONE) { - // if not ringing silently - int ringVolume = AudioService.this.getStreamVolume(AudioManager.STREAM_RING); - if (ringVolume > 0) { - // request audio focus for the communication focus entry - requestAudioFocus(AudioManager.STREAM_RING, - AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, cb, - null /* IAudioFocusDispatcher allowed to be null only for this clientId */, - IN_VOICE_COMM_FOCUS_ID /*clientId*/, - "system"); - - } - } - // if entering call - else if ((newMode == AudioSystem.MODE_IN_CALL) - || (newMode == AudioSystem.MODE_IN_COMMUNICATION)) { - // request audio focus for the communication focus entry - // (it's ok if focus was already requested during ringing) - requestAudioFocus(AudioManager.STREAM_RING, - AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, cb, - null /* IAudioFocusDispatcher allowed to be null only for this clientId */, - IN_VOICE_COMM_FOCUS_ID /*clientId*/, - "system"); - } - // if exiting call - else if (newMode == AudioSystem.MODE_NORMAL) { - // abandon audio focus for communication focus entry - abandonAudioFocus(null, IN_VOICE_COMM_FOCUS_ID); - } - } - /** @see AudioManager#getMode() */ public int getMode() { return mMode; @@ -998,20 +1029,20 @@ public class AudioService extends IAudioService.Stub { /** @see AudioManager#playSoundEffect(int) */ public void playSoundEffect(int effectType) { - sendMsg(mAudioHandler, MSG_PLAY_SOUND_EFFECT, SHARED_MSG, SENDMSG_NOOP, + sendMsg(mAudioHandler, MSG_PLAY_SOUND_EFFECT, SENDMSG_NOOP, effectType, -1, null, 0); } /** @see AudioManager#playSoundEffect(int, float) */ public void playSoundEffectVolume(int effectType, float volume) { loadSoundEffects(); - sendMsg(mAudioHandler, MSG_PLAY_SOUND_EFFECT, SHARED_MSG, SENDMSG_NOOP, + sendMsg(mAudioHandler, MSG_PLAY_SOUND_EFFECT, SENDMSG_NOOP, effectType, (int) (volume * 1000), null, 0); } /** * Loads samples into the soundpool. - * This method must be called at when sound effects are enabled + * This method must be called at first when sound effects are enabled */ public boolean loadSoundEffects() { int status; @@ -1026,10 +1057,6 @@ public class AudioService extends IAudioService.Stub { return true; } mSoundPool = new SoundPool(NUM_SOUNDPOOL_CHANNELS, AudioSystem.STREAM_SYSTEM, 0); - if (mSoundPool == null) { - Log.w(TAG, "loadSoundEffects() could not allocate sound pool"); - return false; - } try { mSoundPoolCallBack = null; @@ -1220,28 +1247,7 @@ public class AudioService extends IAudioService.Stub { for (int streamType = 0; streamType < numStreamTypes; streamType++) { VolumeStreamState streamState = mStreamStates[streamType]; - String settingName = System.VOLUME_SETTINGS[STREAM_VOLUME_ALIAS[streamType]]; - String lastAudibleSettingName = settingName + System.APPEND_FOR_LAST_AUDIBLE; - int index = Settings.System.getInt(mContentResolver, - settingName, - AudioManager.DEFAULT_STREAM_VOLUME[streamType]); - if (STREAM_VOLUME_ALIAS[streamType] != streamType) { - index = rescaleIndex(index * 10, STREAM_VOLUME_ALIAS[streamType], streamType); - } else { - index *= 10; - } - streamState.mIndex = streamState.getValidIndex(index); - - index = (index + 5) / 10; - index = Settings.System.getInt(mContentResolver, - lastAudibleSettingName, - (index > 0) ? index : AudioManager.DEFAULT_STREAM_VOLUME[streamType]); - if (STREAM_VOLUME_ALIAS[streamType] != streamType) { - index = rescaleIndex(index * 10, STREAM_VOLUME_ALIAS[streamType], streamType); - } else { - index *= 10; - } - streamState.mLastAudibleIndex = streamState.getValidIndex(index); + streamState.readSettings(); // unmute stream that was muted but is not affect by mute anymore if (streamState.muteCount() != 0 && !isStreamAffectedByMute(streamType)) { @@ -1253,7 +1259,7 @@ public class AudioService extends IAudioService.Stub { } // apply stream volume if (streamState.muteCount() == 0) { - setStreamVolumeIndex(streamType, streamState.mIndex); + streamState.applyAllVolumes(); } } @@ -1268,7 +1274,7 @@ public class AudioService extends IAudioService.Stub { } mForcedUseForComm = on ? AudioSystem.FORCE_SPEAKER : AudioSystem.FORCE_NONE; - sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SHARED_MSG, SENDMSG_QUEUE, + sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE, AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, null, 0); } @@ -1284,9 +1290,9 @@ public class AudioService extends IAudioService.Stub { } mForcedUseForComm = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE; - sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SHARED_MSG, SENDMSG_QUEUE, + sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE, AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, null, 0); - sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SHARED_MSG, SENDMSG_QUEUE, + sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE, AudioSystem.FOR_RECORD, mForcedUseForComm, null, 0); } @@ -1526,7 +1532,7 @@ public class AudioService extends IAudioService.Stub { // without delay to reset the SCO audio state and clear SCO clients. // If we could get a proxy, send a delayed failure message that will reset our state // in case we don't receive onServiceConnected(). - sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, 0, + sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, SENDMSG_REPLACE, 0, 0, null, result ? BT_HEADSET_CNCT_TIMEOUT_MS : 0); return result; } @@ -1540,7 +1546,7 @@ public class AudioService extends IAudioService.Stub { if (mBluetoothHeadset != null) { if (!mBluetoothHeadset.stopVoiceRecognition( mBluetoothHeadsetDevice)) { - sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, 0, + sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, SENDMSG_REPLACE, 0, 0, null, 0); } } else if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL && @@ -1623,7 +1629,7 @@ public class AudioService extends IAudioService.Stub { } } if (!status) { - sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, 0, + sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, SENDMSG_REPLACE, 0, 0, null, 0); } } @@ -1668,11 +1674,12 @@ public class AudioService extends IAudioService.Stub { */ private boolean checkForRingerModeChange(int oldIndex, int direction, int streamType) { boolean adjustVolumeIndex = true; - int newRingerMode = mRingerMode; + int ringerMode = getRingerMode(); + int newRingerMode = ringerMode; int uiIndex = (oldIndex + 5) / 10; boolean vibeInSilent = System.getInt(mContentResolver, System.VIBRATE_IN_SILENT, 1) == 1; - if (mRingerMode == RINGER_MODE_NORMAL) { + if (ringerMode == RINGER_MODE_NORMAL) { if ((direction == AudioManager.ADJUST_LOWER) && (uiIndex <= 1)) { // enter silent mode if current index is the last audible one and not repeating a // volume key down @@ -1687,7 +1694,7 @@ public class AudioService extends IAudioService.Stub { adjustVolumeIndex = false; } } - } else if (mRingerMode == RINGER_MODE_VIBRATE) { + } else if (ringerMode == RINGER_MODE_VIBRATE) { if ((direction == AudioManager.ADJUST_LOWER)) { // Set it to silent, if it wasn't a long-press if (mPrevVolDirection != AudioManager.ADJUST_LOWER) { @@ -1706,9 +1713,7 @@ public class AudioService extends IAudioService.Stub { adjustVolumeIndex = false; } - if (newRingerMode != mRingerMode) { - setRingerMode(newRingerMode); - } + setRingerMode(newRingerMode); mPrevVolDirection = direction; @@ -1798,10 +1803,10 @@ public class AudioService extends IAudioService.Stub { } } - private void broadcastRingerMode() { + private void broadcastRingerMode(int ringerMode) { // Send sticky broadcast Intent broadcast = new Intent(AudioManager.RINGER_MODE_CHANGED_ACTION); - broadcast.putExtra(AudioManager.EXTRA_RINGER_MODE, mRingerMode); + broadcast.putExtra(AudioManager.EXTRA_RINGER_MODE, ringerMode); broadcast.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | Intent.FLAG_RECEIVER_REPLACE_PENDING); long origCallerIdentityToken = Binder.clearCallingIdentity(); @@ -1820,17 +1825,9 @@ public class AudioService extends IAudioService.Stub { } // Message helper methods - private static int getMsg(int baseMsg, int streamType) { - return (baseMsg & 0xffff) | streamType << 16; - } - private static int getMsgBase(int msg) { - return msg & 0xffff; - } - - private static void sendMsg(Handler handler, int baseMsg, int streamType, + private static void sendMsg(Handler handler, int msg, int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) { - int msg = (streamType == SHARED_MSG) ? baseMsg : getMsg(baseMsg, streamType); if (existingMsgPolicy == SENDMSG_REPLACE) { handler.removeMessages(msg); @@ -1838,8 +1835,7 @@ public class AudioService extends IAudioService.Stub { return; } - handler - .sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delay); + handler.sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delay); } boolean checkAudioSettingsPermission(String method) { @@ -1854,6 +1850,22 @@ public class AudioService extends IAudioService.Stub { return false; } + private int getDeviceForStream(int stream) { + int device = AudioSystem.getDevicesForStream(stream); + if ((device & (device - 1)) != 0) { + // Multiple device selection is either: + // - speaker + one other device: give priority to speaker in this case. + // - one A2DP device + another device: happens with duplicated output. In this case + // retain the device on the A2DP output as the other must not correspond to an active + // selection if not the speaker. + if ((device & AudioSystem.DEVICE_OUT_SPEAKER) != 0) { + device = AudioSystem.DEVICE_OUT_SPEAKER; + } else { + device &= AudioSystem.DEVICE_OUT_ALL_A2DP; + } + } + return device; + } /////////////////////////////////////////////////////////////////////////// // Inner classes @@ -1865,54 +1877,127 @@ public class AudioService extends IAudioService.Stub { private String mVolumeIndexSettingName; private String mLastAudibleVolumeIndexSettingName; private int mIndexMax; - private int mIndex; - private int mLastAudibleIndex; - private ArrayList<VolumeDeathHandler> mDeathHandlers; //handles mute/solo requests client death + private final HashMap <Integer, Integer> mIndex = new HashMap <Integer, Integer>(); + private final HashMap <Integer, Integer> mLastAudibleIndex = + new HashMap <Integer, Integer>(); + private ArrayList<VolumeDeathHandler> mDeathHandlers; //handles mute/solo clients death private VolumeStreamState(String settingName, int streamType) { - setVolumeIndexSettingName(settingName); + mVolumeIndexSettingName = settingName; + mLastAudibleVolumeIndexSettingName = settingName + System.APPEND_FOR_LAST_AUDIBLE; mStreamType = streamType; - - final ContentResolver cr = mContentResolver; mIndexMax = MAX_STREAM_VOLUME[streamType]; - mIndex = Settings.System.getInt(cr, - mVolumeIndexSettingName, - AudioManager.DEFAULT_STREAM_VOLUME[streamType]); - mLastAudibleIndex = Settings.System.getInt(cr, - mLastAudibleVolumeIndexSettingName, - (mIndex > 0) ? mIndex : AudioManager.DEFAULT_STREAM_VOLUME[streamType]); AudioSystem.initStreamVolume(streamType, 0, mIndexMax); mIndexMax *= 10; - mIndex = getValidIndex(10 * mIndex); - mLastAudibleIndex = getValidIndex(10 * mLastAudibleIndex); - setStreamVolumeIndex(streamType, mIndex); + + readSettings(); + + applyAllVolumes(); + mDeathHandlers = new ArrayList<VolumeDeathHandler>(); } - public void setVolumeIndexSettingName(String settingName) { - mVolumeIndexSettingName = settingName; - mLastAudibleVolumeIndexSettingName = settingName + System.APPEND_FOR_LAST_AUDIBLE; + public String getSettingNameForDevice(boolean lastAudible, int device) { + String name = lastAudible ? + mLastAudibleVolumeIndexSettingName : + mVolumeIndexSettingName; + String suffix = AudioSystem.getDeviceName(device); + if (suffix.isEmpty()) { + return name; + } + return name + "_" + suffix; + } + + public void readSettings() { + int index = Settings.System.getInt(mContentResolver, + mVolumeIndexSettingName, + AudioManager.DEFAULT_STREAM_VOLUME[mStreamType]); + + mIndex.clear(); + mIndex.put(AudioSystem.DEVICE_OUT_DEFAULT, index); + + index = Settings.System.getInt(mContentResolver, + mLastAudibleVolumeIndexSettingName, + (index > 0) ? index : AudioManager.DEFAULT_STREAM_VOLUME[mStreamType]); + mLastAudibleIndex.clear(); + mLastAudibleIndex.put(AudioSystem.DEVICE_OUT_DEFAULT, index); + + int remainingDevices = AudioSystem.DEVICE_OUT_ALL; + for (int i = 0; remainingDevices != 0; i++) { + int device = (1 << i); + if ((device & remainingDevices) == 0) { + continue; + } + remainingDevices &= ~device; + + // retrieve current volume for device + String name = getSettingNameForDevice(false, device); + index = Settings.System.getInt(mContentResolver, name, -1); + if (index == -1) { + continue; + } + mIndex.put(device, getValidIndex(10 * index)); + + // retrieve last audible volume for device + name = getSettingNameForDevice(true, device); + index = Settings.System.getInt(mContentResolver, name, -1); + mLastAudibleIndex.put(device, getValidIndex(10 * index)); + } } - public boolean adjustIndex(int deltaIndex) { - return setIndex(mIndex + deltaIndex * 10, true); + public void applyDeviceVolume(int device) { + AudioSystem.setStreamVolumeIndex(mStreamType, + (getIndex(device, false /* lastAudible */) + 5)/10, + device); } - public boolean setIndex(int index, boolean lastAudible) { - int oldIndex = mIndex; - mIndex = getValidIndex(index); + public void applyAllVolumes() { + // apply default volume first: by convention this will reset all + // devices volumes in audio policy manager to the supplied value + AudioSystem.setStreamVolumeIndex(mStreamType, + (getIndex(AudioSystem.DEVICE_OUT_DEFAULT, false /* lastAudible */) + 5)/10, + AudioSystem.DEVICE_OUT_DEFAULT); + // then apply device specific volumes + Set set = mIndex.entrySet(); + Iterator i = set.iterator(); + while (i.hasNext()) { + Map.Entry entry = (Map.Entry)i.next(); + int device = ((Integer)entry.getKey()).intValue(); + if (device != AudioSystem.DEVICE_OUT_DEFAULT) { + AudioSystem.setStreamVolumeIndex(mStreamType, + ((Integer)entry.getValue() + 5)/10, + device); + } + } + } + + public boolean adjustIndex(int deltaIndex, int device) { + return setIndex(getIndex(device, + false /* lastAudible */) + deltaIndex * 10, + device, + true /* lastAudible */); + } - if (oldIndex != mIndex) { + public boolean setIndex(int index, int device, boolean lastAudible) { + int oldIndex = getIndex(device, false /* lastAudible */); + index = getValidIndex(index); + mIndex.put(device, getValidIndex(index)); + + if (oldIndex != index) { if (lastAudible) { - mLastAudibleIndex = mIndex; + mLastAudibleIndex.put(device, index); } // Apply change to all streams using this one as alias int numStreamTypes = AudioSystem.getNumStreamTypes(); for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { if (streamType != mStreamType && STREAM_VOLUME_ALIAS[streamType] == mStreamType) { - mStreamStates[streamType].setIndex(rescaleIndex(mIndex, mStreamType, streamType), lastAudible); + mStreamStates[streamType].setIndex(rescaleIndex(index, + mStreamType, + streamType), + device, + lastAudible); } } return true; @@ -1921,12 +2006,29 @@ public class AudioService extends IAudioService.Stub { } } - public void setLastAudibleIndex(int index) { - mLastAudibleIndex = getValidIndex(index); + public int getIndex(int device, boolean lastAudible) { + HashMap <Integer, Integer> indexes; + if (lastAudible) { + indexes = mLastAudibleIndex; + } else { + indexes = mIndex; + } + Integer index = indexes.get(device); + if (index == null) { + // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT + index = indexes.get(AudioSystem.DEVICE_OUT_DEFAULT); + } + return index.intValue(); + } + + public void setLastAudibleIndex(int index, int device) { + mLastAudibleIndex.put(device, getValidIndex(index)); } - public void adjustLastAudibleIndex(int deltaIndex) { - setLastAudibleIndex(mLastAudibleIndex + deltaIndex * 10); + public void adjustLastAudibleIndex(int deltaIndex, int device) { + setLastAudibleIndex(getIndex(device, + true /* lastAudible */) + deltaIndex * 10, + device); } public int getMaxIndex() { @@ -1971,10 +2073,20 @@ public class AudioService extends IAudioService.Stub { mICallback.linkToDeath(this, 0); } mDeathHandlers.add(this); - // If the stream is not yet muted by any client, set lvel to 0 + // If the stream is not yet muted by any client, set level to 0 if (muteCount() == 0) { - setIndex(0, false); - sendMsg(mAudioHandler, MSG_SET_SYSTEM_VOLUME, mStreamType, SENDMSG_NOOP, 0, 0, + Set set = mIndex.entrySet(); + Iterator i = set.iterator(); + while (i.hasNext()) { + Map.Entry entry = (Map.Entry)i.next(); + int device = ((Integer)entry.getKey()).intValue(); + setIndex(0, device, false /* lastAudible */); + } + sendMsg(mAudioHandler, + MSG_SET_ALL_VOLUMES, + SENDMSG_NOOP, + 0, + 0, VolumeStreamState.this, 0); } } catch (RemoteException e) { @@ -2002,9 +2114,23 @@ public class AudioService extends IAudioService.Stub { if (muteCount() == 0) { // If the stream is not muted any more, restore it's volume if // ringer mode allows it - if (!isStreamAffectedByRingerMode(mStreamType) || mRingerMode == AudioManager.RINGER_MODE_NORMAL) { - setIndex(mLastAudibleIndex, false); - sendMsg(mAudioHandler, MSG_SET_SYSTEM_VOLUME, mStreamType, SENDMSG_NOOP, 0, 0, + if (!isStreamAffectedByRingerMode(mStreamType) || + mRingerMode == AudioManager.RINGER_MODE_NORMAL) { + Set set = mIndex.entrySet(); + Iterator i = set.iterator(); + while (i.hasNext()) { + Map.Entry entry = (Map.Entry)i.next(); + int device = ((Integer)entry.getKey()).intValue(); + setIndex(getIndex(device, + true /* lastAudible */), + device, + false /* lastAudible */); + } + sendMsg(mAudioHandler, + MSG_SET_ALL_VOLUMES, + SENDMSG_NOOP, + 0, + 0, VolumeStreamState.this, 0); } } @@ -2083,38 +2209,63 @@ public class AudioService extends IAudioService.Stub { /** Handles internal volume messages in separate volume thread. */ private class AudioHandler extends Handler { - private void setSystemVolume(VolumeStreamState streamState) { + private void setDeviceVolume(VolumeStreamState streamState, int device) { - // Adjust volume - setStreamVolumeIndex(streamState.mStreamType, streamState.mIndex); + // Apply volume + streamState.applyDeviceVolume(device); // Apply change to all streams using this one as alias int numStreamTypes = AudioSystem.getNumStreamTypes(); for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { if (streamType != streamState.mStreamType && - STREAM_VOLUME_ALIAS[streamType] == streamState.mStreamType) { - setStreamVolumeIndex(streamType, mStreamStates[streamType].mIndex); + STREAM_VOLUME_ALIAS[streamType] == streamState.mStreamType) { + mStreamStates[streamType].applyDeviceVolume(device); } } // Post a persist volume msg - sendMsg(mAudioHandler, MSG_PERSIST_VOLUME, streamState.mStreamType, - SENDMSG_REPLACE, 1, 1, streamState, PERSIST_DELAY); + sendMsg(mAudioHandler, + MSG_PERSIST_VOLUME, + SENDMSG_REPLACE, + PERSIST_CURRENT|PERSIST_LAST_AUDIBLE, + device, + streamState, + PERSIST_DELAY); + } - private void persistVolume(VolumeStreamState streamState, boolean current, boolean lastAudible) { - if (current) { - System.putInt(mContentResolver, streamState.mVolumeIndexSettingName, - (streamState.mIndex + 5)/ 10); + private void setAllVolumes(VolumeStreamState streamState) { + + // Apply volume + streamState.applyAllVolumes(); + + // Apply change to all streams using this one as alias + int numStreamTypes = AudioSystem.getNumStreamTypes(); + for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { + if (streamType != streamState.mStreamType && + STREAM_VOLUME_ALIAS[streamType] == streamState.mStreamType) { + mStreamStates[streamType].applyAllVolumes(); + } } - if (lastAudible) { - System.putInt(mContentResolver, streamState.mLastAudibleVolumeIndexSettingName, - (streamState.mLastAudibleIndex + 5) / 10); + } + + private void persistVolume(VolumeStreamState streamState, + int persistType, + int device) { + if ((persistType & PERSIST_CURRENT) != 0) { + System.putInt(mContentResolver, + streamState.getSettingNameForDevice(false /* lastAudible */, device), + (streamState.getIndex(device, false /* lastAudible */) + 5)/ 10); + } + if ((persistType & PERSIST_LAST_AUDIBLE) != 0) { + System.putInt(mContentResolver, + streamState.getSettingNameForDevice(true /* lastAudible */, device), + (streamState.getIndex(device, true /* lastAudible */) + 5) / 10); } } - private void persistRingerMode() { - System.putInt(mContentResolver, System.MODE_RINGER, mRingerMode); + private void persistRingerMode(int ringerMode) { + System.putInt(mContentResolver, System.MODE_RINGER, ringerMode); } private void persistVibrateSetting() { @@ -2138,32 +2289,30 @@ public class AudioService extends IAudioService.Stub { mSoundPool.play(SOUND_EFFECT_FILES_MAP[effectType][1], volFloat, volFloat, 0, 0, 1.0f); } else { MediaPlayer mediaPlayer = new MediaPlayer(); - if (mediaPlayer != null) { - try { - String filePath = Environment.getRootDirectory() + SOUND_EFFECTS_PATH + SOUND_EFFECT_FILES[SOUND_EFFECT_FILES_MAP[effectType][0]]; - mediaPlayer.setDataSource(filePath); - mediaPlayer.setAudioStreamType(AudioSystem.STREAM_SYSTEM); - mediaPlayer.prepare(); - mediaPlayer.setVolume(volFloat, volFloat); - mediaPlayer.setOnCompletionListener(new OnCompletionListener() { - public void onCompletion(MediaPlayer mp) { - cleanupPlayer(mp); - } - }); - mediaPlayer.setOnErrorListener(new OnErrorListener() { - public boolean onError(MediaPlayer mp, int what, int extra) { - cleanupPlayer(mp); - return true; - } - }); - mediaPlayer.start(); - } catch (IOException ex) { - Log.w(TAG, "MediaPlayer IOException: "+ex); - } catch (IllegalArgumentException ex) { - Log.w(TAG, "MediaPlayer IllegalArgumentException: "+ex); - } catch (IllegalStateException ex) { - Log.w(TAG, "MediaPlayer IllegalStateException: "+ex); - } + try { + String filePath = Environment.getRootDirectory() + SOUND_EFFECTS_PATH + SOUND_EFFECT_FILES[SOUND_EFFECT_FILES_MAP[effectType][0]]; + mediaPlayer.setDataSource(filePath); + mediaPlayer.setAudioStreamType(AudioSystem.STREAM_SYSTEM); + mediaPlayer.prepare(); + mediaPlayer.setVolume(volFloat, volFloat); + mediaPlayer.setOnCompletionListener(new OnCompletionListener() { + public void onCompletion(MediaPlayer mp) { + cleanupPlayer(mp); + } + }); + mediaPlayer.setOnErrorListener(new OnErrorListener() { + public boolean onError(MediaPlayer mp, int what, int extra) { + cleanupPlayer(mp); + return true; + } + }); + mediaPlayer.start(); + } catch (IOException ex) { + Log.w(TAG, "MediaPlayer IOException: "+ex); + } catch (IllegalArgumentException ex) { + Log.w(TAG, "MediaPlayer IllegalArgumentException: "+ex); + } catch (IllegalStateException ex) { + Log.w(TAG, "MediaPlayer IllegalStateException: "+ex); } } } @@ -2191,20 +2340,25 @@ public class AudioService extends IAudioService.Stub { @Override public void handleMessage(Message msg) { - int baseMsgWhat = getMsgBase(msg.what); - switch (baseMsgWhat) { + switch (msg.what) { + + case MSG_SET_DEVICE_VOLUME: + setDeviceVolume((VolumeStreamState) msg.obj, msg.arg1); + break; - case MSG_SET_SYSTEM_VOLUME: - setSystemVolume((VolumeStreamState) msg.obj); + case MSG_SET_ALL_VOLUMES: + setAllVolumes((VolumeStreamState) msg.obj); break; case MSG_PERSIST_VOLUME: - persistVolume((VolumeStreamState) msg.obj, (msg.arg1 != 0), (msg.arg2 != 0)); + persistVolume((VolumeStreamState) msg.obj, msg.arg1, msg.arg2); break; case MSG_PERSIST_RINGER_MODE: - persistRingerMode(); + // note that the value persisted is the current ringer mode, not the + // value of ringer mode as of the time the request was made to persist + persistRingerMode(getRingerMode()); break; case MSG_PERSIST_VIBRATE_SETTING: @@ -2217,7 +2371,7 @@ public class AudioService extends IAudioService.Stub { // Force creation of new IAudioFlinger interface so that we are notified // when new media_server process is back to life. AudioSystem.setErrorCallback(mAudioSystemCallback); - sendMsg(mAudioHandler, MSG_MEDIA_SERVER_DIED, SHARED_MSG, SENDMSG_NOOP, 0, 0, + sendMsg(mAudioHandler, MSG_MEDIA_SERVER_DIED, SENDMSG_NOOP, 0, 0, null, 500); } break; @@ -2234,7 +2388,7 @@ public class AudioService extends IAudioService.Stub { synchronized (mConnectedDevices) { Set set = mConnectedDevices.entrySet(); Iterator i = set.iterator(); - while(i.hasNext()){ + while (i.hasNext()) { Map.Entry device = (Map.Entry)i.next(); AudioSystem.setDeviceConnectionState( ((Integer)device.getKey()).intValue(), @@ -2252,15 +2406,10 @@ public class AudioService extends IAudioService.Stub { // Restore stream volumes int numStreamTypes = AudioSystem.getNumStreamTypes(); for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { - int index; VolumeStreamState streamState = mStreamStates[streamType]; AudioSystem.initStreamVolume(streamType, 0, (streamState.mIndexMax + 5) / 10); - if (streamState.muteCount() == 0) { - index = streamState.mIndex; - } else { - index = 0; - } - setStreamVolumeIndex(streamType, index); + + streamState.applyAllVolumes(); } // Restore ringer mode @@ -2320,6 +2469,10 @@ public class AudioService extends IAudioService.Stub { @Override public void onChange(boolean selfChange) { super.onChange(selfChange); + // FIXME This synchronized is not necessary if mSettingsLock only protects mRingerMode. + // However there appear to be some missing locks around mRingerModeMutedStreams + // and mRingerModeAffectedStreams, so will leave this synchronized for now. + // mRingerModeMutedStreams and mMuteAffectedStreams are safe (only accessed once). synchronized (mSettingsLock) { int ringerModeAffectedStreams = Settings.System.getInt(mContentResolver, Settings.System.MODE_RINGER_STREAMS_AFFECTED, @@ -2666,7 +2819,7 @@ public class AudioService extends IAudioService.Stub { } } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { mBootCompleted = true; - sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SHARED_MSG, SENDMSG_NOOP, + sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SENDMSG_NOOP, 0, 0, null, 0); mKeyguardManager = @@ -2707,9 +2860,10 @@ public class AudioService extends IAudioService.Stub { //========================================================================================== /* constant to identify focus stack entry that is used to hold the focus while the phone - * is ringing or during a call + * is ringing or during a call. Used by com.android.internal.telephony.CallManager when + * entering and exiting calls. */ - private final static String IN_VOICE_COMM_FOCUS_ID = "AudioFocus_For_Phone_Ring_And_Calls"; + public final static String IN_VOICE_COMM_FOCUS_ID = "AudioFocus_For_Phone_Ring_And_Calls"; private final static Object mAudioFocusLock = new Object(); @@ -2791,7 +2945,7 @@ public class AudioService extends IAudioService.Stub { } } - private Stack<FocusStackEntry> mFocusStack = new Stack<FocusStackEntry>(); + private final Stack<FocusStackEntry> mFocusStack = new Stack<FocusStackEntry>(); /** * Helper function: @@ -3168,7 +3322,7 @@ public class AudioService extends IAudioService.Stub { * synchronized on mRCStack, but also BEFORE on mFocusLock as any change in either * stack, audio focus or RC, can lead to a change in the remote control display */ - private Stack<RemoteControlStackEntry> mRCStack = new Stack<RemoteControlStackEntry>(); + private final Stack<RemoteControlStackEntry> mRCStack = new Stack<RemoteControlStackEntry>(); /** * Helper function: diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 95d93b2..3080497 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -27,7 +27,7 @@ package android.media; */ public class AudioSystem { - /* FIXME: Need to finalize this and correlate with native layer */ + /* These values must be kept in sync with AudioSystem.h */ /* * If these are modified, please also update Settings.System.VOLUME_SETTINGS * and attrs.xml and AudioManager.java. @@ -183,6 +183,7 @@ public class AudioSystem } } + /* * AudioPolicyService methods */ @@ -202,6 +203,23 @@ public class AudioSystem public static final int DEVICE_OUT_ANLG_DOCK_HEADSET = 0x800; public static final int DEVICE_OUT_DGTL_DOCK_HEADSET = 0x1000; public static final int DEVICE_OUT_DEFAULT = 0x8000; + public static final int DEVICE_OUT_ALL = (DEVICE_OUT_EARPIECE | + DEVICE_OUT_SPEAKER | + DEVICE_OUT_WIRED_HEADSET | + DEVICE_OUT_WIRED_HEADPHONE | + DEVICE_OUT_BLUETOOTH_SCO | + DEVICE_OUT_BLUETOOTH_SCO_HEADSET | + DEVICE_OUT_BLUETOOTH_SCO_CARKIT | + DEVICE_OUT_BLUETOOTH_A2DP | + DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES | + DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER | + DEVICE_OUT_AUX_DIGITAL | + DEVICE_OUT_ANLG_DOCK_HEADSET | + DEVICE_OUT_DGTL_DOCK_HEADSET | + DEVICE_OUT_DEFAULT); + public static final int DEVICE_OUT_ALL_A2DP = (DEVICE_OUT_BLUETOOTH_A2DP | + DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES | + DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER); // input devices public static final int DEVICE_IN_COMMUNICATION = 0x10000; public static final int DEVICE_IN_AMBIENT = 0x20000; @@ -218,6 +236,54 @@ public class AudioSystem public static final int DEVICE_STATE_AVAILABLE = 1; private static final int NUM_DEVICE_STATES = 1; + public static final String DEVICE_OUT_EARPIECE_NAME = "earpiece"; + public static final String DEVICE_OUT_SPEAKER_NAME = "speaker"; + public static final String DEVICE_OUT_WIRED_HEADSET_NAME = "headset"; + public static final String DEVICE_OUT_WIRED_HEADPHONE_NAME = "headphone"; + public static final String DEVICE_OUT_BLUETOOTH_SCO_NAME = "bt_sco"; + public static final String DEVICE_OUT_BLUETOOTH_SCO_HEADSET_NAME = "bt_sco_hs"; + public static final String DEVICE_OUT_BLUETOOTH_SCO_CARKIT_NAME = "bt_sco_carkit"; + public static final String DEVICE_OUT_BLUETOOTH_A2DP_NAME = "bt_a2dp"; + public static final String DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES_NAME = "bt_a2dp_hp"; + public static final String DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER_NAME = "bt_a2dp_spk"; + public static final String DEVICE_OUT_AUX_DIGITAL_NAME = "aux_digital"; + public static final String DEVICE_OUT_ANLG_DOCK_HEADSET_NAME = "analog_dock"; + public static final String DEVICE_OUT_DGTL_DOCK_HEADSET_NAME = "digital_dock"; + + public static String getDeviceName(int device) + { + switch(device) { + case DEVICE_OUT_EARPIECE: + return DEVICE_OUT_EARPIECE_NAME; + case DEVICE_OUT_SPEAKER: + return DEVICE_OUT_SPEAKER_NAME; + case DEVICE_OUT_WIRED_HEADSET: + return DEVICE_OUT_WIRED_HEADSET_NAME; + case DEVICE_OUT_WIRED_HEADPHONE: + return DEVICE_OUT_WIRED_HEADPHONE_NAME; + case DEVICE_OUT_BLUETOOTH_SCO: + return DEVICE_OUT_BLUETOOTH_SCO_NAME; + case DEVICE_OUT_BLUETOOTH_SCO_HEADSET: + return DEVICE_OUT_BLUETOOTH_SCO_HEADSET_NAME; + case DEVICE_OUT_BLUETOOTH_SCO_CARKIT: + return DEVICE_OUT_BLUETOOTH_SCO_CARKIT_NAME; + case DEVICE_OUT_BLUETOOTH_A2DP: + return DEVICE_OUT_BLUETOOTH_A2DP_NAME; + case DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES: + return DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES_NAME; + case DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER: + return DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER_NAME; + case DEVICE_OUT_AUX_DIGITAL: + return DEVICE_OUT_AUX_DIGITAL_NAME; + case DEVICE_OUT_ANLG_DOCK_HEADSET: + return DEVICE_OUT_ANLG_DOCK_HEADSET_NAME; + case DEVICE_OUT_DGTL_DOCK_HEADSET: + return DEVICE_OUT_DGTL_DOCK_HEADSET_NAME; + default: + return ""; + } + } + // phone state, match audio_mode??? public static final int PHONE_STATE_OFFCALL = 0; public static final int PHONE_STATE_RINGING = 1; @@ -247,11 +313,10 @@ public class AudioSystem public static native int setDeviceConnectionState(int device, int state, String device_address); public static native int getDeviceConnectionState(int device, String device_address); public static native int setPhoneState(int state); - public static native int setRingerMode(int mode, int mask); public static native int setForceUse(int usage, int config); public static native int getForceUse(int usage); public static native int initStreamVolume(int stream, int indexMin, int indexMax); - public static native int setStreamVolumeIndex(int stream, int index); - public static native int getStreamVolumeIndex(int stream); + public static native int setStreamVolumeIndex(int stream, int index, int device); + public static native int getStreamVolumeIndex(int stream, int device); public static native int getDevicesForStream(int stream); } diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index 4f9eb2b..7d4c282 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -29,7 +29,7 @@ import android.util.Log; /** * The AudioTrack class manages and plays a single audio resource for Java applications. - * It allows to stream PCM audio buffers to the audio hardware for playback. This is + * It allows streaming PCM audio buffers to the audio hardware for playback. This is * achieved by "pushing" the data to the AudioTrack object using one of the * {@link #write(byte[], int, int)} and {@link #write(short[], int, int)} methods. * @@ -46,7 +46,7 @@ import android.util.Log; * <li>received or generated while previously queued audio is playing.</li> * </ul> * - * The static mode is to be chosen when dealing with short sounds that fit in memory and + * The static mode should be chosen when dealing with short sounds that fit in memory and * that need to be played with the smallest latency possible. The static mode will * therefore be preferred for UI and game sounds that are played often, and with the * smallest overhead possible. @@ -57,7 +57,7 @@ import android.util.Log; * For an AudioTrack using the static mode, this size is the maximum size of the sound that can * be played from it.<br> * For the streaming mode, data will be written to the hardware in chunks of - * sizes inferior to the total buffer size. + * sizes less than or equal to the total buffer size. */ public class AudioTrack { @@ -76,6 +76,7 @@ public class AudioTrack /** indicates AudioTrack state is playing */ public static final int PLAYSTATE_PLAYING = 3; // matches SL_PLAYSTATE_PLAYING + // keep these values in sync with android_media_AudioTrack.cpp /** * Creation mode where audio data is transferred from Java to the native layer * only once before the audio starts playing. @@ -180,7 +181,7 @@ public class AudioTrack /** * The audio data sampling rate in Hz. */ - private int mSampleRate = 22050; + private int mSampleRate; // initialized by all constructors /** * The number of audio output channels (1 is mono, 2 is stereo). */ @@ -193,8 +194,9 @@ public class AudioTrack /** * The type of the audio stream to play. See * {@link AudioManager#STREAM_VOICE_CALL}, {@link AudioManager#STREAM_SYSTEM}, - * {@link AudioManager#STREAM_RING}, {@link AudioManager#STREAM_MUSIC} and - * {@link AudioManager#STREAM_ALARM} + * {@link AudioManager#STREAM_RING}, {@link AudioManager#STREAM_MUSIC}, + * {@link AudioManager#STREAM_ALARM}, {@link AudioManager#STREAM_NOTIFICATION}, and + * {@link AudioManager#STREAM_DTMF}. */ private int mStreamType = AudioManager.STREAM_MUSIC; /** @@ -240,10 +242,9 @@ public class AudioTrack * Class constructor. * @param streamType the type of the audio stream. See * {@link AudioManager#STREAM_VOICE_CALL}, {@link AudioManager#STREAM_SYSTEM}, - * {@link AudioManager#STREAM_RING}, {@link AudioManager#STREAM_MUSIC} and - * {@link AudioManager#STREAM_ALARM} - * @param sampleRateInHz the sample rate expressed in Hertz. Examples of rates are (but - * not limited to) 44100, 22050 and 11025. + * {@link AudioManager#STREAM_RING}, {@link AudioManager#STREAM_MUSIC}, + * {@link AudioManager#STREAM_ALARM}, and {@link AudioManager#STREAM_NOTIFICATION}. + * @param sampleRateInHz the sample rate expressed in Hertz. * @param channelConfig describes the configuration of the audio channels. * See {@link AudioFormat#CHANNEL_OUT_MONO} and * {@link AudioFormat#CHANNEL_OUT_STEREO} @@ -275,14 +276,15 @@ public class AudioTrack * and media players in the same session and not to the output mix. * When an AudioTrack is created without specifying a session, it will create its own session * which can be retreived by calling the {@link #getAudioSessionId()} method. - * If a session ID is provided, this AudioTrack will share effects attached to this session - * with all other media players or audio tracks in the same session. + * If a non-zero session ID is provided, this AudioTrack will share effects attached to this + * session + * with all other media players or audio tracks in the same session, otherwise a new session + * will be created for this track if none is supplied. * @param streamType the type of the audio stream. See * {@link AudioManager#STREAM_VOICE_CALL}, {@link AudioManager#STREAM_SYSTEM}, - * {@link AudioManager#STREAM_RING}, {@link AudioManager#STREAM_MUSIC} and - * {@link AudioManager#STREAM_ALARM} - * @param sampleRateInHz the sample rate expressed in Hertz. Examples of rates are (but - * not limited to) 44100, 22050 and 11025. + * {@link AudioManager#STREAM_RING}, {@link AudioManager#STREAM_MUSIC}, + * {@link AudioManager#STREAM_ALARM}, and {@link AudioManager#STREAM_NOTIFICATION}. + * @param sampleRateInHz the sample rate expressed in Hertz. * @param channelConfig describes the configuration of the audio channels. * See {@link AudioFormat#CHANNEL_OUT_MONO} and * {@link AudioFormat#CHANNEL_OUT_STEREO} @@ -304,8 +306,8 @@ public class AudioTrack int bufferSizeInBytes, int mode, int sessionId) throws IllegalArgumentException { mState = STATE_UNINITIALIZED; - - // remember which looper is associated with the AudioTrack instanciation + + // remember which looper is associated with the AudioTrack instantiation if ((mInitializationLooper = Looper.myLooper()) == null) { mInitializationLooper = Looper.getMainLooper(); } @@ -365,7 +367,7 @@ public class AudioTrack } //-------------- - // sample rate + // sample rate, note these values are subject to change if ( (sampleRateInHz < 4000) || (sampleRateInHz > 48000) ) { throw (new IllegalArgumentException(sampleRateInHz + "Hz is not a supported sample rate.")); @@ -449,7 +451,7 @@ public class AudioTrack // AudioTrack subclasses too. try { stop(); - } catch(IllegalStateException ise) { + } catch(IllegalStateException ise) { // don't raise an exception, we're releasing the resources. } native_release(); @@ -488,7 +490,7 @@ public class AudioTrack public int getSampleRate() { return mSampleRate; } - + /** * Returns the current playback rate in Hz. */ @@ -508,7 +510,8 @@ public class AudioTrack * Returns the type of audio stream this AudioTrack is configured for. * Compare the result against {@link AudioManager#STREAM_VOICE_CALL}, * {@link AudioManager#STREAM_SYSTEM}, {@link AudioManager#STREAM_RING}, - * {@link AudioManager#STREAM_MUSIC} or {@link AudioManager#STREAM_ALARM} + * {@link AudioManager#STREAM_MUSIC}, {@link AudioManager#STREAM_ALARM}, + * {@link AudioManager#STREAM_NOTIFICATION}, or {@link AudioManager#STREAM_DTMF}. */ public int getStreamType() { return mStreamType; @@ -590,22 +593,22 @@ public class AudioTrack static public int getNativeOutputSampleRate(int streamType) { return native_get_output_sample_rate(streamType); } - + /** * Returns the minimum buffer size required for the successful creation of an AudioTrack * object to be created in the {@link #MODE_STREAM} mode. Note that this size doesn't * guarantee a smooth playback under load, and higher values should be chosen according to - * the expected frequency at which the buffer will be refilled with additional data to play. + * the expected frequency at which the buffer will be refilled with additional data to play. * @param sampleRateInHz the sample rate expressed in Hertz. - * @param channelConfig describes the configuration of the audio channels. + * @param channelConfig describes the configuration of the audio channels. * See {@link AudioFormat#CHANNEL_OUT_MONO} and * {@link AudioFormat#CHANNEL_OUT_STEREO} - * @param audioFormat the format in which the audio data is represented. - * See {@link AudioFormat#ENCODING_PCM_16BIT} and + * @param audioFormat the format in which the audio data is represented. + * See {@link AudioFormat#ENCODING_PCM_16BIT} and * {@link AudioFormat#ENCODING_PCM_8BIT} * @return {@link #ERROR_BAD_VALUE} if an invalid parameter was passed, - * or {@link #ERROR} if the implementation was unable to query the hardware for its output - * properties, + * or {@link #ERROR} if the implementation was unable to query the hardware for its output + * properties, * or the minimum buffer size expressed in bytes. */ static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) { @@ -623,18 +626,19 @@ public class AudioTrack loge("getMinBufferSize(): Invalid channel configuration."); return AudioTrack.ERROR_BAD_VALUE; } - - if ((audioFormat != AudioFormat.ENCODING_PCM_16BIT) + + if ((audioFormat != AudioFormat.ENCODING_PCM_16BIT) && (audioFormat != AudioFormat.ENCODING_PCM_8BIT)) { loge("getMinBufferSize(): Invalid audio format."); return AudioTrack.ERROR_BAD_VALUE; } - + + // sample rate, note these values are subject to change if ( (sampleRateInHz < 4000) || (sampleRateInHz > 48000) ) { loge("getMinBufferSize(): " + sampleRateInHz +"Hz is not a supported sample rate."); return AudioTrack.ERROR_BAD_VALUE; } - + int size = native_get_min_buff_size(sampleRateInHz, channelCount, audioFormat); if ((size == -1) || (size == 0)) { loge("getMinBufferSize(): error querying hardware"); @@ -667,7 +671,7 @@ public class AudioTrack public void setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener listener) { setPlaybackPositionUpdateListener(listener, null); } - + /** * Sets the listener the AudioTrack notifies when a previously set marker is reached or * for each periodic playback head position update. @@ -676,7 +680,7 @@ public class AudioTrack * @param listener * @param handler the Handler that will receive the event notification messages. */ - public void setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener listener, + public void setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener listener, Handler handler) { synchronized (mPositionListenerLock) { mPositionListener = listener; @@ -684,7 +688,7 @@ public class AudioTrack if (listener != null) { mEventHandlerDelegate = new NativeEventHandlerDelegate(this, handler); } - + } @@ -728,7 +732,7 @@ public class AudioTrack * the audio data will be consumed and played back, not the original sampling rate of the * content. Setting it to half the sample rate of the content will cause the playback to * last twice as long, but will also result in a negative pitch shift. - * The valid sample rate range if from 1Hz to twice the value returned by + * The valid sample rate range is from 1Hz to twice the value returned by * {@link #getNativeOutputSampleRate(int)}. * @param sampleRateInHz the sample rate expressed in Hz * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE}, @@ -836,7 +840,10 @@ public class AudioTrack /** * Stops playing the audio data. - * + * When used on an instance created in {@link #MODE_STREAM} mode, audio will stop playing + * after the last buffer that was written has been played. For an immediate stop, use + * {@link #pause()}, followed by {@link #flush()} to discard audio data that hasn't been played + * back yet. * @throws IllegalStateException */ public void stop() @@ -855,7 +862,7 @@ public class AudioTrack /** * Pauses the playback of the audio data. Data that has not been played * back will not be discarded. Subsequent calls to {@link #play} will play - * this data back. + * this data back. See {@link #flush()} to discard this data. * * @throws IllegalStateException */ @@ -906,7 +913,7 @@ public class AudioTrack * the parameters don't resolve to valid data and indexes. */ - public int write(byte[] audioData,int offsetInBytes, int sizeInBytes) { + public int write(byte[] audioData, int offsetInBytes, int sizeInBytes) { if ((mDataLoadMode == MODE_STATIC) && (mState == STATE_NO_STATIC_DATA) && (sizeInBytes > 0)) { @@ -917,7 +924,7 @@ public class AudioTrack return ERROR_INVALID_OPERATION; } - if ( (audioData == null) || (offsetInBytes < 0 ) || (sizeInBytes < 0) + if ( (audioData == null) || (offsetInBytes < 0 ) || (sizeInBytes < 0) || (offsetInBytes + sizeInBytes > audioData.length)) { return ERROR_BAD_VALUE; } @@ -948,12 +955,12 @@ public class AudioTrack && (sizeInShorts > 0)) { mState = STATE_INITIALIZED; } - + if (mState != STATE_INITIALIZED) { return ERROR_INVALID_OPERATION; } - if ( (audioData == null) || (offsetInShorts < 0 ) || (sizeInShorts < 0) + if ( (audioData == null) || (offsetInShorts < 0 ) || (sizeInShorts < 0) || (offsetInShorts + sizeInShorts > audioData.length)) { return ERROR_BAD_VALUE; } @@ -1012,8 +1019,8 @@ public class AudioTrack * <p>Note that the passed level value is a raw scalar. UI controls should be scaled * logarithmically: the gain applied by audio framework ranges from -72dB to 0dB, * so an appropriate conversion from linear UI input x to level is: - * x == 0 -> level = 0 - * 0 < x <= R -> level = 10^(72*(x-R)/20/R) + * x == 0 -> level = 0 + * 0 < x <= R -> level = 10^(72*(x-R)/20/R) * * @param level send level scalar * @return error code or success, see {@link #SUCCESS}, @@ -1047,7 +1054,7 @@ public class AudioTrack * by the playback head. */ void onMarkerReached(AudioTrack track); - + /** * Called on the listener to periodically notify it that the playback head has reached * a multiple of the notification period. @@ -1062,11 +1069,11 @@ public class AudioTrack /** * Helper class to handle the forwarding of native events to the appropriate listener * (potentially) handled in a different thread - */ + */ private class NativeEventHandlerDelegate { private final AudioTrack mAudioTrack; private final Handler mHandler; - + NativeEventHandlerDelegate(AudioTrack track, Handler handler) { mAudioTrack = track; // find the looper for our new event handler @@ -1077,7 +1084,7 @@ public class AudioTrack // no given handler, use the looper the AudioTrack was created in looper = mInitializationLooper; } - + // construct the event handler with this looper if (looper != null) { // implement the event handler delegate @@ -1111,9 +1118,9 @@ public class AudioTrack }; } else { mHandler = null; - } + } } - + Handler getHandler() { return mHandler; } @@ -1133,7 +1140,7 @@ public class AudioTrack } if (track.mEventHandlerDelegate != null) { - Message m = + Message m = track.mEventHandlerDelegate.getHandler().obtainMessage(what, arg1, arg2, obj); track.mEventHandlerDelegate.getHandler().sendMessage(m); } diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index 925f965..9d6c9f6 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -111,7 +111,7 @@ public class ExifInterface { // there can only be one user at a time for the native functions (and // they cannot keep state in the native code across function calls). We // use sLock to serialize the accesses. - private static Object sLock = new Object(); + private static final Object sLock = new Object(); /** * Reads Exif tags from the specified JPEG file. diff --git a/media/java/android/media/MediaFile.java b/media/java/android/media/MediaFile.java index e275aa6..7f7e284 100644 --- a/media/java/android/media/MediaFile.java +++ b/media/java/android/media/MediaFile.java @@ -120,18 +120,18 @@ public class MediaFile { } } - private static HashMap<String, MediaFileType> sFileTypeMap + private static final HashMap<String, MediaFileType> sFileTypeMap = new HashMap<String, MediaFileType>(); - private static HashMap<String, Integer> sMimeTypeMap + private static final HashMap<String, Integer> sMimeTypeMap = new HashMap<String, Integer>(); // maps file extension to MTP format code - private static HashMap<String, Integer> sFileTypeToFormatMap + private static final HashMap<String, Integer> sFileTypeToFormatMap = new HashMap<String, Integer>(); // maps mime type to MTP format code - private static HashMap<String, Integer> sMimeTypeToFormatMap + private static final HashMap<String, Integer> sMimeTypeToFormatMap = new HashMap<String, Integer>(); // maps MTP format code to mime type - private static HashMap<Integer, String> sFormatToMimeTypeMap + private static final HashMap<Integer, String> sFormatToMimeTypeMap = new HashMap<Integer, String>(); static void addFileType(String extension, int fileType, String mimeType) { diff --git a/media/java/android/media/MediaInserter.java b/media/java/android/media/MediaInserter.java index a998407..e92c710 100644 --- a/media/java/android/media/MediaInserter.java +++ b/media/java/android/media/MediaInserter.java @@ -32,7 +32,7 @@ import java.util.List; * {@hide} */ public class MediaInserter { - private HashMap<Uri, List<ContentValues>> mRowMap = + private final HashMap<Uri, List<ContentValues>> mRowMap = new HashMap<Uri, List<ContentValues>>(); private IContentProvider mProvider; diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index 8d71dcf..4c70e9d 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -1735,6 +1735,9 @@ public class MediaPlayer /** * Called to indicate the video size * + * The video size (width and height) could be 0 if there was no video, + * no display surface was set, or the value was not determined yet. + * * @param mp the MediaPlayer associated with this callback * @param width the width of the video * @param height the height of the video diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 08e6032..85d99c1 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -138,10 +138,13 @@ public class MediaRecorder */ public final class AudioSource { /* Do not change these values without updating their counterparts - * in include/media/mediarecorder.h! + * in system/core/include/system/audio.h! */ private AudioSource() {} + + /** Default audio source **/ public static final int DEFAULT = 0; + /** Microphone audio source */ public static final int MIC = 1; @@ -201,18 +204,24 @@ public class MediaRecorder /** MPEG4 media file format*/ public static final int MPEG_4 = 2; - /** The following formats are audio only .aac or .amr formats **/ - /** @deprecated Deprecated in favor of AMR_NB */ - /** Deprecated in favor of MediaRecorder.OutputFormat.AMR_NB */ - /** AMR NB file format */ + /** The following formats are audio only .aac or .amr formats */ + + /** + * AMR NB file format + * @deprecated Deprecated in favor of MediaRecorder.OutputFormat.AMR_NB + */ public static final int RAW_AMR = 3; + /** AMR NB file format */ public static final int AMR_NB = 3; + /** AMR WB file format */ public static final int AMR_WB = 4; + /** @hide AAC ADIF file format */ public static final int AAC_ADIF = 5; - /** @hide AAC ADTS file format */ + + /** AAC ADTS file format */ public static final int AAC_ADTS = 6; /** @hide Stream over a socket, limited to a single stream */ diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java index 386986e..1c13fff 100644 --- a/media/java/android/media/MediaScanner.java +++ b/media/java/android/media/MediaScanner.java @@ -35,6 +35,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.SystemProperties; import android.provider.MediaStore; +import android.provider.MediaStore.Files.FileColumns; import android.provider.Settings; import android.provider.MediaStore.Audio; import android.provider.MediaStore.Files; @@ -58,6 +59,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.Locale; /** * Internal service helper that no-one should use directly. @@ -312,17 +314,8 @@ public class MediaScanner private final String mExternalStoragePath; - // WARNING: Bulk inserts sounded like a great idea and gave us a good performance improvement, - // but unfortunately it also introduced a number of bugs. Many of those bugs were fixed, - // but (at least) one problem is still outstanding: - // - // - Bulk inserts broke the code that sets the default ringtones, notifications, and alarms - // on first boot - // - // This problem might be solvable by moving the logic to the media provider or disabling bulk - // inserts only for those cases. For now, we are disabling bulk inserts until we have a solid - // fix for this problem. - private static final boolean ENABLE_BULK_INSERTS = false; + /** whether to use bulk inserts or individual inserts for each item */ + private static final boolean ENABLE_BULK_INSERTS = true; // used when scanning the image database so we know whether we have to prune // old thumbnail files @@ -352,7 +345,7 @@ public class MediaScanner // this should be set when scanning files on a case insensitive file system. private boolean mCaseInsensitivePaths; - private BitmapFactory.Options mBitmapOptions = new BitmapFactory.Options(); + private final BitmapFactory.Options mBitmapOptions = new BitmapFactory.Options(); private static class FileCacheEntry { long mRowId; @@ -396,6 +389,7 @@ public class MediaScanner setDefaultRingtoneFileNames(); mExternalStoragePath = Environment.getExternalStorageDirectory().getAbsolutePath(); + //mClient.testGenreNameConverter(); } private void setDefaultRingtoneFileNames() { @@ -407,7 +401,7 @@ public class MediaScanner + Settings.System.ALARM_ALERT); } - private MyMediaScannerClient mClient = new MyMediaScannerClient(); + private final MyMediaScannerClient mClient = new MyMediaScannerClient(); private boolean isDrmEnabled() { String prop = SystemProperties.get("drm.service.enabled"); @@ -623,8 +617,36 @@ public class MediaScanner mCompilation = parseSubstring(value, 0, 0); } else if (name.equalsIgnoreCase("isdrm")) { mIsDrm = (parseSubstring(value, 0, 0) == 1); + } else { + //Log.v(TAG, "unknown tag: " + name + " (" + mProcessGenres + ")"); + } + } + + private boolean convertGenreCode(String input, String expected) { + String output = getGenreName(input); + if (output.equals(expected)) { + return true; + } else { + Log.d(TAG, "'" + input + "' -> '" + output + "', expected '" + expected + "'"); + return false; } } + private void testGenreNameConverter() { + convertGenreCode("2", "Country"); + convertGenreCode("(2)", "Country"); + convertGenreCode("(2", "(2"); + convertGenreCode("2 Foo", "Country"); + convertGenreCode("(2) Foo", "Country"); + convertGenreCode("(2 Foo", "(2 Foo"); + convertGenreCode("2Foo", "2Foo"); + convertGenreCode("(2)Foo", "Country"); + convertGenreCode("200 Foo", "Foo"); + convertGenreCode("(200) Foo", "Foo"); + convertGenreCode("200Foo", "200Foo"); + convertGenreCode("(200)Foo", "Foo"); + convertGenreCode("200)Foo", "200)Foo"); + convertGenreCode("200) Foo", "200) Foo"); + } public String getGenreName(String genreTagValue) { @@ -633,18 +655,23 @@ public class MediaScanner } final int length = genreTagValue.length(); - if (length > 0 && genreTagValue.charAt(0) == '(') { + if (length > 0) { + boolean parenthesized = false; StringBuffer number = new StringBuffer(); - int i = 1; - for (; i < length - 1; ++i) { + int i = 0; + for (; i < length; ++i) { char c = genreTagValue.charAt(i); - if (Character.isDigit(c)) { + if (i == 0 && c == '(') { + parenthesized = true; + } else if (Character.isDigit(c)) { number.append(c); } else { break; } } - if (genreTagValue.charAt(i) == ')') { + char charAfterNumber = i < length ? genreTagValue.charAt(i) : ' '; + if ((parenthesized && charAfterNumber == ')') + || !parenthesized && Character.isWhitespace(charAfterNumber)) { try { short genreIndex = Short.parseShort(number.toString()); if (genreIndex >= 0) { @@ -655,7 +682,13 @@ public class MediaScanner } else if (genreIndex < 0xFF && (i + 1) < length) { // genre is valid but unknown, // if there is a string after the value we take it - return genreTagValue.substring(i + 1); + if (parenthesized && charAfterNumber == ')') { + i++; + } + String ret = genreTagValue.substring(i).trim(); + if (ret.length() != 0) { + return ret; + } } else { // else return the number, without parentheses return number.toString(); @@ -855,6 +888,7 @@ public class MediaScanner } } Uri result = null; + boolean needToSetSettings = false; if (rowId == 0) { if (mMtpObjectHandle != 0) { values.put(MediaStore.MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, mMtpObjectHandle); @@ -866,12 +900,37 @@ public class MediaScanner } values.put(Files.FileColumns.FORMAT, format); } + // Setting a flag in order not to use bulk insert for the file related with + // notifications, ringtones, and alarms, because the rowId of the inserted file is + // needed. + if (mWasEmptyPriorToScan) { + if (notifications && !mDefaultNotificationSet) { + if (TextUtils.isEmpty(mDefaultNotificationFilename) || + doesPathHaveFilename(entry.mPath, mDefaultNotificationFilename)) { + needToSetSettings = true; + } + } else if (ringtones && !mDefaultRingtoneSet) { + if (TextUtils.isEmpty(mDefaultRingtoneFilename) || + doesPathHaveFilename(entry.mPath, mDefaultRingtoneFilename)) { + needToSetSettings = true; + } + } else if (alarms && !mDefaultAlarmSet) { + if (TextUtils.isEmpty(mDefaultAlarmAlertFilename) || + doesPathHaveFilename(entry.mPath, mDefaultAlarmAlertFilename)) { + needToSetSettings = true; + } + } + } + // new file, insert it // We insert directories immediately to ensure they are in the database // before the files they contain. // Otherwise we can get duplicate directory entries in the database // if one of the media FileInserters is flushed before the files table FileInserter - if (inserter == null || entry.mFormat == MtpConstants.FORMAT_ASSOCIATION) { + // Also, we immediately insert the file if the rowId of the inserted file is + // needed. + if (inserter == null || needToSetSettings || + entry.mFormat == MtpConstants.FORMAT_ASSOCIATION) { result = mMediaProvider.insert(tableUri, values); } else { inserter.insert(tableUri, values); @@ -887,24 +946,33 @@ public class MediaScanner // path should never change, and we want to avoid replacing mixed cased paths // with squashed lower case paths values.remove(MediaStore.MediaColumns.DATA); + + int mediaType = 0; + if (!MediaScanner.isNoMediaPath(entry.mPath)) { + int fileType = MediaFile.getFileTypeForMimeType(mMimeType); + if (MediaFile.isAudioFileType(fileType)) { + mediaType = FileColumns.MEDIA_TYPE_AUDIO; + } else if (MediaFile.isVideoFileType(fileType)) { + mediaType = FileColumns.MEDIA_TYPE_VIDEO; + } else if (MediaFile.isImageFileType(fileType)) { + mediaType = FileColumns.MEDIA_TYPE_IMAGE; + } else if (MediaFile.isPlayListFileType(fileType)) { + mediaType = FileColumns.MEDIA_TYPE_PLAYLIST; + } + values.put(FileColumns.MEDIA_TYPE, mediaType); + } + mMediaProvider.update(result, values, null, null); } - if (notifications && mWasEmptyPriorToScan && !mDefaultNotificationSet) { - if (TextUtils.isEmpty(mDefaultNotificationFilename) || - doesPathHaveFilename(entry.mPath, mDefaultNotificationFilename)) { + if(needToSetSettings) { + if (notifications) { setSettingIfNotSet(Settings.System.NOTIFICATION_SOUND, tableUri, rowId); mDefaultNotificationSet = true; - } - } else if (ringtones && mWasEmptyPriorToScan && !mDefaultRingtoneSet) { - if (TextUtils.isEmpty(mDefaultRingtoneFilename) || - doesPathHaveFilename(entry.mPath, mDefaultRingtoneFilename)) { + } else if (ringtones) { setSettingIfNotSet(Settings.System.RINGTONE, tableUri, rowId); mDefaultRingtoneSet = true; - } - } else if (alarms && mWasEmptyPriorToScan && !mDefaultAlarmSet) { - if (TextUtils.isEmpty(mDefaultAlarmAlertFilename) || - doesPathHaveFilename(entry.mPath, mDefaultAlarmAlertFilename)) { + } else if (alarms) { setSettingIfNotSet(Settings.System.ALARM_ALERT, tableUri, rowId); mDefaultAlarmSet = true; } @@ -983,7 +1051,7 @@ public class MediaScanner // First read existing files from the files table c = mMediaProvider.query(mFilesUri, FILES_PRESCAN_PROJECTION, - where, selectionArgs, null); + where, selectionArgs, null, null); if (c != null) { mWasEmptyPriorToScan = c.getCount() == 0; @@ -1020,7 +1088,7 @@ public class MediaScanner // compute original size of images mOriginalCount = 0; - c = mMediaProvider.query(mImagesUri, ID_PROJECTION, null, null, null); + c = mMediaProvider.query(mImagesUri, ID_PROJECTION, null, null, null, null); if (c != null) { mOriginalCount = c.getCount(); c.close(); @@ -1055,7 +1123,7 @@ public class MediaScanner new String [] { "_data" }, null, null, - null); + null, null); Log.v(TAG, "pruneDeadThumbnailFiles... " + c); if (c != null && c.moveToFirst()) { do { @@ -1128,6 +1196,10 @@ public class MediaScanner mMediaProvider.delete(ContentUris.withAppendedId(mFilesUri, entry.mRowId), null, null); iterator.remove(); + if (entry.mPath.toLowerCase(Locale.US).endsWith("/.nomedia")) { + File f = new File(path); + mMediaProvider.call(MediaStore.UNHIDE_CALL, f.getParent(), null); + } } } } @@ -1420,7 +1492,7 @@ public class MediaScanner if (bestMatch.mRowId == 0) { Cursor c = mMediaProvider.query(mAudioUri, ID_PROJECTION, MediaStore.Files.FileColumns.DATA + "=?", - new String[] { bestMatch.mPath }, null); + new String[] { bestMatch.mPath }, null, null); if (c != null) { if (c.moveToNext()) { bestMatch.mRowId = c.getLong(0); diff --git a/media/java/android/media/MediaScannerConnection.java b/media/java/android/media/MediaScannerConnection.java index 969da39..21b6e14 100644 --- a/media/java/android/media/MediaScannerConnection.java +++ b/media/java/android/media/MediaScannerConnection.java @@ -46,7 +46,7 @@ public class MediaScannerConnection implements ServiceConnection { private IMediaScannerService mService; private boolean mConnected; // true if connect() has been called since last disconnect() - private IMediaScannerListener.Stub mListener = new IMediaScannerListener.Stub() { + private final IMediaScannerListener.Stub mListener = new IMediaScannerListener.Stub() { public void scanCompleted(String path, Uri uri) { MediaScannerConnectionClient client = mClient; if (client != null) { diff --git a/media/java/android/media/MiniThumbFile.java b/media/java/android/media/MiniThumbFile.java index df141c1..63b149c 100644 --- a/media/java/android/media/MiniThumbFile.java +++ b/media/java/android/media/MiniThumbFile.java @@ -52,7 +52,7 @@ public class MiniThumbFile { private RandomAccessFile mMiniThumbFile; private FileChannel mChannel; private ByteBuffer mBuffer; - private static Hashtable<String, MiniThumbFile> sThumbFiles = + private static final Hashtable<String, MiniThumbFile> sThumbFiles = new Hashtable<String, MiniThumbFile>(); /** diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java index 77acfe6..18b4ee6 100644 --- a/media/java/android/media/RemoteControlClient.java +++ b/media/java/android/media/RemoteControlClient.java @@ -576,6 +576,7 @@ public class RemoteControlClient /** * Cache for the metadata strings. * Access synchronized on mCacheLock + * This is re-initialized in apply() and so cannot be final. */ private Bundle mMetadata = new Bundle(); @@ -621,7 +622,7 @@ public class RemoteControlClient /** * The IRemoteControlClient implementation */ - private IRemoteControlClient mIRCC = new IRemoteControlClient.Stub() { + private final IRemoteControlClient mIRCC = new IRemoteControlClient.Stub() { public void onInformationRequested(int clientGeneration, int infoFlags, int artWidth, int artHeight) { diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index 9c0819f..7aaf4aa 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -224,7 +224,7 @@ public class RingtoneManager { * If a column (item from this list) exists in the Cursor, its value must * be true (value of 1) for the row to be returned. */ - private List<String> mFilterColumns = new ArrayList<String>(); + private final List<String> mFilterColumns = new ArrayList<String>(); private boolean mStopPreviousRingtone = true; private Ringtone mPreviousRingtone; diff --git a/media/java/android/media/SoundPool.java b/media/java/android/media/SoundPool.java index 5e9c018..0f68e98 100644 --- a/media/java/android/media/SoundPool.java +++ b/media/java/android/media/SoundPool.java @@ -161,12 +161,10 @@ public class SoundPool int id = 0; try { File f = new File(path); - if (f != null) { - ParcelFileDescriptor fd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY); - if (fd != null) { - id = _load(fd.getFileDescriptor(), 0, f.length(), priority); - fd.close(); - } + ParcelFileDescriptor fd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY); + if (fd != null) { + id = _load(fd.getFileDescriptor(), 0, f.length(), priority); + fd.close(); } } catch (java.io.IOException e) { Log.e(TAG, "error loading " + path); diff --git a/media/java/android/media/ThumbnailUtils.java b/media/java/android/media/ThumbnailUtils.java index 078d4af..8eb9332 100644 --- a/media/java/android/media/ThumbnailUtils.java +++ b/media/java/android/media/ThumbnailUtils.java @@ -104,8 +104,10 @@ public class ThumbnailUtils { } if (bitmap == null) { + FileInputStream stream = null; try { - FileDescriptor fd = new FileInputStream(filePath).getFD(); + stream = new FileInputStream(filePath); + FileDescriptor fd = stream.getFD(); BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 1; options.inJustDecodeBounds = true; @@ -125,7 +127,16 @@ public class ThumbnailUtils { Log.e(TAG, "", ex); } catch (OutOfMemoryError oom) { Log.e(TAG, "Unable to decode file " + filePath + ". OutOfMemoryError.", oom); + } finally { + try { + if (stream != null) { + stream.close(); + } + } catch (IOException ex) { + Log.e(TAG, "", ex); + } } + } if (kind == Images.Thumbnails.MICRO_KIND) { @@ -472,9 +483,7 @@ public class ThumbnailUtils { byte [] thumbData = null; try { exif = new ExifInterface(filePath); - if (exif != null) { - thumbData = exif.getThumbnail(); - } + thumbData = exif.getThumbnail(); } catch (IOException ex) { Log.w(TAG, ex); } diff --git a/media/java/android/media/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java index 673f9f4..85be267 100644 --- a/media/java/android/media/audiofx/AudioEffect.java +++ b/media/java/android/media/audiofx/AudioEffect.java @@ -386,7 +386,7 @@ public class AudioEffect { default: throw (new RuntimeException( "Cannot initialize effect engine for type: " + type - + "Error: " + initResult)); + + " Error: " + initResult)); } } mId = id[0]; diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java index 19db1c0..18aa4b3 100755 --- a/media/java/android/mtp/MtpDatabase.java +++ b/media/java/android/mtp/MtpDatabase.java @@ -266,7 +266,7 @@ public class MtpDatabase { Cursor c = null; try { c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, PATH_WHERE, - new String[] { path }, null); + new String[] { path }, null, null); if (c != null && c.getCount() > 0) { Log.w(TAG, "file already exists in beginSendObject: " + path); return -1; @@ -433,7 +433,7 @@ public class MtpDatabase { } } - return mMediaProvider.query(mObjectsUri, ID_PROJECTION, where, whereArgs, null); + return mMediaProvider.query(mObjectsUri, ID_PROJECTION, where, whereArgs, null, null); } private int[] getObjectList(int storageID, int format, int parent) { @@ -699,7 +699,7 @@ public class MtpDatabase { String path = null; String[] whereArgs = new String[] { Integer.toString(handle) }; try { - c = mMediaProvider.query(mObjectsUri, PATH_PROJECTION, ID_WHERE, whereArgs, null); + c = mMediaProvider.query(mObjectsUri, PATH_PROJECTION, ID_WHERE, whereArgs, null, null); if (c != null && c.moveToNext()) { path = c.getString(1); } @@ -752,6 +752,29 @@ public class MtpDatabase { return MtpConstants.RESPONSE_GENERAL_ERROR; } + // check if nomedia status changed + if (newFile.isDirectory()) { + // for directories, check if renamed from something hidden to something non-hidden + if (oldFile.getName().startsWith(".") && !newPath.startsWith(".")) { + // directory was unhidden + try { + mMediaProvider.call(MediaStore.UNHIDE_CALL, newPath, null); + } catch (RemoteException e) { + Log.e(TAG, "failed to unhide/rescan for " + newPath); + } + } + } else { + // for files, check if renamed from .nomedia to something else + if (oldFile.getName().toLowerCase(Locale.US).equals(".nomedia") + && !newPath.toLowerCase(Locale.US).equals(".nomedia")) { + try { + mMediaProvider.call(MediaStore.UNHIDE_CALL, oldFile.getParent(), null); + } catch (RemoteException e) { + Log.e(TAG, "failed to unhide/rescan for " + newPath); + } + } + } + return MtpConstants.RESPONSE_OK; } @@ -815,7 +838,7 @@ public class MtpDatabase { Cursor c = null; try { c = mMediaProvider.query(mObjectsUri, OBJECT_INFO_PROJECTION, - ID_WHERE, new String[] { Integer.toString(handle) }, null); + ID_WHERE, new String[] { Integer.toString(handle) }, null, null); if (c != null && c.moveToNext()) { outStorageFormatParent[0] = c.getInt(1); outStorageFormatParent[1] = c.getInt(2); @@ -858,7 +881,7 @@ public class MtpDatabase { Cursor c = null; try { c = mMediaProvider.query(mObjectsUri, PATH_SIZE_FORMAT_PROJECTION, - ID_WHERE, new String[] { Integer.toString(handle) }, null); + ID_WHERE, new String[] { Integer.toString(handle) }, null, null); if (c != null && c.moveToNext()) { String path = c.getString(1); path.getChars(0, path.length(), outFilePath, 0); @@ -887,7 +910,7 @@ public class MtpDatabase { Cursor c = null; try { c = mMediaProvider.query(mObjectsUri, PATH_SIZE_FORMAT_PROJECTION, - ID_WHERE, new String[] { Integer.toString(handle) }, null); + ID_WHERE, new String[] { Integer.toString(handle) }, null, null); if (c != null && c.moveToNext()) { // don't convert to media path here, since we will be matching // against paths in the database matching /data/media @@ -915,6 +938,15 @@ public class MtpDatabase { Uri uri = Files.getMtpObjectsUri(mVolumeName, handle); if (mMediaProvider.delete(uri, null, null) > 0) { + if (format != MtpConstants.FORMAT_ASSOCIATION + && path.toLowerCase(Locale.US).endsWith("/.nomedia")) { + try { + String parentPath = path.substring(0, path.lastIndexOf("/")); + mMediaProvider.call(MediaStore.UNHIDE_CALL, parentPath, null); + } catch (RemoteException e) { + Log.e(TAG, "failed to unhide/rescan for " + path); + } + } return MtpConstants.RESPONSE_OK; } else { return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; @@ -933,7 +965,7 @@ public class MtpDatabase { Uri uri = Files.getMtpReferencesUri(mVolumeName, handle); Cursor c = null; try { - c = mMediaProvider.query(uri, ID_PROJECTION, null, null, null); + c = mMediaProvider.query(uri, ID_PROJECTION, null, null, null, null); if (c == null) { return null; } diff --git a/media/java/android/mtp/MtpPropertyGroup.java b/media/java/android/mtp/MtpPropertyGroup.java index 76c8569..dab5454 100644 --- a/media/java/android/mtp/MtpPropertyGroup.java +++ b/media/java/android/mtp/MtpPropertyGroup.java @@ -191,7 +191,7 @@ class MtpPropertyGroup { // for now we are only reading properties from the "objects" table c = mProvider.query(mUri, new String [] { Files.FileColumns._ID, column }, - ID_WHERE, new String[] { Integer.toString(id) }, null); + ID_WHERE, new String[] { Integer.toString(id) }, null, null); if (c != null && c.moveToNext()) { return c.getString(1); } else { @@ -211,7 +211,7 @@ class MtpPropertyGroup { try { c = mProvider.query(Audio.Media.getContentUri(mVolumeName), new String [] { Files.FileColumns._ID, column }, - ID_WHERE, new String[] { Integer.toString(id) }, null); + ID_WHERE, new String[] { Integer.toString(id) }, null, null); if (c != null && c.moveToNext()) { return c.getString(1); } else { @@ -232,7 +232,7 @@ class MtpPropertyGroup { Uri uri = Audio.Genres.getContentUriForAudioId(mVolumeName, id); c = mProvider.query(uri, new String [] { Files.FileColumns._ID, Audio.GenresColumns.NAME }, - null, null, null); + null, null, null, null); if (c != null && c.moveToNext()) { return c.getString(1); } else { @@ -254,7 +254,7 @@ class MtpPropertyGroup { // for now we are only reading properties from the "objects" table c = mProvider.query(mUri, new String [] { Files.FileColumns._ID, column }, - ID_WHERE, new String[] { Integer.toString(id) }, null); + ID_WHERE, new String[] { Integer.toString(id) }, null, null); if (c != null && c.moveToNext()) { return new Long(c.getLong(1)); } @@ -323,7 +323,7 @@ class MtpPropertyGroup { try { // don't query if not necessary if (depth > 0 || handle == 0xFFFFFFFF || mColumns.length > 1) { - c = mProvider.query(mUri, mColumns, where, whereArgs, null); + c = mProvider.query(mUri, mColumns, where, whereArgs, null, null); if (c == null) { return new MtpPropertyList(0, MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE); } |