ServiceDiscoveryList.java
/*
* Copyright © 2011 Nokia Corporation. All rights reserved.
* Nokia and Nokia Connecting People are registered trademarks of Nokia Corporation.
* Oracle and Java are trademarks or registered trademarks of Oracle and/or its
* affiliates. Other product and company names mentioned herein may be trademarks
* or trade names of their respective owners.
* See LICENSE.TXT for license information.
*/
package com.nokia.example.btsppecho;
import java.util.Hashtable;
import java.util.Vector;
import java.util.Enumeration;
import javax.bluetooth.BluetoothStateException;
import javax.bluetooth.DeviceClass;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.DiscoveryListener;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.RemoteDevice;
import javax.bluetooth.ServiceRecord;
import javax.bluetooth.UUID;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.List;
class ServiceDiscoveryList
extends List
implements CommandListener,
DiscoveryListener,
Runnable {
private final static String TITLE = "New search";
private final static int WAIT_MILLIS = 500; // milliseconds
private final static String[] ACTIVITY = {"",
".",
"..",
"...",
"...."};
private final UUID uuid;
private final MIDletApplication midlet;
private final Command backCommand;
private final Command searchCommand;
private final Command openCommand;
private final Command stopCommand;
private final Command logCommand;
private final Command exitCommand;
private final int sdTransMax;
private final int inquiryAccessCode;
private DiscoveryAgent discoveryAgent;
private volatile boolean inquiryInProgress = false;
private volatile int numServiceSearchesInProgress = 0;
private volatile Thread thread;
private Displayable backDisplayable;
private Vector unsearchedRemoteDevices = new Vector();
private Vector transIds = new Vector();
private Hashtable matchingServiceRecords = new Hashtable();
private int numConnectionsAlreadyOpen = 0;
ServiceDiscoveryList(MIDletApplication midlet,
String uuidString,
int inquiryAccessCode) {
super(TITLE, List.IMPLICIT);
this.midlet = midlet;
uuid = new UUID(uuidString, false);
this.inquiryAccessCode = inquiryAccessCode;
openCommand = new Command("Open connection",
Command.SCREEN,
1);
searchCommand = new Command("Search", Command.SCREEN, 2);
logCommand = new Command("View log", Command.SCREEN, 3);
stopCommand = new Command("Stop", Command.SCREEN, 4);
backCommand = new Command("Back", Command.BACK, 5);
exitCommand = new Command("Exit", Command.EXIT, 0);
String property =
LocalDevice.getProperty("bluetooth.sd.trans.max");
sdTransMax = Integer.parseInt(property);
// create discovery agent
try {
discoveryAgent =
LocalDevice.getLocalDevice().getDiscoveryAgent();
addCommand(logCommand);
addCommand(exitCommand);
addCommand(searchCommand);
setCommandListener(this);
start();
} catch (BluetoothStateException e) {
midlet.serviceDiscoveryListFatalError(
"Couldn't get a discovery agent: '"
+ e.getMessage() + "'");
}
}
static Image makeImage(String filename) {
Image image = null;
try {
image = Image.createImage(filename);
} catch (Exception e) {
// there's nothing we can do, so ignore
}
return image;
}
public void addBackCommand(Displayable backDisplayable) {
this.backDisplayable = backDisplayable;
removeCommand(backCommand);
addCommand(backCommand);
}
public synchronized void start() {
thread = new Thread(this);
thread.start();
}
public synchronized void abort() {
thread = null;
}
public void init(int numConnectionsAlreadyOpen) {
this.numConnectionsAlreadyOpen =
numConnectionsAlreadyOpen;
// stop any pending searches
if (inquiryInProgress
|| numServiceSearchesInProgress > 0) {
cancelPendingSearches();
}
// remove any old list elements
while (size() > 0) {
delete(0);
}
}
private synchronized void setInquiryInProgress(boolean bool) {
inquiryInProgress = bool;
}
public void commandAction(Command command, Displayable d) {
if (command == logCommand) {
midlet.serviceDiscoveryListViewLog(this);
} else if (command == searchCommand
&& !inquiryInProgress
&& (numServiceSearchesInProgress == 0)) {
// new inquiry started
removeCommand(openCommand);
// remove old device lists
unsearchedRemoteDevices.removeAllElements();
for (Enumeration keys = matchingServiceRecords.keys();
keys.hasMoreElements();) {
matchingServiceRecords.remove(keys.nextElement());
}
// delete all old List items
while (size() > 0) {
delete(0);
}
try {
// disable page scan and inquiry scan
LocalDevice dev = LocalDevice.getLocalDevice();
dev.setDiscoverable(DiscoveryAgent.NOT_DISCOVERABLE);
String iacString =
LogScreen.inquiryAccessCodeString(inquiryAccessCode);
LogScreen.log("startInquiry ("
+ iacString + ")\n");
//startActivityIndicator();
// this is non-blocking
discoveryAgent.startInquiry(inquiryAccessCode, this);
setInquiryInProgress(true);
addCommand(stopCommand);
removeCommand(searchCommand);
} catch (BluetoothStateException e) {
addCommand(searchCommand);
removeCommand(stopCommand);
midlet.serviceDiscoveryListError(
"Error during startInquiry: '"
+ e.getMessage() + "'");
}
} else if (command == stopCommand) {
// stop searching
if (cancelPendingSearches()) {
setInquiryInProgress(false);
removeCommand(stopCommand);
addCommand(searchCommand);
}
} else if (command == exitCommand) {
midlet.serviceDiscoveryListExitRequest();
} else if (command == openCommand) {
int size = this.size();
boolean[] flags = new boolean[size];
getSelectedFlags(flags);
Vector selectedServiceRecords = new Vector();
for (int i = 0; i < size; i++) {
if (flags[i]) {
String key = getString(i);
ServiceRecord rec =
(ServiceRecord) matchingServiceRecords.get(key);
selectedServiceRecords.addElement(rec);
}
}
// try to perform an open on selected items
if (selectedServiceRecords.size() > 0) {
String value = LocalDevice.getProperty(
"bluetooth.connected.devices.max");
int maxNum = Integer.parseInt(value);
int total = numConnectionsAlreadyOpen
+ selectedServiceRecords.size();
if (total > maxNum) {
midlet.serviceDiscoveryListError(
"Too many selected. "
+ "This device can connect to at most "
+ maxNum + " other devices");
} else {
midlet.serviceDiscoveryListOpen(
selectedServiceRecords);
}
} else {
midlet.serviceDiscoveryListError(
"Select at least one to open");
}
} else if (command == backCommand) {
midlet.serviceDiscoveryListBackRequest(backDisplayable);
}
}
boolean cancelPendingSearches() {
LogScreen.log("Cancel pending inquiries and searches\n");
boolean everythingCancelled = true;
if (inquiryInProgress) {
// Note: The BT API (v1.0) isn't completely clear
// whether cancelInquiry is blocking or non-blocking.
if (discoveryAgent.cancelInquiry(this)) {
setInquiryInProgress(false);
} else {
everythingCancelled = false;
}
}
for (int i = 0; i < transIds.size(); i++) {
// Note: The BT API (v1.0) isn't completely clear
// whether cancelServiceSearch is blocking or
// non-blocking?
Integer pendingId =
(Integer) transIds.elementAt(i);
if (discoveryAgent.cancelServiceSearch(
pendingId.intValue())) {
transIds.removeElement(pendingId);
} else {
everythingCancelled = false;
}
}
return everythingCancelled;
}
// DiscoveryListener callbacks
public void deviceDiscovered(RemoteDevice remoteDevice,
DeviceClass deviceClass) {
LogScreen.log("deviceDiscovered: "
+ remoteDevice.getBluetoothAddress()
+ " major device class="
+ deviceClass.getMajorDeviceClass()
+ " minor device class="
+ deviceClass.getMinorDeviceClass() + "\n");
// Note: The following check has the effect of only
// performing later service searches on phones.
// If you intend to run the MIDlet on some other device (e.g.
// handheld computer, PDA, etc. you have to add a check
// for them as well.) You might also refine the check
// using getMinorDeviceClass() to check only cellular phones.
boolean isPhone =
(deviceClass.getMajorDeviceClass() == 0x200);
// Setting the following line to 'true' is a workaround
// for some early beta SDK device emulators. Set it
// to false when compiling the MIDlet for download to
// real MIDP phones!
boolean isEmulator = true; //false;
if (isPhone || isEmulator) {
unsearchedRemoteDevices.addElement(remoteDevice);
}
}
public void inquiryCompleted(int discoveryType) {
LogScreen.log("inquiryCompleted: "
+ discoveryType + "\n");
setInquiryInProgress(false);
if (unsearchedRemoteDevices.size() == 0) {
setTitle(TITLE);
addCommand(searchCommand);
removeCommand(stopCommand);
midlet.serviceDiscoveryListError(
"No Bluetooth devices found");
}
}
public void servicesDiscovered(int transId,
ServiceRecord[] serviceRecords) {
LogScreen.log("servicesDiscovered: transId="
+ transId
+ " # serviceRecords="
+ serviceRecords.length + "\n");
// Remove+Add: ensure there is at most one open command
removeCommand(openCommand);
addCommand(openCommand);
// Use the friendly name and/or bluetooth address
// to identify the matching Device + ServiceRecord
// (Note: Devices with different Bluetooth addresses
// might have the same friendly name, for example a
// device's default friendly name.)
// there should only be one record
if (serviceRecords.length == 1) {
RemoteDevice device = serviceRecords[0].getHostDevice();
String name = device.getBluetoothAddress();
// This MIDlet only uses the first matching service
// record found for a particular device.
if (!matchingServiceRecords.containsKey(name)) {
matchingServiceRecords.put(name, serviceRecords[0]);
append(name, null);
// The List should have at least one entry,
// before we add an open command.
if (size() == 1) {
addCommand(openCommand);
}
}
} else {
midlet.serviceDiscoveryListError(
"Error: Unexpected number ("
+ serviceRecords.length + ") of service records "
+ "in servicesDiscovered callback, transId="
+ transId);
}
}
public void serviceSearchCompleted(int transId, int responseCode) {
setTitle("New search");
String responseCodeString =
LogScreen.responseCodeString(responseCode);
LogScreen.log("serviceSearchCompleted: transId="
+ transId
+ " ("
+ responseCodeString + ")\n");
// For any responseCode, decrement the counter
numServiceSearchesInProgress--;
// remove the transaction id from the pending list
for (int i = 0; i < transIds.size(); i++) {
Integer pendingId = (Integer) transIds.elementAt(i);
if (pendingId.intValue() == transId) {
transIds.removeElement(pendingId);
break;
}
}
// all the searches have completed
if (!inquiryInProgress && (transIds.size() == 0)) {
removeCommand(stopCommand);
addCommand(searchCommand);
if (matchingServiceRecords.size() == 0) {
midlet.serviceDiscoveryListError(
"No matching services found");
}
}
}
// Interface Runnable
public void run() {
Thread currentThread = Thread.currentThread();
int i = 0;
running:
while (thread == currentThread) {
synchronized (this) {
if (thread != currentThread) {
break running;
} else if (!inquiryInProgress) {
doServiceSearch();
}
if (inquiryInProgress
|| numServiceSearchesInProgress > 0) {
setTitle("Searching " + ACTIVITY[i]);
if (++i >= ACTIVITY.length) {
i = 0;
}
}
try {
wait(WAIT_MILLIS);
} catch (InterruptedException e) {
// we can safely ignore this
}
}
}
}
public void doServiceSearch() {
if ((unsearchedRemoteDevices.size() > 0)
&& (numServiceSearchesInProgress < sdTransMax)) {
synchronized (this) {
RemoteDevice device =
(RemoteDevice) unsearchedRemoteDevices.elementAt(0);
UUID[] uuids = new UUID[1];
uuids[0] = uuid;
try {
int[] attrSet = null; // default attrSet
numServiceSearchesInProgress++;
int transId = discoveryAgent.searchServices(
attrSet,
uuids,
device,
this);
LogScreen.log("starting service search on device="
+ device.getBluetoothAddress()
+ " transId=" + transId + "\n");
transIds.addElement(new Integer(transId));
unsearchedRemoteDevices.removeElementAt(0);
} catch (BluetoothStateException e) {
numServiceSearchesInProgress--;
midlet.serviceDiscoveryListError(
"Error, could not perform "
+ "service search: '"
+ e.getMessage());
}
}
}
}
public void remove(Vector alreadyOpen) {
if (size() > 0) {
for (int i = 0; i < alreadyOpen.size(); i++) {
// Bluetooth address of slave device that
// we already have a connection open to
String btAddress = (String) alreadyOpen.elementAt(i);
boolean found = false;
for (int j = 0; j < size(); j++) {
if (getString(j).equals(btAddress)) {
found = true;
// if the element we are about to
// remove is selected, select something else
if (getSelectedIndex() == j) {
setSelectedIndex(j, false);
}
delete(j);
break;
}
}
if (found) {
break;
}
}
}
}
}