summaryrefslogtreecommitdiffstats
path: root/media/java
diff options
context:
space:
mode:
Diffstat (limited to 'media/java')
-rw-r--r--media/java/android/media/AudioManager.java28
-rw-r--r--media/java/android/media/AudioService.java674
-rw-r--r--media/java/android/media/IAudioService.aidl28
-rw-r--r--media/java/android/media/IRemoteControlClient.aidl1
-rw-r--r--media/java/android/media/IRemoteControlDisplay.aidl14
-rw-r--r--media/java/android/media/MediaDrm.java227
-rw-r--r--media/java/android/media/MediaMetadataRetriever.java2
-rw-r--r--media/java/android/media/MediaPlayer.java4
-rw-r--r--media/java/android/media/RemoteControlClient.java247
-rw-r--r--media/java/android/mtp/MtpDatabase.java12
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) {