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

Commit 3e8c82ed authored by Matthew Xie's avatar Matthew Xie Committed by Android (Google) Code Review
Browse files

Framework changes for HFP and A2DP profile implementation of the new stack.

Add IBluetoothHeadsetPhone.aidl for a small service in Phone to get
phone state changes

Change-Id: I1015e4a69720c4e9cd18ae4236ccd0dbff2e1b2c
parent e21a4ac0
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -89,6 +89,7 @@ LOCAL_SRC_FILES += \
	core/java/android/bluetooth/IBluetoothA2dp.aidl \
	core/java/android/bluetooth/IBluetoothCallback.aidl \
	core/java/android/bluetooth/IBluetoothHeadset.aidl \
	core/java/android/bluetooth/IBluetoothHeadsetPhone.aidl \
	core/java/android/bluetooth/IBluetoothHealthCallback.aidl \
	core/java/android/bluetooth/IBluetoothPbap.aidl \
	core/java/android/bluetooth/IBluetoothStateChangeCallback.aidl \
+27 −94
Original line number Diff line number Diff line
@@ -18,11 +18,13 @@ package android.bluetooth;

import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;

import java.util.ArrayList;
@@ -42,7 +44,7 @@ import java.util.List;
 */
public final class BluetoothA2dp implements BluetoothProfile {
    private static final String TAG = "BluetoothA2dp";
    private static final boolean DBG = false;
    private static final boolean DBG = true;

    /**
     * Intent used to broadcast the change in connection state of the A2DP
@@ -101,6 +103,7 @@ public final class BluetoothA2dp implements BluetoothProfile {
     */
    public static final int STATE_NOT_PLAYING   =  11;

