diff options
| -rw-r--r-- | Android.mk | 3 | ||||
| -rw-r--r-- | CleanSpec.mk | 1 | ||||
| -rw-r--r-- | media/java/android/media/AudioManager.java | 168 | ||||
| -rw-r--r-- | media/java/android/media/AudioService.java | 297 | ||||
| -rw-r--r-- | media/java/android/media/IAudioService.aidl | 16 | ||||
| -rw-r--r-- | media/java/android/media/IRemoteControlClient.aidl | 51 | ||||
| -rw-r--r-- | media/java/android/media/IRemoteControlClientDispatcher.aidl | 92 | ||||
| -rw-r--r-- | media/java/android/media/IRemoteControlDisplay.aidl | 42 | ||||
| -rw-r--r-- | media/java/android/media/RemoteControlClient.java | 557 |
9 files changed, 853 insertions, 374 deletions
diff --git a/Android.mk b/Android.mk index fdf0933f4638..d4dc08847ae3 100644 --- a/Android.mk +++ b/Android.mk @@ -183,7 +183,8 @@ LOCAL_SRC_FILES += \ 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 25930652e51f..3cec66ff1584 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -105,6 +105,7 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framew $(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/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index f9efd3ca6a69..e3ef71785dba 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -1715,161 +1715,54 @@ public class AudioManager { } } - /** - * 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 class AudioManager { 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 class AudioManager { 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 class AudioManager { 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 class AudioManager { 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 @@ public class AudioManager { "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 46f45a0725b1..acc2b23a76ce 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -34,7 +34,6 @@ import android.content.pm.PackageManager; 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 @@ public class AudioService extends IAudioService.Stub { 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 @@ public class AudioService extends IAudioService.Stub { 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 @@ public class AudioService extends IAudioService.Stub { 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 class AudioService extends IAudioService.Stub { 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 @@ public class AudioService extends IAudioService.Stub { 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 @@ public class AudioService extends IAudioService.Stub { 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 @@ public class AudioService extends IAudioService.Stub { } 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 @@ public class AudioService extends IAudioService.Stub { /** 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 @@ public class AudioService extends IAudioService.Stub { } // 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 @@ public class AudioService extends IAudioService.Stub { } } - /** 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 7f9ced9f055e..7bf981442b27 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -18,7 +18,8 @@ package android.media; 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 @@ interface IAudioService { 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 000000000000..0fbba2026ac0 --- /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 98142cc5dd9b..000000000000 --- 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 000000000000..19ea2028f114 --- /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 c384636ae780..bfe08b93699d 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 @@ public interface RemoteControlClient * 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 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_CHANGED_METADATA = 1 << 0; + 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; + + /** + * 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(); + } + } /** - * 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 + * @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 @@ public interface RemoteControlClient * {@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 @@ public interface RemoteControlClient * {@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 @@ public interface RemoteControlClient * {@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 + */ + private final Object mCacheLock = new Object(); + /** + * Cache for the playback state. + * Access synchronized on mCacheLock */ - Bitmap getAlbumArt(int maxWidth, int maxHeight); + 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; + + } } |