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                                     doBind();
    210                                 }
    211                             } catch (Exception re) {
    212                                 Log.e(TAG,"",re);
    213                             }
    214                         }
    215                     }
    216                 }
    217         };
    218 
    219     /**
    220      * Create a BluetoothInputDevice proxy object for interacting with the local
    221      * Bluetooth Service which handles the InputDevice profile
    222      *
    223      */
    224     /*package*/ BluetoothInputDevice(Context context, ServiceListener l) {
    225         mContext = context;
    226         mServiceListener = l;
    227         mAdapter = BluetoothAdapter.getDefaultAdapter();
    228 
    229         IBluetoothManager mgr = mAdapter.getBluetoothManager();
    230         if (mgr != null) {
    231             try {
    232                 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
    233             } catch (RemoteException e) {
    234                 Log.e(TAG,"",e);
    235             }
    236         }
    237 
    238         doBind();
    239     }
    240 
    241     boolean doBind() {
    242         Intent intent = new Intent(IBluetoothInputDevice.class.getName());
    243         ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
    244         intent.setComponent(comp);
    245         if (comp == null || !mContext.bindService(intent, mConnection, 0)) {
    246             Log.e(TAG, "Could not bind to Bluetooth HID Service with " + intent);
    247             return false;
    248         }
    249         return true;
    250     }
    251 
    252     /*package*/ void close() {
    253         if (VDBG) log("close()");
    254         IBluetoothManager mgr = mAdapter.getBluetoothManager();
    255         if (mgr != null) {
    256             try {
    257                 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
    258             } catch (Exception e) {
    259                 Log.e(TAG,"",e);
    260             }
    261         }
    262 
    263         synchronized (mConnection) {
    264             if (mService != null) {
    265                 try {
    266                     mService = null;
    267                     mContext.unbindService(mConnection);
    268                 } catch (Exception re) {
    269                     Log.e(TAG,"",re);
    270                 }
    271            }
    272         }
    273         mServiceListener = null;
    274     }
    275 
    276     /**
    277      * Initiate connection to a profile of the remote bluetooth device.
    278      *
    279      * <p> The system supports connection to multiple input devices.
    280      *
    281      * <p> This API returns false in scenarios like the profile on the
    282      * device is already connected or Bluetooth is not turned on.
    283      * When this API returns true, it is guaranteed that
    284      * connection state intent for the profile will be broadcasted with
    285      * the state. Users can get the connection state of the profile
    286      * from this intent.
    287      *
    288      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
    289      * permission.
    290      *
    291      * @param device Remote Bluetooth Device
    292      * @return false on immediate error,
    293      *               true otherwise
    294      * @hide
    295      */
    296     public boolean connect(BluetoothDevice device) {
    297         if (DBG) log("connect(" + device + ")");
    298         if (mService != null && isEnabled() && isValidDevice(device)) {
    299             try {
    300                 return mService.connect(device);
    301             } catch (RemoteException e) {
    302                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    303                 return false;
    304             }
    305         }
    306         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    307         return false;
    308     }
    309 
    310     /**
    311      * Initiate disconnection from a profile
    312      *
    313      * <p> This API will return false in scenarios like the profile on the
    314      * Bluetooth device is not in connected state etc. When this API returns,
    315      * true, it is guaranteed that the connection state change
    316      * intent will be broadcasted with the state. Users can get the
    317      * disconnection state of the profile from this intent.
    318      *
    319      * <p> If the disconnection is initiated by a remote device, the state
    320      * will transition from {@link #STATE_CONNECTED} to
    321      * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
    322      * host (local) device the state will transition from
    323      * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
    324      * state {@link #STATE_DISCONNECTED}. The transition to
    325      * {@link #STATE_DISCONNECTING} can be used to distinguish between the
    326      * two scenarios.
    327      *
    328      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
    329      * permission.
    330      *
    331      * @param device Remote Bluetooth Device
    332      * @return false on immediate error,
    333      *               true otherwise
    334      * @hide
    335      */
    336     public boolean disconnect(BluetoothDevice device) {
    337         if (DBG) log("disconnect(" + device + ")");
    338         if (mService != null && isEnabled() && isValidDevice(device)) {
    339             try {
    340                 return mService.disconnect(device);
    341             } catch (RemoteException e) {
    342                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    343                 return false;
    344             }
    345         }
    346         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    347         return false;
    348     }
    349 
    350     /**
    351      * {@inheritDoc}
    352      */
    353     public List<BluetoothDevice> getConnectedDevices() {
    354         if (VDBG) log("getConnectedDevices()");
    355         if (mService != null && isEnabled()) {
    356             try {
    357                 return mService.getConnectedDevices();
    358             } catch (RemoteException e) {
    359                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    360                 return new ArrayList<BluetoothDevice>();
    361             }
    362         }
    363         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    364         return new ArrayList<BluetoothDevice>();
    365     }
    366 
    367     /**
    368      * {@inheritDoc}
    369      */
    370     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
    371         if (VDBG) log("getDevicesMatchingStates()");
    372         if (mService != null && isEnabled()) {
    373             try {
    374                 return mService.getDevicesMatchingConnectionStates(states);
    375             } catch (RemoteException e) {
    376                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    377                 return new ArrayList<BluetoothDevice>();
    378             }
    379         }
    380         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    381         return new ArrayList<BluetoothDevice>();
    382     }
    383 
    384     /**
    385      * {@inheritDoc}
    386      */
    387     public int getConnectionState(BluetoothDevice device) {
    388         if (VDBG) log("getState(" + device + ")");
    389         if (mService != null && isEnabled() && isValidDevice(device)) {
    390             try {
    391                 return mService.getConnectionState(device);
    392             } catch (RemoteException e) {
    393                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    394                 return BluetoothProfile.STATE_DISCONNECTED;
    395             }
    396         }
    397         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    398         return BluetoothProfile.STATE_DISCONNECTED;
    399     }
    400 
    401     /**
    402      * Set priority of the profile
    403      *
    404      * <p> The device should already be paired.
    405      *  Priority can be one of {@link #PRIORITY_ON} or
    406      * {@link #PRIORITY_OFF},
    407      *
    408      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
    409      * permission.
    410      *
    411      * @param device Paired bluetooth device
    412      * @param priority
    413      * @return true if priority is set, false on error
    414      * @hide
    415      */
    416     public boolean setPriority(BluetoothDevice device, int priority) {
    417         if (DBG) log("setPriority(" + device + ", " + priority + ")");
    418         if (mService != null && isEnabled() && isValidDevice(device)) {
    419             if (priority != BluetoothProfile.PRIORITY_OFF &&
    420                 priority != BluetoothProfile.PRIORITY_ON) {
    421               return false;
    422             }
    423             try {
    424                 return mService.setPriority(device, priority);
    425             } catch (RemoteException e) {
    426                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    427                 return false;
    428             }
    429         }
    430         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    431         return false;
    432     }
    433 
    434     /**
    435      * Get the priority of the profile.
    436      *
    437      * <p> The priority can be any of:
    438      * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
    439      * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
    440      *
    441      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    442      *
    443      * @param device Bluetooth device
    444      * @return priority of the device
    445      * @hide
    446      */
    447     public int getPriority(BluetoothDevice device) {
    448         if (VDBG) log("getPriority(" + device + ")");
    449         if (mService != null && isEnabled() && isValidDevice(device)) {
    450             try {
    451                 return mService.getPriority(device);
    452             } catch (RemoteException e) {
    453                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    454                 return BluetoothProfile.PRIORITY_OFF;
    455             }
    456         }
    457         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    458         return BluetoothProfile.PRIORITY_OFF;
    459     }
    460 
    461     private final ServiceConnection mConnection = new ServiceConnection() {
    462         public void onServiceConnected(ComponentName className, IBinder service) {
    463             if (DBG) Log.d(TAG, "Proxy object connected");
    464             mService = IBluetoothInputDevice.Stub.asInterface(service);
    465 
    466             if (mServiceListener != null) {
    467                 mServiceListener.onServiceConnected(BluetoothProfile.INPUT_DEVICE, BluetoothInputDevice.this);
    468             }
    469         }
    470         public void onServiceDisconnected(ComponentName className) {
    471             if (DBG) Log.d(TAG, "Proxy object disconnected");
    472             mService = null;
    473             if (mServiceListener != null) {
    474                 mServiceListener.onServiceDisconnected(BluetoothProfile.INPUT_DEVICE);
    475             }
    476         }
    477     };
    478 
    479     private boolean isEnabled() {
    480        if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
    481        return false;
    482     }
    483 
    484     private boolean isValidDevice(BluetoothDevice device) {
    485        if (device == null) return false;
    486 
    487        if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
    488        return false;
    489     }
    490 
    491 
    492     /**
    493      * Initiate virtual unplug for a HID input device.
    494      *
    495      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
    496      *
    497      * @param device Remote Bluetooth Device
    498      * @return false on immediate error,
    499      *               true otherwise
    500      * @hide
    501      */
    502     public boolean virtualUnplug(BluetoothDevice device) {
    503         if (DBG) log("virtualUnplug(" + device + ")");
    504         if (mService != null && isEnabled() && isValidDevice(device)) {
    505             try {
    506                 return mService.virtualUnplug(device);
    507             } catch (RemoteException e) {
    508                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    509                 return false;
    510             }
    511         }
    512 
    513         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    514         return false;
    515 
    516     }
    517 
    518     /**
    519     * Send Get_Protocol_Mode command to the connected HID input device.
    520     *
    521     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
    522     *
    523     * @param device Remote Bluetooth Device
    524     * @return false on immediate error,
    525     *true otherwise
    526     * @hide
    527     */
    528     public boolean getProtocolMode(BluetoothDevice device) {
    529         if (VDBG) log("getProtocolMode(" + device + ")");
    530         if (mService != null && isEnabled() && isValidDevice(device)) {
    531             try {
    532                 return mService.getProtocolMode(device);
    533             } catch (RemoteException e) {
    534                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    535                 return false;
    536             }
    537         }
    538         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    539             return false;
    540     }
    541 
    542     /**
    543      * Send Set_Protocol_Mode command to the connected HID input device.
    544      *
    545      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
    546      *
    547      * @param device Remote Bluetooth Device
    548      * @return false on immediate error,
    549      *               true otherwise
    550      * @hide
    551      */
    552     public boolean setProtocolMode(BluetoothDevice device, int protocolMode) {
    553         if (DBG) log("setProtocolMode(" + device + ")");
    554         if (mService != null && isEnabled() && isValidDevice(device)) {
    555             try {
    556                 return mService.setProtocolMode(device, protocolMode);
    557             } catch (RemoteException e) {
    558                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    559                 return false;
    560             }
    561         }
    562         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    563         return false;
    564     }
    565 
    566     /**
    567      * Send Get_Report command to the connected HID input device.
    568      *
    569      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
    570      *
    571      * @param device Remote Bluetooth Device
    572      * @param reportType Report type
    573      * @param reportId Report ID
    574      * @param bufferSize Report receiving buffer size
    575      * @return false on immediate error,
    576      *               true otherwise
    577      * @hide
    578      */
    579     public boolean getReport(BluetoothDevice device, byte reportType, byte reportId, int bufferSize) {
    580         if (VDBG) log("getReport(" + device + "), reportType=" + reportType + " reportId=" + reportId + "bufferSize=" + bufferSize);
    581         if (mService != null && isEnabled() && isValidDevice(device)) {
    582             try {
    583                 return mService.getReport(device, reportType, reportId, bufferSize);
    584             } catch (RemoteException e) {
    585                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    586                 return false;
    587             }
    588         }
    589         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    590         return false;
    591     }
    592 
    593     /**
    594      * Send Set_Report command to the connected HID input device.
    595      *
    596      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
    597      *
    598      * @param device Remote Bluetooth Device
    599      * @param reportType Report type
    600      * @param report Report receiving buffer size
    601      * @return false on immediate error,
    602      *               true otherwise
    603      * @hide
    604      */
    605     public boolean setReport(BluetoothDevice device, byte reportType, String report) {
    606         if (DBG) log("setReport(" + device + "), reportType=" + reportType + " report=" + report);
    607         if (mService != null && isEnabled() && isValidDevice(device)) {
    608             try {
    609                 return mService.setReport(device, reportType, report);
    610             } catch (RemoteException e) {
    611                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    612                 return false;
    613             }
    614         }
    615         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    616         return false;
    617     }
    618 
    619     /**
    620      * Send Send_Data command to the connected HID input device.
    621      *
    622      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
    623      *
    624      * @param device Remote Bluetooth Device
    625      * @param data Data to send
    626      * @return false on immediate error,
    627      *               true otherwise
    628      * @hide
    629      */
    630     public boolean sendData(BluetoothDevice device, String report) {
    631         if (DBG) log("sendData(" + device + "), report=" + report);
    632         if (mService != null && isEnabled() && isValidDevice(device)) {
    633             try {
    634                 return mService.sendData(device, report);
    635             } catch (RemoteException e) {
    636                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    637                 return false;
    638             }
    639         }
    640         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    641         return false;
    642     }
    643     private static void log(String msg) {
    644       Log.d(TAG, msg);
    645     }
    646 }
    647