Merge "Add support for audio focus locking" into lmp-mr1-dev
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 645681a..3f08305 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -17,6 +17,7 @@
 package android.media;
 
 import android.Manifest;
+import android.annotation.NonNull;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemApi;
@@ -2318,14 +2319,25 @@
         return status;
     }
 
-    // when adding new flags, add them to AUDIOFOCUS_FLAGS_ALL
+    // when adding new flags, add them to the relevant AUDIOFOCUS_FLAGS_APPS or SYSTEM masks
     /** @hide */
+    @SystemApi
     public static final int AUDIOFOCUS_FLAG_DELAY_OK = 0x1 << 0;
     /** @hide */
-    public static final int AUDIOFOCUS_FLAGS_ALL = AUDIOFOCUS_FLAG_DELAY_OK;
+    @SystemApi
+    public static final int AUDIOFOCUS_FLAG_LOCK     = 0x1 << 1;
+    /** @hide */
+    public static final int AUDIOFOCUS_FLAGS_APPS = AUDIOFOCUS_FLAG_DELAY_OK;
+    /** @hide */
+    public static final int AUDIOFOCUS_FLAGS_SYSTEM = AUDIOFOCUS_FLAG_DELAY_OK
+            | AUDIOFOCUS_FLAG_LOCK;
 
     /**
      * @hide
+     * Request audio focus.
+     * Send a request to obtain the audio focus. This method differs from
+     * {@link #requestAudioFocus(OnAudioFocusChangeListener, int, int)} in that it can express
+     * that the requester accepts delayed grants of audio focus.
      * @param l the listener to be notified of audio focus changes. It is not allowed to be null
      *     when the request is flagged with {@link #AUDIOFOCUS_FLAG_DELAY_OK}.
      * @param requestAttributes non null {@link AudioAttributes} describing the main reason for
@@ -2340,24 +2352,70 @@
      *      usecases such as voice memo recording, or speech recognition.
      *      Use {@link #AUDIOFOCUS_GAIN} for a focus request of unknown duration such
      *      as the playback of a song or a video.
-     * @param flags use 0 when not using any flags for the request, which behaves like
-     *      {@link #requestAudioFocus(OnAudioFocusChangeListener, int, int)}, where either audio
-     *      focus is granted immediately, or the grant request fails because the system is in a
-     *      state where focus cannot change (e.g. a phone call).
-     *      Use {link #AUDIOFOCUS_FLAG_DELAY_OK} if it is ok for the requester to not be granted
-     *      audio focus immediately (as indicated by {@link #AUDIOFOCUS_REQUEST_DELAYED}) when
-     *      the system is in a state where focus cannot change, but be granted focus later when
-     *      this condition ends.
+     * @param flags 0 or {link #AUDIOFOCUS_FLAG_DELAY_OK}.
+     *     <br>Use 0 when not using any flags for the request, which behaves like
+     *     {@link #requestAudioFocus(OnAudioFocusChangeListener, int, int)}, where either audio
+     *     focus is granted immediately, or the grant request fails because the system is in a
+     *     state where focus cannot change (e.g. a phone call).
+     *     <br>Use {link #AUDIOFOCUS_FLAG_DELAY_OK} if it is ok for the requester to not be granted
+     *     audio focus immediately (as indicated by {@link #AUDIOFOCUS_REQUEST_DELAYED}) when
+     *     the system is in a state where focus cannot change, but be granted focus later when
+     *     this condition ends.
      * @return {@link #AUDIOFOCUS_REQUEST_FAILED}, {@link #AUDIOFOCUS_REQUEST_GRANTED}
      *     or {@link #AUDIOFOCUS_REQUEST_DELAYED}.
      *     The return value is never {@link #AUDIOFOCUS_REQUEST_DELAYED} when focus is requested
      *     without the {@link #AUDIOFOCUS_FLAG_DELAY_OK} flag.
      * @throws IllegalArgumentException
      */
