Home | History | Annotate | Download | only in server
      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 /**
     18  * TODO: Move this to
     19  * java/services/com/android/server/BluetoothService.java
     20  * and make the contructor package private again.
     21  *
     22  * @hide
     23  */
     24 
     25 package android.server;
     26 
     27 import android.bluetooth.BluetoothAdapter;
     28 import android.bluetooth.BluetoothClass;
     29 import android.bluetooth.BluetoothDevice;
     30 import android.bluetooth.BluetoothHeadset;
     31 import android.bluetooth.BluetoothSocket;
     32 import android.bluetooth.BluetoothUuid;
     33 import android.bluetooth.IBluetooth;
     34 import android.bluetooth.IBluetoothCallback;
     35 import android.content.BroadcastReceiver;
     36 import android.content.ContentResolver;
     37 import android.content.Context;
     38 import android.content.Intent;
     39 import android.content.IntentFilter;
     40 import android.content.SharedPreferences;
     41 import android.os.Binder;
     42 import android.os.Handler;
     43 import android.os.IBinder;
     44 import android.os.Message;
     45 import android.os.ParcelUuid;
     46 import android.os.RemoteException;
     47 import android.os.ServiceManager;
     48 import android.os.SystemService;
     49 import android.provider.Settings;
     50 import android.util.Log;
     51 
     52 import com.android.internal.app.IBatteryStats;
     53 
     54 import java.io.BufferedInputStream;
     55 import java.io.BufferedReader;
     56 import java.io.BufferedWriter;
     57 import java.io.DataInputStream;
     58 import java.io.File;
     59 import java.io.FileDescriptor;
     60 import java.io.FileInputStream;
     61 import java.io.FileNotFoundException;
     62 import java.io.FileOutputStream;
     63 import java.io.FileWriter;
     64 import java.io.IOException;
     65 import java.io.InputStreamReader;
     66 import java.io.PrintWriter;
     67 import java.io.UnsupportedEncodingException;
     68 import java.util.ArrayList;
     69 import java.util.Arrays;
     70 import java.util.HashMap;
     71 import java.util.Iterator;
     72 import java.util.Map;
     73 
     74 public class BluetoothService extends IBluetooth.Stub {
     75     private static final String TAG = "BluetoothService";
     76     private static final boolean DBG = true;
     77 
     78     private int mNativeData;
     79     private BluetoothEventLoop mEventLoop;
     80     private boolean mIsAirplaneSensitive;
     81     private boolean mIsAirplaneToggleable;
     82     private int mBluetoothState;
     83     private boolean mRestart = false;  // need to call enable() after disable()
     84     private boolean mIsDiscovering;
     85 
     86     private BluetoothAdapter mAdapter;  // constant after init()
     87     private final BondState mBondState = new BondState();  // local cache of bondings
     88     private final IBatteryStats mBatteryStats;
     89     private final Context mContext;
     90 
     91     private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
     92     private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
     93 
     94     private static final String DOCK_ADDRESS_PATH = "/sys/class/switch/dock/bt_addr";
     95     private static final String DOCK_PIN_PATH = "/sys/class/switch/dock/bt_pin";
     96 
     97     private static final String SHARED_PREFERENCE_DOCK_ADDRESS = "dock_bluetooth_address";
     98     private static final String SHARED_PREFERENCES_NAME = "bluetooth_service_settings";
     99 
    100     private static final int MESSAGE_REGISTER_SDP_RECORDS = 1;
    101     private static final int MESSAGE_FINISH_DISABLE = 2;
    102     private static final int MESSAGE_UUID_INTENT = 3;
    103     private static final int MESSAGE_DISCOVERABLE_TIMEOUT = 4;
    104 
    105     // The timeout used to sent the UUIDs Intent
    106     // This timeout should be greater than the page timeout
    107     private static final int UUID_INTENT_DELAY = 6000;
    108 
    109     /** Always retrieve RFCOMM channel for these SDP UUIDs */
    110     private static final ParcelUuid[] RFCOMM_UUIDS = {
    111             BluetoothUuid.Handsfree,
    112             BluetoothUuid.HSP,
    113             BluetoothUuid.ObexObjectPush };
    114 
    115 
    116     private final Map<String, String> mAdapterProperties;
    117     private final HashMap<String, Map<String, String>> mDeviceProperties;
    118 
    119     private final HashMap<String, Map<ParcelUuid, Integer>> mDeviceServiceChannelCache;
    120     private final ArrayList<String> mUuidIntentTracker;
    121     private final HashMap<RemoteService, IBluetoothCallback> mUuidCallbackTracker;
    122 
    123     private final HashMap<Integer, Integer> mServiceRecordToPid;
    124 
    125     private static String mDockAddress;
    126     private String mDockPin;
    127 
    128     private static class RemoteService {
    129         public String address;
    130         public ParcelUuid uuid;
    131         public RemoteService(String address, ParcelUuid uuid) {
    132             this.address = address;
    133             this.uuid = uuid;
    134         }
    135         @Override
    136         public boolean equals(Object o) {
    137             if (o instanceof RemoteService) {
    138                 RemoteService service = (RemoteService)o;
    139                 return address.equals(service.address) && uuid.equals(service.uuid);
    140             }
    141             return false;
    142         }
    143 
    144         @Override
    145         public int hashCode() {
    146             int hash = 1;
    147             hash = hash * 31 + (address == null ? 0 : address.hashCode());
    148             hash = hash * 31 + (uuid == null ? 0 : uuid.hashCode());
    149             return hash;
    150         }
    151     }
    152 
    153     static {
    154         classInitNative();
    155     }
    156 
    157     public BluetoothService(Context context) {
    158         mContext = context;
    159 
    160         // Need to do this in place of:
    161         // mBatteryStats = BatteryStatsService.getService();
    162         // Since we can not import BatteryStatsService from here. This class really needs to be
    163         // moved to java/services/com/android/server/
    164         mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo"));
    165 
    166         initializeNativeDataNative();
    167 
    168         if (isEnabledNative() == 1) {
    169             Log.w(TAG, "Bluetooth daemons already running - runtime restart? ");
    170             disableNative();
    171         }
    172 
    173         mBluetoothState = BluetoothAdapter.STATE_OFF;
    174         mIsDiscovering = false;
    175         mAdapterProperties = new HashMap<String, String>();
    176         mDeviceProperties = new HashMap<String, Map<String,String>>();
    177 
    178         mDeviceServiceChannelCache = new HashMap<String, Map<ParcelUuid, Integer>>();
    179         mUuidIntentTracker = new ArrayList<String>();
    180         mUuidCallbackTracker = new HashMap<RemoteService, IBluetoothCallback>();
    181         mServiceRecordToPid = new HashMap<Integer, Integer>();
    182 
    183         IntentFilter filter = new IntentFilter();
    184         registerForAirplaneMode(filter);
    185 
    186         filter.addAction(Intent.ACTION_DOCK_EVENT);
    187         mContext.registerReceiver(mReceiver, filter);
    188     }
    189 
    190      public static synchronized String readDockBluetoothAddress() {
    191         if (mDockAddress != null) return mDockAddress;
    192 
    193         BufferedInputStream file = null;
    194         String dockAddress;
    195         try {
    196             file = new BufferedInputStream(new FileInputStream(DOCK_ADDRESS_PATH));
    197             byte[] address = new byte[17];
    198             file.read(address);
    199             dockAddress = new String(address);
    200             dockAddress = dockAddress.toUpperCase();
    201             if (BluetoothAdapter.checkBluetoothAddress(dockAddress)) {
    202                 mDockAddress = dockAddress;
    203                 return mDockAddress;
    204             } else {
    205                 log("CheckBluetoothAddress failed for car dock address:" + dockAddress);
    206             }
    207         } catch (FileNotFoundException e) {
    208             log("FileNotFoundException while trying to read dock address");
    209         } catch (IOException e) {
    210             log("IOException while trying to read dock address");
    211         } finally {
    212             if (file != null) {
    213                 try {
    214                     file.close();
    215                 } catch (IOException e) {
    216                     // Ignore
    217                 }
    218             }
    219         }
    220         mDockAddress = null;
    221         return null;
    222     }
    223 
    224     private synchronized boolean writeDockPin() {
    225         BufferedWriter out = null;
    226         try {
    227             out = new BufferedWriter(new FileWriter(DOCK_PIN_PATH));
    228 
    229             // Generate a random 4 digit pin between 0000 and 9999
    230             // This is not truly random but good enough for our purposes.
    231             int pin = (int) Math.floor(Math.random() * 10000);
    232 
    233             mDockPin = String.format("%04d", pin);
    234             out.write(mDockPin);
    235             return true;
    236         } catch (FileNotFoundException e) {
    237             log("FileNotFoundException while trying to write dock pairing pin");
    238         } catch (IOException e) {
    239             log("IOException while while trying to write dock pairing pin");
    240         } finally {
    241             if (out != null) {
    242                 try {
    243                     out.close();
    244                 } catch (IOException e) {
    245                     // Ignore
    246                 }
    247             }
    248         }
    249         mDockPin = null;
    250         return false;
    251     }
    252 
    253     /*package*/ synchronized String getDockPin() {
    254         return mDockPin;
    255     }
    256 
    257     public synchronized void initAfterRegistration() {
    258         mAdapter = BluetoothAdapter.getDefaultAdapter();
    259         mEventLoop = new BluetoothEventLoop(mContext, mAdapter, this);
    260     }
    261 
    262     @Override
    263     protected void finalize() throws Throwable {
    264         mContext.unregisterReceiver(mReceiver);
    265         try {
    266             cleanupNativeDataNative();
    267         } finally {
    268             super.finalize();
    269         }
    270     }
    271 
    272     public boolean isEnabled() {
    273         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
    274         return isEnabledInternal();
    275     }
    276 
    277     private boolean isEnabledInternal() {
    278         return mBluetoothState == BluetoothAdapter.STATE_ON;
    279     }
    280 
    281     public int getBluetoothState() {
    282         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
    283         return mBluetoothState;
    284     }
    285 
    286 
    287     /**
    288      * Bring down bluetooth and disable BT in settings. Returns true on success.
    289      */
    290     public boolean disable() {
    291         return disable(true);
    292     }
    293 
    294     /**
    295      * Bring down bluetooth. Returns true on success.
    296      *
    297      * @param saveSetting If true, persist the new setting
    298      */
    299     public synchronized boolean disable(boolean saveSetting) {
    300         mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
    301 
    302         switch (mBluetoothState) {
    303         case BluetoothAdapter.STATE_OFF:
    304             return true;
    305         case BluetoothAdapter.STATE_ON:
    306             break;
    307         default:
    308             return false;
    309         }
    310         if (mEnableThread != null && mEnableThread.isAlive()) {
    311             return false;
    312         }
    313         setBluetoothState(BluetoothAdapter.STATE_TURNING_OFF);
    314         mHandler.removeMessages(MESSAGE_REGISTER_SDP_RECORDS);
    315 
    316         // Allow 3 seconds for profiles to gracefully disconnect
    317         // TODO: Introduce a callback mechanism so that each profile can notify
    318         // BluetoothService when it is done shutting down
    319         mHandler.sendMessageDelayed(
    320                 mHandler.obtainMessage(MESSAGE_FINISH_DISABLE, saveSetting ? 1 : 0, 0), 3000);
    321         return true;
    322     }
    323 
    324 
    325     private synchronized void finishDisable(boolean saveSetting) {
    326         if (mBluetoothState != BluetoothAdapter.STATE_TURNING_OFF) {
    327             return;
    328         }
    329         mEventLoop.stop();
    330         tearDownNativeDataNative();
    331         disableNative();
    332 
    333         // mark in progress bondings as cancelled
    334         for (String address : mBondState.listInState(BluetoothDevice.BOND_BONDING)) {
    335             mBondState.setBondState(address, BluetoothDevice.BOND_NONE,
    336                                     BluetoothDevice.UNBOND_REASON_AUTH_CANCELED);
    337         }
    338 
    339         // update mode
    340         Intent intent = new Intent(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
    341         intent.putExtra(BluetoothAdapter.EXTRA_SCAN_MODE, BluetoothAdapter.SCAN_MODE_NONE);
    342         mContext.sendBroadcast(intent, BLUETOOTH_PERM);
    343 
    344         mIsDiscovering = false;
    345         mAdapterProperties.clear();
    346         mServiceRecordToPid.clear();
    347 
    348         if (saveSetting) {
    349             persistBluetoothOnSetting(false);
    350         }
    351 
    352         setBluetoothState(BluetoothAdapter.STATE_OFF);
    353 
    354         // Log bluetooth off to battery stats.
    355         long ident = Binder.clearCallingIdentity();
    356         try {
    357             mBatteryStats.noteBluetoothOff();
    358         } catch (RemoteException e) {
    359         } finally {
    360             Binder.restoreCallingIdentity(ident);
    361         }
    362 
    363         if (mRestart) {
    364             mRestart = false;
    365             enable();
    366         }
    367     }
    368 
    369     /** Bring up BT and persist BT on in settings */
    370     public boolean enable() {
    371         return enable(true);
    372     }
    373 
    374     /**
    375      * Enable this Bluetooth device, asynchronously.
    376      * This turns on/off the underlying hardware.
    377      *
    378      * @param saveSetting If true, persist the new state of BT in settings
    379      * @return True on success (so far)
    380      */
    381     public synchronized boolean enable(boolean saveSetting) {
    382         mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
    383                                                 "Need BLUETOOTH_ADMIN permission");
    384 
    385         // Airplane mode can prevent Bluetooth radio from being turned on.
    386         if (mIsAirplaneSensitive && isAirplaneModeOn() && !mIsAirplaneToggleable) {
    387             return false;
    388         }
    389         if (mBluetoothState != BluetoothAdapter.STATE_OFF) {
    390             return false;
    391         }
    392         if (mEnableThread != null && mEnableThread.isAlive()) {
    393             return false;
    394         }
    395         setBluetoothState(BluetoothAdapter.STATE_TURNING_ON);
    396         mEnableThread = new EnableThread(saveSetting);
    397         mEnableThread.start();
    398         return true;
    399     }
    400 
    401     /** Forcibly restart Bluetooth if it is on */
    402     /* package */ synchronized void restart() {
    403         if (mBluetoothState != BluetoothAdapter.STATE_ON) {
    404             return;
    405         }
    406         mRestart = true;
    407         if (!disable(false)) {
    408             mRestart = false;
    409         }
    410     }
    411 
    412     private synchronized void setBluetoothState(int state) {
    413         if (state == mBluetoothState) {
    414             return;
    415         }
    416 
    417         if (DBG) log("Bluetooth state " + mBluetoothState + " -> " + state);
    418 
    419         Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
    420         intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, mBluetoothState);
    421         intent.putExtra(BluetoothAdapter.EXTRA_STATE, state);
    422         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    423 
    424         mBluetoothState = state;
    425 
    426         mContext.sendBroadcast(intent, BLUETOOTH_PERM);
    427     }
    428 
    429     private final Handler mHandler = new Handler() {
    430         @Override
    431         public void handleMessage(Message msg) {
    432             switch (msg.what) {
    433             case MESSAGE_REGISTER_SDP_RECORDS:
    434                 if (!isEnabledInternal()) {
    435                     return;
    436                 }
    437                 // SystemService.start() forks sdptool to register service
    438                 // records. It can fail to register some records if it is
    439                 // forked multiple times in a row, probably because there is
    440                 // some race in sdptool or bluez when operated in parallel.
    441                 // As a workaround, delay 500ms between each fork of sdptool.
    442                 // TODO: Don't fork sdptool in order to regsiter service
    443                 // records, use a DBUS call instead.
    444                 switch (msg.arg1) {
    445                 case 1:
    446                     Log.d(TAG, "Registering hfag record");
    447                     SystemService.start("hfag");
    448                     mHandler.sendMessageDelayed(
    449                             mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS, 2, -1), 500);
    450                     break;
    451                 case 2:
    452                     Log.d(TAG, "Registering hsag record");
    453                     SystemService.start("hsag");
    454                     mHandler.sendMessageDelayed(
    455                             mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS, 3, -1), 500);
    456                     break;
    457                 case 3:
    458                     Log.d(TAG, "Registering opush record");
    459                     SystemService.start("opush");
    460                     mHandler.sendMessageDelayed(
    461                             mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS, 4, -1), 500);
    462                     break;
    463                 case 4:
    464                     Log.d(TAG, "Registering pbap record");
    465                     SystemService.start("pbap");
    466                     break;
    467                 }
    468                 break;
    469             case MESSAGE_FINISH_DISABLE:
    470                 finishDisable(msg.arg1 != 0);
    471                 break;
    472             case MESSAGE_UUID_INTENT:
    473                 String address = (String)msg.obj;
    474                 if (address != null) {
    475                     sendUuidIntent(address);
    476                     makeServiceChannelCallbacks(address);
    477                 }
    478                 break;
    479             case MESSAGE_DISCOVERABLE_TIMEOUT:
    480                 int mode = msg.arg1;
    481                 if (isEnabledInternal()) {
    482                     // TODO: Switch back to the previous scan mode
    483                     // This is ok for now, because we only use
    484                     // CONNECTABLE and CONNECTABLE_DISCOVERABLE
    485                     setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE, -1);
    486                 }
    487                 break;
    488             }
    489         }
    490     };
    491 
    492     private EnableThread mEnableThread;
    493 
    494     private class EnableThread extends Thread {
    495         private final boolean mSaveSetting;
    496         public EnableThread(boolean saveSetting) {
    497             mSaveSetting = saveSetting;
    498         }
    499         public void run() {
    500             boolean res = (enableNative() == 0);
    501             if (res) {
    502                 int retryCount = 2;
    503                 boolean running = false;
    504                 while ((retryCount-- > 0) && !running) {
    505                     mEventLoop.start();
    506                     // it may take a momement for the other thread to do its
    507                     // thing.  Check periodically for a while.
    508                     int pollCount = 5;
    509                     while ((pollCount-- > 0) && !running) {
    510                         if (mEventLoop.isEventLoopRunning()) {
    511                             running = true;
    512                             break;
    513                         }
    514                         try {
    515                             Thread.sleep(100);
    516                         } catch (InterruptedException e) {}
    517                     }
    518                 }
    519                 if (!running) {
    520                     log("bt EnableThread giving up");
    521                     res = false;
    522                     disableNative();
    523                 }
    524             }
    525 
    526 
    527             if (res) {
    528                 if (!setupNativeDataNative()) {
    529                     return;
    530                 }
    531                 if (mSaveSetting) {
    532                     persistBluetoothOnSetting(true);
    533                 }
    534                 mIsDiscovering = false;
    535                 mBondState.readAutoPairingData();
    536                 mBondState.loadBondState();
    537                 mHandler.sendMessageDelayed(
    538                         mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS, 1, -1), 3000);
    539 
    540                 // Log bluetooth on to battery stats.
    541                 long ident = Binder.clearCallingIdentity();
    542                 try {
    543                     mBatteryStats.noteBluetoothOn();
    544                 } catch (RemoteException e) {
    545                 } finally {
    546                     Binder.restoreCallingIdentity(ident);
    547                 }
    548             }
    549 
    550             mEnableThread = null;
    551 
    552             setBluetoothState(res ?
    553                               BluetoothAdapter.STATE_ON :
    554                               BluetoothAdapter.STATE_OFF);
    555 
    556             if (res) {
    557                 // Update mode
    558                 String[] propVal = {"Pairable", getProperty("Pairable")};
    559                 mEventLoop.onPropertyChanged(propVal);
    560             }
    561 
    562             if (mIsAirplaneSensitive && isAirplaneModeOn() && !mIsAirplaneToggleable) {
    563                 disable(false);
    564             }
    565 
    566         }
    567     }
    568 
    569     private void persistBluetoothOnSetting(boolean bluetoothOn) {
    570         long origCallerIdentityToken = Binder.clearCallingIdentity();
    571         Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.BLUETOOTH_ON,
    572                 bluetoothOn ? 1 : 0);
    573         Binder.restoreCallingIdentity(origCallerIdentityToken);
    574     }
    575 
    576     /* package */ BondState getBondState() {
    577         return mBondState;
    578     }
    579 
    580     /** local cache of bonding state.
    581     /* we keep our own state to track the intermediate state BONDING, which
    582     /* bluez does not track.
    583      * All addreses must be passed in upper case.
    584      */
    585     public class BondState {
    586         private final HashMap<String, Integer> mState = new HashMap<String, Integer>();
    587         private final HashMap<String, Integer> mPinAttempt = new HashMap<String, Integer>();
    588 
    589         private static final String AUTO_PAIRING_BLACKLIST =
    590             "/etc/bluetooth/auto_pairing.conf";
    591         private static final String DYNAMIC_AUTO_PAIRING_BLACKLIST =
    592             "/data/misc/bluetooth/dynamic_auto_pairing.conf";
    593         private ArrayList<String>  mAutoPairingAddressBlacklist;
    594         private ArrayList<String> mAutoPairingExactNameBlacklist;
    595         private ArrayList<String> mAutoPairingPartialNameBlacklist;
    596         // Addresses added to blacklist dynamically based on usage.
    597         private ArrayList<String> mAutoPairingDynamicAddressBlacklist;
    598 
    599 
    600         // If this is an outgoing connection, store the address.
    601         // There can be only 1 pending outgoing connection at a time,
    602         private String mPendingOutgoingBonding;
    603 
    604         private synchronized void setPendingOutgoingBonding(String address) {
    605             mPendingOutgoingBonding = address;
    606         }
    607 
    608         public synchronized String getPendingOutgoingBonding() {
    609             return mPendingOutgoingBonding;
    610         }
    611 
    612         public synchronized void loadBondState() {
    613             if (mBluetoothState != BluetoothAdapter.STATE_TURNING_ON) {
    614                 return;
    615             }
    616             String []bonds = null;
    617             String val = getPropertyInternal("Devices");
    618             if (val != null) {
    619                 bonds = val.split(",");
    620             }
    621             if (bonds == null) {
    622                 return;
    623             }
    624             mState.clear();
    625             if (DBG) log("found " + bonds.length + " bonded devices");
    626             for (String device : bonds) {
    627                 mState.put(getAddressFromObjectPath(device).toUpperCase(),
    628                         BluetoothDevice.BOND_BONDED);
    629             }
    630         }
    631 
    632         public synchronized void setBondState(String address, int state) {
    633             setBondState(address, state, 0);
    634         }
    635 
    636         /** reason is ignored unless state == BOND_NOT_BONDED */
    637         public synchronized void setBondState(String address, int state, int reason) {
    638             int oldState = getBondState(address);
    639             if (oldState == state) {
    640                 return;
    641             }
    642 
    643             // Check if this was an pending outgoing bonding.
    644             // If yes, reset the state.
    645             if (oldState == BluetoothDevice.BOND_BONDING) {
    646                 if (address.equals(mPendingOutgoingBonding)) {
    647                     mPendingOutgoingBonding = null;
    648                 }
    649             }
    650 
    651             if (DBG) log(address + " bond state " + oldState + " -> " + state + " (" +
    652                          reason + ")");
    653             Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
    654             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
    655             intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, state);
    656             intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState);
    657             if (state == BluetoothDevice.BOND_NONE) {
    658                 if (reason <= 0) {
    659                     Log.w(TAG, "setBondState() called to unbond device, but reason code is " +
    660                           "invalid. Overriding reason code with BOND_RESULT_REMOVED");
    661                     reason = BluetoothDevice.UNBOND_REASON_REMOVED;
    662                 }
    663                 intent.putExtra(BluetoothDevice.EXTRA_REASON, reason);
    664                 mState.remove(address);
    665             } else {
    666                 mState.put(address, state);
    667             }
    668 
    669             mContext.sendBroadcast(intent, BLUETOOTH_PERM);
    670         }
    671 
    672         public boolean isAutoPairingBlacklisted(String address) {
    673             if (mAutoPairingAddressBlacklist != null) {
    674                 for (String blacklistAddress : mAutoPairingAddressBlacklist) {
    675                     if (address.startsWith(blacklistAddress)) return true;
    676                 }
    677             }
    678 
    679             if (mAutoPairingDynamicAddressBlacklist != null) {
    680                 for (String blacklistAddress: mAutoPairingDynamicAddressBlacklist) {
    681                     if (address.equals(blacklistAddress)) return true;
    682                 }
    683             }
    684             String name = getRemoteName(address);
    685             if (name != null) {
    686                 if (mAutoPairingExactNameBlacklist != null) {
    687                     for (String blacklistName : mAutoPairingExactNameBlacklist) {
    688                         if (name.equals(blacklistName)) return true;
    689                     }
    690                 }
    691 
    692                 if (mAutoPairingPartialNameBlacklist != null) {
    693                     for (String blacklistName : mAutoPairingPartialNameBlacklist) {
    694                         if (name.startsWith(blacklistName)) return true;
    695                     }
    696                 }
    697             }
    698             return false;
    699         }
    700 
    701         public synchronized int getBondState(String address) {
    702             Integer state = mState.get(address);
    703             if (state == null) {
    704                 return BluetoothDevice.BOND_NONE;
    705             }
    706             return state.intValue();
    707         }
    708 
    709         /*package*/ synchronized String[] listInState(int state) {
    710             ArrayList<String> result = new ArrayList<String>(mState.size());
    711             for (Map.Entry<String, Integer> e : mState.entrySet()) {
    712                 if (e.getValue().intValue() == state) {
    713                     result.add(e.getKey());
    714                 }
    715             }
    716             return result.toArray(new String[result.size()]);
    717         }
    718 
    719         public synchronized void addAutoPairingFailure(String address) {
    720             if (mAutoPairingDynamicAddressBlacklist == null) {
    721                 mAutoPairingDynamicAddressBlacklist = new ArrayList<String>();
    722             }
    723 
    724             updateAutoPairingData(address);
    725             mAutoPairingDynamicAddressBlacklist.add(address);
    726         }
    727 
    728         public synchronized boolean isAutoPairingAttemptsInProgress(String address) {
    729             return getAttempt(address) != 0;
    730         }
    731 
    732         public synchronized void clearPinAttempts(String address) {
    733             mPinAttempt.remove(address);
    734         }
    735 
    736         public synchronized boolean hasAutoPairingFailed(String address) {
    737             if (mAutoPairingDynamicAddressBlacklist == null) return false;
    738 
    739             return mAutoPairingDynamicAddressBlacklist.contains(address);
    740         }
    741 
    742         public synchronized int getAttempt(String address) {
    743             Integer attempt = mPinAttempt.get(address);
    744             if (attempt == null) {
    745                 return 0;
    746             }
    747             return attempt.intValue();
    748         }
    749 
    750         public synchronized void attempt(String address) {
    751             Integer attempt = mPinAttempt.get(address);
    752             int newAttempt;
    753             if (attempt == null) {
    754                 newAttempt = 1;
    755             } else {
    756                 newAttempt = attempt.intValue() + 1;
    757             }
    758             mPinAttempt.put(address, new Integer(newAttempt));
    759         }
    760 
    761         private void copyAutoPairingData() {
    762             File file = null;
    763             FileInputStream in = null;
    764             FileOutputStream out = null;
    765             try {
    766                 file = new File(DYNAMIC_AUTO_PAIRING_BLACKLIST);
    767                 if (file.exists()) return;
    768 
    769                 in = new FileInputStream(AUTO_PAIRING_BLACKLIST);
    770                 out= new FileOutputStream(DYNAMIC_AUTO_PAIRING_BLACKLIST);
    771 
    772                 byte[] buf = new byte[1024];
    773                 int len;
    774                 while ((len = in.read(buf)) > 0) {
    775                     out.write(buf, 0, len);
    776                 }
    777             } catch (FileNotFoundException e) {
    778                 log("FileNotFoundException: in copyAutoPairingData");
    779             } catch (IOException e) {
    780                 log("IOException: in copyAutoPairingData");
    781             } finally {
    782                  try {
    783                      if (in != null) in.close();
    784                      if (out != null) out.close();
    785                  } catch (IOException e) {}
    786             }
    787         }
    788 
    789         public void readAutoPairingData() {
    790             if (mAutoPairingAddressBlacklist != null) return;
    791             copyAutoPairingData();
    792             FileInputStream fstream = null;
    793             try {
    794                 fstream = new FileInputStream(DYNAMIC_AUTO_PAIRING_BLACKLIST);
    795                 DataInputStream in = new DataInputStream(fstream);
    796                 BufferedReader file = new BufferedReader(new InputStreamReader(in));
    797                 String line;
    798                 while((line = file.readLine()) != null) {
    799                     line = line.trim();
    800                     if (line.length() == 0 || line.startsWith("//")) continue;
    801                     String[] value = line.split("=");
    802                     if (value != null && value.length == 2) {
    803                         String[] val = value[1].split(",");
    804                         if (value[0].equalsIgnoreCase("AddressBlacklist")) {
    805                             mAutoPairingAddressBlacklist =
    806                                 new ArrayList<String>(Arrays.asList(val));
    807                         } else if (value[0].equalsIgnoreCase("ExactNameBlacklist")) {
    808                             mAutoPairingExactNameBlacklist =
    809                                 new ArrayList<String>(Arrays.asList(val));
    810                         } else if (value[0].equalsIgnoreCase("PartialNameBlacklist")) {
    811                             mAutoPairingPartialNameBlacklist =
    812                                 new ArrayList<String>(Arrays.asList(val));
    813                         } else if (value[0].equalsIgnoreCase("DynamicAddressBlacklist")) {
    814                             mAutoPairingDynamicAddressBlacklist =
    815                                 new ArrayList<String>(Arrays.asList(val));
    816                         } else {
    817                             Log.e(TAG, "Error parsing Auto pairing blacklist file");
    818                         }
    819                     }
    820                 }
    821             } catch (FileNotFoundException e) {
    822                 log("FileNotFoundException: readAutoPairingData" + e.toString());
    823             } catch (IOException e) {
    824                 log("IOException: readAutoPairingData" + e.toString());
    825             } finally {
    826                 if (fstream != null) {
    827                     try {
    828                         fstream.close();
    829                     } catch (IOException e) {
    830                         // Ignore
    831                     }
    832                 }
    833             }
    834         }
    835 
    836         // This function adds a bluetooth address to the auto pairing blacklis
    837         // file. These addresses are added to DynamicAddressBlacklistSection
    838         private void updateAutoPairingData(String address) {
    839             BufferedWriter out = null;
    840             try {
    841                 out = new BufferedWriter(new FileWriter(DYNAMIC_AUTO_PAIRING_BLACKLIST, true));
    842                 StringBuilder str = new StringBuilder();
    843                 if (mAutoPairingDynamicAddressBlacklist.size() == 0) {
    844                     str.append("DynamicAddressBlacklist=");
    845                 }
    846                 str.append(address);
    847                 str.append(",");
    848                 out.write(str.toString());
    849             } catch (FileNotFoundException e) {
    850                 log("FileNotFoundException: updateAutoPairingData" + e.toString());
    851             } catch (IOException e) {
    852                 log("IOException: updateAutoPairingData" + e.toString());
    853             } finally {
    854                 if (out != null) {
    855                     try {
    856                         out.close();
    857                     } catch (IOException e) {
    858                         // Ignore
    859                     }
    860                 }
    861             }
    862         }
    863     }
    864 
    865     private static String toBondStateString(int bondState) {
    866         switch (bondState) {
    867         case BluetoothDevice.BOND_NONE:
    868             return "not bonded";
    869         case BluetoothDevice.BOND_BONDING:
    870             return "bonding";
    871         case BluetoothDevice.BOND_BONDED:
    872             return "bonded";
    873         default:
    874             return "??????";
    875         }
    876     }
    877 
    878     /*package*/ synchronized boolean isAdapterPropertiesEmpty() {
    879         return mAdapterProperties.isEmpty();
    880     }
    881 
    882     /*package*/synchronized void getAllProperties() {
    883 
    884         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
    885         mAdapterProperties.clear();
    886 
    887         String properties[] = (String [])getAdapterPropertiesNative();
    888         // The String Array consists of key-value pairs.
    889         if (properties == null) {
    890             Log.e(TAG, "*Error*: GetAdapterProperties returned NULL");
    891             return;
    892         }
    893 
    894         for (int i = 0; i < properties.length; i++) {
    895             String name = properties[i];
    896             String newValue = null;
    897             int len;
    898             if (name == null) {
    899                 Log.e(TAG, "Error:Adapter Property at index" + i + "is null");
    900                 continue;
    901             }
    902             if (name.equals("Devices")) {
    903                 StringBuilder str = new StringBuilder();
    904                 len = Integer.valueOf(properties[++i]);
    905                 for (int j = 0; j < len; j++) {
    906                     str.append(properties[++i]);
    907                     str.append(",");
    908                 }
    909                 if (len > 0) {
    910                     newValue = str.toString();
    911                 }
    912             } else {
    913                 newValue = properties[++i];
    914             }
    915             mAdapterProperties.put(name, newValue);
    916         }
    917 
    918         // Add adapter object path property.
    919         String adapterPath = getAdapterPathNative();
    920         if (adapterPath != null)
    921             mAdapterProperties.put("ObjectPath", adapterPath + "/dev_");
    922     }
    923 
    924     /* package */ synchronized void setProperty(String name, String value) {
    925         mAdapterProperties.put(name, value);
    926     }
    927 
    928     public synchronized boolean setName(String name) {
    929         mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
    930                                                 "Need BLUETOOTH_ADMIN permission");
    931         if (name == null) {
    932             return false;
    933         }
    934         return setPropertyString("Name", name);
    935     }
    936 
    937     //TODO(): setPropertyString, setPropertyInteger, setPropertyBoolean
    938     // Either have a single property function with Object as the parameter
    939     // or have a function for each property and then obfuscate in the JNI layer.
    940     // The following looks dirty.
    941     private boolean setPropertyString(String key, String value) {
    942         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
    943         if (!isEnabledInternal()) return false;
    944         return setAdapterPropertyStringNative(key, value);
    945     }
    946 
    947     private boolean setPropertyInteger(String key, int value) {
    948         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
    949         if (!isEnabledInternal()) return false;
    950         return setAdapterPropertyIntegerNative(key, value);
    951     }
    952 
    953     private boolean setPropertyBoolean(String key, boolean value) {
    954         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
    955         if (!isEnabledInternal()) return false;
    956         return setAdapterPropertyBooleanNative(key, value ? 1 : 0);
    957     }
    958 
    959     /**
    960      * Set the discoverability window for the device.  A timeout of zero
    961      * makes the device permanently discoverable (if the device is
    962      * discoverable).  Setting the timeout to a nonzero value does not make
    963      * a device discoverable; you need to call setMode() to make the device
    964      * explicitly discoverable.
    965      *
    966      * @param timeout_s The discoverable timeout in seconds.
    967      */
    968     public synchronized boolean setDiscoverableTimeout(int timeout) {
    969         mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
    970                                                 "Need BLUETOOTH_ADMIN permission");
    971         return setPropertyInteger("DiscoverableTimeout", timeout);
    972     }
    973 
    974     public synchronized boolean setScanMode(int mode, int duration) {
    975         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS,
    976                                                 "Need WRITE_SECURE_SETTINGS permission");
    977         boolean pairable = false;
    978         boolean discoverable = false;
    979 
    980         switch (mode) {
    981         case BluetoothAdapter.SCAN_MODE_NONE:
    982             mHandler.removeMessages(MESSAGE_DISCOVERABLE_TIMEOUT);
    983             pairable = false;
    984             discoverable = false;
    985             break;
    986         case BluetoothAdapter.SCAN_MODE_CONNECTABLE:
    987             mHandler.removeMessages(MESSAGE_DISCOVERABLE_TIMEOUT);
    988             pairable = true;
    989             discoverable = false;
    990             break;
    991         case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
    992             mHandler.removeMessages(MESSAGE_DISCOVERABLE_TIMEOUT);
    993             pairable = true;
    994             discoverable = true;
    995             Message msg = mHandler.obtainMessage(MESSAGE_DISCOVERABLE_TIMEOUT);
    996             mHandler.sendMessageDelayed(msg, duration * 1000);
    997             if (DBG) Log.d(TAG, "BT Discoverable for " + duration + " seconds");
    998             break;
    999         default:
   1000             Log.w(TAG, "Requested invalid scan mode " + mode);
   1001             return false;
   1002         }
   1003         setPropertyBoolean("Pairable", pairable);
   1004         setPropertyBoolean("Discoverable", discoverable);
   1005 
   1006         return true;
   1007     }
   1008 
   1009     /*package*/ synchronized String getProperty(String name) {
   1010         if (!isEnabledInternal()) return null;
   1011         return getPropertyInternal(name);
   1012     }
   1013 
   1014     /*package*/ synchronized String getPropertyInternal(String name) {
   1015         if (!mAdapterProperties.isEmpty())
   1016             return mAdapterProperties.get(name);
   1017         getAllProperties();
   1018         return mAdapterProperties.get(name);
   1019     }
   1020 
   1021     public synchronized String getAddress() {
   1022         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
   1023         return getProperty("Address");
   1024     }
   1025 
   1026     public synchronized String getName() {
   1027         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
   1028         return getProperty("Name");
   1029     }
   1030 
   1031     /**
   1032      * Returns the user-friendly name of a remote device.  This value is
   1033      * returned from our local cache, which is updated when onPropertyChange
   1034      * event is received.
   1035      * Do not expect to retrieve the updated remote name immediately after
   1036      * changing the name on the remote device.
   1037      *
   1038      * @param address Bluetooth address of remote device.
   1039      *
   1040      * @return The user-friendly name of the specified remote device.
   1041      */
   1042     public synchronized String getRemoteName(String address) {
   1043         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
   1044         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
   1045             return null;
   1046         }
   1047         return getRemoteDeviceProperty(address, "Name");
   1048     }
   1049 
   1050     /**
   1051      * Get the discoverability window for the device.  A timeout of zero
   1052      * means that the device is permanently discoverable (if the device is
   1053      * in the discoverable mode).
   1054      *
   1055      * @return The discoverability window of the device, in seconds.  A negative
   1056      *         value indicates an error.
   1057      */
   1058     public synchronized int getDiscoverableTimeout() {
   1059         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
   1060         String timeout = getProperty("DiscoverableTimeout");
   1061         if (timeout != null)
   1062            return Integer.valueOf(timeout);
   1063         else
   1064             return -1;
   1065     }
   1066 
   1067     public synchronized int getScanMode() {
   1068         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
   1069         if (!isEnabledInternal())
   1070             return BluetoothAdapter.SCAN_MODE_NONE;
   1071 
   1072         boolean pairable = getProperty("Pairable").equals("true");
   1073         boolean discoverable = getProperty("Discoverable").equals("true");
   1074         return bluezStringToScanMode (pairable, discoverable);
   1075     }
   1076 
   1077     public synchronized boolean startDiscovery() {
   1078         mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
   1079                                                 "Need BLUETOOTH_ADMIN permission");
   1080         if (!isEnabledInternal()) return false;
   1081 
   1082         return startDiscoveryNative();
   1083     }
   1084 
   1085     public synchronized boolean cancelDiscovery() {
   1086         mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
   1087                                                 "Need BLUETOOTH_ADMIN permission");
   1088         if (!isEnabledInternal()) return false;
   1089 
   1090         return stopDiscoveryNative();
   1091     }
   1092 
   1093     public synchronized boolean isDiscovering() {
   1094         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
   1095         return mIsDiscovering;
   1096     }
   1097 
   1098     /* package */ void setIsDiscovering(boolean isDiscovering) {
   1099         mIsDiscovering = isDiscovering;
   1100     }
   1101 
   1102     public synchronized boolean createBond(String address) {
   1103         mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
   1104                                                 "Need BLUETOOTH_ADMIN permission");
   1105         if (!isEnabledInternal()) return false;
   1106 
   1107         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
   1108             return false;
   1109         }
   1110         address = address.toUpperCase();
   1111 
   1112         if (mBondState.getPendingOutgoingBonding() != null) {
   1113             log("Ignoring createBond(): another device is bonding");
   1114             // a different device is currently bonding, fail
   1115             return false;
   1116         }
   1117 
   1118         // Check for bond state only if we are not performing auto
   1119         // pairing exponential back-off attempts.
   1120         if (!mBondState.isAutoPairingAttemptsInProgress(address) &&
   1121                 mBondState.getBondState(address) != BluetoothDevice.BOND_NONE) {
   1122             log("Ignoring createBond(): this device is already bonding or bonded");
   1123             return false;
   1124         }
   1125 
   1126         if (address.equals(mDockAddress)) {
   1127             if (!writeDockPin()) {
   1128                 log("Error while writing Pin for the dock");
   1129                 return false;
   1130             }
   1131         }
   1132 
   1133         if (!createPairedDeviceNative(address, 60000 /* 1 minute */)) {
   1134             return false;
   1135         }
   1136 
   1137         mBondState.setPendingOutgoingBonding(address);
   1138         mBondState.setBondState(address, BluetoothDevice.BOND_BONDING);
   1139 
   1140         return true;
   1141     }
   1142 
   1143     public synchronized boolean cancelBondProcess(String address) {
   1144         mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
   1145                                                 "Need BLUETOOTH_ADMIN permission");
   1146         if (!isEnabledInternal()) return false;
   1147 
   1148         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
   1149             return false;
   1150         }
   1151         address = address.toUpperCase();
   1152         if (mBondState.getBondState(address) != BluetoothDevice.BOND_BONDING) {
   1153             return false;
   1154         }
   1155 
   1156         mBondState.setBondState(address, BluetoothDevice.BOND_NONE,
   1157                                 BluetoothDevice.UNBOND_REASON_AUTH_CANCELED);
   1158         cancelDeviceCreationNative(address);
   1159         return true;
   1160     }
   1161 
   1162     public synchronized boolean removeBond(String address) {
   1163         mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
   1164                                                 "Need BLUETOOTH_ADMIN permission");
   1165         if (!isEnabledInternal()) return false;
   1166 
   1167         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
   1168             return false;
   1169         }
   1170         return removeDeviceNative(getObjectPathFromAddress(address));
   1171     }
   1172 
   1173     public synchronized String[] listBonds() {
   1174         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
   1175         return mBondState.listInState(BluetoothDevice.BOND_BONDED);
   1176     }
   1177 
   1178     public synchronized int getBondState(String address) {
   1179         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
   1180         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
   1181             return BluetoothDevice.ERROR;
   1182         }
   1183         return mBondState.getBondState(address.toUpperCase());
   1184     }
   1185 
   1186     public synchronized boolean isBluetoothDock(String address) {
   1187         SharedPreferences sp = mContext.getSharedPreferences(SHARED_PREFERENCES_NAME,
   1188                 mContext.MODE_PRIVATE);
   1189 
   1190         return sp.contains(SHARED_PREFERENCE_DOCK_ADDRESS + address);
   1191     }
   1192 
   1193     /*package*/ boolean isRemoteDeviceInCache(String address) {
   1194         return (mDeviceProperties.get(address) != null);
   1195     }
   1196 
   1197     /*package*/ String[] getRemoteDeviceProperties(String address) {
   1198         if (!isEnabledInternal()) return null;
   1199 
   1200         String objectPath = getObjectPathFromAddress(address);
   1201         return (String [])getDevicePropertiesNative(objectPath);
   1202     }
   1203 
   1204     /*package*/ synchronized String getRemoteDeviceProperty(String address, String property) {
   1205         Map<String, String> properties = mDeviceProperties.get(address);
   1206         if (properties != null) {
   1207             return properties.get(property);
   1208         } else {
   1209             // Query for remote device properties, again.
   1210             // We will need to reload the cache when we switch Bluetooth on / off
   1211             // or if we crash.
   1212             if (updateRemoteDevicePropertiesCache(address))
   1213                 return getRemoteDeviceProperty(address, property);
   1214         }
   1215         Log.e(TAG, "getRemoteDeviceProperty: " + property + "not present:" + address);
   1216         return null;
   1217     }
   1218 
   1219     /* package */ synchronized boolean updateRemoteDevicePropertiesCache(String address) {
   1220         String[] propValues = getRemoteDeviceProperties(address);
   1221         if (propValues != null) {
   1222             addRemoteDeviceProperties(address, propValues);
   1223             return true;
   1224         }
   1225         return false;
   1226     }
   1227 
   1228     /* package */ synchronized void addRemoteDeviceProperties(String address, String[] properties) {
   1229         /*
   1230          * We get a DeviceFound signal every time RSSI changes or name changes.
   1231          * Don't create a new Map object every time */
   1232         Map<String, String> propertyValues = mDeviceProperties.get(address);
   1233         if (propertyValues == null) {
   1234             propertyValues = new HashMap<String, String>();
   1235         }
   1236 
   1237         for (int i = 0; i < properties.length; i++) {
   1238             String name = properties[i];
   1239             String newValue = null;
   1240             int len;
   1241             if (name == null) {
   1242                 Log.e(TAG, "Error: Remote Device Property at index" + i + "is null");
   1243                 continue;
   1244             }
   1245             if (name.equals("UUIDs") || name.equals("Nodes")) {
   1246                 StringBuilder str = new StringBuilder();
   1247                 len = Integer.valueOf(properties[++i]);
   1248                 for (int j = 0; j < len; j++) {
   1249                     str.append(properties[++i]);
   1250                     str.append(",");
   1251                 }
   1252                 if (len > 0) {
   1253                     newValue = str.toString();
   1254                 }
   1255             } else {
   1256                 newValue = properties[++i];
   1257             }
   1258 
   1259             propertyValues.put(name, newValue);
   1260         }
   1261         mDeviceProperties.put(address, propertyValues);
   1262 
   1263         // We have added a new remote device or updated its properties.
   1264         // Also update the serviceChannel cache.
   1265         updateDeviceServiceChannelCache(address);
   1266     }
   1267 
   1268     /* package */ void removeRemoteDeviceProperties(String address) {
   1269         mDeviceProperties.remove(address);
   1270     }
   1271 
   1272     /* package */ synchronized void setRemoteDeviceProperty(String address, String name,
   1273                                                               String value) {
   1274         Map <String, String> propVal = mDeviceProperties.get(address);
   1275         if (propVal != null) {
   1276             propVal.put(name, value);
   1277             mDeviceProperties.put(address, propVal);
   1278         } else {
   1279             Log.e(TAG, "setRemoteDeviceProperty for a device not in cache:" + address);
   1280         }
   1281     }
   1282 
   1283     /**
   1284      * Sets the remote device trust state.
   1285      *
   1286      * @return boolean to indicate operation success or fail
   1287      */
   1288     public synchronized boolean setTrust(String address, boolean value) {
   1289         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
   1290             mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
   1291                     "Need BLUETOOTH_ADMIN permission");
   1292             return false;
   1293         }
   1294 
   1295         if (!isEnabledInternal()) return false;
   1296 
   1297         return setDevicePropertyBooleanNative(getObjectPathFromAddress(address), "Trusted",
   1298                 value ? 1 : 0);
   1299     }
   1300 
   1301     /**
   1302      * Gets the remote device trust state as boolean.
   1303      * Note: this value may be
   1304      * retrieved from cache if we retrieved the data before *
   1305      *
   1306      * @return boolean to indicate trust or untrust state
   1307      */
   1308     public synchronized boolean getTrustState(String address) {
   1309         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
   1310             mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
   1311             return false;
   1312         }
   1313 
   1314         String val = getRemoteDeviceProperty(address, "Trusted");
   1315         if (val == null) {
   1316             return false;
   1317         } else {
   1318             return val.equals("true") ? true : false;
   1319         }
   1320     }
   1321 
   1322     /**
   1323      * Gets the remote major, minor classes encoded as a 32-bit
   1324      * integer.
   1325      *
   1326      * Note: this value is retrieved from cache, because we get it during
   1327      *       remote-device discovery.
   1328      *
   1329      * @return 32-bit integer encoding the remote major, minor, and service
   1330      *         classes.
   1331      */
   1332     public synchronized int getRemoteClass(String address) {
   1333         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
   1334             mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
   1335             return BluetoothClass.ERROR;
   1336         }
   1337         String val = getRemoteDeviceProperty(address, "Class");
   1338         if (val == null)
   1339             return BluetoothClass.ERROR;
   1340         else {
   1341             return Integer.valueOf(val);
   1342         }
   1343     }
   1344 
   1345 
   1346     /**
   1347      * Gets the UUIDs supported by the remote device
   1348      *
   1349      * @return array of 128bit ParcelUuids
   1350      */
   1351     public synchronized ParcelUuid[] getRemoteUuids(String address) {
   1352         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
   1353         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
   1354             return null;
   1355         }
   1356         return getUuidFromCache(address);
   1357     }
   1358 
   1359     private ParcelUuid[] getUuidFromCache(String address) {
   1360         String value = getRemoteDeviceProperty(address, "UUIDs");
   1361         if (value == null) return null;
   1362 
   1363         String[] uuidStrings = null;
   1364         // The UUIDs are stored as a "," separated string.
   1365         uuidStrings = value.split(",");
   1366         ParcelUuid[] uuids = new ParcelUuid[uuidStrings.length];
   1367 
   1368         for (int i = 0; i < uuidStrings.length; i++) {
   1369             uuids[i] = ParcelUuid.fromString(uuidStrings[i]);
   1370         }
   1371         return uuids;
   1372     }
   1373 
   1374     /**
   1375      * Connect and fetch new UUID's using SDP.
   1376      * The UUID's found are broadcast as intents.
   1377      * Optionally takes a uuid and callback to fetch the RFCOMM channel for the
   1378      * a given uuid.
   1379      * TODO: Don't wait UUID_INTENT_DELAY to broadcast UUID intents on success
   1380      * TODO: Don't wait UUID_INTENT_DELAY to handle the failure case for
   1381      * callback and broadcast intents.
   1382      */
   1383     public synchronized boolean fetchRemoteUuids(String address, ParcelUuid uuid,
   1384             IBluetoothCallback callback) {
   1385         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
   1386         if (!isEnabledInternal()) return false;
   1387 
   1388         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
   1389             return false;
   1390         }
   1391 
   1392         RemoteService service = new RemoteService(address, uuid);
   1393         if (uuid != null && mUuidCallbackTracker.get(service) != null) {
   1394             // An SDP query for this address & uuid is already in progress
   1395             // Do not add this callback for the uuid
   1396             return false;
   1397         }
   1398 
   1399         if (mUuidIntentTracker.contains(address)) {
   1400             // An SDP query for this address is already in progress
   1401             // Add this uuid onto the in-progress SDP query
   1402             if (uuid != null) {
   1403                 mUuidCallbackTracker.put(new RemoteService(address, uuid), callback);
   1404             }
   1405             return true;
   1406         }
   1407 
   1408         boolean ret;
   1409         // Just do the SDP if the device is already  created and UUIDs are not
   1410         // NULL, else create the device and then do SDP.
   1411         if (isRemoteDeviceInCache(address) && getRemoteUuids(address) != null) {
   1412             String path = getObjectPathFromAddress(address);
   1413             if (path == null) return false;
   1414 
   1415             // Use an empty string for the UUID pattern
   1416             ret = discoverServicesNative(path, "");
   1417         } else {
   1418             ret = createDeviceNative(address);
   1419         }
   1420 
   1421         mUuidIntentTracker.add(address);
   1422         if (uuid != null) {
   1423             mUuidCallbackTracker.put(new RemoteService(address, uuid), callback);
   1424         }
   1425 
   1426         Message message = mHandler.obtainMessage(MESSAGE_UUID_INTENT);
   1427         message.obj = address;
   1428         mHandler.sendMessageDelayed(message, UUID_INTENT_DELAY);
   1429         return ret;
   1430     }
   1431 
   1432     /**
   1433      * Gets the rfcomm channel associated with the UUID.
   1434      * Pulls records from the cache only.
   1435      *
   1436      * @param address Address of the remote device
   1437      * @param uuid ParcelUuid of the service attribute
   1438      *
   1439      * @return rfcomm channel associated with the service attribute
   1440      *         -1 on error
   1441      */
   1442     public int getRemoteServiceChannel(String address, ParcelUuid uuid) {
   1443         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
   1444         if (!isEnabledInternal()) return -1;
   1445 
   1446         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
   1447             return BluetoothDevice.ERROR;
   1448         }
   1449         // Check if we are recovering from a crash.
   1450         if (mDeviceProperties.isEmpty()) {
   1451             if (!updateRemoteDevicePropertiesCache(address))
   1452                 return -1;
   1453         }
   1454 
   1455         Map<ParcelUuid, Integer> value = mDeviceServiceChannelCache.get(address);
   1456         if (value != null && value.containsKey(uuid))
   1457             return value.get(uuid);
   1458         return -1;
   1459     }
   1460 
   1461     public synchronized boolean setPin(String address, byte[] pin) {
   1462         mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
   1463                                                 "Need BLUETOOTH_ADMIN permission");
   1464         if (!isEnabledInternal()) return false;
   1465 
   1466         if (pin == null || pin.length <= 0 || pin.length > 16 ||
   1467             !BluetoothAdapter.checkBluetoothAddress(address)) {
   1468             return false;
   1469         }
   1470         address = address.toUpperCase();
   1471         Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address);
   1472         if (data == null) {
   1473             Log.w(TAG, "setPin(" + address + ") called but no native data available, " +
   1474                   "ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" +
   1475                   " or by bluez.\n");
   1476             return false;
   1477         }
   1478         // bluez API wants pin as a string
   1479         String pinString;
   1480         try {
   1481             pinString = new String(pin, "UTF8");
   1482         } catch (UnsupportedEncodingException uee) {
   1483             Log.e(TAG, "UTF8 not supported?!?");
   1484             return false;
   1485         }
   1486         return setPinNative(address, pinString, data.intValue());
   1487     }
   1488 
   1489     public synchronized boolean setPasskey(String address, int passkey) {
   1490         mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
   1491                                                 "Need BLUETOOTH_ADMIN permission");
   1492         if (!isEnabledInternal()) return false;
   1493 
   1494         if (passkey < 0 || passkey > 999999 || !BluetoothAdapter.checkBluetoothAddress(address)) {
   1495             return false;
   1496         }
   1497         address = address.toUpperCase();
   1498         Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address);
   1499         if (data == null) {
   1500             Log.w(TAG, "setPasskey(" + address + ") called but no native data available, " +
   1501                   "ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" +
   1502                   " or by bluez.\n");
   1503             return false;
   1504         }
   1505         return setPasskeyNative(address, passkey, data.intValue());
   1506     }
   1507 
   1508     public synchronized boolean setPairingConfirmation(String address, boolean confirm) {
   1509         mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
   1510                                                 "Need BLUETOOTH_ADMIN permission");
   1511         if (!isEnabledInternal()) return false;
   1512 
   1513         address = address.toUpperCase();
   1514         Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address);
   1515         if (data == null) {
   1516             Log.w(TAG, "setPasskey(" + address + ") called but no native data available, " +
   1517                   "ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" +
   1518                   " or by bluez.\n");
   1519             return false;
   1520         }
   1521         return setPairingConfirmationNative(address, confirm, data.intValue());
   1522     }
   1523 
   1524     public synchronized boolean cancelPairingUserInput(String address) {
   1525         mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
   1526                                                 "Need BLUETOOTH_ADMIN permission");
   1527         if (!isEnabledInternal()) return false;
   1528 
   1529         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
   1530             return false;
   1531         }
   1532         mBondState.setBondState(address, BluetoothDevice.BOND_NONE,
   1533                 BluetoothDevice.UNBOND_REASON_AUTH_CANCELED);
   1534         address = address.toUpperCase();
   1535         Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address);
   1536         if (data == null) {
   1537             Log.w(TAG, "cancelUserInputNative(" + address + ") called but no native data " +
   1538                 "available, ignoring. Maybe the PasskeyAgent Request was already cancelled " +
   1539                 "by the remote or by bluez.\n");
   1540             return false;
   1541         }
   1542         return cancelPairingUserInputNative(address, data.intValue());
   1543     }
   1544 
   1545     /*package*/ void updateDeviceServiceChannelCache(String address) {
   1546         ParcelUuid[] deviceUuids = getRemoteUuids(address);
   1547         // We are storing the rfcomm channel numbers only for the uuids
   1548         // we are interested in.
   1549         int channel;
   1550         if (DBG) log("updateDeviceServiceChannelCache(" + address + ")");
   1551 
   1552         ArrayList<ParcelUuid> applicationUuids = new ArrayList();
   1553 
   1554         synchronized (this) {
   1555             for (RemoteService service : mUuidCallbackTracker.keySet()) {
   1556                 if (service.address.equals(address)) {
   1557                     applicationUuids.add(service.uuid);
   1558                 }
   1559             }
   1560         }
   1561 
   1562         Map <ParcelUuid, Integer> value = new HashMap<ParcelUuid, Integer>();
   1563 
   1564         // Retrieve RFCOMM channel for default uuids
   1565         for (ParcelUuid uuid : RFCOMM_UUIDS) {
   1566             if (BluetoothUuid.isUuidPresent(deviceUuids, uuid)) {
   1567                 channel = getDeviceServiceChannelNative(getObjectPathFromAddress(address),
   1568                         uuid.toString(), 0x0004);
   1569                 if (DBG) log("\tuuid(system): " + uuid + " " + channel);
   1570                 value.put(uuid, channel);
   1571             }
   1572         }
   1573         // Retrieve RFCOMM channel for application requested uuids
   1574         for (ParcelUuid uuid : applicationUuids) {
   1575             if (BluetoothUuid.isUuidPresent(deviceUuids, uuid)) {
   1576                 channel = getDeviceServiceChannelNative(getObjectPathFromAddress(address),
   1577                         uuid.toString(), 0x0004);
   1578                 if (DBG) log("\tuuid(application): " + uuid + " " + channel);
   1579                 value.put(uuid, channel);
   1580             }
   1581         }
   1582 
   1583         synchronized (this) {
   1584             // Make application callbacks
   1585             for (Iterator<RemoteService> iter = mUuidCallbackTracker.keySet().iterator();
   1586                     iter.hasNext();) {
   1587                 RemoteService service = iter.next();
   1588                 if (service.address.equals(address)) {
   1589                     channel = -1;
   1590                     if (value.get(service.uuid) != null) {
   1591                         channel = value.get(service.uuid);
   1592                     }
   1593                     if (channel != -1) {
   1594                         if (DBG) log("Making callback for " + service.uuid + " with result " +
   1595                                 channel);
   1596                         IBluetoothCallback callback = mUuidCallbackTracker.get(service);
   1597                         if (callback != null) {
   1598                             try {
   1599                                 callback.onRfcommChannelFound(channel);
   1600                             } catch (RemoteException e) {Log.e(TAG, "", e);}
   1601                         }
   1602 
   1603                         iter.remove();
   1604                     }
   1605                 }
   1606             }
   1607 
   1608             // Update cache
   1609             mDeviceServiceChannelCache.put(address, value);
   1610         }
   1611     }
   1612 
   1613     /**
   1614      * b is a handle to a Binder instance, so that this service can be notified
   1615      * for Applications that terminate unexpectedly, to clean there service
   1616      * records
   1617      */
   1618     public synchronized int addRfcommServiceRecord(String serviceName, ParcelUuid uuid,
   1619             int channel, IBinder b) {
   1620         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
   1621         if (!isEnabledInternal()) return -1;
   1622 
   1623         if (serviceName == null || uuid == null || channel < 1 ||
   1624                 channel > BluetoothSocket.MAX_RFCOMM_CHANNEL) {
   1625             return -1;
   1626         }
   1627         if (BluetoothUuid.isUuidPresent(BluetoothUuid.RESERVED_UUIDS, uuid)) {
   1628             Log.w(TAG, "Attempted to register a reserved UUID: " + uuid);
   1629             return -1;
   1630         }
   1631         int handle = addRfcommServiceRecordNative(serviceName,
   1632                 uuid.getUuid().getMostSignificantBits(), uuid.getUuid().getLeastSignificantBits(),
   1633                 (short)channel);
   1634         if (DBG) log("new handle " + Integer.toHexString(handle));
   1635         if (handle == -1) {
   1636             return -1;
   1637         }
   1638 
   1639         int pid = Binder.getCallingPid();
   1640         mServiceRecordToPid.put(new Integer(handle), new Integer(pid));
   1641         try {
   1642             b.linkToDeath(new Reaper(handle, pid), 0);
   1643         } catch (RemoteException e) {}
   1644         return handle;
   1645     }
   1646 
   1647     public void removeServiceRecord(int handle) {
   1648         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
   1649                                                 "Need BLUETOOTH permission");
   1650         checkAndRemoveRecord(handle, Binder.getCallingPid());
   1651     }
   1652 
   1653     private synchronized void checkAndRemoveRecord(int handle, int pid) {
   1654         Integer handleInt = new Integer(handle);
   1655         Integer owner = mServiceRecordToPid.get(handleInt);
   1656         if (owner != null && pid == owner.intValue()) {
   1657             if (DBG) log("Removing service record " + Integer.toHexString(handle) + " for pid " +
   1658                     pid);
   1659             mServiceRecordToPid.remove(handleInt);
   1660             removeServiceRecordNative(handle);
   1661         }
   1662     }
   1663 
   1664     private class Reaper implements IBinder.DeathRecipient {
   1665         int pid;
   1666         int handle;
   1667         Reaper(int handle, int pid) {
   1668             this.pid = pid;
   1669             this.handle = handle;
   1670         }
   1671         public void binderDied() {
   1672             synchronized (BluetoothService.this) {
   1673                 if (DBG) log("Tracked app " + pid + " died");
   1674                 checkAndRemoveRecord(handle, pid);
   1675             }
   1676         }
   1677     }
   1678 
   1679     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
   1680         @Override
   1681         public void onReceive(Context context, Intent intent) {
   1682             if (intent == null) return;
   1683 
   1684             String action = intent.getAction();
   1685             if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
   1686                 ContentResolver resolver = context.getContentResolver();
   1687                 // Query the airplane mode from Settings.System just to make sure that
   1688                 // some random app is not sending this intent and disabling bluetooth
   1689                 boolean enabled = !isAirplaneModeOn();
   1690                 // If bluetooth is currently expected to be on, then enable or disable bluetooth
   1691                 if (Settings.Secure.getInt(resolver, Settings.Secure.BLUETOOTH_ON, 0) > 0) {
   1692                     if (enabled) {
   1693                         enable(false);
   1694                     } else {
   1695                         disable(false);
   1696                     }
   1697                 }
   1698             } else if (Intent.ACTION_DOCK_EVENT.equals(action)) {
   1699                 int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
   1700                         Intent.EXTRA_DOCK_STATE_UNDOCKED);
   1701                 if (DBG) Log.v(TAG, "Received ACTION_DOCK_EVENT with State:" + state);
   1702                 if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
   1703                     mDockAddress = null;
   1704                     mDockPin = null;
   1705                 } else {
   1706                     SharedPreferences.Editor editor =
   1707                         mContext.getSharedPreferences(SHARED_PREFERENCES_NAME,
   1708                                 mContext.MODE_PRIVATE).edit();
   1709                     editor.putBoolean(SHARED_PREFERENCE_DOCK_ADDRESS + mDockAddress, true);
   1710                     editor.commit();
   1711                 }
   1712             }
   1713         }
   1714     };
   1715 
   1716     private void registerForAirplaneMode(IntentFilter filter) {
   1717         final ContentResolver resolver = mContext.getContentResolver();
   1718         final String airplaneModeRadios = Settings.System.getString(resolver,
   1719                 Settings.System.AIRPLANE_MODE_RADIOS);
   1720         final String toggleableRadios = Settings.System.getString(resolver,
   1721                 Settings.System.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
   1722 
   1723         mIsAirplaneSensitive = airplaneModeRadios == null ? true :
   1724                 airplaneModeRadios.contains(Settings.System.RADIO_BLUETOOTH);
   1725         mIsAirplaneToggleable = toggleableRadios == null ? false :
   1726                 toggleableRadios.contains(Settings.System.RADIO_BLUETOOTH);
   1727 
   1728         if (mIsAirplaneSensitive) {
   1729             filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
   1730         }
   1731     }
   1732 
   1733     /* Returns true if airplane mode is currently on */
   1734     private final boolean isAirplaneModeOn() {
   1735         return Settings.System.getInt(mContext.getContentResolver(),
   1736                 Settings.System.AIRPLANE_MODE_ON, 0) == 1;
   1737     }
   1738 
   1739     /* Broadcast the Uuid intent */
   1740     /*package*/ synchronized void sendUuidIntent(String address) {
   1741         ParcelUuid[] uuid = getUuidFromCache(address);
   1742         Intent intent = new Intent(BluetoothDevice.ACTION_UUID);
   1743         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
   1744         intent.putExtra(BluetoothDevice.EXTRA_UUID, uuid);
   1745         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
   1746 
   1747         if (mUuidIntentTracker.contains(address))
   1748             mUuidIntentTracker.remove(address);
   1749 
   1750     }
   1751 
   1752     /*package*/ synchronized void makeServiceChannelCallbacks(String address) {
   1753         for (Iterator<RemoteService> iter = mUuidCallbackTracker.keySet().iterator();
   1754                 iter.hasNext();) {
   1755             RemoteService service = iter.next();
   1756             if (service.address.equals(address)) {
   1757                 if (DBG) log("Cleaning up failed UUID channel lookup: " + service.address +
   1758                         " " + service.uuid);
   1759                 IBluetoothCallback callback = mUuidCallbackTracker.get(service);
   1760                 if (callback != null) {
   1761                     try {
   1762                         callback.onRfcommChannelFound(-1);
   1763                     } catch (RemoteException e) {Log.e(TAG, "", e);}
   1764                 }
   1765 
   1766                 iter.remove();
   1767             }
   1768         }
   1769     }
   1770 
   1771     @Override
   1772     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
   1773         switch(mBluetoothState) {
   1774         case BluetoothAdapter.STATE_OFF:
   1775             pw.println("Bluetooth OFF\n");
   1776             return;
   1777         case BluetoothAdapter.STATE_TURNING_ON:
   1778             pw.println("Bluetooth TURNING ON\n");
   1779             return;
   1780         case BluetoothAdapter.STATE_TURNING_OFF:
   1781             pw.println("Bluetooth TURNING OFF\n");
   1782             return;
   1783         case BluetoothAdapter.STATE_ON:
   1784             pw.println("Bluetooth ON\n");
   1785         }
   1786 
   1787         pw.println("mIsAirplaneSensitive = " + mIsAirplaneSensitive);
   1788         pw.println("mIsAirplaneToggleable = " + mIsAirplaneToggleable);
   1789 
   1790         pw.println("Local address = " + getAddress());
   1791         pw.println("Local name = " + getName());
   1792         pw.println("isDiscovering() = " + isDiscovering());
   1793 
   1794         BluetoothHeadset headset = new BluetoothHeadset(mContext, null);
   1795 
   1796         pw.println("\n--Known devices--");
   1797         for (String address : mDeviceProperties.keySet()) {
   1798             int bondState = mBondState.getBondState(address);
   1799             pw.printf("%s %10s (%d) %s\n", address,
   1800                        toBondStateString(bondState),
   1801                        mBondState.getAttempt(address),
   1802                        getRemoteName(address));
   1803 
   1804             Map<ParcelUuid, Integer> uuidChannels = mDeviceServiceChannelCache.get(address);
   1805             if (uuidChannels == null) {
   1806                 pw.println("\tuuids = null");
   1807             } else {
   1808                 for (ParcelUuid uuid : uuidChannels.keySet()) {
   1809                     Integer channel = uuidChannels.get(uuid);
   1810                     if (channel == null) {
   1811                         pw.println("\t" + uuid);
   1812                     } else {
   1813                         pw.println("\t" + uuid + " RFCOMM channel = " + channel);
   1814                     }
   1815                 }
   1816             }
   1817             for (RemoteService service : mUuidCallbackTracker.keySet()) {
   1818                 if (service.address.equals(address)) {
   1819                     pw.println("\tPENDING CALLBACK: " + service.uuid);
   1820                 }
   1821             }
   1822         }
   1823 
   1824         String value = getProperty("Devices");
   1825         String[] devicesObjectPath = null;
   1826         if (value != null) {
   1827             devicesObjectPath = value.split(",");
   1828         }
   1829         pw.println("\n--ACL connected devices--");
   1830         if (devicesObjectPath != null) {
   1831             for (String device : devicesObjectPath) {
   1832                 pw.println(getAddressFromObjectPath(device));
   1833             }
   1834         }
   1835 
   1836         // Rather not do this from here, but no-where else and I need this
   1837         // dump
   1838         pw.println("\n--Headset Service--");
   1839         switch (headset.getState()) {
   1840         case BluetoothHeadset.STATE_DISCONNECTED:
   1841             pw.println("getState() = STATE_DISCONNECTED");
   1842             break;
   1843         case BluetoothHeadset.STATE_CONNECTING:
   1844             pw.println("getState() = STATE_CONNECTING");
   1845             break;
   1846         case BluetoothHeadset.STATE_CONNECTED:
   1847             pw.println("getState() = STATE_CONNECTED");
   1848             break;
   1849         case BluetoothHeadset.STATE_ERROR:
   1850             pw.println("getState() = STATE_ERROR");
   1851             break;
   1852         }
   1853 
   1854         pw.println("\ngetCurrentHeadset() = " + headset.getCurrentHeadset());
   1855         pw.println("getBatteryUsageHint() = " + headset.getBatteryUsageHint());
   1856         headset.close();
   1857         pw.println("\n--Application Service Records--");
   1858         for (Integer handle : mServiceRecordToPid.keySet()) {
   1859             Integer pid = mServiceRecordToPid.get(handle);
   1860             pw.println("\tpid " + pid + " handle " + Integer.toHexString(handle));
   1861         }
   1862     }
   1863 
   1864     /* package */ static int bluezStringToScanMode(boolean pairable, boolean discoverable) {
   1865         if (pairable && discoverable)
   1866             return BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
   1867         else if (pairable && !discoverable)
   1868             return BluetoothAdapter.SCAN_MODE_CONNECTABLE;
   1869         else
   1870             return BluetoothAdapter.SCAN_MODE_NONE;
   1871     }
   1872 
   1873     /* package */ static String scanModeToBluezString(int mode) {
   1874         switch (mode) {
   1875         case BluetoothAdapter.SCAN_MODE_NONE:
   1876             return "off";
   1877         case BluetoothAdapter.SCAN_MODE_CONNECTABLE:
   1878             return "connectable";
   1879         case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
   1880             return "discoverable";
   1881         }
   1882         return null;
   1883     }
   1884 
   1885     /*package*/ String getAddressFromObjectPath(String objectPath) {
   1886         String adapterObjectPath = getPropertyInternal("ObjectPath");
   1887         if (adapterObjectPath == null || objectPath == null) {
   1888             Log.e(TAG, "getAddressFromObjectPath: AdpaterObjectPath:" + adapterObjectPath +
   1889                     "  or deviceObjectPath:" + objectPath + " is null");
   1890             return null;
   1891         }
   1892         if (!objectPath.startsWith(adapterObjectPath)) {
   1893             Log.e(TAG, "getAddressFromObjectPath: AdpaterObjectPath:" + adapterObjectPath +
   1894                     "  is not a prefix of deviceObjectPath:" + objectPath +
   1895                     "bluetoothd crashed ?");
   1896             return null;
   1897         }
   1898         String address = objectPath.substring(adapterObjectPath.length());
   1899         if (address != null) return address.replace('_', ':');
   1900 
   1901         Log.e(TAG, "getAddressFromObjectPath: Address being returned is null");
   1902         return null;
   1903     }
   1904 
   1905     /*package*/ String getObjectPathFromAddress(String address) {
   1906         String path = getPropertyInternal("ObjectPath");
   1907         if (path == null) {
   1908             Log.e(TAG, "Error: Object Path is null");
   1909             return null;
   1910         }
   1911         path = path + address.replace(":", "_");
   1912         return path;
   1913     }
   1914 
   1915     /*package */ void setLinkTimeout(String address, int num_slots) {
   1916         String path = getObjectPathFromAddress(address);
   1917         boolean result = setLinkTimeoutNative(path, num_slots);
   1918 
   1919         if (!result) log("Set Link Timeout to:" + num_slots + " slots failed");
   1920     }
   1921 
   1922     private static void log(String msg) {
   1923         Log.d(TAG, msg);
   1924     }
   1925 
   1926     private native static void classInitNative();
   1927     private native void initializeNativeDataNative();
   1928     private native boolean setupNativeDataNative();
   1929     private native boolean tearDownNativeDataNative();
   1930     private native void cleanupNativeDataNative();
   1931     private native String getAdapterPathNative();
   1932 
   1933     private native int isEnabledNative();
   1934     private native int enableNative();
   1935     private native int disableNative();
   1936 
   1937     private native Object[] getAdapterPropertiesNative();
   1938     private native Object[] getDevicePropertiesNative(String objectPath);
   1939     private native boolean setAdapterPropertyStringNative(String key, String value);
   1940     private native boolean setAdapterPropertyIntegerNative(String key, int value);
   1941     private native boolean setAdapterPropertyBooleanNative(String key, int value);
   1942 
   1943     private native boolean startDiscoveryNative();
   1944     private native boolean stopDiscoveryNative();
   1945 
   1946     private native boolean createPairedDeviceNative(String address, int timeout_ms);
   1947     private native boolean cancelDeviceCreationNative(String address);
   1948     private native boolean removeDeviceNative(String objectPath);
   1949     private native int getDeviceServiceChannelNative(String objectPath, String uuid,
   1950             int attributeId);
   1951 
   1952     private native boolean cancelPairingUserInputNative(String address, int nativeData);
   1953     private native boolean setPinNative(String address, String pin, int nativeData);
   1954     private native boolean setPasskeyNative(String address, int passkey, int nativeData);
   1955     private native boolean setPairingConfirmationNative(String address, boolean confirm,
   1956             int nativeData);
   1957     private native boolean setDevicePropertyBooleanNative(String objectPath, String key,
   1958             int value);
   1959     private native boolean createDeviceNative(String address);
   1960     /*package*/ native boolean discoverServicesNative(String objectPath, String pattern);
   1961 
   1962     private native int addRfcommServiceRecordNative(String name, long uuidMsb, long uuidLsb,
   1963             short channel);
   1964     private native boolean removeServiceRecordNative(int handle);
   1965     private native boolean setLinkTimeoutNative(String path, int num_slots);
   1966 }
   1967