BluetoothA2dp.java revision f5b4b98fada53d91c4c2ebeb5a1d33ccc95c94d2
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.bluetooth;
18
19import android.annotation.SdkConstant;
20import android.annotation.SdkConstant.SdkConstantType;
21import android.server.BluetoothA2dpService;
22import android.content.Context;
23import android.os.ServiceManager;
24import android.os.RemoteException;
25import android.os.IBinder;
26import android.util.Log;
27
28import java.util.List;
29
30/**
31 * Public API for controlling the Bluetooth A2DP Profile Service.
32 *
33 * BluetoothA2dp is a proxy object for controlling the Bluetooth A2DP
34 * Service via IPC.
35 *
36 * Creating a BluetoothA2dp object will initiate a binding with the
37 * BluetoothHeadset service. Users of this object should call close() when they
38 * are finished, so that this proxy object can unbind from the service.
39 *
40 * Currently the BluetoothA2dp service runs in the system server and this
41 * proxy object will be immediately bound to the service on construction.
42 * However this may change in future releases, and error codes such as
43 * BluetoothError.ERROR_IPC_NOT_READY will be returned from this API when the
44 * proxy object is not yet attached.
45 *
46 * Currently this class provides methods to connect to A2DP audio sinks.
47 *
48 * @hide
49 */
50public class BluetoothA2dp {
51    private static final String TAG = "BluetoothA2dp";
52    private static final boolean DBG = false;
53
54    /** int extra for SINK_STATE_CHANGED_ACTION */
55    public static final String SINK_STATE =
56        "android.bluetooth.a2dp.intent.SINK_STATE";
57    /** int extra for SINK_STATE_CHANGED_ACTION */
58    public static final String SINK_PREVIOUS_STATE =
59        "android.bluetooth.a2dp.intent.SINK_PREVIOUS_STATE";
60
61    /** Indicates the state of an A2DP audio sink has changed.
62     *  This intent will always contain SINK_STATE, SINK_PREVIOUS_STATE and
63     *  BluetoothIntent.ADDRESS extras.
64     */
65    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
66    public static final String SINK_STATE_CHANGED_ACTION =
67        "android.bluetooth.a2dp.intent.action.SINK_STATE_CHANGED";
68
69    public static final int STATE_DISCONNECTED = 0;
70    public static final int STATE_CONNECTING   = 1;
71    public static final int STATE_CONNECTED    = 2;
72    public static final int STATE_DISCONNECTING = 3;
73    /** Playing implies connected */
74    public static final int STATE_PLAYING    = 4;
75
76    /** Default priority for a2dp devices that should allow incoming
77     * connections */
78    public static final int PRIORITY_AUTO = 100;
79    /** Default priority for a2dp devices that should not allow incoming
80     * connections */
81    public static final int PRIORITY_OFF = 0;
82    private final IBluetoothA2dp mService;
83    private final Context mContext;
84
85    /**
86     * Create a BluetoothA2dp proxy object for interacting with the local
87     * Bluetooth A2DP service.
88     * @param c Context
89     */
90    public BluetoothA2dp(Context c) {
91        mContext = c;
92        IBinder b = ServiceManager.getService(BluetoothA2dpService.BLUETOOTH_A2DP_SERVICE);
93        if (b == null) {
94            throw new RuntimeException("Bluetooth A2DP service not available!");
95        }
96        mService = IBluetoothA2dp.Stub.asInterface(b);
97    }
98
99    /** Initiate a connection to an A2DP sink.
100     *  Listen for SINK_STATE_CHANGED_ACTION to find out when the
101     *  connection is completed.
102     *  @param address Remote BT address.
103     *  @return Result code, negative indicates an immediate error.
104     *  @hide
105     */
106    public int connectSink(String address) {
107        if (DBG) log("connectSink(" + address + ")");
108        try {
109            return mService.connectSink(address);
110        } catch (RemoteException e) {
111            Log.w(TAG, "", e);
112            return BluetoothError.ERROR_IPC;
113        }
114    }
115
116    /** Initiate disconnect from an A2DP sink.
117     *  Listen for SINK_STATE_CHANGED_ACTION to find out when
118     *  disconnect is completed.
119     *  @param address Remote BT address.
120     *  @return Result code, negative indicates an immediate error.
121     *  @hide
122     */
123    public int disconnectSink(String address) {
124        if (DBG) log("disconnectSink(" + address + ")");
125        try {
126            return mService.disconnectSink(address);
127        } catch (RemoteException e) {
128            Log.w(TAG, "", e);
129            return BluetoothError.ERROR_IPC;
130        }
131    }
132
133    /** Check if a specified A2DP sink is connected.
134     *  @param address Remote BT address.
135     *  @return True if connected (or playing), false otherwise and on error.
136     *  @hide
137     */
138    public boolean isSinkConnected(String address) {
139        if (DBG) log("isSinkConnected(" + address + ")");
140        int state = getSinkState(address);
141        return state == STATE_CONNECTED || state == STATE_PLAYING;
142    }
143
144    /** Check if any A2DP sink is connected.
145     * @return a List of connected A2DP sinks, or null on error.
146     * @hide
147     */
148    public List<String> listConnectedSinks() {
149        if (DBG) log("listConnectedSinks()");
150        try {
151            return mService.listConnectedSinks();
152        } catch (RemoteException e) {
153            Log.w(TAG, "", e);
154            return null;
155        }
156    }
157
158    /** Get the state of an A2DP sink
159     *  @param address Remote BT address.
160     *  @return State code, or negative on error
161     *  @hide
162     */
163    public int getSinkState(String address) {
164        if (DBG) log("getSinkState(" + address + ")");
165        try {
166            return mService.getSinkState(address);
167        } catch (RemoteException e) {
168            Log.w(TAG, "", e);
169            return BluetoothError.ERROR_IPC;
170        }
171    }
172
173    /**
174     * Set priority of a2dp sink.
175     * Priority is a non-negative integer. By default paired sinks will have
176     * a priority of PRIORITY_AUTO, and unpaired headset PRIORITY_NONE (0).
177     * Sinks with priority greater than zero will accept incoming connections
178     * (if no sink is currently connected).
179     * Priority for unpaired sink must be PRIORITY_NONE.
180     * @param address Paired sink
181     * @param priority Integer priority, for example PRIORITY_AUTO or
182     *                 PRIORITY_NONE
183     * @return Result code, negative indicates an error
184     */
185    public int setSinkPriority(String address, int priority) {
186        if (DBG) log("setSinkPriority(" + address + ", " + priority + ")");
187        try {
188            return mService.setSinkPriority(address, priority);
189        } catch (RemoteException e) {
190            Log.w(TAG, "", e);
191            return BluetoothError.ERROR_IPC;
192        }
193    }
194
195    /**
196     * Get priority of a2dp sink.
197     * @param address Sink
198     * @return non-negative priority, or negative error code on error.
199     */
200    public int getSinkPriority(String address) {
201        if (DBG) log("getSinkPriority(" + address + ")");
202        try {
203            return mService.getSinkPriority(address);
204        } catch (RemoteException e) {
205            Log.w(TAG, "", e);
206            return BluetoothError.ERROR_IPC;
207        }
208    }
209
210    /**
211     * Check class bits for possible A2DP Sink support.
212     * This is a simple heuristic that tries to guess if a device with the
213     * given class bits might be a A2DP Sink. It is not accurate for all
214     * devices. It tries to err on the side of false positives.
215     * @return True if this device might be a A2DP sink
216     */
217    public static boolean doesClassMatchSink(int btClass) {
218        if (BluetoothClass.Service.hasService(btClass, BluetoothClass.Service.RENDER)) {
219            return true;
220        }
221        // By the A2DP spec, sinks must indicate the RENDER service.
222        // However we found some that do not (Chordette). So lets also
223        // match on some other class bits.
224        switch (BluetoothClass.Device.getDevice(btClass)) {
225        case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO:
226        case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES:
227        case BluetoothClass.Device.AUDIO_VIDEO_LOUDSPEAKER:
228        case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
229            return true;
230        default:
231            return false;
232        }
233    }
234
235    /** Helper for converting a state to a string.
236     * For debug use only - strings are not internationalized.
237     * @hide
238     */
239    public static String stateToString(int state) {
240        switch (state) {
241        case STATE_DISCONNECTED:
242            return "disconnected";
243        case STATE_CONNECTING:
244            return "connecting";
245        case STATE_CONNECTED:
246            return "connected";
247        case STATE_DISCONNECTING:
248            return "disconnecting";
249        case STATE_PLAYING:
250            return "playing";
251        default:
252            return "<unknown state " + state + ">";
253        }
254    }
255
256    private static void log(String msg) {
257        Log.d(TAG, msg);
258    }
259}
260