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.SdkConstant.SdkConstantType;
     21 import android.content.ComponentName;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.ServiceConnection;
     25 import android.os.Binder;
     26 import android.os.IBinder;
     27 import android.os.RemoteException;
     28 import android.util.Log;
     29 
     30 import java.util.ArrayList;
     31 import java.util.List;
     32 
     33 /**
     34  * This class provides the APIs to control the Bluetooth Pan
     35  * Profile.
     36  *
     37  * <p>BluetoothPan is a proxy object for controlling the Bluetooth
     38  * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
     39  * the BluetoothPan proxy object.
     40  *
     41  * <p>Each method is protected with its appropriate permission.
     42  *
     43  * @hide
     44  */
     45 public final class BluetoothPan implements BluetoothProfile {
     46     private static final String TAG = "BluetoothPan";
     47     private static final boolean DBG = true;
     48     private static final boolean VDBG = false;
     49 
     50     /**
     51      * Intent used to broadcast the change in connection state of the Pan
     52      * profile.
     53      *
     54      * <p>This intent will have 4 extras:
     55      * <ul>
     56      * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
     57      * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
     58      * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
     59      * <li> {@link #EXTRA_LOCAL_ROLE} - Which local role the remote device is
     60      * bound to. </li>
     61      * </ul>
     62      *
     63      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
     64      * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
     65      * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
     66      *
     67      * <p> {@link #EXTRA_LOCAL_ROLE} can be one of {@link #LOCAL_NAP_ROLE} or
     68      * {@link #LOCAL_PANU_ROLE}
     69      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
     70      * receive.
     71      */
     72     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     73     public static final String ACTION_CONNECTION_STATE_CHANGED =
     74             "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED";
     75 
     76     /**
     77      * Extra for {@link #ACTION_CONNECTION_STATE_CHANGED} intent
     78      * The local role of the PAN profile that the remote device is bound to.
     79      * It can be one of {@link #LOCAL_NAP_ROLE} or {@link #LOCAL_PANU_ROLE}.
     80      */
     81     public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE";
     82 
     83     public static final int PAN_ROLE_NONE = 0;
     84     /**
     85      * The local device is acting as a Network Access Point.
     86      */
     87     public static final int LOCAL_NAP_ROLE = 1;
     88     public static final int REMOTE_NAP_ROLE = 1;
     89 
     90     /**
     91      * The local device is acting as a PAN User.
     92      */
     93     public static final int LOCAL_PANU_ROLE = 2;
     94     public static final int REMOTE_PANU_ROLE = 2;
     95 
     96     /**
     97      * Return codes for the connect and disconnect Bluez / Dbus calls.
     98      *
     99      * @hide
    100      */
    101     public static final int PAN_DISCONNECT_FAILED_NOT_CONNECTED = 1000;
    102 
    103     /**
    104      * @hide
    105      */
    106     public static final int PAN_CONNECT_FAILED_ALREADY_CONNECTED = 1001;
    107 
    108     /**
    109      * @hide
    110      */
    111     public static final int PAN_CONNECT_FAILED_ATTEMPT_FAILED = 1002;
    112 
    113     /**
    114      * @hide
    115      */
    116     public static final int PAN_OPERATION_GENERIC_FAILURE = 1003;
    117 
    118     /**
    119      * @hide
    120      */
    121     public static final int PAN_OPERATION_SUCCESS = 1004;
    122 
    123     private Context mContext;
    124     private ServiceListener mServiceListener;
    125     private BluetoothAdapter mAdapter;
    126     private volatile IBluetoothPan mPanService;
    127 
    128     /**
    129      * Create a BluetoothPan proxy object for interacting with the local
    130      * Bluetooth Service which handles the Pan profile
    131      */
    132     /*package*/ BluetoothPan(Context context, ServiceListener l) {
    133         mContext = context;
    134         mServiceListener = l;
    135         mAdapter = BluetoothAdapter.getDefaultAdapter();
    136         try {
    137             mAdapter.getBluetoothManager().registerStateChangeCallback(mStateChangeCallback);
    138         } catch (RemoteException re) {
    139             Log.w(TAG, "Unable to register BluetoothStateChangeCallback", re);
    140         }
    141         if (VDBG) Log.d(TAG, "BluetoothPan() call bindService");
    142         doBind();
    143     }
    144 
    145     boolean doBind() {
    146         Intent intent = new Intent(IBluetoothPan.class.getName());
    147         ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
    148         intent.setComponent(comp);
    149         if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
    150                 mContext.getUser())) {
    151             Log.e(TAG, "Could not bind to Bluetooth Pan Service with " + intent);
    152             return false;
    153         }
    154         return true;
    155     }
    156 
    157     /*package*/ void close() {
    158         if (VDBG) log("close()");
    159 
    160         IBluetoothManager mgr = mAdapter.getBluetoothManager();
    161         if (mgr != null) {
    162             try {
    163                 mgr.unregisterStateChangeCallback(mStateChangeCallback);
    164             } catch (RemoteException re) {
    165                 Log.w(TAG, "Unable to unregister BluetoothStateChangeCallback", re);
    166             }
    167         }
    168 
    169         synchronized (mConnection) {
    170             if (mPanService != null) {
    171                 try {
    172                     mPanService = null;
    173                     mContext.unbindService(mConnection);
    174                 } catch (Exception re) {
    175                     Log.e(TAG, "", re);
    176                 }
    177             }
    178         }
    179         mServiceListener = null;
    180     }
    181 
    182     protected void finalize() {
    183         close();
    184     }
    185 
    186     private final IBluetoothStateChangeCallback mStateChangeCallback =
    187             new IBluetoothStateChangeCallback.Stub() {
    188 
    189                 @Override
    190                 public void onBluetoothStateChange(boolean on) {
    191                     // Handle enable request to bind again.
    192                     Log.d(TAG, "onBluetoothStateChange on: " + on);
    193                     if (on) {
    194                         try {
    195                             if (mPanService == null) {
    196                                 if (VDBG) Log.d(TAG, "onBluetoothStateChange calling doBind()");
    197                                 doBind();
    198                             }
    199 
    200                         } catch (IllegalStateException e) {
    201                             Log.e(TAG, "onBluetoothStateChange: could not bind to PAN service: ",
    202                                     e);
    203 
    204                         } catch (SecurityException e) {
    205                             Log.e(TAG, "onBluetoothStateChange: could not bind to PAN service: ",
    206                                     e);
    207                         }
    208                     } else {
    209                         if (VDBG) Log.d(TAG, "Unbinding service...");
    210                         synchronized (mConnection) {
    211                             try {
    212                                 mPanService = null;
    213                                 mContext.unbindService(mConnection);
    214                             } catch (Exception re) {
    215                                 Log.e(TAG, "", re);
    216                             }
    217                         }
    218                     }
    219                 }
    220             };
    221 
    222     /**
    223      * Initiate connection to a profile of the remote bluetooth device.
    224      *
    225      * <p> This API returns false in scenarios like the profile on the
    226      * device is already connected or Bluetooth is not turned on.
    227      * When this API returns true, it is guaranteed that
    228      * connection state intent for the profile will be broadcasted with
    229      * the state. Users can get the connection state of the profile
    230      * from this intent.
    231      *
    232      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
    233      * permission.
    234      *
    235      * @param device Remote Bluetooth Device
    236      * @return false on immediate error, true otherwise
    237      * @hide
    238      */
    239     public boolean connect(BluetoothDevice device) {
    240         if (DBG) log("connect(" + device + ")");
    241         final IBluetoothPan service = mPanService;
    242         if (service != null && isEnabled() && isValidDevice(device)) {
    243             try {
    244                 return service.connect(device);
    245             } catch (RemoteException e) {
    246                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    247                 return false;
    248             }
    249         }
    250         if (service == null) Log.w(TAG, "Proxy not attached to service");
    251         return false;
    252     }
    253 
    254     /**
    255      * Initiate disconnection from a profile
    256      *
    257      * <p> This API will return false in scenarios like the profile on the
    258      * Bluetooth device is not in connected state etc. When this API returns,
    259      * true, it is guaranteed that the connection state change
    260      * intent will be broadcasted with the state. Users can get the
    261      * disconnection state of the profile from this intent.
    262      *
    263      * <p> If the disconnection is initiated by a remote device, the state
    264      * will transition from {@link #STATE_CONNECTED} to
    265      * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
    266      * host (local) device the state will transition from
    267      * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
    268      * state {@link #STATE_DISCONNECTED}. The transition to
    269      * {@link #STATE_DISCONNECTING} can be used to distinguish between the
    270      * two scenarios.
    271      *
    272      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
    273      * permission.
    274      *
    275      * @param device Remote Bluetooth Device
    276      * @return false on immediate error, true otherwise
    277      * @hide
    278      */
    279     public boolean disconnect(BluetoothDevice device) {
    280         if (DBG) log("disconnect(" + device + ")");
    281         final IBluetoothPan service = mPanService;
    282         if (service != null && isEnabled() && isValidDevice(device)) {
    283             try {
    284                 return service.disconnect(device);
    285             } catch (RemoteException e) {
    286                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    287                 return false;
    288             }
    289         }
    290         if (service == null) Log.w(TAG, "Proxy not attached to service");
    291         return false;
    292     }
    293 
    294     /**
    295      * {@inheritDoc}
    296      */
    297     @Override
    298     public List<BluetoothDevice> getConnectedDevices() {
    299         if (VDBG) log("getConnectedDevices()");
    300         final IBluetoothPan service = mPanService;
    301         if (service != null && isEnabled()) {
    302             try {
    303                 return service.getConnectedDevices();
    304             } catch (RemoteException e) {
    305                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    306                 return new ArrayList<BluetoothDevice>();
    307             }
    308         }
    309         if (service == null) Log.w(TAG, "Proxy not attached to service");
    310         return new ArrayList<BluetoothDevice>();
    311     }
    312 
    313     /**
    314      * {@inheritDoc}
    315      */
    316     @Override
    317     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
    318         if (VDBG) log("getDevicesMatchingStates()");
    319         final IBluetoothPan service = mPanService;
    320         if (service != null && isEnabled()) {
    321             try {
    322                 return service.getDevicesMatchingConnectionStates(states);
    323             } catch (RemoteException e) {
    324                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    325                 return new ArrayList<BluetoothDevice>();
    326             }
    327         }
    328         if (service == null) Log.w(TAG, "Proxy not attached to service");
    329         return new ArrayList<BluetoothDevice>();
    330     }
    331 
    332     /**
    333      * {@inheritDoc}
    334      */
    335     @Override
    336     public int getConnectionState(BluetoothDevice device) {
    337         if (VDBG) log("getState(" + device + ")");
    338         final IBluetoothPan service = mPanService;
    339         if (service != null && isEnabled() && isValidDevice(device)) {
    340             try {
    341                 return service.getConnectionState(device);
    342             } catch (RemoteException e) {
    343                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    344                 return BluetoothProfile.STATE_DISCONNECTED;
    345             }
    346         }
    347         if (service == null) Log.w(TAG, "Proxy not attached to service");
    348         return BluetoothProfile.STATE_DISCONNECTED;
    349     }
    350 
    351     public void setBluetoothTethering(boolean value) {
    352         if (DBG) log("setBluetoothTethering(" + value + ")");
    353         final IBluetoothPan service = mPanService;
    354         if (service != null && isEnabled()) {
    355             try {
    356                 service.setBluetoothTethering(value);
    357             } catch (RemoteException e) {
    358                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    359             }
    360         }
    361     }
    362 
    363     public boolean isTetheringOn() {
    364         if (VDBG) log("isTetheringOn()");
    365         final IBluetoothPan service = mPanService;
    366         if (service != null && isEnabled()) {
    367             try {
    368                 return service.isTetheringOn();
    369             } catch (RemoteException e) {
    370                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    371             }
    372         }
    373         return false;
    374     }
    375 
    376     private final ServiceConnection mConnection = new ServiceConnection() {
    377         public void onServiceConnected(ComponentName className, IBinder service) {
    378             if (DBG) Log.d(TAG, "BluetoothPAN Proxy object connected");
    379             mPanService = IBluetoothPan.Stub.asInterface(Binder.allowBlocking(service));
    380             if (mServiceListener != null) {
    381                 mServiceListener.onServiceConnected(BluetoothProfile.PAN,
    382                         BluetoothPan.this);
    383             }
    384         }
    385 
    386         public void onServiceDisconnected(ComponentName className) {
    387             if (DBG) Log.d(TAG, "BluetoothPAN Proxy object disconnected");
    388             mPanService = null;
    389             if (mServiceListener != null) {
    390                 mServiceListener.onServiceDisconnected(BluetoothProfile.PAN);
    391             }
    392         }
    393     };
    394 
    395     private boolean isEnabled() {
    396         return mAdapter.getState() == BluetoothAdapter.STATE_ON;
    397     }
    398 
    399     private static boolean isValidDevice(BluetoothDevice device) {
    400         return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
    401     }
    402 
    403     private static void log(String msg) {
    404         Log.d(TAG, msg);
    405     }
    406 }
    407