FileDocCategorySizeDatePackage
LocalBluetoothDevice.javaAPI DocAndroid 1.5 API27810Wed May 06 22:42:48 BST 2009com.android.settings.bluetooth

LocalBluetoothDevice

public class LocalBluetoothDevice extends Object implements Comparable
LocalBluetoothDevice represents a remote Bluetooth device. It contains attributes of the device (such as the address, name, RSSI, etc.) and functionality that can be performed on the device (connect, pair, disconnect, etc.).

Fields Summary
private static final String
TAG
private static final int
CONTEXT_ITEM_CONNECT
private static final int
CONTEXT_ITEM_DISCONNECT
private static final int
CONTEXT_ITEM_UNPAIR
private static final int
CONTEXT_ITEM_CONNECT_ADVANCED
private final String
mAddress
private String
mName
private short
mRssi
private int
mBtClass
private List
mProfiles
private boolean
mVisible
private final LocalBluetoothManager
mLocalManager
private List
mCallbacks
private boolean
mIsConnectingErrorPossible
When we connect to multiple profiles, we only want to display a single error even if they all fail. This tracks that state.
private static final long
MAX_WAIT_TIME_FOR_FRAMEWORK
private static LinkedList
workQueue
We want to serialize connect and disconnect calls. http://b/170538 This are some headsets that may have L2CAP resource limitation. We want to limit the bt bandwidth usage. A queue to keep track of asynchronous calls to the bt framework. The first item, if exist, should be in progress i.e. went to the bt framework already, waiting for a notification to come back. The second item and beyond have not been sent to the bt framework yet.
Constructors Summary
LocalBluetoothDevice(android.content.Context context, String address)

        mLocalManager = LocalBluetoothManager.getInstance(context);
        if (mLocalManager == null) {
            throw new IllegalStateException(
                    "Cannot use LocalBluetoothDevice without Bluetooth hardware");
        }

        mAddress = address;

        fillData();
    
Methods Summary
public voidaskDisconnect()

        Context context = mLocalManager.getForegroundActivity();
        if (context == null) {
            // Cannot ask, since we need an activity context
            disconnect();
            return;
        }

        Resources res = context.getResources();

        String name = getName();
        if (TextUtils.isEmpty(name)) {
            name = res.getString(R.string.bluetooth_device);
        }
        String message = res.getString(R.string.bluetooth_disconnect_blank, name);

        DialogInterface.OnClickListener disconnectListener = new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                disconnect();
            }
        };

        AlertDialog ad = new AlertDialog.Builder(context)
                .setTitle(getName())
                .setMessage(message)
                .setPositiveButton(android.R.string.ok, disconnectListener)
                .setNegativeButton(android.R.string.cancel, null)
                .show();
    
public intcompareTo(com.android.settings.bluetooth.LocalBluetoothDevice another)

        int comparison;

        // Connected above not connected
        comparison = (another.isConnected() ? 1 : 0) - (isConnected() ? 1 : 0);
        if (comparison != 0) return comparison;

        // Paired above not paired
        comparison = (another.getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0) -
            (getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0);
        if (comparison != 0) return comparison;

        // Visible above not visible
        comparison = (another.mVisible ? 1 : 0) - (mVisible ? 1 : 0);
        if (comparison != 0) return comparison;

        // Stronger signal above weaker signal
        comparison = another.mRssi - mRssi;
        if (comparison != 0) return comparison;

        // Fallback on name
        return getName().compareTo(another.getName());
    
public voidconnect()

        if (!ensurePaired()) return;

        // Reset the only-show-one-error-dialog tracking variable
        mIsConnectingErrorPossible = true;

        Context context = mLocalManager.getContext();
        boolean hasAtLeastOnePreferredProfile = false;
        for (Profile profile : mProfiles) {
            LocalBluetoothProfileManager profileManager =
                    LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile);
            if (profileManager.isPreferred(mAddress)) {
                hasAtLeastOnePreferredProfile = true;
                queueCommand(new BluetoothJob(BluetoothCommand.CONNECT, this, profile));
            }
        }

        if (!hasAtLeastOnePreferredProfile) {
            connectAndPreferAllProfiles();
        }
    
public voidconnect(com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile profile)

        // Reset the only-show-one-error-dialog tracking variable
        mIsConnectingErrorPossible = true;
        queueCommand(new BluetoothJob(BluetoothCommand.CONNECT, this, profile));
    
