blob: 672014583486d888d83d2c3ef7d5e048ecdba98f [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
17/**
18 * TODO: Move this to
Nick Pellybd022f42009-08-14 18:33:38 -070019 * java/services/com/android/server/BluetoothService.java
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020 * and make the contructor package private again.
21 *
22 * @hide
23 */
24
25package android.server;
26
Nick Pellybd022f42009-08-14 18:33:38 -070027import android.bluetooth.BluetoothAdapter;
Jaikumar Ganeshdd0463a2009-09-16 12:30:02 -070028import android.bluetooth.BluetoothClass;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.bluetooth.BluetoothDevice;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.bluetooth.BluetoothHeadset;
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -070031import android.bluetooth.BluetoothProfileConnectionState;
Nick Pelly24bb9b82009-10-02 20:34:18 -070032import android.bluetooth.BluetoothSocket;
Jaikumar Ganesh10eac972009-09-21 12:48:51 -070033import android.bluetooth.BluetoothUuid;
Nick Pellybd022f42009-08-14 18:33:38 -070034import android.bluetooth.IBluetooth;
Nick Pelly16fb88a2009-10-07 07:44:03 +020035import android.bluetooth.IBluetoothCallback;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036import android.content.BroadcastReceiver;
37import android.content.ContentResolver;
38import android.content.Context;
39import android.content.Intent;
40import android.content.IntentFilter;
Jaikumar Ganesh6e9c4432009-12-09 12:09:21 -080041import android.content.SharedPreferences;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042import android.os.Binder;
43import android.os.Handler;
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -080044import android.os.IBinder;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080045import android.os.Message;
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -080046import android.os.ParcelUuid;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047import android.os.RemoteException;
The Android Open Source Project10592532009-03-18 17:39:46 -070048import android.os.ServiceManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049import android.os.SystemService;
50import android.provider.Settings;
51import android.util.Log;
52
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -070053import com.android.internal.app.IBatteryStats;
54
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -080055import java.io.BufferedInputStream;
Jaikumar Ganeshc06fe592010-01-07 20:22:44 -080056import java.io.BufferedReader;
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -080057import java.io.BufferedWriter;
Jaikumar Ganeshc06fe592010-01-07 20:22:44 -080058import java.io.DataInputStream;
59import java.io.File;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080060import java.io.FileDescriptor;
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -080061import java.io.FileInputStream;
62import java.io.FileNotFoundException;
Jaikumar Ganeshc06fe592010-01-07 20:22:44 -080063import java.io.FileOutputStream;
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -080064import java.io.FileWriter;
65import java.io.IOException;
Jaikumar Ganeshc06fe592010-01-07 20:22:44 -080066import java.io.InputStreamReader;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080067import java.io.PrintWriter;
68import java.io.UnsupportedEncodingException;
69import java.util.ArrayList;
70import java.util.Arrays;
71import java.util.HashMap;
Nick Pelly16fb88a2009-10-07 07:44:03 +020072import java.util.Iterator;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080073import java.util.Map;
74
Nick Pellybd022f42009-08-14 18:33:38 -070075public class BluetoothService extends IBluetooth.Stub {
76 private static final String TAG = "BluetoothService";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080077 private static final boolean DBG = true;
78
79 private int mNativeData;
80 private BluetoothEventLoop mEventLoop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080081 private boolean mIsAirplaneSensitive;
Jeff Sharkey44303922009-12-01 18:25:02 -080082 private boolean mIsAirplaneToggleable;
The Android Open Source Project10592532009-03-18 17:39:46 -070083 private int mBluetoothState;
Nick Pelly997c7612009-03-24 20:44:48 -070084 private boolean mRestart = false; // need to call enable() after disable()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080085 private boolean mIsDiscovering;
86
Nick Pellybd022f42009-08-14 18:33:38 -070087 private BluetoothAdapter mAdapter; // constant after init()
88 private final BondState mBondState = new BondState(); // local cache of bondings
89 private final IBatteryStats mBatteryStats;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080090 private final Context mContext;
91
92 private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
93 private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
94
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -080095 private static final String DOCK_ADDRESS_PATH = "/sys/class/switch/dock/bt_addr";
96 private static final String DOCK_PIN_PATH = "/sys/class/switch/dock/bt_pin";
97
Jaikumar Ganesh6e9c4432009-12-09 12:09:21 -080098 private static final String SHARED_PREFERENCE_DOCK_ADDRESS = "dock_bluetooth_address";
99 private static final String SHARED_PREFERENCES_NAME = "bluetooth_service_settings";
100
The Android Open Source Project10592532009-03-18 17:39:46 -0700101 private static final int MESSAGE_REGISTER_SDP_RECORDS = 1;
102 private static final int MESSAGE_FINISH_DISABLE = 2;
Jaikumar Ganesh1caa6d12009-09-18 11:32:54 -0700103 private static final int MESSAGE_UUID_INTENT = 3;
Nick Pelly12835472009-09-25 15:00:29 -0700104 private static final int MESSAGE_DISCOVERABLE_TIMEOUT = 4;
Jaikumar Ganesh1caa6d12009-09-18 11:32:54 -0700105
106 // The timeout used to sent the UUIDs Intent
107 // This timeout should be greater than the page timeout
108 private static final int UUID_INTENT_DELAY = 6000;
The Android Open Source Project10592532009-03-18 17:39:46 -0700109
Nick Pelly16fb88a2009-10-07 07:44:03 +0200110 /** Always retrieve RFCOMM channel for these SDP UUIDs */
111 private static final ParcelUuid[] RFCOMM_UUIDS = {
112 BluetoothUuid.Handsfree,
113 BluetoothUuid.HSP,
114 BluetoothUuid.ObexObjectPush };
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700115
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700116 // TODO(): Optimize all these string handling
Nick Pelly16fb88a2009-10-07 07:44:03 +0200117 private final Map<String, String> mAdapterProperties;
118 private final HashMap<String, Map<String, String>> mDeviceProperties;
119
120 private final HashMap<String, Map<ParcelUuid, Integer>> mDeviceServiceChannelCache;
121 private final ArrayList<String> mUuidIntentTracker;
122 private final HashMap<RemoteService, IBluetoothCallback> mUuidCallbackTracker;
Jaikumar Ganesh1caa6d12009-09-18 11:32:54 -0700123
Nick Pelly24bb9b82009-10-02 20:34:18 -0700124 private final HashMap<Integer, Integer> mServiceRecordToPid;
125
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700126 private final HashMap<String, BluetoothProfileConnectionState> mProfileConnectionMgr;
127
128 private BluetoothA2dpService mA2dpService;
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -0800129 private static String mDockAddress;
130 private String mDockPin;
131
Nick Pelly16fb88a2009-10-07 07:44:03 +0200132 private static class RemoteService {
133 public String address;
134 public ParcelUuid uuid;
135 public RemoteService(String address, ParcelUuid uuid) {
136 this.address = address;
137 this.uuid = uuid;
138 }
139 @Override
140 public boolean equals(Object o) {
141 if (o instanceof RemoteService) {
142 RemoteService service = (RemoteService)o;
143 return address.equals(service.address) && uuid.equals(service.uuid);
144 }
145 return false;
146 }
Kenny Root5f614162010-02-17 12:00:37 -0800147
148 @Override
149 public int hashCode() {
150 int hash = 1;
151 hash = hash * 31 + (address == null ? 0 : address.hashCode());
152 hash = hash * 31 + (uuid == null ? 0 : uuid.hashCode());
153 return hash;
154 }
Nick Pelly16fb88a2009-10-07 07:44:03 +0200155 }
156
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800157 static {
158 classInitNative();
159 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800160
Nick Pellybd022f42009-08-14 18:33:38 -0700161 public BluetoothService(Context context) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800162 mContext = context;
The Android Open Source Project10592532009-03-18 17:39:46 -0700163
164 // Need to do this in place of:
165 // mBatteryStats = BatteryStatsService.getService();
166 // Since we can not import BatteryStatsService from here. This class really needs to be
167 // moved to java/services/com/android/server/
168 mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo"));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800169
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800170 initializeNativeDataNative();
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700171
172 if (isEnabledNative() == 1) {
173 Log.w(TAG, "Bluetooth daemons already running - runtime restart? ");
174 disableNative();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800175 }
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700176
Nick Pellyde893f52009-09-08 13:15:33 -0700177 mBluetoothState = BluetoothAdapter.STATE_OFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800178 mIsDiscovering = false;
Nick Pellybd022f42009-08-14 18:33:38 -0700179 mAdapterProperties = new HashMap<String, String>();
180 mDeviceProperties = new HashMap<String, Map<String,String>>();
Jaikumar Ganesh10eac972009-09-21 12:48:51 -0700181
182 mDeviceServiceChannelCache = new HashMap<String, Map<ParcelUuid, Integer>>();
Jaikumar Ganesh1caa6d12009-09-18 11:32:54 -0700183 mUuidIntentTracker = new ArrayList<String>();
Nick Pelly16fb88a2009-10-07 07:44:03 +0200184 mUuidCallbackTracker = new HashMap<RemoteService, IBluetoothCallback>();
Nick Pelly24bb9b82009-10-02 20:34:18 -0700185 mServiceRecordToPid = new HashMap<Integer, Integer>();
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700186 mProfileConnectionMgr = new HashMap<String, BluetoothProfileConnectionState>();
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -0800187
188 IntentFilter filter = new IntentFilter();
Jaikumar Ganesh6e9c4432009-12-09 12:09:21 -0800189 registerForAirplaneMode(filter);
190
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -0800191 filter.addAction(Intent.ACTION_DOCK_EVENT);
Jaikumar Ganesh6e9c4432009-12-09 12:09:21 -0800192 mContext.registerReceiver(mReceiver, filter);
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -0800193 }
194
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700195 public static synchronized String readDockBluetoothAddress() {
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -0800196 if (mDockAddress != null) return mDockAddress;
197
198 BufferedInputStream file = null;
199 String dockAddress;
200 try {
201 file = new BufferedInputStream(new FileInputStream(DOCK_ADDRESS_PATH));
202 byte[] address = new byte[17];
203 file.read(address);
204 dockAddress = new String(address);
205 dockAddress = dockAddress.toUpperCase();
206 if (BluetoothAdapter.checkBluetoothAddress(dockAddress)) {
207 mDockAddress = dockAddress;
208 return mDockAddress;
209 } else {
210 log("CheckBluetoothAddress failed for car dock address:" + dockAddress);
211 }
212 } catch (FileNotFoundException e) {
213 log("FileNotFoundException while trying to read dock address");
214 } catch (IOException e) {
215 log("IOException while trying to read dock address");
216 } finally {
217 if (file != null) {
218 try {
219 file.close();
220 } catch (IOException e) {
221 // Ignore
222 }
223 }
224 }
225 mDockAddress = null;
226 return null;
227 }
228
229 private synchronized boolean writeDockPin() {
230 BufferedWriter out = null;
231 try {
232 out = new BufferedWriter(new FileWriter(DOCK_PIN_PATH));
233
234 // Generate a random 4 digit pin between 0000 and 9999
235 // This is not truly random but good enough for our purposes.
236 int pin = (int) Math.floor(Math.random() * 10000);
237
238 mDockPin = String.format("%04d", pin);
239 out.write(mDockPin);
240 return true;
241 } catch (FileNotFoundException e) {
242 log("FileNotFoundException while trying to write dock pairing pin");
243 } catch (IOException e) {
244 log("IOException while while trying to write dock pairing pin");
245 } finally {
246 if (out != null) {
247 try {
248 out.close();
249 } catch (IOException e) {
250 // Ignore
251 }
252 }
253 }
254 mDockPin = null;
255 return false;
256 }
257
258 /*package*/ synchronized String getDockPin() {
259 return mDockPin;
Nick Pellybd022f42009-08-14 18:33:38 -0700260 }
261
262 public synchronized void initAfterRegistration() {
Nick Pellyf242b7b2009-10-08 00:12:45 +0200263 mAdapter = BluetoothAdapter.getDefaultAdapter();
Nick Pellybd022f42009-08-14 18:33:38 -0700264 mEventLoop = new BluetoothEventLoop(mContext, mAdapter, this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800265 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800266
267 @Override
268 protected void finalize() throws Throwable {
Jaikumar Ganesh6e9c4432009-12-09 12:09:21 -0800269 mContext.unregisterReceiver(mReceiver);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800270 try {
271 cleanupNativeDataNative();
272 } finally {
273 super.finalize();
274 }
275 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800276
277 public boolean isEnabled() {
278 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
Jaikumar Ganesh8c9dd7d2009-11-16 16:23:20 -0800279 return isEnabledInternal();
280 }
281
282 private boolean isEnabledInternal() {
Nick Pellyde893f52009-09-08 13:15:33 -0700283 return mBluetoothState == BluetoothAdapter.STATE_ON;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800284 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800285
The Android Open Source Project10592532009-03-18 17:39:46 -0700286 public int getBluetoothState() {
287 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
288 return mBluetoothState;
289 }
290
291
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800292 /**
293 * Bring down bluetooth and disable BT in settings. Returns true on success.
294 */
295 public boolean disable() {
296 return disable(true);
297 }
298
299 /**
300 * Bring down bluetooth. Returns true on success.
301 *
Nick Pellye6ee3be2009-10-08 23:27:28 +0200302 * @param saveSetting If true, persist the new setting
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800303 */
304 public synchronized boolean disable(boolean saveSetting) {
Nick Pellye6ee3be2009-10-08 23:27:28 +0200305 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800306
The Android Open Source Project10592532009-03-18 17:39:46 -0700307 switch (mBluetoothState) {
Nick Pellyde893f52009-09-08 13:15:33 -0700308 case BluetoothAdapter.STATE_OFF:
The Android Open Source Project10592532009-03-18 17:39:46 -0700309 return true;
Nick Pellyde893f52009-09-08 13:15:33 -0700310 case BluetoothAdapter.STATE_ON:
The Android Open Source Project10592532009-03-18 17:39:46 -0700311 break;
312 default:
313 return false;
314 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800315 if (mEnableThread != null && mEnableThread.isAlive()) {
316 return false;
317 }
Nick Pellyde893f52009-09-08 13:15:33 -0700318 setBluetoothState(BluetoothAdapter.STATE_TURNING_OFF);
Nick Pellybc1fc052009-10-12 09:54:39 -0700319 mHandler.removeMessages(MESSAGE_REGISTER_SDP_RECORDS);
The Android Open Source Project10592532009-03-18 17:39:46 -0700320
321 // Allow 3 seconds for profiles to gracefully disconnect
322 // TODO: Introduce a callback mechanism so that each profile can notify
Nick Pellybd022f42009-08-14 18:33:38 -0700323 // BluetoothService when it is done shutting down
The Android Open Source Project10592532009-03-18 17:39:46 -0700324 mHandler.sendMessageDelayed(
325 mHandler.obtainMessage(MESSAGE_FINISH_DISABLE, saveSetting ? 1 : 0, 0), 3000);
326 return true;
327 }
328
329
330 private synchronized void finishDisable(boolean saveSetting) {
Nick Pellyde893f52009-09-08 13:15:33 -0700331 if (mBluetoothState != BluetoothAdapter.STATE_TURNING_OFF) {
The Android Open Source Project10592532009-03-18 17:39:46 -0700332 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800333 }
334 mEventLoop.stop();
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700335 tearDownNativeDataNative();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800336 disableNative();
337
338 // mark in progress bondings as cancelled
339 for (String address : mBondState.listInState(BluetoothDevice.BOND_BONDING)) {
Nick Pelly005b2282009-09-10 10:21:56 -0700340 mBondState.setBondState(address, BluetoothDevice.BOND_NONE,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800341 BluetoothDevice.UNBOND_REASON_AUTH_CANCELED);
342 }
343
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800344 // update mode
Nick Pellyde893f52009-09-08 13:15:33 -0700345 Intent intent = new Intent(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
346 intent.putExtra(BluetoothAdapter.EXTRA_SCAN_MODE, BluetoothAdapter.SCAN_MODE_NONE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800347 mContext.sendBroadcast(intent, BLUETOOTH_PERM);
348
The Android Open Source Project10592532009-03-18 17:39:46 -0700349 mIsDiscovering = false;
Nick Pellybd022f42009-08-14 18:33:38 -0700350 mAdapterProperties.clear();
Nick Pelly24bb9b82009-10-02 20:34:18 -0700351 mServiceRecordToPid.clear();
The Android Open Source Project10592532009-03-18 17:39:46 -0700352
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800353 if (saveSetting) {
354 persistBluetoothOnSetting(false);
355 }
The Android Open Source Project10592532009-03-18 17:39:46 -0700356
Nick Pellyde893f52009-09-08 13:15:33 -0700357 setBluetoothState(BluetoothAdapter.STATE_OFF);
The Android Open Source Project10592532009-03-18 17:39:46 -0700358
359 // Log bluetooth off to battery stats.
360 long ident = Binder.clearCallingIdentity();
361 try {
362 mBatteryStats.noteBluetoothOff();
363 } catch (RemoteException e) {
364 } finally {
365 Binder.restoreCallingIdentity(ident);
366 }
Nick Pelly997c7612009-03-24 20:44:48 -0700367
368 if (mRestart) {
369 mRestart = false;
370 enable();
371 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800372 }
373
The Android Open Source Project10592532009-03-18 17:39:46 -0700374 /** Bring up BT and persist BT on in settings */
375 public boolean enable() {
376 return enable(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800377 }
378
379 /**
380 * Enable this Bluetooth device, asynchronously.
381 * This turns on/off the underlying hardware.
382 *
The Android Open Source Project10592532009-03-18 17:39:46 -0700383 * @param saveSetting If true, persist the new state of BT in settings
384 * @return True on success (so far)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800385 */
The Android Open Source Project10592532009-03-18 17:39:46 -0700386 public synchronized boolean enable(boolean saveSetting) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800387 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
388 "Need BLUETOOTH_ADMIN permission");
389
390 // Airplane mode can prevent Bluetooth radio from being turned on.
Jeff Sharkey44303922009-12-01 18:25:02 -0800391 if (mIsAirplaneSensitive && isAirplaneModeOn() && !mIsAirplaneToggleable) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800392 return false;
393 }
Nick Pellyde893f52009-09-08 13:15:33 -0700394 if (mBluetoothState != BluetoothAdapter.STATE_OFF) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800395 return false;
396 }
397 if (mEnableThread != null && mEnableThread.isAlive()) {
398 return false;
399 }
Nick Pellyde893f52009-09-08 13:15:33 -0700400 setBluetoothState(BluetoothAdapter.STATE_TURNING_ON);
The Android Open Source Project10592532009-03-18 17:39:46 -0700401 mEnableThread = new EnableThread(saveSetting);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800402 mEnableThread.start();
403 return true;
404 }
405
Nick Pelly997c7612009-03-24 20:44:48 -0700406 /** Forcibly restart Bluetooth if it is on */
407 /* package */ synchronized void restart() {
Nick Pellyde893f52009-09-08 13:15:33 -0700408 if (mBluetoothState != BluetoothAdapter.STATE_ON) {
Nick Pelly997c7612009-03-24 20:44:48 -0700409 return;
410 }
411 mRestart = true;
412 if (!disable(false)) {
413 mRestart = false;
414 }
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700415 }
Nick Pelly997c7612009-03-24 20:44:48 -0700416
The Android Open Source Project10592532009-03-18 17:39:46 -0700417 private synchronized void setBluetoothState(int state) {
418 if (state == mBluetoothState) {
419 return;
420 }
421
422 if (DBG) log("Bluetooth state " + mBluetoothState + " -> " + state);
423
Nick Pellyde893f52009-09-08 13:15:33 -0700424 Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
425 intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, mBluetoothState);
426 intent.putExtra(BluetoothAdapter.EXTRA_STATE, state);
The Android Open Source Project10592532009-03-18 17:39:46 -0700427 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
428
429 mBluetoothState = state;
430
431 mContext.sendBroadcast(intent, BLUETOOTH_PERM);
432 }
433
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800434 private final Handler mHandler = new Handler() {
435 @Override
436 public void handleMessage(Message msg) {
437 switch (msg.what) {
The Android Open Source Project10592532009-03-18 17:39:46 -0700438 case MESSAGE_REGISTER_SDP_RECORDS:
Jaikumar Ganesh8c9dd7d2009-11-16 16:23:20 -0800439 if (!isEnabledInternal()) {
Nick Pellybc1fc052009-10-12 09:54:39 -0700440 return;
441 }
442 // SystemService.start() forks sdptool to register service
443 // records. It can fail to register some records if it is
444 // forked multiple times in a row, probably because there is
445 // some race in sdptool or bluez when operated in parallel.
446 // As a workaround, delay 500ms between each fork of sdptool.
447 // TODO: Don't fork sdptool in order to regsiter service
448 // records, use a DBUS call instead.
449 switch (msg.arg1) {
450 case 1:
Jaikumar Ganesh77b4ad02009-11-30 14:17:30 -0800451 Log.d(TAG, "Registering hfag record");
452 SystemService.start("hfag");
Nick Pellybc1fc052009-10-12 09:54:39 -0700453 mHandler.sendMessageDelayed(
454 mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS, 2, -1), 500);
455 break;
456 case 2:
Jaikumar Ganesh77b4ad02009-11-30 14:17:30 -0800457 Log.d(TAG, "Registering hsag record");
458 SystemService.start("hsag");
Nick Pellybc1fc052009-10-12 09:54:39 -0700459 mHandler.sendMessageDelayed(
460 mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS, 3, -1), 500);
461 break;
462 case 3:
463 Log.d(TAG, "Registering opush record");
Nick Pelly03c707a2009-07-14 21:32:14 -0700464 SystemService.start("opush");
Nick Pellybc1fc052009-10-12 09:54:39 -0700465 mHandler.sendMessageDelayed(
466 mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS, 4, -1), 500);
467 break;
468 case 4:
469 Log.d(TAG, "Registering pbap record");
Jaikumar Ganesh67542962009-07-23 21:32:45 -0700470 SystemService.start("pbap");
Nick Pellybc1fc052009-10-12 09:54:39 -0700471 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800472 }
The Android Open Source Project10592532009-03-18 17:39:46 -0700473 break;
474 case MESSAGE_FINISH_DISABLE:
475 finishDisable(msg.arg1 != 0);
476 break;
Jaikumar Ganesh1caa6d12009-09-18 11:32:54 -0700477 case MESSAGE_UUID_INTENT:
478 String address = (String)msg.obj;
Nick Pelly16fb88a2009-10-07 07:44:03 +0200479 if (address != null) {
Jaikumar Ganesh1caa6d12009-09-18 11:32:54 -0700480 sendUuidIntent(address);
Nick Pelly16fb88a2009-10-07 07:44:03 +0200481 makeServiceChannelCallbacks(address);
482 }
Jaikumar Ganesh1caa6d12009-09-18 11:32:54 -0700483 break;
Nick Pelly12835472009-09-25 15:00:29 -0700484 case MESSAGE_DISCOVERABLE_TIMEOUT:
485 int mode = msg.arg1;
Jaikumar Ganesh8c9dd7d2009-11-16 16:23:20 -0800486 if (isEnabledInternal()) {
Nick Pelly12835472009-09-25 15:00:29 -0700487 // TODO: Switch back to the previous scan mode
488 // This is ok for now, because we only use
489 // CONNECTABLE and CONNECTABLE_DISCOVERABLE
490 setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE, -1);
491 }
492 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800493 }
494 }
495 };
496
497 private EnableThread mEnableThread;
498
499 private class EnableThread extends Thread {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800500 private final boolean mSaveSetting;
The Android Open Source Project10592532009-03-18 17:39:46 -0700501 public EnableThread(boolean saveSetting) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800502 mSaveSetting = saveSetting;
503 }
504 public void run() {
505 boolean res = (enableNative() == 0);
506 if (res) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700507 int retryCount = 2;
508 boolean running = false;
509 while ((retryCount-- > 0) && !running) {
510 mEventLoop.start();
The Android Open Source Project10592532009-03-18 17:39:46 -0700511 // it may take a momement for the other thread to do its
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700512 // thing. Check periodically for a while.
513 int pollCount = 5;
514 while ((pollCount-- > 0) && !running) {
515 if (mEventLoop.isEventLoopRunning()) {
516 running = true;
517 break;
518 }
519 try {
520 Thread.sleep(100);
521 } catch (InterruptedException e) {}
522 }
523 }
524 if (!running) {
525 log("bt EnableThread giving up");
526 res = false;
527 disableNative();
528 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800529 }
530
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800531
532 if (res) {
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700533 if (!setupNativeDataNative()) {
534 return;
535 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800536 if (mSaveSetting) {
537 persistBluetoothOnSetting(true);
538 }
539 mIsDiscovering = false;
Jaikumar Ganeshc06fe592010-01-07 20:22:44 -0800540 mBondState.readAutoPairingData();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800541 mBondState.loadBondState();
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700542 initProfileState();
Nick Pellybc1fc052009-10-12 09:54:39 -0700543 mHandler.sendMessageDelayed(
544 mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS, 1, -1), 3000);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800545
The Android Open Source Project10592532009-03-18 17:39:46 -0700546 // Log bluetooth on to battery stats.
547 long ident = Binder.clearCallingIdentity();
548 try {
549 mBatteryStats.noteBluetoothOn();
550 } catch (RemoteException e) {
551 } finally {
552 Binder.restoreCallingIdentity(ident);
553 }
554 }
555
556 mEnableThread = null;
557
558 setBluetoothState(res ?
Nick Pellyde893f52009-09-08 13:15:33 -0700559 BluetoothAdapter.STATE_ON :
560 BluetoothAdapter.STATE_OFF);
The Android Open Source Project10592532009-03-18 17:39:46 -0700561
562 if (res) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800563 // Update mode
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700564 String[] propVal = {"Pairable", getProperty("Pairable")};
565 mEventLoop.onPropertyChanged(propVal);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800566 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700567
Jeff Sharkey44303922009-12-01 18:25:02 -0800568 if (mIsAirplaneSensitive && isAirplaneModeOn() && !mIsAirplaneToggleable) {
Daisuke Miyakawa5c43f732009-06-08 12:55:56 +0900569 disable(false);
570 }
571
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800572 }
573 }
574
575 private void persistBluetoothOnSetting(boolean bluetoothOn) {
576 long origCallerIdentityToken = Binder.clearCallingIdentity();
577 Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.BLUETOOTH_ON,
578 bluetoothOn ? 1 : 0);
579 Binder.restoreCallingIdentity(origCallerIdentityToken);
580 }
581
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800582 /* package */ BondState getBondState() {
583 return mBondState;
584 }
585
586 /** local cache of bonding state.
587 /* we keep our own state to track the intermediate state BONDING, which
588 /* bluez does not track.
589 * All addreses must be passed in upper case.
590 */
591 public class BondState {
592 private final HashMap<String, Integer> mState = new HashMap<String, Integer>();
593 private final HashMap<String, Integer> mPinAttempt = new HashMap<String, Integer>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800594
Jaikumar Ganeshc06fe592010-01-07 20:22:44 -0800595 private static final String AUTO_PAIRING_BLACKLIST =
596 "/etc/bluetooth/auto_pairing.conf";
597 private static final String DYNAMIC_AUTO_PAIRING_BLACKLIST =
598 "/data/misc/bluetooth/dynamic_auto_pairing.conf";
599 private ArrayList<String> mAutoPairingAddressBlacklist;
600 private ArrayList<String> mAutoPairingExactNameBlacklist;
601 private ArrayList<String> mAutoPairingPartialNameBlacklist;
602 // Addresses added to blacklist dynamically based on usage.
603 private ArrayList<String> mAutoPairingDynamicAddressBlacklist;
Jaikumar Ganesh738ed802009-11-11 23:12:04 -0800604
Jaikumar Ganesh482d54b2009-09-14 13:43:09 -0700605
Jaikumar Ganesh20923612009-09-20 12:56:21 -0700606 // If this is an outgoing connection, store the address.
607 // There can be only 1 pending outgoing connection at a time,
608 private String mPendingOutgoingBonding;
609
610 private synchronized void setPendingOutgoingBonding(String address) {
611 mPendingOutgoingBonding = address;
612 }
613
614 public synchronized String getPendingOutgoingBonding() {
615 return mPendingOutgoingBonding;
616 }
617
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800618 public synchronized void loadBondState() {
Nick Pellyde893f52009-09-08 13:15:33 -0700619 if (mBluetoothState != BluetoothAdapter.STATE_TURNING_ON) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800620 return;
621 }
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700622 String []bonds = null;
Jaikumar Ganeshb148bc82009-11-20 13:41:07 -0800623 String val = getPropertyInternal("Devices");
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700624 if (val != null) {
625 bonds = val.split(",");
626 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800627 if (bonds == null) {
628 return;
629 }
630 mState.clear();
631 if (DBG) log("found " + bonds.length + " bonded devices");
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700632 for (String device : bonds) {
633 mState.put(getAddressFromObjectPath(device).toUpperCase(),
634 BluetoothDevice.BOND_BONDED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800635 }
636 }
637
638 public synchronized void setBondState(String address, int state) {
639 setBondState(address, state, 0);
640 }
641
642 /** reason is ignored unless state == BOND_NOT_BONDED */
643 public synchronized void setBondState(String address, int state, int reason) {
644 int oldState = getBondState(address);
645 if (oldState == state) {
646 return;
647 }
Jaikumar Ganesh20923612009-09-20 12:56:21 -0700648
649 // Check if this was an pending outgoing bonding.
650 // If yes, reset the state.
651 if (oldState == BluetoothDevice.BOND_BONDING) {
652 if (address.equals(mPendingOutgoingBonding)) {
653 mPendingOutgoingBonding = null;
654 }
655 }
656
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700657 if (state == BluetoothDevice.BOND_BONDED) {
658 addProfileState(address);
659 } else if (state == BluetoothDevice.BOND_NONE) {
660 removeProfileState(address);
661 }
662
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800663 if (DBG) log(address + " bond state " + oldState + " -> " + state + " (" +
664 reason + ")");
Nick Pelly005b2282009-09-10 10:21:56 -0700665 Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
666 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
667 intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, state);
668 intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState);
669 if (state == BluetoothDevice.BOND_NONE) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800670 if (reason <= 0) {
671 Log.w(TAG, "setBondState() called to unbond device, but reason code is " +
672 "invalid. Overriding reason code with BOND_RESULT_REMOVED");
673 reason = BluetoothDevice.UNBOND_REASON_REMOVED;
674 }
Nick Pelly005b2282009-09-10 10:21:56 -0700675 intent.putExtra(BluetoothDevice.EXTRA_REASON, reason);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800676 mState.remove(address);
677 } else {
678 mState.put(address, state);
679 }
680
681 mContext.sendBroadcast(intent, BLUETOOTH_PERM);
682 }
683
684 public boolean isAutoPairingBlacklisted(String address) {
Jaikumar Ganeshc06fe592010-01-07 20:22:44 -0800685 if (mAutoPairingAddressBlacklist != null) {
686 for (String blacklistAddress : mAutoPairingAddressBlacklist) {
687 if (address.startsWith(blacklistAddress)) return true;
688 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800689 }
Jaikumar Ganesh482d54b2009-09-14 13:43:09 -0700690
Jaikumar Ganeshc06fe592010-01-07 20:22:44 -0800691 if (mAutoPairingDynamicAddressBlacklist != null) {
692 for (String blacklistAddress: mAutoPairingDynamicAddressBlacklist) {
693 if (address.equals(blacklistAddress)) return true;
694 }
695 }
Jaikumar Ganesh482d54b2009-09-14 13:43:09 -0700696 String name = getRemoteName(address);
697 if (name != null) {
Jaikumar Ganeshc06fe592010-01-07 20:22:44 -0800698 if (mAutoPairingExactNameBlacklist != null) {
699 for (String blacklistName : mAutoPairingExactNameBlacklist) {
700 if (name.equals(blacklistName)) return true;
701 }
Jaikumar Ganesh738ed802009-11-11 23:12:04 -0800702 }
703
Jaikumar Ganeshc06fe592010-01-07 20:22:44 -0800704 if (mAutoPairingPartialNameBlacklist != null) {
705 for (String blacklistName : mAutoPairingPartialNameBlacklist) {
706 if (name.startsWith(blacklistName)) return true;
707 }
Jaikumar Ganesh482d54b2009-09-14 13:43:09 -0700708 }
709 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800710 return false;
711 }
712
713 public synchronized int getBondState(String address) {
714 Integer state = mState.get(address);
715 if (state == null) {
Nick Pelly005b2282009-09-10 10:21:56 -0700716 return BluetoothDevice.BOND_NONE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800717 }
718 return state.intValue();
719 }
720
Jaikumar Ganeshe5d93b72009-10-08 02:27:52 -0700721 /*package*/ synchronized String[] listInState(int state) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800722 ArrayList<String> result = new ArrayList<String>(mState.size());
723 for (Map.Entry<String, Integer> e : mState.entrySet()) {
724 if (e.getValue().intValue() == state) {
725 result.add(e.getKey());
726 }
727 }
728 return result.toArray(new String[result.size()]);
729 }
730
731 public synchronized void addAutoPairingFailure(String address) {
Jaikumar Ganeshc06fe592010-01-07 20:22:44 -0800732 if (mAutoPairingDynamicAddressBlacklist == null) {
733 mAutoPairingDynamicAddressBlacklist = new ArrayList<String>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800734 }
Jaikumar Ganeshc06fe592010-01-07 20:22:44 -0800735
736 updateAutoPairingData(address);
737 mAutoPairingDynamicAddressBlacklist.add(address);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800738 }
739
740 public synchronized boolean isAutoPairingAttemptsInProgress(String address) {
741 return getAttempt(address) != 0;
742 }
743
744 public synchronized void clearPinAttempts(String address) {
745 mPinAttempt.remove(address);
746 }
747
748 public synchronized boolean hasAutoPairingFailed(String address) {
Jaikumar Ganeshc06fe592010-01-07 20:22:44 -0800749 if (mAutoPairingDynamicAddressBlacklist == null) return false;
750
751 return mAutoPairingDynamicAddressBlacklist.contains(address);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800752 }
753
754 public synchronized int getAttempt(String address) {
755 Integer attempt = mPinAttempt.get(address);
756 if (attempt == null) {
757 return 0;
758 }
759 return attempt.intValue();
760 }
761
762 public synchronized void attempt(String address) {
763 Integer attempt = mPinAttempt.get(address);
764 int newAttempt;
765 if (attempt == null) {
766 newAttempt = 1;
767 } else {
768 newAttempt = attempt.intValue() + 1;
769 }
770 mPinAttempt.put(address, new Integer(newAttempt));
771 }
772
Jaikumar Ganeshc06fe592010-01-07 20:22:44 -0800773 private void copyAutoPairingData() {
774 File file = null;
775 FileInputStream in = null;
776 FileOutputStream out = null;
777 try {
778 file = new File(DYNAMIC_AUTO_PAIRING_BLACKLIST);
779 if (file.exists()) return;
780
781 in = new FileInputStream(AUTO_PAIRING_BLACKLIST);
782 out= new FileOutputStream(DYNAMIC_AUTO_PAIRING_BLACKLIST);
783
784 byte[] buf = new byte[1024];
785 int len;
786 while ((len = in.read(buf)) > 0) {
787 out.write(buf, 0, len);
788 }
789 } catch (FileNotFoundException e) {
790 log("FileNotFoundException: in copyAutoPairingData");
791 } catch (IOException e) {
792 log("IOException: in copyAutoPairingData");
793 } finally {
794 try {
795 if (in != null) in.close();
796 if (out != null) out.close();
797 } catch (IOException e) {}
798 }
799 }
800
801 public void readAutoPairingData() {
802 if (mAutoPairingAddressBlacklist != null) return;
803 copyAutoPairingData();
804 FileInputStream fstream = null;
805 try {
806 fstream = new FileInputStream(DYNAMIC_AUTO_PAIRING_BLACKLIST);
807 DataInputStream in = new DataInputStream(fstream);
808 BufferedReader file = new BufferedReader(new InputStreamReader(in));
809 String line;
810 while((line = file.readLine()) != null) {
811 line = line.trim();
812 if (line.length() == 0 || line.startsWith("//")) continue;
813 String[] value = line.split("=");
814 if (value != null && value.length == 2) {
815 String[] val = value[1].split(",");
816 if (value[0].equalsIgnoreCase("AddressBlacklist")) {
817 mAutoPairingAddressBlacklist =
818 new ArrayList<String>(Arrays.asList(val));
819 } else if (value[0].equalsIgnoreCase("ExactNameBlacklist")) {
820 mAutoPairingExactNameBlacklist =
821 new ArrayList<String>(Arrays.asList(val));
822 } else if (value[0].equalsIgnoreCase("PartialNameBlacklist")) {
823 mAutoPairingPartialNameBlacklist =
824 new ArrayList<String>(Arrays.asList(val));
825 } else if (value[0].equalsIgnoreCase("DynamicAddressBlacklist")) {
826 mAutoPairingDynamicAddressBlacklist =
827 new ArrayList<String>(Arrays.asList(val));
828 } else {
829 Log.e(TAG, "Error parsing Auto pairing blacklist file");
830 }
831 }
832 }
833 } catch (FileNotFoundException e) {
834 log("FileNotFoundException: readAutoPairingData" + e.toString());
835 } catch (IOException e) {
836 log("IOException: readAutoPairingData" + e.toString());
837 } finally {
838 if (fstream != null) {
839 try {
840 fstream.close();
841 } catch (IOException e) {
842 // Ignore
843 }
844 }
845 }
846 }
847
848 // This function adds a bluetooth address to the auto pairing blacklis
849 // file. These addresses are added to DynamicAddressBlacklistSection
850 private void updateAutoPairingData(String address) {
851 BufferedWriter out = null;
852 try {
853 out = new BufferedWriter(new FileWriter(DYNAMIC_AUTO_PAIRING_BLACKLIST, true));
854 StringBuilder str = new StringBuilder();
855 if (mAutoPairingDynamicAddressBlacklist.size() == 0) {
856 str.append("DynamicAddressBlacklist=");
857 }
858 str.append(address);
859 str.append(",");
860 out.write(str.toString());
861 } catch (FileNotFoundException e) {
862 log("FileNotFoundException: updateAutoPairingData" + e.toString());
863 } catch (IOException e) {
864 log("IOException: updateAutoPairingData" + e.toString());
865 } finally {
866 if (out != null) {
867 try {
868 out.close();
869 } catch (IOException e) {
870 // Ignore
871 }
872 }
873 }
874 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800875 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800876
877 private static String toBondStateString(int bondState) {
878 switch (bondState) {
Nick Pelly005b2282009-09-10 10:21:56 -0700879 case BluetoothDevice.BOND_NONE:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800880 return "not bonded";
881 case BluetoothDevice.BOND_BONDING:
882 return "bonding";
883 case BluetoothDevice.BOND_BONDED:
884 return "bonded";
885 default:
886 return "??????";
887 }
888 }
889
Jaikumar Ganesh9519ce72009-09-08 21:37:32 -0700890 /*package*/ synchronized boolean isAdapterPropertiesEmpty() {
891 return mAdapterProperties.isEmpty();
892 }
893
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700894 /*package*/synchronized void getAllProperties() {
Jaikumar Ganesh8c9dd7d2009-11-16 16:23:20 -0800895
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800896 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
Nick Pellybd022f42009-08-14 18:33:38 -0700897 mAdapterProperties.clear();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800898
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700899 String properties[] = (String [])getAdapterPropertiesNative();
900 // The String Array consists of key-value pairs.
901 if (properties == null) {
902 Log.e(TAG, "*Error*: GetAdapterProperties returned NULL");
903 return;
904 }
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700905
Jaikumar Ganesh8bc8ce42009-06-24 16:42:51 -0700906 for (int i = 0; i < properties.length; i++) {
907 String name = properties[i];
Jaikumar Ganeshefa33672009-08-28 13:48:55 -0700908 String newValue = null;
Jaikumar Ganesh8bc8ce42009-06-24 16:42:51 -0700909 int len;
910 if (name == null) {
911 Log.e(TAG, "Error:Adapter Property at index" + i + "is null");
912 continue;
913 }
914 if (name.equals("Devices")) {
Jaikumar Ganeshefa33672009-08-28 13:48:55 -0700915 StringBuilder str = new StringBuilder();
Jaikumar Ganesh8bc8ce42009-06-24 16:42:51 -0700916 len = Integer.valueOf(properties[++i]);
Jaikumar Ganesh8bc8ce42009-06-24 16:42:51 -0700917 for (int j = 0; j < len; j++) {
Jaikumar Ganeshefa33672009-08-28 13:48:55 -0700918 str.append(properties[++i]);
919 str.append(",");
920 }
921 if (len > 0) {
922 newValue = str.toString();
Jaikumar Ganesh8bc8ce42009-06-24 16:42:51 -0700923 }
924 } else {
925 newValue = properties[++i];
926 }
Nick Pellybd022f42009-08-14 18:33:38 -0700927 mAdapterProperties.put(name, newValue);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700928 }
929
930 // Add adapter object path property.
931 String adapterPath = getAdapterPathNative();
932 if (adapterPath != null)
Nick Pellybd022f42009-08-14 18:33:38 -0700933 mAdapterProperties.put("ObjectPath", adapterPath + "/dev_");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800934 }
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700935
936 /* package */ synchronized void setProperty(String name, String value) {
Nick Pellybd022f42009-08-14 18:33:38 -0700937 mAdapterProperties.put(name, value);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700938 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800939
940 public synchronized boolean setName(String name) {
941 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
942 "Need BLUETOOTH_ADMIN permission");
943 if (name == null) {
944 return false;
945 }
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700946 return setPropertyString("Name", name);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800947 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800948
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700949 //TODO(): setPropertyString, setPropertyInteger, setPropertyBoolean
950 // Either have a single property function with Object as the parameter
951 // or have a function for each property and then obfuscate in the JNI layer.
952 // The following looks dirty.
953 private boolean setPropertyString(String key, String value) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800954 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
Jaikumar Ganesh8c9dd7d2009-11-16 16:23:20 -0800955 if (!isEnabledInternal()) return false;
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700956 return setAdapterPropertyStringNative(key, value);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800957 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800958
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700959 private boolean setPropertyInteger(String key, int value) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800960 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
Jaikumar Ganesh8c9dd7d2009-11-16 16:23:20 -0800961 if (!isEnabledInternal()) return false;
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700962 return setAdapterPropertyIntegerNative(key, value);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800963 }
964
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700965 private boolean setPropertyBoolean(String key, boolean value) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800966 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
Jaikumar Ganesh8c9dd7d2009-11-16 16:23:20 -0800967 if (!isEnabledInternal()) return false;
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700968 return setAdapterPropertyBooleanNative(key, value ? 1 : 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800969 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800970
971 /**
972 * Set the discoverability window for the device. A timeout of zero
973 * makes the device permanently discoverable (if the device is
974 * discoverable). Setting the timeout to a nonzero value does not make
975 * a device discoverable; you need to call setMode() to make the device
976 * explicitly discoverable.
977 *
978 * @param timeout_s The discoverable timeout in seconds.
979 */
980 public synchronized boolean setDiscoverableTimeout(int timeout) {
981 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
982 "Need BLUETOOTH_ADMIN permission");
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700983 return setPropertyInteger("DiscoverableTimeout", timeout);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800984 }
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700985
Nick Pelly12835472009-09-25 15:00:29 -0700986 public synchronized boolean setScanMode(int mode, int duration) {
Nick Pelly18b1e792009-09-24 11:14:15 -0700987 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS,
988 "Need WRITE_SECURE_SETTINGS permission");
Nick Pellyde893f52009-09-08 13:15:33 -0700989 boolean pairable = false;
990 boolean discoverable = false;
Nick Pelly12835472009-09-25 15:00:29 -0700991
Nick Pellyde893f52009-09-08 13:15:33 -0700992 switch (mode) {
993 case BluetoothAdapter.SCAN_MODE_NONE:
Nick Pelly12835472009-09-25 15:00:29 -0700994 mHandler.removeMessages(MESSAGE_DISCOVERABLE_TIMEOUT);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700995 pairable = false;
996 discoverable = false;
Nick Pellyde893f52009-09-08 13:15:33 -0700997 break;
998 case BluetoothAdapter.SCAN_MODE_CONNECTABLE:
Nick Pelly12835472009-09-25 15:00:29 -0700999 mHandler.removeMessages(MESSAGE_DISCOVERABLE_TIMEOUT);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001000 pairable = true;
1001 discoverable = false;
Nick Pelly005b2282009-09-10 10:21:56 -07001002 break;
Nick Pellyde893f52009-09-08 13:15:33 -07001003 case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
Nick Pelly12835472009-09-25 15:00:29 -07001004 mHandler.removeMessages(MESSAGE_DISCOVERABLE_TIMEOUT);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001005 pairable = true;
1006 discoverable = true;
Nick Pelly12835472009-09-25 15:00:29 -07001007 Message msg = mHandler.obtainMessage(MESSAGE_DISCOVERABLE_TIMEOUT);
1008 mHandler.sendMessageDelayed(msg, duration * 1000);
1009 if (DBG) Log.d(TAG, "BT Discoverable for " + duration + " seconds");
Nick Pelly005b2282009-09-10 10:21:56 -07001010 break;
Nick Pellyde893f52009-09-08 13:15:33 -07001011 default:
1012 Log.w(TAG, "Requested invalid scan mode " + mode);
1013 return false;
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001014 }
1015 setPropertyBoolean("Pairable", pairable);
1016 setPropertyBoolean("Discoverable", discoverable);
1017
1018 return true;
1019 }
1020
Jaikumar Ganeshb148bc82009-11-20 13:41:07 -08001021 /*package*/ synchronized String getProperty(String name) {
1022 if (!isEnabledInternal()) return null;
1023 return getPropertyInternal(name);
1024 }
1025
1026 /*package*/ synchronized String getPropertyInternal(String name) {
Nick Pellybd022f42009-08-14 18:33:38 -07001027 if (!mAdapterProperties.isEmpty())
1028 return mAdapterProperties.get(name);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001029 getAllProperties();
Nick Pellybd022f42009-08-14 18:33:38 -07001030 return mAdapterProperties.get(name);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001031 }
1032
1033 public synchronized String getAddress() {
1034 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
1035 return getProperty("Address");
1036 }
1037
1038 public synchronized String getName() {
1039 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
1040 return getProperty("Name");
1041 }
1042
1043 /**
1044 * Returns the user-friendly name of a remote device. This value is
1045 * returned from our local cache, which is updated when onPropertyChange
1046 * event is received.
1047 * Do not expect to retrieve the updated remote name immediately after
1048 * changing the name on the remote device.
1049 *
1050 * @param address Bluetooth address of remote device.
1051 *
1052 * @return The user-friendly name of the specified remote device.
1053 */
1054 public synchronized String getRemoteName(String address) {
1055 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
Nick Pelly005b2282009-09-10 10:21:56 -07001056 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001057 return null;
1058 }
Jaikumar Ganesh55929a92009-09-30 10:49:34 -07001059 return getRemoteDeviceProperty(address, "Name");
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001060 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001061
1062 /**
1063 * Get the discoverability window for the device. A timeout of zero
1064 * means that the device is permanently discoverable (if the device is
1065 * in the discoverable mode).
1066 *
1067 * @return The discoverability window of the device, in seconds. A negative
1068 * value indicates an error.
1069 */
1070 public synchronized int getDiscoverableTimeout() {
1071 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001072 String timeout = getProperty("DiscoverableTimeout");
1073 if (timeout != null)
1074 return Integer.valueOf(timeout);
1075 else
1076 return -1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001077 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001078
1079 public synchronized int getScanMode() {
1080 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
Jaikumar Ganesh8c9dd7d2009-11-16 16:23:20 -08001081 if (!isEnabledInternal())
Nick Pellyde893f52009-09-08 13:15:33 -07001082 return BluetoothAdapter.SCAN_MODE_NONE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001083
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001084 boolean pairable = getProperty("Pairable").equals("true");
1085 boolean discoverable = getProperty("Discoverable").equals("true");
1086 return bluezStringToScanMode (pairable, discoverable);
1087 }
1088
1089 public synchronized boolean startDiscovery() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001090 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
1091 "Need BLUETOOTH_ADMIN permission");
Jaikumar Ganesh8c9dd7d2009-11-16 16:23:20 -08001092 if (!isEnabledInternal()) return false;
1093
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001094 return startDiscoveryNative();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001095 }
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001096
1097 public synchronized boolean cancelDiscovery() {
1098 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
1099 "Need BLUETOOTH_ADMIN permission");
Jaikumar Ganesh8c9dd7d2009-11-16 16:23:20 -08001100 if (!isEnabledInternal()) return false;
1101
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001102 return stopDiscoveryNative();
1103 }
1104
1105 public synchronized boolean isDiscovering() {
1106 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
1107 return mIsDiscovering;
1108 }
1109
1110 /* package */ void setIsDiscovering(boolean isDiscovering) {
1111 mIsDiscovering = isDiscovering;
1112 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001113
1114 public synchronized boolean createBond(String address) {
1115 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
1116 "Need BLUETOOTH_ADMIN permission");
Jaikumar Ganesh8c9dd7d2009-11-16 16:23:20 -08001117 if (!isEnabledInternal()) return false;
1118
Nick Pelly005b2282009-09-10 10:21:56 -07001119 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001120 return false;
1121 }
1122 address = address.toUpperCase();
1123
Jaikumar Ganesh20923612009-09-20 12:56:21 -07001124 if (mBondState.getPendingOutgoingBonding() != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001125 log("Ignoring createBond(): another device is bonding");
1126 // a different device is currently bonding, fail
1127 return false;
1128 }
1129
1130 // Check for bond state only if we are not performing auto
1131 // pairing exponential back-off attempts.
1132 if (!mBondState.isAutoPairingAttemptsInProgress(address) &&
Nick Pelly005b2282009-09-10 10:21:56 -07001133 mBondState.getBondState(address) != BluetoothDevice.BOND_NONE) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001134 log("Ignoring createBond(): this device is already bonding or bonded");
1135 return false;
1136 }
1137
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -08001138 if (address.equals(mDockAddress)) {
1139 if (!writeDockPin()) {
1140 log("Error while writing Pin for the dock");
1141 return false;
1142 }
1143 }
1144
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001145 if (!createPairedDeviceNative(address, 60000 /* 1 minute */)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001146 return false;
1147 }
1148
Jaikumar Ganesh20923612009-09-20 12:56:21 -07001149 mBondState.setPendingOutgoingBonding(address);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001150 mBondState.setBondState(address, BluetoothDevice.BOND_BONDING);
Jaikumar Ganesh20923612009-09-20 12:56:21 -07001151
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001152 return true;
1153 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001154
1155 public synchronized boolean cancelBondProcess(String address) {
1156 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
1157 "Need BLUETOOTH_ADMIN permission");
Jaikumar Ganesh8c9dd7d2009-11-16 16:23:20 -08001158 if (!isEnabledInternal()) return false;
1159
Nick Pelly005b2282009-09-10 10:21:56 -07001160 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001161 return false;
1162 }
1163 address = address.toUpperCase();
1164 if (mBondState.getBondState(address) != BluetoothDevice.BOND_BONDING) {
1165 return false;
1166 }
1167
Nick Pelly005b2282009-09-10 10:21:56 -07001168 mBondState.setBondState(address, BluetoothDevice.BOND_NONE,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001169 BluetoothDevice.UNBOND_REASON_AUTH_CANCELED);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001170 cancelDeviceCreationNative(address);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001171 return true;
1172 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001173
1174 public synchronized boolean removeBond(String address) {
1175 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
1176 "Need BLUETOOTH_ADMIN permission");
Jaikumar Ganesh8c9dd7d2009-11-16 16:23:20 -08001177 if (!isEnabledInternal()) return false;
1178
Nick Pelly005b2282009-09-10 10:21:56 -07001179 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001180 return false;
1181 }
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -07001182 BluetoothProfileConnectionState state = mProfileConnectionMgr.get(address);
1183 if (state != null) {
1184 state.sendMessage(BluetoothProfileConnectionState.UNPAIR);
1185 return true;
1186 } else {
1187 return false;
1188 }
1189 }
1190
1191 public synchronized boolean removeBondInternal(String address) {
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001192 return removeDeviceNative(getObjectPathFromAddress(address));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001193 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001194
1195 public synchronized String[] listBonds() {
1196 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
1197 return mBondState.listInState(BluetoothDevice.BOND_BONDED);
1198 }
1199
1200 public synchronized int getBondState(String address) {
1201 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
Nick Pelly005b2282009-09-10 10:21:56 -07001202 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
Nick Pellyb24e11b2009-09-08 17:40:43 -07001203 return BluetoothDevice.ERROR;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001204 }
1205 return mBondState.getBondState(address.toUpperCase());
1206 }
1207
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -08001208 public synchronized boolean isBluetoothDock(String address) {
Jaikumar Ganesh6e9c4432009-12-09 12:09:21 -08001209 SharedPreferences sp = mContext.getSharedPreferences(SHARED_PREFERENCES_NAME,
1210 mContext.MODE_PRIVATE);
1211
1212 return sp.contains(SHARED_PREFERENCE_DOCK_ADDRESS + address);
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -08001213 }
1214
Jaikumar Ganesh9488cbd2009-08-03 17:17:37 -07001215 /*package*/ boolean isRemoteDeviceInCache(String address) {
Nick Pellybd022f42009-08-14 18:33:38 -07001216 return (mDeviceProperties.get(address) != null);
Jaikumar Ganesh9488cbd2009-08-03 17:17:37 -07001217 }
1218
1219 /*package*/ String[] getRemoteDeviceProperties(String address) {
Jaikumar Ganesh8c9dd7d2009-11-16 16:23:20 -08001220 if (!isEnabledInternal()) return null;
1221
Jaikumar Ganesh9488cbd2009-08-03 17:17:37 -07001222 String objectPath = getObjectPathFromAddress(address);
1223 return (String [])getDevicePropertiesNative(objectPath);
1224 }
1225
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001226 /*package*/ synchronized String getRemoteDeviceProperty(String address, String property) {
Nick Pellybd022f42009-08-14 18:33:38 -07001227 Map<String, String> properties = mDeviceProperties.get(address);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001228 if (properties != null) {
1229 return properties.get(property);
1230 } else {
1231 // Query for remote device properties, again.
1232 // We will need to reload the cache when we switch Bluetooth on / off
1233 // or if we crash.
Jaikumar Ganesh10eac972009-09-21 12:48:51 -07001234 if (updateRemoteDevicePropertiesCache(address))
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001235 return getRemoteDeviceProperty(address, property);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001236 }
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001237 Log.e(TAG, "getRemoteDeviceProperty: " + property + "not present:" + address);
1238 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001239 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001240
Jaikumar Ganesh10eac972009-09-21 12:48:51 -07001241 /* package */ synchronized boolean updateRemoteDevicePropertiesCache(String address) {
1242 String[] propValues = getRemoteDeviceProperties(address);
1243 if (propValues != null) {
1244 addRemoteDeviceProperties(address, propValues);
1245 return true;
1246 }
1247 return false;
1248 }
1249
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001250 /* package */ synchronized void addRemoteDeviceProperties(String address, String[] properties) {
Jaikumar Ganesh395d1022009-06-19 16:12:31 -07001251 /*
1252 * We get a DeviceFound signal every time RSSI changes or name changes.
1253 * Don't create a new Map object every time */
Nick Pellybd022f42009-08-14 18:33:38 -07001254 Map<String, String> propertyValues = mDeviceProperties.get(address);
Jaikumar Ganeshefa33672009-08-28 13:48:55 -07001255 if (propertyValues == null) {
Jaikumar Ganesh395d1022009-06-19 16:12:31 -07001256 propertyValues = new HashMap<String, String>();
1257 }
1258
Jaikumar Ganesh8bc8ce42009-06-24 16:42:51 -07001259 for (int i = 0; i < properties.length; i++) {
1260 String name = properties[i];
Jaikumar Ganeshefa33672009-08-28 13:48:55 -07001261 String newValue = null;
Jaikumar Ganesh8bc8ce42009-06-24 16:42:51 -07001262 int len;
1263 if (name == null) {
1264 Log.e(TAG, "Error: Remote Device Property at index" + i + "is null");
1265 continue;
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001266 }
Jaikumar Ganesh8bc8ce42009-06-24 16:42:51 -07001267 if (name.equals("UUIDs") || name.equals("Nodes")) {
Jaikumar Ganeshefa33672009-08-28 13:48:55 -07001268 StringBuilder str = new StringBuilder();
Jaikumar Ganesh8bc8ce42009-06-24 16:42:51 -07001269 len = Integer.valueOf(properties[++i]);
Jaikumar Ganesh8bc8ce42009-06-24 16:42:51 -07001270 for (int j = 0; j < len; j++) {
Jaikumar Ganeshefa33672009-08-28 13:48:55 -07001271 str.append(properties[++i]);
1272 str.append(",");
1273 }
1274 if (len > 0) {
1275 newValue = str.toString();
Jaikumar Ganesh8bc8ce42009-06-24 16:42:51 -07001276 }
1277 } else {
1278 newValue = properties[++i];
1279 }
Jaikumar Ganeshefa33672009-08-28 13:48:55 -07001280
Jaikumar Ganesh8bc8ce42009-06-24 16:42:51 -07001281 propertyValues.put(name, newValue);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001282 }
Nick Pellybd022f42009-08-14 18:33:38 -07001283 mDeviceProperties.put(address, propertyValues);
Jaikumar Ganesh10eac972009-09-21 12:48:51 -07001284
1285 // We have added a new remote device or updated its properties.
1286 // Also update the serviceChannel cache.
1287 updateDeviceServiceChannelCache(address);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001288 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001289
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001290 /* package */ void removeRemoteDeviceProperties(String address) {
Nick Pellybd022f42009-08-14 18:33:38 -07001291 mDeviceProperties.remove(address);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001292 }
1293
1294 /* package */ synchronized void setRemoteDeviceProperty(String address, String name,
1295 String value) {
Nick Pellybd022f42009-08-14 18:33:38 -07001296 Map <String, String> propVal = mDeviceProperties.get(address);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001297 if (propVal != null) {
1298 propVal.put(name, value);
Nick Pellybd022f42009-08-14 18:33:38 -07001299 mDeviceProperties.put(address, propVal);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001300 } else {
1301 Log.e(TAG, "setRemoteDeviceProperty for a device not in cache:" + address);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001302 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001303 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001304
1305 /**
Lixin Yueefa1dd72009-08-31 15:55:13 +08001306 * Sets the remote device trust state.
1307 *
1308 * @return boolean to indicate operation success or fail
1309 */
1310 public synchronized boolean setTrust(String address, boolean value) {
Nick Pelly005b2282009-09-10 10:21:56 -07001311 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
Nick Pellye6ee3be2009-10-08 23:27:28 +02001312 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
1313 "Need BLUETOOTH_ADMIN permission");
Lixin Yueefa1dd72009-08-31 15:55:13 +08001314 return false;
1315 }
1316
Jaikumar Ganesh8c9dd7d2009-11-16 16:23:20 -08001317 if (!isEnabledInternal()) return false;
1318
Lixin Yueefa1dd72009-08-31 15:55:13 +08001319 return setDevicePropertyBooleanNative(getObjectPathFromAddress(address), "Trusted",
1320 value ? 1 : 0);
1321 }
1322
1323 /**
1324 * Gets the remote device trust state as boolean.
1325 * Note: this value may be
1326 * retrieved from cache if we retrieved the data before *
1327 *
1328 * @return boolean to indicate trust or untrust state
1329 */
1330 public synchronized boolean getTrustState(String address) {
Nick Pelly005b2282009-09-10 10:21:56 -07001331 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
Lixin Yueefa1dd72009-08-31 15:55:13 +08001332 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
1333 return false;
1334 }
1335
1336 String val = getRemoteDeviceProperty(address, "Trusted");
1337 if (val == null) {
1338 return false;
1339 } else {
1340 return val.equals("true") ? true : false;
1341 }
1342 }
1343
1344 /**
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001345 * Gets the remote major, minor classes encoded as a 32-bit
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001346 * integer.
1347 *
1348 * Note: this value is retrieved from cache, because we get it during
1349 * remote-device discovery.
1350 *
1351 * @return 32-bit integer encoding the remote major, minor, and service
1352 * classes.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001353 */
1354 public synchronized int getRemoteClass(String address) {
Nick Pelly005b2282009-09-10 10:21:56 -07001355 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
Nick Pellyea600cc2009-03-31 14:56:26 -07001356 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
1357 return BluetoothClass.ERROR;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001358 }
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001359 String val = getRemoteDeviceProperty(address, "Class");
1360 if (val == null)
1361 return BluetoothClass.ERROR;
1362 else {
1363 return Integer.valueOf(val);
1364 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001365 }
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001366
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001367
1368 /**
Jaikumar Ganeshdd0463a2009-09-16 12:30:02 -07001369 * Gets the UUIDs supported by the remote device
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001370 *
Jaikumar Ganeshdd0463a2009-09-16 12:30:02 -07001371 * @return array of 128bit ParcelUuids
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001372 */
Jaikumar Ganeshdd0463a2009-09-16 12:30:02 -07001373 public synchronized ParcelUuid[] getRemoteUuids(String address) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001374 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
Nick Pelly005b2282009-09-10 10:21:56 -07001375 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001376 return null;
1377 }
Jaikumar Ganesh1caa6d12009-09-18 11:32:54 -07001378 return getUuidFromCache(address);
1379 }
1380
1381 private ParcelUuid[] getUuidFromCache(String address) {
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001382 String value = getRemoteDeviceProperty(address, "UUIDs");
Jaikumar Ganeshdd0463a2009-09-16 12:30:02 -07001383 if (value == null) return null;
1384
1385 String[] uuidStrings = null;
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001386 // The UUIDs are stored as a "," separated string.
Jaikumar Ganeshdd0463a2009-09-16 12:30:02 -07001387 uuidStrings = value.split(",");
1388 ParcelUuid[] uuids = new ParcelUuid[uuidStrings.length];
1389
1390 for (int i = 0; i < uuidStrings.length; i++) {
1391 uuids[i] = ParcelUuid.fromString(uuidStrings[i]);
1392 }
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001393 return uuids;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001394 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001395
Nick Pelly16fb88a2009-10-07 07:44:03 +02001396 /**
1397 * Connect and fetch new UUID's using SDP.
1398 * The UUID's found are broadcast as intents.
1399 * Optionally takes a uuid and callback to fetch the RFCOMM channel for the
1400 * a given uuid.
1401 * TODO: Don't wait UUID_INTENT_DELAY to broadcast UUID intents on success
1402 * TODO: Don't wait UUID_INTENT_DELAY to handle the failure case for
1403 * callback and broadcast intents.
1404 */
1405 public synchronized boolean fetchRemoteUuids(String address, ParcelUuid uuid,
1406 IBluetoothCallback callback) {
Jaikumar Ganesh1caa6d12009-09-18 11:32:54 -07001407 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
Jaikumar Ganesh8c9dd7d2009-11-16 16:23:20 -08001408 if (!isEnabledInternal()) return false;
1409
Jaikumar Ganesh1caa6d12009-09-18 11:32:54 -07001410 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
1411 return false;
1412 }
1413
Nick Pelly16fb88a2009-10-07 07:44:03 +02001414 RemoteService service = new RemoteService(address, uuid);
1415 if (uuid != null && mUuidCallbackTracker.get(service) != null) {
1416 // An SDP query for this address & uuid is already in progress
1417 // Do not add this callback for the uuid
1418 return false;
1419 }
1420
Jaikumar Ganesh1caa6d12009-09-18 11:32:54 -07001421 if (mUuidIntentTracker.contains(address)) {
1422 // An SDP query for this address is already in progress
Nick Pelly16fb88a2009-10-07 07:44:03 +02001423 // Add this uuid onto the in-progress SDP query
1424 if (uuid != null) {
1425 mUuidCallbackTracker.put(new RemoteService(address, uuid), callback);
1426 }
Jaikumar Ganesh1caa6d12009-09-18 11:32:54 -07001427 return true;
1428 }
1429
1430 boolean ret;
Jaikumar Ganesh0e090302010-03-29 00:01:34 -07001431 // Just do the SDP if the device is already created and UUIDs are not
1432 // NULL, else create the device and then do SDP.
1433 if (isRemoteDeviceInCache(address) && getRemoteUuids(address) != null) {
Jaikumar Ganesh1caa6d12009-09-18 11:32:54 -07001434 String path = getObjectPathFromAddress(address);
1435 if (path == null) return false;
1436
1437 // Use an empty string for the UUID pattern
1438 ret = discoverServicesNative(path, "");
1439 } else {
1440 ret = createDeviceNative(address);
1441 }
1442
1443 mUuidIntentTracker.add(address);
Nick Pelly16fb88a2009-10-07 07:44:03 +02001444 if (uuid != null) {
1445 mUuidCallbackTracker.put(new RemoteService(address, uuid), callback);
1446 }
Jaikumar Ganesh1caa6d12009-09-18 11:32:54 -07001447
1448 Message message = mHandler.obtainMessage(MESSAGE_UUID_INTENT);
1449 message.obj = address;
1450 mHandler.sendMessageDelayed(message, UUID_INTENT_DELAY);
1451 return ret;
1452 }
1453
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001454 /**
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001455 * Gets the rfcomm channel associated with the UUID.
Nick Pelly16fb88a2009-10-07 07:44:03 +02001456 * Pulls records from the cache only.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001457 *
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001458 * @param address Address of the remote device
Jaikumar Ganeshdd0463a2009-09-16 12:30:02 -07001459 * @param uuid ParcelUuid of the service attribute
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001460 *
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001461 * @return rfcomm channel associated with the service attribute
Jaikumar Ganesh10eac972009-09-21 12:48:51 -07001462 * -1 on error
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001463 */
Jaikumar Ganeshdd0463a2009-09-16 12:30:02 -07001464 public int getRemoteServiceChannel(String address, ParcelUuid uuid) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001465 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
Jaikumar Ganesh8c9dd7d2009-11-16 16:23:20 -08001466 if (!isEnabledInternal()) return -1;
1467
Nick Pelly005b2282009-09-10 10:21:56 -07001468 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
Nick Pellyb24e11b2009-09-08 17:40:43 -07001469 return BluetoothDevice.ERROR;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001470 }
Jaikumar Ganesh10eac972009-09-21 12:48:51 -07001471 // Check if we are recovering from a crash.
1472 if (mDeviceProperties.isEmpty()) {
1473 if (!updateRemoteDevicePropertiesCache(address))
1474 return -1;
1475 }
1476
1477 Map<ParcelUuid, Integer> value = mDeviceServiceChannelCache.get(address);
1478 if (value != null && value.containsKey(uuid))
1479 return value.get(uuid);
1480 return -1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001481 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001482
1483 public synchronized boolean setPin(String address, byte[] pin) {
1484 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
1485 "Need BLUETOOTH_ADMIN permission");
Jaikumar Ganesh8c9dd7d2009-11-16 16:23:20 -08001486 if (!isEnabledInternal()) return false;
1487
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001488 if (pin == null || pin.length <= 0 || pin.length > 16 ||
Nick Pelly005b2282009-09-10 10:21:56 -07001489 !BluetoothAdapter.checkBluetoothAddress(address)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001490 return false;
1491 }
1492 address = address.toUpperCase();
1493 Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address);
1494 if (data == null) {
1495 Log.w(TAG, "setPin(" + address + ") called but no native data available, " +
1496 "ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" +
1497 " or by bluez.\n");
1498 return false;
1499 }
1500 // bluez API wants pin as a string
1501 String pinString;
1502 try {
1503 pinString = new String(pin, "UTF8");
1504 } catch (UnsupportedEncodingException uee) {
1505 Log.e(TAG, "UTF8 not supported?!?");
1506 return false;
1507 }
1508 return setPinNative(address, pinString, data.intValue());
1509 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001510
Jaikumar Ganeshb0eca412009-07-16 18:26:28 -07001511 public synchronized boolean setPasskey(String address, int passkey) {
1512 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
1513 "Need BLUETOOTH_ADMIN permission");
Jaikumar Ganesh8c9dd7d2009-11-16 16:23:20 -08001514 if (!isEnabledInternal()) return false;
1515
Nick Pelly005b2282009-09-10 10:21:56 -07001516 if (passkey < 0 || passkey > 999999 || !BluetoothAdapter.checkBluetoothAddress(address)) {
Jaikumar Ganeshb0eca412009-07-16 18:26:28 -07001517 return false;
1518 }
1519 address = address.toUpperCase();
1520 Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address);
1521 if (data == null) {
1522 Log.w(TAG, "setPasskey(" + address + ") called but no native data available, " +
1523 "ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" +
1524 " or by bluez.\n");
1525 return false;
1526 }
1527 return setPasskeyNative(address, passkey, data.intValue());
1528 }
1529
1530 public synchronized boolean setPairingConfirmation(String address, boolean confirm) {
1531 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
1532 "Need BLUETOOTH_ADMIN permission");
Jaikumar Ganesh8c9dd7d2009-11-16 16:23:20 -08001533 if (!isEnabledInternal()) return false;
1534
Jaikumar Ganeshb0eca412009-07-16 18:26:28 -07001535 address = address.toUpperCase();
1536 Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address);
1537 if (data == null) {
1538 Log.w(TAG, "setPasskey(" + address + ") called but no native data available, " +
1539 "ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" +
1540 " or by bluez.\n");
1541 return false;
1542 }
1543 return setPairingConfirmationNative(address, confirm, data.intValue());
1544 }
1545
1546 public synchronized boolean cancelPairingUserInput(String address) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001547 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
1548 "Need BLUETOOTH_ADMIN permission");
Jaikumar Ganesh8c9dd7d2009-11-16 16:23:20 -08001549 if (!isEnabledInternal()) return false;
1550
Nick Pelly005b2282009-09-10 10:21:56 -07001551 if (!BluetoothAdapter.checkBluetoothAddress(address)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001552 return false;
1553 }
Nick Pelly005b2282009-09-10 10:21:56 -07001554 mBondState.setBondState(address, BluetoothDevice.BOND_NONE,
Jaikumar Ganesh397d8f42009-08-21 11:50:17 -07001555 BluetoothDevice.UNBOND_REASON_AUTH_CANCELED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001556 address = address.toUpperCase();
1557 Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address);
1558 if (data == null) {
Jaikumar Ganeshb0eca412009-07-16 18:26:28 -07001559 Log.w(TAG, "cancelUserInputNative(" + address + ") called but no native data " +
1560 "available, ignoring. Maybe the PasskeyAgent Request was already cancelled " +
1561 "by the remote or by bluez.\n");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001562 return false;
1563 }
Jaikumar Ganeshb0eca412009-07-16 18:26:28 -07001564 return cancelPairingUserInputNative(address, data.intValue());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001565 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001566
Jaikumar Ganesh8c9dd7d2009-11-16 16:23:20 -08001567 /*package*/ void updateDeviceServiceChannelCache(String address) {
Jaikumar Ganesh10eac972009-09-21 12:48:51 -07001568 ParcelUuid[] deviceUuids = getRemoteUuids(address);
1569 // We are storing the rfcomm channel numbers only for the uuids
1570 // we are interested in.
1571 int channel;
Nick Pelly16fb88a2009-10-07 07:44:03 +02001572 if (DBG) log("updateDeviceServiceChannelCache(" + address + ")");
1573
1574 ArrayList<ParcelUuid> applicationUuids = new ArrayList();
1575
1576 synchronized (this) {
1577 for (RemoteService service : mUuidCallbackTracker.keySet()) {
1578 if (service.address.equals(address)) {
1579 applicationUuids.add(service.uuid);
1580 }
1581 }
1582 }
Jaikumar Ganesh10eac972009-09-21 12:48:51 -07001583
1584 Map <ParcelUuid, Integer> value = new HashMap<ParcelUuid, Integer>();
Nick Pelly16fb88a2009-10-07 07:44:03 +02001585
1586 // Retrieve RFCOMM channel for default uuids
1587 for (ParcelUuid uuid : RFCOMM_UUIDS) {
Jaikumar Ganesh10eac972009-09-21 12:48:51 -07001588 if (BluetoothUuid.isUuidPresent(deviceUuids, uuid)) {
Nick Pelly16fb88a2009-10-07 07:44:03 +02001589 channel = getDeviceServiceChannelNative(getObjectPathFromAddress(address),
1590 uuid.toString(), 0x0004);
1591 if (DBG) log("\tuuid(system): " + uuid + " " + channel);
Jaikumar Ganesh10eac972009-09-21 12:48:51 -07001592 value.put(uuid, channel);
1593 }
1594 }
Nick Pelly16fb88a2009-10-07 07:44:03 +02001595 // Retrieve RFCOMM channel for application requested uuids
1596 for (ParcelUuid uuid : applicationUuids) {
1597 if (BluetoothUuid.isUuidPresent(deviceUuids, uuid)) {
1598 channel = getDeviceServiceChannelNative(getObjectPathFromAddress(address),
1599 uuid.toString(), 0x0004);
1600 if (DBG) log("\tuuid(application): " + uuid + " " + channel);
1601 value.put(uuid, channel);
1602 }
1603 }
1604
1605 synchronized (this) {
1606 // Make application callbacks
1607 for (Iterator<RemoteService> iter = mUuidCallbackTracker.keySet().iterator();
1608 iter.hasNext();) {
1609 RemoteService service = iter.next();
1610 if (service.address.equals(address)) {
1611 channel = -1;
1612 if (value.get(service.uuid) != null) {
1613 channel = value.get(service.uuid);
1614 }
1615 if (channel != -1) {
1616 if (DBG) log("Making callback for " + service.uuid + " with result " +
1617 channel);
1618 IBluetoothCallback callback = mUuidCallbackTracker.get(service);
1619 if (callback != null) {
1620 try {
1621 callback.onRfcommChannelFound(channel);
1622 } catch (RemoteException e) {Log.e(TAG, "", e);}
1623 }
1624
1625 iter.remove();
1626 }
1627 }
1628 }
1629
1630 // Update cache
1631 mDeviceServiceChannelCache.put(address, value);
1632 }
Jaikumar Ganesh10eac972009-09-21 12:48:51 -07001633 }
1634
Nick Pelly24bb9b82009-10-02 20:34:18 -07001635 /**
1636 * b is a handle to a Binder instance, so that this service can be notified
1637 * for Applications that terminate unexpectedly, to clean there service
1638 * records
1639 */
1640 public synchronized int addRfcommServiceRecord(String serviceName, ParcelUuid uuid,
1641 int channel, IBinder b) {
Jaikumar Ganesh8c9dd7d2009-11-16 16:23:20 -08001642 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
1643 if (!isEnabledInternal()) return -1;
1644
Nick Pelly24bb9b82009-10-02 20:34:18 -07001645 if (serviceName == null || uuid == null || channel < 1 ||
1646 channel > BluetoothSocket.MAX_RFCOMM_CHANNEL) {
1647 return -1;
1648 }
1649 if (BluetoothUuid.isUuidPresent(BluetoothUuid.RESERVED_UUIDS, uuid)) {
1650 Log.w(TAG, "Attempted to register a reserved UUID: " + uuid);
1651 return -1;
1652 }
1653 int handle = addRfcommServiceRecordNative(serviceName,
1654 uuid.getUuid().getMostSignificantBits(), uuid.getUuid().getLeastSignificantBits(),
1655 (short)channel);
1656 if (DBG) log("new handle " + Integer.toHexString(handle));
1657 if (handle == -1) {
1658 return -1;
1659 }
1660
1661 int pid = Binder.getCallingPid();
1662 mServiceRecordToPid.put(new Integer(handle), new Integer(pid));
1663 try {
1664 b.linkToDeath(new Reaper(handle, pid), 0);
1665 } catch (RemoteException e) {}
1666 return handle;
1667 }
1668
1669 public void removeServiceRecord(int handle) {
1670 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
1671 "Need BLUETOOTH permission");
1672 checkAndRemoveRecord(handle, Binder.getCallingPid());
1673 }
1674
1675 private synchronized void checkAndRemoveRecord(int handle, int pid) {
1676 Integer handleInt = new Integer(handle);
1677 Integer owner = mServiceRecordToPid.get(handleInt);
1678 if (owner != null && pid == owner.intValue()) {
1679 if (DBG) log("Removing service record " + Integer.toHexString(handle) + " for pid " +
1680 pid);
1681 mServiceRecordToPid.remove(handleInt);
1682 removeServiceRecordNative(handle);
1683 }
1684 }
1685
1686 private class Reaper implements IBinder.DeathRecipient {
1687 int pid;
1688 int handle;
1689 Reaper(int handle, int pid) {
1690 this.pid = pid;
1691 this.handle = handle;
1692 }
1693 public void binderDied() {
1694 synchronized (BluetoothService.this) {
1695 if (DBG) log("Tracked app " + pid + " died");
1696 checkAndRemoveRecord(handle, pid);
1697 }
1698 }
1699 }
1700
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001701 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
1702 @Override
1703 public void onReceive(Context context, Intent intent) {
Jaikumar Ganesh6e9c4432009-12-09 12:09:21 -08001704 if (intent == null) return;
1705
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001706 String action = intent.getAction();
1707 if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
1708 ContentResolver resolver = context.getContentResolver();
1709 // Query the airplane mode from Settings.System just to make sure that
1710 // some random app is not sending this intent and disabling bluetooth
1711 boolean enabled = !isAirplaneModeOn();
1712 // If bluetooth is currently expected to be on, then enable or disable bluetooth
1713 if (Settings.Secure.getInt(resolver, Settings.Secure.BLUETOOTH_ON, 0) > 0) {
1714 if (enabled) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001715 enable(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001716 } else {
1717 disable(false);
1718 }
1719 }
Jaikumar Ganesh6e9c4432009-12-09 12:09:21 -08001720 } else if (Intent.ACTION_DOCK_EVENT.equals(action)) {
1721 int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
1722 Intent.EXTRA_DOCK_STATE_UNDOCKED);
1723 if (DBG) Log.v(TAG, "Received ACTION_DOCK_EVENT with State:" + state);
1724 if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
1725 mDockAddress = null;
1726 mDockPin = null;
1727 } else {
1728 SharedPreferences.Editor editor =
1729 mContext.getSharedPreferences(SHARED_PREFERENCES_NAME,
1730 mContext.MODE_PRIVATE).edit();
1731 editor.putBoolean(SHARED_PREFERENCE_DOCK_ADDRESS + mDockAddress, true);
1732 editor.commit();
1733 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001734 }
1735 }
1736 };
1737
Jaikumar Ganesh6e9c4432009-12-09 12:09:21 -08001738 private void registerForAirplaneMode(IntentFilter filter) {
Jeff Sharkey44303922009-12-01 18:25:02 -08001739 final ContentResolver resolver = mContext.getContentResolver();
1740 final String airplaneModeRadios = Settings.System.getString(resolver,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001741 Settings.System.AIRPLANE_MODE_RADIOS);
Jeff Sharkey44303922009-12-01 18:25:02 -08001742 final String toggleableRadios = Settings.System.getString(resolver,
1743 Settings.System.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
1744
1745 mIsAirplaneSensitive = airplaneModeRadios == null ? true :
1746 airplaneModeRadios.contains(Settings.System.RADIO_BLUETOOTH);
1747 mIsAirplaneToggleable = toggleableRadios == null ? false :
1748 toggleableRadios.contains(Settings.System.RADIO_BLUETOOTH);
1749
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001750 if (mIsAirplaneSensitive) {
Jaikumar Ganesh6e9c4432009-12-09 12:09:21 -08001751 filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001752 }
1753 }
1754
1755 /* Returns true if airplane mode is currently on */
1756 private final boolean isAirplaneModeOn() {
1757 return Settings.System.getInt(mContext.getContentResolver(),
1758 Settings.System.AIRPLANE_MODE_ON, 0) == 1;
1759 }
1760
Jaikumar Ganesh1caa6d12009-09-18 11:32:54 -07001761 /* Broadcast the Uuid intent */
1762 /*package*/ synchronized void sendUuidIntent(String address) {
Jaikumar Ganesh61799652009-09-20 16:01:21 -07001763 ParcelUuid[] uuid = getUuidFromCache(address);
1764 Intent intent = new Intent(BluetoothDevice.ACTION_UUID);
Jaikumar Ganesh2d3b98d2009-09-21 16:11:01 -07001765 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
Jaikumar Ganesh61799652009-09-20 16:01:21 -07001766 intent.putExtra(BluetoothDevice.EXTRA_UUID, uuid);
1767 mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
Jaikumar Ganesh1caa6d12009-09-18 11:32:54 -07001768
Jaikumar Ganesh61799652009-09-20 16:01:21 -07001769 if (mUuidIntentTracker.contains(address))
Jaikumar Ganesh1caa6d12009-09-18 11:32:54 -07001770 mUuidIntentTracker.remove(address);
Nick Pelly16fb88a2009-10-07 07:44:03 +02001771
1772 }
1773
1774 /*package*/ synchronized void makeServiceChannelCallbacks(String address) {
1775 for (Iterator<RemoteService> iter = mUuidCallbackTracker.keySet().iterator();
1776 iter.hasNext();) {
1777 RemoteService service = iter.next();
1778 if (service.address.equals(address)) {
1779 if (DBG) log("Cleaning up failed UUID channel lookup: " + service.address +
1780 " " + service.uuid);
1781 IBluetoothCallback callback = mUuidCallbackTracker.get(service);
1782 if (callback != null) {
1783 try {
1784 callback.onRfcommChannelFound(-1);
1785 } catch (RemoteException e) {Log.e(TAG, "", e);}
1786 }
1787
1788 iter.remove();
1789 }
1790 }
Jaikumar Ganesh1caa6d12009-09-18 11:32:54 -07001791 }
1792
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001793 @Override
1794 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001795 switch(mBluetoothState) {
Nick Pellyde893f52009-09-08 13:15:33 -07001796 case BluetoothAdapter.STATE_OFF:
Nick Pelly24bb9b82009-10-02 20:34:18 -07001797 pw.println("Bluetooth OFF\n");
The Android Open Source Project10592532009-03-18 17:39:46 -07001798 return;
Nick Pellyde893f52009-09-08 13:15:33 -07001799 case BluetoothAdapter.STATE_TURNING_ON:
Nick Pelly24bb9b82009-10-02 20:34:18 -07001800 pw.println("Bluetooth TURNING ON\n");
The Android Open Source Project10592532009-03-18 17:39:46 -07001801 return;
Nick Pellyde893f52009-09-08 13:15:33 -07001802 case BluetoothAdapter.STATE_TURNING_OFF:
Nick Pelly24bb9b82009-10-02 20:34:18 -07001803 pw.println("Bluetooth TURNING OFF\n");
The Android Open Source Project10592532009-03-18 17:39:46 -07001804 return;
Nick Pellyde893f52009-09-08 13:15:33 -07001805 case BluetoothAdapter.STATE_ON:
Nick Pelly24bb9b82009-10-02 20:34:18 -07001806 pw.println("Bluetooth ON\n");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001807 }
The Android Open Source Project10592532009-03-18 17:39:46 -07001808
Nick Pelly24bb9b82009-10-02 20:34:18 -07001809 pw.println("mIsAirplaneSensitive = " + mIsAirplaneSensitive);
Jeff Sharkey44303922009-12-01 18:25:02 -08001810 pw.println("mIsAirplaneToggleable = " + mIsAirplaneToggleable);
Nick Pelly24bb9b82009-10-02 20:34:18 -07001811
1812 pw.println("Local address = " + getAddress());
1813 pw.println("Local name = " + getName());
1814 pw.println("isDiscovering() = " + isDiscovering());
The Android Open Source Project10592532009-03-18 17:39:46 -07001815
1816 BluetoothHeadset headset = new BluetoothHeadset(mContext, null);
1817
The Android Open Source Project10592532009-03-18 17:39:46 -07001818 pw.println("\n--Known devices--");
Nick Pellybd022f42009-08-14 18:33:38 -07001819 for (String address : mDeviceProperties.keySet()) {
Nick Pelly1eada0d2009-08-26 10:57:33 -07001820 int bondState = mBondState.getBondState(address);
The Android Open Source Project10592532009-03-18 17:39:46 -07001821 pw.printf("%s %10s (%d) %s\n", address,
Nick Pelly1eada0d2009-08-26 10:57:33 -07001822 toBondStateString(bondState),
The Android Open Source Project10592532009-03-18 17:39:46 -07001823 mBondState.getAttempt(address),
1824 getRemoteName(address));
Nick Pelly24bb9b82009-10-02 20:34:18 -07001825
1826 Map<ParcelUuid, Integer> uuidChannels = mDeviceServiceChannelCache.get(address);
1827 if (uuidChannels == null) {
1828 pw.println("\tuuids = null");
1829 } else {
1830 for (ParcelUuid uuid : uuidChannels.keySet()) {
1831 Integer channel = uuidChannels.get(uuid);
1832 if (channel == null) {
1833 pw.println("\t" + uuid);
1834 } else {
1835 pw.println("\t" + uuid + " RFCOMM channel = " + channel);
Nick Pelly1eada0d2009-08-26 10:57:33 -07001836 }
1837 }
1838 }
Nick Pelly16fb88a2009-10-07 07:44:03 +02001839 for (RemoteService service : mUuidCallbackTracker.keySet()) {
1840 if (service.address.equals(address)) {
1841 pw.println("\tPENDING CALLBACK: " + service.uuid);
1842 }
1843 }
The Android Open Source Project10592532009-03-18 17:39:46 -07001844 }
1845
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001846 String value = getProperty("Devices");
Nick Pelly1eada0d2009-08-26 10:57:33 -07001847 String[] devicesObjectPath = null;
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001848 if (value != null) {
1849 devicesObjectPath = value.split(",");
1850 }
The Android Open Source Project10592532009-03-18 17:39:46 -07001851 pw.println("\n--ACL connected devices--");
Nick Pelly24bb9b82009-10-02 20:34:18 -07001852 if (devicesObjectPath != null) {
1853 for (String device : devicesObjectPath) {
1854 pw.println(getAddressFromObjectPath(device));
1855 }
The Android Open Source Project10592532009-03-18 17:39:46 -07001856 }
1857
1858 // Rather not do this from here, but no-where else and I need this
1859 // dump
1860 pw.println("\n--Headset Service--");
Jaikumar Ganesh740e39b2010-06-02 12:33:53 -07001861 switch (headset.getState(headset.getCurrentHeadset())) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001862 case BluetoothHeadset.STATE_DISCONNECTED:
1863 pw.println("getState() = STATE_DISCONNECTED");
1864 break;
1865 case BluetoothHeadset.STATE_CONNECTING:
1866 pw.println("getState() = STATE_CONNECTING");
1867 break;
1868 case BluetoothHeadset.STATE_CONNECTED:
1869 pw.println("getState() = STATE_CONNECTED");
1870 break;
1871 case BluetoothHeadset.STATE_ERROR:
1872 pw.println("getState() = STATE_ERROR");
1873 break;
1874 }
Nick Pelly6c901db2009-06-19 10:08:09 -07001875
Nick Pelly24bb9b82009-10-02 20:34:18 -07001876 pw.println("\ngetCurrentHeadset() = " + headset.getCurrentHeadset());
1877 pw.println("getBatteryUsageHint() = " + headset.getBatteryUsageHint());
The Android Open Source Project10592532009-03-18 17:39:46 -07001878 headset.close();
Nick Pelly24bb9b82009-10-02 20:34:18 -07001879 pw.println("\n--Application Service Records--");
1880 for (Integer handle : mServiceRecordToPid.keySet()) {
1881 Integer pid = mServiceRecordToPid.get(handle);
1882 pw.println("\tpid " + pid + " handle " + Integer.toHexString(handle));
1883 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001884 }
1885
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001886 /* package */ static int bluezStringToScanMode(boolean pairable, boolean discoverable) {
1887 if (pairable && discoverable)
Nick Pellybd022f42009-08-14 18:33:38 -07001888 return BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001889 else if (pairable && !discoverable)
Nick Pellybd022f42009-08-14 18:33:38 -07001890 return BluetoothAdapter.SCAN_MODE_CONNECTABLE;
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001891 else
Nick Pellybd022f42009-08-14 18:33:38 -07001892 return BluetoothAdapter.SCAN_MODE_NONE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001893 }
1894
1895 /* package */ static String scanModeToBluezString(int mode) {
1896 switch (mode) {
Nick Pellybd022f42009-08-14 18:33:38 -07001897 case BluetoothAdapter.SCAN_MODE_NONE:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001898 return "off";
Nick Pellybd022f42009-08-14 18:33:38 -07001899 case BluetoothAdapter.SCAN_MODE_CONNECTABLE:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001900 return "connectable";
Nick Pellybd022f42009-08-14 18:33:38 -07001901 case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001902 return "discoverable";
1903 }
1904 return null;
1905 }
1906
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001907 /*package*/ String getAddressFromObjectPath(String objectPath) {
Jaikumar Ganeshb148bc82009-11-20 13:41:07 -08001908 String adapterObjectPath = getPropertyInternal("ObjectPath");
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001909 if (adapterObjectPath == null || objectPath == null) {
1910 Log.e(TAG, "getAddressFromObjectPath: AdpaterObjectPath:" + adapterObjectPath +
1911 " or deviceObjectPath:" + objectPath + " is null");
1912 return null;
1913 }
1914 if (!objectPath.startsWith(adapterObjectPath)) {
1915 Log.e(TAG, "getAddressFromObjectPath: AdpaterObjectPath:" + adapterObjectPath +
1916 " is not a prefix of deviceObjectPath:" + objectPath +
1917 "bluetoothd crashed ?");
1918 return null;
1919 }
1920 String address = objectPath.substring(adapterObjectPath.length());
1921 if (address != null) return address.replace('_', ':');
1922
1923 Log.e(TAG, "getAddressFromObjectPath: Address being returned is null");
1924 return null;
1925 }
1926
1927 /*package*/ String getObjectPathFromAddress(String address) {
Jaikumar Ganeshb148bc82009-11-20 13:41:07 -08001928 String path = getPropertyInternal("ObjectPath");
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07001929 if (path == null) {
1930 Log.e(TAG, "Error: Object Path is null");
1931 return null;
1932 }
1933 path = path + address.replace(":", "_");
1934 return path;
1935 }
1936
Jaikumar Ganeshb7e029d2010-03-09 15:31:24 -08001937 /*package */ void setLinkTimeout(String address, int num_slots) {
1938 String path = getObjectPathFromAddress(address);
1939 boolean result = setLinkTimeoutNative(path, num_slots);
1940
1941 if (!result) log("Set Link Timeout to:" + num_slots + " slots failed");
1942 }
1943
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -07001944 public boolean connectHeadset(String address) {
1945 BluetoothProfileConnectionState state = mProfileConnectionMgr.get(address);
1946 if (state != null) {
1947 state.sendMessage(BluetoothProfileConnectionState.CONNECT_HFP_OUTGOING);
1948 return true;
1949 }
1950 return false;
1951 }
1952
1953 public boolean disconnectHeadset(String address) {
1954 BluetoothProfileConnectionState state = mProfileConnectionMgr.get(address);
1955 if (state != null) {
1956 state.sendMessage(BluetoothProfileConnectionState.DISCONNECT_HFP_OUTGOING);
1957 return true;
1958 }
1959 return false;
1960 }
1961
1962 public boolean connectSink(String address) {
1963 BluetoothProfileConnectionState state = mProfileConnectionMgr.get(address);
1964 if (state != null) {
1965 state.sendMessage(BluetoothProfileConnectionState.CONNECT_A2DP_OUTGOING);
1966 return true;
1967 }
1968 return false;
1969 }
1970
1971 public boolean disconnectSink(String address) {
1972 BluetoothProfileConnectionState state = mProfileConnectionMgr.get(address);
1973 if (state != null) {
1974 state.sendMessage(BluetoothProfileConnectionState.DISCONNECT_A2DP_OUTGOING);
1975 return true;
1976 }
1977 return false;
1978 }
1979
1980 private BluetoothProfileConnectionState addProfileState(String address) {
1981 BluetoothProfileConnectionState state = mProfileConnectionMgr.get(address);
1982 if (state != null) return state;
1983
1984 state = new BluetoothProfileConnectionState(mContext, address, this, mA2dpService);
1985 mProfileConnectionMgr.put(address, state);
1986 state.start();
1987 return state;
1988 }
1989
1990 private void removeProfileState(String address) {
1991 mProfileConnectionMgr.remove(address);
1992 }
1993
1994 private void initProfileState() {
1995 String []bonds = null;
1996 String val = getPropertyInternal("Devices");
1997 if (val != null) {
1998 bonds = val.split(",");
1999 }
2000 if (bonds == null) {
2001 return;
2002 }
2003
2004 for (String path : bonds) {
2005 String address = getAddressFromObjectPath(path);
2006 BluetoothProfileConnectionState state = addProfileState(address);
2007 // Allow 8 secs for SDP records to get registered.
2008 Message msg = new Message();
2009 msg.what = BluetoothProfileConnectionState.AUTO_CONNECT_PROFILES;
2010 state.sendMessageDelayed(msg, 8000);
2011 }
2012 }
2013
2014 public boolean notifyIncomingConnection(String address) {
2015 BluetoothProfileConnectionState state =
2016 mProfileConnectionMgr.get(address);
2017 if (state != null) {
2018 Message msg = new Message();
2019 msg.what = BluetoothProfileConnectionState.CONNECT_HFP_INCOMING;
2020 state.sendMessage(msg);
2021 return true;
2022 }
2023 return false;
2024 }
2025
2026 /*package*/ boolean notifyIncomingA2dpConnection(String address) {
2027 BluetoothProfileConnectionState state =
2028 mProfileConnectionMgr.get(address);
2029 if (state != null) {
2030 Message msg = new Message();
2031 msg.what = BluetoothProfileConnectionState.CONNECT_A2DP_INCOMING;
2032 state.sendMessage(msg);
2033 return true;
2034 }
2035 return false;
2036 }
2037
2038 /*package*/ void setA2dpService(BluetoothA2dpService a2dpService) {
2039 mA2dpService = a2dpService;
2040 }
2041
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002042 private static void log(String msg) {
2043 Log.d(TAG, msg);
2044 }
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07002045
2046 private native static void classInitNative();
2047 private native void initializeNativeDataNative();
2048 private native boolean setupNativeDataNative();
2049 private native boolean tearDownNativeDataNative();
2050 private native void cleanupNativeDataNative();
2051 private native String getAdapterPathNative();
2052
2053 private native int isEnabledNative();
2054 private native int enableNative();
2055 private native int disableNative();
2056
2057 private native Object[] getAdapterPropertiesNative();
2058 private native Object[] getDevicePropertiesNative(String objectPath);
2059 private native boolean setAdapterPropertyStringNative(String key, String value);
2060 private native boolean setAdapterPropertyIntegerNative(String key, int value);
2061 private native boolean setAdapterPropertyBooleanNative(String key, int value);
2062
2063 private native boolean startDiscoveryNative();
2064 private native boolean stopDiscoveryNative();
2065
2066 private native boolean createPairedDeviceNative(String address, int timeout_ms);
2067 private native boolean cancelDeviceCreationNative(String address);
2068 private native boolean removeDeviceNative(String objectPath);
2069 private native int getDeviceServiceChannelNative(String objectPath, String uuid,
2070 int attributeId);
2071
Jaikumar Ganeshb0eca412009-07-16 18:26:28 -07002072 private native boolean cancelPairingUserInputNative(String address, int nativeData);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -07002073 private native boolean setPinNative(String address, String pin, int nativeData);
Jaikumar Ganeshb0eca412009-07-16 18:26:28 -07002074 private native boolean setPasskeyNative(String address, int passkey, int nativeData);
2075 private native boolean setPairingConfirmationNative(String address, boolean confirm,
2076 int nativeData);
Nick Pelly24bb9b82009-10-02 20:34:18 -07002077 private native boolean setDevicePropertyBooleanNative(String objectPath, String key,
2078 int value);
Jaikumar Ganesh1caa6d12009-09-18 11:32:54 -07002079 private native boolean createDeviceNative(String address);
Nick Pelly16fb88a2009-10-07 07:44:03 +02002080 /*package*/ native boolean discoverServicesNative(String objectPath, String pattern);
Jaikumar Ganesh10eac972009-09-21 12:48:51 -07002081
Nick Pelly24bb9b82009-10-02 20:34:18 -07002082 private native int addRfcommServiceRecordNative(String name, long uuidMsb, long uuidLsb,
2083 short channel);
2084 private native boolean removeServiceRecordNative(int handle);
Jaikumar Ganeshb7e029d2010-03-09 15:31:24 -08002085 private native boolean setLinkTimeoutNative(String path, int num_slots);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002086}