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