private voidconnectAndPreferAllProfiles()

        if (!ensurePaired()) return;

        // Reset the only-show-one-error-dialog tracking variable
        mIsConnectingErrorPossible = true;

        Context context = mLocalManager.getContext();
        for (Profile profile : mProfiles) {
            LocalBluetoothProfileManager profileManager =
                    LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile);
            profileManager.setPreferred(mAddress, true);
            queueCommand(new BluetoothJob(BluetoothCommand.CONNECT, this, profile));
        }
    
private booleanconnectInt(com.android.settings.bluetooth.LocalBluetoothDevice device, com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile profile)

        if (!device.ensurePaired()) return false;

        LocalBluetoothProfileManager profileManager =
                LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile);
        int status = profileManager.getConnectionStatus(device.mAddress);
        if (!SettingsBtStatus.isConnectionStatusConnected(status)) {
            if (profileManager.connect(device.mAddress) == BluetoothDevice.RESULT_SUCCESS) {
                return true;
            }
            Log.i(TAG, "Failed to connect " + profile.toString() + " to " + device.mName);
        }
        Log.i(TAG, "Not connected");
        return false;
    
public voiddisconnect()

        for (Profile profile : mProfiles) {
            disconnect(profile);
        }
    
public voiddisconnect(com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile profile)

        queueCommand(new BluetoothJob(BluetoothCommand.DISCONNECT, this, profile));
    
private booleandisconnectInt(com.android.settings.bluetooth.LocalBluetoothDevice device, com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile profile)

        LocalBluetoothProfileManager profileManager =
                LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile);
        int status = profileManager.getConnectionStatus(device.mAddress);
        if (SettingsBtStatus.isConnectionStatusConnected(status)) {
            if (profileManager.disconnect(device.mAddress) == BluetoothDevice.RESULT_SUCCESS) {
                return true;
            }
        }
        return false;
    
private voiddispatchAttributesChanged()

        synchronized (mCallbacks) {
            for (Callback callback : mCallbacks) {
                callback.onDeviceAttributesChanged(this);
            }
        }
    
private booleanensurePaired()

        if (getBondState() == BluetoothDevice.BOND_NOT_BONDED) {
            pair();
            return false;
        } else {
            return true;
        }
    
public booleanequals(java.lang.Object o)

        if ((o == null) || !(o instanceof LocalBluetoothDevice)) {
            throw new ClassCastException();
        }

        return mAddress.equals(((LocalBluetoothDevice) o).mAddress);
    
private voidfetchBtClass()
Fetches a new value for the cached BT class.

        mBtClass = mLocalManager.getBluetoothManager().getRemoteClass(mAddress);
        if (mBtClass != BluetoothClass.ERROR) {
            LocalBluetoothProfileManager.fill(mBtClass, mProfiles);
        }
    
private voidfetchName()

        mName = mLocalManager.getBluetoothManager().getRemoteName(mAddress);

        if (TextUtils.isEmpty(mName)) {
            mName = mAddress;
        }
    
private voidfillData()

        BluetoothDevice manager = mLocalManager.getBluetoothManager();

        fetchName();
        fetchBtClass();

        mVisible = false;

        dispatchAttributesChanged();
    
public java.lang.StringgetAddress()

        return mAddress;
    
public intgetBondState()

        return mLocalManager.getBluetoothManager().getBondState(mAddress);
    
public intgetBtClassDrawable()


        // First try looking at profiles
        if (mProfiles.contains(Profile.A2DP)) {
            return R.drawable.ic_bt_headphones_a2dp;
        } else if (mProfiles.contains(Profile.HEADSET)) {
            return R.drawable.ic_bt_headset_hfp;
        }

        // Fallback on class
        switch (BluetoothClass.Device.Major.getDeviceMajor(mBtClass)) {
        case BluetoothClass.Device.Major.COMPUTER:
            return R.drawable.ic_bt_laptop;

        case BluetoothClass.Device.Major.PHONE:
            return R.drawable.ic_bt_cellphone;

        default:
            return 0;
        }
    
public java.lang.StringgetName()

        return mName;
    
private intgetOneOffSummary()
We have special summaries when particular profiles are connected. This checks for those states and returns an applicable summary.

