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

Commit cbf1840f authored by Eric Laurent's avatar Eric Laurent Committed by Android (Google) Code Review
Browse files

Merge "Issue 2416481: Support Voice Dialer over BT SCO."

parents b82ac6bf 3def1eec
Loading
Loading
Loading
Loading
+127 −1
Original line number Diff line number Diff line
@@ -690,6 +690,132 @@ public class AudioManager {
        }
     }

    //====================================================================
    // Bluetooth SCO control
    /**
     * @hide
     * TODO unhide for SDK
     * Sticky broadcast intent action indicating that the bluetoooth SCO audio
     * connection state has changed. The intent contains on extra {@link EXTRA_SCO_AUDIO_STATE}
     * indicating the new state which is either {@link #SCO_AUDIO_STATE_DISCONNECTED}
     * or {@link #SCO_AUDIO_STATE_CONNECTED}
     *
     * @see #startBluetoothSco()
     */
    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
    public static final String ACTION_SCO_AUDIO_STATE_CHANGED =
            "android.media.SCO_AUDIO_STATE_CHANGED";
    /**
     * @hide
     * TODO unhide for SDK
     * Extra for intent {@link #ACTION_SCO_AUDIO_STATE_CHANGED} containing the new
     * bluetooth SCO connection state.
     */
    public static final String EXTRA_SCO_AUDIO_STATE =
            "android.media.extra.SCO_AUDIO_STATE";

    /**
     * @hide
     * TODO unhide for SDK
     * Value for extra {@link #EXTRA_SCO_AUDIO_STATE} indicating that the
     * SCO audio channel is not established
     */
    public static final int SCO_AUDIO_STATE_DISCONNECTED = 0;
    /**
     * @hide
     * TODO unhide for SDK
     * Value for extra {@link #EXTRA_SCO_AUDIO_STATE} indicating that the
     * SCO audio channel is established
     */
    public static final int SCO_AUDIO_STATE_CONNECTED = 1;
    /**
     * @hide
     * TODO unhide for SDK
     * Value for extra {@link #EXTRA_SCO_AUDIO_STATE} indicating that
     * there was an error trying to obtain the state
     */
    public static final int SCO_AUDIO_STATE_ERROR = -1;


    /**
     * @hide
     * TODO unhide for SDK
     * Indicates if current platform supports use of SCO for off call use cases.
     * Application wanted to use bluetooth SCO audio when the phone is not in call
     * must first call thsi method to make sure that the platform supports this
     * feature.
     * @return true if bluetooth SCO can be used for audio when not in call
     *         false otherwise
     * @see #startBluetoothSco()
    */
    public boolean isBluetoothScoAvailableOffCall() {
        return mContext.getResources().getBoolean(
               com.android.internal.R.bool.config_bluetooth_sco_off_call);
    }

    /**
     * @hide
     * TODO unhide for SDK
     * Start bluetooth SCO audio connection.
     * <p>Requires Permission:
     *   {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS}.
     * <p>This method can be used by applications wanting to send and received audio
     * to/from a bluetooth SCO headset while the phone is not in call.
     * <p>As the SCO connection establishment can take several seconds,
     * applications should not rely on the connection to be available when the method
     * returns but instead register to receive the intent {@link #ACTION_SCO_AUDIO_STATE_CHANGED}
     * and wait for the state to be {@link #SCO_AUDIO_STATE_CONNECTED}.
     * <p>As the connection is not guaranteed to succeed, applications must wait for this intent with
     * a timeout.
     * <p>When finished with the SCO connection or if the establishment times out,
     * the application must call {@link #stopBluetoothSco()} to clear the request and turn
     * down the bluetooth connection.
     * <p>Even if a SCO connection is established, the following restrictions apply on audio
     * output streams so that they can be routed to SCO headset:
     * - the stream type must be {@link #STREAM_VOICE_CALL} or {@link #STREAM_BLUETOOTH_SCO}
     * - the format must be mono
     * - the sampling must be 16kHz or 8kHz
     * <p>The following restrictions apply on input streams:
     * - the format must be mono
     * - the sampling must be 8kHz
     *
     * <p>Note that the phone application always has the priority on the usage of the SCO
     * connection for telephony. If this method is called while the phone is in call
     * it will be ignored. Similarly, if a call is received or sent while an application
     * is using the SCO connection, the connection will be lost for the application and NOT
     * returned automatically when the call ends.
     * @see #stopBluetoothSco()
     * @see #ACTION_SCO_AUDIO_STATE_CHANGED
     */
    public void startBluetoothSco(){
        IAudioService service = getService();
        try {
            service.startBluetoothSco(mICallBack);
        } catch (RemoteException e) {
            Log.e(TAG, "Dead object in startBluetoothSco", e);
        }
    }

    /**
     * @hide
     * TODO unhide for SDK
     * Stop bluetooth SCO audio connection.
     * <p>Requires Permission:
     *   {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS}.
     * <p>This method must be called by applications having requested the use of
     * bluetooth SCO audio with {@link #startBluetoothSco()}
     * when finished with the SCO connection or if the establishment times out.
     * @see #startBluetoothSco()
     */
    public void stopBluetoothSco(){
        IAudioService service = getService();
        try {
            service.stopBluetoothSco(mICallBack);
        } catch (RemoteException e) {
            Log.e(TAG, "Dead object in stopBluetoothSco", e);
        }
    }

    /**
     * Request use of Bluetooth SCO headset for communications.
     * <p>
+195 −0
Original line number Diff line number Diff line
@@ -240,6 +240,15 @@ public class AudioService extends IAudioService.Stub {
    // The last process to have called setMode() is at the top of the list.
    private ArrayList <SetModeDeathHandler> mSetModeDeathHandlers = new ArrayList <SetModeDeathHandler>();

    // List of clients having issued a SCO start request
    private ArrayList <ScoClient> mScoClients = new ArrayList <ScoClient>();

    // BluetoothHeadset API to control SCO connection
    private BluetoothHeadset mBluetoothHeadset;

    // Bluetooth headset connection state
    private boolean mBluetoothHeadsetConnected;

    ///////////////////////////////////////////////////////////////////////////
    // Construction
    ///////////////////////////////////////////////////////////////////////////
@@ -267,12 +276,17 @@ public class AudioService extends IAudioService.Stub {
        AudioSystem.setErrorCallback(mAudioSystemCallback);
        loadSoundEffects();

        mBluetoothHeadsetConnected = false;
        mBluetoothHeadset = new BluetoothHeadset(context,
                                                 mBluetoothHeadsetServiceListener);

        // Register for device connection intent broadcasts.
        IntentFilter intentFilter =
                new IntentFilter(Intent.ACTION_HEADSET_PLUG);
        intentFilter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
        intentFilter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED);
        intentFilter.addAction(Intent.ACTION_DOCK_EVENT);
        intentFilter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
        context.registerReceiver(mReceiver, intentFilter);

        // Register for media button intent broadcasts.
@@ -705,6 +719,10 @@ public class AudioService extends IAudioService.Stub {
                        mSetModeDeathHandlers.add(0, hdlr);
                        hdlr.setMode(mode);
                    }

                    if (mode != AudioSystem.MODE_NORMAL) {
                        clearAllScoClients();
                    }
                }
            }
            int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
@@ -909,6 +927,157 @@ public class AudioService extends IAudioService.Stub {
        }
    }

    /** @see AudioManager#startBluetoothSco() */
    public void startBluetoothSco(IBinder cb){
        if (!checkAudioSettingsPermission("startBluetoothSco()")) {
            return;
        }
        ScoClient client = getScoClient(cb);
        client.incCount();
    }

    /** @see AudioManager#stopBluetoothSco() */
    public void stopBluetoothSco(IBinder cb){
        if (!checkAudioSettingsPermission("stopBluetoothSco()")) {
            return;
        }
        ScoClient client = getScoClient(cb);
        client.decCount();
    }

    private class ScoClient implements IBinder.DeathRecipient {
        private IBinder mCb; // To be notified of client's death
        private int mStartcount; // number of SCO connections started by this client

        ScoClient(IBinder cb) {
            mCb = cb;
            mStartcount = 0;
        }

        public void binderDied() {
            synchronized(mScoClients) {
                Log.w(TAG, "SCO client died");
                int index = mScoClients.indexOf(this);
                if (index < 0) {
                    Log.w(TAG, "unregistered SCO client died");
                } else {
                    clearCount(true);
                    mScoClients.remove(this);
                }
            }
        }

        public void incCount() {
            synchronized(mScoClients) {
                requestScoState(BluetoothHeadset.AUDIO_STATE_CONNECTED);
                if (mStartcount == 0) {
                    try {
                        mCb.linkToDeath(this, 0);
                    } catch (RemoteException e) {
                        // client has already died!
                        Log.w(TAG, "ScoClient  incCount() could not link to "+mCb+" binder death");
                    }
                }
                mStartcount++;
            }
        }

        public void decCount() {
            synchronized(mScoClients) {
                if (mStartcount == 0) {
                    Log.w(TAG, "ScoClient.decCount() already 0");
                } else {
                    mStartcount--;
                    if (mStartcount == 0) {
                        mCb.unlinkToDeath(this, 0);
                    }
                    requestScoState(BluetoothHeadset.AUDIO_STATE_DISCONNECTED);
                }
            }
        }

        public void clearCount(boolean stopSco) {
            synchronized(mScoClients) {
                mStartcount = 0;
                mCb.unlinkToDeath(this, 0);
                if (stopSco) {
                    requestScoState(BluetoothHeadset.AUDIO_STATE_DISCONNECTED);
                }
            }
        }

        public int getCount() {
            return mStartcount;
        }

        public IBinder getBinder() {
            return mCb;
        }

        public int totalCount() {
            synchronized(mScoClients) {
                int count = 0;
                int size = mScoClients.size();
                for (int i = 0; i < size; i++) {
                    count += mScoClients.get(i).getCount();
                }
                return count;
            }
        }

        private void requestScoState(int state) {
            if (totalCount() == 0 &&
                mBluetoothHeadsetConnected &&
                AudioService.this.mMode == AudioSystem.MODE_NORMAL) {
                if (state == BluetoothHeadset.AUDIO_STATE_CONNECTED) {
                    mBluetoothHeadset.startVoiceRecognition();
                } else {
                    mBluetoothHeadset.stopVoiceRecognition();
                }
            }
        }
    }

    public ScoClient getScoClient(IBinder cb) {
        synchronized(mScoClients) {
            ScoClient client;
            int size = mScoClients.size();
            for (int i = 0; i < size; i++) {
                client = mScoClients.get(i);
                if (client.getBinder() == cb)
                    return client;
            }
            client = new ScoClient(cb);
            mScoClients.add(client);
            return client;
        }
    }

    public void clearAllScoClients() {
        synchronized(mScoClients) {
            int size = mScoClients.size();
            for (int i = 0; i < size; i++) {
                mScoClients.get(i).clearCount(false);
            }
        }
    }

    private BluetoothHeadset.ServiceListener mBluetoothHeadsetServiceListener =
        new BluetoothHeadset.ServiceListener() {
        public void onServiceConnected() {
            if (mBluetoothHeadset != null &&
                mBluetoothHeadset.getState() == BluetoothHeadset.STATE_CONNECTED) {
                mBluetoothHeadsetConnected = true;
            }
        }
        public void onServiceDisconnected() {
            if (mBluetoothHeadset != null &&
                mBluetoothHeadset.getState() == BluetoothHeadset.STATE_DISCONNECTED) {
                mBluetoothHeadsetConnected = false;
                clearAllScoClients();
            }
        }
    };

    ///////////////////////////////////////////////////////////////////////////
    // Internal methods
