Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9a62c9cd authored by Jake Hamby's avatar Jake Hamby Committed by Jaikumar Ganesh
Browse files

Refactor android.server.BluetoothService classes.

Several cleanups to BluetoothService:
- Move BluetoothService.BondState inner class to top level.
- Extract adapter and remote device properties cache management
  logic into two new classes: BluetoothAdapterProperties and
  BluetoothDeviceProperties.
- Add getter methods for other classes in the package to access
  the new properties cache objects for multi-part operations.
- Inline log() method in BluetoothService and selected the
  appropriate Log method (Log.d, Log.w, Log.e) for each message.
- Refactor dump() method into smaller sized pieces.
- Clean up logic of updateCountersAndCheckForConnectionStateChange()
  method for better readability.
- Change sendConnectionStateChange() to return instead of sending
  an intent if the current or previous state values are invalid.
  Previously the code sent an intent with -1 for the invalid state.
- Added Javadoc comments to document the methods that are called from
  native Bluez code.
- Fixed some typos and code style issues.

Original Change by: Jake Hamby
Modified by: Jaikumar Ganesh

Change-Id: I76ebac00ecd29566dcb2d1ad3e7a486b7530ce24
parent 59362456
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -82,7 +82,6 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
    public static final int TRANSITION_TO_STABLE = 102;
    public static final int CONNECT_OTHER_PROFILES = 103;

    private static final int AUTO_CONNECT_DELAY = 6000; // 6 secs
    private static final int CONNECT_OTHER_PROFILES_DELAY = 4000; // 4 secs

    private BondedDevice mBondedDevice = new BondedDevice();
+31 −26
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@

/**
 * TODO: Move this to services.jar
 * and make the contructor package private again.
 * and make the constructor package private again.
 * @hide
 */

@@ -57,8 +57,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {

    private static final String PROPERTY_STATE = "State";

    private static int mSinkCount;

    private final Context mContext;
    private final IntentFilter mIntentFilter;
    private HashMap<BluetoothDevice, Integer> mAudioDevices;
@@ -128,7 +126,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
        }
    };


    private boolean isPhoneDocked(BluetoothDevice device) {
        // This works only because these broadcast intents are "sticky"
        Intent i = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT));