return
A one-off summary that is applicable for the current state, or 0.

        boolean isA2dpConnected = false, isHeadsetConnected = false, isConnecting = false;

        if (mProfiles.contains(Profile.A2DP)) {
            LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager
                    .getProfileManager(mLocalManager, Profile.A2DP);
            isConnecting = profileManager.getConnectionStatus(mAddress) ==
                    SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
            isA2dpConnected = profileManager.isConnected(mAddress);
        }

        if (mProfiles.contains(Profile.HEADSET)) {
            LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager
                    .getProfileManager(mLocalManager, Profile.HEADSET);
            isConnecting |= profileManager.getConnectionStatus(mAddress) ==
                    SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
            isHeadsetConnected = profileManager.isConnected(mAddress);
        }

        if (isConnecting) {
            // If any of these important profiles is connecting, prefer that
            return SettingsBtStatus.getConnectionStatusSummary(
                    SettingsBtStatus.CONNECTION_STATUS_CONNECTING);
        } else if (isA2dpConnected && isHeadsetConnected) {
            return R.string.bluetooth_summary_connected_to_a2dp_headset;
        } else if (isA2dpConnected) {
            return R.string.bluetooth_summary_connected_to_a2dp;
        } else if (isHeadsetConnected) {
            return R.string.bluetooth_summary_connected_to_headset;
        } else {
            return 0;
        }
    
public java.util.ListgetProfiles()

        return new ArrayList<Profile>(mProfiles);
    
public intgetSummary()

        // TODO: clean up
        int oneOffSummary = getOneOffSummary();
        if (oneOffSummary != 0) {
            return oneOffSummary;
        }

        for (Profile profile : mProfiles) {
            LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager
                    .getProfileManager(mLocalManager, profile);
            int connectionStatus = profileManager.getConnectionStatus(mAddress);

            if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus) ||
                    connectionStatus == SettingsBtStatus.CONNECTION_STATUS_CONNECTING ||
                    connectionStatus == SettingsBtStatus.CONNECTION_STATUS_DISCONNECTING) {
                return SettingsBtStatus.getConnectionStatusSummary(connectionStatus);
            }
        }

        return SettingsBtStatus.getPairingStatusSummary(getBondState());
    
public inthashCode()

        return mAddress.hashCode();
    
public booleanisBusy()

        for (Profile profile : mProfiles) {
            int status = LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile)
                    .getConnectionStatus(mAddress);
            if (SettingsBtStatus.isConnectionStatusBusy(status)) {
                return true;
            }
        }

        if (getBondState() == BluetoothDevice.BOND_BONDING) {
            return true;
        }

        return false;
    
public booleanisConnected()
Checks whether we are connected to this device (any profile counts).

return
Whether it is connected.

        for (Profile profile : mProfiles) {
            int status = LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile)
                    .getConnectionStatus(mAddress);
            if (SettingsBtStatus.isConnectionStatusConnected(status)) {
                return true;
            }
        }

        return false;
    
public booleanisVisible()

        return mVisible;
    
public voidonClicked()

        int bondState = getBondState();

        if (isConnected()) {
            askDisconnect();
        } else if (bondState == BluetoothDevice.BOND_BONDED) {
            connect();
        } else if (bondState == BluetoothDevice.BOND_NOT_BONDED) {
            pair();
        }
    
public voidonContextItemSelected(android.view.MenuItem item)
Called when a context menu item is clicked.

param
item The item that was clicked.

        switch (item.getItemId()) {
            case CONTEXT_ITEM_DISCONNECT:
                disconnect();
                break;

            case CONTEXT_ITEM_CONNECT:
                connect();
                break;

            case CONTEXT_ITEM_UNPAIR:
                mLocalManager.getBluetoothManager().disconnectRemoteDeviceAcl(mAddress);
                unpair();
                break;

            case CONTEXT_ITEM_CONNECT_ADVANCED:
                Intent intent = new Intent();
                // Need an activity context to open this in our task
                Context context = mLocalManager.getForegroundActivity();
                if (context == null) {
                    // Fallback on application context, and open in a new task
                    context = mLocalManager.getContext();
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                }
                intent.setClass(context, ConnectSpecificProfilesActivity.class);
                intent.putExtra(ConnectSpecificProfilesActivity.EXTRA_ADDRESS, mAddress);
                context.startActivity(intent);
                break;
        }
    
