blob: 2304a70708506072ee5e267d7f6b86fd4b651ede [file] [log] [blame]
Jake Hamby9a62c9c2010-12-09 14:47:57 -08001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.server;
18
19import android.bluetooth.BluetoothAdapter;
20import android.bluetooth.BluetoothDevice;
21import android.content.Context;
22import android.content.Intent;
23import android.util.Log;
24
25import java.io.BufferedReader;
26import java.io.BufferedWriter;
27import java.io.DataInputStream;
28import java.io.File;
29import java.io.FileInputStream;
30import java.io.FileNotFoundException;
31import java.io.FileOutputStream;
32import java.io.FileWriter;
33import java.io.IOException;
34import java.io.InputStreamReader;
35import java.util.ArrayList;
36import java.util.Arrays;
37import java.util.HashMap;
38import java.util.Map;
39
40/**
41 * Local cache of bonding state.
42 * We keep our own state to track the intermediate state BONDING, which
43 * bluez does not track.
44 * All addresses must be passed in upper case.
45 */
46class BluetoothBondState {
47 private static final String TAG = "BluetoothBondState";
48 private static final boolean DBG = true;
49
50 private final HashMap<String, Integer> mState = new HashMap<String, Integer>();
51 private final HashMap<String, Integer> mPinAttempt = new HashMap<String, Integer>();
52
53 private static final String AUTO_PAIRING_BLACKLIST =
54 "/etc/bluetooth/auto_pairing.conf";
55 private static final String DYNAMIC_AUTO_PAIRING_BLACKLIST =
56 "/data/misc/bluetooth/dynamic_auto_pairing.conf";
57 private ArrayList<String> mAutoPairingAddressBlacklist;
58 private ArrayList<String> mAutoPairingExactNameBlacklist;
59 private ArrayList<String> mAutoPairingPartialNameBlacklist;
60 private ArrayList<String> mAutoPairingFixedPinZerosKeyboardList;
61 // Addresses added to blacklist dynamically based on usage.
62 private ArrayList<String> mAutoPairingDynamicAddressBlacklist;
63
64 // If this is an outgoing connection, store the address.
65 // There can be only 1 pending outgoing connection at a time,
66 private String mPendingOutgoingBonding;
67
68 private final Context mContext;
69 private final BluetoothService mService;
70 private final BluetoothInputProfileHandler mBluetoothInputProfileHandler;
71
72 BluetoothBondState(Context context, BluetoothService service) {
73 mContext = context;
74 mService = service;
75 mBluetoothInputProfileHandler =
76 BluetoothInputProfileHandler.getInstance(mContext, mService);
77 }
78
79 synchronized void setPendingOutgoingBonding(String address) {
80 mPendingOutgoingBonding = address;
81 }
82
83 public synchronized String getPendingOutgoingBonding() {
84 return mPendingOutgoingBonding;
85 }
86
87 public synchronized void loadBondState() {
88 if (mService.getBluetoothStateInternal() !=
89 BluetoothAdapter.STATE_TURNING_ON) {
90 return;
91 }
92 String val = mService.getAdapterProperties().getProperty("Devices");
93 if (val == null) {
94 return;
95 }
96 String[] bonds = val.split(",");
97 if (bonds == null) {
98 return;
99 }
100 mState.clear();
101 if (DBG) Log.d(TAG, "found " + bonds.length + " bonded devices");
102 for (String device : bonds) {
103 mState.put(mService.getAddressFromObjectPath(device).toUpperCase(),
104 BluetoothDevice.BOND_BONDED);
105 }
106 }
107
108 public synchronized void setBondState(String address, int state) {
109 setBondState(address, state, 0);
110 }
111
112 /** reason is ignored unless state == BOND_NOT_BONDED */
113 public synchronized void setBondState(String address, int state, int reason) {
114 int oldState = getBondState(address);
115 if (oldState == state) {
116 return;
117 }
118
119 // Check if this was an pending outgoing bonding.
120 // If yes, reset the state.
121 if (oldState == BluetoothDevice.BOND_BONDING) {
122 if (address.equals(mPendingOutgoingBonding)) {
123 mPendingOutgoingBonding = null;
124 }
125 }
126
127 if (state == BluetoothDevice.BOND_BONDED) {
128 mService.addProfileState(address);
129 } else if (state == BluetoothDevice.BOND_NONE) {
130 mService.removeProfileState(address);
131 }
132
133 // HID is handled by BluetoothService, other profiles
134 // will be handled by their respective services.
135 mBluetoothInputProfileHandler.setInitialInputDevicePriority(
136 mService.getRemoteDevice(address), state);
137
138 if (DBG) {
139 Log.d(TAG, address + " bond state " + oldState + " -> " + state
140 + " (" + reason + ")");
141 }
142 Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
143 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mService.getRemoteDevice(address));
144 intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, state);
145 intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState);
146 if (state == BluetoothDevice.BOND_NONE) {
147 if (reason <= 0) {
148 Log.w(TAG, "setBondState() called to unbond device, but reason code is " +
149 "invalid. Overriding reason code with BOND_RESULT_REMOVED");
150 reason = BluetoothDevice.UNBOND_REASON_REMOVED;
151 }
152 intent.putExtra(BluetoothDevice.EXTRA_REASON, reason);
153 mState.remove(address);
154 } else {
155 mState.put(address, state);
156 }
157
158 mContext.sendBroadcast(intent, BluetoothService.BLUETOOTH_PERM);
159 }
160
161 public boolean isAutoPairingBlacklisted(String address) {
162 if (mAutoPairingAddressBlacklist != null) {
163 for (String blacklistAddress : mAutoPairingAddressBlacklist) {
164 if (address.startsWith(blacklistAddress)) return true;
165 }
166 }
167
168 if (mAutoPairingDynamicAddressBlacklist != null) {
169 for (String blacklistAddress: mAutoPairingDynamicAddressBlacklist) {
170 if (address.equals(blacklistAddress)) return true;
171 }
172 }
173
174 String name = mService.getRemoteName(address);
175 if (name != null) {
176 if (mAutoPairingExactNameBlacklist != null) {
177 for (String blacklistName : mAutoPairingExactNameBlacklist) {
178 if (name.equals(blacklistName)) return true;
179 }
180 }
181
182 if (mAutoPairingPartialNameBlacklist != null) {
183 for (String blacklistName : mAutoPairingPartialNameBlacklist) {
184 if (name.startsWith(blacklistName)) return true;
185 }
186 }
187 }
188 return false;
189 }
190
191 public boolean isFixedPinZerosAutoPairKeyboard(String address) {
192 // Note: the meaning of blacklist is reversed in this case.
193 // If its in the list, we can go ahead and auto pair since
194 // by default keyboard should have a variable PIN that we don't
195 // auto pair using 0000.
196 if (mAutoPairingFixedPinZerosKeyboardList != null) {
197 for (String blacklistAddress : mAutoPairingFixedPinZerosKeyboardList) {
198 if (address.startsWith(blacklistAddress)) return true;
199 }
200 }
201 return false;
202 }
203
204 public synchronized int getBondState(String address) {
205 Integer state = mState.get(address);
206 if (state == null) {
207 return BluetoothDevice.BOND_NONE;
208 }
209 return state.intValue();
210 }
211
212 /*package*/ synchronized String[] listInState(int state) {
213 ArrayList<String> result = new ArrayList<String>(mState.size());
214 for (Map.Entry<String, Integer> e : mState.entrySet()) {
215 if (e.getValue().intValue() == state) {
216 result.add(e.getKey());
217 }
218 }
219 return result.toArray(new String[result.size()]);
220 }
221
222 public synchronized void addAutoPairingFailure(String address) {
223 if (mAutoPairingDynamicAddressBlacklist == null) {
224 mAutoPairingDynamicAddressBlacklist = new ArrayList<String>();
225 }
226
227 updateAutoPairingData(address);
228 mAutoPairingDynamicAddressBlacklist.add(address);
229 }
230
231 public synchronized boolean isAutoPairingAttemptsInProgress(String address) {
232 return getAttempt(address) != 0;
233 }
234
235 public synchronized void clearPinAttempts(String address) {
236 mPinAttempt.remove(address);
237 }
238
239 public synchronized boolean hasAutoPairingFailed(String address) {
240 if (mAutoPairingDynamicAddressBlacklist == null) return false;
241
242 return mAutoPairingDynamicAddressBlacklist.contains(address);
243 }
244
245 public synchronized int getAttempt(String address) {
246 Integer attempt = mPinAttempt.get(address);
247 if (attempt == null) {
248 return 0;
249 }
250 return attempt.intValue();
251 }
252
253 public synchronized void attempt(String address) {
254 Integer attempt = mPinAttempt.get(address);
255 int newAttempt;
256 if (attempt == null) {
257 newAttempt = 1;
258 } else {
259 newAttempt = attempt.intValue() + 1;
260 }
261 mPinAttempt.put(address, new Integer(newAttempt));
262 }
263
264 private void copyAutoPairingData() {
265 FileInputStream in = null;
266 FileOutputStream out = null;
267 try {
268 File file = new File(DYNAMIC_AUTO_PAIRING_BLACKLIST);
269 if (file.exists()) return;
270
271 in = new FileInputStream(AUTO_PAIRING_BLACKLIST);
272 out= new FileOutputStream(DYNAMIC_AUTO_PAIRING_BLACKLIST);
273
274 byte[] buf = new byte[1024];
275 int len;
276 while ((len = in.read(buf)) > 0) {
277 out.write(buf, 0, len);
278 }
279 } catch (FileNotFoundException e) {
280 Log.e(TAG, "FileNotFoundException: copyAutoPairingData " + e);
281 } catch (IOException e) {
282 Log.e(TAG, "IOException: copyAutoPairingData " + e);
283 } finally {
284 try {
285 if (in != null) in.close();
286 if (out != null) out.close();
287 } catch (IOException e) {}
288 }
289 }
290
291 synchronized public void readAutoPairingData() {
292 if (mAutoPairingAddressBlacklist != null) return;
293 copyAutoPairingData();
294 FileInputStream fstream = null;
295 try {
296 fstream = new FileInputStream(DYNAMIC_AUTO_PAIRING_BLACKLIST);
297 DataInputStream in = new DataInputStream(fstream);
298 BufferedReader file = new BufferedReader(new InputStreamReader(in));
299 String line;
300 while((line = file.readLine()) != null) {
301 line = line.trim();
302 if (line.length() == 0 || line.startsWith("//")) continue;
303 String[] value = line.split("=");
304 if (value != null && value.length == 2) {
305 String[] val = value[1].split(",");
306 if (value[0].equalsIgnoreCase("AddressBlacklist")) {
307 mAutoPairingAddressBlacklist =
308 new ArrayList<String>(Arrays.asList(val));
309 } else if (value[0].equalsIgnoreCase("ExactNameBlacklist")) {
310 mAutoPairingExactNameBlacklist =
311 new ArrayList<String>(Arrays.asList(val));
312 } else if (value[0].equalsIgnoreCase("PartialNameBlacklist")) {
313 mAutoPairingPartialNameBlacklist =
314 new ArrayList<String>(Arrays.asList(val));
315 } else if (value[0].equalsIgnoreCase("FixedPinZerosKeyboardBlacklist")) {
316 mAutoPairingFixedPinZerosKeyboardList =
317 new ArrayList<String>(Arrays.asList(val));
318 } else if (value[0].equalsIgnoreCase("DynamicAddressBlacklist")) {
319 mAutoPairingDynamicAddressBlacklist =
320 new ArrayList<String>(Arrays.asList(val));
321 } else {
322 Log.e(TAG, "Error parsing Auto pairing blacklist file");
323 }
324 }
325 }
326 } catch (FileNotFoundException e) {
327 Log.e(TAG, "FileNotFoundException: readAutoPairingData " + e);
328 } catch (IOException e) {
329 Log.e(TAG, "IOException: readAutoPairingData " + e);
330 } finally {
331 if (fstream != null) {
332 try {
333 fstream.close();
334 } catch (IOException e) {
335 // Ignore
336 }
337 }
338 }
339 }
340
341 // This function adds a bluetooth address to the auto pairing blacklist
342 // file. These addresses are added to DynamicAddressBlacklistSection
343 private void updateAutoPairingData(String address) {
344 BufferedWriter out = null;
345 try {
346 out = new BufferedWriter(new FileWriter(DYNAMIC_AUTO_PAIRING_BLACKLIST, true));
347 StringBuilder str = new StringBuilder();
348 if (mAutoPairingDynamicAddressBlacklist.size() == 0) {
349 str.append("DynamicAddressBlacklist=");
350 }
351 str.append(address);
352 str.append(",");
353 out.write(str.toString());
354 } catch (FileNotFoundException e) {
355 Log.e(TAG, "FileNotFoundException: updateAutoPairingData " + e);
356 } catch (IOException e) {
357 Log.e(TAG, "IOException: updateAutoPairingData " + e);
358 } finally {
359 if (out != null) {
360 try {
361 out.close();
362 } catch (IOException e) {
363 // Ignore
364 }
365 }
366 }
367 }
368}