    private Context mContext;
    private ServiceListener mServiceListener;
    private IBluetoothA2dp mService;
    private BluetoothAdapter mAdapter;
@@ -110,22 +113,12 @@ public final class BluetoothA2dp implements BluetoothProfile {
     * Bluetooth A2DP service.
     *
     */
    /*package*/ BluetoothA2dp(Context mContext, ServiceListener l) {
        //TODO(BT): Fix this
        IBinder b = null;
    /*package*/ BluetoothA2dp(Context context, ServiceListener l) {
        mContext = context;
        mServiceListener = l;
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        if (b != null) {
            mService = IBluetoothA2dp.Stub.asInterface(b);
            if (mServiceListener != null) {
                mServiceListener.onServiceConnected(BluetoothProfile.A2DP, this);
            }
        } else {
            Log.w(TAG, "Bluetooth A2DP service not available!");

            // Instead of throwing an exception which prevents people from going
            // into Wireless settings in the emulator. Let it crash later when it is actually used.
            mService = null;
        if (!context.bindService(new Intent(IBluetoothA2dp.class.getName()), mConnection, 0)) {
            Log.e(TAG, "Could not bind to Bluetooth A2DP Service");
        }
    }

@@ -346,67 +339,6 @@ public final class BluetoothA2dp implements BluetoothProfile {
        return false;
    }

    /**
     * Initiate suspend from an A2DP sink.
     *
     * <p> This API will return false in scenarios like the A2DP
     * device is not in connected state etc. When this API returns,
     * true, it is guaranteed that {@link #ACTION_CONNECTION_STATE_CHANGED}
     * intent will be broadcasted with the state. Users can get the
     * state of the A2DP device from this intent.
     *
     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
     * permission.
     *
     * @param device Remote A2DP sink
     * @return false on immediate error,
     *               true otherwise
     * @hide
     */
    public boolean suspendSink(BluetoothDevice device) {
        if (mService != null && isEnabled()
            && isValidDevice(device)) {
            try {
                return mService.suspendSink(device);
            } catch (RemoteException e) {
                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                return false;
            }
        }
        if (mService == null) Log.w(TAG, "Proxy not attached to service");
        return false;
    }

    /**
     * Initiate resume from a suspended A2DP sink.
     *
     * <p> This API will return false in scenarios like the A2DP
     * device is not in suspended state etc. When this API returns,
     * true, it is guaranteed that {@link #ACTION_SINK_STATE_CHANGED}
     * intent will be broadcasted with the state. Users can get the
     * state of the A2DP device from this intent.
     *
     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
     *
     * @param device Remote A2DP sink
     * @return false on immediate error,
     *               true otherwise
     * @hide
     */
    public boolean resumeSink(BluetoothDevice device) {
        if (mService != null && isEnabled()
            && isValidDevice(device)) {
            try {
                return mService.resumeSink(device);
            } catch (RemoteException e) {
                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                return false;
            }
        }
        if (mService == null) Log.w(TAG, "Proxy not attached to service");
        return false;
    }

    /**
     * This function checks if the remote device is an AVCRP
     * target and thus whether we should send volume keys
@@ -427,23 +359,6 @@ public final class BluetoothA2dp implements BluetoothProfile {
        return false;
    }

    /**
     * Allow or disallow incoming connection
     * @param device Sink
     * @param value True / False
     * @return Success or Failure of the binder call.
     * @hide
     */
    public boolean allowIncomingConnect(BluetoothDevice device, boolean value) {
        if (DBG) log("allowIncomingConnect(" + device + ":" + value + ")");
        try {
            return mService.allowIncomingConnect(device, value);
        } catch (RemoteException e) {
            Log.e(TAG, "", e);
            return false;
        }
    }

    /**
     * Helper for converting a state to a string.
     *
@@ -469,6 +384,24 @@ public final class BluetoothA2dp implements BluetoothProfile {
        }
    }

    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            if (DBG) Log.d(TAG, "Proxy object connected");
            mService = IBluetoothA2dp.Stub.asInterface(service);

            if (mServiceListener != null) {
                mServiceListener.onServiceConnected(BluetoothProfile.A2DP, BluetoothA2dp.this);
            }
        }
        public void onServiceDisconnected(ComponentName className) {
            if (DBG) Log.d(TAG, "Proxy object disconnected");
            mService = null;
            if (mServiceListener != null) {
                mServiceListener.onServiceDisconnected(BluetoothProfile.A2DP);
            }
        }
    };

    private boolean isEnabled() {
       if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
       return false;
+105 −69
Original line number Diff line number Diff line
@@ -561,25 +561,6 @@ public final class BluetoothHeadset implements BluetoothProfile {
                com.android.internal.R.bool.config_bluetooth_sco_off_call);
    }

    /**
     * Cancel the outgoing connection.
     * Note: This is an internal function and shouldn't be exposed
     *
     * @hide
     */
    public boolean cancelConnectThread() {
        if (DBG) log("cancelConnectThread");
        if (mService != null && isEnabled()) {
            try {
                return mService.cancelConnectThread();
            } catch (RemoteException e) {Log.e(TAG, e.toString());}
        } else {
            Log.w(TAG, "Proxy not attached to service");
            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
        }
        return false;
    }

    /**
     * Accept the incoming connection.
     * Note: This is an internal function and shouldn't be exposed
@@ -599,25 +580,6 @@ public final class BluetoothHeadset implements BluetoothProfile {
        return false;
    }

    /**
     * Create the connect thread for the incoming connection.
     * Note: This is an internal function and shouldn't be exposed
     *
     * @hide
     */
    public boolean createIncomingConnect(BluetoothDevice device) {
        if (DBG) log("createIncomingConnect");
        if (mService != null && isEnabled()) {
            try {
                return mService.createIncomingConnect(device);
            } catch (RemoteException e) {Log.e(TAG, e.toString());}
        } else {
            Log.w(TAG, "Proxy not attached to service");
            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
        }
        return false;
    }

    /**
     * Reject the incoming connection.
     * @hide
@@ -636,55 +598,63 @@ public final class BluetoothHeadset implements BluetoothProfile {
    }

    /**
     * Connect to a Bluetooth Headset.
     * Get the current audio state of the Headset.
     * Note: This is an internal function and shouldn't be exposed
     *
     * @hide
     */
    public boolean connectHeadsetInternal(BluetoothDevice device) {
        if (DBG) log("connectHeadsetInternal");
        if (mService != null && isEnabled()) {
    public int getAudioState(BluetoothDevice device) {
        if (DBG) log("getAudioState");
        if (mService != null && !isDisabled()) {
            try {
                return mService.connectHeadsetInternal(device);
                return mService.getAudioState(device);
            } catch (RemoteException e) {Log.e(TAG, e.toString());}
        } else {
            Log.w(TAG, "Proxy not attached to service");
            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
        }
        return false;
        return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
    }

    /**
     * Disconnect a Bluetooth Headset.
     * Note: This is an internal function and shouldn't be exposed
     * Check if Bluetooth SCO audio is connected.
     *
     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
     *
     * @return true if SCO is connected,
     *         false otherwise or on error
     * @hide
     */
    public boolean disconnectHeadsetInternal(BluetoothDevice device) {
        if (DBG) log("disconnectHeadsetInternal");
        if (mService != null && !isDisabled()) {
    public boolean isAudioOn() {
        if (DBG) log("isAudioOn()");
        if (mService != null && isEnabled()) {
            try {
                 return mService.disconnectHeadsetInternal(device);
            } catch (RemoteException e) {Log.e(TAG, e.toString());}
        } else {
            Log.w(TAG, "Proxy not attached to service");
            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
              return mService.isAudioOn();
            } catch (RemoteException e) {
              Log.e(TAG,  Log.getStackTraceString(new Throwable()));
            }
        }
        if (mService == null) Log.w(TAG, "Proxy not attached to service");
        return false;

    }

    /**
     * Set the audio state of the Headset.
     * Note: This is an internal function and shouldn't be exposed
     * Initiates a connection of headset audio.
     * It setup SCO channel with remote connected headset device.
     *
     * @return true if successful
     *         false if there was some error such as
     *               there is no connected headset
     * @hide
     */
    public boolean setAudioState(BluetoothDevice device, int state) {
        if (DBG) log("setAudioState");
        if (mService != null && !isDisabled()) {
    public boolean connectAudio() {
        if (mService != null && isEnabled()) {
            try {
                return mService.setAudioState(device, state);
            } catch (RemoteException e) {Log.e(TAG, e.toString());}
                return mService.connectAudio();
            } catch (RemoteException e) {
                Log.e(TAG, e.toString());
            }
        } else {
            Log.w(TAG, "Proxy not attached to service");
            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
@@ -693,22 +663,26 @@ public final class BluetoothHeadset implements BluetoothProfile {
    }

    /**
     * Get the current audio state of the Headset.
     * Note: This is an internal function and shouldn't be exposed
     * Initiates a disconnection of headset audio.
     * It tears down the SCO channel from remote headset device.
     *
     * @return true if successful
     *         false if there was some error such as
     *               there is no connected SCO channel
     * @hide
     */
    public int getAudioState(BluetoothDevice device) {
        if (DBG) log("getAudioState");
        if (mService != null && !isDisabled()) {
    public boolean disconnectAudio() {
        if (mService != null && isEnabled()) {
            try {
                return mService.getAudioState(device);
            } catch (RemoteException e) {Log.e(TAG, e.toString());}
                return mService.disconnectAudio();
            } catch (RemoteException e) {
                Log.e(TAG, e.toString());
            }
        } else {
            Log.w(TAG, "Proxy not attached to service");
            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
        }
        return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
        return false;
    }

    /**
@@ -760,6 +734,68 @@ public final class BluetoothHeadset implements BluetoothProfile {
        return false;
    }

    /**
     * Notify Headset of phone state change.
     * This is a backdoor for phone app to call BluetoothHeadset since
     * there is currently not a good way to get precise call state change outside
     * of phone app.
     *
     * @hide
     */
    public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
                                  int type) {
        if (mService != null && isEnabled()) {
            try {
                mService.phoneStateChanged(numActive, numHeld, callState, number, type);
            } catch (RemoteException e) {
                Log.e(TAG, e.toString());
            }
        } else {
            Log.w(TAG, "Proxy not attached to service");
            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
        }
    }

    /**
     * Notify Headset of phone roam state change.
     * This is a backdoor for phone app to call BluetoothHeadset since
     * there is currently not a good way to get roaming state change outside
     * of phone app.
     *
     * @hide
     */
    public void roamChanged(boolean roaming) {
        if (mService != null && isEnabled()) {
            try {
                mService.roamChanged(roaming);
            } catch (RemoteException e) {
                Log.e(TAG, e.toString());
            }
        } else {
            Log.w(TAG, "Proxy not attached to service");
            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
        }
    }

    /**
     * Send Headset of CLCC response
     *
     * @hide
     */
    public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
                             String number, int type) {
        if (mService != null && isEnabled()) {
            try {
                mService.clccResponse(index, direction, status, mode, mpty, number, type);
            } catch (RemoteException e) {
                Log.e(TAG, e.toString());
            }
        } else {
            Log.w(TAG, "Proxy not attached to service");
            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
        }
    }

    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            if (DBG) Log.d(TAG, "Proxy object connected");
+0 −8
Original line number Diff line number Diff line
@@ -33,12 +33,4 @@ interface IBluetoothA2dp {
    boolean setPriority(in BluetoothDevice device, int priority);
    int getPriority(in BluetoothDevice device);
    boolean isA2dpPlaying(in BluetoothDevice device);

    // Internal APIs
    boolean suspendSink(in BluetoothDevice device);
    boolean resumeSink(in BluetoothDevice device);
    boolean connectSinkInternal(in BluetoothDevice device);
    boolean disconnectSinkInternal(in BluetoothDevice device);
    boolean allowIncomingConnect(in BluetoothDevice device, boolean value);

}
+7 −5
Original line number Diff line number Diff line
@@ -40,15 +40,17 @@ interface IBluetoothHeadset {
    int getBatteryUsageHint(in BluetoothDevice device);

    // Internal functions, not be made public
    boolean createIncomingConnect(in BluetoothDevice device);
    boolean acceptIncomingConnect(in BluetoothDevice device);
    boolean rejectIncomingConnect(in BluetoothDevice device);
    boolean cancelConnectThread();
    boolean connectHeadsetInternal(in BluetoothDevice device);
    boolean disconnectHeadsetInternal(in BluetoothDevice device);
    boolean setAudioState(in BluetoothDevice device, int state);
    int getAudioState(in BluetoothDevice device);

    boolean isAudioOn();
    boolean connectAudio();
    boolean disconnectAudio();
    boolean startScoUsingVirtualVoiceCall(in BluetoothDevice device);
    boolean stopScoUsingVirtualVoiceCall(in BluetoothDevice device);
    void phoneStateChanged(int numActive, int numHeld, int callState, String number, int type);
    void roamChanged(boolean roam);
    void clccResponse(int index, int direction, int status, int mode, boolean mpty,
                      String number, int type);
}
Loading