Home | History | Annotate | Download | only in bluetooth
      1 /*
      2  * Copyright (C) 2011 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.IBinder;
     26 import android.os.RemoteException;
     27 import android.os.ServiceManager;
     28 import android.util.Log;
     29 
     30 import java.util.ArrayList;
     31 import java.util.List;
     32 
     33 
     34 /**
     35  * This class provides the public APIs to control the Bluetooth Input
     36  * Device Profile.
     37  *
     38  *<p>BluetoothInputDevice is a proxy object for controlling the Bluetooth
     39  * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
     40  * the BluetoothInputDevice proxy object.
     41  *
     42  *<p>Each method is protected with its appropriate permission.
     43  *@hide
     44  */
     45 public final class BluetoothInputDevice implements BluetoothProfile {
     46     private static final String TAG = "BluetoothInputDevice";
     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 Input
     52      * Device profile.
     53      *
     54      * <p>This intent will have 3 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      * </ul>
     60      *
     61      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
     62      * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
     63      * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
     64      *
     65      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
     66      * receive.
     67      */
     68     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     69     public static final String ACTION_CONNECTION_STATE_CHANGED =
     70         "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";
     71 
     72     /**
     73      * @hide
     74      */
     75     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     76     public static final String ACTION_PROTOCOL_MODE_CHANGED =
     77         "android.bluetooth.input.profile.action.PROTOCOL_MODE_CHANGED";
     78 
     79 
     80     /**
     81      * @hide
     82      */
     83     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     84     public static final String ACTION_VIRTUAL_UNPLUG_STATUS =
     85         "android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS";
     86 
     87 
     88     /**
     89      * Return codes for the connect and disconnect Bluez / Dbus calls.
     90      * @hide
     91      */
     92     public static final int INPUT_DISCONNECT_FAILED_NOT_CONNECTED = 5000;
     93 
     94     /**
     95      * @hide
     96      */
     97     public static final int INPUT_CONNECT_FAILED_ALREADY_CONNECTED = 5001;
     98 
     99     /**
    100      * @hide
    101      */
    102     public static final int INPUT_CONNECT_FAILED_ATTEMPT_FAILED = 5002;
    103 
    104     /**
    105      * @hide
    106      */
    107     public static final int INPUT_OPERATION_GENERIC_FAILURE = 5003;
    108 
    109     /**
    110      * @hide
    111      */
    112     public static final int INPUT_OPERATION_SUCCESS = 5004;
    113 
    114     /**
    115      * @hide
    116      */
    117     public static final int PROTOCOL_REPORT_MODE = 0;
    118 
    119     /**
    120      * @hide
    121      */
    122     public static final int PROTOCOL_BOOT_MODE = 1;
    123 
    124     /**
    125      * @hide
    126      */
    127     public static final int PROTOCOL_UNSUPPORTED_MODE = 255;
    128 
    129     /*  int reportType, int reportType, int bufferSize */
    130     /**
    131      * @hide
    132      */
    133     public static final byte REPORT_TYPE_INPUT = 0;
    134 
    135     /**
    136      * @hide
    137      */
    138     public static final byte REPORT_TYPE_OUTPUT = 1;
    139 
    140     /**
    141      * @hide
    142      */
    143     public static final byte REPORT_TYPE_FEATURE = 2;
    144 
    145     /**
    146      * @hide
    147      */
    148     public static final int VIRTUAL_UNPLUG_STATUS_SUCCESS = 0;
    149 
    150     /**
    151      * @hide
    152      */
    153     public static final int VIRTUAL_UNPLUG_STATUS_FAIL = 1;
    154 
    155     /**
    156      * @hide
    157      */
    158     public static final String EXTRA_PROTOCOL_MODE = "android.bluetooth.BluetoothInputDevice.extra.PROTOCOL_MODE";
    159 
    160     /**
    161      * @hide
    162      */
    163     public static final String EXTRA_REPORT_TYPE = "android.bluetooth.BluetoothInputDevice.extra.REPORT_TYPE";
    164 
    165     /**
    166      * @hide
    167      */
    168     public static final String EXTRA_REPORT_ID = "android.bluetooth.BluetoothInputDevice.extra.REPORT_ID";
    169 
    170     /**
    171      * @hide
    172      */
    173     public static final String EXTRA_REPORT_BUFFER_SIZE = "android.bluetooth.BluetoothInputDevice.extra.REPORT_BUFFER_SIZE";
    174 
    175     /**
    176      * @hide
    177      */
    178     public static final String EXTRA_REPORT = "android.bluetooth.BluetoothInputDevice.extra.REPORT";
    179 
    180     /**
    181      * @hide
    182      */
    183     public static final String EXTRA_VIRTUAL_UNPLUG_STATUS = "android.bluetooth.BluetoothInputDevice.extra.VIRTUAL_UNPLUG_STATUS";
    184 
    185     private Context mContext;
    186     private ServiceListener mServiceListener;
    187     private BluetoothAdapter mAdapter;
    188     private IBluetoothInputDevice mService;
    189 
    190     final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
    191             new IBluetoothStateChangeCallback.Stub() {
    192                 public void onBluetoothStateChange(boolean up) {
    193                     if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
    194                     if (!up) {
    195                         if (VDBG) Log.d(TAG,"Unbinding service...");
    196                         synchronized (mConnection) {
    197                             try {
    198                                 mService = null;
    199                                 mContext.unbindService(mConnection);
    200                             } catch (Exception re) {
    201                                 Log.e(TAG,"",re);
    202                             }
    203                         }
    204                     } else {
    205                         synchronized (mConnection) {
    206                             try {
    207                                 if (mService == null) {
    208                                     if (VDBG) Log.d(TAG,"Binding service...");
    209                                     if (!mContext.bindService(new Intent(IBluetoothInputDevice.class.getName()), mConnection, 0)) {
    210                                         Log.e(TAG, "Could not bind to Bluetooth HID Service");
    211                                     }
    212                                 }
    213                             } catch (Exception re) {
    214                                 Log.e(TAG,"",re);
    215                             }
    216                         }
    217                     }
    218                 }
    219         };
    220 
    221     /**
    222      * Create a BluetoothInputDevice proxy object for interacting with the local
    223      * Bluetooth Service which handles the InputDevice profile
    224      *
    225      */
    226     /*package*/ BluetoothInputDevice(Context context, ServiceListener l) {
    227         mContext = context;
    228         mServiceListener = l;
    229         mAdapter = BluetoothAdapter.getDefaultAdapter();
    230 
    231         IBluetoothManager mgr = mAdapter.getBluetoothManager();
    232         if (mgr != null) {
    233             try {
    234                 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
    235             } catch (RemoteException e) {
    236                 Log.e(TAG,"",e);
    237             }
    238         }
    239 
    240         if (!context.bindService(new Intent(IBluetoothInputDevice.class.getName()),
    241                                  mConnection, 0)) {
    242             Log.e(TAG, "Could not bind to Bluetooth HID Service");
    243         }
    244     }
    245 
    246     /*package*/ void close() {
    247         if (VDBG) log("close()");
    248         IBluetoothManager mgr = mAdapter.getBluetoothManager();
    249         if (mgr != null) {
    250             try {
    251                 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
    252             } catch (Exception e) {
    253                 Log.e(TAG,"",e);
    254             }
    255         }
    256 
    257         synchronized (mConnection) {
    258             if (mService != null) {
    259                 try {
    260                     mService = null;
    261                     mContext.unbindService(mConnection);
    262                 } catch (Exception re) {
    263                     Log.e(TAG,"",re);
    264                 }
    265            }
    266         }
    267         mServiceListener = null;
    268     }
    269 
    270     /**
    271      * Initiate connection to a profile of the remote bluetooth device.
    272      *
    273      * <p> The system supports connection to multiple input devices.
    274      *
    275      * <p> This API returns false in scenarios like the profile on the
    276      * device is already connected or Bluetooth is not turned on.
    277      * When this API returns true, it is guaranteed that
    278      * connection state intent for the profile will be broadcasted with
    279      * the state. Users can get the connection state of the profile
    280      * from this intent.
    281      *
    282      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
    283      * permission.
    284      *
    285      * @param device Remote Bluetooth Device
    286      * @return false on immediate error,
    287      *               true otherwise
    288      * @hide
    289      */
    290     public boolean connect(BluetoothDevice device) {
    291         if (DBG) log("connect(" + device + ")");
    292         if (mService != null && isEnabled() && isValidDevice(device)) {
    293             try {
    294                 return mService.connect(device);
    295             } catch (RemoteException e) {
    296                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    297                 return false;
    298             }
    299         }
    300         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    301         return false;
    302     }
    303 
    304     /**
    305      * Initiate disconnection from a profile
    306      *
    307      * <p> This API will return false in scenarios like the profile on the
    308      * Bluetooth device is not in connected state etc. When this API returns,
    309      * true, it is guaranteed that the connection state change
    310      * intent will be broadcasted with the state. Users can get the
    311      * disconnection state of the profile from this intent.
    312      *
    313      * <p> If the disconnection is initiated by a remote device, the state
    314      * will transition from {@link #STATE_CONNECTED} to
    315      * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
    316      * host (local) device the state will transition from
    317      * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
    318      * state {@link #STATE_DISCONNECTED}. The transition to
    319      * {@link #STATE_DISCONNECTING} can be used to distinguish between the
    320      * two scenarios.
    321      *
    322      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
    323      * permission.
    324      *
    325      * @param device Remote Bluetooth Device
    326      * @return false on immediate error,
    327      *               true otherwise
    328      * @hide
    329      */
    330     public boolean disconnect(BluetoothDevice device) {
    331         if (DBG) log("disconnect(" + device + ")");
    332         if (mService != null && isEnabled() && isValidDevice(device)) {
    333             try {
    334                 return mService.disconnect(device);
    335             } catch (RemoteException e) {
    336                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    337                 return false;
    338             }
    339         }
    340         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    341         return false;
    342     }
    343 
    344     /**
    345      * {@inheritDoc}
    346      */
    347     public List<BluetoothDevice> getConnectedDevices() {
    348         if (VDBG) log("getConnectedDevices()");
    349         if (mService != null && isEnabled()) {
    350             try {
    351                 return mService.getConnectedDevices();
    352             } catch (RemoteException e) {
    353                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    354                 return new ArrayList<BluetoothDevice>();
    355             }
    356         }
    357         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    358         return new ArrayList<BluetoothDevice>();
    359     }
    360 
    361     /**
    362      * {@inheritDoc}
    363      */
    364     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
    365         if (VDBG) log("getDevicesMatchingStates()");
    366         if (mService != null && isEnabled()) {
    367             try {
    368                 return mService.getDevicesMatchingConnectionStates(states);
    369             } catch (RemoteException e) {
    370                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    371                 return new ArrayList<BluetoothDevice>();
    372             }
    373         }
    374         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    375         return new ArrayList<BluetoothDevice>();
    376     }
    377 
    378     /**
    379      * {@inheritDoc}
    380      */
    381     public int getConnectionState(BluetoothDevice device) {
    382         if (VDBG) log("getState(" + device + ")");
    383         if (mService != null && isEnabled() && isValidDevice(device)) {
    384             try {
    385                 return mService.getConnectionState(device);
    386             } catch (RemoteException e) {
    387                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    388                 return BluetoothProfile.STATE_DISCONNECTED;
    389             }
    390         }
    391         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    392         return BluetoothProfile.STATE_DISCONNECTED;
    393     }
    394 
    395     /**
    396      * Set priority of the profile
    397      *
    398      * <p> The device should already be paired.
    399      *  Priority can be one of {@link #PRIORITY_ON} or
    400      * {@link #PRIORITY_OFF},
    401      *
    402      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
    403      * permission.
    404      *
    405      * @param device Paired bluetooth device
    406      * @param priority
    407      * @return true if priority is set, false on error
    408      * @hide
    409      */
    410     public boolean setPriority(BluetoothDevice device, int priority) {
    411         if (DBG) log("setPriority(" + device + ", " + priority + ")");
    412         if (mService != null && isEnabled() && isValidDevice(device)) {
    413             if (priority != BluetoothProfile.PRIORITY_OFF &&
    414                 priority != BluetoothProfile.PRIORITY_ON) {
    415               return false;
    416             }
    417             try {
    418                 return mService.setPriority(device, priority);
    419             } catch (RemoteException e) {
    420                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    421                 return false;
    422             }
    423         }
    424         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    425         return false;
    426     }
    427 
    428     /**
    429      * Get the priority of the profile.
    430      *
    431      * <p> The priority can be any of:
    432      * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
    433      * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
    434      *
    435      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    436      *
    437      * @param device Bluetooth device
    438      * @return priority of the device
    439      * @hide
    440      */
    441     public int getPriority(BluetoothDevice device) {
    442         if (VDBG) log("getPriority(" + device + ")");
    443         if (mService != null && isEnabled() && isValidDevice(device)) {
    444             try {
    445                 return mService.getPriority(device);
    446             } catch (RemoteException e) {
    447                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    448                 return BluetoothProfile.PRIORITY_OFF;
    449             }
    450         }
    451         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    452         return BluetoothProfile.PRIORITY_OFF;
    453     }
    454 
    455     private ServiceConnection mConnection = new ServiceConnection() {
    456         public void onServiceConnected(ComponentName className, IBinder service) {
    457             if (DBG) Log.d(TAG, "Proxy object connected");
    458             mService = IBluetoothInputDevice.Stub.asInterface(service);
    459 
    460             if (mServiceListener != null) {
    461                 mServiceListener.onServiceConnected(BluetoothProfile.INPUT_DEVICE, BluetoothInputDevice.this);
    462             }
    463         }
    464         public void onServiceDisconnected(ComponentName className) {
    465             if (DBG) Log.d(TAG, "Proxy object disconnected");
    466             mService = null;
    467             if (mServiceListener != null) {
    468                 mServiceListener.onServiceDisconnected(BluetoothProfile.INPUT_DEVICE);
    469             }
    470         }
    471     };
    472 
    473     private boolean isEnabled() {
    474        if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
    475        return false;
    476     }
    477 
    478     private boolean isValidDevice(BluetoothDevice device) {
    479        if (device == null) return false;
    480 
    481        if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
    482        return false;
    483     }
    484 
    485 
    486     /**
    487      * Initiate virtual unplug for a HID input device.
    488      *
    489      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
    490      *
    491      * @param device Remote Bluetooth Device
    492      * @return false on immediate error,
    493      *               true otherwise
    494      * @hide
    495      */
    496     public boolean virtualUnplug(BluetoothDevice device) {
    497         if (DBG) log("virtualUnplug(" + device + ")");
    498         if (mService != null && isEnabled() && isValidDevice(device)) {
    499             try {
    500                 return mService.virtualUnplug(device);
    501             } catch (RemoteException e) {
    502                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    503                 return false;
    504             }
    505         }
    506 
    507         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    508         return false;
    509 
    510     }
    511 
    512     /**
    513     * Send Get_Protocol_Mode command to the connected HID input device.
    514     *
    515     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
    516     *
    517     * @param device Remote Bluetooth Device
    518     * @return false on immediate error,
    519     *true otherwise
    520     * @hide
    521     */
    522     public boolean getProtocolMode(BluetoothDevice device) {
    523         if (VDBG) log("getProtocolMode(" + device + ")");
    524         if (mService != null && isEnabled() && isValidDevice(device)) {
    525             try {
    526                 return mService.getProtocolMode(device);
    527             } catch (RemoteException e) {
    528                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    529                 return false;
    530             }
    531         }
    532         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    533             return false;
    534     }
    535 
    536     /**
    537      * Send Set_Protocol_Mode command to the connected HID input device.
    538      *
    539      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
    540      *
    541      * @param device Remote Bluetooth Device
    542      * @return false on immediate error,
    543      *               true otherwise
    544      * @hide
    545      */
    546     public boolean setProtocolMode(BluetoothDevice device, int protocolMode) {
    547         if (DBG) log("setProtocolMode(" + device + ")");
    548         if (mService != null && isEnabled() && isValidDevice(device)) {
    549             try {
    550                 return mService.setProtocolMode(device, protocolMode);
    551             } catch (RemoteException e) {
    552                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    553                 return false;
    554             }
    555         }
    556         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    557         return false;
    558     }
    559 
    560     /**
    561      * Send Get_Report command to the connected HID input device.
    562      *
    563      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
    564      *
    565      * @param device Remote Bluetooth Device
    566      * @param reportType Report type
    567      * @param reportId Report ID
    568      * @param bufferSize Report receiving buffer size
    569      * @return false on immediate error,
    570      *               true otherwise
    571      * @hide
    572      */
    573     public boolean getReport(BluetoothDevice device, byte reportType, byte reportId, int bufferSize) {
    574         if (VDBG) log("getReport(" + device + "), reportType=" + reportType + " reportId=" + reportId + "bufferSize=" + bufferSize);
    575         if (mService != null && isEnabled() && isValidDevice(device)) {
    576             try {
    577                 return mService.getReport(device, reportType, reportId, bufferSize);
    578             } catch (RemoteException e) {
    579                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    580                 return false;
    581             }
    582         }
    583         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    584         return false;
    585     }
    586 
    587     /**
    588      * Send Set_Report command to the connected HID input device.
    589      *
    590      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
    591      *
    592      * @param device Remote Bluetooth Device
    593      * @param reportType Report type
    594      * @param report Report receiving buffer size
    595      * @return false on immediate error,
    596      *               true otherwise
    597      * @hide
    598      */
    599     public boolean setReport(BluetoothDevice device, byte reportType, String report) {
    600         if (DBG) log("setReport(" + device + "), reportType=" + reportType + " report=" + report);
    601         if (mService != null && isEnabled() && isValidDevice(device)) {
    602             try {
    603                 return mService.setReport(device, reportType, report);
    604             } catch (RemoteException e) {
    605                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    606                 return false;
    607             }
    608         }
    609         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    610         return false;
    611     }
    612 
    613     /**
    614      * Send Send_Data command to the connected HID input device.
    615      *
    616      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
    617      *
    618      * @param device Remote Bluetooth Device
    619      * @param data Data to send
    620      * @return false on immediate error,
    621      *               true otherwise
    622      * @hide
    623      */
    624     public boolean sendData(BluetoothDevice device, String report) {
    625         if (DBG) log("sendData(" + device + "), report=" + report);
    626         if (mService != null && isEnabled() && isValidDevice(device)) {
    627             try {
    628                 return mService.sendData(device, report);
    629             } catch (RemoteException e) {
    630                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    631                 return false;
    632             }
    633         }
    634         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    635         return false;
    636     }
    637     private static void log(String msg) {
    638       Log.d(TAG, msg);
    639     }
    640 }
    641