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.BluetoothDevice;
22import android.bluetooth.BluetoothError;
23import android.bluetooth.BluetoothIntent;
24import android.bluetooth.IBluetoothDeviceCallback;
25import android.content.Context;
26import android.content.Intent;
27import android.os.Handler;
28import android.os.Message;
29import android.os.RemoteException;
30import android.util.Log;
31
32import java.util.HashMap;
33
34/**
35 * TODO: Move this to
36 * java/services/com/android/server/BluetoothEventLoop.java
37 * and make the contructor package private again.
38 *
39 * @hide
40 */
41class BluetoothEventLoop {
42    private static final String TAG = "BluetoothEventLoop";
43    private static final boolean DBG = false;
44
45    private int mNativeData;
46    private Thread mThread;
47    private boolean mStarted;
48    private boolean mInterrupted;
49    private final HashMap<String, Integer> mPasskeyAgentRequestData;
50    private final HashMap<String, IBluetoothDeviceCallback> mGetRemoteServiceChannelCallbacks;
51    private final BluetoothDeviceService mBluetoothService;
52    private final Context mContext;
53
54    private static final int EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 1;
55    private static final int EVENT_RESTART_BLUETOOTH = 2;
56
57    // The time (in millisecs) to delay the pairing attempt after the first
58    // auto pairing attempt fails. We use an exponential delay with
59    // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the initial value and
60    // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the max value.
61    private static final long INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 3000;
62    private static final long MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 12000;
63
64    private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
65    private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
66
67    private final Handler mHandler = new Handler() {
68        @Override
69        public void handleMessage(Message msg) {
70            switch (msg.what) {
71            case EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY:
72                String address = (String)msg.obj;
73                if (address != null) {
74                    mBluetoothService.createBond(address);
75                    return;
76                }
77                break;
78            case EVENT_RESTART_BLUETOOTH:
79                mBluetoothService.restart();
80                break;
81            }
82        }
83    };
84
85    static { classInitNative(); }
86    private static native void classInitNative();
87
88    /* pacakge */ BluetoothEventLoop(Context context, BluetoothDeviceService bluetoothService) {
89        mBluetoothService = bluetoothService;
90        mContext = context;
91        mPasskeyAgentRequestData = new HashMap();
92        mGetRemoteServiceChannelCallbacks = new HashMap();
93        initializeNativeDataNative();
94    }
95    private native void initializeNativeDataNative();
96
97    protected void finalize() throws Throwable {
98        try {
99            cleanupNativeDataNative();
100        } finally {
101            super.finalize();
102        }
103    }
104    private native void cleanupNativeDataNative();
105
106    /* pacakge */ HashMap<String, IBluetoothDeviceCallback> getRemoteServiceChannelCallbacks() {
107        return mGetRemoteServiceChannelCallbacks;
108    }
109
110    /* pacakge */ HashMap<String, Integer> getPasskeyAgentRequestData() {
111        return mPasskeyAgentRequestData;
112    }
113
114    private native void startEventLoopNative();
115    private native void stopEventLoopNative();
116    private native boolean isEventLoopRunningNative();
117
118    /* package */ void start() {
119
120        if (!isEventLoopRunningNative()) {
121            if (DBG) log("Starting Event Loop thread");
122            startEventLoopNative();
123        }
124    }
125
126    public void stop() {
127        if (isEventLoopRunningNative()) {
128            if (DBG) log("Stopping Event Loop thread");
129            stopEventLoopNative();
130        }
131    }
132
133    public boolean isEventLoopRunning() {
134        return isEventLoopRunningNative();
135    }
136
137    /*package*/ void onModeChanged(String bluezMode) {
138        int mode = BluetoothDeviceService.bluezStringToScanMode(bluezMode);
139        if (mode >= 0) {
140            Intent intent = new Intent(BluetoothIntent.SCAN_MODE_CHANGED_ACTION);
141            intent.putExtra(BluetoothIntent.SCAN_MODE, mode);
142            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
143            mContext.sendBroadcast(intent, BLUETOOTH_PERM);
144        }
145    }
146
147    private void onDiscoveryStarted() {
148        mBluetoothService.setIsDiscovering(true);
149        Intent intent = new Intent(BluetoothIntent.DISCOVERY_STARTED_ACTION);
150        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
151    }
152    private void onDiscoveryCompleted() {
153        mBluetoothService.setIsDiscovering(false);
154        Intent intent = new Intent(BluetoothIntent.DISCOVERY_COMPLETED_ACTION);
155        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
156    }
157
158    private void onRemoteDeviceFound(String address, int deviceClass, short rssi) {
159        Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_FOUND_ACTION);
160        intent.putExtra(BluetoothIntent.ADDRESS, address);
161        intent.putExtra(BluetoothIntent.CLASS, deviceClass);
162        intent.putExtra(BluetoothIntent.RSSI, rssi);
163        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
164    }
165    private void onRemoteDeviceDisappeared(String address) {
166        Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_DISAPPEARED_ACTION);
167        intent.putExtra(BluetoothIntent.ADDRESS, address);
168        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
169    }
170    private void onRemoteClassUpdated(String address, int deviceClass) {
171        Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_CLASS_UPDATED_ACTION);
172        intent.putExtra(BluetoothIntent.ADDRESS, address);
173        intent.putExtra(BluetoothIntent.CLASS, deviceClass);
174        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
175    }
176    private void onRemoteDeviceConnected(String address) {
177        Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION);
178        intent.putExtra(BluetoothIntent.ADDRESS, address);
179        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
180    }
181    private void onRemoteDeviceDisconnectRequested(String address) {
182        Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_DISCONNECT_REQUESTED_ACTION);
183        intent.putExtra(BluetoothIntent.ADDRESS, address);
184        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
185    }
186    private void onRemoteDeviceDisconnected(String address) {
187        Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_DISCONNECTED_ACTION);
188        intent.putExtra(BluetoothIntent.ADDRESS, address);
189        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
190    }
191    private void onRemoteNameUpdated(String address, String name) {
192        Intent intent = new Intent(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION);
193        intent.putExtra(BluetoothIntent.ADDRESS, address);
194        intent.putExtra(BluetoothIntent.NAME, name);
195        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
196    }
197    private void onRemoteNameFailed(String address) {
198        Intent intent = new Intent(BluetoothIntent.REMOTE_NAME_FAILED_ACTION);
199        intent.putExtra(BluetoothIntent.ADDRESS, address);
200        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
201    }
202    private void onRemoteNameChanged(String address, String name) {
203        Intent intent = new Intent(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION);
204        intent.putExtra(BluetoothIntent.ADDRESS, address);
205        intent.putExtra(BluetoothIntent.NAME, name);
206        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
207    }
208
209    private void onCreateBondingResult(String address, int result) {
210        address = address.toUpperCase();
211        if (result == BluetoothError.SUCCESS) {
212            mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDED);
213            if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) {
214                mBluetoothService.getBondState().clearPinAttempts(address);
215            }
216        } else if (result == BluetoothDevice.UNBOND_REASON_AUTH_FAILED &&
217                mBluetoothService.getBondState().getAttempt(address) == 1) {
218            mBluetoothService.getBondState().addAutoPairingFailure(address);
219            pairingAttempt(address, result);
220        } else if (result == BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN &&
221                mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) {
222            pairingAttempt(address, result);
223        } else {
224            mBluetoothService.getBondState().setBondState(address,
225                                                          BluetoothDevice.BOND_NOT_BONDED, result);
226            if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) {
227                mBluetoothService.getBondState().clearPinAttempts(address);
228            }
229        }
230    }
231
232    private void pairingAttempt(String address, int result) {
233        // This happens when our initial guess of "0000" as the pass key
234        // fails. Try to create the bond again and display the pin dialog
235        // to the user. Use back-off while posting the delayed
236        // message. The initial value is
237        // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY and the max value is
238        // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY. If the max value is
239        // reached, display an error to the user.
240        int attempt = mBluetoothService.getBondState().getAttempt(address);
241        if (attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY >
242                    MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY) {
243            mBluetoothService.getBondState().clearPinAttempts(address);
244            mBluetoothService.getBondState().setBondState(address,
245                    BluetoothDevice.BOND_NOT_BONDED, result);
246            return;
247        }
248
249        Message message = mHandler.obtainMessage(EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY);
250        message.obj = address;
251        boolean postResult =  mHandler.sendMessageDelayed(message,
252                                        attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY);
253        if (!postResult) {
254            mBluetoothService.getBondState().clearPinAttempts(address);
255            mBluetoothService.getBondState().setBondState(address,
256                    BluetoothDevice.BOND_NOT_BONDED, result);
257            return;
258        }
259        mBluetoothService.getBondState().attempt(address);
260    }
261
262    private void onBondingCreated(String address) {
263        mBluetoothService.getBondState().setBondState(address.toUpperCase(),
264                                                      BluetoothDevice.BOND_BONDED);
265    }
266
267    private void onBondingRemoved(String address) {
268        mBluetoothService.getBondState().setBondState(address.toUpperCase(),
269                BluetoothDevice.BOND_NOT_BONDED, BluetoothDevice.UNBOND_REASON_REMOVED);
270    }
271
272    private void onNameChanged(String name) {
273        Intent intent = new Intent(BluetoothIntent.NAME_CHANGED_ACTION);
274        intent.putExtra(BluetoothIntent.NAME, name);
275        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
276    }
277
278    private void onPasskeyAgentRequest(String address, int nativeData) {
279        address = address.toUpperCase();
280        mPasskeyAgentRequestData.put(address, new Integer(nativeData));
281
282        if (mBluetoothService.getBluetoothState() == BluetoothDevice.BLUETOOTH_STATE_TURNING_OFF) {
283            // shutdown path
284            mBluetoothService.cancelPin(address);
285            return;
286        }
287
288        if (mBluetoothService.getBondState().getBondState(address) ==
289                BluetoothDevice.BOND_BONDING) {
290            // we initiated the bonding
291            int btClass = mBluetoothService.getRemoteClass(address);
292
293            // try 0000 once if the device looks dumb
294            switch (BluetoothClass.Device.getDevice(btClass)) {
295            case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
296            case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
297            case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES:
298            case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO:
299            case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
300            case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO:
301                if (!mBluetoothService.getBondState().hasAutoPairingFailed(address) &&
302                    !mBluetoothService.getBondState().isAutoPairingBlacklisted(address)) {
303                    mBluetoothService.getBondState().attempt(address);
304                    mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes("0000"));
305                    return;
306                }
307           }
308        }
309        Intent intent = new Intent(BluetoothIntent.PAIRING_REQUEST_ACTION);
310        intent.putExtra(BluetoothIntent.ADDRESS, address);
311        mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
312    }
313
314    private void onPasskeyAgentCancel(String address) {
315        address = address.toUpperCase();
316        mBluetoothService.cancelPin(address);
317        Intent intent = new Intent(BluetoothIntent.PAIRING_CANCEL_ACTION);
318        intent.putExtra(BluetoothIntent.ADDRESS, address);
319        mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
320        mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_NOT_BONDED,
321                                                      BluetoothDevice.UNBOND_REASON_AUTH_CANCELED);
322    }
323
324    private boolean onAuthAgentAuthorize(String address, String service, String uuid) {
325        boolean authorized = false;
326        if (mBluetoothService.isEnabled() && service.endsWith("service_audio")) {
327            BluetoothA2dp a2dp = new BluetoothA2dp(mContext);
328            authorized = a2dp.getSinkPriority(address) > BluetoothA2dp.PRIORITY_OFF;
329            if (authorized) {
330                Log.i(TAG, "Allowing incoming A2DP connection from " + address);
331            } else {
332                Log.i(TAG, "Rejecting incoming A2DP connection from " + address);
333            }
334        } else {
335            Log.i(TAG, "Rejecting incoming " + service + " connection from " + address);
336        }
337        return authorized;
338    }
339
340    private void onAuthAgentCancel(String address, String service, String uuid) {
341        // We immediately response to DBUS Authorize() so this should not
342        // usually happen
343        log("onAuthAgentCancel(" + address + ", " + service + ", " + uuid + ")");
344    }
345
346    private void onGetRemoteServiceChannelResult(String address, int channel) {
347        IBluetoothDeviceCallback callback = mGetRemoteServiceChannelCallbacks.get(address);
348        if (callback != null) {
349            mGetRemoteServiceChannelCallbacks.remove(address);
350            try {
351                callback.onGetRemoteServiceChannelResult(address, channel);
352            } catch (RemoteException e) {}
353        }
354    }
355
356    private void onRestartRequired() {
357        if (mBluetoothService.isEnabled()) {
358            Log.e(TAG, "*** A serious error occured (did hcid crash?) - restarting Bluetooth ***");
359            mHandler.sendEmptyMessage(EVENT_RESTART_BLUETOOTH);
360        }
361    }
362
363    private static void log(String msg) {
364        Log.d(TAG, msg);
365    }
366}
367