Loading Android.mk +1 −0 Original line number Diff line number Diff line Loading @@ -167,6 +167,7 @@ LOCAL_SRC_FILES += \ location/java/android/location/ILocationProvider.aidl \ location/java/android/location/INetInitiatedListener.aidl \ media/java/android/media/IAudioService.aidl \ media/java/android/media/IAudioFocusDispatcher.aidl \ media/java/android/media/IMediaScannerListener.aidl \ media/java/android/media/IMediaScannerService.aidl \ telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl \ Loading media/java/android/media/AudioManager.java +240 −0 Original line number Diff line number Diff line Loading @@ -23,11 +23,16 @@ import android.database.ContentObserver; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.provider.Settings; import android.util.Log; import java.util.Iterator; import java.util.HashMap; /** * AudioManager provides access to volume and ringer mode control. * <p> Loading Loading @@ -1126,6 +1131,241 @@ public class AudioManager { } } /** * TODO unhide for SDK * Used to indicate a loss of audio focus of unknown duration. * @see OnAudioFocusChangeListener#onAudioFocusChanged(int) * {@hide} */ public static final int AUDIOFOCUS_LOSS = -1; /** * TODO unhide for SDK * Used to indicate a transient loss of audio focus. * @see OnAudioFocusChangeListener#onAudioFocusChanged(int) * {@hide} */ public static final int AUDIOFOCUS_LOSS_TRANSIENT = -2; /** * TODO unhide for SDK * Used to indicate a gain of audio focus, or a request of audio focus, of unknown duration. * @see OnAudioFocusChangeListener#onAudioFocusChanged(int) * @see #requestAudioFocus(OnAudioFocusChangeListener, int, int) * {@hide} */ public static final int AUDIOFOCUS_GAIN = 1; /** * TODO unhide for SDK * Used to indicate a temporary gain or request of audio focus, anticipated to last a short * amount of time. Examples of temporary changes are the playback of driving directions, or an * event notification. * @see OnAudioFocusChangeListener#onAudioFocusChanged(int) * @see #requestAudioFocus(OnAudioFocusChangeListener, int, int) * {@hide} */ public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; /** * TODO unhide for SDK * {@hide} * Interface definition for a callback to be invoked when the audio focus of the system is * updated. */ public interface OnAudioFocusChangeListener { /** * Called on the listener to notify it the audio focus for this listener has been changed. * The focusChange value indicates whether the focus was gained, * whether the focus was lost, and whether that loss is transient, or whether the new focus * holder will hold it for an unknown amount of time. * When losing focus, listeners can use the duration hint to decide what * behavior to adopt when losing focus. A music player could for instance elect to duck its * music stream for transient focus losses, and pause otherwise. * @param focusChange one of {@link AudioManager#AUDIOFOCUS_GAIN}, * {@link AudioManager#AUDIOFOCUS_LOSS}, {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT}. */ public void onAudioFocusChanged(int focusChange); } /** * Map to convert focus event listener IDs, as used in the AudioService audio focus stack, * to actual listener objects. */ private HashMap<String, OnAudioFocusChangeListener> mFocusIdListenerMap = new HashMap<String, OnAudioFocusChangeListener>(); /** * Lock to prevent concurrent changes to the list of focus listeners for this AudioManager * instance. */ private final Object mFocusListenerLock = new Object(); private OnAudioFocusChangeListener findFocusListener(String id) { return mFocusIdListenerMap.get(id); } /** * Handler for audio focus events coming from the audio service. */ private FocusEventHandlerDelegate mFocusEventHandlerDelegate = new FocusEventHandlerDelegate(); /** * Event id denotes a loss of focus */ private static final int AUDIOFOCUS_EVENT_LOSS = 0; /** * Event id denotes a gain of focus */ private static final int AUDIOFOCUS_EVENT_GAIN = 1; /** * Helper class to handle the forwarding of audio focus events to the appropriate listener */ private class FocusEventHandlerDelegate { private final Handler mHandler; FocusEventHandlerDelegate() { Looper looper; if ((looper = Looper.myLooper()) == null) { looper = Looper.getMainLooper(); } if (looper != null) { // implement the event handler delegate to receive audio focus events mHandler = new Handler(looper) { @Override public void handleMessage(Message msg) { OnAudioFocusChangeListener listener = null; synchronized(mFocusListenerLock) { listener = findFocusListener((String)msg.obj); } if (listener != null) { listener.onAudioFocusChanged(msg.what); } } }; } else { mHandler = null; } } Handler getHandler() { return mHandler; } } private IAudioFocusDispatcher mFocusDispatcher = new IAudioFocusDispatcher.Stub() { public void dispatchAudioFocusChange(int focusChange, String id) { Message m = mFocusEventHandlerDelegate.getHandler().obtainMessage(focusChange, id); mFocusEventHandlerDelegate.getHandler().sendMessage(m); } }; private String getIdForFocusListener(OnAudioFocusChangeListener l) { if (l == null) { return new String(); } else { return new String(this.toString() + l.toString()); } } /** * TODO unhide for SDK * {@hide} * Register a listener for audio focus updates. */ public void registerAudioFocusListener(OnAudioFocusChangeListener l) { if (l == null) { return; } synchronized(mFocusListenerLock) { if (mFocusIdListenerMap.containsKey(getIdForFocusListener(l))) { return; } mFocusIdListenerMap.put(getIdForFocusListener(l), l); } } /** * TODO unhide for SDK * TODO document for SDK * {@hide} */ public void unregisterAudioFocusListener(OnAudioFocusChangeListener l) { // notify service to remove it from audio focus stack IAudioService service = getService(); try { service.unregisterFocusClient(getIdForFocusListener(l)); } catch (RemoteException e) { Log.e(TAG, "Can't call unregisterFocusClient() from AudioService due to "+e); } // remove locally synchronized(mFocusListenerLock) { mFocusIdListenerMap.remove(getIdForFocusListener(l)); } } /** * TODO unhide for SDK * TODO document for SDK * {@hide} */ public static final int AUDIOFOCUS_REQUEST_FAILED = 0; /** * TODO unhide for SDK * TODO document for SDK * {@hide} */ public static final int AUDIOFOCUS_REQUEST_GRANTED = 1; /** * TODO unhide for SDK * {@hide} * Request audio focus. * Send a request to obtain the audio focus for a specific stream type * @param l the listener to be notified of audio focus changes * @param streamType the main audio stream type affected by the focus request * @param durationHint use {@link #AUDIOFOCUS_GAIN_TRANSIENT} to indicate this focus request * is temporary, and focus will be abandonned shortly. Examples of transient requests are * for the playback of driving directions, or notifications sounds. Use * {@link #AUDIOFOCUS_GAIN} for a focus request of unknown duration such * as the playback of a song or a video. * @return {@link #AUDIOFOCUS_REQUEST_FAILED} or {@link #AUDIOFOCUS_REQUEST_GRANTED} */ public int requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint) { int status = AUDIOFOCUS_REQUEST_FAILED; registerAudioFocusListener(l); //TODO protect request by permission check? IAudioService service = getService(); try { status = service.requestAudioFocus(streamType, durationHint, mICallBack, mFocusDispatcher, getIdForFocusListener(l)); } catch (RemoteException e) { Log.e(TAG, "Can't call requestAudioFocus() from AudioService due to "+e); } return status; } /** * TODO unhide for SDK * TODO document for SDK * {@hide} * Abandon audio focus. * @return {@link #AUDIOFOCUS_REQUEST_FAILED} or {@link #AUDIOFOCUS_REQUEST_GRANTED} */ public int abandonAudioFocus(OnAudioFocusChangeListener l) { int status = AUDIOFOCUS_REQUEST_FAILED; registerAudioFocusListener(l); IAudioService service = getService(); try { status = service.abandonAudioFocus(mFocusDispatcher, getIdForFocusListener(l)); } catch (RemoteException e) { Log.e(TAG, "Can't call abandonAudioFocus() from AudioService due to "+e); } return status; } /** * @hide * Reload audio settings. This method is called by Settings backup Loading media/java/android/media/AudioService.java +224 −1 Original line number Diff line number Diff line Loading @@ -26,7 +26,6 @@ import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.content.pm.PackageManager; import android.database.ContentObserver; import android.media.MediaPlayer.OnCompletionListener; Loading @@ -47,12 +46,15 @@ import android.os.SystemProperties; import com.android.internal.telephony.ITelephony; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.Stack; /** * The implementation of the volume manager service. Loading @@ -76,6 +78,7 @@ public class AudioService extends IAudioService.Stub { private Context mContext; private ContentResolver mContentResolver; /** The UI */ private VolumePanel mVolumePanel; Loading Loading @@ -888,6 +891,7 @@ public class AudioService extends IAudioService.Stub { } } /////////////////////////////////////////////////////////////////////////// // Internal methods /////////////////////////////////////////////////////////////////////////// Loading Loading @@ -1600,4 +1604,223 @@ public class AudioService extends IAudioService.Stub { } } } //========================================================================================== // AudioFocus //========================================================================================== private static class FocusStackEntry { public int mStreamType = -1;// no stream type public boolean mIsTransportControlReceiver = false; public IAudioFocusDispatcher mFocusDispatcher = null; public IBinder mSourceRef = null; public String mClientId; public int mDurationHint; public FocusStackEntry() { } public FocusStackEntry(int streamType, int duration, boolean isTransportControlReceiver, IAudioFocusDispatcher afl, IBinder source, String id) { mStreamType = streamType; mIsTransportControlReceiver = isTransportControlReceiver; mFocusDispatcher = afl; mSourceRef = source; mClientId = id; mDurationHint = duration; } } private Stack<FocusStackEntry> mFocusStack = new Stack<FocusStackEntry>(); /** * Helper function: * Display in the log the current entries in the audio focus stack */ private void dumpFocusStack(PrintWriter pw) { pw.println("Audio Focus stack entries:"); synchronized(mFocusStack) { Iterator<FocusStackEntry> stackIterator = mFocusStack.iterator(); while(stackIterator.hasNext()) { FocusStackEntry fse = stackIterator.next(); pw.println(" source:" + fse.mSourceRef + " -- client: " + fse.mClientId + " -- duration: " +fse.mDurationHint); } } } /** * Helper function: * Remove a focus listener from the focus stack. * @param focusListenerToRemove the focus listener * @param signal if true and the listener was at the top of the focus stack, i.e. it was holding * focus, notify the next item in the stack it gained focus. */ private void removeFocusStackEntry(String clientToRemove, boolean signal) { // is the current top of the focus stack abandoning focus? (because of death or request) if (!mFocusStack.empty() && mFocusStack.peek().mClientId.equals(clientToRemove)) { //Log.i(TAG, " removeFocusStackEntry() removing top of stack"); mFocusStack.pop(); if (signal) { // notify the new top of the stack it gained focus if (!mFocusStack.empty() && (mFocusStack.peek().mFocusDispatcher != null) && canReassignFocus()) { try { mFocusStack.peek().mFocusDispatcher.dispatchAudioFocusChange( AudioManager.AUDIOFOCUS_GAIN, mFocusStack.peek().mClientId); } catch (RemoteException e) { Log.e(TAG, " Failure to signal gain of focus due to "+ e); e.printStackTrace(); } } } } else { // focus is abandoned by a client that's not at the top of the stack, // no need to update focus. Iterator<FocusStackEntry> stackIterator = mFocusStack.iterator(); while(stackIterator.hasNext()) { FocusStackEntry fse = (FocusStackEntry)stackIterator.next(); if(fse.mClientId.equals(clientToRemove)) { Log.i(TAG, " AudioFocus abandonAudioFocus(): removing entry for " + fse.mClientId); mFocusStack.remove(fse); } } } } /** * Helper function: * Remove focus listeners from the focus stack for a particular client. */ private void removeFocusStackEntryForClient(IBinder cb) { // focus is abandoned by a client that's not at the top of the stack, // no need to update focus. Iterator<FocusStackEntry> stackIterator = mFocusStack.iterator(); while(stackIterator.hasNext()) { FocusStackEntry fse = (FocusStackEntry)stackIterator.next(); if(fse.mSourceRef.equals(cb)) { Log.i(TAG, " AudioFocus abandonAudioFocus(): removing entry for " + fse.mClientId); mFocusStack.remove(fse); } } } /** * Helper function: * Returns true if the system is in a state where the focus can be reevaluated, false otherwise. */ private boolean canReassignFocus() { // 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"); return false; } return true; } /** * Inner class to monitor audio focus client deaths, and remove them from the audio focus * stack if necessary. */ private class AudioFocusDeathHandler implements IBinder.DeathRecipient { private IBinder mCb; // To be notified of client's death AudioFocusDeathHandler(IBinder cb) { mCb = cb; } public void binderDied() { synchronized(mFocusStack) { Log.w(TAG, " AudioFocus audio focus client died"); removeFocusStackEntryForClient(mCb); } } public IBinder getBinder() { return mCb; } } /** @see AudioManager#requestAudioFocus(int, int, IBinder, IAudioFocusDispatcher, String) */ public int requestAudioFocus(int mainStreamType, int durationHint, IBinder cb, IAudioFocusDispatcher fd, String clientId) { Log.i(TAG, " AudioFocus requestAudioFocus() from " + clientId); // the main stream type for the audio focus request is currently not used. It may // potentially be used to handle multiple stream type-dependent audio focuses. if ((cb == null) || !cb.pingBinder()) { Log.i(TAG, " AudioFocus DOA client for requestAudioFocus(), exiting"); return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } if (!canReassignFocus()) { return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } synchronized(mFocusStack) { if (!mFocusStack.empty() && mFocusStack.peek().mClientId.equals(clientId)) { mFocusStack.peek().mDurationHint = durationHint; // if focus is already owned by this client, don't do anything return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; } // notify current top of stack it is losing focus if (!mFocusStack.empty() && (mFocusStack.peek().mFocusDispatcher != null)) { try { mFocusStack.peek().mFocusDispatcher.dispatchAudioFocusChange( (durationHint == AudioManager.AUDIOFOCUS_GAIN) ? AudioManager.AUDIOFOCUS_LOSS : AudioManager.AUDIOFOCUS_LOSS_TRANSIENT, mFocusStack.peek().mClientId); } catch (RemoteException e) { Log.e(TAG, " Failure to signal loss of focus due to "+ e); e.printStackTrace(); } } // push focus requester at the top of the audio focus stack mFocusStack.push(new FocusStackEntry(mainStreamType, durationHint, false, fd, cb, clientId)); }//synchronized(mFocusStack) // handle the potential premature death of the new holder of the focus // (premature death == death before abandoning focus) // Register for client death notification AudioFocusDeathHandler afdh = new AudioFocusDeathHandler(cb); try { cb.linkToDeath(afdh, 0); } catch (RemoteException e) { // client has already died! Log.w(TAG, " AudioFocus requestAudioFocus() could not link to "+cb+" binder death"); } return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; } /** @see AudioManager#abandonAudioFocus(IBinder, IAudioFocusDispatcher, String) */ public int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId) { Log.i(TAG, " AudioFocus abandonAudioFocus() from " + clientId); // this will take care of notifying the new focus owner if needed removeFocusStackEntry(clientId, true); return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; } public void unregisterFocusClient(String clientId) { removeFocusStackEntry(clientId, false); } @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { // TODO probably a lot more to do here than just the audio focus stack dumpFocusStack(pw); } } media/java/android/media/IAudioFocusDispatcher.aidl 0 → 100755 +28 −0 Original line number Diff line number Diff line /* * Copyright (C) 2010 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; /** * AIDL for the AudioService to signal audio focus listeners of focus updates. * * {@hide} */ oneway interface IAudioFocusDispatcher { void dispatchAudioFocusChange(int focusChange, String clientId); } media/java/android/media/IAudioService.aidl +9 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package android.media; import android.media.IAudioFocusDispatcher; /** * {@hide} */ Loading Loading @@ -68,4 +70,11 @@ interface IAudioService { void setBluetoothScoOn(boolean on); boolean isBluetoothScoOn(); int requestAudioFocus(int mainStreamType, int durationHint, IBinder cb, IAudioFocusDispatcher l, String clientId); int abandonAudioFocus(IAudioFocusDispatcher l, String clientId); void unregisterFocusClient(String clientId); } Loading
Android.mk +1 −0 Original line number Diff line number Diff line Loading @@ -167,6 +167,7 @@ LOCAL_SRC_FILES += \ location/java/android/location/ILocationProvider.aidl \ location/java/android/location/INetInitiatedListener.aidl \ media/java/android/media/IAudioService.aidl \ media/java/android/media/IAudioFocusDispatcher.aidl \ media/java/android/media/IMediaScannerListener.aidl \ media/java/android/media/IMediaScannerService.aidl \ telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl \ Loading
media/java/android/media/AudioManager.java +240 −0 Original line number Diff line number Diff line Loading @@ -23,11 +23,16 @@ import android.database.ContentObserver; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.provider.Settings; import android.util.Log; import java.util.Iterator; import java.util.HashMap; /** * AudioManager provides access to volume and ringer mode control. * <p> Loading Loading @@ -1126,6 +1131,241 @@ public class AudioManager { } } /** * TODO unhide for SDK * Used to indicate a loss of audio focus of unknown duration. * @see OnAudioFocusChangeListener#onAudioFocusChanged(int) * {@hide} */ public static final int AUDIOFOCUS_LOSS = -1; /** * TODO unhide for SDK * Used to indicate a transient loss of audio focus. * @see OnAudioFocusChangeListener#onAudioFocusChanged(int) * {@hide} */ public static final int AUDIOFOCUS_LOSS_TRANSIENT = -2; /** * TODO unhide for SDK * Used to indicate a gain of audio focus, or a request of audio focus, of unknown duration. * @see OnAudioFocusChangeListener#onAudioFocusChanged(int) * @see #requestAudioFocus(OnAudioFocusChangeListener, int, int) * {@hide} */ public static final int AUDIOFOCUS_GAIN = 1; /** * TODO unhide for SDK * Used to indicate a temporary gain or request of audio focus, anticipated to last a short * amount of time. Examples of temporary changes are the playback of driving directions, or an * event notification. * @see OnAudioFocusChangeListener#onAudioFocusChanged(int) * @see #requestAudioFocus(OnAudioFocusChangeListener, int, int) * {@hide} */ public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; /** * TODO unhide for SDK * {@hide} * Interface definition for a callback to be invoked when the audio focus of the system is * updated. */ public interface OnAudioFocusChangeListener { /** * Called on the listener to notify it the audio focus for this listener has been changed. * The focusChange value indicates whether the focus was gained, * whether the focus was lost, and whether that loss is transient, or whether the new focus * holder will hold it for an unknown amount of time. * When losing focus, listeners can use the duration hint to decide what * behavior to adopt when losing focus. A music player could for instance elect to duck its * music stream for transient focus losses, and pause otherwise. * @param focusChange one of {@link AudioManager#AUDIOFOCUS_GAIN}, * {@link AudioManager#AUDIOFOCUS_LOSS}, {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT}. */ public void onAudioFocusChanged(int focusChange); } /** * Map to convert focus event listener IDs, as used in the AudioService audio focus stack, * to actual listener objects. */ private HashMap<String, OnAudioFocusChangeListener> mFocusIdListenerMap = new HashMap<String, OnAudioFocusChangeListener>(); /** * Lock to prevent concurrent changes to the list of focus listeners for this AudioManager * instance. */ private final Object mFocusListenerLock = new Object(); private OnAudioFocusChangeListener findFocusListener(String id) { return mFocusIdListenerMap.get(id); } /** * Handler for audio focus events coming from the audio service. */ private FocusEventHandlerDelegate mFocusEventHandlerDelegate = new FocusEventHandlerDelegate(); /** * Event id denotes a loss of focus */ private static final int AUDIOFOCUS_EVENT_LOSS = 0; /** * Event id denotes a gain of focus */ private static final int AUDIOFOCUS_EVENT_GAIN = 1; /** * Helper class to handle the forwarding of audio focus events to the appropriate listener */ private class FocusEventHandlerDelegate { private final Handler mHandler; FocusEventHandlerDelegate() { Looper looper; if ((looper = Looper.myLooper()) == null) { looper = Looper.getMainLooper(); } if (looper != null) { // implement the event handler delegate to receive audio focus events mHandler = new Handler(looper) { @Override public void handleMessage(Message msg) { OnAudioFocusChangeListener listener = null; synchronized(mFocusListenerLock) { listener = findFocusListener((String)msg.obj); } if (listener != null) { listener.onAudioFocusChanged(msg.what); } } }; } else { mHandler = null; } } Handler getHandler() { return mHandler; } } private IAudioFocusDispatcher mFocusDispatcher = new IAudioFocusDispatcher.Stub() { public void dispatchAudioFocusChange(int focusChange, String id) { Message m = mFocusEventHandlerDelegate.getHandler().obtainMessage(focusChange, id); mFocusEventHandlerDelegate.getHandler().sendMessage(m); } }; private String getIdForFocusListener(OnAudioFocusChangeListener l) { if (l == null) { return new String(); } else { return new String(this.toString() + l.toString()); } } /** * TODO unhide for SDK * {@hide} * Register a listener for audio focus updates. */ public void registerAudioFocusListener(OnAudioFocusChangeListener l) { if (l == null) { return; } synchronized(mFocusListenerLock) { if (mFocusIdListenerMap.containsKey(getIdForFocusListener(l))) { return; } mFocusIdListenerMap.put(getIdForFocusListener(l), l); } } /** * TODO unhide for SDK * TODO document for SDK * {@hide} */ public void unregisterAudioFocusListener(OnAudioFocusChangeListener l) { // notify service to remove it from audio focus stack IAudioService service = getService(); try { service.unregisterFocusClient(getIdForFocusListener(l)); } catch (RemoteException e) { Log.e(TAG, "Can't call unregisterFocusClient() from AudioService due to "+e); } // remove locally synchronized(mFocusListenerLock) { mFocusIdListenerMap.remove(getIdForFocusListener(l)); } } /** * TODO unhide for SDK * TODO document for SDK * {@hide} */ public static final int AUDIOFOCUS_REQUEST_FAILED = 0; /** * TODO unhide for SDK * TODO document for SDK * {@hide} */ public static final int AUDIOFOCUS_REQUEST_GRANTED = 1; /** * TODO unhide for SDK * {@hide} * Request audio focus. * Send a request to obtain the audio focus for a specific stream type * @param l the listener to be notified of audio focus changes * @param streamType the main audio stream type affected by the focus request * @param durationHint use {@link #AUDIOFOCUS_GAIN_TRANSIENT} to indicate this focus request * is temporary, and focus will be abandonned shortly. Examples of transient requests are * for the playback of driving directions, or notifications sounds. Use * {@link #AUDIOFOCUS_GAIN} for a focus request of unknown duration such * as the playback of a song or a video. * @return {@link #AUDIOFOCUS_REQUEST_FAILED} or {@link #AUDIOFOCUS_REQUEST_GRANTED} */ public int requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint) { int status = AUDIOFOCUS_REQUEST_FAILED; registerAudioFocusListener(l); //TODO protect request by permission check? IAudioService service = getService(); try { status = service.requestAudioFocus(streamType, durationHint, mICallBack, mFocusDispatcher, getIdForFocusListener(l)); } catch (RemoteException e) { Log.e(TAG, "Can't call requestAudioFocus() from AudioService due to "+e); } return status; } /** * TODO unhide for SDK * TODO document for SDK * {@hide} * Abandon audio focus. * @return {@link #AUDIOFOCUS_REQUEST_FAILED} or {@link #AUDIOFOCUS_REQUEST_GRANTED} */ public int abandonAudioFocus(OnAudioFocusChangeListener l) { int status = AUDIOFOCUS_REQUEST_FAILED; registerAudioFocusListener(l); IAudioService service = getService(); try { status = service.abandonAudioFocus(mFocusDispatcher, getIdForFocusListener(l)); } catch (RemoteException e) { Log.e(TAG, "Can't call abandonAudioFocus() from AudioService due to "+e); } return status; } /** * @hide * Reload audio settings. This method is called by Settings backup Loading
media/java/android/media/AudioService.java +224 −1 Original line number Diff line number Diff line Loading @@ -26,7 +26,6 @@ import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.content.pm.PackageManager; import android.database.ContentObserver; import android.media.MediaPlayer.OnCompletionListener; Loading @@ -47,12 +46,15 @@ import android.os.SystemProperties; import com.android.internal.telephony.ITelephony; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.Stack; /** * The implementation of the volume manager service. Loading @@ -76,6 +78,7 @@ public class AudioService extends IAudioService.Stub { private Context mContext; private ContentResolver mContentResolver; /** The UI */ private VolumePanel mVolumePanel; Loading Loading @@ -888,6 +891,7 @@ public class AudioService extends IAudioService.Stub { } } /////////////////////////////////////////////////////////////////////////// // Internal methods /////////////////////////////////////////////////////////////////////////// Loading Loading @@ -1600,4 +1604,223 @@ public class AudioService extends IAudioService.Stub { } } } //========================================================================================== // AudioFocus //========================================================================================== private static class FocusStackEntry { public int mStreamType = -1;// no stream type public boolean mIsTransportControlReceiver = false; public IAudioFocusDispatcher mFocusDispatcher = null; public IBinder mSourceRef = null; public String mClientId; public int mDurationHint; public FocusStackEntry() { } public FocusStackEntry(int streamType, int duration, boolean isTransportControlReceiver, IAudioFocusDispatcher afl, IBinder source, String id) { mStreamType = streamType; mIsTransportControlReceiver = isTransportControlReceiver; mFocusDispatcher = afl; mSourceRef = source; mClientId = id; mDurationHint = duration; } } private Stack<FocusStackEntry> mFocusStack = new Stack<FocusStackEntry>(); /** * Helper function: * Display in the log the current entries in the audio focus stack */ private void dumpFocusStack(PrintWriter pw) { pw.println("Audio Focus stack entries:"); synchronized(mFocusStack) { Iterator<FocusStackEntry> stackIterator = mFocusStack.iterator(); while(stackIterator.hasNext()) { FocusStackEntry fse = stackIterator.next(); pw.println(" source:" + fse.mSourceRef + " -- client: " + fse.mClientId + " -- duration: " +fse.mDurationHint); } } } /** * Helper function: * Remove a focus listener from the focus stack. * @param focusListenerToRemove the focus listener * @param signal if true and the listener was at the top of the focus stack, i.e. it was holding * focus, notify the next item in the stack it gained focus. */ private void removeFocusStackEntry(String clientToRemove, boolean signal) { // is the current top of the focus stack abandoning focus? (because of death or request) if (!mFocusStack.empty() && mFocusStack.peek().mClientId.equals(clientToRemove)) { //Log.i(TAG, " removeFocusStackEntry() removing top of stack"); mFocusStack.pop(); if (signal) { // notify the new top of the stack it gained focus if (!mFocusStack.empty() && (mFocusStack.peek().mFocusDispatcher != null) && canReassignFocus()) { try { mFocusStack.peek().mFocusDispatcher.dispatchAudioFocusChange( AudioManager.AUDIOFOCUS_GAIN, mFocusStack.peek().mClientId); } catch (RemoteException e) { Log.e(TAG, " Failure to signal gain of focus due to "+ e); e.printStackTrace(); } } } } else { // focus is abandoned by a client that's not at the top of the stack, // no need to update focus. Iterator<FocusStackEntry> stackIterator = mFocusStack.iterator(); while(stackIterator.hasNext()) { FocusStackEntry fse = (FocusStackEntry)stackIterator.next(); if(fse.mClientId.equals(clientToRemove)) { Log.i(TAG, " AudioFocus abandonAudioFocus(): removing entry for " + fse.mClientId); mFocusStack.remove(fse); } } } } /** * Helper function: * Remove focus listeners from the focus stack for a particular client. */ private void removeFocusStackEntryForClient(IBinder cb) { // focus is abandoned by a client that's not at the top of the stack, // no need to update focus. Iterator<FocusStackEntry> stackIterator = mFocusStack.iterator(); while(stackIterator.hasNext()) { FocusStackEntry fse = (FocusStackEntry)stackIterator.next(); if(fse.mSourceRef.equals(cb)) { Log.i(TAG, " AudioFocus abandonAudioFocus(): removing entry for " + fse.mClientId); mFocusStack.remove(fse); } } } /** * Helper function: * Returns true if the system is in a state where the focus can be reevaluated, false otherwise. */ private boolean canReassignFocus() { // 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"); return false; } return true; } /** * Inner class to monitor audio focus client deaths, and remove them from the audio focus * stack if necessary. */ private class AudioFocusDeathHandler implements IBinder.DeathRecipient { private IBinder mCb; // To be notified of client's death AudioFocusDeathHandler(IBinder cb) { mCb = cb; } public void binderDied() { synchronized(mFocusStack) { Log.w(TAG, " AudioFocus audio focus client died"); removeFocusStackEntryForClient(mCb); } } public IBinder getBinder() { return mCb; } } /** @see AudioManager#requestAudioFocus(int, int, IBinder, IAudioFocusDispatcher, String) */ public int requestAudioFocus(int mainStreamType, int durationHint, IBinder cb, IAudioFocusDispatcher fd, String clientId) { Log.i(TAG, " AudioFocus requestAudioFocus() from " + clientId); // the main stream type for the audio focus request is currently not used. It may // potentially be used to handle multiple stream type-dependent audio focuses. if ((cb == null) || !cb.pingBinder()) { Log.i(TAG, " AudioFocus DOA client for requestAudioFocus(), exiting"); return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } if (!canReassignFocus()) { return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } synchronized(mFocusStack) { if (!mFocusStack.empty() && mFocusStack.peek().mClientId.equals(clientId)) { mFocusStack.peek().mDurationHint = durationHint; // if focus is already owned by this client, don't do anything return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; } // notify current top of stack it is losing focus if (!mFocusStack.empty() && (mFocusStack.peek().mFocusDispatcher != null)) { try { mFocusStack.peek().mFocusDispatcher.dispatchAudioFocusChange( (durationHint == AudioManager.AUDIOFOCUS_GAIN) ? AudioManager.AUDIOFOCUS_LOSS : AudioManager.AUDIOFOCUS_LOSS_TRANSIENT, mFocusStack.peek().mClientId); } catch (RemoteException e) { Log.e(TAG, " Failure to signal loss of focus due to "+ e); e.printStackTrace(); } } // push focus requester at the top of the audio focus stack mFocusStack.push(new FocusStackEntry(mainStreamType, durationHint, false, fd, cb, clientId)); }//synchronized(mFocusStack) // handle the potential premature death of the new holder of the focus // (premature death == death before abandoning focus) // Register for client death notification AudioFocusDeathHandler afdh = new AudioFocusDeathHandler(cb); try { cb.linkToDeath(afdh, 0); } catch (RemoteException e) { // client has already died! Log.w(TAG, " AudioFocus requestAudioFocus() could not link to "+cb+" binder death"); } return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; } /** @see AudioManager#abandonAudioFocus(IBinder, IAudioFocusDispatcher, String) */ public int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId) { Log.i(TAG, " AudioFocus abandonAudioFocus() from " + clientId); // this will take care of notifying the new focus owner if needed removeFocusStackEntry(clientId, true); return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; } public void unregisterFocusClient(String clientId) { removeFocusStackEntry(clientId, false); } @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { // TODO probably a lot more to do here than just the audio focus stack dumpFocusStack(pw); } }
media/java/android/media/IAudioFocusDispatcher.aidl 0 → 100755 +28 −0 Original line number Diff line number Diff line /* * Copyright (C) 2010 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; /** * AIDL for the AudioService to signal audio focus listeners of focus updates. * * {@hide} */ oneway interface IAudioFocusDispatcher { void dispatchAudioFocusChange(int focusChange, String clientId); }
media/java/android/media/IAudioService.aidl +9 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package android.media; import android.media.IAudioFocusDispatcher; /** * {@hide} */ Loading Loading @@ -68,4 +70,11 @@ interface IAudioService { void setBluetoothScoOn(boolean on); boolean isBluetoothScoOn(); int requestAudioFocus(int mainStreamType, int durationHint, IBinder cb, IAudioFocusDispatcher l, String clientId); int abandonAudioFocus(IAudioFocusDispatcher l, String clientId); void unregisterFocusClient(String clientId); }