Home | History | Annotate | Download | only in server
      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 
     17 package android.server;
     18 
     19 import android.bluetooth.BluetoothAdapter;
     20 import android.bluetooth.BluetoothDevice;
     21 import android.bluetooth.BluetoothProfile;
     22 import android.bluetooth.BluetoothA2dp;
     23 import android.bluetooth.BluetoothHeadset;
     24 import android.content.BroadcastReceiver;
     25 import android.content.ContentResolver;
     26 import android.content.Context;
     27 import android.content.Intent;
     28 import android.content.IntentFilter;
     29 import android.content.SharedPreferences;
     30 import android.provider.Settings;
     31 import android.util.Log;
     32 
     33 import java.io.BufferedReader;
     34 import java.io.BufferedWriter;
     35 import java.io.DataInputStream;
     36 import java.io.File;
     37 import java.io.FileInputStream;
     38 import java.io.FileNotFoundException;
     39 import java.io.FileOutputStream;
     40 import java.io.FileWriter;
     41 import java.io.IOException;
     42 import java.io.InputStreamReader;
     43 import java.util.ArrayList;
     44 import java.util.Arrays;
     45 import java.util.HashMap;
     46 import 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  */
     54 class 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 a 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