@@ -184,7 +181,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
        }
    }

    private int convertBluezSinkStringtoState(String value) {
    private int convertBluezSinkStringToState(String value) {
        if (value.equalsIgnoreCase("disconnected"))
            return BluetoothA2dp.STATE_DISCONNECTED;
        if (value.equalsIgnoreCase("connecting"))
@@ -215,7 +212,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
        // Properties are name-value pairs
        for (int i = 0; i < propValues.length; i+=2) {
            if (propValues[i].equals(PROPERTY_STATE)) {
                state = new Integer(convertBluezSinkStringtoState(propValues[i+1]));
                state = new Integer(convertBluezSinkStringToState(propValues[i+1]));
                break;
            }
        }
@@ -226,7 +223,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {

    private synchronized void onBluetoothEnable() {
        String devices = mBluetoothService.getProperty("Devices");
        mSinkCount = 0;
        if (devices != null) {
            String [] paths = devices.split(",");
            for (String path: paths) {
@@ -467,6 +463,14 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
                Settings.Secure.getBluetoothA2dpSinkPriorityKey(device.getAddress()), priority);
    }

    /**
     * Called by native code on a PropertyChanged signal from
     * org.bluez.AudioSink.
     *
     * @param path the object path for the changed device
     * @param propValues a string array containing the key and one or more
     *  values.
     */
    private synchronized void onSinkPropertyChanged(String path, String[] propValues) {
        if (!mBluetoothService.isEnabled()) {
            return;
@@ -482,7 +486,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
        BluetoothDevice device = mAdapter.getRemoteDevice(address);

        if (name.equals(PROPERTY_STATE)) {
            int state = convertBluezSinkStringtoState(propValues[1]);
            int state = convertBluezSinkStringToState(propValues[1]);
            log("A2DP: onSinkPropertyChanged newState is: " + state + "mPlayingA2dpDevice: " + mPlayingA2dpDevice);

            if (mAudioDevices.get(device) == null) {
@@ -508,12 +512,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {

    private void handleSinkStateChange(BluetoothDevice device, int prevState, int state) {
        if (state != prevState) {
            if (state == BluetoothA2dp.STATE_DISCONNECTED ||
                    state == BluetoothA2dp.STATE_DISCONNECTING) {
                mSinkCount--;
            } else if (state == BluetoothA2dp.STATE_CONNECTED) {
                mSinkCount ++;
            }
            mAudioDevices.put(device, state);

            checkSinkSuspendState(state);
@@ -577,6 +575,13 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
        return result;
    }

    /**
     * Called by native code for the async response to a Connect
     * method call to org.bluez.AudioSink.
     *
     * @param deviceObjectPath the object path for the connecting device
     * @param result true on success; false on error
     */
    private void onConnectSinkResult(String deviceObjectPath, boolean result) {
        // If the call was a success, ignore we will update the state
        // when we a Sink Property Change
+106 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.server;

import android.content.Context;
import android.util.Log;

import java.util.HashMap;
import java.util.Map;

class BluetoothAdapterProperties {

    private static final String TAG = "BluetoothAdapterProperties";

    private final Map<String, String> mPropertiesMap;
    private final Context mContext;
    private final BluetoothService mService;

    BluetoothAdapterProperties(Context context, BluetoothService service) {
        mPropertiesMap = new HashMap<String, String>();
        mContext = context;
        mService = service;
    }

    synchronized String getProperty(String name) {
        if (mPropertiesMap.isEmpty()) {
            getAllProperties();
        }
        return mPropertiesMap.get(name);
    }

    String getObjectPath() {
        return getProperty("ObjectPath");
    }

    synchronized void clear() {
        mPropertiesMap.clear();
    }

    synchronized boolean isEmpty() {
        return mPropertiesMap.isEmpty();
    }

    synchronized void setProperty(String name, String value) {
        mPropertiesMap.put(name, value);
    }

    synchronized void getAllProperties() {
        mContext.enforceCallingOrSelfPermission(
                BluetoothService.BLUETOOTH_PERM,
                "Need BLUETOOTH permission");
        mPropertiesMap.clear();

        String properties[] = (String[]) mService
                .getAdapterPropertiesNative();
        // The String Array consists of key-value pairs.
        if (properties == null) {
            Log.e(TAG, "*Error*: GetAdapterProperties returned NULL");
            return;
        }

        for (int i = 0; i < properties.length; i++) {
            String name = properties[i];
            String newValue = null;
            int len;
            if (name == null) {
                Log.e(TAG, "Error:Adapter Property at index " + i + " is null");
                continue;
            }
            if (name.equals("Devices") || name.equals("UUIDs")) {
                StringBuilder str = new StringBuilder();
                len = Integer.valueOf(properties[++i]);
                for (int j = 0; j < len; j++) {
                    str.append(properties[++i]);
                    str.append(",");
                }
                if (len > 0) {
                    newValue = str.toString();
                }
            } else {
                newValue = properties[++i];
            }
            mPropertiesMap.put(name, newValue);
        }

        // Add adapter object path property.
        String adapterPath = mService.getAdapterPathNative();
        if (adapterPath != null) {
            mPropertiesMap.put("ObjectPath", adapterPath + "/dev_");
        }
    }
}
+368 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.server;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * Local cache of bonding state.
 * We keep our own state to track the intermediate state BONDING, which
 * bluez does not track.
 * All addresses must be passed in upper case.
 */
class BluetoothBondState {
    private static final String TAG = "BluetoothBondState";
    private static final boolean DBG =  true;

    private final HashMap<String, Integer> mState = new HashMap<String, Integer>();
    private final HashMap<String, Integer> mPinAttempt = new HashMap<String, Integer>();

    private static final String AUTO_PAIRING_BLACKLIST =
        "/etc/bluetooth/auto_pairing.conf";
    private static final String DYNAMIC_AUTO_PAIRING_BLACKLIST =
        "/data/misc/bluetooth/dynamic_auto_pairing.conf";
    private ArrayList<String> mAutoPairingAddressBlacklist;
    private ArrayList<String> mAutoPairingExactNameBlacklist;
    private ArrayList<String> mAutoPairingPartialNameBlacklist;
    private ArrayList<String> mAutoPairingFixedPinZerosKeyboardList;
    // Addresses added to blacklist dynamically based on usage.
    private ArrayList<String> mAutoPairingDynamicAddressBlacklist;

    // If this is an outgoing connection, store the address.
    // There can be only 1 pending outgoing connection at a time,
    private String mPendingOutgoingBonding;

    private final Context mContext;
    private final BluetoothService mService;
    private final BluetoothInputProfileHandler mBluetoothInputProfileHandler;

    BluetoothBondState(Context context, BluetoothService service) {
        mContext = context;
        mService = service;
        mBluetoothInputProfileHandler =
            BluetoothInputProfileHandler.getInstance(mContext, mService);
    }

    synchronized void setPendingOutgoingBonding(String address) {
        mPendingOutgoingBonding = address;
    }

    public synchronized String getPendingOutgoingBonding() {
        return mPendingOutgoingBonding;
    }

    public synchronized void loadBondState() {
        if (mService.getBluetoothStateInternal() !=
                BluetoothAdapter.STATE_TURNING_ON) {
            return;
        }
        String val = mService.getAdapterProperties().getProperty("Devices");
        if (val == null) {
            return;
        }
        String[] bonds = val.split(",");
        if (bonds == null) {
            return;
        }
        mState.clear();
        if (DBG) Log.d(TAG, "found " + bonds.length + " bonded devices");
        for (String device : bonds) {
            mState.put(mService.getAddressFromObjectPath(device).toUpperCase(),
                    BluetoothDevice.BOND_BONDED);
        }
    }

    public synchronized void setBondState(String address, int state) {
        setBondState(address, state, 0);
    }

    /** reason is ignored unless state == BOND_NOT_BONDED */
    public synchronized void setBondState(String address, int state, int reason) {
        int oldState = getBondState(address);
        if (oldState == state) {
            return;
        }

        // Check if this was an pending outgoing bonding.
        // If yes, reset the state.
        if (oldState == BluetoothDevice.BOND_BONDING) {
            if (address.equals(mPendingOutgoingBonding)) {
                mPendingOutgoingBonding = null;
            }
        }

        if (state == BluetoothDevice.BOND_BONDED) {
            mService.addProfileState(address);
        } else if (state == BluetoothDevice.BOND_NONE) {
            mService.removeProfileState(address);
        }

        // HID is handled by BluetoothService, other profiles
        // will be handled by their respective services.
        mBluetoothInputProfileHandler.setInitialInputDevicePriority(
            mService.getRemoteDevice(address), state);

        if (DBG) {
            Log.d(TAG, address + " bond state " + oldState + " -> " + state
                + " (" + reason + ")");
        }
        Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mService.getRemoteDevice(address));
        intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, state);
        intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState);
        if (state == BluetoothDevice.BOND_NONE) {
            if (reason <= 0) {
                Log.w(TAG, "setBondState() called to unbond device, but reason code is " +
                      "invalid. Overriding reason code with BOND_RESULT_REMOVED");
                reason = BluetoothDevice.UNBOND_REASON_REMOVED;
            }
            intent.putExtra(BluetoothDevice.EXTRA_REASON, reason);
            mState.remove(address);
        } else {
            mState.put(address, state);
        }

        mContext.sendBroadcast(intent, BluetoothService.BLUETOOTH_PERM);
    }

    public boolean isAutoPairingBlacklisted(String address) {
        if (mAutoPairingAddressBlacklist != null) {
            for (String blacklistAddress : mAutoPairingAddressBlacklist) {
                if (address.startsWith(blacklistAddress)) return true;
            }
        }

        if (mAutoPairingDynamicAddressBlacklist != null) {
            for (String blacklistAddress: mAutoPairingDynamicAddressBlacklist) {
                if (address.equals(blacklistAddress)) return true;
            }
        }

        String name = mService.getRemoteName(address);
        if (name != null) {
            if (mAutoPairingExactNameBlacklist != null) {
                for (String blacklistName : mAutoPairingExactNameBlacklist) {
                    if (name.equals(blacklistName)) return true;
                }
            }

            if (mAutoPairingPartialNameBlacklist != null) {
                for (String blacklistName : mAutoPairingPartialNameBlacklist) {
                    if (name.startsWith(blacklistName)) return true;
                }
            }
        }
        return false;
    }

    public boolean isFixedPinZerosAutoPairKeyboard(String address) {
        // Note: the meaning of blacklist is reversed in this case.
        // If its in the list, we can go ahead and auto pair since
        // by default keyboard should have a variable PIN that we don't
        // auto pair using 0000.
        if (mAutoPairingFixedPinZerosKeyboardList != null) {
            for (String blacklistAddress : mAutoPairingFixedPinZerosKeyboardList) {
                if (address.startsWith(blacklistAddress)) return true;
            }
        }
        return false;
    }

    public synchronized int getBondState(String address) {
        Integer state = mState.get(address);
        if (state == null) {
            return BluetoothDevice.BOND_NONE;
        }
        return state.intValue();
    }

    /*package*/ synchronized String[] listInState(int state) {
        ArrayList<String> result = new ArrayList<String>(mState.size());
        for (Map.Entry<String, Integer> e : mState.entrySet()) {
            if (e.getValue().intValue() == state) {
                result.add(e.getKey());
            }
        }
        return result.toArray(new String[result.size()]);
    }

    public synchronized void addAutoPairingFailure(String address) {
        if (mAutoPairingDynamicAddressBlacklist == null) {
            mAutoPairingDynamicAddressBlacklist = new ArrayList<String>();
        }

        updateAutoPairingData(address);
        mAutoPairingDynamicAddressBlacklist.add(address);
    }

    public synchronized boolean isAutoPairingAttemptsInProgress(String address) {
        return getAttempt(address) != 0;
    }

    public synchronized void clearPinAttempts(String address) {
        mPinAttempt.remove(address);
    }

    public synchronized boolean hasAutoPairingFailed(String address) {
        if (mAutoPairingDynamicAddressBlacklist == null) return false;

        return mAutoPairingDynamicAddressBlacklist.contains(address);
    }

    public synchronized int getAttempt(String address) {
        Integer attempt = mPinAttempt.get(address);
        if (attempt == null) {
            return 0;
        }
        return attempt.intValue();
    }

    public synchronized void attempt(String address) {
        Integer attempt = mPinAttempt.get(address);
        int newAttempt;
        if (attempt == null) {
            newAttempt = 1;
        } else {
            newAttempt = attempt.intValue() + 1;
        }
        mPinAttempt.put(address, new Integer(newAttempt));
    }

    private void copyAutoPairingData() {
        FileInputStream in = null;
        FileOutputStream out = null;
        try {
            File file = new File(DYNAMIC_AUTO_PAIRING_BLACKLIST);
            if (file.exists()) return;

            in = new FileInputStream(AUTO_PAIRING_BLACKLIST);
            out= new FileOutputStream(DYNAMIC_AUTO_PAIRING_BLACKLIST);

            byte[] buf = new byte[1024];
            int len;
            while ((len = in.read(buf)) > 0) {
                out.write(buf, 0, len);
            }
        } catch (FileNotFoundException e) {
            Log.e(TAG, "FileNotFoundException: copyAutoPairingData " + e);
        } catch (IOException e) {
            Log.e(TAG, "IOException: copyAutoPairingData " + e);
        } finally {
             try {
                 if (in != null) in.close();
                 if (out != null) out.close();
             } catch (IOException e) {}
        }
    }

    synchronized public void readAutoPairingData() {
        if (mAutoPairingAddressBlacklist != null) return;
        copyAutoPairingData();
        FileInputStream fstream = null;
        try {
            fstream = new FileInputStream(DYNAMIC_AUTO_PAIRING_BLACKLIST);
            DataInputStream in = new DataInputStream(fstream);
            BufferedReader file = new BufferedReader(new InputStreamReader(in));
            String line;
            while((line = file.readLine()) != null) {
                line = line.trim();
                if (line.length() == 0 || line.startsWith("//")) continue;
                String[] value = line.split("=");
                if (value != null && value.length == 2) {
                    String[] val = value[1].split(",");
                    if (value[0].equalsIgnoreCase("AddressBlacklist")) {
                        mAutoPairingAddressBlacklist =
                            new ArrayList<String>(Arrays.asList(val));
                    } else if (value[0].equalsIgnoreCase("ExactNameBlacklist")) {
                        mAutoPairingExactNameBlacklist =
                            new ArrayList<String>(Arrays.asList(val));
                    } else if (value[0].equalsIgnoreCase("PartialNameBlacklist")) {
                        mAutoPairingPartialNameBlacklist =
                            new ArrayList<String>(Arrays.asList(val));
                    } else if (value[0].equalsIgnoreCase("FixedPinZerosKeyboardBlacklist")) {
                        mAutoPairingFixedPinZerosKeyboardList =
                            new ArrayList<String>(Arrays.asList(val));
                    } else if (value[0].equalsIgnoreCase("DynamicAddressBlacklist")) {
                        mAutoPairingDynamicAddressBlacklist =
                            new ArrayList<String>(Arrays.asList(val));
                    } else {
                        Log.e(TAG, "Error parsing Auto pairing blacklist file");
                    }
                }
            }
        } catch (FileNotFoundException e) {
            Log.e(TAG, "FileNotFoundException: readAutoPairingData " + e);
        } catch (IOException e) {
            Log.e(TAG, "IOException: readAutoPairingData " + e);
        } finally {
            if (fstream != null) {
                try {
                    fstream.close();
                } catch (IOException e) {
                    // Ignore
                }
            }
        }
    }

    // This function adds a bluetooth address to the auto pairing blacklist
    // file. These addresses are added to DynamicAddressBlacklistSection
    private void updateAutoPairingData(String address) {
        BufferedWriter out = null;
        try {
            out = new BufferedWriter(new FileWriter(DYNAMIC_AUTO_PAIRING_BLACKLIST, true));
            StringBuilder str = new StringBuilder();
            if (mAutoPairingDynamicAddressBlacklist.size() == 0) {
                str.append("DynamicAddressBlacklist=");
            }
            str.append(address);
            str.append(",");
            out.write(str.toString());
        } catch (FileNotFoundException e) {
            Log.e(TAG, "FileNotFoundException: updateAutoPairingData " + e);
        } catch (IOException e) {
            Log.e(TAG, "IOException: updateAutoPairingData " + e);
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    // Ignore
                }
            }
        }
    }
}
+128 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading