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