BluetoothService.java revision 2d3b98d868cda30535505b2a2fba47aa1c9c052b
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
25package android.server;
26
27import android.bluetooth.BluetoothAdapter;
28import android.bluetooth.BluetoothClass;
29import android.bluetooth.BluetoothDevice;
30import android.bluetooth.BluetoothHeadset;
31import android.bluetooth.BluetoothUuid;
32import android.bluetooth.IBluetooth;
33import android.bluetooth.ParcelUuid;
34import android.content.BroadcastReceiver;
35import android.content.ContentResolver;
36import android.content.Context;
37import android.content.Intent;
38import android.content.IntentFilter;
39import android.os.Binder;
40import android.os.Handler;
41import android.os.Message;
42import android.os.RemoteException;
43import android.os.ServiceManager;
44import android.os.SystemService;
45import android.provider.Settings;
46import android.util.Log;
47
48import com.android.internal.app.IBatteryStats;
49
50import java.io.FileDescriptor;
51import java.io.PrintWriter;
52import java.io.UnsupportedEncodingException;
53import java.util.ArrayList;
54import java.util.Arrays;
55import java.util.HashMap;
56import java.util.Map;
57
58public class BluetoothService extends IBluetooth.Stub {
59    private static final String TAG = "BluetoothService";
60    private static final boolean DBG = true;
61
62    private int mNativeData;
63    private BluetoothEventLoop mEventLoop;
64    private IntentFilter mIntentFilter;
65    private boolean mIsAirplaneSensitive;
66    private int mBluetoothState;
67    private boolean mRestart = false;  // need to call enable() after disable()
68    private boolean mIsDiscovering;
69
70    private BluetoothAdapter mAdapter;  // constant after init()
71    private final BondState mBondState = new BondState();  // local cache of bondings
72    private final IBatteryStats mBatteryStats;
73    private final Context mContext;
74
75    private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
76    private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
77
78    private static final int MESSAGE_REGISTER_SDP_RECORDS = 1;
79    private static final int MESSAGE_FINISH_DISABLE = 2;
80    private static final int MESSAGE_UUID_INTENT = 3;
81
82    // The timeout used to sent the UUIDs Intent
83    // This timeout should be greater than the page timeout
84    private static final int UUID_INTENT_DELAY = 6000;
85
86    private final Map<String, String> mAdapterProperties;
87    private final HashMap <String, Map<String, String>> mDeviceProperties;
88
89    private final HashMap <String, Map<ParcelUuid, Integer>> mDeviceServiceChannelCache;
90    private final ArrayList <String> mUuidIntentTracker;
91
92    static {
93        classInitNative();
94    }
95
96    public BluetoothService(Context context) {
97        mContext = context;
98
99        // Need to do this in place of:
100        // mBatteryStats = BatteryStatsService.getService();
101        // Since we can not import BatteryStatsService from here. This class really needs to be
102        // moved to java/services/com/android/server/
103        mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo"));
104
105        initializeNativeDataNative();
106
107        if (isEnabledNative() == 1) {
108            Log.w(TAG, "Bluetooth daemons already running - runtime restart? ");
109            disableNative();
110        }
111
112        mBluetoothState = BluetoothAdapter.STATE_OFF;
113        mIsDiscovering = false;
114        mAdapterProperties = new HashMap<String, String>();
115        mDeviceProperties = new HashMap<String, Map<String,String>>();
116
117        mDeviceServiceChannelCache = new HashMap<String, Map<ParcelUuid, Integer>>();
118        mUuidIntentTracker = new ArrayList<String>();
119        registerForAirplaneMode();
120    }
121
122    public synchronized void initAfterRegistration() {
123        mAdapter = (BluetoothAdapter) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
124        mEventLoop = new BluetoothEventLoop(mContext, mAdapter, this);
125    }
126
127    @Override
128    protected void finalize() throws Throwable {
129        if (mIsAirplaneSensitive) {
130            mContext.unregisterReceiver(mReceiver);
131        }
132        try {
133            cleanupNativeDataNative();
134        } finally {
135            super.finalize();
136        }
137    }
138
139    public boolean isEnabled() {
140        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
141        return mBluetoothState == BluetoothAdapter.STATE_ON;
142    }
143
144    public int getBluetoothState() {
145        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
146        return mBluetoothState;
147    }
148
149
150    /**
151     * Bring down bluetooth and disable BT in settings. Returns true on success.
152     */
153    public boolean disable() {
154        return disable(true);
155    }
156
157    /**
158     * Bring down bluetooth. Returns true on success.
159     *
160     * @param saveSetting If true, disable BT in settings
161     */
162    public synchronized boolean disable(boolean saveSetting) {
163        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
164
165        switch (mBluetoothState) {
166        case BluetoothAdapter.STATE_OFF:
167            return true;
168        case BluetoothAdapter.STATE_ON:
169            break;
170        default:
171            return false;
172        }
173        if (mEnableThread != null && mEnableThread.isAlive()) {
174            return false;
175        }
176        setBluetoothState(BluetoothAdapter.STATE_TURNING_OFF);
177
178        // Allow 3 seconds for profiles to gracefully disconnect
179        // TODO: Introduce a callback mechanism so that each profile can notify
180        // BluetoothService when it is done shutting down
181        mHandler.sendMessageDelayed(
182                mHandler.obtainMessage(MESSAGE_FINISH_DISABLE, saveSetting ? 1 : 0, 0), 3000);
183        return true;
184    }
185
186
187    private synchronized void finishDisable(boolean saveSetting) {
188        if (mBluetoothState != BluetoothAdapter.STATE_TURNING_OFF) {
189            return;
190        }
191        mEventLoop.stop();
192        tearDownNativeDataNative();
193        disableNative();
194
195        // mark in progress bondings as cancelled
196        for (String address : mBondState.listInState(BluetoothDevice.BOND_BONDING)) {
197            mBondState.setBondState(address, BluetoothDevice.BOND_NONE,
198                                    BluetoothDevice.UNBOND_REASON_AUTH_CANCELED);
199        }
200
201        // update mode
202        Intent intent = new Intent(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
203        intent.putExtra(BluetoothAdapter.EXTRA_SCAN_MODE, BluetoothAdapter.SCAN_MODE_NONE);
204        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
205
206        mIsDiscovering = false;
207        mAdapterProperties.clear();
208
209        if (saveSetting) {
210            persistBluetoothOnSetting(false);
211        }
212
213        setBluetoothState(BluetoothAdapter.STATE_OFF);
214
215        // Log bluetooth off to battery stats.
216        long ident = Binder.clearCallingIdentity();
217        try {
218            mBatteryStats.noteBluetoothOff();
219        } catch (RemoteException e) {
220        } finally {
221            Binder.restoreCallingIdentity(ident);
222        }
223
224        if (mRestart) {
225            mRestart = false;
226            enable();
227        }
228    }
229
230    /** Bring up BT and persist BT on in settings */
231    public boolean enable() {
232        return enable(true);
233    }
234
235    /**
236     * Enable this Bluetooth device, asynchronously.
237     * This turns on/off the underlying hardware.
238     *
239     * @param saveSetting If true, persist the new state of BT in settings
240     * @return True on success (so far)
241     */
242    public synchronized boolean enable(boolean saveSetting) {
243        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
244                                                "Need BLUETOOTH_ADMIN permission");
245
246        // Airplane mode can prevent Bluetooth radio from being turned on.
247        if (mIsAirplaneSensitive && isAirplaneModeOn()) {
248            return false;
249        }
250        if (mBluetoothState != BluetoothAdapter.STATE_OFF) {
251            return false;
252        }
253        if (mEnableThread != null && mEnableThread.isAlive()) {
254            return false;
255        }
256        setBluetoothState(BluetoothAdapter.STATE_TURNING_ON);
257        mEnableThread = new EnableThread(saveSetting);
258        mEnableThread.start();
259        return true;
260    }
261
262    /** Forcibly restart Bluetooth if it is on */
263    /* package */ synchronized void restart() {
264        if (mBluetoothState != BluetoothAdapter.STATE_ON) {
265            return;
266        }
267        mRestart = true;
268        if (!disable(false)) {
269            mRestart = false;
270        }
271    }
272
273    private synchronized void setBluetoothState(int state) {
274        if (state == mBluetoothState) {
275            return;
276        }
277
278        if (DBG) log("Bluetooth state " + mBluetoothState + " -> " + state);
279
280        Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
281        intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, mBluetoothState);
282        intent.putExtra(BluetoothAdapter.EXTRA_STATE, state);
283        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
284
285        mBluetoothState = state;
286
287        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
288    }
289
290    private final Handler mHandler = new Handler() {
291        @Override
292        public void handleMessage(Message msg) {
293            switch (msg.what) {
294            case MESSAGE_REGISTER_SDP_RECORDS:
295                //TODO: Don't assume HSP/HFP is running, don't use sdptool,
296                if (isEnabled()) {
297                    SystemService.start("hsag");
298                    SystemService.start("hfag");
299                    SystemService.start("opush");
300                    SystemService.start("pbap");
301                }
302                break;
303            case MESSAGE_FINISH_DISABLE:
304                finishDisable(msg.arg1 != 0);
305                break;
306            case MESSAGE_UUID_INTENT:
307                String address = (String)msg.obj;
308                if (address != null)
309                    sendUuidIntent(address);
310                break;
311            }
312        }
313    };
314
315    private EnableThread mEnableThread;
316
317    private class EnableThread extends Thread {
318        private final boolean mSaveSetting;
319        public EnableThread(boolean saveSetting) {
320            mSaveSetting = saveSetting;
321        }
322        public void run() {
323            boolean res = (enableNative() == 0);
324            if (res) {
325                int retryCount = 2;
326                boolean running = false;
327                while ((retryCount-- > 0) && !running) {
328                    mEventLoop.start();
329                    // it may take a momement for the other thread to do its
330                    // thing.  Check periodically for a while.
331                    int pollCount = 5;
332                    while ((pollCount-- > 0) && !running) {
333                        if (mEventLoop.isEventLoopRunning()) {
334                            running = true;
335                            break;
336                        }
337                        try {
338                            Thread.sleep(100);
339                        } catch (InterruptedException e) {}
340                    }
341                }
342                if (!running) {
343                    log("bt EnableThread giving up");
344                    res = false;
345                    disableNative();
346                }
347            }
348
349
350            if (res) {
351                if (!setupNativeDataNative()) {
352                    return;
353                }
354                if (mSaveSetting) {
355                    persistBluetoothOnSetting(true);
356                }
357                mIsDiscovering = false;
358                mBondState.loadBondState();
359                mHandler.sendMessageDelayed(mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS),
360                                            3000);
361
362                // Log bluetooth on to battery stats.
363                long ident = Binder.clearCallingIdentity();
364                try {
365                    mBatteryStats.noteBluetoothOn();
366                } catch (RemoteException e) {
367                } finally {
368                    Binder.restoreCallingIdentity(ident);
369                }
370            }
371
372            mEnableThread = null;
373
374            setBluetoothState(res ?
375                              BluetoothAdapter.STATE_ON :
376                              BluetoothAdapter.STATE_OFF);
377
378            if (res) {
379                // Update mode
380                String[] propVal = {"Pairable", getProperty("Pairable")};
381                mEventLoop.onPropertyChanged(propVal);
382            }
383
384            if (mIsAirplaneSensitive && isAirplaneModeOn()) {
385                disable(false);
386            }
387
388        }
389    }
390
391    private void persistBluetoothOnSetting(boolean bluetoothOn) {
392        long origCallerIdentityToken = Binder.clearCallingIdentity();
393        Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.BLUETOOTH_ON,
394                bluetoothOn ? 1 : 0);
395        Binder.restoreCallingIdentity(origCallerIdentityToken);
396    }
397
398    /* package */ BondState getBondState() {
399        return mBondState;
400    }
401
402    /** local cache of bonding state.
403    /* we keep our own state to track the intermediate state BONDING, which
404    /* bluez does not track.
405     * All addreses must be passed in upper case.
406     */
407    public class BondState {
408        private final HashMap<String, Integer> mState = new HashMap<String, Integer>();
409        private final HashMap<String, Integer> mPinAttempt = new HashMap<String, Integer>();
410        private final ArrayList<String> mAutoPairingFailures = new ArrayList<String>();
411        // List of all the vendor_id prefix of Bluetooth addresses for
412        // which auto pairing is not attempted.
413        // The following companies are included in the list below:
414        // ALPS (lexus), Murata (Prius 2007, Nokia 616), TEMIC SDS (Porsche, Audi),
415        // Parrot, Zhongshan General K-mate Electronics, Great Well
416        // Electronics, Flaircomm Electronics, Jatty Electronics, Delphi,
417        // Clarion, Novero, Denso (Lexus, Toyota), Johnson Controls (Acura),
418        // Continental Automotive, Harman/Becker, Panasonic/Kyushu Ten
419        private final ArrayList<String>  mAutoPairingAddressBlacklist =
420                new ArrayList<String>(Arrays.asList(
421                        "00:02:C7", "00:16:FE", "00:19:C1", "00:1B:FB", "00:1E:3D", "00:21:4F",
422                        "00:23:06", "00:24:33", "00:A0:79", "00:0E:6D", "00:13:E0", "00:21:E8",
423                        "00:60:57", "00:0E:9F", "00:12:1C", "00:18:91", "00:18:96", "00:13:04",
424                        "00:16:FD", "00:22:A0", "00:0B:4C", "00:60:6F", "00:23:3D", "00:C0:59",
425                        "00:0A:30", "00:1E:AE", "00:1C:D7", "00:80:F0"
426                        ));
427
428        // List of names of Bluetooth devices for which auto pairing should be
429        // disabled.
430        private final ArrayList<String> mAutoPairingNameBlacklist =
431                new ArrayList<String>(Arrays.asList(
432                        "Motorola IHF1000", "i.TechBlueBAND", "X5 Stereo v1.3"));
433
434        // If this is an outgoing connection, store the address.
435        // There can be only 1 pending outgoing connection at a time,
436        private String mPendingOutgoingBonding;
437
438        private synchronized void setPendingOutgoingBonding(String address) {
439            mPendingOutgoingBonding = address;
440        }
441
442        public synchronized String getPendingOutgoingBonding() {
443            return mPendingOutgoingBonding;
444        }
445
446        public synchronized void loadBondState() {
447            if (mBluetoothState != BluetoothAdapter.STATE_TURNING_ON) {
448                return;
449            }
450            String []bonds = null;
451            String val = getProperty("Devices");
452            if (val != null) {
453                bonds = val.split(",");
454            }
455            if (bonds == null) {
456                return;
457            }
458            mState.clear();
459            if (DBG) log("found " + bonds.length + " bonded devices");
460            for (String device : bonds) {
461                mState.put(getAddressFromObjectPath(device).toUpperCase(),
462                        BluetoothDevice.BOND_BONDED);
463            }
464        }
465
466        public synchronized void setBondState(String address, int state) {
467            setBondState(address, state, 0);
468        }
469
470        /** reason is ignored unless state == BOND_NOT_BONDED */
471        public synchronized void setBondState(String address, int state, int reason) {
472            int oldState = getBondState(address);
473            if (oldState == state) {
474                return;
475            }
476
477            // Check if this was an pending outgoing bonding.
478            // If yes, reset the state.
479            if (oldState == BluetoothDevice.BOND_BONDING) {
480                if (address.equals(mPendingOutgoingBonding)) {
481                    mPendingOutgoingBonding = null;
482                }
483            }
484
485            if (DBG) log(address + " bond state " + oldState + " -> " + state + " (" +
486                         reason + ")");
487            Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
488            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
489            intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, state);
490            intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState);
491            if (state == BluetoothDevice.BOND_NONE) {
492                if (reason <= 0) {
493                    Log.w(TAG, "setBondState() called to unbond device, but reason code is " +
494                          "invalid. Overriding reason code with BOND_RESULT_REMOVED");
495                    reason = BluetoothDevice.UNBOND_REASON_REMOVED;
496                }
497                intent.putExtra(BluetoothDevice.EXTRA_REASON, reason);
498                mState.remove(address);
499            } else {
500                mState.put(address, state);
501            }
502
503            mContext.sendBroadcast(intent, BLUETOOTH_PERM);
504        }
505
506        public boolean isAutoPairingBlacklisted(String address) {
507            for (String blacklistAddress : mAutoPairingAddressBlacklist) {
508                if (address.startsWith(blacklistAddress)) return true;
509            }
510
511            String name = getRemoteName(address);
512            if (name != null) {
513                for (String blacklistName : mAutoPairingNameBlacklist) {
514                    if (name.equals(blacklistName)) return true;
515                }
516            }
517            return false;
518        }
519
520        public synchronized int getBondState(String address) {
521            Integer state = mState.get(address);
522            if (state == null) {
523                return BluetoothDevice.BOND_NONE;
524            }
525            return state.intValue();
526        }
527
528        private synchronized String[] listInState(int state) {
529            ArrayList<String> result = new ArrayList<String>(mState.size());
530            for (Map.Entry<String, Integer> e : mState.entrySet()) {
531                if (e.getValue().intValue() == state) {
532                    result.add(e.getKey());
533                }
534            }
535            return result.toArray(new String[result.size()]);
536        }
537
538        public synchronized void addAutoPairingFailure(String address) {
539            if (!mAutoPairingFailures.contains(address)) {
540                mAutoPairingFailures.add(address);
541            }
542        }
543
544        public synchronized boolean isAutoPairingAttemptsInProgress(String address) {
545            return getAttempt(address) != 0;
546        }
547
548        public synchronized void clearPinAttempts(String address) {
549            mPinAttempt.remove(address);
550        }
551
552        public synchronized boolean hasAutoPairingFailed(String address) {
553            return mAutoPairingFailures.contains(address);
554        }
555
556        public synchronized int getAttempt(String address) {
557            Integer attempt = mPinAttempt.get(address);
558            if (attempt == null) {
559                return 0;
560            }
561            return attempt.intValue();
562        }
563
564        public synchronized void attempt(String address) {
565            Integer attempt = mPinAttempt.get(address);
566            int newAttempt;
567            if (attempt == null) {
568                newAttempt = 1;
569            } else {
570                newAttempt = attempt.intValue() + 1;
571            }
572            mPinAttempt.put(address, new Integer(newAttempt));
573        }
574
575    }
576
577    private static String toBondStateString(int bondState) {
578        switch (bondState) {
579        case BluetoothDevice.BOND_NONE:
580            return "not bonded";
581        case BluetoothDevice.BOND_BONDING:
582            return "bonding";
583        case BluetoothDevice.BOND_BONDED:
584            return "bonded";
585        default:
586            return "??????";
587        }
588    }
589
590    /*package*/ synchronized boolean isAdapterPropertiesEmpty() {
591        return mAdapterProperties.isEmpty();
592    }
593
594    /*package*/synchronized void getAllProperties() {
595        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
596        mAdapterProperties.clear();
597
598        String properties[] = (String [])getAdapterPropertiesNative();
599        // The String Array consists of key-value pairs.
600        if (properties == null) {
601            Log.e(TAG, "*Error*: GetAdapterProperties returned NULL");
602            return;
603        }
604
605        for (int i = 0; i < properties.length; i++) {
606            String name = properties[i];
607            String newValue = null;
608            int len;
609            if (name == null) {
610                Log.e(TAG, "Error:Adapter Property at index" + i + "is null");
611                continue;
612            }
613            if (name.equals("Devices")) {
614                StringBuilder str = new StringBuilder();
615                len = Integer.valueOf(properties[++i]);
616                for (int j = 0; j < len; j++) {
617                    str.append(properties[++i]);
618                    str.append(",");
619                }
620                if (len > 0) {
621                    newValue = str.toString();
622                }
623            } else {
624                newValue = properties[++i];
625            }
626            mAdapterProperties.put(name, newValue);
627        }
628
629        // Add adapter object path property.
630        String adapterPath = getAdapterPathNative();
631        if (adapterPath != null)
632            mAdapterProperties.put("ObjectPath", adapterPath + "/dev_");
633    }
634
635    /* package */ synchronized void setProperty(String name, String value) {
636        mAdapterProperties.put(name, value);
637    }
638
639    public synchronized boolean setName(String name) {
640        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
641                                                "Need BLUETOOTH_ADMIN permission");
642        if (name == null) {
643            return false;
644        }
645        return setPropertyString("Name", name);
646    }
647
648    //TODO(): setPropertyString, setPropertyInteger, setPropertyBoolean
649    // Either have a single property function with Object as the parameter
650    // or have a function for each property and then obfuscate in the JNI layer.
651    // The following looks dirty.
652    private boolean setPropertyString(String key, String value) {
653        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
654        return setAdapterPropertyStringNative(key, value);
655    }
656
657    private boolean setPropertyInteger(String key, int value) {
658        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
659        return setAdapterPropertyIntegerNative(key, value);
660    }
661
662    private boolean setPropertyBoolean(String key, boolean value) {
663        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
664        return setAdapterPropertyBooleanNative(key, value ? 1 : 0);
665    }
666
667    /**
668     * Set the discoverability window for the device.  A timeout of zero
669     * makes the device permanently discoverable (if the device is
670     * discoverable).  Setting the timeout to a nonzero value does not make
671     * a device discoverable; you need to call setMode() to make the device
672     * explicitly discoverable.
673     *
674     * @param timeout_s The discoverable timeout in seconds.
675     */
676    public synchronized boolean setDiscoverableTimeout(int timeout) {
677        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
678                                                "Need BLUETOOTH_ADMIN permission");
679        return setPropertyInteger("DiscoverableTimeout", timeout);
680    }
681
682    public synchronized boolean setScanMode(int mode) {
683        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
684                                                "Need BLUETOOTH_ADMIN permission");
685        boolean pairable = false;
686        boolean discoverable = false;
687        switch (mode) {
688        case BluetoothAdapter.SCAN_MODE_NONE:
689            pairable = false;
690            discoverable = false;
691            break;
692        case BluetoothAdapter.SCAN_MODE_CONNECTABLE:
693            pairable = true;
694            discoverable = false;
695            break;
696        case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
697            pairable = true;
698            discoverable = true;
699            break;
700        default:
701            Log.w(TAG, "Requested invalid scan mode " + mode);
702            return false;
703        }
704        setPropertyBoolean("Pairable", pairable);
705        setPropertyBoolean("Discoverable", discoverable);
706
707        return true;
708    }
709
710    /*package*/ synchronized String getProperty (String name) {
711        if (!mAdapterProperties.isEmpty())
712            return mAdapterProperties.get(name);
713        getAllProperties();
714        return mAdapterProperties.get(name);
715    }
716
717    public synchronized String getAddress() {
718        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
719        return getProperty("Address");
720    }
721
722    public synchronized String getName() {
723        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
724        return getProperty("Name");
725    }
726
727    /**
728     * Returns the user-friendly name of a remote device.  This value is
729     * returned from our local cache, which is updated when onPropertyChange
730     * event is received.
731     * Do not expect to retrieve the updated remote name immediately after
732     * changing the name on the remote device.
733     *
734     * @param address Bluetooth address of remote device.
735     *
736     * @return The user-friendly name of the specified remote device.
737     */
738    public synchronized String getRemoteName(String address) {
739        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
740        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
741            return null;
742        }
743        Map <String, String> properties = mDeviceProperties.get(address);
744        if (properties != null) return properties.get("Name");
745        return null;
746    }
747
748    /**
749     * Get the discoverability window for the device.  A timeout of zero
750     * means that the device is permanently discoverable (if the device is
751     * in the discoverable mode).
752     *
753     * @return The discoverability window of the device, in seconds.  A negative
754     *         value indicates an error.
755     */
756    public synchronized int getDiscoverableTimeout() {
757        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
758        String timeout = getProperty("DiscoverableTimeout");
759        if (timeout != null)
760           return Integer.valueOf(timeout);
761        else
762            return -1;
763    }
764
765    public synchronized int getScanMode() {
766        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
767        if (!isEnabled())
768            return BluetoothAdapter.SCAN_MODE_NONE;
769
770        boolean pairable = getProperty("Pairable").equals("true");
771        boolean discoverable = getProperty("Discoverable").equals("true");
772        return bluezStringToScanMode (pairable, discoverable);
773    }
774
775    public synchronized boolean startDiscovery() {
776        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
777                                                "Need BLUETOOTH_ADMIN permission");
778        if (!isEnabled()) {
779            return false;
780        }
781        return startDiscoveryNative();
782    }
783
784    public synchronized boolean cancelDiscovery() {
785        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
786                                                "Need BLUETOOTH_ADMIN permission");
787        return stopDiscoveryNative();
788    }
789
790    public synchronized boolean isDiscovering() {
791        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
792        return mIsDiscovering;
793    }
794
795    /* package */ void setIsDiscovering(boolean isDiscovering) {
796        mIsDiscovering = isDiscovering;
797    }
798
799    public synchronized boolean createBond(String address) {
800        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
801                                                "Need BLUETOOTH_ADMIN permission");
802        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
803            return false;
804        }
805        address = address.toUpperCase();
806
807        if (mBondState.getPendingOutgoingBonding() != null) {
808            log("Ignoring createBond(): another device is bonding");
809            // a different device is currently bonding, fail
810            return false;
811        }
812
813        // Check for bond state only if we are not performing auto
814        // pairing exponential back-off attempts.
815        if (!mBondState.isAutoPairingAttemptsInProgress(address) &&
816                mBondState.getBondState(address) != BluetoothDevice.BOND_NONE) {
817            log("Ignoring createBond(): this device is already bonding or bonded");
818            return false;
819        }
820
821        if (!createPairedDeviceNative(address, 60000 /* 1 minute */)) {
822            return false;
823        }
824
825        mBondState.setPendingOutgoingBonding(address);
826        mBondState.setBondState(address, BluetoothDevice.BOND_BONDING);
827
828        return true;
829    }
830
831    public synchronized boolean cancelBondProcess(String address) {
832        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
833                                                "Need BLUETOOTH_ADMIN permission");
834        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
835            return false;
836        }
837        address = address.toUpperCase();
838        if (mBondState.getBondState(address) != BluetoothDevice.BOND_BONDING) {
839            return false;
840        }
841
842        mBondState.setBondState(address, BluetoothDevice.BOND_NONE,
843                                BluetoothDevice.UNBOND_REASON_AUTH_CANCELED);
844        cancelDeviceCreationNative(address);
845        return true;
846    }
847
848    public synchronized boolean removeBond(String address) {
849        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
850                                                "Need BLUETOOTH_ADMIN permission");
851        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
852            return false;
853        }
854        return removeDeviceNative(getObjectPathFromAddress(address));
855    }
856
857    public synchronized String[] listBonds() {
858        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
859        return mBondState.listInState(BluetoothDevice.BOND_BONDED);
860    }
861
862    public synchronized int getBondState(String address) {
863        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
864        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
865            return BluetoothDevice.ERROR;
866        }
867        return mBondState.getBondState(address.toUpperCase());
868    }
869
870    /*package*/ boolean isRemoteDeviceInCache(String address) {
871        return (mDeviceProperties.get(address) != null);
872    }
873
874    /*package*/ String[] getRemoteDeviceProperties(String address) {
875        String objectPath = getObjectPathFromAddress(address);
876        return (String [])getDevicePropertiesNative(objectPath);
877    }
878
879    /*package*/ synchronized String getRemoteDeviceProperty(String address, String property) {
880        Map<String, String> properties = mDeviceProperties.get(address);
881        if (properties != null) {
882            return properties.get(property);
883        } else {
884            // Query for remote device properties, again.
885            // We will need to reload the cache when we switch Bluetooth on / off
886            // or if we crash.
887            if (updateRemoteDevicePropertiesCache(address))
888                return getRemoteDeviceProperty(address, property);
889        }
890        Log.e(TAG, "getRemoteDeviceProperty: " + property + "not present:" + address);
891        return null;
892    }
893
894    /* package */ synchronized boolean updateRemoteDevicePropertiesCache(String address) {
895        String[] propValues = getRemoteDeviceProperties(address);
896        if (propValues != null) {
897            addRemoteDeviceProperties(address, propValues);
898            return true;
899        }
900        return false;
901    }
902
903    /* package */ synchronized void addRemoteDeviceProperties(String address, String[] properties) {
904        /*
905         * We get a DeviceFound signal every time RSSI changes or name changes.
906         * Don't create a new Map object every time */
907        Map<String, String> propertyValues = mDeviceProperties.get(address);
908        if (propertyValues == null) {
909            propertyValues = new HashMap<String, String>();
910        }
911
912        for (int i = 0; i < properties.length; i++) {
913            String name = properties[i];
914            String newValue = null;
915            int len;
916            if (name == null) {
917                Log.e(TAG, "Error: Remote Device Property at index" + i + "is null");
918                continue;
919            }
920            if (name.equals("UUIDs") || name.equals("Nodes")) {
921                StringBuilder str = new StringBuilder();
922                len = Integer.valueOf(properties[++i]);
923                for (int j = 0; j < len; j++) {
924                    str.append(properties[++i]);
925                    str.append(",");
926                }
927                if (len > 0) {
928                    newValue = str.toString();
929                }
930            } else {
931                newValue = properties[++i];
932            }
933
934            propertyValues.put(name, newValue);
935        }
936        mDeviceProperties.put(address, propertyValues);
937
938        // We have added a new remote device or updated its properties.
939        // Also update the serviceChannel cache.
940        updateDeviceServiceChannelCache(address);
941    }
942
943    /* package */ void removeRemoteDeviceProperties(String address) {
944        mDeviceProperties.remove(address);
945    }
946
947    /* package */ synchronized void setRemoteDeviceProperty(String address, String name,
948                                                              String value) {
949        Map <String, String> propVal = mDeviceProperties.get(address);
950        if (propVal != null) {
951            propVal.put(name, value);
952            mDeviceProperties.put(address, propVal);
953        } else {
954            Log.e(TAG, "setRemoteDeviceProperty for a device not in cache:" + address);
955        }
956    }
957
958    /**
959     * Sets the remote device trust state.
960     *
961     * @return boolean to indicate operation success or fail
962     */
963    public synchronized boolean setTrust(String address, boolean value) {
964        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
965            mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
966            return false;
967        }
968
969        return setDevicePropertyBooleanNative(getObjectPathFromAddress(address), "Trusted",
970                value ? 1 : 0);
971    }
972
973    /**
974     * Gets the remote device trust state as boolean.
975     * Note: this value may be
976     * retrieved from cache if we retrieved the data before *
977     *
978     * @return boolean to indicate trust or untrust state
979     */
980    public synchronized boolean getTrustState(String address) {
981        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
982            mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
983            return false;
984        }
985
986        String val = getRemoteDeviceProperty(address, "Trusted");
987        if (val == null) {
988            return false;
989        } else {
990            return val.equals("true") ? true : false;
991        }
992    }
993
994    /**
995     * Gets the remote major, minor classes encoded as a 32-bit
996     * integer.
997     *
998     * Note: this value is retrieved from cache, because we get it during
999     *       remote-device discovery.
1000     *
1001     * @return 32-bit integer encoding the remote major, minor, and service
1002     *         classes.
1003     */
1004    public synchronized int getRemoteClass(String address) {
1005        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
1006            mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
1007            return BluetoothClass.ERROR;
1008        }
1009        String val = getRemoteDeviceProperty(address, "Class");
1010        if (val == null)
1011            return BluetoothClass.ERROR;
1012        else {
1013            return Integer.valueOf(val);
1014        }
1015    }
1016
1017
1018    /**
1019     * Gets the UUIDs supported by the remote device
1020     *
1021     * @return array of 128bit ParcelUuids
1022     */
1023    public synchronized ParcelUuid[] getRemoteUuids(String address) {
1024        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
1025        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
1026            return null;
1027        }
1028        return getUuidFromCache(address);
1029    }
1030
1031    private ParcelUuid[] getUuidFromCache(String address) {
1032        String value = getRemoteDeviceProperty(address, "UUIDs");
1033        if (value == null) return null;
1034
1035        String[] uuidStrings = null;
1036        // The UUIDs are stored as a "," separated string.
1037        uuidStrings = value.split(",");
1038        ParcelUuid[] uuids = new ParcelUuid[uuidStrings.length];
1039
1040        for (int i = 0; i < uuidStrings.length; i++) {
1041            uuids[i] = ParcelUuid.fromString(uuidStrings[i]);
1042        }
1043        return uuids;
1044    }
1045
1046    public synchronized boolean fetchRemoteUuidsWithSdp(String address) {
1047        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
1048        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
1049            return false;
1050        }
1051
1052        if (mUuidIntentTracker.contains(address)) {
1053            // An SDP query for this address is already in progress
1054            return true;
1055        }
1056
1057        boolean ret;
1058        if (getBondState(address) == BluetoothDevice.BOND_BONDED) {
1059            String path = getObjectPathFromAddress(address);
1060            if (path == null) return false;
1061
1062            // Use an empty string for the UUID pattern
1063            ret = discoverServicesNative(path, "");
1064        } else {
1065            ret = createDeviceNative(address);
1066        }
1067
1068        mUuidIntentTracker.add(address);
1069
1070        Message message = mHandler.obtainMessage(MESSAGE_UUID_INTENT);
1071        message.obj = address;
1072        mHandler.sendMessageDelayed(message, UUID_INTENT_DELAY);
1073        return ret;
1074    }
1075
1076    /**
1077     * Gets the rfcomm channel associated with the UUID.
1078     *
1079     * @param address Address of the remote device
1080     * @param uuid ParcelUuid of the service attribute
1081     *
1082     * @return rfcomm channel associated with the service attribute
1083     *         -1 on error
1084     */
1085    public int getRemoteServiceChannel(String address, ParcelUuid uuid) {
1086        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
1087        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
1088            return BluetoothDevice.ERROR;
1089        }
1090        // Check if we are recovering from a crash.
1091        if (mDeviceProperties.isEmpty()) {
1092            if (!updateRemoteDevicePropertiesCache(address))
1093                return -1;
1094        }
1095
1096        Map<ParcelUuid, Integer> value = mDeviceServiceChannelCache.get(address);
1097        if (value != null && value.containsKey(uuid))
1098            return value.get(uuid);
1099        return -1;
1100    }
1101
1102    public synchronized boolean setPin(String address, byte[] pin) {
1103        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
1104                                                "Need BLUETOOTH_ADMIN permission");
1105        if (pin == null || pin.length <= 0 || pin.length > 16 ||
1106            !BluetoothAdapter.checkBluetoothAddress(address)) {
1107            return false;
1108        }
1109        address = address.toUpperCase();
1110        Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address);
1111        if (data == null) {
1112            Log.w(TAG, "setPin(" + address + ") called but no native data available, " +
1113                  "ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" +
1114                  " or by bluez.\n");
1115            return false;
1116        }
1117        // bluez API wants pin as a string
1118        String pinString;
1119        try {
1120            pinString = new String(pin, "UTF8");
1121        } catch (UnsupportedEncodingException uee) {
1122            Log.e(TAG, "UTF8 not supported?!?");
1123            return false;
1124        }
1125        return setPinNative(address, pinString, data.intValue());
1126    }
1127
1128    public synchronized boolean setPasskey(String address, int passkey) {
1129        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
1130                                                "Need BLUETOOTH_ADMIN permission");
1131        if (passkey < 0 || passkey > 999999 || !BluetoothAdapter.checkBluetoothAddress(address)) {
1132            return false;
1133        }
1134        address = address.toUpperCase();
1135        Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address);
1136        if (data == null) {
1137            Log.w(TAG, "setPasskey(" + address + ") called but no native data available, " +
1138                  "ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" +
1139                  " or by bluez.\n");
1140            return false;
1141        }
1142        return setPasskeyNative(address, passkey, data.intValue());
1143    }
1144
1145    public synchronized boolean setPairingConfirmation(String address, boolean confirm) {
1146        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
1147                                                "Need BLUETOOTH_ADMIN permission");
1148        address = address.toUpperCase();
1149        Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address);
1150        if (data == null) {
1151            Log.w(TAG, "setPasskey(" + address + ") called but no native data available, " +
1152                  "ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" +
1153                  " or by bluez.\n");
1154            return false;
1155        }
1156        return setPairingConfirmationNative(address, confirm, data.intValue());
1157    }
1158
1159    public synchronized boolean cancelPairingUserInput(String address) {
1160        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
1161                                                "Need BLUETOOTH_ADMIN permission");
1162        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
1163            return false;
1164        }
1165        mBondState.setBondState(address, BluetoothDevice.BOND_NONE,
1166                BluetoothDevice.UNBOND_REASON_AUTH_CANCELED);
1167        address = address.toUpperCase();
1168        Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address);
1169        if (data == null) {
1170            Log.w(TAG, "cancelUserInputNative(" + address + ") called but no native data " +
1171                "available, ignoring. Maybe the PasskeyAgent Request was already cancelled " +
1172                "by the remote or by bluez.\n");
1173            return false;
1174        }
1175        return cancelPairingUserInputNative(address, data.intValue());
1176    }
1177
1178    public void updateDeviceServiceChannelCache(String address) {
1179        ParcelUuid[] deviceUuids = getRemoteUuids(address);
1180        // We are storing the rfcomm channel numbers only for the uuids
1181        // we are interested in.
1182        int channel;
1183        ParcelUuid[] interestedUuids = {BluetoothUuid.Handsfree,
1184                                        BluetoothUuid.HSP,
1185                                        BluetoothUuid.ObexObjectPush};
1186
1187        Map <ParcelUuid, Integer> value = new HashMap<ParcelUuid, Integer>();
1188        for (ParcelUuid uuid: interestedUuids) {
1189            if (BluetoothUuid.isUuidPresent(deviceUuids, uuid)) {
1190                channel =
1191                   getDeviceServiceChannelNative(getObjectPathFromAddress(address), uuid.toString(),
1192                                                 0x0004);
1193                value.put(uuid, channel);
1194            }
1195        }
1196        mDeviceServiceChannelCache.put(address, value);
1197    }
1198
1199    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
1200        @Override
1201        public void onReceive(Context context, Intent intent) {
1202            String action = intent.getAction();
1203            if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
1204                ContentResolver resolver = context.getContentResolver();
1205                // Query the airplane mode from Settings.System just to make sure that
1206                // some random app is not sending this intent and disabling bluetooth
1207                boolean enabled = !isAirplaneModeOn();
1208                // If bluetooth is currently expected to be on, then enable or disable bluetooth
1209                if (Settings.Secure.getInt(resolver, Settings.Secure.BLUETOOTH_ON, 0) > 0) {
1210                    if (enabled) {
1211                        enable(false);
1212                    } else {
1213                        disable(false);
1214                    }
1215                }
1216            }
1217        }
1218    };
1219
1220    private void registerForAirplaneMode() {
1221        String airplaneModeRadios = Settings.System.getString(mContext.getContentResolver(),
1222                Settings.System.AIRPLANE_MODE_RADIOS);
1223        mIsAirplaneSensitive = airplaneModeRadios == null
1224                ? true : airplaneModeRadios.contains(Settings.System.RADIO_BLUETOOTH);
1225        if (mIsAirplaneSensitive) {
1226            mIntentFilter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
1227            mContext.registerReceiver(mReceiver, mIntentFilter);
1228        }
1229    }
1230
1231    /* Returns true if airplane mode is currently on */
1232    private final boolean isAirplaneModeOn() {
1233        return Settings.System.getInt(mContext.getContentResolver(),
1234                Settings.System.AIRPLANE_MODE_ON, 0) == 1;
1235    }
1236
1237    /* Broadcast the Uuid intent */
1238    /*package*/ synchronized void sendUuidIntent(String address) {
1239        ParcelUuid[] uuid = getUuidFromCache(address);
1240        Intent intent = new Intent(BluetoothDevice.ACTION_UUID);
1241        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
1242        intent.putExtra(BluetoothDevice.EXTRA_UUID, uuid);
1243        mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
1244
1245        if (mUuidIntentTracker.contains(address))
1246            mUuidIntentTracker.remove(address);
1247    }
1248
1249    @Override
1250    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1251        pw.println("\nmIsAirplaneSensitive = " + mIsAirplaneSensitive + "\n");
1252
1253        switch(mBluetoothState) {
1254        case BluetoothAdapter.STATE_OFF:
1255            pw.println("\nBluetooth OFF\n");
1256            return;
1257        case BluetoothAdapter.STATE_TURNING_ON:
1258            pw.println("\nBluetooth TURNING ON\n");
1259            return;
1260        case BluetoothAdapter.STATE_TURNING_OFF:
1261            pw.println("\nBluetooth TURNING OFF\n");
1262            return;
1263        case BluetoothAdapter.STATE_ON:
1264            pw.println("\nBluetooth ON\n");
1265        }
1266
1267        pw.println("\nLocal address = " + getAddress());
1268        pw.println("\nLocal name = " + getName());
1269        pw.println("\nisDiscovering() = " + isDiscovering());
1270
1271        BluetoothHeadset headset = new BluetoothHeadset(mContext, null);
1272
1273        pw.println("\n--Known devices--");
1274        for (String address : mDeviceProperties.keySet()) {
1275            int bondState = mBondState.getBondState(address);
1276            pw.printf("%s %10s (%d) %s\n", address,
1277                       toBondStateString(bondState),
1278                       mBondState.getAttempt(address),
1279                       getRemoteName(address));
1280            if (bondState == BluetoothDevice.BOND_BONDED) {
1281                ParcelUuid[] uuids = getRemoteUuids(address);
1282                if (uuids == null) {
1283                    pw.printf("\tuuids = null\n");
1284                } else {
1285                    for (ParcelUuid uuid : uuids) {
1286                        pw.printf("\t" + uuid + "\n");
1287                    }
1288                }
1289            }
1290        }
1291
1292        String value = getProperty("Devices");
1293        String[] devicesObjectPath = null;
1294        if (value != null) {
1295            devicesObjectPath = value.split(",");
1296        }
1297        pw.println("\n--ACL connected devices--");
1298        for (String device : devicesObjectPath) {
1299            pw.println(getAddressFromObjectPath(device));
1300        }
1301
1302        // Rather not do this from here, but no-where else and I need this
1303        // dump
1304        pw.println("\n--Headset Service--");
1305        switch (headset.getState()) {
1306        case BluetoothHeadset.STATE_DISCONNECTED:
1307            pw.println("getState() = STATE_DISCONNECTED");
1308            break;
1309        case BluetoothHeadset.STATE_CONNECTING:
1310            pw.println("getState() = STATE_CONNECTING");
1311            break;
1312        case BluetoothHeadset.STATE_CONNECTED:
1313            pw.println("getState() = STATE_CONNECTED");
1314            break;
1315        case BluetoothHeadset.STATE_ERROR:
1316            pw.println("getState() = STATE_ERROR");
1317            break;
1318        }
1319        pw.println("getCurrentHeadset() = " + headset.getCurrentHeadset());
1320        pw.println("getBatteryUsageHint() = " + headset.getBatteryUsageHint());
1321
1322        headset.close();
1323    }
1324
1325    /* package */ static int bluezStringToScanMode(boolean pairable, boolean discoverable) {
1326        if (pairable && discoverable)
1327            return BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
1328        else if (pairable && !discoverable)
1329            return BluetoothAdapter.SCAN_MODE_CONNECTABLE;
1330        else
1331            return BluetoothAdapter.SCAN_MODE_NONE;
1332    }
1333
1334    /* package */ static String scanModeToBluezString(int mode) {
1335        switch (mode) {
1336        case BluetoothAdapter.SCAN_MODE_NONE:
1337            return "off";
1338        case BluetoothAdapter.SCAN_MODE_CONNECTABLE:
1339            return "connectable";
1340        case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
1341            return "discoverable";
1342        }
1343        return null;
1344    }
1345
1346    /*package*/ String getAddressFromObjectPath(String objectPath) {
1347        String adapterObjectPath = getProperty("ObjectPath");
1348        if (adapterObjectPath == null || objectPath == null) {
1349            Log.e(TAG, "getAddressFromObjectPath: AdpaterObjectPath:" + adapterObjectPath +
1350                    "  or deviceObjectPath:" + objectPath + " is null");
1351            return null;
1352        }
1353        if (!objectPath.startsWith(adapterObjectPath)) {
1354            Log.e(TAG, "getAddressFromObjectPath: AdpaterObjectPath:" + adapterObjectPath +
1355                    "  is not a prefix of deviceObjectPath:" + objectPath +
1356                    "bluetoothd crashed ?");
1357            return null;
1358        }
1359        String address = objectPath.substring(adapterObjectPath.length());
1360        if (address != null) return address.replace('_', ':');
1361
1362        Log.e(TAG, "getAddressFromObjectPath: Address being returned is null");
1363        return null;
1364    }
1365
1366    /*package*/ String getObjectPathFromAddress(String address) {
1367        String path = getProperty("ObjectPath");
1368        if (path == null) {
1369            Log.e(TAG, "Error: Object Path is null");
1370            return null;
1371        }
1372        path = path + address.replace(":", "_");
1373        return path;
1374    }
1375
1376    private static void log(String msg) {
1377        Log.d(TAG, msg);
1378    }
1379
1380    private native static void classInitNative();
1381    private native void initializeNativeDataNative();
1382    private native boolean setupNativeDataNative();
1383    private native boolean tearDownNativeDataNative();
1384    private native void cleanupNativeDataNative();
1385    private native String getAdapterPathNative();
1386
1387    private native int isEnabledNative();
1388    private native int enableNative();
1389    private native int disableNative();
1390
1391    private native Object[] getAdapterPropertiesNative();
1392    private native Object[] getDevicePropertiesNative(String objectPath);
1393    private native boolean setAdapterPropertyStringNative(String key, String value);
1394    private native boolean setAdapterPropertyIntegerNative(String key, int value);
1395    private native boolean setAdapterPropertyBooleanNative(String key, int value);
1396
1397    private native boolean startDiscoveryNative();
1398    private native boolean stopDiscoveryNative();
1399
1400    private native boolean createPairedDeviceNative(String address, int timeout_ms);
1401    private native boolean cancelDeviceCreationNative(String address);
1402    private native boolean removeDeviceNative(String objectPath);
1403    private native int getDeviceServiceChannelNative(String objectPath, String uuid,
1404            int attributeId);
1405
1406    private native boolean cancelPairingUserInputNative(String address, int nativeData);
1407    private native boolean setPinNative(String address, String pin, int nativeData);
1408    private native boolean setPasskeyNative(String address, int passkey, int nativeData);
1409    private native boolean setPairingConfirmationNative(String address, boolean confirm,
1410            int nativeData);
1411    private native boolean setDevicePropertyBooleanNative(String objectPath, String key, int value);
1412    private native boolean createDeviceNative(String address);
1413    private native boolean discoverServicesNative(String objectPath, String pattern);
1414
1415}
1416