blob: 6be8eb96ba21d5d48419c1e5c9c543fe06794da1 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070047 private boolean mStarted;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080048 private boolean mInterrupted;
The Android Open Source Project10592532009-03-18 17:39:46 -070049 private final HashMap<String, Integer> mPasskeyAgentRequestData;
50 private final HashMap<String, IBluetoothDeviceCallback> mGetRemoteServiceChannelCallbacks;
51 private final BluetoothDeviceService mBluetoothService;
52 private final Context mContext;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053
54 private static final int EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 1;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070055 private static final int EVENT_RESTART_BLUETOOTH = 2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056
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;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070078 case EVENT_RESTART_BLUETOOTH:
79 mBluetoothService.disable();
The Android Open Source Project10592532009-03-18 17:39:46 -070080 mBluetoothService.enable();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070081 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080082 }
83 }
84 };
85
86 static { classInitNative(); }
87 private static native void classInitNative();
88
89 /* pacakge */ BluetoothEventLoop(Context context, BluetoothDeviceService bluetoothService) {
90 mBluetoothService = bluetoothService;
91 mContext = context;
92 mPasskeyAgentRequestData = new HashMap();
93 mGetRemoteServiceChannelCallbacks = new HashMap();
94 initializeNativeDataNative();
95 }
96 private native void initializeNativeDataNative();
97
98 protected void finalize() throws Throwable {
99 try {
100 cleanupNativeDataNative();
101 } finally {
102 super.finalize();
103 }
104 }
105 private native void cleanupNativeDataNative();
106
107 /* pacakge */ HashMap<String, IBluetoothDeviceCallback> getRemoteServiceChannelCallbacks() {
108 return mGetRemoteServiceChannelCallbacks;
109 }
110
111 /* pacakge */ HashMap<String, Integer> getPasskeyAgentRequestData() {
112 return mPasskeyAgentRequestData;
113 }
114
115 private synchronized boolean waitForAndDispatchEvent(int timeout_ms) {
116 return waitForAndDispatchEventNative(timeout_ms);
117 }
118 private native boolean waitForAndDispatchEventNative(int timeout_ms);
119
120 /* package */ synchronized void start() {
121
122 if (mThread != null) {
123 // Already running.
124 return;
125 }
126 mThread = new Thread("Bluetooth Event Loop") {
127 @Override
128 public void run() {
129 try {
130 if (setUpEventLoopNative()) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700131 mStarted = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800132 while (!mInterrupted) {
133 waitForAndDispatchEvent(0);
134 sleep(500);
135 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800136 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700137 // tear down even in the error case to clean
138 // up anything we started to setup
139 tearDownEventLoopNative();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800140 } catch (InterruptedException e) { }
141 if (DBG) log("Event Loop thread finished");
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700142 mThread = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800143 }
144 };
145 if (DBG) log("Starting Event Loop thread");
146 mInterrupted = false;
147 mThread.start();
148 }
149 private native boolean setUpEventLoopNative();
150 private native void tearDownEventLoopNative();
151
152 public synchronized void stop() {
153 if (mThread != null) {
154 mInterrupted = true;
155 try {
156 mThread.join();
157 mThread = null;
158 } catch (InterruptedException e) {
159 Log.i(TAG, "Interrupted waiting for Event Loop thread to join");
160 }
161 }
162 }
163
164 public synchronized boolean isEventLoopRunning() {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700165 return mThread != null && mStarted;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800166 }
167
168 /*package*/ void onModeChanged(String bluezMode) {
169 int mode = BluetoothDeviceService.bluezStringToScanMode(bluezMode);
170 if (mode >= 0) {
171 Intent intent = new Intent(BluetoothIntent.SCAN_MODE_CHANGED_ACTION);
172 intent.putExtra(BluetoothIntent.SCAN_MODE, mode);
173 mContext.sendBroadcast(intent, BLUETOOTH_PERM);
174 }
175 }
176
177 private void onDiscoveryStarted() {
178 mBluetoothService.setIsDiscovering(true);
179 Intent intent = new Intent(BluetoothIntent.DISCOVERY_STARTED_ACTION);
180 mContext.sendBroadcast(intent, BLUETOOTH_PERM);
181 }
182 private void onDiscoveryCompleted() {
183 mBluetoothService.setIsDiscovering(false);
184 Intent intent = new Intent(BluetoothIntent.DISCOVERY_COMPLETED_ACTION);
185 mContext.sendBroadcast(intent, BLUETOOTH_PERM);
186 }
187
188 private void onRemoteDeviceFound(String address, int deviceClass, short rssi) {
189 Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_FOUND_ACTION);
190 intent.putExtra(BluetoothIntent.ADDRESS, address);
191 intent.putExtra(BluetoothIntent.CLASS, deviceClass);
192 intent.putExtra(BluetoothIntent.RSSI, rssi);
193 mContext.sendBroadcast(intent, BLUETOOTH_PERM);
194 }
195 private void onRemoteDeviceDisappeared(String address) {
196 Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_DISAPPEARED_ACTION);
197 intent.putExtra(BluetoothIntent.ADDRESS, address);
198 mContext.sendBroadcast(intent, BLUETOOTH_PERM);
199 }
200 private void onRemoteClassUpdated(String address, int deviceClass) {
201 Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_CLASS_UPDATED_ACTION);
202 intent.putExtra(BluetoothIntent.ADDRESS, address);
203 intent.putExtra(BluetoothIntent.CLASS, deviceClass);
204 mContext.sendBroadcast(intent, BLUETOOTH_PERM);
205 }
206 private void onRemoteDeviceConnected(String address) {
207 Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION);
208 intent.putExtra(BluetoothIntent.ADDRESS, address);
209 mContext.sendBroadcast(intent, BLUETOOTH_PERM);
210 }
211 private void onRemoteDeviceDisconnectRequested(String address) {
212 Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_DISCONNECT_REQUESTED_ACTION);
213 intent.putExtra(BluetoothIntent.ADDRESS, address);
214 mContext.sendBroadcast(intent, BLUETOOTH_PERM);
215 }
216 private void onRemoteDeviceDisconnected(String address) {
217 Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_DISCONNECTED_ACTION);
218 intent.putExtra(BluetoothIntent.ADDRESS, address);
219 mContext.sendBroadcast(intent, BLUETOOTH_PERM);
220 }
221 private void onRemoteNameUpdated(String address, String name) {
222 Intent intent = new Intent(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION);
223 intent.putExtra(BluetoothIntent.ADDRESS, address);
224 intent.putExtra(BluetoothIntent.NAME, name);
225 mContext.sendBroadcast(intent, BLUETOOTH_PERM);
226 }
227 private void onRemoteNameFailed(String address) {
228 Intent intent = new Intent(BluetoothIntent.REMOTE_NAME_FAILED_ACTION);
229 intent.putExtra(BluetoothIntent.ADDRESS, address);
230 mContext.sendBroadcast(intent, BLUETOOTH_PERM);
231 }
232 private void onRemoteNameChanged(String address, String name) {
233 Intent intent = new Intent(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION);
234 intent.putExtra(BluetoothIntent.ADDRESS, address);
235 intent.putExtra(BluetoothIntent.NAME, name);
236 mContext.sendBroadcast(intent, BLUETOOTH_PERM);
237 }
238
239 private void onCreateBondingResult(String address, int result) {
240 address = address.toUpperCase();
241 if (result == BluetoothError.SUCCESS) {
242 mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDED);
243 if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) {
244 mBluetoothService.getBondState().clearPinAttempts(address);
245 }
246 } else if (result == BluetoothDevice.UNBOND_REASON_AUTH_FAILED &&
247 mBluetoothService.getBondState().getAttempt(address) == 1) {
248 mBluetoothService.getBondState().addAutoPairingFailure(address);
249 pairingAttempt(address, result);
250 } else if (result == BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN &&
251 mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) {
252 pairingAttempt(address, result);
253 } else {
254 mBluetoothService.getBondState().setBondState(address,
255 BluetoothDevice.BOND_NOT_BONDED, result);
256 if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) {
257 mBluetoothService.getBondState().clearPinAttempts(address);
258 }
259 }
260 }
261
262 private void pairingAttempt(String address, int result) {
263 // This happens when our initial guess of "0000" as the pass key
264 // fails. Try to create the bond again and display the pin dialog
265 // to the user. Use back-off while posting the delayed
266 // message. The initial value is
267 // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY and the max value is
268 // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY. If the max value is
269 // reached, display an error to the user.
270 int attempt = mBluetoothService.getBondState().getAttempt(address);
271 if (attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY >
272 MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY) {
273 mBluetoothService.getBondState().clearPinAttempts(address);
274 mBluetoothService.getBondState().setBondState(address,
275 BluetoothDevice.BOND_NOT_BONDED, result);
276 return;
277 }
278
279 Message message = mHandler.obtainMessage(EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY);
280 message.obj = address;
281 boolean postResult = mHandler.sendMessageDelayed(message,
282 attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY);
283 if (!postResult) {
284 mBluetoothService.getBondState().clearPinAttempts(address);
285 mBluetoothService.getBondState().setBondState(address,
286 BluetoothDevice.BOND_NOT_BONDED, result);
287 return;
288 }
289 mBluetoothService.getBondState().attempt(address);
290 }
291
292 private void onBondingCreated(String address) {
293 mBluetoothService.getBondState().setBondState(address.toUpperCase(),
294 BluetoothDevice.BOND_BONDED);
295 }
296
297 private void onBondingRemoved(String address) {
298 mBluetoothService.getBondState().setBondState(address.toUpperCase(),
299 BluetoothDevice.BOND_NOT_BONDED, BluetoothDevice.UNBOND_REASON_REMOVED);
300 }
301
302 private void onNameChanged(String name) {
303 Intent intent = new Intent(BluetoothIntent.NAME_CHANGED_ACTION);
304 intent.putExtra(BluetoothIntent.NAME, name);
305 mContext.sendBroadcast(intent, BLUETOOTH_PERM);
306 }
307
308 private void onPasskeyAgentRequest(String address, int nativeData) {
309 address = address.toUpperCase();
310 mPasskeyAgentRequestData.put(address, new Integer(nativeData));
311
The Android Open Source Project10592532009-03-18 17:39:46 -0700312 if (mBluetoothService.getBluetoothState() == BluetoothDevice.BLUETOOTH_STATE_TURNING_OFF) {
313 // shutdown path
314 mBluetoothService.cancelPin(address);
315 return;
316 }
317
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800318 if (mBluetoothService.getBondState().getBondState(address) ==
319 BluetoothDevice.BOND_BONDING) {
320 // we initiated the bonding
321 int btClass = mBluetoothService.getRemoteClass(address);
322
323 // try 0000 once if the device looks dumb
324 switch (BluetoothClass.Device.getDevice(btClass)) {
325 case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
326 case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
327 case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES:
328 case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO:
329 case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
330 case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO:
331 if (!mBluetoothService.getBondState().hasAutoPairingFailed(address) &&
332 !mBluetoothService.getBondState().isAutoPairingBlacklisted(address)) {
333 mBluetoothService.getBondState().attempt(address);
334 mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes("0000"));
335 return;
336 }
337 }
338 }
339 Intent intent = new Intent(BluetoothIntent.PAIRING_REQUEST_ACTION);
340 intent.putExtra(BluetoothIntent.ADDRESS, address);
341 mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
342 }
343
344 private void onPasskeyAgentCancel(String address) {
345 address = address.toUpperCase();
346 mPasskeyAgentRequestData.remove(address);
347 Intent intent = new Intent(BluetoothIntent.PAIRING_CANCEL_ACTION);
348 intent.putExtra(BluetoothIntent.ADDRESS, address);
349 mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
350 mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_NOT_BONDED,
351 BluetoothDevice.UNBOND_REASON_AUTH_CANCELED);
352 }
353
354 private boolean onAuthAgentAuthorize(String address, String service, String uuid) {
355 boolean authorized = false;
The Android Open Source Project10592532009-03-18 17:39:46 -0700356 if (mBluetoothService.isEnabled() && service.endsWith("service_audio")) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800357 BluetoothA2dp a2dp = new BluetoothA2dp(mContext);
358 authorized = a2dp.getSinkPriority(address) > BluetoothA2dp.PRIORITY_OFF;
359 if (authorized) {
360 Log.i(TAG, "Allowing incoming A2DP connection from " + address);
361 } else {
362 Log.i(TAG, "Rejecting incoming A2DP connection from " + address);
363 }
364 } else {
365 Log.i(TAG, "Rejecting incoming " + service + " connection from " + address);
366 }
367 return authorized;
368 }
369
370 private void onAuthAgentCancel(String address, String service, String uuid) {
371 // We immediately response to DBUS Authorize() so this should not
372 // usually happen
373 log("onAuthAgentCancel(" + address + ", " + service + ", " + uuid + ")");
374 }
375
376 private void onGetRemoteServiceChannelResult(String address, int channel) {
377 IBluetoothDeviceCallback callback = mGetRemoteServiceChannelCallbacks.get(address);
378 if (callback != null) {
379 mGetRemoteServiceChannelCallbacks.remove(address);
380 try {
381 callback.onGetRemoteServiceChannelResult(address, channel);
382 } catch (RemoteException e) {}
383 }
384 }
385
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700386 private void onRestartRequired() {
387 if (mBluetoothService.isEnabled()) {
388 Log.e(TAG, "*** A serious error occured (did hcid crash?) - restarting Bluetooth ***");
389 mHandler.sendEmptyMessage(EVENT_RESTART_BLUETOOTH);
390 }
391 }
392
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800393 private static void log(String msg) {
394 Log.d(TAG, msg);
395 }
396}