+    @SystemApi
     public int requestAudioFocus(OnAudioFocusChangeListener l,
-            AudioAttributes requestAttributes,
+            @NonNull AudioAttributes requestAttributes,
             int durationHint,
             int flags) throws IllegalArgumentException {
+        if (flags != (flags & AUDIOFOCUS_FLAGS_APPS)) {
+            throw new IllegalArgumentException("Invalid flags 0x"
+                    + Integer.toHexString(flags).toUpperCase());
+        }
+        return requestAudioFocus(l, requestAttributes, durationHint,
+                flags & AUDIOFOCUS_FLAGS_APPS,
+                null /* no AudioPolicy*/);
+    }
+
+    /**
+     * @hide
+     * Request or lock audio focus.
+     * This method is to be used by system components that have registered an
+     * {@link android.media.audiopolicy.AudioPolicy} to request audio focus, but also to "lock" it
+     * so focus granting is temporarily disabled.
+     * @param l see the description of the same parameter in
+     *     {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int)}
+     * @param requestAttributes non null {@link AudioAttributes} describing the main reason for
+     *     requesting audio focus.
+     * @param durationHint see the description of the same parameter in
+     *     {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int)}
+     * @param flags 0 or a combination of {link #AUDIOFOCUS_FLAG_DELAY_OK},
+     *     {@link #AUDIOFOCUS_FLAG_LOCK}
+     *     <br>Use 0 when not using any flags for the request, which behaves like
+     *     {@link #requestAudioFocus(OnAudioFocusChangeListener, int, int)}, where either audio
+     *     focus is granted immediately, or the grant request fails because the system is in a
+     *     state where focus cannot change (e.g. a phone call).
+     *     <br>Use {link #AUDIOFOCUS_FLAG_DELAY_OK} if it is ok for the requester to not be granted
+     *     audio focus immediately (as indicated by {@link #AUDIOFOCUS_REQUEST_DELAYED}) when
+     *     the system is in a state where focus cannot change, but be granted focus later when
+     *     this condition ends.
+     *     <br>Use {@link #AUDIOFOCUS_FLAG_LOCK} when locking audio focus so granting is
+     *     temporarily disabled.
+     * @param ap a registered {@link android.media.audiopolicy.AudioPolicy} instance when locking
+     *     focus, or null.
+     * @return see the description of the same return value in
+     *     {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int)}
+     * @throws IllegalArgumentException
+     */
+    public int requestAudioFocus(OnAudioFocusChangeListener l,
+            @NonNull AudioAttributes requestAttributes,
+            int durationHint,
+            int flags,
+            AudioPolicy ap) throws IllegalArgumentException {
         // parameter checking
         if (requestAttributes == null) {
             throw new IllegalArgumentException("Illegal null AudioAttributes argument");
@@ -2366,7 +2424,7 @@
                 (durationHint > AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)) {
             throw new IllegalArgumentException("Invalid duration hint");
         }
-        if (flags != (flags & AUDIOFOCUS_FLAGS_ALL)) {
+        if (flags != (flags & AUDIOFOCUS_FLAGS_SYSTEM)) {
             throw new IllegalArgumentException("Illegal flags 0x"
                 + Integer.toHexString(flags).toUpperCase());
         }
@@ -2374,6 +2432,10 @@
             throw new IllegalArgumentException(
                     "Illegal null focus listener when flagged as accepting delayed focus grant");
         }
+        if (((flags & AUDIOFOCUS_FLAG_LOCK) == AUDIOFOCUS_FLAG_LOCK) && (ap == null)) {
+            throw new IllegalArgumentException(
+                    "Illegal null audio policy when locking audio focus");
+        }
 
         int status = AUDIOFOCUS_REQUEST_FAILED;
         registerAudioFocusListener(l);
@@ -2381,9 +2443,10 @@
         try {
             status = service.requestAudioFocus(requestAttributes, durationHint, mICallBack,
                     mAudioFocusDispatcher, getIdForAudioFocusListener(l),
-                    mContext.getOpPackageName() /* package name */, flags);
+                    mContext.getOpPackageName() /* package name */, flags,
+                    ap != null ? ap.token() : null);
         } catch (RemoteException e) {
-            Log.e(TAG, "Can't call requestAudioFocus() on AudioService due to "+e);
+            Log.e(TAG, "Can't call requestAudioFocus() on AudioService:", e);
         }
         return status;
     }