@@ -1577,11 +1746,14 @@ public class AudioService extends IAudioService.Stub {
                                                         AudioSystem.DEVICE_STATE_UNAVAILABLE,
                                                         address);
                    mConnectedDevices.remove(device);
                    mBluetoothHeadsetConnected = false;
                    clearAllScoClients();
                } else if (!isConnected && state == BluetoothHeadset.STATE_CONNECTED) {
                    AudioSystem.setDeviceConnectionState(device,
                                                         AudioSystem.DEVICE_STATE_AVAILABLE,
                                                         address);
                    mConnectedDevices.put(new Integer(device), address);
                    mBluetoothHeadsetConnected = true;
                }
            } else if (action.equals(Intent.ACTION_HEADSET_PLUG)) {
                int state = intent.getIntExtra("state", 0);
@@ -1614,6 +1786,29 @@ public class AudioService extends IAudioService.Stub {
                        mConnectedDevices.put( new Integer(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE), "");
                    }
                }
            } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
                int state = intent.getIntExtra(BluetoothHeadset.EXTRA_AUDIO_STATE,
                                               BluetoothHeadset.STATE_ERROR);
                synchronized (mScoClients) {
                    if (!mScoClients.isEmpty()) {
                        switch (state) {
                        case BluetoothHeadset.AUDIO_STATE_CONNECTED:
                            state = AudioManager.SCO_AUDIO_STATE_CONNECTED;
                            break;
                        case BluetoothHeadset.AUDIO_STATE_DISCONNECTED:
                            state = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
                            break;
                        default:
                            state = AudioManager.SCO_AUDIO_STATE_ERROR;
                            break;
                        }
                        if (state != AudioManager.SCO_AUDIO_STATE_ERROR) {
                            Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
                            newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state);
                            mContext.sendStickyBroadcast(newIntent);
                        }
                    }
                }
            }
        }
    }
+4 −0
Original line number Diff line number Diff line
@@ -82,4 +82,8 @@ interface IAudioService {
    void registerMediaButtonEventReceiver(in ComponentName eventReceiver);

    void unregisterMediaButtonEventReceiver(in ComponentName eventReceiver);

    void startBluetoothSco(IBinder cb);

    void stopBluetoothSco(IBinder cb);
}