First implementation of the audio focus management as an extension
of AudioManager and AudioService.
diff --git a/Android.mk b/Android.mk
index 12dddd6..8a8678d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -167,6 +167,7 @@
 	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 \
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 32c5c23..b0a179f 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -23,11 +23,16 @@
 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>
@@ -1127,6 +1132,241 @@
     }
 
     /**
+     * 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
      *  agent when audio settings are restored and causes the AudioService
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 947307d4..a36ee85 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -26,7 +26,6 @@
 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;
@@ -47,12 +46,15 @@
 
 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.
@@ -76,6 +78,7 @@
     private Context mContext;
     private ContentResolver mContentResolver;
 
+
     /** The UI */
     private VolumePanel mVolumePanel;
 
@@ -888,6 +891,7 @@
         }
     }
 
+
     ///////////////////////////////////////////////////////////////////////////
     // Internal methods
     ///////////////////////////////////////////////////////////////////////////
@@ -1600,4 +1604,223 @@
             }
         }
     }
+
+    //==========================================================================================
+    // 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);
+    }
+
+
 }
diff --git a/media/java/android/media/IAudioFocusDispatcher.aidl b/media/java/android/media/IAudioFocusDispatcher.aidl
new file mode 100755
index 0000000..09575f7
--- /dev/null
+++ b/media/java/android/media/IAudioFocusDispatcher.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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);
+
+}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 83581d2..b275488 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -16,6 +16,8 @@
 
 package android.media;
 
+import android.media.IAudioFocusDispatcher;
+
 /**
  * {@hide}
  */
@@ -68,4 +70,11 @@
     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);
 }