/* * Copyright @ 2012 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 || command == List.SELECT_COMMAND) { 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; } } } } }