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