@@ -2405,9 +2468,11 @@
                         .setInternalLegacyStreamType(streamType).build(),
                     durationHint, mICallBack, null,
                     MediaFocusControl.IN_VOICE_COMM_FOCUS_ID,
-                    mContext.getOpPackageName(), 0 /* flags, legacy behavior*/ );
+                    mContext.getOpPackageName(),
+                    AUDIOFOCUS_FLAG_LOCK,
+                    null /* policy token */);
         } catch (RemoteException e) {
-            Log.e(TAG, "Can't call requestAudioFocusForCall() on AudioService due to "+e);
+            Log.e(TAG, "Can't call requestAudioFocusForCall() on AudioService:", e);
         }
     }
 
@@ -2420,9 +2485,10 @@
     public void abandonAudioFocusForCall() {
         IAudioService service = getService();
         try {
-            service.abandonAudioFocus(null, MediaFocusControl.IN_VOICE_COMM_FOCUS_ID);
+            service.abandonAudioFocus(null, MediaFocusControl.IN_VOICE_COMM_FOCUS_ID,
+                    null /*AudioAttributes, legacy behavior*/);
         } catch (RemoteException e) {
-            Log.e(TAG, "Can't call abandonAudioFocusForCall() on AudioService due to "+e);
+            Log.e(TAG, "Can't call abandonAudioFocusForCall() on AudioService:", e);
         }
     }
 
@@ -2432,19 +2498,30 @@
      *  @return {@link #AUDIOFOCUS_REQUEST_FAILED} or {@link #AUDIOFOCUS_REQUEST_GRANTED}
      */
     public int abandonAudioFocus(OnAudioFocusChangeListener l) {
+        return abandonAudioFocus(l, null /*AudioAttributes, legacy behavior*/);
+    }
+
+    /**
+     * @hide
+     * Abandon audio focus. Causes the previous focus owner, if any, to receive focus.
+     *  @param l the listener with which focus was requested.
+     * @param aa the {@link AudioAttributes} with which audio focus was requested
+     * @return {@link #AUDIOFOCUS_REQUEST_FAILED} or {@link #AUDIOFOCUS_REQUEST_GRANTED}
+     */
+    @SystemApi
+    public int abandonAudioFocus(OnAudioFocusChangeListener l, AudioAttributes aa) {
         int status = AUDIOFOCUS_REQUEST_FAILED;
         unregisterAudioFocusListener(l);
         IAudioService service = getService();
         try {
             status = service.abandonAudioFocus(mAudioFocusDispatcher,
-                    getIdForAudioFocusListener(l));
+                    getIdForAudioFocusListener(l), aa);
         } catch (RemoteException e) {
-            Log.e(TAG, "Can't call abandonAudioFocus() on AudioService due to "+e);
+            Log.e(TAG, "Can't call abandonAudioFocus() on AudioService:", e);
         }
         return status;
     }
 
