diff options
Diffstat (limited to 'media/java')
| -rw-r--r-- | media/java/android/media/AudioManager.java | 79 | ||||
| -rw-r--r-- | media/java/android/media/AudioService.java | 671 | ||||
| -rw-r--r-- | media/java/android/media/AudioSystem.java | 64 | ||||
| -rw-r--r-- | media/java/android/media/DataSource.java | 43 | ||||
| -rw-r--r-- | media/java/android/media/IAudioService.aidl | 2 | ||||
| -rw-r--r-- | media/java/android/media/MediaExtractor.java | 9 | ||||
| -rw-r--r-- | media/java/android/media/MediaFile.java | 2 | ||||
| -rw-r--r-- | media/java/android/media/MediaMetadataRetriever.java | 5 | ||||
| -rw-r--r-- | media/java/android/media/MediaPlayer.java | 26 | ||||
| -rw-r--r-- | media/java/android/media/MediaRecorder.java | 21 | ||||
| -rw-r--r-- | media/java/android/media/MediaScanner.java | 64 | ||||
| -rw-r--r-- | media/java/android/media/RemoteDisplay.java | 153 | ||||
| -rwxr-xr-x | media/java/android/mtp/MtpDatabase.java | 29 |
13 files changed, 955 insertions, 213 deletions
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index b6e4659..ee17bd3 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -49,6 +49,7 @@ public class AudioManager { private final Context mContext; private long mVolumeKeyUpTime; private final boolean mUseMasterVolume; + private final boolean mUseVolumeKeySounds; private static String TAG = "AudioManager"; /** @@ -313,6 +314,13 @@ public class AudioManager { public static final int FLAG_VIBRATE = 1 << 4; /** + * Indicates to VolumePanel that the volume slider should be disabled as user + * cannot change the stream volume + * @hide + */ + public static final int FLAG_FIXED_VOLUME = 1 << 5; + + /** * Ringer mode that will be silent and will not vibrate. (This overrides the * vibrate setting.) * @@ -412,6 +420,8 @@ public class AudioManager { mContext = context; mUseMasterVolume = mContext.getResources().getBoolean( com.android.internal.R.bool.config_useMasterVolume); + mUseVolumeKeySounds = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_useVolumeKeySounds); } private static IAudioService getService() @@ -463,6 +473,7 @@ public class AudioManager { * responsive to the user. */ int flags = FLAG_SHOW_UI | FLAG_VIBRATE; + if (mUseMasterVolume) { adjustMasterVolume( keyCode == KeyEvent.KEYCODE_VOLUME_UP @@ -502,18 +513,17 @@ public class AudioManager { * Play a sound. This is done on key up since we don't want the * sound to play when a user holds down volume down to mute. */ - if (mUseMasterVolume) { - if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { + if (mUseVolumeKeySounds) { + if (mUseMasterVolume) { adjustMasterVolume(ADJUST_SAME, FLAG_PLAY_SOUND); + } else { + int flags = FLAG_PLAY_SOUND; + adjustSuggestedStreamVolume( + ADJUST_SAME, + stream, + flags); } - } else { - int flags = FLAG_PLAY_SOUND; - adjustSuggestedStreamVolume( - ADJUST_SAME, - stream, - flags); } - mVolumeKeyUpTime = SystemClock.uptimeMillis(); break; } @@ -1161,7 +1171,7 @@ public class AudioManager { /** * Indicates if current platform supports use of SCO for off call use cases. * Application wanted to use bluetooth SCO audio when the phone is not in call - * must first call thsi method to make sure that the platform supports this + * must first call this method to make sure that the platform supports this * feature. * @return true if bluetooth SCO can be used for audio when not in call * false otherwise @@ -1297,6 +1307,19 @@ public class AudioManager { } /** + * @hide + * Signals whether remote submix audio rerouting is enabled. + */ + public void setRemoteSubmixOn(boolean on, int address) { + IAudioService service = getService(); + try { + service.setRemoteSubmixOn(on, address); + } catch (RemoteException e) { + Log.e(TAG, "Dead object in setRemoteSubmixOn", e); + } + } + + /** * Sets audio routing to the wired headset on or off. * * @param on set <var>true</var> to route audio to/from wired @@ -2427,4 +2450,40 @@ public class AudioManager { return null; } } + + /** + * Used as a key for {@link #getProperty} to request the native or optimal output sample rate + * for this device's primary output stream, in decimal Hz. + */ + public static final String PROPERTY_OUTPUT_SAMPLE_RATE = + "android.media.property.OUTPUT_SAMPLE_RATE"; + + /** + * Used as a key for {@link #getProperty} to request the native or optimal output buffer size + * for this device's primary output stream, in decimal PCM frames. + */ + public static final String PROPERTY_OUTPUT_FRAMES_PER_BUFFER = + "android.media.property.OUTPUT_FRAMES_PER_BUFFER"; + + /** + * Returns the value of the property with the specified key. + * @param key One of the strings corresponding to a property key: either + * {@link #PROPERTY_OUTPUT_SAMPLE_RATE} or + * {@link #PROPERTY_OUTPUT_FRAMES_PER_BUFFER} + * @return A string representing the associated value for that property key, + * or null if there is no value for that key. + */ + public String getProperty(String key) { + if (PROPERTY_OUTPUT_SAMPLE_RATE.equals(key)) { + int outputSampleRate = AudioSystem.getPrimaryOutputSamplingRate(); + return outputSampleRate > 0 ? Integer.toString(outputSampleRate) : null; + } else if (PROPERTY_OUTPUT_FRAMES_PER_BUFFER.equals(key)) { + int outputFramesPerBuffer = AudioSystem.getPrimaryOutputFrameCount(); + return outputFramesPerBuffer > 0 ? Integer.toString(outputFramesPerBuffer) : null; + } else { + // null or unknown key + return null; + } + } + } diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index 2e153dd..a754ef3 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -57,6 +57,7 @@ import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; +import android.os.UserHandle; import android.os.Vibrator; import android.provider.Settings; import android.provider.Settings.System; @@ -143,12 +144,17 @@ public class AudioService extends IAudioService.Stub implements OnFinished { private static final int MSG_REEVALUATE_REMOTE = 17; private static final int MSG_RCC_NEW_PLAYBACK_INFO = 18; private static final int MSG_RCC_NEW_VOLUME_OBS = 19; + private static final int MSG_SET_FORCE_BT_A2DP_USE = 20; // start of messages handled under wakelock // these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(), // and not with sendMsg(..., ..., SENDMSG_QUEUE, ...) - private static final int MSG_SET_WIRED_DEVICE_CONNECTION_STATE = 20; - private static final int MSG_SET_A2DP_CONNECTION_STATE = 21; + private static final int MSG_SET_WIRED_DEVICE_CONNECTION_STATE = 21; + private static final int MSG_SET_A2DP_CONNECTION_STATE = 22; // end of messages handled under wakelock + private static final int MSG_SET_RSX_CONNECTION_STATE = 23; // change remote submix connection + private static final int MSG_SET_FORCE_RSX_USE = 24; // force remote submix audio routing + private static final int MSG_CHECK_MUSIC_ACTIVE = 25; + private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 26; // flags for MSG_PERSIST_VOLUME indicating if current and/or last audible volume should be // persisted @@ -381,10 +387,12 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // message looper for SoundPool listener private Looper mSoundPoolLooper = null; // volume applied to sound played with playSoundEffect() - private static int SOUND_EFFECT_VOLUME_DB; - // getActiveStreamType() will return STREAM_NOTIFICATION during this period after a notification + private static int sSoundEffectVolumeDb; + // getActiveStreamType() will return: + // - STREAM_NOTIFICATION on tablets during this period after a notification stopped + // - STREAM_MUSIC on phones during this period after music or talkback/voice search prompt // stopped - private static final int NOTIFICATION_VOLUME_DELAY_MS = 5000; + private static final int DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS = 5000; // previous volume adjustment direction received by checkForRingerModeChange() private int mPrevVolDirection = AudioManager.ADJUST_SAME; // Keyguard manager proxy @@ -416,6 +424,11 @@ public class AudioService extends IAudioService.Stub implements OnFinished { */ public final static int STREAM_REMOTE_MUSIC = -200; + // Devices for which the volume is fixed and VolumePanel slider should be disabled + final int mFixedVolumeDevices = AudioSystem.DEVICE_OUT_AUX_DIGITAL | + AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET | + AudioSystem.DEVICE_OUT_ALL_USB; + /////////////////////////////////////////////////////////////////////////// // Construction /////////////////////////////////////////////////////////////////////////// @@ -426,6 +439,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mContentResolver = context.getContentResolver(); mVoiceCapable = mContext.getResources().getBoolean( com.android.internal.R.bool.config_voice_capable); + mSafeMediaVolumeIndex = mContext.getResources().getInteger( + com.android.internal.R.integer.config_safe_media_volume_index) * 10; PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent"); @@ -438,7 +453,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { "ro.config.vc_call_vol_steps", MAX_STREAM_VOLUME[AudioSystem.STREAM_VOICE_CALL]); - SOUND_EFFECT_VOLUME_DB = context.getResources().getInteger( + sSoundEffectVolumeDb = context.getResources().getInteger( com.android.internal.R.integer.config_soundEffectVolumeDb); mVolumePanel = new VolumePanel(context, this); @@ -450,6 +465,11 @@ public class AudioService extends IAudioService.Stub implements OnFinished { updateStreamVolumeAlias(false /*updateVolumes*/); createStreamStates(); + mSafeMediaVolumeEnabled = new Boolean(true); + synchronized (mSafeMediaVolumeEnabled) { + enforceSafeMediaVolume(); + } + mMediaServerOk = true; // Call setRingerModeInt() to apply correct mute @@ -469,6 +489,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { intentFilter.addAction(Intent.ACTION_BOOT_COMPLETED); intentFilter.addAction(Intent.ACTION_SCREEN_ON); intentFilter.addAction(Intent.ACTION_SCREEN_OFF); + intentFilter.addAction(Intent.ACTION_USER_SWITCHED); // Register a configuration change listener only if requested by system properties // to monitor orientation changes (off by default) @@ -597,7 +618,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { final ContentResolver cr = mContentResolver; int ringerModeFromSettings = - System.getInt(cr, System.MODE_RINGER, AudioManager.RINGER_MODE_NORMAL); + Settings.Global.getInt(cr, System.MODE_RINGER, AudioManager.RINGER_MODE_NORMAL); int ringerMode = ringerModeFromSettings; // sanity check in case the settings are restored from a device with incompatible // ringer modes @@ -608,7 +629,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { ringerMode = AudioManager.RINGER_MODE_SILENT; } if (ringerMode != ringerModeFromSettings) { - System.putInt(cr, System.MODE_RINGER, ringerMode); + Settings.Global.putInt(cr, System.MODE_RINGER, ringerMode); } synchronized(mSettingsLock) { mRingerMode = ringerMode; @@ -627,23 +648,30 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // make sure settings for ringer mode are consistent with device type: non voice capable // devices (tablets) include media stream in silent mode whereas phones don't. - mRingerModeAffectedStreams = Settings.System.getInt(cr, + mRingerModeAffectedStreams = Settings.System.getIntForUser(cr, Settings.System.MODE_RINGER_STREAMS_AFFECTED, ((1 << AudioSystem.STREAM_RING)|(1 << AudioSystem.STREAM_NOTIFICATION)| - (1 << AudioSystem.STREAM_SYSTEM)|(1 << AudioSystem.STREAM_SYSTEM_ENFORCED))); + (1 << AudioSystem.STREAM_SYSTEM)|(1 << AudioSystem.STREAM_SYSTEM_ENFORCED)), + UserHandle.USER_CURRENT); if (mVoiceCapable) { mRingerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_MUSIC); } else { mRingerModeAffectedStreams |= (1 << AudioSystem.STREAM_MUSIC); } - Settings.System.putInt(cr, - Settings.System.MODE_RINGER_STREAMS_AFFECTED, mRingerModeAffectedStreams); + Settings.System.putIntForUser(cr, + Settings.System.MODE_RINGER_STREAMS_AFFECTED, + mRingerModeAffectedStreams, + UserHandle.USER_CURRENT); - mMuteAffectedStreams = System.getInt(cr, + mMuteAffectedStreams = System.getIntForUser(cr, System.MUTE_STREAMS_AFFECTED, - ((1 << AudioSystem.STREAM_MUSIC)|(1 << AudioSystem.STREAM_RING)|(1 << AudioSystem.STREAM_SYSTEM))); + ((1 << AudioSystem.STREAM_MUSIC)| + (1 << AudioSystem.STREAM_RING)| + (1 << AudioSystem.STREAM_SYSTEM)), + UserHandle.USER_CURRENT); - boolean masterMute = System.getInt(cr, System.VOLUME_MASTER_MUTE, 0) == 1; + boolean masterMute = System.getIntForUser(cr, System.VOLUME_MASTER_MUTE, + 0, UserHandle.USER_CURRENT) == 1; AudioSystem.setMasterMute(masterMute); broadcastMasterMuteStatus(masterMute); @@ -734,58 +762,71 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // convert one UI step (+/-1) into a number of internal units on the stream alias int step = rescaleIndex(10, streamType, streamTypeAlias); - // If either the client forces allowing ringer modes for this adjustment, - // or the stream type is one that is affected by ringer modes - if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) || - (streamTypeAlias == getMasterStreamType())) { - int ringerMode = getRingerMode(); - // do not vibrate if already in vibrate mode - if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) { - flags &= ~AudioManager.FLAG_VIBRATE; - } - // Check if the ringer mode changes with this volume adjustment. If - // it does, it will handle adjusting the volume, so we won't below - adjustVolume = checkForRingerModeChange(aliasIndex, direction, step); - if ((streamTypeAlias == getMasterStreamType()) && - (mRingerMode == AudioManager.RINGER_MODE_SILENT)) { - streamState.setLastAudibleIndex(0, device); - } + if ((direction == AudioManager.ADJUST_RAISE) && + !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) { + return; } - // If stream is muted, adjust last audible index only int index; - final int oldIndex = mStreamStates[streamType].getIndex(device, - (mStreamStates[streamType].muteCount() != 0) /* lastAudible */); + int oldIndex; - if (streamState.muteCount() != 0) { - if (adjustVolume) { - // Post a persist volume msg - // no need to persist volume on all streams sharing the same alias - streamState.adjustLastAudibleIndex(direction * step, device); - sendMsg(mAudioHandler, - MSG_PERSIST_VOLUME, - SENDMSG_QUEUE, - PERSIST_LAST_AUDIBLE, - device, - streamState, - PERSIST_DELAY); - } - index = mStreamStates[streamType].getIndex(device, true /* lastAudible */); + if ((streamTypeAlias == AudioSystem.STREAM_MUSIC) && + ((device & mFixedVolumeDevices) != 0)) { + flags |= AudioManager.FLAG_FIXED_VOLUME; + index = mStreamStates[streamType].getMaxIndex(); + oldIndex = index; } else { - if (adjustVolume && streamState.adjustIndex(direction * step, 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_DEVICE_VOLUME, - SENDMSG_QUEUE, - device, - 0, - streamState, - 0); + // If either the client forces allowing ringer modes for this adjustment, + // or the stream type is one that is affected by ringer modes + if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) || + (streamTypeAlias == getMasterStreamType())) { + int ringerMode = getRingerMode(); + // do not vibrate if already in vibrate mode + if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) { + flags &= ~AudioManager.FLAG_VIBRATE; + } + // Check if the ringer mode changes with this volume adjustment. If + // it does, it will handle adjusting the volume, so we won't below + adjustVolume = checkForRingerModeChange(aliasIndex, direction, step); + if ((streamTypeAlias == getMasterStreamType()) && + (mRingerMode == AudioManager.RINGER_MODE_SILENT)) { + streamState.setLastAudibleIndex(0, device); + } } - index = mStreamStates[streamType].getIndex(device, false /* lastAudible */); - } + // If stream is muted, adjust last audible index only + oldIndex = mStreamStates[streamType].getIndex(device, + (mStreamStates[streamType].muteCount() != 0) /* lastAudible */); + + if (streamState.muteCount() != 0) { + if (adjustVolume) { + // Post a persist volume msg + // no need to persist volume on all streams sharing the same alias + streamState.adjustLastAudibleIndex(direction * step, device); + sendMsg(mAudioHandler, + MSG_PERSIST_VOLUME, + SENDMSG_QUEUE, + PERSIST_LAST_AUDIBLE, + device, + streamState, + PERSIST_DELAY); + } + index = mStreamStates[streamType].getIndex(device, true /* lastAudible */); + } else { + if (adjustVolume && streamState.adjustIndex(direction * step, 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_DEVICE_VOLUME, + SENDMSG_QUEUE, + device, + 0, + streamState, + 0); + } + index = mStreamStates[streamType].getIndex(device, false /* lastAudible */); + } + } sendVolumeUpdate(streamType, oldIndex, index, flags); } @@ -811,35 +852,47 @@ public class AudioService extends IAudioService.Stub implements OnFinished { VolumeStreamState streamState = mStreamStates[mStreamVolumeAlias[streamType]]; 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 */); + int oldIndex; - index = rescaleIndex(index * 10, streamType, mStreamVolumeAlias[streamType]); - - // setting volume on master stream type also controls silent mode - if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) || - (mStreamVolumeAlias[streamType] == getMasterStreamType())) { - int newRingerMode; - if (index == 0) { - newRingerMode = mHasVibrator ? AudioManager.RINGER_MODE_VIBRATE - : AudioManager.RINGER_MODE_SILENT; - setStreamVolumeInt(mStreamVolumeAlias[streamType], - index, - device, - false, - true); - } else { - newRingerMode = AudioManager.RINGER_MODE_NORMAL; + if ((mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) && + ((device & mFixedVolumeDevices) != 0)) { + flags |= AudioManager.FLAG_FIXED_VOLUME; + index = mStreamStates[streamType].getMaxIndex(); + oldIndex = index; + } else { + // get last audible index if stream is muted, current index otherwise + oldIndex = streamState.getIndex(device, + (streamState.muteCount() != 0) /* lastAudible */); + + index = rescaleIndex(index * 10, streamType, mStreamVolumeAlias[streamType]); + + if (!checkSafeMediaVolume(mStreamVolumeAlias[streamType], index, device)) { + return; } - setRingerMode(newRingerMode); - } - setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, false, true); - // get last audible index if stream is muted, current index otherwise - index = mStreamStates[streamType].getIndex(device, - (mStreamStates[streamType].muteCount() != 0) /* lastAudible */); + // setting volume on master stream type also controls silent mode + if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) || + (mStreamVolumeAlias[streamType] == getMasterStreamType())) { + int newRingerMode; + if (index == 0) { + newRingerMode = mHasVibrator ? AudioManager.RINGER_MODE_VIBRATE + : AudioManager.RINGER_MODE_SILENT; + setStreamVolumeInt(mStreamVolumeAlias[streamType], + index, + device, + false, + true); + } else { + newRingerMode = AudioManager.RINGER_MODE_NORMAL; + } + setRingerMode(newRingerMode); + } + setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, false, true); + // get last audible index if stream is muted, current index otherwise + index = mStreamStates[streamType].getIndex(device, + (mStreamStates[streamType].muteCount() != 0) /* lastAudible */); + } sendVolumeUpdate(streamType, oldIndex, index, flags); } @@ -929,6 +982,24 @@ public class AudioService extends IAudioService.Stub implements OnFinished { return delta; } + private void sendBroadcastToAll(Intent intent) { + final long ident = Binder.clearCallingIdentity(); + try { + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void sendStickyBroadcastToAll(Intent intent) { + final long ident = Binder.clearCallingIdentity(); + try { + mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + // UI update and Broadcast Intent private void sendVolumeUpdate(int streamType, int oldIndex, int index, int flags) { if (!mVoiceCapable && (streamType == AudioSystem.STREAM_RING)) { @@ -937,13 +1008,15 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mVolumePanel.postVolumeChanged(streamType, flags); - oldIndex = (oldIndex + 5) / 10; - index = (index + 5) / 10; - Intent intent = new Intent(AudioManager.VOLUME_CHANGED_ACTION); - intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, streamType); - intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index); - intent.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex); - mContext.sendBroadcast(intent); + if ((flags & AudioManager.FLAG_FIXED_VOLUME) == 0) { + oldIndex = (oldIndex + 5) / 10; + index = (index + 5) / 10; + Intent intent = new Intent(AudioManager.VOLUME_CHANGED_ACTION); + intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, streamType); + intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index); + intent.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex); + sendBroadcastToAll(intent); + } } // UI update and Broadcast Intent @@ -953,7 +1026,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { Intent intent = new Intent(AudioManager.MASTER_VOLUME_CHANGED_ACTION); intent.putExtra(AudioManager.EXTRA_PREV_MASTER_VOLUME_VALUE, oldVolume); intent.putExtra(AudioManager.EXTRA_MASTER_VOLUME_VALUE, newVolume); - mContext.sendBroadcast(intent); + sendBroadcastToAll(intent); } // UI update and Broadcast Intent @@ -967,9 +1040,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { intent.putExtra(AudioManager.EXTRA_MASTER_VOLUME_MUTED, muted); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | Intent.FLAG_RECEIVER_REPLACE_PENDING); - long origCallerIdentityToken = Binder.clearCallingIdentity(); - mContext.sendStickyBroadcast(intent); - Binder.restoreCallingIdentity(origCallerIdentityToken); + sendStickyBroadcastToAll(intent); } /** @@ -1060,7 +1131,15 @@ public class AudioService extends IAudioService.Stub implements OnFinished { public int getStreamVolume(int streamType) { ensureValidStreamType(streamType); int device = getDeviceForStream(streamType); - return (mStreamStates[streamType].getIndex(device, false /* lastAudible */) + 5) / 10; + int index; + + if ((mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) && + (device & mFixedVolumeDevices) != 0) { + index = mStreamStates[streamType].getMaxIndex(); + } else { + index = mStreamStates[streamType].getIndex(device, false /* lastAudible */); + } + return (index + 5) / 10; } public int getMasterVolume() { @@ -1200,8 +1279,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { private void restoreMasterVolume() { if (mUseMasterVolume) { - float volume = Settings.System.getFloat(mContentResolver, - Settings.System.VOLUME_MASTER, -1.0f); + float volume = Settings.System.getFloatForUser(mContentResolver, + Settings.System.VOLUME_MASTER, -1.0f, UserHandle.USER_CURRENT); if (volume >= 0.0f) { AudioSystem.setMasterVolume(volume); } @@ -1637,6 +1716,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished { /** @see AudioManager#reloadAudioSettings() */ public void reloadAudioSettings() { + readAudioSettings(false /*userSwitch*/); + } + + private void readAudioSettings(boolean userSwitch) { // restore ringer mode, ringer mode affected streams, mute affected streams and vibrate settings readPersistedSettings(); @@ -1645,6 +1728,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished { for (int streamType = 0; streamType < numStreamTypes; streamType++) { VolumeStreamState streamState = mStreamStates[streamType]; + if (userSwitch && mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) { + continue; + } + synchronized (streamState) { streamState.readSettings(); @@ -1661,6 +1748,12 @@ public class AudioService extends IAudioService.Stub implements OnFinished { checkAllAliasStreamVolumes(); + synchronized (mSafeMediaVolumeEnabled) { + if (mSafeMediaVolumeEnabled) { + enforceSafeMediaVolume(); + } + } + // apply new ringer mode setRingerModeInt(getRingerMode(), false); } @@ -1701,7 +1794,13 @@ public class AudioService extends IAudioService.Stub implements OnFinished { /** @see AudioManager#setBluetoothA2dpOn() */ public void setBluetoothA2dpOn(boolean on) { - setBluetoothA2dpOnInt(on); + synchronized (mBluetoothA2dpEnabledLock) { + mBluetoothA2dpEnabled = on; + sendMsg(mAudioHandler, MSG_SET_FORCE_BT_A2DP_USE, SENDMSG_QUEUE, + AudioSystem.FOR_MEDIA, + mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP, + null, 0); + } } /** @see AudioManager#isBluetoothA2dpOn() */ @@ -1985,7 +2084,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state); newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE, mScoConnectionState); - mContext.sendStickyBroadcast(newIntent); + sendStickyBroadcastToAll(newIntent); mScoConnectionState = state; } } @@ -2085,6 +2184,63 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } }; + /** see AudioManager.setRemoteSubmixOn(boolean on) */ + public void setRemoteSubmixOn(boolean on, int address) { + sendMsg(mAudioHandler, MSG_SET_RSX_CONNECTION_STATE, + SENDMSG_REPLACE /* replace with QUEUE when multiple addresses are supported */, + on ? 1 : 0 /*arg1*/, + address /*arg2*/, + null/*obj*/, 0/*delay*/); + + // Note that we are currently forcing use of remote submix as soon as corresponding device + // is made available + sendMsg(mAudioHandler, MSG_SET_FORCE_RSX_USE, SENDMSG_REPLACE, + AudioSystem.FOR_MEDIA, + on ? AudioSystem.FORCE_REMOTE_SUBMIX : AudioSystem.FORCE_NONE, + null/*obj*/, 0/*delay*/); + } + + private void onSetRsxConnectionState(int available, int address) { + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_REMOTE_SUBMIX, + available == 1 ? + AudioSystem.DEVICE_STATE_AVAILABLE : AudioSystem.DEVICE_STATE_UNAVAILABLE, + String.valueOf(address) /*device_address*/); + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_REMOTE_SUBMIX, + available == 1 ? + AudioSystem.DEVICE_STATE_AVAILABLE : AudioSystem.DEVICE_STATE_UNAVAILABLE, + String.valueOf(address) /*device_address*/); + } + + private void onCheckMusicActive() { + synchronized (mSafeMediaVolumeEnabled) { + if (!mSafeMediaVolumeEnabled) { + int device = getDeviceForStream(AudioSystem.STREAM_MUSIC); + + if ((device & mSafeMediaVolumeDevices) != 0) { + sendMsg(mAudioHandler, + MSG_CHECK_MUSIC_ACTIVE, + SENDMSG_REPLACE, + 0, + 0, + null, + MUSIC_ACTIVE_POLL_PERIOD_MS); + int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device, + false /*lastAudible*/); + if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) && + (index > mSafeMediaVolumeIndex)) { + // Approximate cumulative active music time + mMusicActiveMs += MUSIC_ACTIVE_POLL_PERIOD_MS; + if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) { + setSafeMediaVolumeEnabled(true); + mMusicActiveMs = 0; + mVolumePanel.postDisplaySafeVolumeWarning(); + } + } + } + } + } + } + /////////////////////////////////////////////////////////////////////////// // Internal methods /////////////////////////////////////////////////////////////////////////// @@ -2217,7 +2373,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_REMOTE_MUSIC"); return STREAM_REMOTE_MUSIC; - } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) { + } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, + DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS)) { if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active"); return AudioSystem.STREAM_MUSIC; @@ -2246,9 +2403,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished { return AudioSystem.STREAM_VOICE_CALL; } } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_NOTIFICATION, - NOTIFICATION_VOLUME_DELAY_MS) || + DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS) || AudioSystem.isStreamActive(AudioSystem.STREAM_RING, - NOTIFICATION_VOLUME_DELAY_MS)) { + DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS)) { if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION"); return AudioSystem.STREAM_NOTIFICATION; } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) { @@ -2276,9 +2433,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { 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(); - mContext.sendStickyBroadcast(broadcast); - Binder.restoreCallingIdentity(origCallerIdentityToken); + sendStickyBroadcastToAll(broadcast); } private void broadcastVibrateSetting(int vibrateType) { @@ -2287,7 +2442,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { Intent broadcast = new Intent(AudioManager.VIBRATE_SETTING_CHANGED_ACTION); broadcast.putExtra(AudioManager.EXTRA_VIBRATE_TYPE, vibrateType); broadcast.putExtra(AudioManager.EXTRA_VIBRATE_SETTING, getVibrateSetting(vibrateType)); - mContext.sendBroadcast(broadcast); + sendBroadcastToAll(broadcast); } } @@ -2429,7 +2584,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // device, continue otherwise int defaultIndex = (device == AudioSystem.DEVICE_OUT_DEFAULT) ? AudioManager.DEFAULT_STREAM_VOLUME[mStreamType] : -1; - int index = Settings.System.getInt(mContentResolver, name, defaultIndex); + int index = Settings.System.getIntForUser( + mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT); if (index == -1) { continue; } @@ -2440,7 +2596,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // or default index defaultIndex = (index > 0) ? index : AudioManager.DEFAULT_STREAM_VOLUME[mStreamType]; - int lastAudibleIndex = Settings.System.getInt(mContentResolver, name, defaultIndex); + int lastAudibleIndex = Settings.System.getIntForUser( + mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT); // a last audible index of 0 should never be stored for ring and notification // streams on phones (voice capable devices). @@ -2852,19 +3009,21 @@ public class AudioService extends IAudioService.Stub implements OnFinished { int persistType, int device) { if ((persistType & PERSIST_CURRENT) != 0) { - System.putInt(mContentResolver, + System.putIntForUser(mContentResolver, streamState.getSettingNameForDevice(false /* lastAudible */, device), - (streamState.getIndex(device, false /* lastAudible */) + 5)/ 10); + (streamState.getIndex(device, false /* lastAudible */) + 5)/ 10, + UserHandle.USER_CURRENT); } if ((persistType & PERSIST_LAST_AUDIBLE) != 0) { - System.putInt(mContentResolver, + System.putIntForUser(mContentResolver, streamState.getSettingNameForDevice(true /* lastAudible */, device), - (streamState.getIndex(device, true /* lastAudible */) + 5) / 10); + (streamState.getIndex(device, true /* lastAudible */) + 5) / 10, + UserHandle.USER_CURRENT); } } private void persistRingerMode(int ringerMode) { - System.putInt(mContentResolver, System.MODE_RINGER, ringerMode); + Settings.Global.putInt(mContentResolver, System.MODE_RINGER, ringerMode); } private void playSoundEffect(int effectType, int volume) { @@ -2875,7 +3034,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { float volFloat; // use default if volume is not specified by caller if (volume < 0) { - volFloat = (float)Math.pow(10, SOUND_EFFECT_VOLUME_DB/20); + volFloat = (float)Math.pow(10, (float)sSoundEffectVolumeDb/20); } else { volFloat = (float) volume / 1000.0f; } @@ -2914,8 +3073,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } private void onHandlePersistMediaButtonReceiver(ComponentName receiver) { - Settings.System.putString(mContentResolver, Settings.System.MEDIA_BUTTON_RECEIVER, - receiver == null ? "" : receiver.flattenToString()); + Settings.System.putStringForUser(mContentResolver, + Settings.System.MEDIA_BUTTON_RECEIVER, + receiver == null ? "" : receiver.flattenToString(), + UserHandle.USER_CURRENT); } private void cleanupPlayer(MediaPlayer mp) { @@ -2951,13 +3112,17 @@ public class AudioService extends IAudioService.Stub implements OnFinished { break; case MSG_PERSIST_MASTER_VOLUME: - Settings.System.putFloat(mContentResolver, Settings.System.VOLUME_MASTER, - (float)msg.arg1 / (float)1000.0); + Settings.System.putFloatForUser(mContentResolver, + Settings.System.VOLUME_MASTER, + (float)msg.arg1 / (float)1000.0, + UserHandle.USER_CURRENT); break; case MSG_PERSIST_MASTER_VOLUME_MUTE: - Settings.System.putInt(mContentResolver, Settings.System.VOLUME_MASTER_MUTE, - msg.arg1); + Settings.System.putIntForUser(mContentResolver, + Settings.System.VOLUME_MASTER_MUTE, + msg.arg1, + UserHandle.USER_CURRENT); break; case MSG_PERSIST_RINGER_MODE: @@ -3049,6 +3214,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { break; case MSG_SET_FORCE_USE: + case MSG_SET_FORCE_BT_A2DP_USE: + case MSG_SET_FORCE_RSX_USE: setForceUse(msg.arg1, msg.arg2); break; @@ -3111,6 +3278,18 @@ public class AudioService extends IAudioService.Stub implements OnFinished { onRegisterVolumeObserverForRcc(msg.arg1 /* rccId */, (IRemoteVolumeObserver)msg.obj /* rvo */); break; + + case MSG_SET_RSX_CONNECTION_STATE: + onSetRsxConnectionState(msg.arg1/*available*/, msg.arg2/*address*/); + break; + + case MSG_CHECK_MUSIC_ACTIVE: + onCheckMusicActive(); + break; + + case MSG_BROADCAST_AUDIO_BECOMING_NOISY: + onSendBecomingNoisyIntent(); + break; } } } @@ -3131,10 +3310,11 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // 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, + int ringerModeAffectedStreams = Settings.System.getIntForUser(mContentResolver, Settings.System.MODE_RINGER_STREAMS_AFFECTED, ((1 << AudioSystem.STREAM_RING)|(1 << AudioSystem.STREAM_NOTIFICATION)| - (1 << AudioSystem.STREAM_SYSTEM)|(1 << AudioSystem.STREAM_SYSTEM_ENFORCED))); + (1 << AudioSystem.STREAM_SYSTEM)|(1 << AudioSystem.STREAM_SYSTEM_ENFORCED)), + UserHandle.USER_CURRENT); if (mVoiceCapable) { ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_MUSIC); } else { @@ -3166,8 +3346,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { address); } - private void sendBecomingNoisyIntent() { - mContext.sendBroadcast(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); + private void onSendBecomingNoisyIntent() { + sendBroadcastToAll(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); } // must be called synchronized on mConnectedDevices @@ -3286,7 +3466,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // sent if none of these devices is connected. int mBecomingNoisyIntentDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE | - AudioSystem.DEVICE_OUT_ALL_A2DP; + AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_AUX_DIGITAL | + AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET | + AudioSystem.DEVICE_OUT_ALL_USB; // must be called before removing the device from mConnectedDevices private int checkSendBecomingNoisyIntent(int device, int state) { @@ -3299,8 +3481,14 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } } if (devices == device) { + sendMsg(mAudioHandler, + MSG_BROADCAST_AUDIO_BECOMING_NOISY, + SENDMSG_REPLACE, + 0, + 0, + null, + 0); delay = 1000; - sendBecomingNoisyIntent(); } } @@ -3356,7 +3544,12 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } } - ActivityManagerNative.broadcastStickyIntent(intent, null); + final long ident = Binder.clearCallingIdentity(); + try { + ActivityManagerNative.broadcastStickyIntent(intent, null, UserHandle.USER_ALL); + } finally { + Binder.restoreCallingIdentity(ident); + } } private void onSetWiredDeviceConnectionState(int device, int state, String name) @@ -3366,12 +3559,26 @@ public class AudioService extends IAudioService.Stub implements OnFinished { (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE))) { setBluetoothA2dpOnInt(true); } - handleDeviceConnection((state == 1), device, ""); - if ((state != 0) && ((device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) || - (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE))) { - setBluetoothA2dpOnInt(false); + boolean isUsb = ((device & AudioSystem.DEVICE_OUT_ALL_USB) != 0); + handleDeviceConnection((state == 1), device, (isUsb ? name : "")); + if (state != 0) { + if ((device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) || + (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE)) { + setBluetoothA2dpOnInt(false); + } + if ((device & mSafeMediaVolumeDevices) != 0) { + sendMsg(mAudioHandler, + MSG_CHECK_MUSIC_ACTIVE, + SENDMSG_REPLACE, + 0, + 0, + null, + MUSIC_ACTIVE_POLL_PERIOD_MS); + } + } + if (!isUsb) { + sendDeviceConnectionIntent(device, state, name); } - sendDeviceConnectionIntent(device, state, name); } } @@ -3463,7 +3670,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { + (action.equals(Intent.ACTION_USB_AUDIO_ACCESSORY_PLUG) ? "ACTION_USB_AUDIO_ACCESSORY_PLUG" : "ACTION_USB_AUDIO_DEVICE_PLUG") + ", state = " + state + ", card: " + alsaCard + ", device: " + alsaDevice); - handleDeviceConnection((state == 1), device, params); + setWiredDeviceConnectionState(device, state, params); } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { boolean broadcast = false; int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR; @@ -3508,7 +3715,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate. Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED); newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState); - mContext.sendStickyBroadcast(newIntent); + sendStickyBroadcastToAll(newIntent); } } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { mBootCompleted = true; @@ -3525,7 +3732,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED); newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - mContext.sendStickyBroadcast(newIntent); + sendStickyBroadcastToAll(newIntent); BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); if (adapter != null) { @@ -3546,6 +3753,24 @@ public class AudioService extends IAudioService.Stub implements OnFinished { AudioSystem.setParameters("screen_state=off"); } else if (action.equalsIgnoreCase(Intent.ACTION_CONFIGURATION_CHANGED)) { handleConfigurationChanged(context); + } else if (action.equals(Intent.ACTION_USER_SWITCHED)) { + // attempt to stop music playabck for background user + sendMsg(mAudioHandler, + MSG_BROADCAST_AUDIO_BECOMING_NOISY, + SENDMSG_REPLACE, + 0, + 0, + null, + 0); + // load volume settings for new user + readAudioSettings(true /*userSwitch*/); + // preserve STREAM_MUSIC volume from one user to the next. + sendMsg(mAudioHandler, + MSG_SET_ALL_VOLUMES, + SENDMSG_QUEUE, + 0, + 0, + mStreamStates[AudioSystem.STREAM_MUSIC], 0); } } } @@ -3652,9 +3877,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished { Iterator<FocusStackEntry> stackIterator = mFocusStack.iterator(); while(stackIterator.hasNext()) { FocusStackEntry fse = stackIterator.next(); - pw.println(" source:" + fse.mSourceRef + " -- client: " + fse.mClientId + pw.println(" source:" + fse.mSourceRef + " -- client: " + fse.mClientId + " -- duration: " + fse.mFocusChangeType - + " -- uid: " + fse.mCallingUid); + + " -- uid: " + fse.mCallingUid + + " -- stream: " + fse.mStreamType); } } } @@ -3915,8 +4141,13 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mMediaEventWakeLock.acquire(); keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED); } - mContext.sendOrderedBroadcast(keyIntent, null, mKeyEventDone, - mAudioHandler, Activity.RESULT_OK, null, null); + final long ident = Binder.clearCallingIdentity(); + try { + mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL, + null, mKeyEventDone, mAudioHandler, Activity.RESULT_OK, null, null); + } finally { + Binder.restoreCallingIdentity(ident); + } } /** @@ -3949,8 +4180,14 @@ public class AudioService extends IAudioService.Stub implements OnFinished { if (needWakeLock) { keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED); } - mContext.sendOrderedBroadcast(keyIntent, null, mKeyEventDone, - mAudioHandler, Activity.RESULT_OK, null, null); + final long ident = Binder.clearCallingIdentity(); + try { + mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL, + null, mKeyEventDone, + mAudioHandler, Activity.RESULT_OK, null, null); + } finally { + Binder.restoreCallingIdentity(ident); + } } } } @@ -4014,7 +4251,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { startVoiceBasedInteractions(needWakeLock); break; case VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS: - if (DEBUG_RC) Log.v(TAG, " send simulated key event"); + if (DEBUG_RC) Log.v(TAG, " send simulated key event, wakelock=" + needWakeLock); sendSimulatedMediaButtonEvent(keyEvent, needWakeLock); break; } @@ -4350,7 +4587,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { " -- vol: " + rcse.mPlaybackVolume + " -- volMax: " + rcse.mPlaybackVolumeMax + " -- volObs: " + rcse.mRemoteVolumeObs); - + } } synchronized (mMainRemote) { @@ -4408,8 +4645,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { * Restore remote control receiver from the system settings. */ private void restoreMediaButtonReceiver() { - String receiverName = Settings.System.getString(mContentResolver, - Settings.System.MEDIA_BUTTON_RECEIVER); + String receiverName = Settings.System.getStringForUser(mContentResolver, + Settings.System.MEDIA_BUTTON_RECEIVER, UserHandle.USER_CURRENT); if ((null != receiverName) && !receiverName.isEmpty()) { ComponentName eventReceiver = ComponentName.unflattenFromString(receiverName); // construct a PendingIntent targeted to the restored component name @@ -4646,17 +4883,40 @@ public class AudioService extends IAudioService.Stub implements OnFinished { clearRemoteControlDisplay_syncAfRcs(); return; } - // if the top of the two stacks belong to different packages, there is a mismatch, clear + + // determine which entry in the AudioFocus stack to consider, and compare against the + // top of the stack for the media button event receivers : simply using the top of the + // stack would make the entry disappear from the RemoteControlDisplay in conditions such as + // notifications playing during music playback. + // crawl the AudioFocus stack until an entry is found with the following characteristics: + // - focus gain on STREAM_MUSIC stream + // - non-transient focus gain on a stream other than music + FocusStackEntry af = null; + Iterator<FocusStackEntry> stackIterator = mFocusStack.iterator(); + while(stackIterator.hasNext()) { + FocusStackEntry fse = (FocusStackEntry)stackIterator.next(); + if ((fse.mStreamType == AudioManager.STREAM_MUSIC) + || (fse.mFocusChangeType == AudioManager.AUDIOFOCUS_GAIN)) { + af = fse; + break; + } + } + if (af == null) { + clearRemoteControlDisplay_syncAfRcs(); + return; + } + + // if the audio focus and RC owners belong to different packages, there is a mismatch, clear if ((mRCStack.peek().mCallingPackageName != null) - && (mFocusStack.peek().mPackageName != null) + && (af.mPackageName != null) && !(mRCStack.peek().mCallingPackageName.compareTo( - mFocusStack.peek().mPackageName) == 0)) { + af.mPackageName) == 0)) { clearRemoteControlDisplay_syncAfRcs(); return; } // if the audio focus didn't originate from the same Uid as the one in which the remote // control information will be retrieved, clear - if (mRCStack.peek().mCallingUid != mFocusStack.peek().mCallingUid) { + if (mRCStack.peek().mCallingUid != af.mCallingUid) { clearRemoteControlDisplay_syncAfRcs(); return; } @@ -5290,10 +5550,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished { public void setBluetoothA2dpOnInt(boolean on) { synchronized (mBluetoothA2dpEnabledLock) { mBluetoothA2dpEnabled = on; - sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE, - AudioSystem.FOR_MEDIA, - mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP, - null, 0); + mAudioHandler.removeMessages(MSG_SET_FORCE_BT_A2DP_USE); + AudioSystem.setForceUse(AudioSystem.FOR_MEDIA, + mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP); } } @@ -5317,6 +5576,108 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } } + + //========================================================================================== + // Safe media volume management. + // MUSIC stream volume level is limited when headphones are connected according to safety + // regulation. When the user attempts to raise the volume above the limit, a warning is + // displayed and the user has to acknowlegde before the volume is actually changed. + // The volume index corresponding to the limit is stored in config_safe_media_volume_index + // property. Platforms with a different limit must set this property accordingly in their + // overlay. + //========================================================================================== + + // mSafeMediaVolumeEnabled indicates whether the media volume is limited over headphones. + // It is true by default when headphones or a headset are inserted and can be overriden by + // calling AudioService.disableSafeMediaVolume() (when user opts out). + private Boolean mSafeMediaVolumeEnabled; + // mSafeMediaVolumeIndex is the cached value of config_safe_media_volume_index property + private final int mSafeMediaVolumeIndex; + // mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced, + private final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET | + AudioSystem.DEVICE_OUT_WIRED_HEADPHONE; + // mMusicActiveMs is the cumulative time of music activity since safe volume was disabled. + // When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled + // automatically. mMusicActiveMs is rounded to a multiple of MUSIC_ACTIVE_POLL_PERIOD_MS. + private int mMusicActiveMs; + private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours + private static final int MUSIC_ACTIVE_POLL_PERIOD_MS = 60000; // 1 minute polling interval + + private void setSafeMediaVolumeEnabled(boolean on) { + synchronized (mSafeMediaVolumeEnabled) { + if (on && !mSafeMediaVolumeEnabled) { + enforceSafeMediaVolume(); + } else if (!on && mSafeMediaVolumeEnabled) { + mMusicActiveMs = 0; + sendMsg(mAudioHandler, + MSG_CHECK_MUSIC_ACTIVE, + SENDMSG_REPLACE, + 0, + 0, + null, + MUSIC_ACTIVE_POLL_PERIOD_MS); + } + mSafeMediaVolumeEnabled = on; + } + } + + private void enforceSafeMediaVolume() { + VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC]; + boolean lastAudible = (streamState.muteCount() != 0); + int devices = mSafeMediaVolumeDevices; + int i = 0; + + while (devices != 0) { + int device = 1 << i++; + if ((device & devices) == 0) { + continue; + } + int index = streamState.getIndex(device, lastAudible); + if (index > mSafeMediaVolumeIndex) { + if (lastAudible) { + streamState.setLastAudibleIndex(mSafeMediaVolumeIndex, device); + sendMsg(mAudioHandler, + MSG_PERSIST_VOLUME, + SENDMSG_QUEUE, + PERSIST_LAST_AUDIBLE, + device, + streamState, + PERSIST_DELAY); + } else { + streamState.setIndex(mSafeMediaVolumeIndex, device, true); + sendMsg(mAudioHandler, + MSG_SET_DEVICE_VOLUME, + SENDMSG_QUEUE, + device, + 0, + streamState, + 0); + } + } + devices &= ~device; + } + } + + private boolean checkSafeMediaVolume(int streamType, int index, int device) { + synchronized (mSafeMediaVolumeEnabled) { + if (mSafeMediaVolumeEnabled && + (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) && + ((device & mSafeMediaVolumeDevices) != 0) && + (index > mSafeMediaVolumeIndex)) { + mVolumePanel.postDisplaySafeVolumeWarning(); + return false; + } + return true; + } + } + + public void disableSafeMediaVolume() { + synchronized (mSafeMediaVolumeEnabled) { + setSafeMediaVolumeEnabled(false); + } + } + + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 1ca0df4..2cff4ff 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -188,6 +188,13 @@ public class AudioSystem * AudioPolicyService methods */ + // + // audio device definitions: must be kept in sync with values in system/core/audio.h + // + + // reserved bits + public static final int DEVICE_BIT_IN = 0x80000000; + public static final int DEVICE_BIT_DEFAULT = 0x40000000; // output devices, be sure to update AudioManager.java also public static final int DEVICE_OUT_EARPIECE = 0x1; public static final int DEVICE_OUT_SPEAKER = 0x2; @@ -204,8 +211,10 @@ public class AudioSystem public static final int DEVICE_OUT_DGTL_DOCK_HEADSET = 0x1000; public static final int DEVICE_OUT_USB_ACCESSORY = 0x2000; public static final int DEVICE_OUT_USB_DEVICE = 0x4000; + public static final int DEVICE_OUT_REMOTE_SUBMIX = 0x8000; + + public static final int DEVICE_OUT_DEFAULT = DEVICE_BIT_DEFAULT; - 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 | @@ -221,6 +230,7 @@ public class AudioSystem DEVICE_OUT_DGTL_DOCK_HEADSET | DEVICE_OUT_USB_ACCESSORY | DEVICE_OUT_USB_DEVICE | + DEVICE_OUT_REMOTE_SUBMIX | DEVICE_OUT_DEFAULT); public static final int DEVICE_OUT_ALL_A2DP = (DEVICE_OUT_BLUETOOTH_A2DP | DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES | @@ -232,15 +242,36 @@ public class AudioSystem DEVICE_OUT_USB_DEVICE); // input devices - public static final int DEVICE_IN_COMMUNICATION = 0x10000; - public static final int DEVICE_IN_AMBIENT = 0x20000; - public static final int DEVICE_IN_BUILTIN_MIC1 = 0x40000; - public static final int DEVICE_IN_BUILTIN_MIC2 = 0x80000; - public static final int DEVICE_IN_MIC_ARRAY = 0x100000; - public static final int DEVICE_IN_BLUETOOTH_SCO_HEADSET = 0x200000; - public static final int DEVICE_IN_WIRED_HEADSET = 0x400000; - public static final int DEVICE_IN_AUX_DIGITAL = 0x800000; - public static final int DEVICE_IN_DEFAULT = 0x80000000; + public static final int DEVICE_IN_COMMUNICATION = DEVICE_BIT_IN | 0x1; + public static final int DEVICE_IN_AMBIENT = DEVICE_BIT_IN | 0x2; + public static final int DEVICE_IN_BUILTIN_MIC = DEVICE_BIT_IN | 0x4; + public static final int DEVICE_IN_BLUETOOTH_SCO_HEADSET = DEVICE_BIT_IN | 0x8; + public static final int DEVICE_IN_WIRED_HEADSET = DEVICE_BIT_IN | 0x10; + public static final int DEVICE_IN_AUX_DIGITAL = DEVICE_BIT_IN | 0x20; + public static final int DEVICE_IN_VOICE_CALL = DEVICE_BIT_IN | 0x40; + public static final int DEVICE_IN_BACK_MIC = DEVICE_BIT_IN | 0x80; + public static final int DEVICE_IN_REMOTE_SUBMIX = DEVICE_BIT_IN | 0x100; + public static final int DEVICE_IN_ANLG_DOCK_HEADSET = DEVICE_BIT_IN | 0x200; + public static final int DEVICE_IN_DGTL_DOCK_HEADSET = DEVICE_BIT_IN | 0x400; + public static final int DEVICE_IN_USB_ACCESSORY = DEVICE_BIT_IN | 0x800; + public static final int DEVICE_IN_USB_DEVICE = DEVICE_BIT_IN | 0x1000; + public static final int DEVICE_IN_DEFAULT = DEVICE_BIT_IN | DEVICE_BIT_DEFAULT; + + public static final int DEVICE_IN_ALL = (DEVICE_IN_COMMUNICATION | + DEVICE_IN_AMBIENT | + DEVICE_IN_BUILTIN_MIC | + DEVICE_IN_BLUETOOTH_SCO_HEADSET | + DEVICE_IN_WIRED_HEADSET | + DEVICE_IN_AUX_DIGITAL | + DEVICE_IN_VOICE_CALL | + DEVICE_IN_BACK_MIC | + DEVICE_IN_REMOTE_SUBMIX | + DEVICE_IN_ANLG_DOCK_HEADSET | + DEVICE_IN_DGTL_DOCK_HEADSET | + DEVICE_IN_USB_ACCESSORY | + DEVICE_IN_USB_DEVICE | + DEVICE_IN_DEFAULT); + public static final int DEVICE_IN_ALL_SCO = DEVICE_IN_BLUETOOTH_SCO_HEADSET; // device states, must match AudioSystem::device_connection_state public static final int DEVICE_STATE_UNAVAILABLE = 0; @@ -262,6 +293,7 @@ public class AudioSystem public static final String DEVICE_OUT_DGTL_DOCK_HEADSET_NAME = "digital_dock"; public static final String DEVICE_OUT_USB_ACCESSORY_NAME = "usb_accessory"; public static final String DEVICE_OUT_USB_DEVICE_NAME = "usb_device"; + public static final String DEVICE_OUT_REMOTE_SUBMIX_NAME = "remote_submix"; public static String getDeviceName(int device) { @@ -296,7 +328,9 @@ public class AudioSystem return DEVICE_OUT_USB_ACCESSORY_NAME; case DEVICE_OUT_USB_DEVICE: return DEVICE_OUT_USB_DEVICE_NAME; - case DEVICE_IN_DEFAULT: + case DEVICE_OUT_REMOTE_SUBMIX: + return DEVICE_OUT_REMOTE_SUBMIX_NAME; + case DEVICE_OUT_DEFAULT: default: return ""; } @@ -319,7 +353,8 @@ public class AudioSystem public static final int FORCE_ANALOG_DOCK = 8; public static final int FORCE_DIGITAL_DOCK = 9; public static final int FORCE_NO_BT_A2DP = 10; - private static final int NUM_FORCE_CONFIG = 11; + public static final int FORCE_REMOTE_SUBMIX = 11; + private static final int NUM_FORCE_CONFIG = 12; public static final int FORCE_DEFAULT = FORCE_NONE; // usage for setForceUse, must match AudioSystem::force_use @@ -346,4 +381,9 @@ public class AudioSystem public static native int setMasterMute(boolean mute); public static native boolean getMasterMute(); public static native int getDevicesForStream(int stream); + + // helpers for android.media.AudioManager.getProperty(), see description there for meaning + public static native int getPrimaryOutputSamplingRate(); + public static native int getPrimaryOutputFrameCount(); + } diff --git a/media/java/android/media/DataSource.java b/media/java/android/media/DataSource.java new file mode 100644 index 0000000..347bd5f --- /dev/null +++ b/media/java/android/media/DataSource.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.media; + +import java.io.Closeable; + +/** + * An abstraction for a media data source, e.g. a file or an http stream + * {@hide} + */ +public interface DataSource extends Closeable { + /** + * Reads data from the data source at the requested position + * + * @param offset where in the source to read + * @param buffer the buffer to read the data into + * @param size how many bytes to read + * @return the number of bytes read, or -1 if there was an error + */ + public int readAt(long offset, byte[] buffer, int size); + + /** + * Gets the size of the data source. + * + * @return size of data source, or -1 if the length is unknown + */ + public long getSize(); +} diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 854eb3f..7ae61cd 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -108,6 +108,8 @@ interface IAudioService { boolean isBluetoothA2dpOn(); + oneway void setRemoteSubmixOn(boolean on, int address); + int requestAudioFocus(int mainStreamType, int durationHint, IBinder cb, IAudioFocusDispatcher l, String clientId, String callingPackageName); diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java index 687d3a5..749ef12 100644 --- a/media/java/android/media/MediaExtractor.java +++ b/media/java/android/media/MediaExtractor.java @@ -22,6 +22,7 @@ import android.content.res.AssetFileDescriptor; import android.media.MediaCodec; import android.media.MediaFormat; import android.net.Uri; + import java.io.FileDescriptor; import java.io.IOException; import java.nio.ByteBuffer; @@ -44,7 +45,7 @@ import java.util.Map; * } * ByteBuffer inputBuffer = ByteBuffer.allocate(...) * while (extractor.readSampleData(inputBuffer, ...) >= 0) { - * int trackIndex = extractor.getTrackIndex(); + * int trackIndex = extractor.getSampleTrackIndex(); * long presentationTimeUs = extractor.getSampleTime(); * ... * extractor.advance(); @@ -60,6 +61,12 @@ final public class MediaExtractor { } /** + * Sets the DataSource object to be used as the data source for this extractor + * {@hide} + */ + public native final void setDataSource(DataSource source); + + /** * Sets the data source as a content Uri. * * @param context the Context to use when resolving the Uri diff --git a/media/java/android/media/MediaFile.java b/media/java/android/media/MediaFile.java index d21ada4..06d43a2 100644 --- a/media/java/android/media/MediaFile.java +++ b/media/java/android/media/MediaFile.java @@ -325,7 +325,7 @@ public class MediaFile { } int lastDot = fileName.lastIndexOf('.'); if (lastDot > 0) { - String extension = fileName.substring(lastDot + 1); + String extension = fileName.substring(lastDot + 1).toUpperCase(); Integer value = sFileTypeToFormatMap.get(extension); if (value != null) { return value.intValue(); diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java index aef631f..cc59d02 100644 --- a/media/java/android/media/MediaMetadataRetriever.java +++ b/media/java/android/media/MediaMetadataRetriever.java @@ -483,5 +483,10 @@ public class MediaMetadataRetriever * of 180 degrees will be retrieved as "-90.0000+180.0000", for instance. */ public static final int METADATA_KEY_LOCATION = 23; + /** + * This key retrieves the video rotation angle in degrees, if available. + * The video rotation angle may be 0, 90, 180, or 270 degrees. + */ + public static final int METADATA_KEY_VIDEO_ROTATION = 24; // Add more here... } diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index cd25865..ef0da3a 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -2265,6 +2265,16 @@ public class MediaPlayer */ public static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200; + /** File or network related operation errors. */ + public static final int MEDIA_ERROR_IO = -1004; + /** Bitstream is not conforming to the related coding standard or file spec. */ + public static final int MEDIA_ERROR_MALFORMED = -1007; + /** Bitstream is conforming to the related coding standard or file spec, but + * the media framework does not support the feature. */ + public static final int MEDIA_ERROR_UNSUPPORTED = -1010; + /** Some operation takes too long to complete, usually more than 3-5 seconds. */ + public static final int MEDIA_ERROR_TIMED_OUT = -110; + /** * Interface definition of a callback to be invoked when there * has been an error during an asynchronous operation (other errors @@ -2282,7 +2292,13 @@ public class MediaPlayer * <li>{@link #MEDIA_ERROR_SERVER_DIED} * </ul> * @param extra an extra code, specific to the error. Typically - * implementation dependant. + * implementation dependent. + * <ul> + * <li>{@link #MEDIA_ERROR_IO} + * <li>{@link #MEDIA_ERROR_MALFORMED} + * <li>{@link #MEDIA_ERROR_UNSUPPORTED} + * <li>{@link #MEDIA_ERROR_TIMED_OUT} + * </ul> * @return True if the method handled the error, false if it didn't. * Returning false, or not having an OnErrorListener at all, will * cause the OnCompletionListener to be called. @@ -2319,6 +2335,11 @@ public class MediaPlayer */ public static final int MEDIA_INFO_STARTED_AS_NEXT = 2; + /** The player just pushed the very first video frame for rendering. + * @see android.media.MediaPlayer.OnInfoListener + */ + public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; + /** The video is too complex for the decoder: it can't decode frames fast * enough. Possibly only the audio plays fine at this stage. * @see android.media.MediaPlayer.OnInfoListener @@ -2374,6 +2395,7 @@ public class MediaPlayer * <ul> * <li>{@link #MEDIA_INFO_UNKNOWN} * <li>{@link #MEDIA_INFO_VIDEO_TRACK_LAGGING} + * <li>{@link #MEDIA_INFO_VIDEO_RENDERING_START} * <li>{@link #MEDIA_INFO_BUFFERING_START} * <li>{@link #MEDIA_INFO_BUFFERING_END} * <li>{@link #MEDIA_INFO_BAD_INTERLEAVING} @@ -2381,7 +2403,7 @@ public class MediaPlayer * <li>{@link #MEDIA_INFO_METADATA_UPDATE} * </ul> * @param extra an extra code, specific to the info. Typically - * implementation dependant. + * implementation dependent. * @return True if the method handled the info, false if it didn't. * Returning false, or not having an OnErrorListener at all, will * cause the info to be discarded. diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 613354f..48bea52 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -177,6 +177,12 @@ public class MediaRecorder * is applied. */ public static final int VOICE_COMMUNICATION = 7; + + /** + * @hide + * Audio source for remote submix. + */ + public static final int REMOTE_SUBMIX_SOURCE = 8; } /** @@ -291,7 +297,12 @@ public class MediaRecorder * Gets the maximum value for audio sources. * @see android.media.MediaRecorder.AudioSource */ - public static final int getAudioSourceMax() { return AudioSource.VOICE_COMMUNICATION; } + public static final int getAudioSourceMax() { + // FIXME disable selection of the remote submxi source selection once test code + // doesn't rely on it + return AudioSource.REMOTE_SUBMIX_SOURCE; + //return AudioSource.VOICE_COMMUNICATION; + } /** * Sets the video source to be used for recording. If this method is not @@ -720,12 +731,17 @@ public class MediaRecorder public native int getMaxAmplitude() throws IllegalStateException; /* Do not change this value without updating its counterpart - * in include/media/mediarecorder.h! + * in include/media/mediarecorder.h or mediaplayer.h! */ /** Unspecified media recorder error. * @see android.media.MediaRecorder.OnErrorListener */ public static final int MEDIA_RECORDER_ERROR_UNKNOWN = 1; + /** Media server died. In this case, the application must release the + * MediaRecorder object and instantiate a new one. + * @see android.media.MediaRecorder.OnErrorListener + */ + public static final int MEDIA_ERROR_SERVER_DIED = 100; /** * Interface definition for a callback to be invoked when an error @@ -740,6 +756,7 @@ public class MediaRecorder * @param what the type of error that has occurred: * <ul> * <li>{@link #MEDIA_RECORDER_ERROR_UNKNOWN} + * <li>{@link #MEDIA_ERROR_SERVER_DIED} * </ul> * @param extra an extra code, specific to the error type */ diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java index fd37bcf..88cf4ac 100644 --- a/media/java/android/media/MediaScanner.java +++ b/media/java/android/media/MediaScanner.java @@ -314,6 +314,7 @@ public class MediaScanner private int mMtpObjectHandle; private final String mExternalStoragePath; + private final boolean mExternalIsEmulated; /** whether to use bulk inserts or individual inserts for each item */ private static final boolean ENABLE_BULK_INSERTS = true; @@ -392,6 +393,7 @@ public class MediaScanner setDefaultRingtoneFileNames(); mExternalStoragePath = Environment.getExternalStorageDirectory().getAbsolutePath(); + mExternalIsEmulated = Environment.isExternalStorageEmulated(); //mClient.testGenreNameConverter(); } @@ -543,13 +545,28 @@ public class MediaScanner boolean music = (lowpath.indexOf(MUSIC_DIR) > 0) || (!ringtones && !notifications && !alarms && !podcasts); + boolean isaudio = MediaFile.isAudioFileType(mFileType); + boolean isvideo = MediaFile.isVideoFileType(mFileType); + boolean isimage = MediaFile.isImageFileType(mFileType); + + if (isaudio || isvideo || isimage) { + if (mExternalIsEmulated && path.startsWith(mExternalStoragePath)) { + // try to rewrite the path to bypass the sd card fuse layer + String directPath = Environment.getMediaStorageDirectory() + + path.substring(mExternalStoragePath.length()); + File f = new File(directPath); + if (f.exists()) { + path = directPath; + } + } + } + // we only extract metadata for audio and video files - if (MediaFile.isAudioFileType(mFileType) - || MediaFile.isVideoFileType(mFileType)) { + if (isaudio || isvideo) { processFile(path, mimeType, this); } - if (MediaFile.isImageFileType(mFileType)) { + if (isimage) { processImageFile(path); } @@ -972,7 +989,6 @@ public class MediaScanner } values.put(FileColumns.MEDIA_TYPE, mediaType); } - mMediaProvider.update(result, values, null, null); } @@ -1448,24 +1464,42 @@ public class MediaScanner } FileEntry makeEntryFor(String path) { - String key = path; String where; String[] selectionArgs; - if (mCaseInsensitivePaths) { - // the 'like' makes it use the index, the 'lower()' makes it correct - // when the path contains sqlite wildcard characters - where = "_data LIKE ?1 AND lower(_data)=lower(?1)"; - selectionArgs = new String[] { path }; - } else { - where = Files.FileColumns.DATA + "=?"; - selectionArgs = new String[] { path }; - } Cursor c = null; try { + boolean hasWildCards = path.contains("_") || path.contains("%"); + + if (hasWildCards || !mCaseInsensitivePaths) { + // if there are wildcard characters in the path, the "like" match + // will be slow, and it's worth trying an "=" comparison + // first, since in most cases the case will match. + // Also, we shouldn't do a "like" match on case-sensitive filesystems + where = Files.FileColumns.DATA + "=?"; + selectionArgs = new String[] { path }; + } else { + // if there are no wildcard characters in the path, then the "like" + // match will be just as fast as the "=" case, because of the index + where = "_data LIKE ?1 AND lower(_data)=lower(?1)"; + selectionArgs = new String[] { path }; + } c = mMediaProvider.query(mFilesUri, FILES_PRESCAN_PROJECTION, where, selectionArgs, null, null); - if (c.moveToNext()) { + if (!c.moveToFirst() && hasWildCards && mCaseInsensitivePaths) { + // Try again with case-insensitive match. This will be slower, especially + // if the path contains wildcard characters. + // The 'like' makes it use the index, the 'lower()' makes it correct + // when the path contains sqlite wildcard characters, + where = "_data LIKE ?1 AND lower(_data)=lower(?1)"; + selectionArgs = new String[] { path }; + c.close(); + c = mMediaProvider.query(mFilesUri, FILES_PRESCAN_PROJECTION, + where, selectionArgs, null, null); + // TODO update the path in the db with the correct case so the fast + // path works next time? + } + if (c.moveToFirst()) { long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX); int format = c.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX); long lastModified = c.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX); diff --git a/media/java/android/media/RemoteDisplay.java b/media/java/android/media/RemoteDisplay.java new file mode 100644 index 0000000..b463d26 --- /dev/null +++ b/media/java/android/media/RemoteDisplay.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import dalvik.system.CloseGuard; + +import android.os.Handler; +import android.view.Surface; + +/** + * Listens for Wifi remote display connections managed by the media server. + * + * @hide + */ +public final class RemoteDisplay { + /* these constants must be kept in sync with IRemoteDisplayClient.h */ + + public static final int DISPLAY_FLAG_SECURE = 1 << 0; + + public static final int DISPLAY_ERROR_UNKOWN = 1; + public static final int DISPLAY_ERROR_CONNECTION_DROPPED = 2; + + private final CloseGuard mGuard = CloseGuard.get(); + private final Listener mListener; + private final Handler mHandler; + + private int mPtr; + + private native int nativeListen(String iface); + private native void nativeDispose(int ptr); + + private RemoteDisplay(Listener listener, Handler handler) { + mListener = listener; + mHandler = handler; + } + + @Override + protected void finalize() throws Throwable { + try { + dispose(true); + } finally { + super.finalize(); + } + } + + /** + * Starts listening for displays to be connected on the specified interface. + * + * @param iface The interface address and port in the form "x.x.x.x:y". + * @param listener The listener to invoke when displays are connected or disconnected. + * @param handler The handler on which to invoke the listener. + */ + public static RemoteDisplay listen(String iface, Listener listener, Handler handler) { + if (iface == null) { + throw new IllegalArgumentException("iface must not be null"); + } + if (listener == null) { + throw new IllegalArgumentException("listener must not be null"); + } + if (handler == null) { + throw new IllegalArgumentException("handler must not be null"); + } + + RemoteDisplay display = new RemoteDisplay(listener, handler); + display.startListening(iface); + return display; + } + + /** + * Disconnects the remote display and stops listening for new connections. + */ + public void dispose() { + dispose(false); + } + + private void dispose(boolean finalized) { + if (mPtr != 0) { + if (mGuard != null) { + if (finalized) { + mGuard.warnIfOpen(); + } else { + mGuard.close(); + } + } + + nativeDispose(mPtr); + mPtr = 0; + } + } + + private void startListening(String iface) { + mPtr = nativeListen(iface); + if (mPtr == 0) { + throw new IllegalStateException("Could not start listening for " + + "remote display connection on \"" + iface + "\""); + } + mGuard.open("dispose"); + } + + // Called from native. + private void notifyDisplayConnected(final Surface surface, + final int width, final int height, final int flags) { + mHandler.post(new Runnable() { + @Override + public void run() { + mListener.onDisplayConnected(surface, width, height, flags); + } + }); + } + + // Called from native. + private void notifyDisplayDisconnected() { + mHandler.post(new Runnable() { + @Override + public void run() { + mListener.onDisplayDisconnected(); + } + }); + } + + // Called from native. + private void notifyDisplayError(final int error) { + mHandler.post(new Runnable() { + @Override + public void run() { + mListener.onDisplayError(error); + } + }); + } + + /** + * Listener invoked when the remote display connection changes state. + */ + public interface Listener { + void onDisplayConnected(Surface surface, int width, int height, int flags); + void onDisplayDisconnected(); + void onDisplayError(int error); + } +} diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java index a0325dd..487585e 100755 --- a/media/java/android/mtp/MtpDatabase.java +++ b/media/java/android/mtp/MtpDatabase.java @@ -84,11 +84,10 @@ public class MtpDatabase { Files.FileColumns._ID, // 0 Files.FileColumns.DATA, // 1 }; - private static final String[] PATH_SIZE_FORMAT_PROJECTION = new String[] { + private static final String[] PATH_FORMAT_PROJECTION = new String[] { Files.FileColumns._ID, // 0 Files.FileColumns.DATA, // 1 - Files.FileColumns.SIZE, // 2 - Files.FileColumns.FORMAT, // 3 + Files.FileColumns.FORMAT, // 2 }; private static final String[] OBJECT_INFO_PROJECTION = new String[] { Files.FileColumns._ID, // 0 @@ -96,15 +95,14 @@ public class MtpDatabase { Files.FileColumns.FORMAT, // 2 Files.FileColumns.PARENT, // 3 Files.FileColumns.DATA, // 4 - Files.FileColumns.SIZE, // 5 - Files.FileColumns.DATE_MODIFIED, // 6 + Files.FileColumns.DATE_MODIFIED, // 5 }; private static final String ID_WHERE = Files.FileColumns._ID + "=?"; private static final String PATH_WHERE = Files.FileColumns.DATA + "=?"; private static final String STORAGE_WHERE = Files.FileColumns.STORAGE_ID + "=?"; - private static final String FORMAT_WHERE = Files.FileColumns.PARENT + "=?"; - private static final String PARENT_WHERE = Files.FileColumns.FORMAT + "=?"; + private static final String FORMAT_WHERE = Files.FileColumns.FORMAT + "=?"; + private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?"; private static final String STORAGE_FORMAT_WHERE = STORAGE_WHERE + " AND " + Files.FileColumns.FORMAT + "=?"; private static final String STORAGE_PARENT_WHERE = STORAGE_WHERE + " AND " @@ -835,7 +833,7 @@ public class MtpDatabase { } private boolean getObjectInfo(int handle, int[] outStorageFormatParent, - char[] outName, long[] outSizeModified) { + char[] outName, long[] outModified) { Cursor c = null; try { c = mMediaProvider.query(mObjectsUri, OBJECT_INFO_PROJECTION, @@ -856,8 +854,7 @@ public class MtpDatabase { path.getChars(start, end, outName, 0); outName[end - start] = 0; - outSizeModified[0] = c.getLong(5); - outSizeModified[1] = c.getLong(6); + outModified[0] = c.getLong(5); return true; } } catch (RemoteException e) { @@ -881,14 +878,16 @@ public class MtpDatabase { } Cursor c = null; try { - c = mMediaProvider.query(mObjectsUri, PATH_SIZE_FORMAT_PROJECTION, + c = mMediaProvider.query(mObjectsUri, PATH_FORMAT_PROJECTION, 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); outFilePath[path.length()] = 0; - outFileLengthFormat[0] = c.getLong(2); - outFileLengthFormat[1] = c.getLong(3); + // File transfers from device to host will likely fail if the size is incorrect. + // So to be safe, use the actual file size here. + outFileLengthFormat[0] = new File(path).length(); + outFileLengthFormat[1] = c.getLong(2); return MtpConstants.RESPONSE_OK; } else { return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; @@ -910,13 +909,13 @@ public class MtpDatabase { Cursor c = null; try { - c = mMediaProvider.query(mObjectsUri, PATH_SIZE_FORMAT_PROJECTION, + c = mMediaProvider.query(mObjectsUri, PATH_FORMAT_PROJECTION, 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 path = c.getString(1); - format = c.getInt(3); + format = c.getInt(2); } else { return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; } |
