Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1bef2674 authored by Jean-Michel Trivi's avatar Jean-Michel Trivi Committed by Android (Google) Code Review
Browse files

Merge "First implementation of the audio focus management as an extension of...

Merge "First implementation of the audio focus management as an extension of AudioManager and AudioService."
parents f79a819f d5176cfe
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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 \
+240 −0
Original line number Diff line number Diff line
@@ -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>
@@ -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
+224 −1
Original line number Diff line number Diff line
@@ -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;
@@ -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.
@@ -76,6 +78,7 @@ public class AudioService extends IAudioService.Stub {
    private Context mContext;
    private ContentResolver mContentResolver;


    /** The UI */
    private VolumePanel mVolumePanel;

@@ -888,6 +891,7 @@ public class AudioService extends IAudioService.Stub {
        }
    }


    ///////////////////////////////////////////////////////////////////////////
    // Internal methods
    ///////////////////////////////////////////////////////////////////////////
@@ -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);
    }


}
+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);

}
+9 −0
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package android.media;

import android.media.IAudioFocusDispatcher;

/**
 * {@hide}
 */
@@ -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);
}