-
     //====================================================================
     // Remote Control
     /**
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index c70ac55..b6b24a4 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -49,6 +49,7 @@
 import android.media.MediaPlayer.OnCompletionListener;
 import android.media.MediaPlayer.OnErrorListener;
 import android.media.audiopolicy.AudioMix;
+import android.media.audiopolicy.AudioPolicy;
 import android.media.audiopolicy.AudioPolicyConfig;
 import android.os.Binder;
 import android.os.Build;
@@ -5018,13 +5019,34 @@
     // Audio Focus
     //==========================================================================================
     public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb,
-            IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags) {
+            IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
+            IBinder policyToken) {
+        // permission checks
+        if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) == AudioManager.AUDIOFOCUS_FLAG_LOCK) {
+            if (mMediaFocusControl.IN_VOICE_COMM_FOCUS_ID.equals(clientId)) {
+                if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(
+                            android.Manifest.permission.MODIFY_PHONE_STATE)) {
+                    Log.e(TAG, "Invalid permission to (un)lock audio focus", new Exception());
+                    return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+                }
+            } else {
+                // only a registered audio policy can be used to lock focus
+                synchronized (mAudioPolicies) {
+                    if (!mAudioPolicies.containsKey(policyToken)) {
+                        Log.e(TAG, "Invalid unregistered AudioPolicy to (un)lock audio focus",
+                                new Exception());
+                        return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+                    }
+                }
+            }
+        }
+
         return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd,
                 clientId, callingPackageName, flags);
     }
 
-    public int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId) {
-        return mMediaFocusControl.abandonAudioFocus(fd, clientId);
+    public int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, AudioAttributes aa) {
+        return mMediaFocusControl.abandonAudioFocus(fd, clientId, aa);
     }
 
     public void unregisterAudioFocusClient(String clientId) {
@@ -5725,6 +5747,9 @@
         // TODO implement clearing mix attribute matching info in native audio policy
     }
 
+    //======================
+    // Audio policy proxy
+    //======================
     /**
      * This internal class inherits from AudioPolicyConfig which contains all the mixes and
      * their configurations.
@@ -5742,8 +5767,8 @@
         public void binderDied() {
             synchronized (mAudioPolicies) {
                 Log.i(TAG, "audio policy " + mToken + " died");
-                mAudioPolicies.remove(mToken);
                 disconnectMixes();
+                mAudioPolicies.remove(mToken);
             }
         }
 
diff --git a/media/java/android/media/FocusRequester.java b/media/java/android/media/FocusRequester.java
index 682d54c..0d675fc 100644
--- a/media/java/android/media/FocusRequester.java
+++ b/media/java/android/media/FocusRequester.java
@@ -83,6 +83,10 @@
         }
     }
 
+    boolean isLockedFocusOwner() {
+        return ((mGrantFlags & AudioManager.AUDIOFOCUS_FLAG_LOCK) != 0);
+    }
+
     boolean hasSameBinder(IBinder ib) {
         return (mSourceRef != null) && mSourceRef.equals(ib);
     }
@@ -99,6 +103,9 @@
         return mCallingUid == uid;
     }
 
+    String getClientId() {
+        return mClientId;
+    }
 
     int getGainRequest() {
         return mFocusGainRequest;
@@ -144,12 +151,20 @@
         return focusChangeToString(mFocusLossReceived);
     }
 
+    private static String flagsToString(int flags) {
+        String msg = new String();
+        if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) != 0) { msg += "DELAY_OK"; }
+        if (!msg.isEmpty()) { msg += "|"; }
+        if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) != 0)     { msg += "LOCK"; }
+        return msg;
+    }
+
     void dump(PrintWriter pw) {
         pw.println("  source:" + mSourceRef
                 + " -- pack: " + mPackageName
                 + " -- client: " + mClientId
                 + " -- gain: " + focusGainToString()
-                + " -- grant: " + mGrantFlags
+                + " -- flags: " + flagsToString(mGrantFlags)
                 + " -- loss: " + focusLossToString()
                 + " -- uid: " + mCallingUid
                 + " -- attr: " + mAttributes);
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 47a5291..b691447 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -118,9 +118,10 @@
     boolean isBluetoothA2dpOn();
 
     int requestAudioFocus(in AudioAttributes aa, int durationHint, IBinder cb,
-            IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags);
+            IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
+            IBinder policyToken);
 
-    int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId);
+    int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, in AudioAttributes aa);
 
     void unregisterAudioFocusClient(String clientId);
 
diff --git a/media/java/android/media/MediaFocusControl.java b/media/java/android/media/MediaFocusControl.java
index c495106..8d96970 100644
--- a/media/java/android/media/MediaFocusControl.java
+++ b/media/java/android/media/MediaFocusControl.java
@@ -390,7 +390,8 @@
     // AudioFocus
     //==========================================================================================
 
-    /* constant to identify focus stack entry that is used to hold the focus while the phone
+    /**
+     * Constant to identify a focus stack entry that is used to hold the focus while the phone
      * is ringing or during a call. Used by com.android.internal.telephony.CallManager when
      * entering and exiting calls.
      */
