Bluetooth Devices Example

This example shows how to access and list Bluetooth devices via the BluetoothAdapter API.

A screenshot of the application.

We import the classes and modules needed by our application. The most relevant ones for this example are the BluetoothAdapter and BluetoothDevice classes.

from java.lang import String
from android.bluetooth import BluetoothAdapter, BluetoothDevice
from android.content import BroadcastReceiver, Context, Intent, IntentFilter
from android.graphics import Typeface
from android.os import Build
from android.view import View
from android.widget import Button, LinearLayout, ListView, Space, TextView

from serpentine.activities import Activity
from serpentine.adapters import StringListAdapter
from serpentine.widgets import HBox, VBox

The BluetoothDevicesActivity is derived from the custom Activity class provided by the serpentine package and represents the application. Android will create an instance of this class when the user runs it.

class BluetoothDevicesActivity(Activity):

    __interfaces__ = [View.OnClickListener]

The initialisation method only needs to call the corresponding method in the base class.

    def __init__(self):
    
        Activity.__init__(self)

The onCreate method is called when the activity is created. Our implementation calls the onCreate method of the base class, queries the available Bluetooth devices and displays them in a graphical layout.

    def onCreate(self, bundle):
    
        Activity.onCreate(self, bundle)

Information about the available Bluetooth devices is obtained from the device's Bluetooth adapter which is itself obtained from the Bluetooth service. The custom activity provides a method to do this for us.

        self.adapter = self.getBluetoothAdapter()

We want the application to receive notifications about the Bluetooth adapter and other devices. These are delivered by intents that are broadcast by the operating system. We create an IntentFilter to specify the ones we are interested in and register a custom BroadcastReceiver instance to handle them.

        intentFilter = IntentFilter()
        intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED)
        intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)
        intentFilter.addAction(BluetoothDevice.ACTION_FOUND)
        
        self.receiver = Receiver(self)
        self.registerReceiver(self.receiver, intentFilter)

We set up the user interface, putting information about the Bluetooth adapter first, followed by a ListView for showing information about remote devices, and a button that the user presses to start a scan.

        localLabel = TextView(self)
        localLabel.setText("Local Device:")
        localLabel.setTypeface(Typeface.create(None, Typeface.BOLD))
        
        self.localNameLabel = TextView(self)
        self.localAddressLabel = TextView(self)
        
        hbox = HBox(self)
        hbox.addWeightedView(self.localNameLabel, 1)
        hbox.addWeightedView(self.localAddressLabel, 1)
        
        remoteLabel = TextView(self)
        remoteLabel.setText("Remote Devices:")
        remoteLabel.setTypeface(Typeface.create(None, Typeface.BOLD))
        
        self.listView = ListView(self)
        self.stringAdapter = StringListAdapter([])
        self.listView.setAdapter(self.stringAdapter)
        
        self.scanButton = Button(self)
        self.scanButton.setText("Scan")
        self.scanButton.setOnClickListener(self)
        
        self.setScanningState(self.adapter.isEnabled())

The state of the user interface depends on whether a Bluetooth adapter is available and ready. Initially, this is set using the current state of the adapter, but changes to this will be made when the application receives certain intents.

We create a vertical layout in which to place the user interface elements.

        layout = VBox(self)
        layout.addView(localLabel)
        layout.addView(hbox)
        layout.addView(remoteLabel)
        layout.addView(self.listView)
        layout.addWeightedView(Space(self), 1)
        layout.addView(self.scanButton)
        
        self.setContentView(layout)

When the user clicks the scan button the following method is called. We disable the button while a scan is in progress, starting the scan by calling the adapter's startDiscovery method. We clear the existing list of strings in the StringListAdapter used by the ListView and refresh the view itself by setting the string adapter on it again.

    def onClick(self, view):
    
        self.scanButton.setEnabled(False)
        self.adapter.startDiscovery()
        self.stringAdapter.items.clear()
        self.listView.setAdapter(self.stringAdapter)

The following method changes the user interface to reflect the ability of the Bluetooth adapter to scan for other devices. The enable parameter is used to enable or disable the scan button and the local device labels.

    @args(void, [bool])
    def setScanningState(self, enable):
    
        if enable:
            self.localNameLabel.setText(self.adapter.getName())
            self.localAddressLabel.setText(self.adapter.getAddress())
        
        self.localNameLabel.setEnabled(enable)
        self.localAddressLabel.setEnabled(enable)
        self.scanButton.setEnabled(enable)

When the application receives information about a new remote device we add its name to the ListView containing the names of other devices, using the same process as described above to refresh the view.

    @args(void, [String])
    def addDevice(self, name):
    
        if name in self.stringAdapter.items:
            return
        
        self.stringAdapter.items.add(name)
        self.listView.setAdapter(self.stringAdapter)

The following class is used to receive broadcasts from the operating system that we are interested in, handle them according to their actions, and call the relevant methods in the activity class when appropriate.

class Receiver(BroadcastReceiver):

    @args(void, [BluetoothDevicesActivity])
    def __init__(self, parent):
    
        BroadcastReceiver.__init__(self)
        self.parent = parent

This method is called when broadcast intents are delivered to the application. The context in which the receiver is running is supplied, but we use the parent field of this custom class to call methods in the activity because its type is more specific.

When the adapter's state changes we call the activity's setScanningState method to update the user interface.

    def onReceive(self, context, intent):
    
        if intent.getAction() == BluetoothAdapter.ACTION_STATE_CHANGED:
            state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
                                       BluetoothAdapter.STATE_OFF)
            self.parent.setScanningState(state == BluetoothAdapter.STATE_ON)
        
        elif intent.getAction() == BluetoothDevice.ACTION_FOUND:
            extra = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
            device = CAST(extra, BluetoothDevice)
            name = device.getName()
            if name != None:
                self.parent.addDevice(name)
        
        elif intent.getAction() == BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
            self.parent.setScanningState(True)

During a scan for remote devices, intents are delivered to report the devices found. We respond to these by unpacking data about the device from each intent and calling the activity's addDevice method. When the scan finishes, an intent is delivered reporting that discovery is finished. We call the activity's setScanningState method to reset the user interface to be ready for another scan.

Files