Merge "Fix bug 5202226 - Leave menu button visible for no-button phones if targetSdk < ICS"
diff --git a/Android.mk b/Android.mk
index fdf0933..d4dc088 100644
--- a/Android.mk
+++ b/Android.mk
@@ -183,7 +183,8 @@
media/java/android/media/IAudioFocusDispatcher.aidl \
media/java/android/media/IMediaScannerListener.aidl \
media/java/android/media/IMediaScannerService.aidl \
- media/java/android/media/IRemoteControlClientDispatcher.aidl \
+ media/java/android/media/IRemoteControlClient.aidl \
+ media/java/android/media/IRemoteControlDisplay.aidl \
telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl \
telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl \
telephony/java/com/android/internal/telephony/ITelephony.aidl \
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 2593065..3cec66f 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -105,6 +105,7 @@
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/SystemUI_intermediates)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/R/com/android/systemui/R.java)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/media/java/android/media/IAudioService.P)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/media/java/android/media/IAudioService.P)
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
# ************************************************
diff --git a/api/current.txt b/api/current.txt
index fb16830..033cccb 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4226,6 +4226,7 @@
method public java.util.Set<android.bluetooth.BluetoothDevice> getBondedDevices();
method public static synchronized android.bluetooth.BluetoothAdapter getDefaultAdapter();
method public java.lang.String getName();
+ method public int getProfileConnectionState(int);
method public boolean getProfileProxy(android.content.Context, android.bluetooth.BluetoothProfile.ServiceListener, int);
method public android.bluetooth.BluetoothDevice getRemoteDevice(java.lang.String);
method public int getScanMode();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index e376220..6fb7965 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4091,7 +4091,7 @@
final void removeDeadProvider(String name, IContentProvider provider) {
synchronized(mProviderMap) {
ProviderClientRecord pr = mProviderMap.get(name);
- if (pr.mProvider.asBinder() == provider.asBinder()) {
+ if (pr != null && pr.mProvider.asBinder() == provider.asBinder()) {
Slog.i(TAG, "Removing dead content provider: " + name);
ProviderClientRecord removed = mProviderMap.remove(name);
if (removed != null) {
@@ -4101,17 +4101,6 @@
}
}
- final void removeDeadProviderLocked(String name, IContentProvider provider) {
- ProviderClientRecord pr = mProviderMap.get(name);
- if (pr.mProvider.asBinder() == provider.asBinder()) {
- Slog.i(TAG, "Removing dead content provider: " + name);
- ProviderClientRecord removed = mProviderMap.remove(name);
- if (removed != null) {
- removed.mProvider.asBinder().unlinkToDeath(removed, 0);
- }
- }
- }
-
private IContentProvider installProvider(Context context,
IContentProvider provider, ProviderInfo info, boolean noisy) {
ContentProvider localProvider = null;
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 264db19..2236928 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -777,23 +777,24 @@
* Get the current connection state of a profile.
* This function can be used to check whether the local Bluetooth adapter
* is connected to any remote device for a specific profile.
- * Profile can be one of {@link BluetoothProfile.HEADSET},
- * {@link BluetoothProfile.A2DP}.
+ * Profile can be one of {@link BluetoothProfile#HEADSET},
+ * {@link BluetoothProfile#A2DP}.
*
* <p>Requires {@link android.Manifest.permission#BLUETOOTH}.
*
* <p> Return value can be one of
- * {@link * BluetoothProfile.STATE_DISCONNECTED},
- * {@link * BluetoothProfile.STATE_CONNECTING},
- * {@link * BluetoothProfile.STATE_CONNECTED},
- * {@link * BluetoothProfile.STATE_DISCONNECTING}
- * @hide
+ * {@link BluetoothProfile#STATE_DISCONNECTED},
+ * {@link BluetoothProfile#STATE_CONNECTING},
+ * {@link BluetoothProfile#STATE_CONNECTED},
+ * {@link BluetoothProfile#STATE_DISCONNECTING}
*/
public int getProfileConnectionState(int profile) {
if (getState() != STATE_ON) return BluetoothProfile.STATE_DISCONNECTED;
try {
return mService.getProfileConnectionState(profile);
- } catch (RemoteException e) {Log.e(TAG, "getProfileConnectionState:", e);}
+ } catch (RemoteException e) {
+ Log.e(TAG, "getProfileConnectionState:", e);
+ }
return BluetoothProfile.STATE_DISCONNECTED;
}
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index 9f435fd..e942969 100755
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -602,6 +602,11 @@
* It inits bond state and profile state before STATE_ON intent is broadcasted.
*/
/*package*/ void initBluetoothAfterTurningOn() {
+ String discoverable = getProperty("Discoverable", false);
+ String timeout = getProperty("DiscoverableTimeout", false);
+ if (discoverable.equals("true") && Integer.valueOf(timeout) != 0) {
+ setAdapterPropertyBooleanNative("Discoverable", 0);
+ }
mBondState.initBondState();
initProfileState();
}
diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java
index 79ade26..d78bbbf 100644
--- a/core/java/android/server/search/SearchManagerService.java
+++ b/core/java/android/server/search/SearchManagerService.java
@@ -97,8 +97,18 @@
* Refreshes the "searchables" list when packages are added/removed.
*/
class MyPackageMonitor extends PackageMonitor {
+
@Override
public void onSomePackagesChanged() {
+ updateSearchables();
+ }
+
+ @Override
+ public void onPackageModified(String pkg) {
+ updateSearchables();
+ }
+
+ private void updateSearchables() {
// Update list of searchable activities
getSearchables().buildSearchableList();
// Inform all listeners that the list of searchables has been updated.
diff --git a/core/java/android/view/VolumePanel.java b/core/java/android/view/VolumePanel.java
index e1aa9a4..fb87e23 100644
--- a/core/java/android/view/VolumePanel.java
+++ b/core/java/android/view/VolumePanel.java
@@ -259,8 +259,9 @@
mStreamControls = new HashMap<Integer,StreamControl>(STREAM_TYPES.length);
Resources res = mContext.getResources();
for (int i = 0; i < STREAM_TYPES.length; i++) {
+ final int streamType = STREAM_TYPES[i];
StreamControl sc = new StreamControl();
- sc.streamType = STREAM_TYPES[i];
+ sc.streamType = streamType;
sc.group = (ViewGroup) inflater.inflate(R.layout.volume_adjust_item, null);
sc.group.setTag(sc);
sc.icon = (ImageView) sc.group.findViewById(R.id.stream_icon);
@@ -273,10 +274,12 @@
sc.iconMuteRes = STREAM_ICONS_MUTED[i];
sc.icon.setImageResource(sc.iconRes);
sc.seekbarView = (SeekBar) sc.group.findViewById(R.id.seekbar);
- sc.seekbarView.setMax(mAudioManager.getStreamMaxVolume(STREAM_TYPES[i]));
+ int plusOne = (streamType == AudioSystem.STREAM_BLUETOOTH_SCO ||
+ streamType == AudioSystem.STREAM_VOICE_CALL) ? 1 : 0;
+ sc.seekbarView.setMax(mAudioManager.getStreamMaxVolume(streamType) + plusOne);
sc.seekbarView.setOnSeekBarChangeListener(this);
sc.seekbarView.setTag(sc);
- mStreamControls.put(STREAM_TYPES[i], sc);
+ mStreamControls.put(streamType, sc);
}
}
@@ -476,6 +479,9 @@
StreamControl sc = mStreamControls.get(streamType);
if (sc != null) {
+ if (sc.seekbarView.getMax() != max) {
+ sc.seekbarView.setMax(max);
+ }
sc.seekbarView.setProgress(index);
}
@@ -557,28 +563,6 @@
}
}
-// /**
-// * Makes the ringer icon visible with an icon that is chosen
-// * based on the current ringer mode.
-// */
-// private void setRingerIcon() {
-// mSmallStreamIcon.setVisibility(View.GONE);
-// mLargeStreamIcon.setVisibility(View.VISIBLE);
-//
-// int ringerMode = mAudioService.getRingerMode();
-// int icon;
-//
-// if (LOGD) Log.d(TAG, "setRingerIcon(), ringerMode: " + ringerMode);
-//
-// if (ringerMode == AudioManager.RINGER_MODE_SILENT) {
-// icon = com.android.internal.R.drawable.ic_volume_off;
-// } else if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) {
-// icon = com.android.internal.R.drawable.ic_vibrate;
-// } else {
-// icon = com.android.internal.R.drawable.ic_volume;
-// }
-// mLargeStreamIcon.setImageResource(icon);
-// }
/**
* Switch between icons because Bluetooth music is same as music volume, but with
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index f9efd3c..e3ef717 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1715,161 +1715,54 @@
}
}
- /**
- * Acts as a proxy between AudioService and the RemoteControlClient
- */
- private IRemoteControlClientDispatcher mRcClientDispatcher =
- new IRemoteControlClientDispatcher.Stub() {
-
- public String getMetadataStringForClient(String clientName, int field) {
- RemoteControlClient realClient;
- synchronized(mRcClientMap) {
- realClient = mRcClientMap.get(clientName);
- }
- if (realClient != null) {
- return realClient.getMetadataString(field);
- } else {
- return null;
- }
- }
-
- public int getPlaybackStateForClient(String clientName) {
- RemoteControlClient realClient;
- synchronized(mRcClientMap) {
- realClient = mRcClientMap.get(clientName);
- }
- if (realClient != null) {
- return realClient.getPlaybackState();
- } else {
- return 0;
- }
- }
-
- public int getTransportControlFlagsForClient(String clientName) {
- RemoteControlClient realClient;
- synchronized(mRcClientMap) {
- realClient = mRcClientMap.get(clientName);
- }
- if (realClient != null) {
- return realClient.getTransportControlFlags();
- } else {
- return 0;
- }
- }
-
- public Bitmap getAlbumArtForClient(String clientName, int maxWidth, int maxHeight) {
- RemoteControlClient realClient;
- synchronized(mRcClientMap) {
- realClient = mRcClientMap.get(clientName);
- }
- if (realClient != null) {
- return realClient.getAlbumArt(maxWidth, maxHeight);
- } else {
- return null;
- }
- }
- };
-
- private HashMap<String, RemoteControlClient> mRcClientMap =
- new HashMap<String, RemoteControlClient>();
-
- private String getIdForRcClient(RemoteControlClient client) {
- // client is guaranteed to be non-null
- return client.toString();
- }
/**
* @hide
+ * CANDIDATE FOR SDK
* Registers the remote control client for providing information to display on the remote
* controls.
- * @param eventReceiver identifier of a {@link android.content.BroadcastReceiver}
- * that will receive the media button intent, and associated with the remote control
- * client. This method has no effect if
- * {@link #registerMediaButtonEventReceiver(ComponentName)} hasn't been called
- * with the same eventReceiver, or if
- * {@link #unregisterMediaButtonEventReceiver(ComponentName)} has been called.
- * @param rcClient the remote control client associated with the event receiver, responsible
+ * @param rcClient the remote control client associated responsible
* for providing the information to display on the remote control.
*/
- public void registerRemoteControlClient(ComponentName eventReceiver,
- RemoteControlClient rcClient) {
- if ((eventReceiver == null) || (rcClient == null)) {
+ public void registerRemoteControlClient(RemoteControlClient rcClient) {
+ if ((rcClient == null) || (rcClient.getRcEventReceiver() == null)) {
return;
}
- String clientKey = getIdForRcClient(rcClient);
- synchronized(mRcClientMap) {
- if (mRcClientMap.containsKey(clientKey)) {
- return;
- }
- mRcClientMap.put(clientKey, rcClient);
- }
IAudioService service = getService();
try {
- service.registerRemoteControlClient(eventReceiver, mRcClientDispatcher, clientKey,
+ service.registerRemoteControlClient(rcClient.getRcEventReceiver(), /* eventReceiver */
+ rcClient.getIRemoteControlClient(), /* rcClient */
+ rcClient.toString(), /* clientName */
// used to match media button event receiver and audio focus
- mContext.getPackageName());
+ mContext.getPackageName()); /* packageName */
} catch (RemoteException e) {
Log.e(TAG, "Dead object in registerRemoteControlClient"+e);
- synchronized(mRcClientMap) {
- mRcClientMap.remove(clientKey);
- }
}
}
/**
* @hide
+ * CANDIDATE FOR SDK
* Unregisters the remote control client that was providing information to display on the
* remotes.
- * @param eventReceiver identifier of a {@link android.content.BroadcastReceiver}
- * that receives the media button intent, and associated with the remote control
- * client.
* @param rcClient the remote control client to unregister
- * @see #registerRemoteControlClient(ComponentName, RemoteControlClient)
+ * @see #registerRemoteControlClient(RemoteControlClient)
*/
- public void unregisterRemoteControlClient(ComponentName eventReceiver,
- RemoteControlClient rcClient) {
- if ((eventReceiver == null) || (rcClient == null)) {
+ public void unregisterRemoteControlClient(RemoteControlClient rcClient) {
+ if ((rcClient == null) || (rcClient.getRcEventReceiver() == null)) {
return;
}
IAudioService service = getService();
try {
- // remove locally
- boolean unregister = true;
- synchronized(mRcClientMap) {
- if (mRcClientMap.remove(getIdForRcClient(rcClient)) == null) {
- unregister = false;
- }
- }
- if (unregister) {
- // unregistering a RemoteControlClient is equivalent to setting it to null
- service.registerRemoteControlClient(eventReceiver, null, null,
- mContext.getPackageName());
- }
+ service.unregisterRemoteControlClient(rcClient.getRcEventReceiver(), /* eventReceiver */
+ rcClient.getIRemoteControlClient()); /* rcClient */
} catch (RemoteException e) {
Log.e(TAG, "Dead object in unregisterRemoteControlClient"+e);
}
}
- /**
- * @hide
- * Returns the current remote control client.
- * @param rcClientId the generation counter that matches the extra
- * {@link AudioManager#EXTRA_REMOTE_CONTROL_CLIENT_GENERATION} in the
- * {@link AudioManager#REMOTE_CONTROL_CLIENT_CHANGED} event
- * @return the current RemoteControlClient from which information to display on the remote
- * control can be retrieved, or null if rcClientId doesn't match the current generation
- * counter.
- */
- public IRemoteControlClientDispatcher getRemoteControlClientDispatcher(int rcClientId) {
- IAudioService service = getService();
- try {
- return service.getRemoteControlClientDispatcher(rcClientId);
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in getRemoteControlClient "+e);
- return null;
- }
- }
+ // FIXME remove because we are not using intents anymore between AudioService and RcDisplay
/**
* @hide
* Broadcast intent action indicating that the displays on the remote controls
@@ -1882,6 +1775,7 @@
public static final String REMOTE_CONTROL_CLIENT_CHANGED =
"android.media.REMOTE_CONTROL_CLIENT_CHANGED";
+ // FIXME remove because we are not using intents anymore between AudioService and RcDisplay
/**
* @hide
* The IRemoteControlClientDispatcher monotonically increasing generation counter.
@@ -1891,6 +1785,7 @@
public static final String EXTRA_REMOTE_CONTROL_CLIENT_GENERATION =
"android.media.EXTRA_REMOTE_CONTROL_CLIENT_GENERATION";
+ // FIXME remove because we are not using intents anymore between AudioService and RcDisplay
/**
* @hide
* The name of the RemoteControlClient.
@@ -1902,6 +1797,7 @@
public static final String EXTRA_REMOTE_CONTROL_CLIENT_NAME =
"android.media.EXTRA_REMOTE_CONTROL_CLIENT_NAME";
+ // FIXME remove because we are not using intents anymore between AudioService and RcDisplay
/**
* @hide
* The media button event receiver associated with the RemoteControlClient.
@@ -1913,6 +1809,7 @@
public static final String EXTRA_REMOTE_CONTROL_EVENT_RECEIVER =
"android.media.EXTRA_REMOTE_CONTROL_EVENT_RECEIVER";
+ // FIXME remove because we are not using intents anymore between AudioService and RcDisplay
/**
* @hide
* The flags describing what information has changed in the current remote control client.
@@ -1923,33 +1820,6 @@
"android.media.EXTRA_REMOTE_CONTROL_CLIENT_INFO_CHANGED";
/**
- * @hide
- * Notifies the users of the associated remote control client that the information to display
- * has changed.
- @param eventReceiver identifier of a {@link android.content.BroadcastReceiver}
- * that will receive the media button intent, and associated with the remote control
- * client. This method has no effect if
- * {@link #registerMediaButtonEventReceiver(ComponentName)} hasn't been called
- * with the same eventReceiver, or if
- * {@link #unregisterMediaButtonEventReceiver(ComponentName)} has been called.
- * @param infoFlag the type of information that has changed since this method was last called,
- * or the event receiver was registered. Use one or multiple of the following flags to
- * describe what changed:
- * {@link RemoteControlClient#FLAG_INFORMATION_CHANGED_METADATA},
- * {@link RemoteControlClient#FLAG_INFORMATION_CHANGED_KEY_MEDIA},
- * {@link RemoteControlClient#FLAG_INFORMATION_CHANGED_PLAYSTATE},
- * {@link RemoteControlClient#FLAG_INFORMATION_CHANGED_ALBUM_ART}.
- */
- public void notifyRemoteControlInformationChanged(ComponentName eventReceiver, int infoFlag) {
- IAudioService service = getService();
- try {
- service.notifyRemoteControlInformationChanged(eventReceiver, infoFlag);
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in refreshRemoteControlDisplay"+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 46f45a0..acc2b23 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -34,7 +34,6 @@
import android.database.ContentObserver;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
-import android.media.IRemoteControlClientDispatcher;
import android.os.Binder;
import android.os.Environment;
import android.os.Handler;
@@ -2167,45 +2166,12 @@
break;
case MSG_RCDISPLAY_CLEAR:
- // TODO remove log before release
- Log.i(TAG, "Clear remote control display");
- Intent clearIntent = new Intent(AudioManager.REMOTE_CONTROL_CLIENT_CHANGED);
- // no extra means no IRemoteControlClientDispatcher, which is a request to clear
- clearIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mContext.sendBroadcast(clearIntent);
+ onRcDisplayClear();
break;
case MSG_RCDISPLAY_UPDATE:
- synchronized(mCurrentRcLock) {
- // msg.obj is guaranteed to be non null
- RemoteControlStackEntry rcse = (RemoteControlStackEntry)msg.obj;
- if ((mCurrentRcClient == null) ||
- (!mCurrentRcClient.equals(rcse.mRcClient))) {
- // the remote control display owner has changed between the
- // the message to update the display was sent, and the time it
- // gets to be processed (now)
- } else {
- mCurrentRcClientGen++;
- // TODO remove log before release
- Log.i(TAG, "Display/update remote control ");
- Intent rcClientIntent = new Intent(
- AudioManager.REMOTE_CONTROL_CLIENT_CHANGED);
- rcClientIntent.putExtra(
- AudioManager.EXTRA_REMOTE_CONTROL_CLIENT_GENERATION,
- mCurrentRcClientGen);
- rcClientIntent.putExtra(
- AudioManager.EXTRA_REMOTE_CONTROL_CLIENT_INFO_CHANGED,
- msg.arg1);
- rcClientIntent.putExtra(
- AudioManager.EXTRA_REMOTE_CONTROL_EVENT_RECEIVER,
- rcse.mReceiverComponent.flattenToString());
- rcClientIntent.putExtra(
- AudioManager.EXTRA_REMOTE_CONTROL_CLIENT_NAME,
- rcse.mRcClientName);
- rcClientIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mContext.sendBroadcast(rcClientIntent);
- }
- }
+ // msg.obj is guaranteed to be non null
+ onRcDisplayUpdate( (RemoteControlStackEntry) msg.obj, msg.arg1);
break;
case MSG_BT_HEADSET_CNCT_FAILED:
@@ -2893,18 +2859,18 @@
private final Object mCurrentRcLock = new Object();
/**
- * The one remote control client to be polled for display information.
+ * The one remote control client which will receive a request for display information.
* This object may be null.
* Access protected by mCurrentRcLock.
*/
- private IRemoteControlClientDispatcher mCurrentRcClient = null;
+ private IRemoteControlClient mCurrentRcClient = null;
private final static int RC_INFO_NONE = 0;
private final static int RC_INFO_ALL =
- RemoteControlClient.FLAG_INFORMATION_CHANGED_ALBUM_ART |
- RemoteControlClient.FLAG_INFORMATION_CHANGED_KEY_MEDIA |
- RemoteControlClient.FLAG_INFORMATION_CHANGED_METADATA |
- RemoteControlClient.FLAG_INFORMATION_CHANGED_PLAYSTATE;
+ RemoteControlClient.FLAG_INFORMATION_REQUEST_ALBUM_ART |
+ RemoteControlClient.FLAG_INFORMATION_REQUEST_KEY_MEDIA |
+ RemoteControlClient.FLAG_INFORMATION_REQUEST_METADATA |
+ RemoteControlClient.FLAG_INFORMATION_REQUEST_PLAYSTATE;
/**
* A monotonically increasing generation counter for mCurrentRcClient.
@@ -2914,25 +2880,6 @@
private int mCurrentRcClientGen = 0;
/**
- * Returns the current remote control client.
- * @param rcClientId the counter value that matches the extra
- * {@link AudioManager#EXTRA_REMOTE_CONTROL_CLIENT_GENERATION} in the
- * {@link AudioManager#REMOTE_CONTROL_CLIENT_CHANGED} event
- * @return the current IRemoteControlClientDispatcher from which information to display on the
- * remote control can be retrieved, or null if rcClientId doesn't match the current
- * generation counter.
- */
- public IRemoteControlClientDispatcher getRemoteControlClientDispatcher(int rcClientId) {
- synchronized(mCurrentRcLock) {
- if (rcClientId == mCurrentRcClientGen) {
- return mCurrentRcClient;
- } else {
- return null;
- }
- }
- }
-
- /**
* Inner class to monitor remote control client deaths, and remove the client for the
* remote control stack if necessary.
*/
@@ -2965,7 +2912,7 @@
public int mCallingUid;
/** provides access to the information to display on the remote control */
- public IRemoteControlClientDispatcher mRcClient;
+ public IRemoteControlClient mRcClient;
public RcClientDeathHandler mRcClientDeathHandler;
public RemoteControlStackEntry(ComponentName r) {
@@ -3119,6 +3066,103 @@
return false;
}
+ //==========================================================================================
+ // Remote control display / client
+ //==========================================================================================
+ /**
+ * Update the remote control displays with the new "focused" client generation
+ */
+ private void setNewRcClientGenerationOnDisplays_syncRcStack(int newClientGeneration) {
+ // NOTE: Only one IRemoteControlDisplay supported in this implementation
+ if (mRcDisplay != null) {
+ try {
+ mRcDisplay.setCurrentClientGenerationId(newClientGeneration);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead display in onRcDisplayUpdate() "+e);
+ // if we had a display before, stop monitoring its death
+ rcDisplay_stopDeathMonitor_syncRcStack();
+ mRcDisplay = null;
+ }
+ }
+ }
+
+ /**
+ * Update the remote control clients with the new "focused" client generation
+ */
+ private void setNewRcClientGenerationOnClients_syncRcStack(int newClientGeneration) {
+ Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+ while(stackIterator.hasNext()) {
+ RemoteControlStackEntry se = stackIterator.next();
+ if ((se != null) && (se.mRcClient != null)) {
+ try {
+ se.mRcClient.setCurrentClientGenerationId(newClientGeneration);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Dead client in onRcDisplayUpdate()"+e);
+ stackIterator.remove();
+ se.unlinkToRcClientDeath();
+ }
+ }
+ }
+ }
+
+ /**
+ * Update the displays and clients with the new "focused" client generation
+ */
+ private void setNewRcClientGeneration(int newClientGeneration) {
+ synchronized(mRCStack) {
+ // send the new valid client generation ID to all displays
+ setNewRcClientGenerationOnDisplays_syncRcStack(newClientGeneration);
+ // send the new valid client generation ID to all clients
+ setNewRcClientGenerationOnClients_syncRcStack(newClientGeneration);
+ }
+ }
+
+ /**
+ * Called when processing MSG_RCDISPLAY_CLEAR event
+ */
+ private void onRcDisplayClear() {
+ // TODO remove log before release
+ Log.i(TAG, "Clear remote control display");
+
+ synchronized(mCurrentRcLock) {
+ mCurrentRcClientGen++;
+
+ // synchronously update the displays and clients with the new client generation
+ setNewRcClientGeneration(mCurrentRcClientGen);
+ }
+ }
+
+ /**
+ * Called when processing MSG_RCDISPLAY_UPDATE event
+ */
+ private void onRcDisplayUpdate(RemoteControlStackEntry rcse, int flags /* USED ?*/) {
+ synchronized(mCurrentRcLock) {
+ if ((mCurrentRcClient != null) && (mCurrentRcClient.equals(rcse.mRcClient))) {
+ // TODO remove log before release
+ Log.i(TAG, "Display/update remote control ");
+
+ mCurrentRcClientGen++;
+
+ // synchronously update the displays and clients with the new client generation
+ setNewRcClientGeneration(mCurrentRcClientGen);
+
+ // ask the current client that it needs to send info
+ try {
+ mCurrentRcClient.onInformationRequested(mCurrentRcClientGen,
+ flags, mArtworkExpectedWidth, mArtworkExpectedHeight);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Current valid remote client is dead: "+e);
+ mCurrentRcClient = null;
+ }
+ } else {
+ // the remote control display owner has changed between the
+ // the message to update the display was sent, and the time it
+ // gets to be processed (now)
+ }
+ }
+ }
+
+
/**
* Helper function:
* Called synchronized on mRCStack
@@ -3127,6 +3171,7 @@
synchronized(mCurrentRcLock) {
mCurrentRcClient = null;
}
+ // will cause onRcDisplayClear() to be called in AudioService's handler thread
mAudioHandler.sendMessage( mAudioHandler.obtainMessage(MSG_RCDISPLAY_CLEAR) );
}
@@ -3152,6 +3197,7 @@
}
mCurrentRcClient = rcse.mRcClient;
}
+ // will cause onRcDisplayUpdate() to be called in AudioService's handler thread
mAudioHandler.sendMessage( mAudioHandler.obtainMessage(MSG_RCDISPLAY_UPDATE,
infoFlagsAboutToBeUsed /* arg1 */, 0, rcse /* obj, != null */) );
}
@@ -3220,7 +3266,7 @@
/** see AudioManager.registerRemoteControlClient(ComponentName eventReceiver, ...) */
public void registerRemoteControlClient(ComponentName eventReceiver,
- IRemoteControlClientDispatcher rcClient, String clientName, String callingPackageName) {
+ IRemoteControlClient rcClient, String clientName, String callingPackageName) {
synchronized(mAudioFocusLock) {
synchronized(mRCStack) {
// store the new display information
@@ -3235,6 +3281,14 @@
}
// save the new remote control client
rcse.mRcClient = rcClient;
+ if (mRcDisplay != null) {
+ try {
+ rcse.mRcClient.plugRemoteControlDisplay(mRcDisplay);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error connecting remote control display to client: "+e);
+ e.printStackTrace();
+ }
+ }
rcse.mCallingPackageName = callingPackageName;
rcse.mRcClientName = clientName;
rcse.mCallingUid = Binder.getCallingUid();
@@ -3266,18 +3320,121 @@
}
}
- /** see AudioManager.notifyRemoteControlInformationChanged(ComponentName er, int infoFlag) */
- public void notifyRemoteControlInformationChanged(ComponentName eventReceiver, int infoFlag) {
- synchronized(mAudioFocusLock) {
+ /** see AudioManager.unregisterRemoteControlClient(ComponentName eventReceiver, ...) */
+ public void unregisterRemoteControlClient(ComponentName eventReceiver,
+ IRemoteControlClient rcClient) {
+ //FIXME implement
+ }
+
+ /**
+ * The remote control displays.
+ * Access synchronized on mRCStack
+ * NOTE: Only one IRemoteControlDisplay supported in this implementation
+ */
+ private IRemoteControlDisplay mRcDisplay;
+ private RcDisplayDeathHandler mRcDisplayDeathHandler;
+ private int mArtworkExpectedWidth = -1;
+ private int mArtworkExpectedHeight = -1;
+ /**
+ * Inner class to monitor remote control display deaths, and unregister them from the list
+ * of displays if necessary.
+ */
+ private class RcDisplayDeathHandler implements IBinder.DeathRecipient {
+ public void binderDied() {
synchronized(mRCStack) {
- // only refresh if the eventReceiver is at the top of the stack
- if (isCurrentRcController(eventReceiver)) {
- checkUpdateRemoteControlDisplay(infoFlag);
+ Log.w(TAG, " RemoteControl: display died");
+ mRcDisplay = null;
+ }
+ }
+
+ }
+
+ private void rcDisplay_stopDeathMonitor_syncRcStack() {
+ if (mRcDisplay != null) {
+ // we had a display before, stop monitoring its death
+ IBinder b = mRcDisplay.asBinder();
+ try {
+ b.unlinkToDeath(mRcDisplayDeathHandler, 0);
+ } catch (java.util.NoSuchElementException e) {
+ // being conservative here
+ Log.e(TAG, "Error while trying to unlink display death handler " + e);
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private void rcDisplay_startDeathMonitor_syncRcStack() {
+ if (mRcDisplay != null) {
+ // new non-null display, monitor its death
+ IBinder b = mRcDisplay.asBinder();
+ mRcDisplayDeathHandler = new RcDisplayDeathHandler();
+ try {
+ b.linkToDeath(mRcDisplayDeathHandler, 0);
+ } catch (RemoteException e) {
+ // remote control display is DOA, disqualify it
+ Log.w(TAG, "registerRemoteControlDisplay() has a dead client " + b);
+ mRcDisplay = null;
+ }
+ }
+ }
+
+ public void registerRemoteControlDisplay(IRemoteControlDisplay rcd) {
+ synchronized(mRCStack) {
+ if (mRcDisplay == rcd) {
+ return;
+ }
+ // if we had a display before, stop monitoring its death
+ rcDisplay_stopDeathMonitor_syncRcStack();
+ mRcDisplay = rcd;
+ // new display, start monitoring its death
+ rcDisplay_startDeathMonitor_syncRcStack();
+
+ // let all the remote control clients there is a new display
+ Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+ while(stackIterator.hasNext()) {
+ RemoteControlStackEntry rcse = stackIterator.next();
+ if(rcse.mRcClient != null) {
+ try {
+ rcse.mRcClient.plugRemoteControlDisplay(mRcDisplay);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error connecting remote control display to client: " + e);
+ e.printStackTrace();
+ }
}
}
}
}
+ public void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) {
+ synchronized(mRCStack) {
+ // if we had a display before, stop monitoring its death
+ rcDisplay_stopDeathMonitor_syncRcStack();
+ mRcDisplay = null;
+
+ // disconnect this remote control display from all the clients
+ Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+ while(stackIterator.hasNext()) {
+ RemoteControlStackEntry rcse = stackIterator.next();
+ if(rcse.mRcClient != null) {
+ try {
+ rcse.mRcClient.unplugRemoteControlDisplay(rcd);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error disconnecting remote control display to client: " + e);
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ }
+
+ public void remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h) {
+ synchronized(mRCStack) {
+ // NOTE: Only one IRemoteControlDisplay supported in this implementation
+ mArtworkExpectedWidth = w;
+ mArtworkExpectedHeight = h;
+ }
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
// TODO probably a lot more to do here than just the audio focus and remote control stacks
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 7f9ced9..7bf9814 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -18,7 +18,8 @@
import android.content.ComponentName;
import android.media.IAudioFocusDispatcher;
-import android.media.IRemoteControlClientDispatcher;
+import android.media.IRemoteControlClient;
+import android.media.IRemoteControlDisplay;
/**
* {@hide}
@@ -88,13 +89,14 @@
void unregisterMediaButtonEventReceiver(in ComponentName eventReceiver);
- void registerRemoteControlClient(in ComponentName eventReceiver,
- in IRemoteControlClientDispatcher rcClient, in String clientName,
- in String callingPackageName);
+ oneway void registerRemoteControlClient(in ComponentName eventReceiver,
+ in IRemoteControlClient rcClient, in String clientName, in String callingPackageName);
+ oneway void unregisterRemoteControlClient(in ComponentName eventReceiver,
+ in IRemoteControlClient rcClient);
- IRemoteControlClientDispatcher getRemoteControlClientDispatcher(in int rcClientId);
-
- void notifyRemoteControlInformationChanged(in ComponentName eventReceiver, int infoFlag);
+ oneway void registerRemoteControlDisplay(in IRemoteControlDisplay rcd);
+ oneway void unregisterRemoteControlDisplay(in IRemoteControlDisplay rcd);
+ oneway void remoteControlDisplayUsesBitmapSize(in IRemoteControlDisplay rcd, int w, int h);
void startBluetoothSco(IBinder cb);
diff --git a/media/java/android/media/IRemoteControlClient.aidl b/media/java/android/media/IRemoteControlClient.aidl
new file mode 100644
index 0000000..0fbba20
--- /dev/null
+++ b/media/java/android/media/IRemoteControlClient.aidl
@@ -0,0 +1,51 @@
+/* Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.graphics.Bitmap;
+import android.media.IRemoteControlDisplay;
+
+/**
+ * @hide
+ * Interface registered by AudioManager to notify a source of remote control information
+ * that information is requested to be displayed on the remote control (through
+ * IRemoteControlDisplay).
+ * {@see AudioManager#registerRemoteControlClient(RemoteControlClient)}.
+ */
+oneway interface IRemoteControlClient
+{
+ /**
+ * Notifies a remote control client that information for the given generation ID is
+ * requested. If the flags contains
+ * {@link RemoteControlClient#FLAG_INFORMATION_REQUESTED_ALBUM_ART} then the width and height
+ * parameters are valid.
+ * @param generationId
+ * @param infoFlags
+ * @param artWidth if > 0, artHeight must be > 0 too.
+ * @param artHeight
+ * FIXME: is infoFlags required? since the RCC pushes info, this might always be called
+ * with RC_INFO_ALL
+ */
+ void onInformationRequested(int generationId, int infoFlags, int artWidth, int artHeight);
+
+ /**
+ * Sets the generation counter of the current client that is displayed on the remote control.
+ */
+ void setCurrentClientGenerationId(int clientGeneration);
+
+ void plugRemoteControlDisplay(IRemoteControlDisplay rcd);
+ void unplugRemoteControlDisplay(IRemoteControlDisplay rcd);
+}
\ No newline at end of file
diff --git a/media/java/android/media/IRemoteControlClientDispatcher.aidl b/media/java/android/media/IRemoteControlClientDispatcher.aidl
deleted file mode 100644
index 98142cc..0000000
--- a/media/java/android/media/IRemoteControlClientDispatcher.aidl
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media;
-
-import android.graphics.Bitmap;
-
-/**
- * @hide
- * Interface registered by AudioManager to dispatch remote control information requests
- * to the RemoteControlClient implementation. This is used by AudioService.
- * {@see AudioManager#registerRemoteControlClient(ComponentName, RemoteControlClient)}.
- */
-interface IRemoteControlClientDispatcher
-{
- /**
- * Called by a remote control to retrieve a String of information to display.
- * @param field the identifier for a metadata field to retrieve. Valid values are
- * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUM},
- * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST},
- * {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
- * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ARTIST},
- * {@link android.media.MediaMetadataRetriever#METADATA_KEY_AUTHOR},
- * {@link android.media.MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER},
- * {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPILATION},
- * {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPOSER},
- * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DATE},
- * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER},
- * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION},
- * {@link android.media.MediaMetadataRetriever#METADATA_KEY_GENRE},
- * {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
- * {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER},
- * {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}.
- * @return null if the requested field is not supported, or the String matching the
- * metadata field.
- */
- String getMetadataStringForClient(String clientName, int field);
-
- /**
- * Called by a remote control to retrieve the current playback state.
- * @return one of the following values:
- * {@link android.media.AudioManager.RemoteControlParameters#PLAYSTATE_STOPPED},
- * {@link android.media.AudioManager.RemoteControlParameters#PLAYSTATE_PAUSED},
- * {@link android.media.AudioManager.RemoteControlParameters#PLAYSTATE_PLAYING},
- * {@link android.media.AudioManager.RemoteControlParameters#PLAYSTATE_FAST_FORWARDING},
- * {@link android.media.AudioManager.RemoteControlParameters#PLAYSTATE_REWINDING},
- * {@link android.media.AudioManager.RemoteControlParameters#PLAYSTATE_SKIPPING_FORWARDS},
- * {@link android.media.AudioManager.RemoteControlParameters#PLAYSTATE_SKIPPING_BACKWARDS},
- * {@link android.media.AudioManager.RemoteControlParameters#PLAYSTATE_BUFFERING},
- * {@link android.media.AudioManager.RemoteControlParameters#PLAYSTATE_ERROR}.
- */
- int getPlaybackStateForClient(String clientName);
-
- /**
- * Called by a remote control to retrieve the flags for the media transport control buttons
- * that this client supports.
- * @see {@link android.media.AudioManager.RemoteControlParameters#FLAG_KEY_MEDIA_PREVIOUS},
- * {@link android.media.AudioManager.RemoteControlParameters#FLAG_KEY_MEDIA_REWIND},
- * {@link android.media.AudioManager.RemoteControlParameters#FLAG_KEY_MEDIA_PLAY},
- * {@link android.media.AudioManager.RemoteControlParameters#FLAG_KEY_MEDIA_PLAY_PAUSE},
- * {@link android.media.AudioManager.RemoteControlParameters#FLAG_KEY_MEDIA_PAUSE},
- * {@link android.media.AudioManager.RemoteControlParameters#FLAG_KEY_MEDIA_STOP},
- * {@link android.media.AudioManager.RemoteControlParameters#FLAG_KEY_MEDIA_FAST_FORWARD},
- * {@link android.media.AudioManager.RemoteControlParameters#FLAG_KEY_MEDIA_NEXT}
- */
- int getTransportControlFlagsForClient(String clientName);
-
- /**
- * Called by a remote control to retrieve the album art picture at the requested size.
- * Note that returning a bitmap smaller than the maximum requested dimension is accepted
- * and it will be scaled as needed, but exceeding the maximum dimensions may produce
- * unspecified results, such as the image being cropped or simply not being displayed.
- * @param maxWidth the maximum width of the requested bitmap expressed in pixels.
- * @param maxHeight the maximum height of the requested bitmap expressed in pixels.
- * @return the bitmap for the album art, or null if there isn't any.
- * @see android.graphics.Bitmap
- */
- Bitmap getAlbumArtForClient(String clientName, int maxWidth, int maxHeight);
-}
diff --git a/media/java/android/media/IRemoteControlDisplay.aidl b/media/java/android/media/IRemoteControlDisplay.aidl
new file mode 100644
index 0000000..19ea202
--- /dev/null
+++ b/media/java/android/media/IRemoteControlDisplay.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.graphics.Bitmap;
+import android.os.Bundle;
+
+/**
+ * @hide
+ * Interface registered through AudioManager of an object that displays information
+ * received from a remote control client.
+ * {@see AudioManager#registerRemoteControlDisplay(IRemoteControlDisplay)}.
+ */
+oneway interface IRemoteControlDisplay
+{
+ /**
+ * Sets the generation counter of the current client that is displayed on the remote control.
+ */
+ void setCurrentClientGenerationId(int clientGeneration);
+
+ void setPlaybackState(int generationId, int state);
+
+ void setMetadata(int generationId, in Bundle metadata);
+
+ void setTransportControlFlags(int generationId, int transportControlFlags);
+
+ void setArtwork(int generationId, in Bitmap artwork);
+}
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index c384636..bfe08b9 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -17,69 +17,84 @@
package android.media;
import android.content.ComponentName;
+import android.content.SharedPreferences.Editor;
import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
/**
* @hide
- * Interface for an object that exposes information meant to be consumed by remote controls
+ * CANDIDATE FOR SDK
+ * RemoteControlClient enables exposing information meant to be consumed by remote controls
* capable of displaying metadata, album art and media transport control buttons.
- * Such a remote control client object is associated with a media button event receiver
+ * A remote control client object is associated with a media button event receiver
* when registered through
- * {@link AudioManager#registerRemoteControlClient(ComponentName, RemoteControlClient)}.
+ * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
*/
-public interface RemoteControlClient
+public class RemoteControlClient
{
+ private final static String TAG = "RemoteControlClient";
+
/**
* Playback state of a RemoteControlClient which is stopped.
*
- * @see android.media.RemoteControlClient#getPlaybackState()
+ * @see #setPlaybackState(int)
*/
public final static int PLAYSTATE_STOPPED = 1;
/**
* Playback state of a RemoteControlClient which is paused.
*
- * @see android.media.RemoteControlClient#getPlaybackState()
+ * @see #setPlaybackState(int)
*/
public final static int PLAYSTATE_PAUSED = 2;
/**
* Playback state of a RemoteControlClient which is playing media.
*
- * @see android.media.RemoteControlClient#getPlaybackState()
+ * @see #setPlaybackState(int)
*/
public final static int PLAYSTATE_PLAYING = 3;
/**
* Playback state of a RemoteControlClient which is fast forwarding in the media
* it is currently playing.
*
- * @see android.media.RemoteControlClient#getPlaybackState()
+ * @see #setPlaybackState(int)
*/
public final static int PLAYSTATE_FAST_FORWARDING = 4;
/**
* Playback state of a RemoteControlClient which is fast rewinding in the media
* it is currently playing.
*
- * @see android.media.RemoteControlClient#getPlaybackState()
+ * @see #setPlaybackState(int)
*/
public final static int PLAYSTATE_REWINDING = 5;
/**
* Playback state of a RemoteControlClient which is skipping to the next
* logical chapter (such as a song in a playlist) in the media it is currently playing.
*
- * @see android.media.RemoteControlClient#getPlaybackState()
+ * @see #setPlaybackState(int)
*/
public final static int PLAYSTATE_SKIPPING_FORWARDS = 6;
/**
* Playback state of a RemoteControlClient which is skipping back to the previous
* logical chapter (such as a song in a playlist) in the media it is currently playing.
*
- * @see android.media.RemoteControlClient#getPlaybackState()
+ * @see #setPlaybackState(int)
*/
public final static int PLAYSTATE_SKIPPING_BACKWARDS = 7;
/**
* Playback state of a RemoteControlClient which is buffering data to play before it can
* start or resume playback.
*
- * @see android.media.RemoteControlClient#getPlaybackState()
+ * @see #setPlaybackState(int)
*/
public final static int PLAYSTATE_BUFFERING = 8;
/**
@@ -88,98 +103,188 @@
* connectivity when attempting to stream data from a server, or expired user credentials
* when trying to play subscription-based content.
*
- * @see android.media.RemoteControlClient#getPlaybackState()
+ * @see #setPlaybackState(int)
*/
public final static int PLAYSTATE_ERROR = 9;
+ /**
+ * @hide
+ * The value of a playback state when none has been declared
+ */
+ public final static int PLAYSTATE_NONE = 0;
/**
* Flag indicating a RemoteControlClient makes use of the "previous" media key.
*
- * @see android.media.RemoteControlClient#getTransportControlFlags()
+ * @see #setTransportControlFlags(int)
* @see android.view.KeyEvent#KEYCODE_MEDIA_PREVIOUS
*/
public final static int FLAG_KEY_MEDIA_PREVIOUS = 1 << 0;
/**
* Flag indicating a RemoteControlClient makes use of the "rewing" media key.
*
- * @see android.media.RemoteControlClient#getTransportControlFlags()
+ * @see #setTransportControlFlags(int)
* @see android.view.KeyEvent#KEYCODE_MEDIA_REWIND
*/
public final static int FLAG_KEY_MEDIA_REWIND = 1 << 1;
/**
* Flag indicating a RemoteControlClient makes use of the "play" media key.
*
- * @see android.media.RemoteControlClient#getTransportControlFlags()
+ * @see #setTransportControlFlags(int)
* @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY
*/
public final static int FLAG_KEY_MEDIA_PLAY = 1 << 2;
/**
* Flag indicating a RemoteControlClient makes use of the "play/pause" media key.
*
- * @see android.media.RemoteControlClient#getTransportControlFlags()
+ * @see #setTransportControlFlags(int)
* @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE
*/
public final static int FLAG_KEY_MEDIA_PLAY_PAUSE = 1 << 3;
/**
* Flag indicating a RemoteControlClient makes use of the "pause" media key.
*
- * @see android.media.RemoteControlClient#getTransportControlFlags()
+ * @see #setTransportControlFlags(int)
* @see android.view.KeyEvent#KEYCODE_MEDIA_PAUSE
*/
public final static int FLAG_KEY_MEDIA_PAUSE = 1 << 4;
/**
* Flag indicating a RemoteControlClient makes use of the "stop" media key.
*
- * @see android.media.RemoteControlClient#getTransportControlFlags()
+ * @see #setTransportControlFlags(int)
* @see android.view.KeyEvent#KEYCODE_MEDIA_STOP
*/
public final static int FLAG_KEY_MEDIA_STOP = 1 << 5;
/**
* Flag indicating a RemoteControlClient makes use of the "fast forward" media key.
*
- * @see android.media.RemoteControlClient#getTransportControlFlags()
+ * @see #setTransportControlFlags(int)
* @see android.view.KeyEvent#KEYCODE_MEDIA_FAST_FORWARD
*/
public final static int FLAG_KEY_MEDIA_FAST_FORWARD = 1 << 6;
/**
* Flag indicating a RemoteControlClient makes use of the "next" media key.
*
- * @see android.media.RemoteControlClient#getTransportControlFlags()
+ * @see #setTransportControlFlags(int)
* @see android.view.KeyEvent#KEYCODE_MEDIA_NEXT
*/
public final static int FLAG_KEY_MEDIA_NEXT = 1 << 7;
/**
- * Flag used to signal that the metadata exposed by the RemoteControlClient has changed.
- *
- * @see #notifyRemoteControlInformationChanged(ComponentName, int)
+ * @hide
+ * The flags for when no media keys are declared supported
*/
- public final static int FLAG_INFORMATION_CHANGED_METADATA = 1 << 0;
+ public final static int FLAGS_KEY_MEDIA_NONE = 0;
+
/**
+ * @hide
+ * Flag used to signal some type of metadata exposed by the RemoteControlClient is requested.
+ */
+ public final static int FLAG_INFORMATION_REQUEST_METADATA = 1 << 0;
+ /**
+ * @hide
+ * FIXME doc not valid
* Flag used to signal that the transport control buttons supported by the
* RemoteControlClient have changed.
* This can for instance happen when playback is at the end of a playlist, and the "next"
* operation is not supported anymore.
- *
- * @see #notifyRemoteControlInformationChanged(ComponentName, int)
*/
- public final static int FLAG_INFORMATION_CHANGED_KEY_MEDIA = 1 << 1;
+ public final static int FLAG_INFORMATION_REQUEST_KEY_MEDIA = 1 << 1;
/**
+ * @hide
+ * FIXME doc not valid
* Flag used to signal that the playback state of the RemoteControlClient has changed.
- *
- * @see #notifyRemoteControlInformationChanged(ComponentName, int)
*/
- public final static int FLAG_INFORMATION_CHANGED_PLAYSTATE = 1 << 2;
+ public final static int FLAG_INFORMATION_REQUEST_PLAYSTATE = 1 << 2;
/**
+ * @hide
+ * FIXME doc not valid
* Flag used to signal that the album art for the RemoteControlClient has changed.
- *
- * @see #notifyRemoteControlInformationChanged(ComponentName, int)
*/
- public final static int FLAG_INFORMATION_CHANGED_ALBUM_ART = 1 << 3;
+ public final static int FLAG_INFORMATION_REQUEST_ALBUM_ART = 1 << 3;
/**
- * Called by a remote control to retrieve a String of information to display.
- * @param field the identifier for a metadata field to retrieve. Valid values are
+ * Class constructor.
+ * @param mediaButtonEventReceiver the receiver for the media button events.
+ * @see AudioManager#registerMediaButtonEventReceiver(ComponentName)
+ * @see AudioManager#registerRemoteControlClient(RemoteControlClient)
+ */
+ public RemoteControlClient(ComponentName mediaButtonEventReceiver) {
+ mRcEventReceiver = mediaButtonEventReceiver;
+
+ Looper looper;
+ if ((looper = Looper.myLooper()) != null) {
+ mEventHandler = new EventHandler(this, looper);
+ } else if ((looper = Looper.getMainLooper()) != null) {
+ mEventHandler = new EventHandler(this, looper);
+ } else {
+ mEventHandler = null;
+ Log.e(TAG, "RemoteControlClient() couldn't find main application thread");
+ }
+ }
+
+ /**
+ * Class constructor for a remote control client whose internal event handling
+ * happens on a user-provided Looper.
+ * @param mediaButtonEventReceiver the receiver for the media button events.
+ * @param looper the Looper running the event loop.
+ * @see AudioManager#registerMediaButtonEventReceiver(ComponentName)
+ * @see AudioManager#registerRemoteControlClient(RemoteControlClient)
+ */
+ public RemoteControlClient(ComponentName mediaButtonEventReceiver, Looper looper) {
+ mRcEventReceiver = mediaButtonEventReceiver;
+
+ mEventHandler = new EventHandler(this, looper);
+ }
+
+ /**
+ * Class used to modify metadata in a {@link RemoteControlClient} object.
+ */
+ public class MetadataEditor {
+
+ private MetadataEditor() { /* only use factory */ }
+
+ public MetadataEditor putString(int key, String value) {
+ return this;
+ }
+
+ public MetadataEditor putBitmap(int key, Bitmap bitmap) {
+ return this;
+ }
+
+ public void clear() {
+
+ }
+
+ public void apply() {
+
+ }
+ }
+
+ public MetadataEditor editMetadata(boolean startEmpty) {
+ return (new MetadataEditor());
+ }
+
+
+ /**
+ * @hide
+ * FIXME migrate this functionality under MetadataEditor
+ * Start collecting information to be displayed.
+ * Use {@link #commitMetadata()} to signal the end of the collection which has been created
+ * through one or multiple calls to {@link #addMetadataString(int, int, String)}.
+ */
+ public void startMetadata() {
+ synchronized(mCacheLock) {
+ mMetadata.clear();
+ }
+ }
+
+ /**
+ * @hide
+ * FIXME migrate this functionality under MetadataEditor
+ * Adds textual information to be displayed.
+ * Note that none of the information added before {@link #startMetadata()},
+ * and after {@link #commitMetadata()} has been called, will be displayed.
+ * @param key the identifier of a the metadata field to set. Valid values are
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUM},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
@@ -195,14 +300,54 @@
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}.
- * @return null if the requested field is not supported, or the String matching the
- * metadata field.
+ * @param value the String for the field value, or null to signify there is no valid
+ * information for the field.
*/
- String getMetadataString(int field);
+ public void addMetadataString(int key, String value) {
+ synchronized(mCacheLock) {
+ // store locally
+ mMetadata.putString(String.valueOf(key), value);
+ }
+ }
/**
- * Called by a remote control to retrieve the current playback state.
- * @return one of the following values:
+ * @hide
+ * FIXME migrate this functionality under MetadataEditor
+ * Marks all the metadata previously set with {@link #addMetadataString(int, int, String)} as
+ * eligible to be displayed.
+ */
+ public void commitMetadata() {
+ synchronized(mCacheLock) {
+ // send to remote control display if conditions are met
+ sendMetadata_syncCacheLock();
+ }
+ }
+
+ /**
+ * @hide
+ * FIXME migrate this functionality under MetadataEditor
+ * Sets the album / artwork picture to be displayed on the remote control.
+ * @param artwork the bitmap for the artwork, or null if there isn't any.
+ * @see android.graphics.Bitmap
+ */
+ public void setArtwork(Bitmap artwork) {
+ synchronized(mCacheLock) {
+ // resize and store locally
+ if (mArtworkExpectedWidth > 0) {
+ mArtwork = scaleBitmapIfTooBig(artwork,
+ mArtworkExpectedWidth, mArtworkExpectedHeight);
+ } else {
+ // no valid resize dimensions, store as is
+ mArtwork = artwork;
+ }
+ // send to remote control display if conditions are met
+ sendArtwork_syncCacheLock();
+ }
+ }
+
+ /**
+ * Sets the current playback state.
+ * @param state the current playback state, one of the following values:
* {@link #PLAYSTATE_STOPPED},
* {@link #PLAYSTATE_PAUSED},
* {@link #PLAYSTATE_PLAYING},
@@ -213,12 +358,20 @@
* {@link #PLAYSTATE_BUFFERING},
* {@link #PLAYSTATE_ERROR}.
*/
- int getPlaybackState();
+ public void setPlaybackState(int state) {
+ synchronized(mCacheLock) {
+ // store locally
+ mPlaybackState = state;
+
+ // send to remote control display if conditions are met
+ sendPlaybackState_syncCacheLock();
+ }
+ }
/**
- * Called by a remote control to retrieve the flags for the media transport control buttons
- * that this client supports.
- * @see {@link #FLAG_KEY_MEDIA_PREVIOUS},
+ * Sets the flags for the media transport control buttons that this client supports.
+ * @param a combination of the following flags:
+ * {@link #FLAG_KEY_MEDIA_PREVIOUS},
* {@link #FLAG_KEY_MEDIA_REWIND},
* {@link #FLAG_KEY_MEDIA_PLAY},
* {@link #FLAG_KEY_MEDIA_PLAY_PAUSE},
@@ -227,17 +380,311 @@
* {@link #FLAG_KEY_MEDIA_FAST_FORWARD},
* {@link #FLAG_KEY_MEDIA_NEXT}
*/
- int getTransportControlFlags();
+ public void setTransportControlFlags(int transportControlFlags) {
+ synchronized(mCacheLock) {
+ // store locally
+ mTransportControlFlags = transportControlFlags;
+
+ // send to remote control display if conditions are met
+ sendTransportControlFlags_syncCacheLock();
+ }
+ }
/**
- * Called by a remote control to retrieve the album art picture at the requested size.
- * Note that returning a bitmap smaller than the maximum requested dimension is accepted
- * and it will be scaled as needed, but exceeding the maximum dimensions may produce
- * unspecified results, such as the image being cropped or simply not being displayed.
- * @param maxWidth the maximum width of the requested bitmap expressed in pixels.
- * @param maxHeight the maximum height of the requested bitmap expressed in pixels.
- * @return the bitmap for the album art, or null if there isn't any.
- * @see android.graphics.Bitmap
+ * Lock for all cached data
*/
- Bitmap getAlbumArt(int maxWidth, int maxHeight);
+ private final Object mCacheLock = new Object();
+ /**
+ * Cache for the playback state.
+ * Access synchronized on mCacheLock
+ */
+ private int mPlaybackState = PLAYSTATE_NONE;
+ /**
+ * Cache for the artwork bitmap.
+ * Access synchronized on mCacheLock
+ */
+ private Bitmap mArtwork;
+ private final int ARTWORK_DEFAULT_SIZE = 256;
+ private int mArtworkExpectedWidth = ARTWORK_DEFAULT_SIZE;
+ private int mArtworkExpectedHeight = ARTWORK_DEFAULT_SIZE;
+ /**
+ * Cache for the transport control mask.
+ * Access synchronized on mCacheLock
+ */
+ private int mTransportControlFlags = FLAGS_KEY_MEDIA_NONE;
+ /**
+ * Cache for the metadata strings.
+ * Access synchronized on mCacheLock
+ */
+ private Bundle mMetadata = new Bundle();
+ /**
+ * The current remote control client generation ID across the system
+ */
+ private int mCurrentClientGenId = -1;
+ /**
+ * The remote control client generation ID, the last time it was told it was the current RC.
+ * If (mCurrentClientGenId == mInternalClientGenId) is true, it means that this remote control
+ * client is the "focused" one, and that whenever this client's info is updated, it needs to
+ * send it to the known IRemoteControlDisplay interfaces.
+ */
+ private int mInternalClientGenId = -2;
+
+ /**
+ * The media button event receiver associated with this remote control client
+ */
+ private final ComponentName mRcEventReceiver;
+
+ /**
+ * The remote control display to which this client will send information.
+ * NOTE: Only one IRemoteControlDisplay supported in this implementation
+ */
+ private IRemoteControlDisplay mRcDisplay;
+
+ /**
+ * @hide
+ * Accessor to media button event receiver
+ */
+ public ComponentName getRcEventReceiver() {
+ return mRcEventReceiver;
+ }
+ /**
+ * @hide
+ * Accessor to IRemoteControlClient
+ */
+ public IRemoteControlClient getIRemoteControlClient() {
+ return mIRCC;
+ }
+
+ /**
+ * The IRemoteControlClient implementation
+ */
+ private IRemoteControlClient mIRCC = new IRemoteControlClient.Stub() {
+
+ public void onInformationRequested(int clientGeneration, int infoFlags,
+ int artWidth, int artHeight) {
+ // 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,
+ artWidth, artHeight,
+ new Integer(clientGeneration)));
+ // send the information
+ mEventHandler.removeMessages(MSG_REQUEST_PLAYBACK_STATE);
+ mEventHandler.removeMessages(MSG_REQUEST_METADATA);
+ mEventHandler.removeMessages(MSG_REQUEST_TRANSPORTCONTROL);
+ mEventHandler.removeMessages(MSG_REQUEST_ARTWORK);
+ mEventHandler.dispatchMessage(
+ mEventHandler.obtainMessage(MSG_REQUEST_PLAYBACK_STATE));
+ mEventHandler.dispatchMessage(
+ mEventHandler.obtainMessage(MSG_REQUEST_TRANSPORTCONTROL));
+ mEventHandler.dispatchMessage(mEventHandler.obtainMessage(MSG_REQUEST_METADATA));
+ mEventHandler.dispatchMessage(mEventHandler.obtainMessage(MSG_REQUEST_ARTWORK));
+ }
+ }
+
+ public void setCurrentClientGenerationId(int clientGeneration) {
+ // only post messages, we can't block here
+ if (mEventHandler != null) {
+ mEventHandler.removeMessages(MSG_NEW_CURRENT_CLIENT_GEN);
+ mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
+ MSG_NEW_CURRENT_CLIENT_GEN, clientGeneration, 0/*ignored*/));
+ }
+ }
+
+ public void plugRemoteControlDisplay(IRemoteControlDisplay rcd) {
+ // only post messages, we can't block here
+ if (mEventHandler != null) {
+ mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
+ MSG_PLUG_DISPLAY, rcd));
+ }
+ }
+
+ public void unplugRemoteControlDisplay(IRemoteControlDisplay rcd) {
+ // only post messages, we can't block here
+ if (mEventHandler != null) {
+ mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
+ MSG_UNPLUG_DISPLAY, rcd));
+ }
+ }
+ };
+
+ private EventHandler mEventHandler;
+ private final static int MSG_REQUEST_PLAYBACK_STATE = 1;
+ private final static int MSG_REQUEST_METADATA = 2;
+ private final static int MSG_REQUEST_TRANSPORTCONTROL = 3;
+ private final static int MSG_REQUEST_ARTWORK = 4;
+ private final static int MSG_NEW_INTERNAL_CLIENT_GEN = 5;
+ private final static int MSG_NEW_CURRENT_CLIENT_GEN = 6;
+ private final static int MSG_PLUG_DISPLAY = 7;
+ private final static int MSG_UNPLUG_DISPLAY = 8;
+
+ private class EventHandler extends Handler {
+ public EventHandler(RemoteControlClient rcc, Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch(msg.what) {
+ case MSG_REQUEST_PLAYBACK_STATE:
+ synchronized (mCacheLock) {
+ sendPlaybackState_syncCacheLock();
+ }
+ break;
+ case MSG_REQUEST_METADATA:
+ synchronized (mCacheLock) {
+ sendMetadata_syncCacheLock();
+ }
+ break;
+ case MSG_REQUEST_TRANSPORTCONTROL:
+ synchronized (mCacheLock) {
+ sendTransportControlFlags_syncCacheLock();
+ }
+ break;
+ case MSG_REQUEST_ARTWORK:
+ synchronized (mCacheLock) {
+ sendArtwork_syncCacheLock();
+ }
+ break;
+ case MSG_NEW_INTERNAL_CLIENT_GEN:
+ onNewInternalClientGen((Integer)msg.obj, msg.arg1, msg.arg2);
+ break;
+ case MSG_NEW_CURRENT_CLIENT_GEN:
+ onNewCurrentClientGen(msg.arg1);
+ break;
+ case MSG_PLUG_DISPLAY:
+ onPlugDisplay((IRemoteControlDisplay)msg.obj);
+ break;
+ case MSG_UNPLUG_DISPLAY:
+ onUnplugDisplay((IRemoteControlDisplay)msg.obj);
+ break;
+ default:
+ Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler");
+ }
+ }
+ }
+
+ private void sendPlaybackState_syncCacheLock() {
+ if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
+ try {
+ mRcDisplay.setPlaybackState(mInternalClientGenId, mPlaybackState);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in setPlaybackState(), dead display "+e);
+ mRcDisplay = null;
+ mArtworkExpectedWidth = -1;
+ mArtworkExpectedHeight = -1;
+ }
+ }
+ }
+
+ private void sendMetadata_syncCacheLock() {
+ if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
+ try {
+ mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in sendPlaybackState(), dead display "+e);
+ mRcDisplay = null;
+ mArtworkExpectedWidth = -1;
+ mArtworkExpectedHeight = -1;
+ }
+ }
+ }
+
+ private void sendTransportControlFlags_syncCacheLock() {
+ if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
+ try {
+ mRcDisplay.setTransportControlFlags(mInternalClientGenId,
+ mTransportControlFlags);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in sendTransportControlFlags(), dead display "+e);
+ mRcDisplay = null;
+ mArtworkExpectedWidth = -1;
+ mArtworkExpectedHeight = -1;
+ }
+ }
+ }
+
+ private void sendArtwork_syncCacheLock() {
+ if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
+ // even though we have already scaled in setArtwork(), when this client needs to
+ // send the bitmap, there might be newer and smaller expected dimensions, so we have
+ // to check again.
+ mArtwork = scaleBitmapIfTooBig(mArtwork, mArtworkExpectedWidth, mArtworkExpectedHeight);
+ try {
+ mRcDisplay.setArtwork(mInternalClientGenId, mArtwork);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in sendArtwork(), dead display "+e);
+ mRcDisplay = null;
+ mArtworkExpectedWidth = -1;
+ mArtworkExpectedHeight = -1;
+ }
+ }
+ }
+
+ private void onNewInternalClientGen(Integer clientGeneration, int artWidth, int artHeight) {
+ synchronized (mCacheLock) {
+ // this remote control client is told it is the "focused" one:
+ // it implies that now (mCurrentClientGenId == mInternalClientGenId) is true
+ mInternalClientGenId = clientGeneration.intValue();
+ if (artWidth > 0) {
+ mArtworkExpectedWidth = artWidth;
+ mArtworkExpectedHeight = artHeight;
+ }
+ }
+ }
+
+ private void onNewCurrentClientGen(int clientGeneration) {
+ synchronized (mCacheLock) {
+ mCurrentClientGenId = clientGeneration;
+ }
+ }
+
+ private void onPlugDisplay(IRemoteControlDisplay rcd) {
+ synchronized(mCacheLock) {
+ mRcDisplay = rcd;
+ }
+ }
+
+ private void onUnplugDisplay(IRemoteControlDisplay rcd) {
+ synchronized(mCacheLock) {
+ if ((mRcDisplay != null) && (mRcDisplay.equals(rcd))) {
+ mRcDisplay = null;
+ mArtworkExpectedWidth = ARTWORK_DEFAULT_SIZE;
+ mArtworkExpectedHeight = ARTWORK_DEFAULT_SIZE;
+ }
+ }
+ }
+
+ /**
+ * Scale a bitmap to fit the smallest dimension by uniformly scaling the incoming bitmap.
+ * If the bitmap fits, then do nothing and return the original.
+ *
+ * @param bitmap
+ * @param maxWidth
+ * @param maxHeight
+ * @return
+ */
+
+ private Bitmap scaleBitmapIfTooBig(Bitmap bitmap, int maxWidth, int maxHeight) {
+ final int width = bitmap.getWidth();
+ final int height = bitmap.getHeight();
+ if (width > maxWidth || height > maxHeight) {
+ float scale = Math.min((float) maxWidth / width, (float) maxHeight / height);
+ int newWidth = Math.round(scale * width);
+ int newHeight = Math.round(scale * height);
+ Bitmap outBitmap = Bitmap.createBitmap(newWidth, newHeight, bitmap.getConfig());
+ Canvas canvas = new Canvas(outBitmap);
+ Paint paint = new Paint();
+ paint.setAntiAlias(true);
+ paint.setFilterBitmap(true);
+ canvas.drawBitmap(bitmap, null,
+ new RectF(0, 0, outBitmap.getWidth(), outBitmap.getHeight()), paint);
+ bitmap = outBitmap;
+ }
+ return bitmap;
+
+ }
}
diff --git a/media/libstagefright/codecs/aacdec/SoftAAC.cpp b/media/libstagefright/codecs/aacdec/SoftAAC.cpp
index f0a330f..2abdb56 100644
--- a/media/libstagefright/codecs/aacdec/SoftAAC.cpp
+++ b/media/libstagefright/codecs/aacdec/SoftAAC.cpp
@@ -378,24 +378,36 @@
// fall through
}
- if (mUpsamplingFactor == 2) {
- if (mConfig->desiredChannels == 1) {
- memcpy(&mConfig->pOutputBuffer[1024],
- &mConfig->pOutputBuffer[2048],
- numOutBytes * 2);
+ if (decoderErr == MP4AUDEC_SUCCESS || mNumSamplesOutput > 0) {
+ // We'll only output data if we successfully decoded it or
+ // we've previously decoded valid data, in the latter case
+ // (decode failed) we'll output a silent frame.
+
+ if (mUpsamplingFactor == 2) {
+ if (mConfig->desiredChannels == 1) {
+ memcpy(&mConfig->pOutputBuffer[1024],
+ &mConfig->pOutputBuffer[2048],
+ numOutBytes * 2);
+ }
+ numOutBytes *= 2;
}
- numOutBytes *= 2;
+
+ outHeader->nFilledLen = numOutBytes;
+ outHeader->nFlags = 0;
+
+ outHeader->nTimeStamp =
+ mAnchorTimeUs
+ + (mNumSamplesOutput * 1000000ll) / mConfig->samplingRate;
+
+ mNumSamplesOutput += mConfig->frameLength * mUpsamplingFactor;
+
+ outInfo->mOwnedByUs = false;
+ outQueue.erase(outQueue.begin());
+ outInfo = NULL;
+ notifyFillBufferDone(outHeader);
+ outHeader = NULL;
}
- outHeader->nFilledLen = numOutBytes;
- outHeader->nFlags = 0;
-
- outHeader->nTimeStamp =
- mAnchorTimeUs
- + (mNumSamplesOutput * 1000000ll) / mConfig->samplingRate;
-
- mNumSamplesOutput += mConfig->frameLength * mUpsamplingFactor;
-
if (inHeader->nFilledLen == 0) {
inInfo->mOwnedByUs = false;
inQueue.erase(inQueue.begin());
@@ -404,12 +416,6 @@
inHeader = NULL;
}
- outInfo->mOwnedByUs = false;
- outQueue.erase(outQueue.begin());
- outInfo = NULL;
- notifyFillBufferDone(outHeader);
- outHeader = NULL;
-
if (decoderErr == MP4AUDEC_SUCCESS) {
++mInputBufferCount;
}