diff options
Diffstat (limited to 'media/java')
-rw-r--r-- | media/java/android/media/AudioManager.java | 28 | ||||
-rw-r--r-- | media/java/android/media/AudioService.java | 674 | ||||
-rw-r--r-- | media/java/android/media/IAudioService.aidl | 28 | ||||
-rw-r--r-- | media/java/android/media/IRemoteControlClient.aidl | 1 | ||||
-rw-r--r-- | media/java/android/media/IRemoteControlDisplay.aidl | 14 | ||||
-rw-r--r-- | media/java/android/media/MediaDrm.java | 227 | ||||
-rw-r--r-- | media/java/android/media/MediaMetadataRetriever.java | 2 | ||||
-rw-r--r-- | media/java/android/media/MediaPlayer.java | 4 | ||||
-rw-r--r-- | media/java/android/media/RemoteControlClient.java | 247 | ||||
-rw-r--r-- | media/java/android/mtp/MtpDatabase.java | 12 |
10 files changed, 757 insertions, 480 deletions
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 6f284f8..b80a166 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -2127,7 +2127,7 @@ public class AudioManager { mediaButtonIntent.setComponent(eventReceiver); PendingIntent pi = PendingIntent.getBroadcast(mContext, 0/*requestCode, ignored*/, mediaButtonIntent, 0/*flags*/); - unregisterMediaButtonIntent(pi, eventReceiver); + unregisterMediaButtonIntent(pi); } /** @@ -2139,16 +2139,16 @@ public class AudioManager { if (eventReceiver == null) { return; } - unregisterMediaButtonIntent(eventReceiver, null); + unregisterMediaButtonIntent(eventReceiver); } /** * @hide */ - public void unregisterMediaButtonIntent(PendingIntent pi, ComponentName eventReceiver) { + public void unregisterMediaButtonIntent(PendingIntent pi) { IAudioService service = getService(); try { - service.unregisterMediaButtonIntent(pi, eventReceiver); + service.unregisterMediaButtonIntent(pi); } catch (RemoteException e) { Log.e(TAG, "Dead object in unregisterMediaButtonIntent"+e); } @@ -2274,6 +2274,26 @@ public class AudioManager { } /** + * @hide + * Request the user of a RemoteControlClient to seek to the given playback position. + * @param generationId the RemoteControlClient generation counter for which this request is + * issued. Requests for an older generation than current one will be ignored. + * @param timeMs the time in ms to seek to, must be positive. + */ + public void setRemoteControlClientPlaybackPosition(int generationId, long timeMs) { + if (timeMs < 0) { + return; + } + IAudioService service = getService(); + try { + service.setRemoteControlClientPlaybackPosition(generationId, timeMs); + } catch (RemoteException e) { + Log.e(TAG, "Dead object in setRccPlaybackPosition("+ generationId + ", " + + timeMs + ")", e); + } + } + + /** * @hide * Reload audio settings. This method is called by Settings backup * agent when audio settings are restored and causes the AudioService diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index 38cdb8a..fd71d79 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -166,12 +166,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { private static final int MSG_PROMOTE_RCC = 29; private static final int MSG_BROADCAST_BT_CONNECTION_STATE = 30; private static final int MSG_UNLOAD_SOUND_EFFECTS = 31; - - - // 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 MSG_RCC_NEW_PLAYBACK_STATE = 32; + private static final int MSG_RCC_SEEK_REQUEST = 33; private static final int BTA2DP_DOCK_TIMEOUT_MILLIS = 8000; // Timeout for connection to bluetooth headset service @@ -581,14 +577,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished { for (int streamType = 0; streamType < numStreamTypes; streamType++) { if (streamType != mStreamVolumeAlias[streamType]) { mStreamStates[streamType]. - setAllIndexes(mStreamStates[mStreamVolumeAlias[streamType]], - false /*lastAudible*/); - mStreamStates[streamType]. - setAllIndexes(mStreamStates[mStreamVolumeAlias[streamType]], - true /*lastAudible*/); + setAllIndexes(mStreamStates[mStreamVolumeAlias[streamType]]); } // apply stream volume - if (mStreamStates[streamType].muteCount() == 0) { + if (!mStreamStates[streamType].isMuted()) { mStreamStates[streamType].applyAllVolumes(); } } @@ -632,10 +624,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } mStreamVolumeAlias[AudioSystem.STREAM_DTMF] = dtmfStreamAlias; if (updateVolumes) { - mStreamStates[AudioSystem.STREAM_DTMF].setAllIndexes(mStreamStates[dtmfStreamAlias], - false /*lastAudible*/); - mStreamStates[AudioSystem.STREAM_DTMF].setAllIndexes(mStreamStates[dtmfStreamAlias], - true /*lastAudible*/); + mStreamStates[AudioSystem.STREAM_DTMF].setAllIndexes(mStreamStates[dtmfStreamAlias]); sendMsg(mAudioHandler, MSG_SET_ALL_VOLUMES, SENDMSG_QUEUE, @@ -835,14 +824,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished { final int device = getDeviceForStream(streamTypeAlias); - // get last audible index if stream is muted, current index otherwise - int aliasIndex = streamState.getIndex(device, - (streamState.muteCount() != 0) /* lastAudible */); + int aliasIndex = streamState.getIndex(device); boolean adjustVolume = true; - int step; - int index; - int oldIndex; // reset any pending volume command synchronized (mSafeMediaVolumeState) { @@ -871,64 +855,40 @@ public class AudioService extends IAudioService.Stub implements OnFinished { step = rescaleIndex(10, streamType, streamTypeAlias); } - if ((direction == AudioManager.ADJUST_RAISE) && - !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) { - index = mStreamStates[streamType].getIndex(device, - (streamState.muteCount() != 0) /* lastAudible */); - oldIndex = index; - mVolumePanel.postDisplaySafeVolumeWarning(flags); - } else { - // 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 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 */); + // 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); + } + + int oldIndex = mStreamStates[streamType].getIndex(device); + + if (adjustVolume && (direction != AudioManager.ADJUST_SAME)) { + if ((direction == AudioManager.ADJUST_RAISE) && + !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) { + Log.e(TAG, "adjustStreamVolume() safe volume index = "+oldIndex); + mVolumePanel.postDisplaySafeVolumeWarning(flags); + } else if (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); } } + int index = mStreamStates[streamType].getIndex(device); sendVolumeUpdate(streamType, oldIndex, index, flags); } @@ -968,6 +928,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { }; private void onSetStreamVolume(int streamType, int index, int flags, int device) { + setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, false); // setting volume on master stream type also controls silent mode if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) || (mStreamVolumeAlias[streamType] == getMasterStreamType())) { @@ -975,18 +936,11 @@ public class AudioService extends IAudioService.Stub implements OnFinished { 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); } /** @see AudioManager#setStreamVolume(int, int, int) */ @@ -1005,9 +959,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // reset any pending volume command mPendingVolumeCommand = null; - // get last audible index if stream is muted, current index otherwise - oldIndex = streamState.getIndex(device, - (streamState.muteCount() != 0) /* lastAudible */); + oldIndex = streamState.getIndex(device); index = rescaleIndex(index * 10, streamType, mStreamVolumeAlias[streamType]); @@ -1033,9 +985,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { streamType, index, flags, device); } else { onSetStreamVolume(streamType, index, flags, device); - // get last audible index if stream is muted, current index otherwise - index = mStreamStates[streamType].getIndex(device, - (mStreamStates[streamType].muteCount() != 0) /* lastAudible */); + index = mStreamStates[streamType].getIndex(device); } } sendVolumeUpdate(streamType, oldIndex, index, flags); @@ -1197,41 +1147,23 @@ public class AudioService extends IAudioService.Stub implements OnFinished { * @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, int device, - boolean force, - boolean lastAudible) { + boolean force) { 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, device); - // Post a persist volume msg - sendMsg(mAudioHandler, - MSG_PERSIST_VOLUME, - SENDMSG_QUEUE, - PERSIST_LAST_AUDIBLE, - device, - streamState, - PERSIST_DELAY); - } - } else { - 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_DEVICE_VOLUME, - SENDMSG_QUEUE, - device, - 0, - streamState, - 0); - } + if (streamState.setIndex(index, device) || force) { + // Post message to set system volume (it in turn will post a message + // to persist). + sendMsg(mAudioHandler, + MSG_SET_DEVICE_VOLUME, + SENDMSG_QUEUE, + device, + 0, + streamState, + 0); } } @@ -1243,7 +1175,6 @@ public class AudioService extends IAudioService.Stub implements OnFinished { for (int stream = 0; stream < mStreamStates.length; stream++) { if (!isStreamAffectedByMute(stream) || stream == streamType) continue; - // Bring back last audible volume mStreamStates[stream].mute(cb, state); } } @@ -1261,7 +1192,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { /** get stream mute state. */ public boolean isStreamMute(int streamType) { - return (mStreamStates[streamType].muteCount() != 0); + return mStreamStates[streamType].isMuted(); } /** @see AudioManager#setMasterMute(boolean, int) */ @@ -1288,8 +1219,12 @@ public class AudioService extends IAudioService.Stub implements OnFinished { public int getStreamVolume(int streamType) { ensureValidStreamType(streamType); int device = getDeviceForStream(streamType); - int index = mStreamStates[streamType].getIndex(device, false /* lastAudible */); + int index = mStreamStates[streamType].getIndex(device); + // by convention getStreamVolume() returns 0 when a stream is muted. + if (mStreamStates[streamType].isMuted()) { + index = 0; + } if (index != 0 && (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) && (device & mFixedVolumeDevices) != 0) { index = mStreamStates[streamType].getMaxIndex(); @@ -1346,7 +1281,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { public int getLastAudibleStreamVolume(int streamType) { ensureValidStreamType(streamType); int device = getDeviceForStream(streamType); - return (mStreamStates[streamType].getIndex(device, true /* lastAudible */) + 5) / 10; + return (mStreamStates[streamType].getIndex(device) + 5) / 10; } /** Get last audible master volume before it was muted. */ @@ -1411,7 +1346,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { if (mVoiceCapable && mStreamVolumeAlias[streamType] == AudioSystem.STREAM_RING) { synchronized (mStreamStates[streamType]) { - Set set = mStreamStates[streamType].mLastAudibleIndex.entrySet(); + Set set = mStreamStates[streamType].mIndex.entrySet(); Iterator i = set.iterator(); while (i.hasNext()) { Map.Entry entry = (Map.Entry)i.next(); @@ -1533,7 +1468,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all // SCO connections not started by the application changing the mode if (newModeOwnerPid != 0) { - disconnectBluetoothSco(newModeOwnerPid); + final long ident = Binder.clearCallingIdentity(); + disconnectBluetoothSco(newModeOwnerPid); + Binder.restoreCallingIdentity(ident); } } @@ -1658,8 +1595,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { streamType = AudioManager.STREAM_MUSIC; } int device = getDeviceForStream(streamType); - int index = mStreamStates[mStreamVolumeAlias[streamType]].getIndex(device, false); - setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, true, false); + int index = mStreamStates[mStreamVolumeAlias[streamType]].getIndex(device); + setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, true); updateStreamVolumeAlias(true /*updateVolumes*/); } @@ -1893,7 +1830,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { streamState.readSettings(); // unmute stream that was muted but is not affect by mute anymore - if (streamState.muteCount() != 0 && ((!isStreamAffectedByMute(streamType) && + if (streamState.isMuted() && ((!isStreamAffectedByMute(streamType) && !isStreamMutedByRingerMode(streamType)) || mUseFixedVolume)) { int size = streamState.mDeathHandlers.size(); for (int i = 0; i < size; i++) { @@ -2393,8 +2330,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { 0, null, MUSIC_ACTIVE_POLL_PERIOD_MS); - int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device, - false /*lastAudible*/); + int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device); if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) && (index > mSafeMediaVolumeIndex)) { // Approximate cumulative active music time @@ -2739,18 +2675,14 @@ public class AudioService extends IAudioService.Stub implements OnFinished { private final int mStreamType; private String mVolumeIndexSettingName; - private String mLastAudibleVolumeIndexSettingName; private int mIndexMax; private final ConcurrentHashMap<Integer, Integer> mIndex = new ConcurrentHashMap<Integer, Integer>(8, 0.75f, 4); - private final ConcurrentHashMap<Integer, Integer> mLastAudibleIndex = - new ConcurrentHashMap<Integer, Integer>(8, 0.75f, 4); private ArrayList<VolumeDeathHandler> mDeathHandlers; //handles mute/solo clients death private VolumeStreamState(String settingName, int streamType) { mVolumeIndexSettingName = settingName; - mLastAudibleVolumeIndexSettingName = settingName + System.APPEND_FOR_LAST_AUDIBLE; mStreamType = streamType; mIndexMax = MAX_STREAM_VOLUME[streamType]; @@ -2763,10 +2695,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { readSettings(); } - public String getSettingNameForDevice(boolean lastAudible, int device) { - String name = lastAudible ? - mLastAudibleVolumeIndexSettingName : - mVolumeIndexSettingName; + public String getSettingNameForDevice(int device) { + String name = mVolumeIndexSettingName; String suffix = AudioSystem.getDeviceName(device); if (suffix.isEmpty()) { return name; @@ -2778,14 +2708,11 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // force maximum volume on all streams if fixed volume property is set if (mUseFixedVolume) { mIndex.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax); - mLastAudibleIndex.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax); return; } // do not read system stream volume from settings: this stream is always aliased // to another stream type and its volume is never persisted. Values in settings can // only be stale values - // on first call to readSettings() at init time, muteCount() is always 0 so we will - // always create entries for default device if ((mStreamType == AudioSystem.STREAM_SYSTEM) || (mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED)) { int index = 10 * AudioManager.DEFAULT_STREAM_VOLUME[mStreamType]; @@ -2794,10 +2721,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { index = mIndexMax; } } - if (muteCount() == 0) { - mIndex.put(AudioSystem.DEVICE_OUT_DEFAULT, index); - } - mLastAudibleIndex.put(AudioSystem.DEVICE_OUT_DEFAULT, index); + mIndex.put(AudioSystem.DEVICE_OUT_DEFAULT, index); return; } @@ -2811,7 +2735,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { remainingDevices &= ~device; // retrieve current volume for device - String name = getSettingNameForDevice(false /* lastAudible */, device); + String name = getSettingNameForDevice(device); // if no volume stored for current stream and device, use default volume if default // device, continue otherwise int defaultIndex = (device == AudioSystem.DEVICE_OUT_DEFAULT) ? @@ -2825,72 +2749,33 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // ignore settings for fixed volume devices: volume should always be at max or 0 if ((mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC) && ((device & mFixedVolumeDevices) != 0)) { - if ((muteCount()) == 0 && (index != 0)) { - mIndex.put(device, mIndexMax); - } else { - mIndex.put(device, 0); - } - mLastAudibleIndex.put(device, mIndexMax); - continue; - } - - // retrieve last audible volume for device - name = getSettingNameForDevice(true /* lastAudible */, device); - // use stored last audible index if present, otherwise use current index if not 0 - // or default index - defaultIndex = (index > 0) ? - index : AudioManager.DEFAULT_STREAM_VOLUME[mStreamType]; - 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). - if ((lastAudibleIndex == 0) && mVoiceCapable && - (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_RING)) { - lastAudibleIndex = AudioManager.DEFAULT_STREAM_VOLUME[mStreamType]; - // Correct the data base - sendMsg(mAudioHandler, - MSG_PERSIST_VOLUME, - SENDMSG_QUEUE, - PERSIST_LAST_AUDIBLE, - device, - this, - PERSIST_DELAY); - } - mLastAudibleIndex.put(device, getValidIndex(10 * lastAudibleIndex)); - // the initial index should never be 0 for ring and notification streams on phones - // (voice capable devices) if not in silent or vibrate mode. - if ((index == 0) && (mRingerMode == AudioManager.RINGER_MODE_NORMAL) && - mVoiceCapable && - (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_RING)) { - index = lastAudibleIndex; - // Correct the data base - sendMsg(mAudioHandler, - MSG_PERSIST_VOLUME, - SENDMSG_QUEUE, - PERSIST_CURRENT, - device, - this, - PERSIST_DELAY); - } - if (muteCount() == 0) { + mIndex.put(device, (index != 0) ? mIndexMax : 0); + } else { mIndex.put(device, getValidIndex(10 * index)); } } } public void applyDeviceVolume(int device) { - AudioSystem.setStreamVolumeIndex(mStreamType, - (getIndex(device, false /* lastAudible */) + 5)/10, - device); + int index; + if (isMuted()) { + index = 0; + } else { + index = (getIndex(device) + 5)/10; + } + AudioSystem.setStreamVolumeIndex(mStreamType, index, device); } public synchronized 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); + int index; + if (isMuted()) { + index = 0; + } else { + index = (getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5)/10; + } + AudioSystem.setStreamVolumeIndex(mStreamType, index, AudioSystem.DEVICE_OUT_DEFAULT); // then apply device specific volumes Set set = mIndex.entrySet(); Iterator i = set.iterator(); @@ -2898,22 +2783,23 @@ public class AudioService extends IAudioService.Stub implements OnFinished { 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); + if (isMuted()) { + index = 0; + } else { + index = ((Integer)entry.getValue() + 5)/10; + } + AudioSystem.setStreamVolumeIndex(mStreamType, index, device); } } } public boolean adjustIndex(int deltaIndex, int device) { - return setIndex(getIndex(device, - false /* lastAudible */) + deltaIndex, - device, - true /* lastAudible */); + return setIndex(getIndex(device) + deltaIndex, + device); } - public synchronized boolean setIndex(int index, int device, boolean lastAudible) { - int oldIndex = getIndex(device, false /* lastAudible */); + public synchronized boolean setIndex(int index, int device) { + int oldIndex = getIndex(device); index = getValidIndex(index); synchronized (mCameraSoundForced) { if ((mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED) && mCameraSoundForced) { @@ -2923,9 +2809,6 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mIndex.put(device, index); if (oldIndex != index) { - if (lastAudible) { - mLastAudibleIndex.put(device, index); - } // Apply change to all streams using this one as alias // if changing volume of current device, also change volume of current // device on aliased stream @@ -2936,12 +2819,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mStreamVolumeAlias[streamType] == mStreamType) { int scaledIndex = rescaleIndex(index, mStreamType, streamType); mStreamStates[streamType].setIndex(scaledIndex, - device, - lastAudible); + device); if (currentDevice) { mStreamStates[streamType].setIndex(scaledIndex, - getDeviceForStream(streamType), - lastAudible); + getDeviceForStream(streamType)); } } } @@ -2951,63 +2832,21 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } } - public synchronized int getIndex(int device, boolean lastAudible) { - ConcurrentHashMap <Integer, Integer> indexes; - if (lastAudible) { - indexes = mLastAudibleIndex; - } else { - indexes = mIndex; - } - Integer index = indexes.get(device); + public synchronized int getIndex(int device) { + Integer index = mIndex.get(device); if (index == null) { // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT - index = indexes.get(AudioSystem.DEVICE_OUT_DEFAULT); + index = mIndex.get(AudioSystem.DEVICE_OUT_DEFAULT); } return index.intValue(); } - public synchronized void setLastAudibleIndex(int index, int device) { - // Apply change to all streams using this one as alias - // if changing volume of current device, also change volume of current - // device on aliased stream - boolean currentDevice = (device == getDeviceForStream(mStreamType)); - int numStreamTypes = AudioSystem.getNumStreamTypes(); - for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { - if (streamType != mStreamType && - mStreamVolumeAlias[streamType] == mStreamType) { - int scaledIndex = rescaleIndex(index, mStreamType, streamType); - mStreamStates[streamType].setLastAudibleIndex(scaledIndex, device); - if (currentDevice) { - mStreamStates[streamType].setLastAudibleIndex(scaledIndex, - getDeviceForStream(streamType)); - } - } - } - mLastAudibleIndex.put(device, getValidIndex(index)); - } - - public synchronized void adjustLastAudibleIndex(int deltaIndex, int device) { - setLastAudibleIndex(getIndex(device, - true /* lastAudible */) + deltaIndex, - device); - } - public int getMaxIndex() { return mIndexMax; } - // only called by setAllIndexes() which is already synchronized - public ConcurrentHashMap <Integer, Integer> getAllIndexes(boolean lastAudible) { - if (lastAudible) { - return mLastAudibleIndex; - } else { - return mIndex; - } - } - - public synchronized void setAllIndexes(VolumeStreamState srcStream, boolean lastAudible) { - ConcurrentHashMap <Integer, Integer> indexes = srcStream.getAllIndexes(lastAudible); - Set set = indexes.entrySet(); + public synchronized void setAllIndexes(VolumeStreamState srcStream) { + Set set = srcStream.mIndex.entrySet(); Iterator i = set.iterator(); while (i.hasNext()) { Map.Entry entry = (Map.Entry)i.next(); @@ -3015,11 +2854,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { int index = ((Integer)entry.getValue()).intValue(); index = rescaleIndex(index, srcStream.getStreamType(), mStreamType); - if (lastAudible) { - setLastAudibleIndex(index, device); - } else { - setIndex(index, device, false /* lastAudible */); - } + setIndex(index, device); } } @@ -3030,12 +2865,6 @@ public class AudioService extends IAudioService.Stub implements OnFinished { Map.Entry entry = (Map.Entry)i.next(); entry.setValue(mIndexMax); } - set = mLastAudibleIndex.entrySet(); - i = set.iterator(); - while (i.hasNext()) { - Map.Entry entry = (Map.Entry)i.next(); - entry.setValue(mIndexMax); - } } public synchronized void mute(IBinder cb, boolean state) { @@ -3071,6 +2900,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // must be called while synchronized on parent VolumeStreamState public void mute(boolean state) { + boolean updateVolume = false; if (state) { if (mMuteCount == 0) { // Register for client death notification @@ -3079,22 +2909,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished { if (mICallback != null) { mICallback.linkToDeath(this, 0); } - mDeathHandlers.add(this); + VolumeStreamState.this.mDeathHandlers.add(this); // If the stream is not yet muted by any client, set level to 0 - if (muteCount() == 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_QUEUE, - 0, - 0, - VolumeStreamState.this, 0); + if (!VolumeStreamState.this.isMuted()) { + updateVolume = true; } } catch (RemoteException e) { // Client has died! @@ -3112,37 +2930,25 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mMuteCount--; if (mMuteCount == 0) { // Unregister from client death notification - mDeathHandlers.remove(this); + VolumeStreamState.this.mDeathHandlers.remove(this); // mICallback can be 0 if muted by AudioService if (mICallback != null) { mICallback.unlinkToDeath(this, 0); } - if (muteCount() == 0) { - // If the stream is not muted any more, restore its volume if - // ringer mode allows it - 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_QUEUE, - 0, - 0, - VolumeStreamState.this, 0); - } + if (!VolumeStreamState.this.isMuted()) { + updateVolume = true; } } } } + if (updateVolume) { + sendMsg(mAudioHandler, + MSG_SET_ALL_VOLUMES, + SENDMSG_QUEUE, + 0, + 0, + VolumeStreamState.this, 0); + } } public void binderDied() { @@ -3164,6 +2970,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished { return count; } + private synchronized boolean isMuted() { + return muteCount() != 0; + } + // only called by mute() which is already synchronized private VolumeDeathHandler getDeathHandler(IBinder cb, boolean state) { VolumeDeathHandler handler; @@ -3196,14 +3006,6 @@ public class AudioService extends IAudioService.Stub implements OnFinished { pw.print(Integer.toHexString(((Integer)entry.getKey()).intValue()) + ": " + ((((Integer)entry.getValue()).intValue() + 5) / 10)+", "); } - pw.print("\n Last audible: "); - set = mLastAudibleIndex.entrySet(); - i = set.iterator(); - while (i.hasNext()) { - Map.Entry entry = (Map.Entry)i.next(); - pw.print(Integer.toHexString(((Integer)entry.getKey()).intValue()) - + ": " + ((((Integer)entry.getValue()).intValue() + 5) / 10)+", "); - } } } @@ -3251,8 +3053,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { sendMsg(mAudioHandler, MSG_PERSIST_VOLUME, SENDMSG_QUEUE, - PERSIST_CURRENT|PERSIST_LAST_AUDIBLE, device, + 0, streamState, PERSIST_DELAY); @@ -3273,24 +3075,14 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } } - private void persistVolume(VolumeStreamState streamState, - int persistType, - int device) { + private void persistVolume(VolumeStreamState streamState, int device) { if (mUseFixedVolume) { return; } - if ((persistType & PERSIST_CURRENT) != 0) { - System.putIntForUser(mContentResolver, - streamState.getSettingNameForDevice(false /* lastAudible */, device), - (streamState.getIndex(device, false /* lastAudible */) + 5)/ 10, - UserHandle.USER_CURRENT); - } - if ((persistType & PERSIST_LAST_AUDIBLE) != 0) { - System.putIntForUser(mContentResolver, - streamState.getSettingNameForDevice(true /* lastAudible */, device), - (streamState.getIndex(device, true /* lastAudible */) + 5) / 10, - UserHandle.USER_CURRENT); - } + System.putIntForUser(mContentResolver, + streamState.getSettingNameForDevice(device), + (streamState.getIndex(device) + 5)/ 10, + UserHandle.USER_CURRENT); } private void persistRingerMode(int ringerMode) { @@ -3542,7 +3334,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { break; case MSG_PERSIST_VOLUME: - persistVolume((VolumeStreamState) msg.obj, msg.arg1, msg.arg2); + persistVolume((VolumeStreamState) msg.obj, msg.arg1); break; case MSG_PERSIST_MASTER_VOLUME: @@ -3739,6 +3531,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished { onRegisterVolumeObserverForRcc(msg.arg1 /* rccId */, (IRemoteVolumeObserver)msg.obj /* rvo */); break; + case MSG_RCC_NEW_PLAYBACK_STATE: + onNewPlaybackStateForRcc(msg.arg1 /* rccId */, msg.arg2 /* state */, + (RccPlaybackState)msg.obj /* newState */); + break; case MSG_SET_RSX_CONNECTION_STATE: onSetRsxConnectionState(msg.arg1/*available*/, msg.arg2/*address*/); @@ -4912,6 +4708,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } }; + /** + * Synchronization on mCurrentRcLock always inside a block synchronized on mRCStack + */ private final Object mCurrentRcLock = new Object(); /** * The one remote control client which will receive a request for display information. @@ -4999,6 +4798,59 @@ public class AudioService extends IAudioService.Stub implements OnFinished { */ private boolean mHasRemotePlayback; + private static class RccPlaybackState { + public int mState; + public long mPositionMs; + public float mSpeed; + + public RccPlaybackState(int state, long positionMs, float speed) { + mState = state; + mPositionMs = positionMs; + mSpeed = speed; + } + + public void reset() { + mState = RemoteControlClient.PLAYSTATE_STOPPED; + mPositionMs = RemoteControlClient.PLAYBACK_POSITION_INVALID; + mSpeed = RemoteControlClient.PLAYBACK_SPEED_1X; + } + + @Override + public String toString() { + return stateToString() + ", " + + ((mPositionMs == RemoteControlClient.PLAYBACK_POSITION_INVALID) ? + "PLAYBACK_POSITION_INVALID ," : String.valueOf(mPositionMs)) + "ms ," + + mSpeed + "X"; + } + + private String stateToString() { + switch (mState) { + case RemoteControlClient.PLAYSTATE_NONE: + return "PLAYSTATE_NONE"; + case RemoteControlClient.PLAYSTATE_STOPPED: + return "PLAYSTATE_STOPPED"; + case RemoteControlClient.PLAYSTATE_PAUSED: + return "PLAYSTATE_PAUSED"; + case RemoteControlClient.PLAYSTATE_PLAYING: + return "PLAYSTATE_PLAYING"; + case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: + return "PLAYSTATE_FAST_FORWARDING"; + case RemoteControlClient.PLAYSTATE_REWINDING: + return "PLAYSTATE_REWINDING"; + case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: + return "PLAYSTATE_SKIPPING_FORWARDS"; + case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: + return "PLAYSTATE_SKIPPING_BACKWARDS"; + case RemoteControlClient.PLAYSTATE_BUFFERING: + return "PLAYSTATE_BUFFERING"; + case RemoteControlClient.PLAYSTATE_ERROR: + return "PLAYSTATE_ERROR"; + default: + return "[invalid playstate]"; + } + } + } + private static class RemoteControlStackEntry { public int mRccId = RemoteControlClient.RCSE_ID_UNREGISTERED; /** @@ -5027,7 +4879,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { public int mPlaybackVolumeMax; public int mPlaybackVolumeHandling; public int mPlaybackStream; - public int mPlaybackState; + public RccPlaybackState mPlaybackState; public IRemoteVolumeObserver mRemoteVolumeObs; public void resetPlaybackInfo() { @@ -5036,17 +4888,21 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mPlaybackVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME; mPlaybackVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING; mPlaybackStream = AudioManager.STREAM_MUSIC; - mPlaybackState = RemoteControlClient.PLAYSTATE_STOPPED; + mPlaybackState.reset(); mRemoteVolumeObs = null; } - /** precondition: mediaIntent != null, eventReceiver != null */ + /** precondition: mediaIntent != null */ public RemoteControlStackEntry(PendingIntent mediaIntent, ComponentName eventReceiver) { mMediaIntent = mediaIntent; mReceiverComponent = eventReceiver; mCallingUid = -1; mRcClient = null; mRccId = ++sLastRccId; + mPlaybackState = new RccPlaybackState( + RemoteControlClient.PLAYSTATE_STOPPED, + RemoteControlClient.PLAYBACK_POSITION_INVALID, + RemoteControlClient.PLAYBACK_SPEED_1X); resetPlaybackInfo(); } @@ -5126,6 +4982,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished { " -- volMax: " + rcse.mPlaybackVolumeMax + " -- volObs: " + rcse.mRemoteVolumeObs); } + synchronized(mCurrentRcLock) { + pw.println("\nCurrent remote control generation ID = " + mCurrentRcClientGen); + } } synchronized (mMainRemote) { pw.println("\nRemote Volume State:"); @@ -5209,6 +5068,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished { Settings.System.MEDIA_BUTTON_RECEIVER, UserHandle.USER_CURRENT); if ((null != receiverName) && !receiverName.isEmpty()) { ComponentName eventReceiver = ComponentName.unflattenFromString(receiverName); + if (eventReceiver == null) { + // an invalid name was persisted + return; + } // construct a PendingIntent targeted to the restored component name // for the media button and register it Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); @@ -5224,7 +5087,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { * Helper function: * Set the new remote control receiver at the top of the RC focus stack. * Called synchronized on mAudioFocusLock, then mRCStack - * precondition: mediaIntent != null, target != null + * precondition: mediaIntent != null */ private void pushMediaButtonReceiver_syncAfRcs(PendingIntent mediaIntent, ComponentName target) { // already at top of stack? @@ -5253,8 +5116,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mRCStack.push(rcse); // rcse is never null // post message to persist the default media button receiver - mAudioHandler.sendMessage( mAudioHandler.obtainMessage( - MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, target/*obj*/) ); + if (target != null) { + mAudioHandler.sendMessage( mAudioHandler.obtainMessage( + MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, target/*obj*/) ); + } } /** @@ -5554,7 +5419,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { /** * see AudioManager.registerMediaButtonIntent(PendingIntent pi, ComponentName c) - * precondition: mediaIntent != null, target != null + * precondition: mediaIntent != null */ public void registerMediaButtonIntent(PendingIntent mediaIntent, ComponentName eventReceiver) { Log.i(TAG, " Remote Control registerMediaButtonIntent() for " + mediaIntent); @@ -5572,7 +5437,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { * see AudioManager.unregisterMediaButtonIntent(PendingIntent mediaIntent) * precondition: mediaIntent != null, eventReceiver != null */ - public void unregisterMediaButtonIntent(PendingIntent mediaIntent, ComponentName eventReceiver) + public void unregisterMediaButtonIntent(PendingIntent mediaIntent) { Log.i(TAG, " Remote Control unregisterMediaButtonIntent() for " + mediaIntent); @@ -5954,6 +5819,29 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } } + public void setRemoteControlClientPlaybackPosition(int generationId, long timeMs) { + sendMsg(mAudioHandler, MSG_RCC_SEEK_REQUEST, SENDMSG_QUEUE, generationId /* arg1 */, + 0 /* arg2 ignored*/, new Long(timeMs) /* obj */, 0 /* delay */); + } + + public void onSetRemoteControlClientPlaybackPosition(int generationId, long timeMs) { + if(DEBUG_RC) Log.d(TAG, "onSetRemoteControlClientPlaybackPosition(genId=" + generationId + + ", timeMs=" + timeMs + ")"); + synchronized(mRCStack) { + synchronized(mCurrentRcLock) { + if ((mCurrentRcClient != null) && (mCurrentRcClientGen == generationId)) { + // tell the current client to seek to the requested location + try { + mCurrentRcClient.seekTo(generationId, timeMs); + } catch (RemoteException e) { + Log.e(TAG, "Current valid remote client is dead: "+e); + mCurrentRcClient = null; + } + } + } + } + } + public void setPlaybackInfoForRcc(int rccId, int what, int value) { sendMsg(mAudioHandler, MSG_RCC_NEW_PLAYBACK_INFO, SENDMSG_QUEUE, rccId /* arg1 */, what /* arg2 */, Integer.valueOf(value) /* obj */, 0 /* delay */); @@ -6005,21 +5893,6 @@ public class AudioService extends IAudioService.Stub implements OnFinished { case RemoteControlClient.PLAYBACKINFO_USES_STREAM: rcse.mPlaybackStream = value; break; - case RemoteControlClient.PLAYBACKINFO_PLAYSTATE: - rcse.mPlaybackState = value; - synchronized (mMainRemote) { - if (rccId == mMainRemote.mRccId) { - mMainRemoteIsActive = isPlaystateActive(value); - postReevaluateRemote(); - } - } - // an RCC moving to a "playing" state should become the media button - // event receiver so it can be controlled, without requiring the - // app to re-register its receiver - if (isPlaystateActive(value)) { - postPromoteRcc(rccId); - } - break; default: Log.e(TAG, "unhandled key " + key + " for RCC " + rccId); break; @@ -6029,7 +5902,45 @@ public class AudioService extends IAudioService.Stub implements OnFinished { }//for } catch (ArrayIndexOutOfBoundsException e) { // not expected to happen, indicates improper concurrent modification - Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e); + Log.e(TAG, "Wrong index mRCStack on onNewPlaybackInfoForRcc, lock error? ", e); + } + } + } + + public void setPlaybackStateForRcc(int rccId, int state, long timeMs, float speed) { + sendMsg(mAudioHandler, MSG_RCC_NEW_PLAYBACK_STATE, SENDMSG_QUEUE, + rccId /* arg1 */, state /* arg2 */, + new RccPlaybackState(state, timeMs, speed) /* obj */, 0 /* delay */); + } + + public void onNewPlaybackStateForRcc(int rccId, int state, RccPlaybackState newState) { + if(DEBUG_RC) Log.d(TAG, "onNewPlaybackStateForRcc(id=" + rccId + ", state=" + state + + ", time=" + newState.mPositionMs + ", speed=" + newState.mSpeed + ")"); + synchronized(mRCStack) { + // iterating from top of stack as playback information changes are more likely + // on entries at the top of the remote control stack + try { + for (int index = mRCStack.size()-1; index >= 0; index--) { + final RemoteControlStackEntry rcse = mRCStack.elementAt(index); + if (rcse.mRccId == rccId) { + rcse.mPlaybackState = newState; + synchronized (mMainRemote) { + if (rccId == mMainRemote.mRccId) { + mMainRemoteIsActive = isPlaystateActive(state); + postReevaluateRemote(); + } + } + // an RCC moving to a "playing" state should become the media button + // event receiver so it can be controlled, without requiring the + // app to re-register its receiver + if (isPlaystateActive(state)) { + postPromoteRcc(rccId); + } + } + }//for + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index on mRCStack in onNewPlaybackStateForRcc, lock error? ", e); } } } @@ -6073,7 +5984,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { for (int index = mRCStack.size()-1; index >= 0; index--) { final RemoteControlStackEntry rcse = mRCStack.elementAt(index); if ((rcse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE) - && isPlaystateActive(rcse.mPlaybackState) + && isPlaystateActive(rcse.mPlaybackState.mState) && (rcse.mPlaybackStream == streamType)) { if (DEBUG_RC) Log.d(TAG, "remote playback active on stream " + streamType + ", vol =" + rcse.mPlaybackVolume); @@ -6304,10 +6215,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mRingerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED); } else { - s.setAllIndexes(mStreamStates[AudioSystem.STREAM_SYSTEM], - false /*lastAudible*/); - s.setAllIndexes(mStreamStates[AudioSystem.STREAM_SYSTEM], - true /*lastAudible*/); + s.setAllIndexes(mStreamStates[AudioSystem.STREAM_SYSTEM]); mRingerModeAffectedStreams |= (1 << AudioSystem.STREAM_SYSTEM_ENFORCED); } @@ -6453,7 +6361,6 @@ public class AudioService extends IAudioService.Stub implements OnFinished { private void enforceSafeMediaVolume() { VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC]; - boolean lastAudible = (streamState.muteCount() != 0); int devices = mSafeMediaVolumeDevices; int i = 0; @@ -6462,27 +6369,16 @@ public class AudioService extends IAudioService.Stub implements OnFinished { if ((device & devices) == 0) { continue; } - int index = streamState.getIndex(device, lastAudible); + int index = streamState.getIndex(device); 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); - } + streamState.setIndex(mSafeMediaVolumeIndex, device); + sendMsg(mAudioHandler, + MSG_SET_DEVICE_VOLUME, + SENDMSG_QUEUE, + device, + 0, + streamState, + 0); } devices &= ~device; } diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index efa8089..25aae8f 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -121,16 +121,11 @@ interface IAudioService { void dispatchMediaKeyEventUnderWakelock(in KeyEvent keyEvent); void registerMediaButtonIntent(in PendingIntent pi, in ComponentName c); - oneway void unregisterMediaButtonIntent(in PendingIntent pi, in ComponentName c); + oneway void unregisterMediaButtonIntent(in PendingIntent pi); oneway void registerMediaButtonEventReceiverForCalls(in ComponentName c); oneway void unregisterMediaButtonEventReceiverForCalls(); - int registerRemoteControlClient(in PendingIntent mediaIntent, - in IRemoteControlClient rcClient, in String callingPackageName); - oneway void unregisterRemoteControlClient(in PendingIntent mediaIntent, - in IRemoteControlClient rcClient); - /** * Register an IRemoteControlDisplay. * Notify all IRemoteControlClient of the new display and cause the RemoteControlClient @@ -157,8 +152,29 @@ interface IAudioService { * display doesn't need to receive artwork. */ oneway void remoteControlDisplayUsesBitmapSize(in IRemoteControlDisplay rcd, int w, int h); + /** + * Request the user of a RemoteControlClient to seek to the given playback position. + * @param generationId the RemoteControlClient generation counter for which this request is + * issued. Requests for an older generation than current one will be ignored. + * @param timeMs the time in ms to seek to, must be positive. + */ + void setRemoteControlClientPlaybackPosition(int generationId, long timeMs); + + /** + * Do not use directly, use instead + * {@link android.media.AudioManager#registerRemoteControlClient(RemoteControlClient)} + */ + int registerRemoteControlClient(in PendingIntent mediaIntent, + in IRemoteControlClient rcClient, in String callingPackageName); + /** + * Do not use directly, use instead + * {@link android.media.AudioManager#unregisterRemoteControlClient(RemoteControlClient)} + */ + oneway void unregisterRemoteControlClient(in PendingIntent mediaIntent, + in IRemoteControlClient rcClient); oneway void setPlaybackInfoForRcc(int rccId, int what, int value); + void setPlaybackStateForRcc(int rccId, int state, long timeMs, float speed); int getRemoteStreamMaxVolume(); int getRemoteStreamVolume(); oneway void registerRemoteVolumeObserverForRcc(int rccId, in IRemoteVolumeObserver rvo); diff --git a/media/java/android/media/IRemoteControlClient.aidl b/media/java/android/media/IRemoteControlClient.aidl index 5600263..e4cee06 100644 --- a/media/java/android/media/IRemoteControlClient.aidl +++ b/media/java/android/media/IRemoteControlClient.aidl @@ -47,4 +47,5 @@ oneway interface IRemoteControlClient void plugRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h); void unplugRemoteControlDisplay(IRemoteControlDisplay rcd); void setBitmapSizeForDisplay(IRemoteControlDisplay rcd, int w, int h); + void seekTo(int clientGeneration, long timeMs); }
\ No newline at end of file diff --git a/media/java/android/media/IRemoteControlDisplay.aidl b/media/java/android/media/IRemoteControlDisplay.aidl index 204de3c..c70889c 100644 --- a/media/java/android/media/IRemoteControlDisplay.aidl +++ b/media/java/android/media/IRemoteControlDisplay.aidl @@ -40,9 +40,19 @@ oneway interface IRemoteControlDisplay void setCurrentClientId(int clientGeneration, in PendingIntent clientMediaIntent, boolean clearing); - void setPlaybackState(int generationId, int state, long stateChangeTimeMs); + void setPlaybackState(int generationId, int state, long stateChangeTimeMs, long currentPosMs, + float speed); - void setTransportControlFlags(int generationId, int transportControlFlags); + /** + * Sets the transport control flags and playback position capabilities of a client. + * @param generationId the current generation ID as known by this client + * @param transportControlFlags bitmask of the transport controls this client supports, see + * {@link RemoteControlClient#setTransportControlFlags(int)} + * @param posCapabilities a bit mask for playback position capabilities, see + * {@link RemoteControlClient#MEDIA_POSITION_READABLE} and + * {@link RemoteControlClient#MEDIA_POSITION_WRITABLE} + */ + void setTransportControlInfo(int generationId, int transportControlFlags, int posCapabilities); void setMetadata(int generationId, in Bundle metadata); diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java index 4561d3f..3cdf261 100644 --- a/media/java/android/media/MediaDrm.java +++ b/media/java/android/media/MediaDrm.java @@ -28,8 +28,8 @@ import android.os.Bundle; import android.util.Log; /** - * MediaDrm class can be used in conjunction with {@link android.media.MediaCrypto} - * to obtain licenses for decoding encrypted media data. + * MediaDrm can be used in conjunction with {@link android.media.MediaCrypto} + * to obtain keys for decrypting protected media data. * * Crypto schemes are assigned 16 byte UUIDs, * the method {@link #isCryptoSchemeSupported} can be used to query if a given @@ -131,11 +131,15 @@ public final class MediaDrm { void onEvent(MediaDrm md, byte[] sessionId, int event, int extra, byte[] data); } + public static final int MEDIA_DRM_EVENT_PROVISION_REQUIRED = 1; + public static final int MEDIA_DRM_EVENT_KEY_REQUIRED = 2; + public static final int MEDIA_DRM_EVENT_KEY_EXPIRED = 3; + public static final int MEDIA_DRM_EVENT_VENDOR_DEFINED = 4; + /* Do not change these values without updating their counterparts * in include/media/mediadrm.h! */ private static final int DRM_EVENT = 200; - private class EventHandler extends Handler { private MediaDrm mMediaDrm; @@ -197,68 +201,88 @@ public final class MediaDrm { public native byte[] openSession() throws MediaDrmException; /** - * Close a session on the MediaDrm object. + * Close a session on the MediaDrm object that was previously opened + * with {@link #openSession}. */ public native void closeSession(byte[] sessionId) throws MediaDrmException; - public static final int MEDIA_DRM_LICENSE_TYPE_STREAMING = 1; - public static final int MEDIA_DRM_LICENSE_TYPE_OFFLINE = 2; + public static final int MEDIA_DRM_KEY_TYPE_STREAMING = 1; + public static final int MEDIA_DRM_KEY_TYPE_OFFLINE = 2; - public final class LicenseRequest { - public LicenseRequest() {} + public final class KeyRequest { + public KeyRequest() {} public byte[] data; public String defaultUrl; }; /** - * A license request/response exchange occurs between the app and a License - * Server to obtain the keys required to decrypt the content. getLicenseRequest() - * is used to obtain an opaque license request byte array that is delivered to the - * license server. The opaque license request byte array is returned in - * LicenseReqeust.data. The recommended URL to deliver the license request to is - * returned in LicenseRequest.defaultUrl + * A key request/response exchange occurs between the app and a license + * server to obtain the keys to decrypt encrypted content. getKeyRequest() + * is used to obtain an opaque key request byte array that is delivered to the + * license server. The opaque key request byte array is returned in + * KeyRequest.data. The recommended URL to deliver the key request to is + * returned in KeyRequest.defaultUrl. + * + * After the app has received the key request response from the server, + * it should deliver to the response to the DRM engine plugin using the method + * {@link #provideKeyResponse}. * * @param sessonId the session ID for the drm session * @param init container-specific data, its meaning is interpreted based on the * mime type provided in the mimeType parameter. It could contain, for example, * the content ID, key ID or other data obtained from the content metadata that is - * required in generating the license request. + * required in generating the key request. * @param mimeType identifies the mime type of the content - * @param licenseType specifes if the license is for streaming or offline content - * @param optionalParameters are included in the license server request message to + * @param keyType specifes if the request is for streaming or offline content + * @param optionalParameters are included in the key request message to * allow a client application to provide additional message parameters to the server. */ - public native LicenseRequest getLicenseRequest( byte[] sessionId, byte[] init, - String mimeType, int licenseType, - HashMap<String, String> optionalParameters ) + public native KeyRequest getKeyRequest(byte[] sessionId, byte[] init, + String mimeType, int keyType, + HashMap<String, String> optionalParameters) throws MediaDrmException; /** - * After a license response is received by the app, it is provided to the DRM plugin - * using provideLicenseResponse. + * A key response is received from the license server by the app, then it is + * provided to the DRM engine plugin using provideKeyResponse. The byte array + * returned is a keySetId that can be used to later restore the keys to a new + * session with the method {@link restoreKeys}, enabling offline key use. * * @param sessionId the session ID for the DRM session * @param response the byte array response from the server */ - public native void provideLicenseResponse( byte[] sessionId, byte[] response ) + public native byte[] provideKeyResponse(byte[] sessionId, byte[] response) throws MediaDrmException; /** - * Remove the keys associated with a license for a session + * Restore persisted offline keys into a new session. keySetId identifies the + * keys to load, obtained from a prior call to {@link provideKeyResponse}. + * * @param sessionId the session ID for the DRM session + * @param keySetId identifies the saved key set to restore */ - public native void removeLicense( byte[] sessionId ) throws MediaDrmException; + public native void restoreKeys(byte[] sessionId, byte[] keySetId) + throws MediaDrmException; /** - * Request an informative description of the license for the session. The status is + * Remove the persisted keys associated with an offline license. Keys are persisted + * when {@link provideKeyResponse} is called with keys obtained from the method + * {@link getKeyRequest} using keyType = MEDIA_DRM_KEY_TYPE_OFFLINE. + * + * @param keySetId identifies the saved key set to remove + */ + public native void removeKeys(byte[] keySetId) throws MediaDrmException; + + /** + * Request an informative description of the key status for the session. The status is * in the form of {name, value} pairs. Since DRM license policies vary by vendor, * the specific status field names are determined by each DRM vendor. Refer to your * DRM provider documentation for definitions of the field names for a particular - * DrmEngine. + * DRM engine plugin. * * @param sessionId the session ID for the DRM session */ - public native HashMap<String, String> queryLicenseStatus( byte[] sessionId ) + public native HashMap<String, String> queryKeyStatus(byte[] sessionId) throws MediaDrmException; public final class ProvisionRequest { @@ -269,22 +293,23 @@ public final class MediaDrm { /** * A provision request/response exchange occurs between the app and a provisioning - * server to retrieve a device certificate. getProvisionRequest is used to obtain - * an opaque license request byte array that is delivered to the provisioning server. - * The opaque provision request byte array is returned in ProvisionRequest.data - * The recommended URL to deliver the license request to is returned in - * ProvisionRequest.defaultUrl. + * server to retrieve a device certificate. If provisionining is required, the + * MEDIA_DRM_EVENT_PROVISION_REQUIRED event will be sent to the event handler. + * getProvisionRequest is used to obtain the opaque provision request byte array that + * should be delivered to the provisioning server. The provision request byte array + * is returned in ProvisionRequest.data. The recommended URL to deliver the provision + * request to is returned in ProvisionRequest.defaultUrl. */ public native ProvisionRequest getProvisionRequest() throws MediaDrmException; /** * After a provision response is received by the app, it is provided to the DRM - * plugin using this method. + * engine plugin using this method. * * @param response the opaque provisioning response byte array to provide to the - * DrmEngine. + * DRM engine plugin. */ - public native void provideProvisionResponse( byte[] response ) + public native void provideProvisionResponse(byte[] response) throws MediaDrmException; /** @@ -314,38 +339,140 @@ public final class MediaDrm { * * @param ssRelease the server response indicating which secure stops to release */ - public native void releaseSecureStops( byte[] ssRelease ) + public native void releaseSecureStops(byte[] ssRelease) throws MediaDrmException; /** - * Read a Drm plugin property value, given the property name string. There are several - * forms of property access functions, depending on the data type returned. + * Read a DRM engine plugin property value, given the property name string. There are + * several forms of property access functions, depending on the data type returned. * * Standard fields names are: - * vendor String - identifies the maker of the plugin - * version String - identifies the version of the plugin - * description String - describes the plugin + * vendor String - identifies the maker of the DRM engine plugin + * version String - identifies the version of the DRM engine plugin + * description String - describes the DRM engine plugin * deviceUniqueId byte[] - The device unique identifier is established during device - * provisioning and provides a means of uniquely identifying - * each device + * provisioning and provides a means of uniquely identifying + * each device + * algorithms String - a comma-separate list of cipher and mac algorithms supported + * by CryptoSession. The list may be empty if the DRM engine + * plugin does not support CryptoSession operations. */ - public native String getPropertyString( String propertyName ) + public native String getPropertyString(String propertyName) throws MediaDrmException; - public native byte[] getPropertyByteArray( String propertyName ) + public native byte[] getPropertyByteArray(String propertyName) throws MediaDrmException; /** - * Write a Drm plugin property value. There are several forms of property setting - * functions, depending on the data type being set. + * Write a DRM engine plugin property value. There are several forms of + * property setting functions, depending on the data type being set. */ - public native void setPropertyString( String propertyName, String value ) + public native void setPropertyString(String propertyName, String value) throws MediaDrmException; - public native void setPropertyByteArray( String propertyName, byte[] value ) + public native void setPropertyByteArray(String propertyName, byte[] value) throws MediaDrmException; + /** + * In addition to supporting decryption of DASH Common Encrypted Media, the + * MediaDrm APIs provide the ability to securely deliver session keys from + * an operator's session key server to a client device, based on the factory-installed + * root of trust, and provide the ability to do encrypt, decrypt, sign and verify + * with the session key on arbitrary user data. + * + * The CryptoSession class implements generic encrypt/decrypt/sign/verify methods + * based on the established session keys. These keys are exchanged using the + * getKeyRequest/provideKeyResponse methods. + * + * Applications of this capability could include securing various types of + * purchased or private content, such as applications, books and other media, + * photos or media delivery protocols. + * + * Operators can create session key servers that are functionally similar to a + * license key server, except that instead of receiving license key requests and + * providing encrypted content keys which are used specifically to decrypt A/V media + * content, the session key server receives session key requests and provides + * encrypted session keys which can be used for general purpose crypto operations. + */ + + private static final native void setCipherAlgorithmNative(MediaDrm drm, byte[] sessionId, + String algorithm); + + private static final native void setMacAlgorithmNative(MediaDrm drm, byte[] sessionId, + String algorithm); + + private static final native byte[] encryptNative(MediaDrm drm, byte[] sessionId, + byte[] keyId, byte[] input, byte[] iv); + + private static final native byte[] decryptNative(MediaDrm drm, byte[] sessionId, + byte[] keyId, byte[] input, byte[] iv); + + private static final native byte[] signNative(MediaDrm drm, byte[] sessionId, + byte[] keyId, byte[] message); + + private static final native boolean verifyNative(MediaDrm drm, byte[] sessionId, + byte[] keyId, byte[] message, + byte[] signature); + + public final class CryptoSession { + private MediaDrm mDrm; + private byte[] mSessionId; + + /** + * Construct a CryptoSession which can be used to encrypt, decrypt, + * sign and verify messages or data using the session keys established + * for the session using methods {@link getKeyRequest} and + * {@link provideKeyResponse} using a session key server. + * + * @param sessionId the session ID for the session containing keys + * to be used for encrypt, decrypt, sign and/or verify + * + * @param cipherAlgorithm the algorithm to use for encryption and + * decryption ciphers. The algorithm string conforms to JCA Standard + * Names for Cipher Transforms and is case insensitive. For example + * "AES/CBC/PKCS5Padding". + * + * @param macAlgorithm the algorithm to use for sign and verify + * The algorithm string conforms to JCA Standard Names for Mac + * Algorithms and is case insensitive. For example "HmacSHA256". + * + * The list of supported algorithms for a DRM engine plugin can be obtained + * using the method {@link getPropertyString("algorithms")} + */ + + public CryptoSession(MediaDrm drm, byte[] sessionId, + String cipherAlgorithm, String macAlgorithm) + throws MediaDrmException { + mSessionId = sessionId; + mDrm = drm; + setCipherAlgorithmNative(drm, sessionId, cipherAlgorithm); + setMacAlgorithmNative(drm, sessionId, macAlgorithm); + } + + public byte[] encrypt(byte[] keyid, byte[] input, byte[] iv) { + return encryptNative(mDrm, mSessionId, keyid, input, iv); + } + + public byte[] decrypt(byte[] keyid, byte[] input, byte[] iv) { + return decryptNative(mDrm, mSessionId, keyid, input, iv); + } + + public byte[] sign(byte[] keyid, byte[] message) { + return signNative(mDrm, mSessionId, keyid, message); + } + public boolean verify(byte[] keyid, byte[] message, byte[] signature) { + return verifyNative(mDrm, mSessionId, keyid, message, signature); + } + }; + + public CryptoSession getCryptoSession(byte[] sessionId, + String cipherAlgorithm, + String macAlgorithm) + throws MediaDrmException { + return new CryptoSession(this, sessionId, cipherAlgorithm, macAlgorithm); + } + @Override protected void finalize() { native_finalize(); diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java index cc59d02..376bb2d 100644 --- a/media/java/android/media/MediaMetadataRetriever.java +++ b/media/java/android/media/MediaMetadataRetriever.java @@ -367,7 +367,7 @@ public class MediaMetadataRetriever * counterparts in include/media/mediametadataretriever.h! */ /** - * The metadata key to retrieve the numberic string describing the + * The metadata key to retrieve the numeric string describing the * order of the audio data source on its original recording. */ public static final int METADATA_KEY_CD_TRACK_NUMBER = 0; diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index 11f4180..85a32ca 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -328,8 +328,8 @@ import java.lang.ref.WeakReference; * the state. Calling this method in an invalid state transfers the * object to the <em>Error</em> state. </p></td></tr> * <tr><td>pause </p></td> - * <td>{Started, Paused}</p></td> - * <td>{Idle, Initialized, Prepared, Stopped, PlaybackCompleted, Error}</p></td> + * <td>{Started, Paused, PlaybackCompleted}</p></td> + * <td>{Idle, Initialized, Prepared, Stopped, Error}</p></td> * <td>Successful invoke of this method in a valid state transfers the * object to the <em>Paused</em> state. Calling this method in an * invalid state transfers the object to the <em>Error</em> state.</p></td></tr> diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java index 9a0ecdf..e076ef0 100644 --- a/media/java/android/media/RemoteControlClient.java +++ b/media/java/android/media/RemoteControlClient.java @@ -172,6 +172,17 @@ public class RemoteControlClient */ public final static int PLAYBACKINFO_INVALID_VALUE = Integer.MIN_VALUE; + /** + * @hide + * An unknown or invalid playback position value. + */ + public final static long PLAYBACK_POSITION_INVALID = -1; + /** + * @hide + * The default playback speed, 1x. + */ + public final static float PLAYBACK_SPEED_1X = 1.0f; + //========================================== // Public keys for playback information /** @@ -208,15 +219,7 @@ public class RemoteControlClient public final static int PLAYBACKINFO_USES_STREAM = 5; //========================================== - // Private keys for playback information - /** - * @hide - * Used internally to relay playback state (set by the application with - * {@link #setPlaybackState(int)}) to AudioService - */ - public final static int PLAYBACKINFO_PLAYSTATE = 255; - - + // Public flags for the supported transport control capabililities /** * Flag indicating a RemoteControlClient makes use of the "previous" media key. * @@ -273,6 +276,18 @@ public class RemoteControlClient * @see android.view.KeyEvent#KEYCODE_MEDIA_NEXT */ public final static int FLAG_KEY_MEDIA_NEXT = 1 << 7; + /** + * @hide + * TODO un-hide and add in javadoc of setTransportControlFlags(int) + * Flag indicating a RemoteControlClient can receive changes in the media playback position + * through the {@link #OnPlaybackPositionUpdateListener} interface. This flag must be set + * in order for components that display the RemoteControlClient information, to display and + * let the user control media playback position. + * @see #setTransportControlFlags(int) + * @see #setPlaybackPositionProvider(PlaybackPositionProvider) + * @see #setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener) + */ + public final static int FLAG_KEY_MEDIA_POSITION_UPDATE = 1 << 8; /** * @hide @@ -588,17 +603,49 @@ public class RemoteControlClient * {@link #PLAYSTATE_ERROR}. */ public void setPlaybackState(int state) { + setPlaybackState(state, PLAYBACK_POSITION_INVALID, PLAYBACK_SPEED_1X); + } + + /** + * @hide + * TODO un-hide + * Sets the current playback state and the matching media position for the current playback + * speed. + * @param state The current playback state, one of the following values: + * {@link #PLAYSTATE_STOPPED}, + * {@link #PLAYSTATE_PAUSED}, + * {@link #PLAYSTATE_PLAYING}, + * {@link #PLAYSTATE_FAST_FORWARDING}, + * {@link #PLAYSTATE_REWINDING}, + * {@link #PLAYSTATE_SKIPPING_FORWARDS}, + * {@link #PLAYSTATE_SKIPPING_BACKWARDS}, + * {@link #PLAYSTATE_BUFFERING}, + * {@link #PLAYSTATE_ERROR}. + * @param timeInMs a 0 or positive value for the current media position expressed in ms + * (same unit as for when sending the media duration, if applicable, with + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} in the + * {@link RemoteControlClient.MetadataEditor}). Negative values imply that position is not + * known (e.g. listening to a live stream of a radio) or not applicable (e.g. when state + * is {@link #PLAYSTATE_BUFFERING} and nothing had played yet). + * @param playbackSpeed a value expressed as a ratio of 1x playback: 1.0f is normal playback, + * 2.0f is 2x, 0.5f is half-speed, -2.0f is rewind at 2x speed. 0.0f means nothing is + * playing (e.g. when state is {@link #PLAYSTATE_ERROR}). + */ + public void setPlaybackState(int state, long timeInMs, float playbackSpeed) { synchronized(mCacheLock) { - if (mPlaybackState != state) { + if ((mPlaybackState != state) || (mPlaybackPositionMs != timeInMs) + || (mPlaybackSpeed != playbackSpeed)) { // store locally mPlaybackState = state; + mPlaybackPositionMs = timeInMs; + mPlaybackSpeed = playbackSpeed; // keep track of when the state change occurred mPlaybackStateChangeTimeMs = SystemClock.elapsedRealtime(); // send to remote control display if conditions are met sendPlaybackState_syncCacheLock(); // update AudioService - sendAudioServiceNewPlaybackInfo_syncCacheLock(PLAYBACKINFO_PLAYSTATE, state); + sendAudioServiceNewPlaybackState_syncCacheLock(); } } } @@ -621,10 +668,112 @@ public class RemoteControlClient mTransportControlFlags = transportControlFlags; // send to remote control display if conditions are met - sendTransportControlFlags_syncCacheLock(); + sendTransportControlInfo_syncCacheLock(); + } + } + + /** + * @hide + * TODO un-hide + * Interface definition for a callback to be invoked when the media playback position is + * requested to be updated. + * @see RemoteControlClient#FLAG_KEY_MEDIA_POSITION_UPDATE + */ + public interface OnPlaybackPositionUpdateListener { + /** + * Called on the implementer to notify it that the playback head should be set at the given + * position. If the position can be changed from its current value, the implementor of + * the interface should also update the playback position using + * {@link RemoteControlClient#setPlaybackState(int, long, int)} to reflect the actual new + * position being used, regardless of whether it differs from the requested position. + * @param newPositionMs the new requested position in the current media, expressed in ms. + */ + void onPlaybackPositionUpdate(long newPositionMs); + } + + /** + * @hide + * TODO un-hide + * Interface definition for a callback to be invoked when the media playback position is + * queried. + * @see RemoteControlClient#FLAG_KEY_MEDIA_POSITION_UPDATE + */ + public interface PlaybackPositionProvider { + /** + * Called on the implementer of the interface to query the current playback position. + * @return a negative value if the current playback position (or the last valid playback + * position) is not known, or a zero or positive value expressed in ms indicating the + * current position, or the last valid known position. + */ + long getPlaybackPosition(); + } + + /** + * @hide + * TODO un-hide + * Sets the listener to be called whenever the media playback position is requested + * to be updated. + * Notifications will be received in the same thread as the one in which RemoteControlClient + * was created. + * @param l + */ + public void setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener l) { + synchronized(mCacheLock) { + int oldCapa = mPlaybackPositionCapabilities; + if (l != null) { + mPlaybackPositionCapabilities |= MEDIA_POSITION_WRITABLE; + } else { + mPlaybackPositionCapabilities &= ~MEDIA_POSITION_WRITABLE; + } + mPositionUpdateListener = l; + if (oldCapa != mPlaybackPositionCapabilities) { + // tell RCDs that this RCC's playback position capabilities have changed + sendTransportControlInfo_syncCacheLock(); + } + } + } + + /** + * @hide + * TODO un-hide + * Sets the listener to be called whenever the media current playback position is needed. + * Queries will be received in the same thread as the one in which RemoteControlClient + * was created. + * @param l + */ + public void setPlaybackPositionProvider(PlaybackPositionProvider l) { + synchronized(mCacheLock) { + int oldCapa = mPlaybackPositionCapabilities; + if (l != null) { + mPlaybackPositionCapabilities |= MEDIA_POSITION_READABLE; + } else { + mPlaybackPositionCapabilities &= ~MEDIA_POSITION_READABLE; + } + mPositionProvider = l; + if (oldCapa != mPlaybackPositionCapabilities) { + // tell RCDs that this RCC's playback position capabilities have changed + sendTransportControlInfo_syncCacheLock(); + } } } + /** + * @hide + * Flag to reflect that the application controlling this RemoteControlClient sends playback + * position updates. The playback position being "readable" is considered from the application's + * point of view. + */ + public static int MEDIA_POSITION_READABLE = 1 << 0; + /** + * @hide + * Flag to reflect that the application controlling this RemoteControlClient can receive + * playback position updates. The playback position being "writable" + * is considered from the application's point of view. + */ + public static int MEDIA_POSITION_WRITABLE = 1 << 1; + + private int mPlaybackPositionCapabilities = 0; + /** @hide */ public final static int DEFAULT_PLAYBACK_VOLUME_HANDLING = PLAYBACK_VOLUME_VARIABLE; /** @hide */ @@ -756,6 +905,14 @@ public class RemoteControlClient */ private long mPlaybackStateChangeTimeMs = 0; /** + * Last playback position in ms reported by the user + */ + private long mPlaybackPositionMs = PLAYBACK_POSITION_INVALID; + /** + * Last playback speed reported by the user + */ + private float mPlaybackSpeed = PLAYBACK_SPEED_1X; + /** * Cache for the artwork bitmap. * Access synchronized on mCacheLock * Artwork and metadata are not kept in one Bundle because the bitmap sometimes needs to be @@ -774,9 +931,17 @@ public class RemoteControlClient * This is re-initialized in apply() and so cannot be final. */ private Bundle mMetadata = new Bundle(); - /** - * The current remote control client generation ID across the system + * Listener registered by user of RemoteControlClient to receive requests for playback position + * update requests. + */ + private OnPlaybackPositionUpdateListener mPositionUpdateListener; + /** + * Provider registered by user of RemoteControlClient to provide the current playback position. + */ + private PlaybackPositionProvider mPositionProvider; + /** + * The current remote control client generation ID across the system, as known by this object */ private int mCurrentClientGenId = -1; /** @@ -789,7 +954,8 @@ public class RemoteControlClient /** * The media button intent description associated with this remote control client - * (can / should include target component for intent handling) + * (can / should include target component for intent handling, used when persisting media + * button event receiver across reboots). */ private final PendingIntent mRcMediaIntent; @@ -836,14 +1002,14 @@ public class RemoteControlClient */ private final IRemoteControlClient mIRCC = new IRemoteControlClient.Stub() { - public void onInformationRequested(int clientGeneration, int infoFlags) { + public void onInformationRequested(int generationId, int infoFlags) { // only post messages, we can't block here if (mEventHandler != null) { // signal new client mEventHandler.removeMessages(MSG_NEW_INTERNAL_CLIENT_GEN); mEventHandler.dispatchMessage( mEventHandler.obtainMessage(MSG_NEW_INTERNAL_CLIENT_GEN, - /*arg1*/ clientGeneration, /*arg2, ignored*/ 0)); + /*arg1*/ generationId, /*arg2, ignored*/ 0)); // send the information mEventHandler.removeMessages(MSG_REQUEST_PLAYBACK_STATE); mEventHandler.removeMessages(MSG_REQUEST_METADATA); @@ -890,6 +1056,16 @@ public class RemoteControlClient MSG_UPDATE_DISPLAY_ARTWORK_SIZE, w, h, rcd)); } } + + public void seekTo(int generationId, long timeMs) { + // only post messages, we can't block here + if (mEventHandler != null) { + mEventHandler.removeMessages(MSG_SEEK_TO); + mEventHandler.dispatchMessage(mEventHandler.obtainMessage( + MSG_SEEK_TO, generationId /* arg1 */, 0 /* arg2, ignored */, + new Long(timeMs))); + } + } }; /** @@ -930,6 +1106,7 @@ public class RemoteControlClient private final static int MSG_PLUG_DISPLAY = 7; private final static int MSG_UNPLUG_DISPLAY = 8; private final static int MSG_UPDATE_DISPLAY_ARTWORK_SIZE = 9; + private final static int MSG_SEEK_TO = 10; private class EventHandler extends Handler { public EventHandler(RemoteControlClient rcc, Looper looper) { @@ -951,7 +1128,7 @@ public class RemoteControlClient break; case MSG_REQUEST_TRANSPORTCONTROL: synchronized (mCacheLock) { - sendTransportControlFlags_syncCacheLock(); + sendTransportControlInfo_syncCacheLock(); } break; case MSG_REQUEST_ARTWORK: @@ -974,6 +1151,8 @@ public class RemoteControlClient case MSG_UPDATE_DISPLAY_ARTWORK_SIZE: onUpdateDisplayArtworkSize((IRemoteControlDisplay)msg.obj, msg.arg1, msg.arg2); break; + case MSG_SEEK_TO: + onSeekTo(msg.arg1, ((Long)msg.obj).longValue()); default: Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler"); } @@ -990,7 +1169,8 @@ public class RemoteControlClient final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); try { di.mRcDisplay.setPlaybackState(mInternalClientGenId, - mPlaybackState, mPlaybackStateChangeTimeMs); + mPlaybackState, mPlaybackStateChangeTimeMs, mPlaybackPositionMs, + mPlaybackSpeed); } catch (RemoteException e) { Log.e(TAG, "Error in setPlaybackState(), dead display " + di.mRcDisplay, e); displayIterator.remove(); @@ -1014,14 +1194,14 @@ public class RemoteControlClient } } - private void sendTransportControlFlags_syncCacheLock() { + private void sendTransportControlInfo_syncCacheLock() { if (mCurrentClientGenId == mInternalClientGenId) { final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); while (displayIterator.hasNext()) { final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); try { - di.mRcDisplay.setTransportControlFlags(mInternalClientGenId, - mTransportControlFlags); + di.mRcDisplay.setTransportControlInfo(mInternalClientGenId, + mTransportControlFlags, mPlaybackPositionCapabilities); } catch (RemoteException e) { Log.e(TAG, "Error in setTransportControlFlags(), dead display " + di.mRcDisplay, e); @@ -1109,7 +1289,20 @@ public class RemoteControlClient try { service.setPlaybackInfoForRcc(mRcseId, what, value); } catch (RemoteException e) { - Log.e(TAG, "Dead object in sendAudioServiceNewPlaybackInfo_syncCacheLock", e); + Log.e(TAG, "Dead object in setPlaybackInfoForRcc", e); + } + } + + private void sendAudioServiceNewPlaybackState_syncCacheLock() { + if (mRcseId == RCSE_ID_UNREGISTERED) { + return; + } + IAudioService service = getService(); + try { + service.setPlaybackStateForRcc(mRcseId, + mPlaybackState, mPlaybackPositionMs, mPlaybackSpeed); + } catch (RemoteException e) { + Log.e(TAG, "Dead object in setPlaybackStateForRcc", e); } } @@ -1190,6 +1383,14 @@ public class RemoteControlClient } } + private void onSeekTo(int generationId, long timeMs) { + synchronized (mCacheLock) { + if ((mCurrentClientGenId == generationId) && (mPositionUpdateListener != null)) { + mPositionUpdateListener.onPlaybackPositionUpdate(timeMs); + } + } + } + //=========================================================== // Internal utilities diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java index ea12803..632334b 100644 --- a/media/java/android/mtp/MtpDatabase.java +++ b/media/java/android/mtp/MtpDatabase.java @@ -96,7 +96,8 @@ public class MtpDatabase { Files.FileColumns.FORMAT, // 2 Files.FileColumns.PARENT, // 3 Files.FileColumns.DATA, // 4 - Files.FileColumns.DATE_MODIFIED, // 5 + Files.FileColumns.DATE_ADDED, // 5 + Files.FileColumns.DATE_MODIFIED, // 6 }; private static final String ID_WHERE = Files.FileColumns._ID + "=?"; private static final String PATH_WHERE = Files.FileColumns.DATA + "=?"; @@ -840,7 +841,7 @@ public class MtpDatabase { } private boolean getObjectInfo(int handle, int[] outStorageFormatParent, - char[] outName, long[] outModified) { + char[] outName, long[] outCreatedModified) { Cursor c = null; try { c = mMediaProvider.query(mPackageName, mObjectsUri, OBJECT_INFO_PROJECTION, @@ -861,7 +862,12 @@ public class MtpDatabase { path.getChars(start, end, outName, 0); outName[end - start] = 0; - outModified[0] = c.getLong(5); + outCreatedModified[0] = c.getLong(5); + outCreatedModified[1] = c.getLong(6); + // use modification date as creation date if date added is not set + if (outCreatedModified[0] == 0) { + outCreatedModified[0] = outCreatedModified[1]; + } return true; } } catch (RemoteException e) { |