public voidonCreateContextMenu(android.view.ContextMenu menu)

        // No context menu if it is busy (none of these items are applicable if busy)
        if (isBusy()) return;

        int bondState = getBondState();
        boolean isConnected = isConnected();
        boolean hasProfiles = mProfiles.size() > 0;

        menu.setHeaderTitle(getName());

        if (isConnected) {
            menu.add(0, CONTEXT_ITEM_DISCONNECT, 0, R.string.bluetooth_device_context_disconnect);
        } else if (hasProfiles) {
            // For connection action, show either "Connect" or "Pair & connect"
            int connectString = (bondState == BluetoothDevice.BOND_NOT_BONDED)
                    ? R.string.bluetooth_device_context_pair_connect
                    : R.string.bluetooth_device_context_connect;
            menu.add(0, CONTEXT_ITEM_CONNECT, 0, connectString);
        }

        if (bondState == BluetoothDevice.BOND_BONDED) {
            // For unpair action, show either "Unpair" or "Disconnect & unpair"
            int unpairString = isConnected
                    ? R.string.bluetooth_device_context_disconnect_unpair
                    : R.string.bluetooth_device_context_unpair;
            menu.add(0, CONTEXT_ITEM_UNPAIR, 0, unpairString);

            // Show the connection options item
            menu.add(0, CONTEXT_ITEM_CONNECT_ADVANCED, 0,
                    R.string.bluetooth_device_context_connect_advanced);
        }
    
public voidonProfileStateChanged()

        Log.d(TAG, "onProfileStateChanged:" + workQueue.toString());
        BluetoothJob job = workQueue.peek();
        if (job == null) {
            Log.v(TAG, "Yikes, onProfileStateChanged called but job queue is empty. "
                    + "(Okay for device initiated actions and BluetoothA2dpService initiated "
                    + "Auto-connections)");
            return;
        } else if (job.device.mAddress != mAddress) {
            // This can happen in 2 cases: 1) BT device initiated pairing and
            // 2) disconnects of one headset that's triggered by connects of
            // another.
            Log.v(TAG, "onProfileStateChanged called. The addresses differ. this.mAddress="
                    + mAddress + " workQueue.head=" + job.toString());

            // Check to see if we need to remove the stale items from the queue
            if (!pruneQueue(null)) {
                // nothing in the queue was modify. Just ignore the notification and return.
                return;
            }
        } else {
            // Remove the first item and process the next one
            Log.d(TAG, "LocalBluetoothDevice.onProfileStateChanged() called. MAC addr matched");
            workQueue.poll();
        }

        processCommands();
    
public voidpair()

        BluetoothDevice manager = mLocalManager.getBluetoothManager();

        // Pairing is unreliable while scanning, so cancel discovery
        if (manager.isDiscovering()) {
            manager.cancelDiscovery();
        }

        if (!mLocalManager.getBluetoothManager().createBond(mAddress)) {
            mLocalManager.showError(mAddress, R.string.bluetooth_error_title,
                    R.string.bluetooth_pairing_error_message);
        }
    
private booleanprocessCommand(com.android.settings.bluetooth.LocalBluetoothDevice$BluetoothJob job)

        boolean successful = false;
        if (job.timeSent == 0) {
            job.timeSent = System.currentTimeMillis();                
            switch (job.command) {
            case CONNECT:
                successful = connectInt(job.device, job.profile);
                break;
            case DISCONNECT:
                successful = disconnectInt(job.device, job.profile);
                break;
            }

            if (successful) {
                Log.d(TAG, "Command sent successfully:" + job.toString());
            } else {
                Log.d(TAG, "Framework rejected command immediately:" + job.toString());
            }
            
        } else {
            Log.d(TAG, "Job already has a sent time. Skip. " + job.toString());
        }

        return successful;
    
private voidprocessCommands()

        Log.d(TAG, "processCommands:" + workQueue.toString());
        Iterator<BluetoothJob> it = workQueue.iterator();
        while (it.hasNext()) {
            BluetoothJob job = it.next();
            if (processCommand(job)) {
                // Sent to bt framework. Done for now. Will remove this job
                // from queue when we get an event
                return;
            } else {
                /*
                 * If the command failed immediately, there will be no event
                 * callbacks. So delete the job immediately and move on to the
                 * next one
                 */
                it.remove();
            }
        }
    
