BluetoothEventLoop.java revision 9519ce75f15ba287a641166c1b7ed10f2aa73f74
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
17package android.server;
18
19import android.bluetooth.BluetoothA2dp;
20import android.bluetooth.BluetoothClass;
21import android.bluetooth.BluetoothAdapter;
22import android.bluetooth.BluetoothDevice;
23import android.bluetooth.BluetoothError;
24import android.bluetooth.BluetoothIntent;
25import android.bluetooth.BluetoothUuid;
26import android.content.Context;
27import android.content.Intent;
28import android.os.Handler;
29import android.os.Message;
30import android.util.Log;
31
32import java.util.HashMap;
33import java.util.UUID;
34
35/**
36 * TODO: Move this to
37 * java/services/com/android/server/BluetoothEventLoop.java
38 * and make the contructor package private again.
39 *
40 * @hide
41 */
42class BluetoothEventLoop {
43    private static final String TAG = "BluetoothEventLoop";
44    private static final boolean DBG = false;
45
46    private int mNativeData;
47    private Thread mThread;
48    private boolean mStarted;
49    private boolean mInterrupted;
50
51    private final HashMap<String, Integer> mPasskeyAgentRequestData;
52    private final BluetoothService mBluetoothService;
53    private final BluetoothAdapter mAdapter;
54    private final Context mContext;
55
56    private static final int EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 1;
57    private static final int EVENT_RESTART_BLUETOOTH = 2;
58
59    // The time (in millisecs) to delay the pairing attempt after the first
60    // auto pairing attempt fails. We use an exponential delay with
61    // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the initial value and
62    // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the max value.
63    private static final long INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 3000;
64    private static final long MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 12000;
65
66    private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
67    private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
68
69    private final Handler mHandler = new Handler() {
70        @Override
71        public void handleMessage(Message msg) {
72            switch (msg.what) {
73            case EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY:
74                String address = (String)msg.obj;
75                if (address != null) {
76                    mBluetoothService.createBond(address);
77                    return;
78                }
79                break;
80            case EVENT_RESTART_BLUETOOTH:
81                mBluetoothService.restart();
82                break;
83            }
84        }
85    };
86
87    static { classInitNative(); }
88    private static native void classInitNative();
89
90    /* pacakge */ BluetoothEventLoop(Context context, BluetoothAdapter adapter,
91            BluetoothService bluetoothService) {
92        mBluetoothService = bluetoothService;
93        mContext = context;
94        mPasskeyAgentRequestData = new HashMap();
95        mAdapter = adapter;
96        initializeNativeDataNative();
97    }
98
99    protected void finalize() throws Throwable {
100        try {
101            cleanupNativeDataNative();
102        } finally {
103            super.finalize();
104        }
105    }
106
107    /* package */ HashMap<String, Integer> getPasskeyAgentRequestData() {
108        return mPasskeyAgentRequestData;
109    }
110
111    /* package */ void start() {
112
113        if (!isEventLoopRunningNative()) {
114            if (DBG) log("Starting Event Loop thread");
115            startEventLoopNative();
116        }
117    }
118
119    public void stop() {
120        if (isEventLoopRunningNative()) {
121            if (DBG) log("Stopping Event Loop thread");
122            stopEventLoopNative();
123        }
124    }
125
126    public boolean isEventLoopRunning() {
127        return isEventLoopRunningNative();
128    }
129
130    private void addDevice(String address, String[] properties) {
131        mBluetoothService.addRemoteDeviceProperties(address, properties);
132        String rssi = mBluetoothService.getRemoteDeviceProperty(address, "RSSI");
133        String classValue = mBluetoothService.getRemoteDeviceProperty(address, "Class");
134        String name = mBluetoothService.getRemoteDeviceProperty(address, "Name");
135        short rssiValue;
136        // For incoming connections, we don't get the RSSI value. Use a default of MIN_VALUE.
137        // If we accept the pairing, we will automatically show it at the top of the list.
138        if (rssi != null) {
139            rssiValue = (short)Integer.valueOf(rssi).intValue();
140        } else {
141            rssiValue = Short.MIN_VALUE;
142        }
143        if (classValue != null) {
144            Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_FOUND_ACTION);
145            intent.putExtra(BluetoothIntent.DEVICE, mAdapter.getRemoteDevice(address));
146            intent.putExtra(BluetoothIntent.CLASS, Integer.valueOf(classValue));
147            intent.putExtra(BluetoothIntent.RSSI, rssiValue);
148            intent.putExtra(BluetoothIntent.NAME, name);
149
150            mContext.sendBroadcast(intent, BLUETOOTH_PERM);
151        } else {
152            log ("ClassValue: " + classValue + " for remote device: " + address + " is null");
153        }
154    }
155
156    private void onDeviceFound(String address, String[] properties) {
157        if (properties == null) {
158            Log.e(TAG, "ERROR: Remote device properties are null");
159            return;
160        }
161        addDevice(address, properties);
162    }
163
164    private void onDeviceDisappeared(String address) {
165        Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_DISAPPEARED_ACTION);
166        intent.putExtra(BluetoothIntent.DEVICE, mAdapter.getRemoteDevice(address));
167        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
168    }
169
170    private void onCreatePairedDeviceResult(String address, int result) {
171        address = address.toUpperCase();
172        if (result == BluetoothError.SUCCESS) {
173            mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDED);
174            if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) {
175                mBluetoothService.getBondState().clearPinAttempts(address);
176            }
177        } else if (result == BluetoothDevice.UNBOND_REASON_AUTH_FAILED &&
178                mBluetoothService.getBondState().getAttempt(address) == 1) {
179            mBluetoothService.getBondState().addAutoPairingFailure(address);
180            pairingAttempt(address, result);
181        } else if (result == BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN &&
182                mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) {
183            pairingAttempt(address, result);
184        } else {
185            mBluetoothService.getBondState().setBondState(address,
186                                                          BluetoothDevice.BOND_NOT_BONDED, result);
187            if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) {
188                mBluetoothService.getBondState().clearPinAttempts(address);
189            }
190        }
191    }
192
193    private void pairingAttempt(String address, int result) {
194        // This happens when our initial guess of "0000" as the pass key
195        // fails. Try to create the bond again and display the pin dialog
196        // to the user. Use back-off while posting the delayed
197        // message. The initial value is
198        // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY and the max value is
199        // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY. If the max value is
200        // reached, display an error to the user.
201        int attempt = mBluetoothService.getBondState().getAttempt(address);
202        if (attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY >
203                    MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY) {
204            mBluetoothService.getBondState().clearPinAttempts(address);
205            mBluetoothService.getBondState().setBondState(address,
206                    BluetoothDevice.BOND_NOT_BONDED, result);
207            return;
208        }
209
210        Message message = mHandler.obtainMessage(EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY);
211        message.obj = address;
212        boolean postResult =  mHandler.sendMessageDelayed(message,
213                                        attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY);
214        if (!postResult) {
215            mBluetoothService.getBondState().clearPinAttempts(address);
216            mBluetoothService.getBondState().setBondState(address,
217                    BluetoothDevice.BOND_NOT_BONDED, result);
218            return;
219        }
220        mBluetoothService.getBondState().attempt(address);
221    }
222
223    private void onDeviceCreated(String deviceObjectPath) {
224        String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
225        if (!mBluetoothService.isRemoteDeviceInCache(address)) {
226            // Incoming connection, we haven't seen this device, add to cache.
227            String[] properties = mBluetoothService.getRemoteDeviceProperties(address);
228            if (properties != null) {
229                addDevice(address, properties);
230            }
231        }
232        return;
233    }
234
235    private void onDeviceRemoved(String deviceObjectPath) {
236        String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
237        if (address != null)
238            mBluetoothService.getBondState().setBondState(address.toUpperCase(),
239                    BluetoothDevice.BOND_NOT_BONDED, BluetoothDevice.UNBOND_REASON_REMOVED);
240    }
241
242    /*package*/ void onPropertyChanged(String[] propValues) {
243        if (mBluetoothService.isAdapterPropertiesEmpty()) {
244            // We have got a property change before
245            // we filled up our cache.
246            mBluetoothService.getAllProperties();
247        }
248        String name = propValues[0];
249        if (name.equals("Name")) {
250            Intent intent = new Intent(BluetoothIntent.NAME_CHANGED_ACTION);
251            intent.putExtra(BluetoothIntent.NAME, propValues[1]);
252            mContext.sendBroadcast(intent, BLUETOOTH_PERM);
253            mBluetoothService.setProperty(name, propValues[1]);
254        } else if (name.equals("Pairable") || name.equals("Discoverable")) {
255            String pairable = name.equals("Pairable") ? propValues[1] :
256                mBluetoothService.getProperty("Pairable");
257            String discoverable = name.equals("Discoverable") ? propValues[1] :
258                mBluetoothService.getProperty("Discoverable");
259
260            // This shouldn't happen, unless Adapter Properties are null.
261            if (pairable == null || discoverable == null)
262                return;
263
264            int mode = BluetoothService.bluezStringToScanMode(
265                    pairable.equals("true"),
266                    discoverable.equals("true"));
267            if (mode >= 0) {
268                Intent intent = new Intent(BluetoothIntent.SCAN_MODE_CHANGED_ACTION);
269                intent.putExtra(BluetoothIntent.SCAN_MODE, mode);
270                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
271                mContext.sendBroadcast(intent, BLUETOOTH_PERM);
272            }
273            mBluetoothService.setProperty(name, propValues[1]);
274        } else if (name.equals("Discovering")) {
275            Intent intent;
276            if (propValues[1].equals("true")) {
277                mBluetoothService.setIsDiscovering(true);
278                intent = new Intent(BluetoothIntent.DISCOVERY_STARTED_ACTION);
279            } else {
280                // Stop the discovery.
281                mBluetoothService.cancelDiscovery();
282                mBluetoothService.setIsDiscovering(false);
283                intent = new Intent(BluetoothIntent.DISCOVERY_COMPLETED_ACTION);
284            }
285            mContext.sendBroadcast(intent, BLUETOOTH_PERM);
286            mBluetoothService.setProperty(name, propValues[1]);
287        } else if (name.equals("Devices")) {
288            String value = null;
289            int len = Integer.valueOf(propValues[1]);
290            if (len > 0) {
291                StringBuilder str = new StringBuilder();
292                for (int i = 2; i < propValues.length; i++) {
293                    str.append(propValues[i]);
294                    str.append(",");
295                }
296                value = str.toString();
297            }
298            mBluetoothService.setProperty(name, value);
299        } else if (name.equals("Powered")) {
300            // bluetoothd has restarted, re-read all our properties.
301            // Note: bluez only sends this property change when it restarts.
302            if (propValues[1].equals("true"))
303                onRestartRequired();
304        }
305    }
306
307    private void onDevicePropertyChanged(String deviceObjectPath, String[] propValues) {
308        String name = propValues[0];
309        String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
310        if (address == null) {
311            Log.e(TAG, "onDevicePropertyChanged: Address of the remote device in null");
312            return;
313        }
314        BluetoothDevice device = mAdapter.getRemoteDevice(address);
315        if (name.equals("Name")) {
316            Intent intent = new Intent(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION);
317            intent.putExtra(BluetoothIntent.DEVICE, device);
318            intent.putExtra(BluetoothIntent.NAME, propValues[1]);
319            mContext.sendBroadcast(intent, BLUETOOTH_PERM);
320            mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
321        } else if (name.equals("Class")) {
322            Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_CLASS_UPDATED_ACTION);
323            intent.putExtra(BluetoothIntent.DEVICE, device);
324            intent.putExtra(BluetoothIntent.CLASS, propValues[1]);
325            mContext.sendBroadcast(intent, BLUETOOTH_PERM);
326            mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
327        } else if (name.equals("Connected")) {
328            Intent intent = null;
329            if (propValues[1].equals("true")) {
330                intent = new Intent(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION);
331            } else {
332                intent = new Intent(BluetoothIntent.REMOTE_DEVICE_DISCONNECTED_ACTION);
333            }
334            intent.putExtra(BluetoothIntent.DEVICE, device);
335            mContext.sendBroadcast(intent, BLUETOOTH_PERM);
336            mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
337        } else if (name.equals("UUIDs")) {
338            String uuid = null;
339            int len = Integer.valueOf(propValues[1]);
340            if (len > 0) {
341                StringBuilder str = new StringBuilder();
342                for (int i = 2; i < propValues.length; i++) {
343                    str.append(propValues[i]);
344                    str.append(",");
345                }
346                uuid = str.toString();
347            }
348            mBluetoothService.setRemoteDeviceProperty(address, name, uuid);
349        } else if (name.equals("Paired")) {
350            if (propValues[1].equals("true")) {
351                mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDED);
352            } else {
353                mBluetoothService.getBondState().setBondState(address,
354                        BluetoothDevice.BOND_NOT_BONDED);
355                mBluetoothService.setRemoteDeviceProperty(address, "Trusted", "false");
356            }
357        } else if (name.equals("Trusted")) {
358            if (DBG)
359                log("set trust state succeded, value is  " + propValues[1]);
360            mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
361        }
362    }
363
364    private String checkPairingRequestAndGetAddress(String objectPath, int nativeData) {
365        String address = mBluetoothService.getAddressFromObjectPath(objectPath);
366        if (address == null) {
367            Log.e(TAG, "Unable to get device address in checkPairingRequestAndGetAddress, " +
368                  "returning null");
369            return null;
370        }
371        address = address.toUpperCase();
372        mPasskeyAgentRequestData.put(address, new Integer(nativeData));
373
374        if (mBluetoothService.getBluetoothState() == BluetoothAdapter.BLUETOOTH_STATE_TURNING_OFF) {
375            // shutdown path
376            mBluetoothService.cancelPairingUserInput(address);
377            return null;
378        }
379        return address;
380    }
381
382    private void onRequestConfirmation(String objectPath, int passkey, int nativeData) {
383        String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
384        if (address == null) return;
385
386        Intent intent = new Intent(BluetoothIntent.PAIRING_REQUEST_ACTION);
387        intent.putExtra(BluetoothIntent.DEVICE, mAdapter.getRemoteDevice(address));
388        intent.putExtra(BluetoothIntent.PASSKEY, passkey);
389        intent.putExtra(BluetoothIntent.PAIRING_VARIANT,
390                BluetoothDevice.PAIRING_VARIANT_CONFIRMATION);
391        mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
392        return;
393    }
394
395    private void onRequestPasskey(String objectPath, int nativeData) {
396        String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
397        if (address == null) return;
398
399        Intent intent = new Intent(BluetoothIntent.PAIRING_REQUEST_ACTION);
400        intent.putExtra(BluetoothIntent.DEVICE, mAdapter.getRemoteDevice(address));
401        intent.putExtra(BluetoothIntent.PAIRING_VARIANT, BluetoothDevice.PAIRING_VARIANT_PASSKEY);
402        mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
403        return;
404    }
405
406    private void onRequestPinCode(String objectPath, int nativeData) {
407        String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
408        if (address == null) return;
409
410        if (mBluetoothService.getBondState().getBondState(address) ==
411                BluetoothDevice.BOND_BONDING) {
412            // we initiated the bonding
413            int btClass = mBluetoothService.getRemoteClass(address);
414
415            // try 0000 once if the device looks dumb
416            switch (BluetoothClass.Device.getDevice(btClass)) {
417            case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
418            case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
419            case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES:
420            case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO:
421            case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
422            case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO:
423                if (!mBluetoothService.getBondState().hasAutoPairingFailed(address) &&
424                    !mBluetoothService.getBondState().isAutoPairingBlacklisted(address)) {
425                    mBluetoothService.getBondState().attempt(address);
426                    mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes("0000"));
427                    return;
428                }
429           }
430        }
431        Intent intent = new Intent(BluetoothIntent.PAIRING_REQUEST_ACTION);
432        intent.putExtra(BluetoothIntent.DEVICE, mAdapter.getRemoteDevice(address));
433        intent.putExtra(BluetoothIntent.PAIRING_VARIANT, BluetoothDevice.PAIRING_VARIANT_PIN);
434        mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
435        return;
436    }
437
438    private boolean onAgentAuthorize(String objectPath, String deviceUuid) {
439        String address = mBluetoothService.getAddressFromObjectPath(objectPath);
440        if (address == null) {
441            Log.e(TAG, "Unable to get device address in onAuthAgentAuthorize");
442            return false;
443        }
444
445        boolean authorized = false;
446        UUID uuid = UUID.fromString(deviceUuid);
447        // Bluez sends the UUID of the local service being accessed, _not_ the
448        // remote service
449        if (mBluetoothService.isEnabled() &&
450                (BluetoothUuid.isAudioSource(uuid) || BluetoothUuid.isAvrcpTarget(uuid)
451                        || BluetoothUuid.isAdvAudioDist(uuid))) {
452            BluetoothA2dp a2dp = new BluetoothA2dp(mContext);
453            BluetoothDevice device = mAdapter.getRemoteDevice(address);
454            authorized = a2dp.getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF;
455            if (authorized) {
456                Log.i(TAG, "Allowing incoming A2DP / AVRCP connection from " + address);
457            } else {
458                Log.i(TAG, "Rejecting incoming A2DP / AVRCP connection from " + address);
459            }
460        } else {
461            Log.i(TAG, "Rejecting incoming " + deviceUuid + " connection from " + address);
462        }
463        log("onAgentAuthorize(" + objectPath + ", " + deviceUuid + ") = " + authorized);
464        return authorized;
465    }
466
467    private void onAgentCancel() {
468        Intent intent = new Intent(BluetoothIntent.PAIRING_CANCEL_ACTION);
469        mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
470        return;
471    }
472
473    private void onRestartRequired() {
474        if (mBluetoothService.isEnabled()) {
475            Log.e(TAG, "*** A serious error occured (did bluetoothd crash?) - " +
476                       "restarting Bluetooth ***");
477            mHandler.sendEmptyMessage(EVENT_RESTART_BLUETOOTH);
478        }
479    }
480
481    private static void log(String msg) {
482        Log.d(TAG, msg);
483    }
484
485    private native void initializeNativeDataNative();
486    private native void startEventLoopNative();
487    private native void stopEventLoopNative();
488    private native boolean isEventLoopRunningNative();
489    private native void cleanupNativeDataNative();
490}
491