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.Arrays;
29import java.util.Collections;
30import java.util.Set;
31import java.util.HashSet;
32
33/**
34 * Public API for controlling the Bluetooth A2DP Profile Service.
35 *
36 * BluetoothA2dp is a proxy object for controlling the Bluetooth A2DP
37 * Service via IPC.
38 *
39 * Creating a BluetoothA2dp object will initiate a binding with the
40 * BluetoothHeadset service. Users of this object should call close() when they
41 * are finished, so that this proxy object can unbind from the service.
42 *
43 * Currently the BluetoothA2dp service runs in the system server and this
44 * proxy object will be immediately bound to the service on construction.
45 *
46 * Currently this class provides methods to connect to A2DP audio sinks.
47 *
48 * @hide
49 */
50public final class BluetoothA2dp {
51    private static final String TAG = "BluetoothA2dp";
52    private static final boolean DBG = false;
53
54    /** int extra for ACTION_SINK_STATE_CHANGED */
55    public static final String EXTRA_SINK_STATE =
56        "android.bluetooth.a2dp.extra.SINK_STATE";
57    /** int extra for ACTION_SINK_STATE_CHANGED */
58    public static final String EXTRA_PREVIOUS_SINK_STATE =
59        "android.bluetooth.a2dp.extra.PREVIOUS_SINK_STATE";
60
61    /** Indicates the state of an A2DP audio sink has changed.
62     * This intent will always contain EXTRA_SINK_STATE,
63     * EXTRA_PREVIOUS_SINK_STATE and BluetoothDevice.EXTRA_DEVICE
64     * extras.
65     */
66    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
67    public static final String ACTION_SINK_STATE_CHANGED =
68        "android.bluetooth.a2dp.action.SINK_STATE_CHANGED";
69
70    public static final int STATE_DISCONNECTED = 0;
71    public static final int STATE_CONNECTING   = 1;
72    public static final int STATE_CONNECTED    = 2;
73    public static final int STATE_DISCONNECTING = 3;
74    /** Playing implies connected */
75    public static final int STATE_PLAYING    = 4;
76
77    /** Default priority for a2dp devices that we try to auto-connect
78     * and allow incoming connections */
79    public static final int PRIORITY_AUTO_CONNECT = 1000;
80    /** Default priority for a2dp devices that should allow incoming
81     * connections */
82    public static final int PRIORITY_ON = 100;
83    /** Default priority for a2dp devices that should not allow incoming
84     * connections */
85    public static final int PRIORITY_OFF = 0;
86    /** Default priority when not set or when the device is unpaired */
87    public static final int PRIORITY_UNDEFINED = -1;
88
89    private final IBluetoothA2dp mService;
90    private final Context mContext;
91
92    /**
93     * Create a BluetoothA2dp proxy object for interacting with the local
94     * Bluetooth A2DP service.
95     * @param c Context
96     */
97    public BluetoothA2dp(Context c) {
98        mContext = c;
99
100        IBinder b = ServiceManager.getService(BluetoothA2dpService.BLUETOOTH_A2DP_SERVICE);
101        if (b != null) {
102            mService = IBluetoothA2dp.Stub.asInterface(b);
103        } else {
104            Log.w(TAG, "Bluetooth A2DP service not available!");
105
106            // Instead of throwing an exception which prevents people from going
107            // into Wireless settings in the emulator. Let it crash later when it is actually used.
108            mService = null;
109        }
110    }
111
112    /** Initiate a connection to an A2DP sink.
113     *  Listen for SINK_STATE_CHANGED_ACTION to find out when the
114     *  connection is completed.
115     *  @param device Remote BT device.
116     *  @return false on immediate error, true otherwise
117     *  @hide
118     */
119    public boolean connectSink(BluetoothDevice device) {
120        if (DBG) log("connectSink(" + device + ")");
121        try {
122            return mService.connectSink(device);
123        } catch (RemoteException e) {
124            Log.e(TAG, "", e);
125            return false;
126        }
127    }
128
129    /** Initiate disconnect from an A2DP sink.
130     *  Listen for SINK_STATE_CHANGED_ACTION to find out when
131     *  disconnect is completed.
132     *  @param device Remote BT device.
133     *  @return false on immediate error, true otherwise
134     *  @hide
135     */
136    public boolean disconnectSink(BluetoothDevice device) {
137        if (DBG) log("disconnectSink(" + device + ")");
138        try {
139            return mService.disconnectSink(device);
140        } catch (RemoteException e) {
141            Log.e(TAG, "", e);
142            return false;
143        }
144    }
145
146    /** Initiate suspend from an A2DP sink.
147     *  Listen for SINK_STATE_CHANGED_ACTION to find out when
148     *  suspend is completed.
149     *  @param device Remote BT device.
150     *  @return false on immediate error, true otherwise
151     *  @hide
152     */
153    public boolean suspendSink(BluetoothDevice device) {
154        try {
155            return mService.suspendSink(device);
156        } catch (RemoteException e) {
157            Log.e(TAG, "", e);
158            return false;
159        }
160    }
161
162    /** Initiate resume from an suspended A2DP sink.
163     *  Listen for SINK_STATE_CHANGED_ACTION to find out when
164     *  resume is completed.
165     *  @param device Remote BT device.
166     *  @return false on immediate error, true otherwise
167     *  @hide
168     */
169    public boolean resumeSink(BluetoothDevice device) {
170        try {
171            return mService.resumeSink(device);
172        } catch (RemoteException e) {
173            Log.e(TAG, "", e);
174            return false;
175        }
176    }
177
178    /** Check if a specified A2DP sink is connected.
179     *  @param device Remote BT device.
180     *  @return True if connected (or playing), false otherwise and on error.
181     *  @hide
182     */
183    public boolean isSinkConnected(BluetoothDevice device) {
184        if (DBG) log("isSinkConnected(" + device + ")");
185        int state = getSinkState(device);
186        return state == STATE_CONNECTED || state == STATE_PLAYING;
187    }
188
189    /** Check if any A2DP sink is connected.
190     * @return a unmodifiable set of connected A2DP sinks, or null on error.
191     * @hide
192     */
193    public Set<BluetoothDevice> getConnectedSinks() {
194        if (DBG) log("getConnectedSinks()");
195        try {
196            return Collections.unmodifiableSet(
197                    new HashSet<BluetoothDevice>(Arrays.asList(mService.getConnectedSinks())));
198        } catch (RemoteException e) {
199            Log.e(TAG, "", e);
200            return null;
201        }
202    }
203
204    /** Check if any A2DP sink is in Non Disconnected state
205     * i.e playing, connected, connecting, disconnecting.
206     * @return a unmodifiable set of connected A2DP sinks, or null on error.
207     * @hide
208     */
209    public Set<BluetoothDevice> getNonDisconnectedSinks() {
210        if (DBG) log("getNonDisconnectedSinks()");
211        try {
212            return Collections.unmodifiableSet(
213                    new HashSet<BluetoothDevice>(Arrays.asList(mService.getNonDisconnectedSinks())));
214        } catch (RemoteException e) {
215            Log.e(TAG, "", e);
216            return null;
217        }
218    }
219
220    /** Get the state of an A2DP sink
221     *  @param device Remote BT device.
222     *  @return State code, one of STATE_
223     *  @hide
224     */
225    public int getSinkState(BluetoothDevice device) {
226        if (DBG) log("getSinkState(" + device + ")");
227        try {
228            return mService.getSinkState(device);
229        } catch (RemoteException e) {
230            Log.e(TAG, "", e);
231            return BluetoothA2dp.STATE_DISCONNECTED;
232        }
233    }
234
235    /**
236     * Set priority of a2dp sink.
237     * Priority is a non-negative integer. By default paired sinks will have
238     * a priority of PRIORITY_AUTO, and unpaired headset PRIORITY_NONE (0).
239     * Sinks with priority greater than zero will accept incoming connections
240     * (if no sink is currently connected).
241     * Priority for unpaired sink must be PRIORITY_NONE.
242     * @param device Paired sink
243     * @param priority Integer priority, for example PRIORITY_AUTO or
244     *                 PRIORITY_NONE
245     * @return true if priority is set, false on error
246     */
247    public boolean setSinkPriority(BluetoothDevice device, int priority) {
248        if (DBG) log("setSinkPriority(" + device + ", " + priority + ")");
249        try {
250            return mService.setSinkPriority(device, priority);
251        } catch (RemoteException e) {
252            Log.e(TAG, "", e);
253            return false;
254        }
255    }
256
257    /**
258     * Get priority of a2dp sink.
259     * @param device Sink
260     * @return non-negative priority, or negative error code on error.
261     */
262    public int getSinkPriority(BluetoothDevice device) {
263        if (DBG) log("getSinkPriority(" + device + ")");
264        try {
265            return mService.getSinkPriority(device);
266        } catch (RemoteException e) {
267            Log.e(TAG, "", e);
268            return PRIORITY_OFF;
269        }
270    }
271
272    /**
273     * Allow or disallow incoming connection
274     * @param device Sink
275     * @param value True / False
276     * @return Success or Failure of the binder call.
277     */
278    public boolean allowIncomingConnect(BluetoothDevice device, boolean value) {
279        if (DBG) log("allowIncomingConnect(" + device + ":" + value + ")");
280        try {
281            return mService.allowIncomingConnect(device, value);
282        } catch (RemoteException e) {
283            Log.e(TAG, "", e);
284            return false;
285        }
286    }
287
288    /** Helper for converting a state to a string.
289     * For debug use only - strings are not internationalized.
290     * @hide
291     */
292    public static String stateToString(int state) {
293        switch (state) {
294        case STATE_DISCONNECTED:
295            return "disconnected";
296        case STATE_CONNECTING:
297            return "connecting";
298        case STATE_CONNECTED:
299            return "connected";
300        case STATE_DISCONNECTING:
301            return "disconnecting";
302        case STATE_PLAYING:
303            return "playing";
304        default:
305            return "<unknown state " + state + ">";
306        }
307    }
308
309    private static void log(String msg) {
310        Log.d(TAG, msg);
311    }
312}
313