diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 4fa33279a6912..8462889935349 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -18,6 +18,7 @@ package android.media; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.content.ComponentName; import android.content.Context; import android.database.ContentObserver; import android.os.Binder; @@ -29,6 +30,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.provider.Settings; import android.util.Log; +import android.view.KeyEvent; import java.util.Iterator; import java.util.HashMap; @@ -1179,7 +1181,7 @@ public class AudioManager { * Map to convert focus event listener IDs, as used in the AudioService audio focus stack, * to actual listener objects. */ - private HashMap mFocusIdListenerMap = + private HashMap mAudioFocusIdListenerMap = new HashMap(); /** * Lock to prevent concurrent changes to the list of focus listeners for this AudioManager @@ -1188,13 +1190,14 @@ public class AudioManager { private final Object mFocusListenerLock = new Object(); private OnAudioFocusChangeListener findFocusListener(String id) { - return mFocusIdListenerMap.get(id); + return mAudioFocusIdListenerMap.get(id); } /** * Handler for audio focus events coming from the audio service. */ - private FocusEventHandlerDelegate mFocusEventHandlerDelegate = new FocusEventHandlerDelegate(); + private FocusEventHandlerDelegate mAudioFocusEventHandlerDelegate = + new FocusEventHandlerDelegate(); /** * Event id denotes a loss of focus */ @@ -1239,16 +1242,16 @@ public class AudioManager { } } - private IAudioFocusDispatcher mFocusDispatcher = new IAudioFocusDispatcher.Stub() { + private IAudioFocusDispatcher mAudioFocusDispatcher = new IAudioFocusDispatcher.Stub() { public void dispatchAudioFocusChange(int focusChange, String id) { - Message m = mFocusEventHandlerDelegate.getHandler().obtainMessage(focusChange, id); - mFocusEventHandlerDelegate.getHandler().sendMessage(m); + Message m = mAudioFocusEventHandlerDelegate.getHandler().obtainMessage(focusChange, id); + mAudioFocusEventHandlerDelegate.getHandler().sendMessage(m); } }; - private String getIdForFocusListener(OnAudioFocusChangeListener l) { + private String getIdForAudioFocusListener(OnAudioFocusChangeListener l) { if (l == null) { return new String(); } else { @@ -1264,10 +1267,10 @@ public class AudioManager { return; } synchronized(mFocusListenerLock) { - if (mFocusIdListenerMap.containsKey(getIdForFocusListener(l))) { + if (mAudioFocusIdListenerMap.containsKey(getIdForAudioFocusListener(l))) { return; } - mFocusIdListenerMap.put(getIdForFocusListener(l), l); + mAudioFocusIdListenerMap.put(getIdForAudioFocusListener(l), l); } } @@ -1278,13 +1281,13 @@ public class AudioManager { // notify service to remove it from audio focus stack IAudioService service = getService(); try { - service.unregisterFocusClient(getIdForFocusListener(l)); + service.unregisterAudioFocusClient(getIdForAudioFocusListener(l)); } catch (RemoteException e) { Log.e(TAG, "Can't call unregisterFocusClient() from AudioService due to "+e); } // remove locally synchronized(mFocusListenerLock) { - mFocusIdListenerMap.remove(getIdForFocusListener(l)); + mAudioFocusIdListenerMap.remove(getIdForAudioFocusListener(l)); } } @@ -1318,7 +1321,7 @@ public class AudioManager { IAudioService service = getService(); try { status = service.requestAudioFocus(streamType, durationHint, mICallBack, - mFocusDispatcher, getIdForFocusListener(l)); + mAudioFocusDispatcher, getIdForAudioFocusListener(l)); } catch (RemoteException e) { Log.e(TAG, "Can't call requestAudioFocus() from AudioService due to "+e); } @@ -1336,7 +1339,8 @@ public class AudioManager { registerAudioFocusListener(l); IAudioService service = getService(); try { - status = service.abandonAudioFocus(mFocusDispatcher, getIdForFocusListener(l)); + status = service.abandonAudioFocus(mAudioFocusDispatcher, + getIdForAudioFocusListener(l)); } catch (RemoteException e) { Log.e(TAG, "Can't call abandonAudioFocus() from AudioService due to "+e); } @@ -1344,6 +1348,63 @@ public class AudioManager { } + //==================================================================== + // Remote Control + /** + * @hide + * TODO unhide for SDK + * TODO document for SDK + * @param eventReceiver identifier of a {@link android.content.BroadcastReceiver} + * that will receive the media button intent. This broadcast receiver must be declared + * in the application manifest. + */ + public void registerMediaButtonEventReceiver(ComponentName eventReceiver) { + //TODO enforce the rule about the receiver being declared in the manifest + IAudioService service = getService(); + try { + service.registerMediaButtonEventReceiver(eventReceiver); + } catch (RemoteException e) { + Log.e(TAG, "Dead object in registerMediaButtonEventReceiver"+e); + } + } + + /** + * @hide + * TODO unhide for SDK + * TODO document for SDK + * @param eventReceiverClass class of a {@link android.content.BroadcastReceiver} that will + * receive the media button intent. This broadcast receiver must be declared in the + * application manifest. + */ + public void registerMediaButtonEventReceiver(Class eventReceiverClass) { + registerMediaButtonEventReceiver(new ComponentName( + eventReceiverClass.getPackage().getName(), eventReceiverClass.getName())); + } + + /** + * @hide + * TODO unhide for SDK + * TODO document for SDK + */ + public void unregisterMediaButtonEventReceiver(ComponentName eventReceiver) { + IAudioService service = getService(); + try { + service.unregisterMediaButtonEventReceiver(eventReceiver); + } catch (RemoteException e) { + Log.e(TAG, "Dead object in unregisterMediaButtonEventReceiver"+e); + } + } + + /** + * @hide + * TODO unhide for SDK + * TODO document for SDK + */ + public void unregisterMediaButtonEventReceiver(Class eventReceiverClass) { + unregisterMediaButtonEventReceiver(new ComponentName( + eventReceiverClass.getPackage().getName(), eventReceiverClass.getName())); + } + /** * @hide * Reload audio settings. This method is called by Settings backup diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index 618f239653c3e..81e17b7f22255 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -18,6 +18,7 @@ package android.media; import android.app.ActivityManagerNative; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -41,6 +42,7 @@ import android.os.ServiceManager; import android.provider.Settings; import android.provider.Settings.System; import android.util.Log; +import android.view.KeyEvent; import android.view.VolumePanel; import android.os.SystemProperties; @@ -224,6 +226,10 @@ public class AudioService extends IAudioService.Stub { // Broadcast receiver for device connections intent broadcasts private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver(); + // Broadcast receiver for media button broadcasts (separate from mReceiver to + // independently change its priority) + private final BroadcastReceiver mMediaButtonReceiver = new MediaButtonBroadcastReceiver(); + // Devices currently connected private HashMap mConnectedDevices = new HashMap (); @@ -269,6 +275,10 @@ public class AudioService extends IAudioService.Stub { intentFilter.addAction(Intent.ACTION_DOCK_EVENT); context.registerReceiver(mReceiver, intentFilter); + // Register for media button intent broadcasts. + intentFilter = new IntentFilter(Intent.ACTION_MEDIA_BUTTON); + intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); + context.registerReceiver(mMediaButtonReceiver, intentFilter); } private void createAudioSystemThread() { @@ -1667,7 +1677,7 @@ public class AudioService extends IAudioService.Stub { if (signal) { // notify the new top of the stack it gained focus if (!mFocusStack.empty() && (mFocusStack.peek().mFocusDispatcher != null) - && canReassignFocus()) { + && canReassignAudioFocus()) { try { mFocusStack.peek().mFocusDispatcher.dispatchAudioFocusChange( AudioManager.AUDIOFOCUS_GAIN, mFocusStack.peek().mClientId); @@ -1714,7 +1724,7 @@ public class AudioService extends IAudioService.Stub { * Helper function: * Returns true if the system is in a state where the focus can be reevaluated, false otherwise. */ - private boolean canReassignFocus() { + private boolean canReassignAudioFocus() { // focus requests are rejected during a phone call if (getMode() == AudioSystem.MODE_IN_CALL) { Log.i(TAG, " AudioFocus can't be reassigned during a call, exiting"); @@ -1747,7 +1757,7 @@ public class AudioService extends IAudioService.Stub { } - /** @see AudioManager#requestAudioFocus(int, int, IBinder, IAudioFocusDispatcher, String) */ + /** @see AudioManager#requestAudioFocus(IAudioFocusDispatcher, int, int) */ public int requestAudioFocus(int mainStreamType, int durationHint, IBinder cb, IAudioFocusDispatcher fd, String clientId) { Log.i(TAG, " AudioFocus requestAudioFocus() from " + clientId); @@ -1759,7 +1769,7 @@ public class AudioService extends IAudioService.Stub { return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } - if (!canReassignFocus()) { + if (!canReassignAudioFocus()) { return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } @@ -1803,7 +1813,7 @@ public class AudioService extends IAudioService.Stub { return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; } - /** @see AudioManager#abandonAudioFocus(IBinder, IAudioFocusDispatcher, String) */ + /** @see AudioManager#abandonAudioFocus(IAudioFocusDispatcher) */ public int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId) { Log.i(TAG, " AudioFocus abandonAudioFocus() from " + clientId); @@ -1814,15 +1824,141 @@ public class AudioService extends IAudioService.Stub { } - public void unregisterFocusClient(String clientId) { + public void unregisterAudioFocusClient(String clientId) { removeFocusStackEntry(clientId, false); } + //========================================================================================== + // RemoteControl + //========================================================================================== + /** + * Receiver for media button intents. Handles the dispatching of the media button event + * to one of the registered listeners, or if there was none, resumes the intent broadcast + * to the rest of the system. + */ + private class MediaButtonBroadcastReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (!Intent.ACTION_MEDIA_BUTTON.equals(action)) { + return; + } + KeyEvent event = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); + if (event != null) { + // if in a call or ringing, do not break the current phone app behavior + // TODO modify this to let the phone app specifically get the RC focus + // add modify the phone app to take advantage of the new API + if ((getMode() == AudioSystem.MODE_IN_CALL) || + (getMode() == AudioSystem.MODE_RINGTONE)) { + return; + } + synchronized(mRCStack) { + if (!mRCStack.empty()) { + // create a new intent specifically aimed at the current registered listener + Intent targetedIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); + targetedIntent.putExtras(intent.getExtras()); + targetedIntent.setComponent(mRCStack.peek().mReceiverComponent); + // trap the current broadcast + abortBroadcast(); + //Log.v(TAG, " Sending intent" + targetedIntent); + context.sendBroadcast(targetedIntent, null); + } + } + } + } + } + + private static class RemoteControlStackEntry { + public ComponentName mReceiverComponent;// always non null + // TODO implement registration expiration? + //public int mRegistrationTime; + + public RemoteControlStackEntry() { + } + + public RemoteControlStackEntry(ComponentName r) { + mReceiverComponent = r; + } + } + + private Stack mRCStack = new Stack(); + + /** + * Helper function: + * Display in the log the current entries in the remote control focus stack + */ + private void dumpRCStack(PrintWriter pw) { + pw.println("Remote Control stack entries:"); + synchronized(mRCStack) { + Iterator stackIterator = mRCStack.iterator(); + while(stackIterator.hasNext()) { + RemoteControlStackEntry fse = stackIterator.next(); + pw.println(" receiver:" + fse.mReceiverComponent); + } + } + } + + /** + * Helper function: + * Set the new remote control receiver at the top of the RC focus stack + */ + private void pushMediaButtonReceiver(ComponentName newReceiver) { + // already at top of stack? + if (!mRCStack.empty() && mRCStack.peek().mReceiverComponent.equals(newReceiver)) { + return; + } + Iterator stackIterator = mRCStack.iterator(); + while(stackIterator.hasNext()) { + RemoteControlStackEntry rcse = (RemoteControlStackEntry)stackIterator.next(); + if(rcse.mReceiverComponent.equals(newReceiver)) { + mRCStack.remove(rcse); + break; + } + } + mRCStack.push(new RemoteControlStackEntry(newReceiver)); + } + + /** + * Helper function: + * Remove the remote control receiver from the RC focus stack + */ + private void removeMediaButtonReceiver(ComponentName newReceiver) { + Iterator stackIterator = mRCStack.iterator(); + while(stackIterator.hasNext()) { + RemoteControlStackEntry rcse = (RemoteControlStackEntry)stackIterator.next(); + if(rcse.mReceiverComponent.equals(newReceiver)) { + mRCStack.remove(rcse); + break; + } + } + } + + + /** see AudioManager.registerMediaButtonEventReceiver(ComponentName eventReceiver) */ + public void registerMediaButtonEventReceiver(ComponentName eventReceiver) { + Log.i(TAG, " Remote Control registerMediaButtonEventReceiver() for " + eventReceiver); + + synchronized(mRCStack) { + pushMediaButtonReceiver(eventReceiver); + } + } + + /** see AudioManager.unregisterMediaButtonEventReceiver(ComponentName eventReceiver) */ + public void unregisterMediaButtonEventReceiver(ComponentName eventReceiver) { + Log.i(TAG, " Remote Control registerMediaButtonEventReceiver() for " + eventReceiver); + + synchronized(mRCStack) { + removeMediaButtonReceiver(eventReceiver); + } + } + + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - // TODO probably a lot more to do here than just the audio focus stack + // TODO probably a lot more to do here than just the audio focus and remote control stacks dumpFocusStack(pw); + dumpRCStack(pw); } diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index b275488ca5ac4..953892b644839 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -16,6 +16,7 @@ package android.media; +import android.content.ComponentName; import android.media.IAudioFocusDispatcher; /** @@ -76,5 +77,9 @@ interface IAudioService { int abandonAudioFocus(IAudioFocusDispatcher l, String clientId); - void unregisterFocusClient(String clientId); + void unregisterAudioFocusClient(String clientId); + + void registerMediaButtonEventReceiver(in ComponentName eventReceiver); + + void unregisterMediaButtonEventReceiver(in ComponentName eventReceiver); }