private booleanpruneQueue(com.android.settings.bluetooth.LocalBluetoothDevice$BluetoothJob job)

        boolean removedStaleItems = false;
        long now = System.currentTimeMillis();
        Iterator<BluetoothJob> it = workQueue.iterator();
        while (it.hasNext()) {
            BluetoothJob existingJob = it.next();

            // Remove any pending CONNECTS when we receive a DISCONNECT
            if (job != null && job.command == BluetoothCommand.DISCONNECT) {
                if (existingJob.timeSent == 0
                        && existingJob.command == BluetoothCommand.CONNECT
                        && existingJob.device.mAddress.equals(job.device.mAddress)
                        && existingJob.profile == job.profile) {
                    Log.d(TAG, "Removed because of a pending disconnect. " + existingJob);
                    it.remove();
                    continue;
                }
            }

            // Defensive Code: Remove any job that older than a preset time.
            // We never got a call back. It is better to have overlapping
            // calls than to get stuck.
            Log.d(TAG, "Age:" + (now - existingJob.timeSent));
            if (existingJob.timeSent != 0
                    && (now - existingJob.timeSent) >= MAX_WAIT_TIME_FOR_FRAMEWORK) {
                Log.w(TAG, "Timeout. Removing Job:" + existingJob.toString());
                it.remove();
                removedStaleItems = true;
                continue;
            }
        }
        return removedStaleItems;
    
private voidqueueCommand(com.android.settings.bluetooth.LocalBluetoothDevice$BluetoothJob job)


        
        Log.d(TAG, workQueue.toString());
        synchronized (workQueue) {
            boolean processNow = pruneQueue(job);

            // Add job to queue
            Log.d(TAG, "Adding: " + job.toString());
            workQueue.add(job);

            // if there's nothing pending from before, send the command to bt
            // framework immediately.
            if (workQueue.size() == 1 || processNow) {
                Log.d(TAG, "workQueue.size() == 1 || TimeOut -> process command now");
                // If the failed to process, just drop it from the queue.
                // There will be no callback to remove this from the queue.
                processCommands();
            }
        }
    
public voidrefresh()

        dispatchAttributesChanged();
    
public voidrefreshBtClass()
Refreshes the UI for the BT class, including fetching the latest value for the class.

        fetchBtClass();
        dispatchAttributesChanged();
    
public voidrefreshName()

        fetchName();
        dispatchAttributesChanged();
    
public voidregisterCallback(com.android.settings.bluetooth.LocalBluetoothDevice$Callback callback)

        synchronized (mCallbacks) {
            mCallbacks.add(callback);
        }
    
voidsetRssi(short rssi)

        if (mRssi != rssi) {
            mRssi = rssi;
            dispatchAttributesChanged();
        }
    
voidsetVisible(boolean visible)

        if (mVisible != visible) {
            mVisible = visible;
            dispatchAttributesChanged();
        }
    
public voidshowConnectingError()

        if (!mIsConnectingErrorPossible) return;
        mIsConnectingErrorPossible = false;

        mLocalManager.showError(mAddress, R.string.bluetooth_error_title,
                R.string.bluetooth_connecting_error_message);
    
public java.lang.StringtoString()

        return mAddress;
    
public voidunpair()

        synchronized (workQueue) {
            // Remove any pending commands for this device
            boolean processNow = false;            
            Iterator<BluetoothJob> it = workQueue.iterator();
            while (it.hasNext()) {
                BluetoothJob job = it.next();
                if (job.device.mAddress.equals(this.mAddress)) {
                    it.remove();
                    if (job.timeSent != 0) {
                        processNow = true;
                    }
                }
            }
            if (processNow) {
                processCommands();
            }
        }

        BluetoothDevice manager = mLocalManager.getBluetoothManager();

        switch (getBondState()) {
        case BluetoothDevice.BOND_BONDED:
            manager.removeBond(mAddress);
            break;

        case BluetoothDevice.BOND_BONDING:
            manager.cancelBondProcess(mAddress);
            break;
        }
    
public voidunregisterCallback(com.android.settings.bluetooth.LocalBluetoothDevice$Callback callback)

        synchronized (mCallbacks) {
            mCallbacks.remove(callback);
        }