Home | History | Annotate | Download | only in bluetooth
      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