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.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.ServiceConnection;
24import android.os.IBinder;
25import android.os.RemoteException;
26import android.util.Log;
27
28import java.util.ArrayList;
29import java.util.Arrays;
30import java.util.List;
31
32/**
33 * The Android Bluetooth API is not finalized, and *will* change. Use at your
34 * own risk.
35 *
36 * Public API for controlling the Bluetooth Pbap Service. This includes
37 * Bluetooth Phone book Access profile.
38 * BluetoothPbap is a proxy object for controlling the Bluetooth Pbap
39 * Service via IPC.
40 *
41 * Creating a BluetoothPbap object will create a binding with the
42 * BluetoothPbap service. Users of this object should call close() when they
43 * are finished with the BluetoothPbap, so that this proxy object can unbind
44 * from the service.
45 *
46 * This BluetoothPbap object is not immediately bound to the
47 * BluetoothPbap service. Use the ServiceListener interface to obtain a
48 * notification when it is bound, this is especially important if you wish to
49 * immediately call methods on BluetoothPbap after construction.
50 *
51 * Android only supports one connected Bluetooth Pce at a time.
52 *
53 * @hide
54 */
55public class BluetoothPbap implements BluetoothProfile {
56
57    private static final String TAG = "BluetoothPbap";
58    private static final boolean DBG = false;
59
60    /**
61     * Intent used to broadcast the change in connection state of the PBAP
62     * profile.
63     *
64     * <p>This intent will have 3 extras:
65     * <ul>
66     * <li> {@link BluetoothProfile#EXTRA_STATE} - The current state of the profile. </li>
67     * <li> {@link BluetoothProfile#EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
68     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
69     * </ul>
70     * <p>{@link BluetoothProfile#EXTRA_STATE} or {@link BluetoothProfile#EXTRA_PREVIOUS_STATE}
71     *  can be any of {@link BluetoothProfile#STATE_DISCONNECTED},
72     *  {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_CONNECTED},
73     *  {@link BluetoothProfile#STATE_DISCONNECTING}.
74     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
75     * receive.
76     */
77    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
78    public static final String ACTION_CONNECTION_STATE_CHANGED =
79            "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
80
81    private volatile IBluetoothPbap mService;
82    private final Context mContext;
83    private ServiceListener mServiceListener;
84    private BluetoothAdapter mAdapter;
85
86    public static final int RESULT_FAILURE = 0;
87    public static final int RESULT_SUCCESS = 1;
88    /** Connection canceled before completion. */
89    public static final int RESULT_CANCELED = 2;
90
91    /**
92     * An interface for notifying Bluetooth PCE IPC clients when they have
93     * been connected to the BluetoothPbap service.
94     */
95    public interface ServiceListener {
96        /**
97         * Called to notify the client when this proxy object has been
98         * connected to the BluetoothPbap service. Clients must wait for
99         * this callback before making IPC calls on the BluetoothPbap
100         * service.
101         */
102        public void onServiceConnected(BluetoothPbap proxy);
103
104        /**
105         * Called to notify the client that this proxy object has been
106         * disconnected from the BluetoothPbap service. Clients must not
107         * make IPC calls on the BluetoothPbap service after this callback.
108         * This callback will currently only occur if the application hosting
109         * the BluetoothPbap service, but may be called more often in future.
110         */
111        public void onServiceDisconnected();
112    }
113
114    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
115            new IBluetoothStateChangeCallback.Stub() {
116                public void onBluetoothStateChange(boolean up) {
117                    log("onBluetoothStateChange: up=" + up);
118                    if (!up) {
119                        log("Unbinding service...");
120                        synchronized (mConnection) {
121                            try {
122                                mService = null;
123                                mContext.unbindService(mConnection);
124                            } catch (Exception re) {
125                                Log.e(TAG, "", re);
126                            }
127                        }
128                    } else {
129                        synchronized (mConnection) {
130                            try {
131                                if (mService == null) {
132                                    log("Binding service...");
133                                    doBind();
134                                }
135                            } catch (Exception re) {
136                                Log.e(TAG, "", re);
137                            }
138                        }
139                    }
140                }
141            };
142
143    /**
144     * Create a BluetoothPbap proxy object.
145     */
146    public BluetoothPbap(Context context, ServiceListener l) {
147        mContext = context;
148        mServiceListener = l;
149        mAdapter = BluetoothAdapter.getDefaultAdapter();
150        IBluetoothManager mgr = mAdapter.getBluetoothManager();
151        if (mgr != null) {
152            try {
153                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
154            } catch (RemoteException e) {
155                Log.e(TAG, "", e);
156            }
157        }
158        doBind();
159    }
160
161    boolean doBind() {
162        Intent intent = new Intent(IBluetoothPbap.class.getName());
163        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
164        intent.setComponent(comp);
165        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
166                mContext.getUser())) {
167            Log.e(TAG, "Could not bind to Bluetooth Pbap Service with " + intent);
168            return false;
169        }
170        return true;
171    }
172
173    protected void finalize() throws Throwable {
174        try {
175            close();
176        } finally {
177            super.finalize();
178        }
179    }
180
181    /**
182     * Close the connection to the backing service.
183     * Other public functions of BluetoothPbap will return default error
184     * results once close() has been called. Multiple invocations of close()
185     * are ok.
186     */
187    public synchronized void close() {
188        IBluetoothManager mgr = mAdapter.getBluetoothManager();
189        if (mgr != null) {
190            try {
191                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
192            } catch (Exception e) {
193                Log.e(TAG, "", e);
194            }
195        }
196
197        synchronized (mConnection) {
198            if (mService != null) {
199                try {
200                    mService = null;
201                    mContext.unbindService(mConnection);
202                } catch (Exception re) {
203                    Log.e(TAG, "", re);
204                }
205            }
206        }
207        mServiceListener = null;
208    }
209
210    /**
211     * {@inheritDoc}
212     */
213    @Override
214    public List<BluetoothDevice> getConnectedDevices() {
215        log("getConnectedDevices()");
216        final IBluetoothPbap service = mService;
217        if (service == null) {
218            Log.w(TAG, "Proxy not attached to service");
219            return new ArrayList<BluetoothDevice>();
220        }
221        try {
222            return service.getConnectedDevices();
223        } catch (RemoteException e) {
224            Log.e(TAG, e.toString());
225        }
226        return new ArrayList<BluetoothDevice>();
227    }
228
229    /**
230     * {@inheritDoc}
231     */
232    @Override
233    public int getConnectionState(BluetoothDevice device) {
234        log("getConnectionState: device=" + device);
235        final IBluetoothPbap service = mService;
236        if (service == null) {
237            Log.w(TAG, "Proxy not attached to service");
238            return BluetoothProfile.STATE_DISCONNECTED;
239        }
240        try {
241            return service.getConnectionState(device);
242        } catch (RemoteException e) {
243            Log.e(TAG, e.toString());
244        }
245        return BluetoothProfile.STATE_DISCONNECTED;
246    }
247
248    /**
249     * {@inheritDoc}
250     */
251    @Override
252    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
253        log("getDevicesMatchingConnectionStates: states=" + Arrays.toString(states));
254        final IBluetoothPbap service = mService;
255        if (service == null) {
256            Log.w(TAG, "Proxy not attached to service");
257            return new ArrayList<BluetoothDevice>();
258        }
259        try {
260            return service.getDevicesMatchingConnectionStates(states);
261        } catch (RemoteException e) {
262            Log.e(TAG, e.toString());
263        }
264        return new ArrayList<BluetoothDevice>();
265    }
266
267    /**
268     * Returns true if the specified Bluetooth device is connected (does not
269     * include connecting). Returns false if not connected, or if this proxy
270     * object is not currently connected to the Pbap service.
271     */
272    // TODO: This is currently being used by SettingsLib and internal app.
273    public boolean isConnected(BluetoothDevice device) {
274        return getConnectionState(device) == BluetoothAdapter.STATE_CONNECTED;
275    }
276
277    /**
278     * Disconnects the current Pbap client (PCE). Currently this call blocks,
279     * it may soon be made asynchronous. Returns false if this proxy object is
280     * not currently connected to the Pbap service.
281     */
282    // TODO: This is currently being used by SettingsLib and will be used in the future.
283    // TODO: Must specify target device. Implement this in the service.
284    public boolean disconnect(BluetoothDevice device) {
285        log("disconnect()");
286        final IBluetoothPbap service = mService;
287        if (service == null) {
288            Log.w(TAG, "Proxy not attached to service");
289            return false;
290        }
291        try {
292            service.disconnect(device);
293            return true;
294        } catch (RemoteException e) {
295            Log.e(TAG, e.toString());
296        }
297        return false;
298    }
299
300    private final ServiceConnection mConnection = new ServiceConnection() {
301        public void onServiceConnected(ComponentName className, IBinder service) {
302            log("Proxy object connected");
303            mService = IBluetoothPbap.Stub.asInterface(service);
304            if (mServiceListener != null) {
305                mServiceListener.onServiceConnected(BluetoothPbap.this);
306            }
307        }
308
309        public void onServiceDisconnected(ComponentName className) {
310            log("Proxy object disconnected");
311            mService = null;
312            if (mServiceListener != null) {
313                mServiceListener.onServiceDisconnected();
314            }
315        }
316    };
317
318    private static void log(String msg) {
319        if (DBG) {
320            Log.d(TAG, msg);
321        }
322    }
323}
324