blob: 11c297cb99e4dcae0a990fb0a8f8fed475f1c5af [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:
Nick Pelly37b5a102009-03-24 20:46:18 -070079 mBluetoothService.restart();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070080 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080081 }
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 synchronized boolean waitForAndDispatchEvent(int timeout_ms) {
115 return waitForAndDispatchEventNative(timeout_ms);
116 }
117 private native boolean waitForAndDispatchEventNative(int timeout_ms);
118
119 /* package */ synchronized void start() {
120
121 if (mThread != null) {
122 // Already running.
123 return;
124 }
125 mThread = new Thread("Bluetooth Event Loop") {
126 @Override
127 public void run() {
128 try {
129 if (setUpEventLoopNative()) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700130 mStarted = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800131 while (!mInterrupted) {
132 waitForAndDispatchEvent(0);
133 sleep(500);
134 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800135 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700136 // tear down even in the error case to clean
137 // up anything we started to setup
138 tearDownEventLoopNative();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800139 } catch (InterruptedException e) { }
140 if (DBG) log("Event Loop thread finished");
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700141 mThread = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800142 }
143 };
144 if (DBG) log("Starting Event Loop thread");
145 mInterrupted = false;
146 mThread.start();
147 }
148 private native boolean setUpEventLoopNative();
149 private native void tearDownEventLoopNative();
150
151 public synchronized void stop() {
152 if (mThread != null) {
153 mInterrupted = true;
154 try {
155 mThread.join();
156 mThread = null;
157 } catch (InterruptedException e) {
158 Log.i(TAG, "Interrupted waiting for Event Loop thread to join");
159 }
160 }
161 }
162
163 public synchronized boolean isEventLoopRunning() {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700164 return mThread != null && mStarted;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800165 }
166
167 /*package*/ void onModeChanged(String bluezMode) {
168 int mode = BluetoothDeviceService.bluezStringToScanMode(bluezMode);
169 if (mode >= 0) {
170 Intent intent = new Intent(BluetoothIntent.SCAN_MODE_CHANGED_ACTION);
171 intent.putExtra(BluetoothIntent.SCAN_MODE, mode);
Nick Pelly37b5a102009-03-24 20:46:18 -0700172 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800173 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();
Nick Pelly9c0c4922009-03-24 18:51:24 -0700346 mBluetoothService.cancelPin(address);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800347 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}