1/*
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.bluetooth.BluetoothProfile;
22import android.bluetooth.BluetoothA2dp;
23import android.bluetooth.BluetoothHeadset;
24import android.content.BroadcastReceiver;
25import android.content.ContentResolver;
26import android.content.Context;
27import android.content.Intent;
28import android.content.IntentFilter;
29import android.content.SharedPreferences;
30import android.provider.Settings;
31import android.util.Log;
32
33import java.io.BufferedReader;
34import java.io.BufferedWriter;
35import java.io.DataInputStream;
36import java.io.File;
37import java.io.FileInputStream;
38import java.io.FileNotFoundException;
39import java.io.FileOutputStream;
40import java.io.FileWriter;
41import java.io.IOException;
42import java.io.InputStreamReader;
43import java.util.ArrayList;
44import java.util.Arrays;
45import java.util.HashMap;
46import java.util.Map;
47
48/**
49 * Local cache of bonding state.
50 * We keep our own state to track the intermediate state BONDING, which
51 * bluez does not track.
52 * All addresses must be passed in upper case.
53 */
54class BluetoothBondState {
55    private static final String TAG = "BluetoothBondState";
56    private static final boolean DBG =  true;
57
58    private final HashMap<String, Integer> mState = new HashMap<String, Integer>();
59    private final HashMap<String, Integer> mPinAttempt = new HashMap<String, Integer>();
60
61    private static final String AUTO_PAIRING_BLACKLIST =
62        "/etc/bluetooth/auto_pairing.conf";
63    private static final String DYNAMIC_AUTO_PAIRING_BLACKLIST =
64        "/data/misc/bluetooth/dynamic_auto_pairing.conf";
65    private ArrayList<String> mAutoPairingAddressBlacklist;
66    private ArrayList<String> mAutoPairingExactNameBlacklist;
67    private ArrayList<String> mAutoPairingPartialNameBlacklist;
68    private ArrayList<String> mAutoPairingFixedPinZerosKeyboardList;
69    // Addresses added to blacklist dynamically based on usage.
70    private ArrayList<String> mAutoPairingDynamicAddressBlacklist;
71
72    // If this is an outgoing connection, store the address.
73    // There can be only 1 pending outgoing connection at a time,
74    private String mPendingOutgoingBonding;
75
76    private final Context mContext;
77    private final BluetoothService mService;
78    private final BluetoothInputProfileHandler mBluetoothInputProfileHandler;
79    private BluetoothA2dp mA2dpProxy;
80    private BluetoothHeadset mHeadsetProxy;
81
82    private ArrayList<String> mPairingRequestRcvd = new ArrayList<String>();
83
84    BluetoothBondState(Context context, BluetoothService service) {
85        mContext = context;
86        mService = service;
87        mBluetoothInputProfileHandler =
88            BluetoothInputProfileHandler.getInstance(mContext, mService);
89
90        IntentFilter filter = new IntentFilter();
91        filter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST);
92        mContext.registerReceiver(mReceiver, filter);
93        readAutoPairingData();
94    }
95
96    synchronized void setPendingOutgoingBonding(String address) {
97        mPendingOutgoingBonding = address;
98    }
99
100    public synchronized String getPendingOutgoingBonding() {
101        return mPendingOutgoingBonding;
102    }
103
104    public synchronized void initBondState() {
105        getProfileProxy();
106        loadBondState();
107    }
108
109    private void loadBondState() {
110        if (mService.getBluetoothStateInternal() !=
111                BluetoothAdapter.STATE_TURNING_ON) {
112            return;
113        }
114        String val = mService.getAdapterProperties().getProperty("Devices");
115        if (val == null) {
116            return;
117        }
118        String[] bonds = val.split(",");
119        if (bonds == null) {
120            return;
121        }
122        mState.clear();
123        if (DBG) Log.d(TAG, "found " + bonds.length + " bonded devices");
124        for (String device : bonds) {
125            mState.put(mService.getAddressFromObjectPath(device).toUpperCase(),
126                    BluetoothDevice.BOND_BONDED);
127        }
128    }
129
130    public synchronized void setBondState(String address, int state) {
131        setBondState(address, state, 0);
132    }
133
134    /** reason is ignored unless state == BOND_NOT_BONDED */
135    public synchronized void setBondState(String address, int state, int reason) {
136        if (DBG) Log.d(TAG, "setBondState " + "address" + " " + state + "reason: " + reason);
137
138        int oldState = getBondState(address);
139        if (oldState == state) {
140            return;
141        }
142
143        // Check if this was an pending outgoing bonding.
144        // If yes, reset the state.
145        if (oldState == BluetoothDevice.BOND_BONDING) {
146            if (address.equals(mPendingOutgoingBonding)) {
147                mPendingOutgoingBonding = null;
148            }
149        }
150
151        if (state == BluetoothDevice.BOND_BONDED) {
152            boolean setTrust = false;
153            if (mPairingRequestRcvd.contains(address)) setTrust = true;
154
155            mService.addProfileState(address, setTrust);
156            mPairingRequestRcvd.remove(address);
157
158        } else if (state == BluetoothDevice.BOND_BONDING) {
159            if (mA2dpProxy == null || mHeadsetProxy == null) {
160                getProfileProxy();
161            }
162        } else if (state == BluetoothDevice.BOND_NONE) {
163            mPairingRequestRcvd.remove(address);
164        }
165
166        setProfilePriorities(address, state);
167
168        if (DBG) {
169            Log.d(TAG, address + " bond state " + oldState + " -> " + state
170                + " (" + reason + ")");
171        }
172        Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
173        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mService.getRemoteDevice(address));
174        intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, state);
175        intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState);
176        if (state == BluetoothDevice.BOND_NONE) {
177            if (reason <= 0) {
178                Log.w(TAG, "setBondState() called to unbond device, but reason code is " +
179                      "invalid. Overriding reason code with BOND_RESULT_REMOVED");
180                reason = BluetoothDevice.UNBOND_REASON_REMOVED;
181            }
182            intent.putExtra(BluetoothDevice.EXTRA_REASON, reason);
183            mState.remove(address);
184        } else {
185            mState.put(address, state);
186        }
187
188        mContext.sendBroadcast(intent, BluetoothService.BLUETOOTH_PERM);
189    }
190
191    public boolean isAutoPairingBlacklisted(String address) {
192        if (mAutoPairingAddressBlacklist != null) {
193            for (String blacklistAddress : mAutoPairingAddressBlacklist) {
194                if (address.startsWith(blacklistAddress)) return true;
195            }
196        }
197
198        if (mAutoPairingDynamicAddressBlacklist != null) {
199            for (String blacklistAddress: mAutoPairingDynamicAddressBlacklist) {
200                if (address.equals(blacklistAddress)) return true;
201            }
202        }
203
204        String name = mService.getRemoteName(address);
205        if (name != null) {
206            if (mAutoPairingExactNameBlacklist != null) {
207                for (String blacklistName : mAutoPairingExactNameBlacklist) {
208                    if (name.equals(blacklistName)) return true;
209                }
210            }
211
212            if (mAutoPairingPartialNameBlacklist != null) {
213                for (String blacklistName : mAutoPairingPartialNameBlacklist) {
214                    if (name.startsWith(blacklistName)) return true;
215                }
216            }
217        }
218        return false;
219    }
220
221    public boolean isFixedPinZerosAutoPairKeyboard(String address) {
222        // Note: the meaning of blacklist is reversed in this case.
223        // If its in the list, we can go ahead and auto pair since
224        // by default keyboard should have a variable PIN that we don't
225        // auto pair using 0000.
226        if (mAutoPairingFixedPinZerosKeyboardList != null) {
227            for (String blacklistAddress : mAutoPairingFixedPinZerosKeyboardList) {
228                if (address.startsWith(blacklistAddress)) return true;
229            }
230        }
231        return false;
232    }
233
234    public synchronized int getBondState(String address) {
235        Integer state = mState.get(address);
236        if (state == null) {
237            return BluetoothDevice.BOND_NONE;
238        }
239        return state.intValue();
240    }
241
242    /*package*/ synchronized String[] listInState(int state) {
243        ArrayList<String> result = new ArrayList<String>(mState.size());
244        for (Map.Entry<String, Integer> e : mState.entrySet()) {
245            if (e.getValue().intValue() == state) {
246                result.add(e.getKey());
247            }
248        }
249        return result.toArray(new String[result.size()]);
250    }
251
252    public synchronized void addAutoPairingFailure(String address) {
253        if (mAutoPairingDynamicAddressBlacklist == null) {
254            mAutoPairingDynamicAddressBlacklist = new ArrayList<String>();
255        }
256
257        updateAutoPairingData(address);
258        mAutoPairingDynamicAddressBlacklist.add(address);
259    }
260
261    public synchronized boolean isAutoPairingAttemptsInProgress(String address) {
262        return getAttempt(address) != 0;
263    }
264
265    public synchronized void clearPinAttempts(String address) {
266        if (DBG) Log.d(TAG, "clearPinAttempts: " + address);
267
268        mPinAttempt.remove(address);
269    }
270
271    public synchronized boolean hasAutoPairingFailed(String address) {
272        if (mAutoPairingDynamicAddressBlacklist == null) return false;
273
274        return mAutoPairingDynamicAddressBlacklist.contains(address);
275    }
276
277    public synchronized int getAttempt(String address) {
278        Integer attempt = mPinAttempt.get(address);
279        if (attempt == null) {
280            return 0;
281        }
282        return attempt.intValue();
283    }
284
285    public synchronized void attempt(String address) {
286        Integer attempt = mPinAttempt.get(address);
287        int newAttempt;
288        if (attempt == null) {
289            newAttempt = 1;
290        } else {
291            newAttempt = attempt.intValue() + 1;
292        }
293        if (DBG) Log.d(TAG, "attemp newAttempt: " + newAttempt);
294
295        mPinAttempt.put(address, new Integer(newAttempt));
296    }
297
298    private void getProfileProxy() {
299        BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
300
301        if (mA2dpProxy == null) {
302            bluetoothAdapter.getProfileProxy(mContext, mProfileServiceListener,
303                                             BluetoothProfile.A2DP);
304        }
305
306        if (mHeadsetProxy == null) {
307            bluetoothAdapter.getProfileProxy(mContext, mProfileServiceListener,
308                                             BluetoothProfile.HEADSET);
309        }
310    }
311
312    private void closeProfileProxy() {
313        BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
314
315        if (mA2dpProxy != null) {
316            bluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP, mA2dpProxy);
317        }
318
319        if (mHeadsetProxy != null) {
320            bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mHeadsetProxy);
321        }
322    }
323
324    private BluetoothProfile.ServiceListener mProfileServiceListener =
325        new BluetoothProfile.ServiceListener() {
326
327        public void onServiceConnected(int profile, BluetoothProfile proxy) {
328            if (profile == BluetoothProfile.A2DP) {
329                mA2dpProxy = (BluetoothA2dp) proxy;
330            } else if (profile == BluetoothProfile.HEADSET) {
331                mHeadsetProxy = (BluetoothHeadset) proxy;
332            }
333        }
334
335        public void onServiceDisconnected(int profile) {
336            if (profile == BluetoothProfile.A2DP) {
337                mA2dpProxy = null;
338            } else if (profile == BluetoothProfile.HEADSET) {
339                mHeadsetProxy = null;
340            }
341        }
342    };
343
344    private void copyAutoPairingData() {
345        FileInputStream in = null;
346        FileOutputStream out = null;
347        try {
348            File file = new File(DYNAMIC_AUTO_PAIRING_BLACKLIST);
349            if (file.exists()) return;
350
351            in = new FileInputStream(AUTO_PAIRING_BLACKLIST);
352            out= new FileOutputStream(DYNAMIC_AUTO_PAIRING_BLACKLIST);
353
354            byte[] buf = new byte[1024];
355            int len;
356            while ((len = in.read(buf)) > 0) {
357                out.write(buf, 0, len);
358            }
359        } catch (FileNotFoundException e) {
360            Log.e(TAG, "FileNotFoundException: copyAutoPairingData " + e);
361        } catch (IOException e) {
362            Log.e(TAG, "IOException: copyAutoPairingData " + e);
363        } finally {
364             try {
365                 if (in != null) in.close();
366                 if (out != null) out.close();
367             } catch (IOException e) {}
368        }
369    }
370
371    synchronized public void readAutoPairingData() {
372        if (mAutoPairingAddressBlacklist != null) return;
373        copyAutoPairingData();
374        FileInputStream fstream = null;
375        try {
376            fstream = new FileInputStream(DYNAMIC_AUTO_PAIRING_BLACKLIST);
377            DataInputStream in = new DataInputStream(fstream);
378            BufferedReader file = new BufferedReader(new InputStreamReader(in));
379            String line;
380            while((line = file.readLine()) != null) {
381                line = line.trim();
382                if (line.length() == 0 || line.startsWith("//")) continue;
383                String[] value = line.split("=");
384                if (value != null && value.length == 2) {
385                    String[] val = value[1].split(",");
386                    if (value[0].equalsIgnoreCase("AddressBlacklist")) {
387                        mAutoPairingAddressBlacklist =
388                            new ArrayList<String>(Arrays.asList(val));
389                    } else if (value[0].equalsIgnoreCase("ExactNameBlacklist")) {
390                        mAutoPairingExactNameBlacklist =
391                            new ArrayList<String>(Arrays.asList(val));
392                    } else if (value[0].equalsIgnoreCase("PartialNameBlacklist")) {
393                        mAutoPairingPartialNameBlacklist =
394                            new ArrayList<String>(Arrays.asList(val));
395                    } else if (value[0].equalsIgnoreCase("FixedPinZerosKeyboardBlacklist")) {
396                        mAutoPairingFixedPinZerosKeyboardList =
397                            new ArrayList<String>(Arrays.asList(val));
398                    } else if (value[0].equalsIgnoreCase("DynamicAddressBlacklist")) {
399                        mAutoPairingDynamicAddressBlacklist =
400                            new ArrayList<String>(Arrays.asList(val));
401                    } else {
402                        Log.e(TAG, "Error parsing Auto pairing blacklist file");
403                    }
404                }
405            }
406        } catch (FileNotFoundException e) {
407            Log.e(TAG, "FileNotFoundException: readAutoPairingData " + e);
408        } catch (IOException e) {
409            Log.e(TAG, "IOException: readAutoPairingData " + e);
410        } finally {
411            if (fstream != null) {
412                try {
413                    fstream.close();
414                } catch (IOException e) {
415                    // Ignore
416                }
417            }
418        }
419    }
420
421    // This function adds a bluetooth address to the auto pairing blacklist
422    // file. These addresses are added to DynamicAddressBlacklistSection
423    private void updateAutoPairingData(String address) {
424        BufferedWriter out = null;
425        try {
426            out = new BufferedWriter(new FileWriter(DYNAMIC_AUTO_PAIRING_BLACKLIST, true));
427            StringBuilder str = new StringBuilder();
428            if (mAutoPairingDynamicAddressBlacklist.size() == 0) {
429                str.append("DynamicAddressBlacklist=");
430            }
431            str.append(address);
432            str.append(",");
433            out.write(str.toString());
434        } catch (FileNotFoundException e) {
435            Log.e(TAG, "FileNotFoundException: updateAutoPairingData " + e);
436        } catch (IOException e) {
437            Log.e(TAG, "IOException: updateAutoPairingData " + e);
438        } finally {
439            if (out != null) {
440                try {
441                    out.close();
442                } catch (IOException e) {
443                    // Ignore
444                }
445            }
446        }
447    }
448
449    // Set service priority of Hid, A2DP and Headset profiles depending on
450    // the bond state change
451    private void setProfilePriorities(String address, int state) {
452        BluetoothDevice remoteDevice = mService.getRemoteDevice(address);
453        // HID is handled by BluetoothService
454        mBluetoothInputProfileHandler.setInitialInputDevicePriority(remoteDevice, state);
455
456        // Set service priority of A2DP and Headset
457        // We used to do the priority change in the 2 services after the broadcast
458        //   intent reach them. But that left a small time gap that could reject
459        //   incoming connection due to undefined priorities.
460        if (state == BluetoothDevice.BOND_BONDED) {
461            if (mA2dpProxy != null &&
462                  mA2dpProxy.getPriority(remoteDevice) == BluetoothProfile.PRIORITY_UNDEFINED) {
463                mA2dpProxy.setPriority(remoteDevice, BluetoothProfile.PRIORITY_ON);
464            }
465
466            if (mHeadsetProxy != null &&
467                  mHeadsetProxy.getPriority(remoteDevice) == BluetoothProfile.PRIORITY_UNDEFINED) {
468                mHeadsetProxy.setPriority(remoteDevice, BluetoothProfile.PRIORITY_ON);
469            }
470        } else if (state == BluetoothDevice.BOND_NONE) {
471            if (mA2dpProxy != null) {
472                mA2dpProxy.setPriority(remoteDevice, BluetoothProfile.PRIORITY_UNDEFINED);
473            }
474            if (mHeadsetProxy != null) {
475                mHeadsetProxy.setPriority(remoteDevice, BluetoothProfile.PRIORITY_UNDEFINED);
476            }
477        }
478
479        if (mA2dpProxy == null || mHeadsetProxy == null) {
480            Log.e(TAG, "Proxy is null:" + mA2dpProxy + ":" + mHeadsetProxy);
481        }
482    }
483
484    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
485        @Override
486        public void onReceive(Context context, Intent intent) {
487            if (intent == null) return;
488
489            String action = intent.getAction();
490            if (action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) {
491                BluetoothDevice dev = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
492                String address = dev.getAddress();
493                mPairingRequestRcvd.add(address);
494            }
495        }
496    };
497}
498