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