@@ -539,40 +540,40 @@
      * Helper function:
      * Returns true if the system is in a state where the focus can be reevaluated, false otherwise.
      * The implementation guarantees that a state where focus cannot be immediately reassigned
-     * implies that an "exclusive" focus owner is at the top of the focus stack.
+     * implies that an "locked" focus owner is at the top of the focus stack.
      * Modifications to the implementation that break this assumption will cause focus requests to
      * misbehave when honoring the AudioManager.AUDIOFOCUS_FLAG_DELAY_OK flag.
      */
     private boolean canReassignAudioFocus() {
         // focus requests are rejected during a phone call or when the phone is ringing
         // this is equivalent to IN_VOICE_COMM_FOCUS_ID having the focus
-        if (!mFocusStack.isEmpty() && isExclusiveFocusOwner(mFocusStack.peek())) {
+        if (!mFocusStack.isEmpty() && isLockedFocusOwner(mFocusStack.peek())) {
             return false;
         }
         return true;
     }
 
-    private boolean isExclusiveFocusOwner(FocusRequester fr) {
-        return fr.hasSameClient(IN_VOICE_COMM_FOCUS_ID);
+    private boolean isLockedFocusOwner(FocusRequester fr) {
+        return (fr.hasSameClient(IN_VOICE_COMM_FOCUS_ID) || fr.isLockedFocusOwner());
     }
 
     /**
      * Helper function
-     * Pre-conditions: focus stack is not empty, there is one or more exclusive focus owner
+     * Pre-conditions: focus stack is not empty, there is one or more locked focus owner
      *                 at the top of the focus stack
      * Push the focus requester onto the audio focus stack at the first position immediately
-     * following the exclusive focus owners.
+     * following the locked focus owners.
      * @return {@link AudioManager#AUDIOFOCUS_REQUEST_GRANTED} or
      *     {@link AudioManager#AUDIOFOCUS_REQUEST_DELAYED}
      */
-    private int pushBelowExclusiveFocusOwners(FocusRequester nfr) {
-        int lastExclusiveFocusOwnerIndex = mFocusStack.size();
+    private int pushBelowLockedFocusOwners(FocusRequester nfr) {
+        int lastLockedFocusOwnerIndex = mFocusStack.size();
         for (int index = mFocusStack.size()-1; index >= 0; index--) {
-            if (isExclusiveFocusOwner(mFocusStack.elementAt(index))) {
-                lastExclusiveFocusOwnerIndex = index;
+            if (isLockedFocusOwner(mFocusStack.elementAt(index))) {
+                lastLockedFocusOwnerIndex = index;
             }
         }
-        if (lastExclusiveFocusOwnerIndex == mFocusStack.size()) {
+        if (lastLockedFocusOwnerIndex == mFocusStack.size()) {
             // this should not happen, but handle it and log an error
             Log.e(TAG, "No exclusive focus owner found in propagateFocusLossFromGain_syncAf()",
                     new Exception());
@@ -581,7 +582,7 @@
             mFocusStack.push(nfr);
             return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
         } else {
-            mFocusStack.insertElementAt(nfr, lastExclusiveFocusOwnerIndex);
+            mFocusStack.insertElementAt(nfr, lastLockedFocusOwnerIndex);
             return AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
         }
     }
@@ -687,7 +688,7 @@
             if (focusGrantDelayed) {
                 // focusGrantDelayed being true implies we can't reassign focus right now
                 // which implies the focus stack is not empty.
-                return pushBelowExclusiveFocusOwners(nfr);
+                return pushBelowLockedFocusOwners(nfr);
             } else {
                 // propagate the focus change through the stack
                 if (!mFocusStack.empty()) {
@@ -703,8 +704,11 @@
         return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
     }
 
-    /** @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener)  */
-    protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId) {
+    /**
+     * @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener, AudioAttributes)
+     * */
+    protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa) {
+        // AudioAttributes are currently ignored, to be used for zones
         Log.i(TAG, " AudioFocus  abandonAudioFocus() from " + clientId);
         try {
             // this will take care of notifying the new focus owner if needed