From e2d8aae2ccc51340cf4846ce28f635b825711368 Mon Sep 17 00:00:00 2001 From: Jean-Michel Trivi Date: Tue, 30 Jan 2018 15:09:47 -0800 Subject: [PATCH] Synchronous audio focus behavior with external focus policy This patch preserves the synchronous nature of audio focus requests, even when an external focus policy is installed. When focus is requested, the request is blocked on the client-side, while AudioService informs the external policy of the request, and until the ext policy responds with the focus request result for this client, or it times out. The new AudioPolicy API is the call for the external policy to send the focus request result. Bug: 63906162 Test: gts-tradefed run gts -m GtsGmscoreHostTestCases -t 'com.google.android.gts.audio.AudioHostTest#testFocusPolicy' Change-Id: I4671517f7f00eaaed8748bd4013b7d20be3085fb --- api/system-current.txt | 1 + media/java/android/media/AudioFocusInfo.java | 21 +- media/java/android/media/AudioManager.java | 182 ++++++++++++++++-- .../android/media/IAudioFocusDispatcher.aidl | 2 + media/java/android/media/IAudioService.aidl | 3 + .../media/audiopolicy/AudioPolicy.java | 8 +- .../android/server/audio/AudioService.java | 23 +++ .../android/server/audio/FocusRequester.java | 35 +++- .../server/audio/MediaFocusControl.java | 47 +++-- 9 files changed, 284 insertions(+), 38 deletions(-) diff --git a/api/system-current.txt b/api/system-current.txt index ad71e7cbb2783..c343ee8c72451 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -2513,6 +2513,7 @@ package android.media { method public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes, int, int) throws java.lang.IllegalArgumentException; method public deprecated int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes, int, int, android.media.audiopolicy.AudioPolicy) throws java.lang.IllegalArgumentException; method public int requestAudioFocus(android.media.AudioFocusRequest, android.media.audiopolicy.AudioPolicy); + method public void setFocusRequestResult(android.media.AudioFocusInfo, int, android.media.audiopolicy.AudioPolicy); method public void unregisterAudioPolicyAsync(android.media.audiopolicy.AudioPolicy); field public static final int AUDIOFOCUS_FLAG_DELAY_OK = 1; // 0x1 field public static final int AUDIOFOCUS_FLAG_LOCK = 4; // 0x4 diff --git a/media/java/android/media/AudioFocusInfo.java b/media/java/android/media/AudioFocusInfo.java index 5d0c8e234d401..5467a69ea0bb9 100644 --- a/media/java/android/media/AudioFocusInfo.java +++ b/media/java/android/media/AudioFocusInfo.java @@ -38,6 +38,10 @@ public final class AudioFocusInfo implements Parcelable { private int mLossReceived; private int mFlags; + // generation count for the validity of a request/response async exchange between + // external focus policy and MediaFocusControl + private long mGenCount = -1; + /** * Class constructor @@ -61,6 +65,16 @@ public final class AudioFocusInfo implements Parcelable { mSdkTarget = sdk; } + /** @hide */ + public void setGen(long g) { + mGenCount = g; + } + + /** @hide */ + public long getGen() { + return mGenCount; + } + /** * The audio attributes for the audio focus request. @@ -128,6 +142,7 @@ public final class AudioFocusInfo implements Parcelable { dest.writeInt(mLossReceived); dest.writeInt(mFlags); dest.writeInt(mSdkTarget); + dest.writeLong(mGenCount); } @Override @@ -168,6 +183,8 @@ public final class AudioFocusInfo implements Parcelable { if (mSdkTarget != other.mSdkTarget) { return false; } + // mGenCount is not used to verify equality between two focus holds as multiple requests + // (hence of different generations) could correspond to the same hold return true; } @@ -175,7 +192,7 @@ public final class AudioFocusInfo implements Parcelable { = new Parcelable.Creator() { public AudioFocusInfo createFromParcel(Parcel in) { - return new AudioFocusInfo( + final AudioFocusInfo afi = new AudioFocusInfo( AudioAttributes.CREATOR.createFromParcel(in), //AudioAttributes aa in.readInt(), // int clientUid in.readString(), //String clientId @@ -185,6 +202,8 @@ public final class AudioFocusInfo implements Parcelable { in.readInt(), //int flags in.readInt() //int sdkTarget ); + afi.setGen(in.readLong()); + return afi; } public AudioFocusInfo[] newArray(int size) { diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index bf51d97f6b2f8..0be54ec2b2d9b 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -32,6 +32,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.media.audiopolicy.AudioPolicy; +import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener; import android.media.session.MediaController; import android.media.session.MediaSession; import android.media.session.MediaSessionLegacyHelper; @@ -54,10 +55,13 @@ import android.util.Log; import android.util.Slog; import android.view.KeyEvent; +import com.android.internal.annotations.GuardedBy; + import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.concurrent.ConcurrentHashMap; @@ -2338,6 +2342,20 @@ public class AudioManager { } } } + + @Override + public void dispatchFocusResultFromExtPolicy(int requestResult, String clientId) { + synchronized (mFocusRequestsLock) { + // TODO use generation counter as the key instead + final BlockingFocusResultReceiver focusReceiver = + mFocusRequestsAwaitingResult.remove(clientId); + if (focusReceiver != null) { + focusReceiver.notifyResult(requestResult); + } else { + Log.e(TAG, "dispatchFocusResultFromExtPolicy found no result receiver"); + } + } + } }; private String getIdForAudioFocusListener(OnAudioFocusChangeListener l) { @@ -2390,6 +2408,40 @@ public class AudioManager { */ public static final int AUDIOFOCUS_REQUEST_DELAYED = 2; + /** @hide */ + @IntDef(flag = false, prefix = "AUDIOFOCUS_REQUEST", value = { + AUDIOFOCUS_REQUEST_FAILED, + AUDIOFOCUS_REQUEST_GRANTED, + AUDIOFOCUS_REQUEST_DELAYED } + ) + @Retention(RetentionPolicy.SOURCE) + public @interface FocusRequestResult {} + + /** + * @hide + * code returned when a synchronous focus request on the client-side is to be blocked + * until the external audio focus policy decides on the response for the client + */ + public static final int AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY = 100; + + /** + * Timeout duration in ms when waiting on an external focus policy for the result for a + * focus request + */ + private static final int EXT_FOCUS_POLICY_TIMEOUT_MS = 200; + + private static final String FOCUS_CLIENT_ID_STRING = "android_audio_focus_client_id"; + + private final Object mFocusRequestsLock = new Object(); + /** + * Map of all receivers of focus request results, one per unresolved focus request. + * Receivers are added before sending the request to the external focus policy, + * and are removed either after receiving the result, or after the timeout. + * This variable is lazily initialized. + */ + @GuardedBy("mFocusRequestsLock") + private HashMap mFocusRequestsAwaitingResult; + /** * Request audio focus. @@ -2656,18 +2708,100 @@ public class AudioManager { // some tests don't have a Context sdk = Build.VERSION.SDK_INT; } - try { - status = service.requestAudioFocus(afr.getAudioAttributes(), - afr.getFocusGain(), mICallBack, - mAudioFocusDispatcher, - getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener()), - getContext().getOpPackageName() /* package name */, afr.getFlags(), - ap != null ? ap.cb() : null, - sdk); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + + final String clientId = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener()); + final BlockingFocusResultReceiver focusReceiver; + synchronized (mFocusRequestsLock) { + try { + // TODO status contains result and generation counter for ext policy + status = service.requestAudioFocus(afr.getAudioAttributes(), + afr.getFocusGain(), mICallBack, + mAudioFocusDispatcher, + clientId, + getContext().getOpPackageName() /* package name */, afr.getFlags(), + ap != null ? ap.cb() : null, + sdk); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) { + // default path with no external focus policy + return status; + } + if (mFocusRequestsAwaitingResult == null) { + mFocusRequestsAwaitingResult = + new HashMap(1); + } + focusReceiver = new BlockingFocusResultReceiver(clientId); + mFocusRequestsAwaitingResult.put(clientId, focusReceiver); + } + focusReceiver.waitForResult(EXT_FOCUS_POLICY_TIMEOUT_MS); + if (DEBUG && !focusReceiver.receivedResult()) { + Log.e(TAG, "requestAudio response from ext policy timed out, denying request"); + } + synchronized (mFocusRequestsLock) { + mFocusRequestsAwaitingResult.remove(clientId); + } + return focusReceiver.requestResult(); + } + + // helper class that abstracts out the handling of spurious wakeups in Object.wait() + private static final class SafeWaitObject { + private boolean mQuit = false; + + public void safeNotify() { + synchronized (this) { + mQuit = true; + this.notify(); + } + } + + public void safeWait(long millis) throws InterruptedException { + final long timeOutTime = java.lang.System.currentTimeMillis() + millis; + synchronized (this) { + while (!mQuit) { + final long timeToWait = timeOutTime - java.lang.System.currentTimeMillis(); + if (timeToWait < 0) { break; } + this.wait(timeToWait); + } + } + } + } + + private static final class BlockingFocusResultReceiver { + private final SafeWaitObject mLock = new SafeWaitObject(); + @GuardedBy("mLock") + private boolean mResultReceived = false; + // request denied by default (e.g. timeout) + private int mFocusRequestResult = AudioManager.AUDIOFOCUS_REQUEST_FAILED; + private final String mFocusClientId; + + BlockingFocusResultReceiver(String clientId) { + mFocusClientId = clientId; + } + + boolean receivedResult() { return mResultReceived; } + int requestResult() { return mFocusRequestResult; } + + void notifyResult(int requestResult) { + synchronized (mLock) { + mResultReceived = true; + mFocusRequestResult = requestResult; + mLock.safeNotify(); + } + } + + public void waitForResult(long timeOutMs) { + synchronized (mLock) { + if (mResultReceived) { + // the result was received before waiting + return; + } + try { + mLock.safeWait(timeOutMs); + } catch (InterruptedException e) { } + } } - return status; } /** @@ -2712,6 +2846,32 @@ public class AudioManager { } } + /** + * @hide + * Set the result to the audio focus request received through + * {@link AudioPolicyFocusListener#onAudioFocusRequest(AudioFocusInfo, int)}. + * @param afi the information about the focus requester + * @param requestResult the result to the focus request to be passed to the requester + * @param ap a valid registered {@link AudioPolicy} configured as a focus policy. + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public void setFocusRequestResult(@NonNull AudioFocusInfo afi, + @FocusRequestResult int requestResult, @NonNull AudioPolicy ap) { + if (afi == null) { + throw new IllegalArgumentException("Illegal null AudioFocusInfo"); + } + if (ap == null) { + throw new IllegalArgumentException("Illegal null AudioPolicy"); + } + final IAudioService service = getService(); + try { + service.setFocusRequestResultFromExtPolicy(afi, requestResult, ap.cb()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * @hide * Notifies an application with a focus listener of gain or loss of audio focus. diff --git a/media/java/android/media/IAudioFocusDispatcher.aidl b/media/java/android/media/IAudioFocusDispatcher.aidl index 09575f733e328..3b33c5b7a46a0 100644 --- a/media/java/android/media/IAudioFocusDispatcher.aidl +++ b/media/java/android/media/IAudioFocusDispatcher.aidl @@ -25,4 +25,6 @@ oneway interface IAudioFocusDispatcher { void dispatchAudioFocusChange(int focusChange, String clientId); + void dispatchFocusResultFromExtPolicy(int requestResult, String clientId); + } diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 88d0a60880261..cd4143cf31201 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -207,5 +207,8 @@ interface IAudioService { int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(in BluetoothDevice device, int state, int profile, boolean suppressNoisyIntent); + oneway void setFocusRequestResultFromExtPolicy(in AudioFocusInfo afi, int requestResult, + in IAudioPolicyCallback pcb); + // WARNING: read warning at top of file, it is recommended to add new methods at the end } diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java index 4de731a9a8a3f..219063564132f 100644 --- a/media/java/android/media/audiopolicy/AudioPolicy.java +++ b/media/java/android/media/audiopolicy/AudioPolicy.java @@ -463,9 +463,9 @@ public class AudioPolicy { * Only ever called if the {@link AudioPolicy} was built with * {@link AudioPolicy.Builder#setIsAudioFocusPolicy(boolean)} set to {@code true}. * @param afi information about the focus request and the requester - * @param requestResult the result that was returned synchronously by the framework to the - * application, {@link #AUDIOFOCUS_REQUEST_FAILED},or - * {@link #AUDIOFOCUS_REQUEST_DELAYED}. + * @param requestResult deprecated after the addition of + * {@link AudioManager#setFocusRequestResult(AudioFocusInfo, int, AudioPolicy)} + * in Android P, always equal to {@link #AUDIOFOCUS_REQUEST_GRANTED}. */ public void onAudioFocusRequest(AudioFocusInfo afi, int requestResult) {} /** @@ -534,7 +534,7 @@ public class AudioPolicy { sendMsg(MSG_FOCUS_REQUEST, afi, requestResult); if (DEBUG) { Log.v(TAG, "notifyAudioFocusRequest: pack=" + afi.getPackageName() + " client=" - + afi.getClientId() + "reqRes=" + requestResult); + + afi.getClientId() + " gen=" + afi.getGen()); } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 1825db8647bf4..56d66de21f985 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -7296,6 +7296,12 @@ public class AudioService extends IAudioService.Stub //====================== /** */ public int dispatchFocusChange(AudioFocusInfo afi, int focusChange, IAudioPolicyCallback pcb) { + if (afi == null) { + throw new IllegalArgumentException("Illegal null AudioFocusInfo"); + } + if (pcb == null) { + throw new IllegalArgumentException("Illegal null AudioPolicy callback"); + } synchronized (mAudioPolicies) { if (!mAudioPolicies.containsKey(pcb.asBinder())) { throw new IllegalStateException("Unregistered AudioPolicy for focus dispatch"); @@ -7304,6 +7310,23 @@ public class AudioService extends IAudioService.Stub } } + public void setFocusRequestResultFromExtPolicy(AudioFocusInfo afi, int requestResult, + IAudioPolicyCallback pcb) { + if (afi == null) { + throw new IllegalArgumentException("Illegal null AudioFocusInfo"); + } + if (pcb == null) { + throw new IllegalArgumentException("Illegal null AudioPolicy callback"); + } + synchronized (mAudioPolicies) { + if (!mAudioPolicies.containsKey(pcb.asBinder())) { + throw new IllegalStateException("Unregistered AudioPolicy for external focus"); + } + mMediaFocusControl.setFocusRequestResultFromExtPolicy(afi, requestResult); + } + } + + //====================== // misc //====================== diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java index f2ef02fb2579b..99f08405a375c 100644 --- a/services/core/java/com/android/server/audio/FocusRequester.java +++ b/services/core/java/com/android/server/audio/FocusRequester.java @@ -241,15 +241,15 @@ public class FocusRequester { void release() { + final IBinder srcRef = mSourceRef; + final AudioFocusDeathHandler deathHdlr = mDeathHandler; try { - if (mSourceRef != null && mDeathHandler != null) { - mSourceRef.unlinkToDeath(mDeathHandler, 0); - mDeathHandler = null; - mFocusDispatcher = null; + if (srcRef != null && deathHdlr != null) { + srcRef.unlinkToDeath(deathHdlr, 0); } - } catch (java.util.NoSuchElementException e) { - Log.e(TAG, "FocusRequester.release() hit ", e); - } + } catch (java.util.NoSuchElementException e) { } + mDeathHandler = null; + mFocusDispatcher = null; } @Override @@ -424,7 +424,7 @@ public class FocusRequester { int dispatchFocusChange(int focusChange) { if (mFocusDispatcher == null) { - if (MediaFocusControl.DEBUG) { Log.v(TAG, "dispatchFocusChange: no focus dispatcher"); } + if (MediaFocusControl.DEBUG) { Log.e(TAG, "dispatchFocusChange: no focus dispatcher"); } return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } if (focusChange == AudioManager.AUDIOFOCUS_NONE) { @@ -445,12 +445,29 @@ public class FocusRequester { try { mFocusDispatcher.dispatchAudioFocusChange(focusChange, mClientId); } catch (android.os.RemoteException e) { - Log.v(TAG, "dispatchFocusChange: error talking to focus listener", e); + Log.e(TAG, "dispatchFocusChange: error talking to focus listener " + mClientId, e); return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; } + void dispatchFocusResultFromExtPolicy(int requestResult) { + if (mFocusDispatcher == null) { + if (MediaFocusControl.DEBUG) { + Log.e(TAG, "dispatchFocusResultFromExtPolicy: no focus dispatcher"); + } + } + if (DEBUG) { + Log.v(TAG, "dispatching result" + requestResult + " to " + mClientId); + } + try { + mFocusDispatcher.dispatchFocusResultFromExtPolicy(requestResult, mClientId); + } catch (android.os.RemoteException e) { + Log.e(TAG, "dispatchFocusResultFromExtPolicy: error talking to focus listener" + + mClientId, e); + } + } + AudioFocusInfo toAudioFocusInfo() { return new AudioFocusInfo(mAttributes, mCallingUid, mClientId, mPackageName, mFocusGainRequest, mFocusLossReceived, mGrantFlags, mSdkTarget); diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java index 9ddc52a1826eb..d023bd7827ff1 100644 --- a/services/core/java/com/android/server/audio/MediaFocusControl.java +++ b/services/core/java/com/android/server/audio/MediaFocusControl.java @@ -83,6 +83,10 @@ public class MediaFocusControl implements PlayerFocusEnforcer { private boolean mRingOrCallActive = false; + private final Object mExtFocusChangeLock = new Object(); + @GuardedBy("mExtFocusChangeLock") + private long mExtFocusChangeCounter; + protected MediaFocusControl(Context cntxt, PlayerFocusEnforcer pfe) { mContext = cntxt; mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE); @@ -521,7 +525,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer { * @param requestResult * @return true if the external audio focus policy (if any) is handling the focus request */ - boolean notifyExtFocusPolicyFocusRequest_syncAf(AudioFocusInfo afi, int requestResult, + boolean notifyExtFocusPolicyFocusRequest_syncAf(AudioFocusInfo afi, IAudioFocusDispatcher fd, IBinder cb) { if (mFocusPolicy == null) { return false; @@ -530,6 +534,9 @@ public class MediaFocusControl implements PlayerFocusEnforcer { Log.v(TAG, "notifyExtFocusPolicyFocusRequest client="+afi.getClientId() + " dispatcher=" + fd); } + synchronized (mExtFocusChangeLock) { + afi.setGen(mExtFocusChangeCounter++); + } final FocusRequester existingFr = mFocusOwnersForFocusPolicy.get(afi.getClientId()); if (existingFr != null) { if (!existingFr.hasSameDispatcher(fd)) { @@ -538,8 +545,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer { mFocusOwnersForFocusPolicy.put(afi.getClientId(), new FocusRequester(afi, fd, cb, hdlr, this)); } - } else if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED - || requestResult == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) { + } else { // new focus (future) focus owner to keep track of final AudioFocusDeathHandler hdlr = new AudioFocusDeathHandler(cb); mFocusOwnersForFocusPolicy.put(afi.getClientId(), @@ -547,12 +553,25 @@ public class MediaFocusControl implements PlayerFocusEnforcer { } try { //oneway - mFocusPolicy.notifyAudioFocusRequest(afi, requestResult); + mFocusPolicy.notifyAudioFocusRequest(afi, AudioManager.AUDIOFOCUS_REQUEST_GRANTED); + return true; } catch (RemoteException e) { Log.e(TAG, "Can't call notifyAudioFocusRequest() on IAudioPolicyCallback " + mFocusPolicy.asBinder(), e); } - return true; + return false; + } + + void setFocusRequestResultFromExtPolicy(AudioFocusInfo afi, int requestResult) { + synchronized (mExtFocusChangeLock) { + if (afi.getGen() > mExtFocusChangeCounter) { + return; + } + } + final FocusRequester fr = mFocusOwnersForFocusPolicy.get(afi.getClientId()); + if (fr != null) { + fr.dispatchFocusResultFromExtPolicy(requestResult); + } } /** @@ -590,7 +609,12 @@ public class MediaFocusControl implements PlayerFocusEnforcer { if (DEBUG) { Log.v(TAG, "> failed: no focus policy" ); } return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } - final FocusRequester fr = mFocusOwnersForFocusPolicy.get(afi.getClientId()); + final FocusRequester fr; + if (focusChange == AudioManager.AUDIOFOCUS_LOSS) { + fr = mFocusOwnersForFocusPolicy.remove(afi.getClientId()); + } else { + fr = mFocusOwnersForFocusPolicy.get(afi.getClientId()); + } if (fr == null) { if (DEBUG) { Log.v(TAG, "> failed: no such focus requester known" ); } return AudioManager.AUDIOFOCUS_REQUEST_FAILED; @@ -710,9 +734,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer { boolean focusGrantDelayed = false; if (!canReassignAudioFocus()) { if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) { - final int result = AudioManager.AUDIOFOCUS_REQUEST_FAILED; - notifyExtFocusPolicyFocusRequest_syncAf(afiForExtPolicy, result, fd, cb); - return result; + return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } else { // request has AUDIOFOCUS_FLAG_DELAY_OK: focus can't be // granted right now, so the requester will be inserted in the focus stack @@ -721,12 +743,11 @@ public class MediaFocusControl implements PlayerFocusEnforcer { } } - // external focus policy: delay request for focus gain? - final int resultWithExtPolicy = AudioManager.AUDIOFOCUS_REQUEST_DELAYED; + // external focus policy? if (notifyExtFocusPolicyFocusRequest_syncAf( - afiForExtPolicy, resultWithExtPolicy, fd, cb)) { + afiForExtPolicy, fd, cb)) { // stop handling focus request here as it is handled by external audio focus policy - return resultWithExtPolicy; + return AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY; } // handle the potential premature death of the new holder of the focus