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