/*
* USBDemo.java
*
* Copyright � 1998-2011 Research In Motion Limited
*
* 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.
*
* Note: For the sake of simplicity, this sample application may not leverage
* resource bundles and resource strings. However, it is STRONGLY recommended
* that application developers make use of the localization features available
* within the BlackBerry development platform to ensure a seamless application
* experience across a variety of languages and geographies. For more information
* on localizing your application, please refer to the BlackBerry Java Development
* Environment Development Guide associated with this release.
*/
package com.rim.samples.device.usbdemo;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Vector;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import net.rim.device.api.command.Command;
import net.rim.device.api.command.CommandHandler;
import net.rim.device.api.command.ReadOnlyCommandMetadata;
import net.rim.device.api.system.SystemListener2;
import net.rim.device.api.system.USBPort;
import net.rim.device.api.system.USBPortListener;
import net.rim.device.api.ui.MenuItem;
import net.rim.device.api.ui.UiApplication;
import net.rim.device.api.ui.component.RichTextField;
import net.rim.device.api.ui.container.MainScreen;
import net.rim.device.api.util.DataBuffer;
import net.rim.device.api.util.StringProvider;
/**
* A sample application to demonstrate opening of the USB port for communication
* with a desktop USB client (one that uses the COM interfaces exposed by
* BBDevMgr.exe).
*/
public final class USBDemo extends UiApplication {
private static final String CHANNEL = "JDE_USBClient";
private final RichTextField _output;
private UsbThread _usbThread;
/**
* Entry point for application.
*
* @param args
* Command line arguments (not used)
*/
public static final void main(final String[] args) {
// Create a new instance of the application and make the currently
// running thread the application's event dispatch thread.
new USBDemo().enterEventDispatcher();
}
/**
* Creates a new USBDemo object
*/
public USBDemo() {
final USBScreen screen = new USBScreen();
screen.setTitle("USB Demo");
_output = new RichTextField("<output>");
screen.add(_output);
pushScreen(screen);
}
/**
* This is the main screen that allows the user to connect to a desktop
* client.
*/
private class USBScreen extends MainScreen {
/**
* Creates a new USBScreen object
*/
private USBScreen() {
// Connects through a GCF USB connection
final MenuItem connectGCF =
new MenuItem(new StringProvider("Connect (GCF)"), 0x230010,
0);
connectGCF.setCommand(new Command(new CommandHandler() {
/**
* @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata,
* Object)
*/
public void execute(final ReadOnlyCommandMetadata metadata,
final Object context) {
// Cleanup the old thread if present
onExit();
_usbThread = new GCFUsbThread();
connect();
}
}));
/*
* Connects through a low level USB connection.
*/
final MenuItem connectLowLevel =
new MenuItem(new StringProvider("Connect (low level)"),
0x230020, 1);
connectLowLevel.setCommand(new Command(new CommandHandler() {
/**
* @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata,
* Object)
*/
public void execute(final ReadOnlyCommandMetadata metadata,
final Object context) {
// Cleanup old thread if present.
onExit();
_usbThread = new LowLevelUsbThread();
connect();
}
}));
addMenuItem(connectGCF);
addMenuItem(connectLowLevel);
}
/**
* @see net.rim.device.api.ui.container.MainScreen#onSavePrompt()
*/
public boolean onSavePrompt() {
// Prevent the save dialog from being displayed
return true;
}
/**
* @see net.rim.device.api.ui.Screen#close()
*/
public void close() {
onExit();
super.close();
}
}
/**
* Provides an abstract base for the GCFUsbThread and LowLevelUsbThread to
* use to connect to a desktop through its USB port.
*/
private abstract class UsbThread extends Thread {
/**
* Provides the action of connecting to a desktop client by calling the
* abstract methods to be implemented by subclasses.
*/
public void run() {
try {
init();
String s = read("Hello from PC");
message("received: " + s);
// Write back a hello string
s = "Hello from Device";
write(s);
message("wrote: " + s);
message("Sleeping to demonstrate queueing");
try {
Thread.sleep(1000);
} catch (final InterruptedException e) {
}
for (int i = 0; i < 5; i++) {
s = read("Q packet " + (i + 1));
message("received: " + s);
}
// Wait for goodbye
s = read("Goodbye from PC");
message("received: " + s);
s = "Goodbye from Device";
write(s);
message("wrote: " + s);
close();
} catch (final IOException e) {
message(e.toString());
}
}
/**
* Initializes the connection to the USB port
*
* @throws IOException
* Thrown if an error occurs while opening the USB port
* connection
*/
abstract protected void init() throws IOException;
/**
* Writes to the desktop client
*
* @param s
* The string to write out
* @throws IOException
* Thrown if a write error occurs
*/
abstract protected void write(String s) throws IOException;
/**
* Reads from the desktop client and returns the read string only if it
* matches the given string.
*
* @param match
* The specified string to match with the data read in
* @return The data read in if it matches the specified string, null
* otherwise
* @throws IOException
* Thrown if a read error occurs
*/
abstract protected String read(String match) throws IOException;
/**
* Closes the USB connection
*/
abstract public void close();
}
/**
* The GCF prepends the length of the data written to the outputstream on
* each transaction. Your server code should expect two bytes length data
* prior to the actual data when the client is using the GCF.
*/
private final class GCFUsbThread extends UsbThread {
private DataInputStream _dis;
private DataOutputStream _dos;
private StreamConnection _sc;
/**
* @see UsbThread#init()
*/
protected void init() throws IOException {
// Using the connector interface
// - no support for maxRxSize and maxTxSize changes
// - size is set to 1024 bytes
synchronized (this) {
_sc =
(StreamConnection) Connector.open("comm:USB;channel="
+ CHANNEL);
_dis = _sc.openDataInputStream();
_dos = _sc.openDataOutputStream();
}
message("Channel Open: " + CHANNEL);
}
/**
* @see UsbThread#write(String)
*/
protected void write(final String s) throws IOException {
synchronized (this) {
_dos.writeUTF(s);
_dos.flush();
}
}
/**
* @see UsbThread#read(String)
*/
protected String read(final String match) throws IOException {
boolean loop;
final int len = match.length();
final byte[] data = new byte[len];
do {
loop = false;
try {
synchronized (this) {
_dis.read(data, 0, len);
}
} catch (final IOException e) {
message(e.toString());
loop = true;
close();
init();
}
} while (loop);
final String s = new String(data);
return s.equals(match) ? s : null;
}
/**
* @see UsbThread#close()
*/
public void close() {
message("closing connections...");
try {
if (_dis != null) {
_dis.close();
}
} catch (final IOException e) {
}
try {
if (_dos != null) {
_dos.close();
}
} catch (final IOException e) {
}
try {
if (_sc != null) {
_sc.close();
}
} catch (final IOException e) {
}
message("connections closed");
}
// Default constructor
public GCFUsbThread() {
}
}
/**
* This thread implements a low level USB connector through the USBPort
* class.
*
* Note: This method is not portable. See the USBPort javadocs for details.
*
* @see net.rim.device.api.system.USBPort
*/
private final class LowLevelUsbThread extends UsbThread implements
USBPortListener, SystemListener2 {
private boolean _dataAvailable;
private Vector _readQueue;
private int _channel;
private USBPort _port;
private boolean _waiting;
private boolean _connected;
private boolean _abort;
/**
* @see UsbThread#init()
*/
protected void init() throws IOException {
_readQueue = new Vector();
// Register the app for callbacks.
// NOTE: These are here for demonstration, but
// they can be used in GCF thread if needed.
USBDemo.this.addIOPortListener(this);
USBDemo.this.addSystemListener(this);
message("using low level usb interface");
// Register the channel
_channel = USBPort.registerChannel(CHANNEL, 16380, 16380);
message("Registering channel: " + CHANNEL);
synchronized (this) {
// Wait for a channel
if (_port == null) {
_waiting = true;
try {
this.wait();
} catch (final InterruptedException e) {
}
_waiting = false;
}
}
}
/**
* @see UsbThread#write(String)
*/
protected void write(final String s) throws IOException {
_port.write(s.getBytes());
}
/**
* @see UsbThread#read(String)
*/
protected String read(final String match) throws IOException {
do {
if (_abort) {
// Disconnect happened, so reopen the channel
close();
init();
_abort = false;
}
synchronized (this) {
if (!_dataAvailable) {
// wait for data
_waiting = true;
try {
this.wait();
} catch (final InterruptedException e) {
}
_waiting = false;
}
_dataAvailable = false;
}
} while (_abort); // Check for disconnect
// Loop through the queue of data and extract each item
for (;;) {
byte[] data = null;
synchronized (this) {
if (_readQueue.isEmpty()) {
// The read queue is empty, we didn't find our match
message("did not recieve match for " + match);
break;
} else {
data = (byte[]) _readQueue.firstElement();
// Ensure we always remove the correct object
_readQueue.removeElementAt(0);
if (!_readQueue.isEmpty()) {
_dataAvailable = true;
}
}
}
final String s = new String(data);
if (s.equals(match)) {
return s;
}
}
return null;
}
// Wakeup the thread so it can reconnect the channel
public void abort() {
_abort = true;
if (_waiting) {
synchronized (this) {
this.notify();
}
}
}
/**
* @see UsbThread#close()
*/
public void close() {
_connected = false;
try {
message("closing connections...");
// Close port if not already closed
if (_port != null) {
_port.close();
}
_port = null;
// Deregister the channel
USBPort.deregisterChannel(_channel);
message("connections closed");
} catch (final IOException e) {
message(e.toString());
}
}
// USBPortListener methods
// --------------------------------------------------
/**
* @see net.rim.device.api.system.USBPortListener#getChannel()
*/
public int getChannel() {
return _channel;
}
/**
* @see net.rim.device.api.system.USBPortListener#connectionRequested()
*/
public void connectionRequested() {
message("lowlevelusb: Connection requested!");
try {
synchronized (this) {
_port = new USBPort(_channel);
_connected = true;
this.notify();
}
} catch (final IOException e) {
message(e.toString());
}
}
/**
* @see net.rim.device.api.system.USBPortListener#dataNotSent()
*/
public void dataNotSent() {
message("lowlevelusb: Data not sent");
}
/**
* @see net.rim.device.api.system.IOPortListener#connected()
*/
public void connected() {
message("lowlevelusb: connected");
}
/**
* @see net.rim.device.api.system.IOPortListener#disconnected()
*/
public void disconnected() {
message("lowlevelusb: disconnected");
if (_connected) {
abort();
}
}
/**
* @see net.rim.device.api.system.IOPortListener#receiveError(int)
*/
public void receiveError(final int error) {
message("lowlevelusb: Got rxError: " + error);
}
/**
* @see net.rim.device.api.system.IOPortListener#dataReceived(int)
*/
public void dataReceived(int length) {
final DataBuffer db = new DataBuffer();
int len;
try {
final byte[] receiveBuffer = new byte[16380];
if (length == -1) // Length not available
{
length = receiveBuffer.length;
}
// If something was read then add it to the data buffer to
// send back later.
synchronized (this) {
len = _port.read(receiveBuffer, 0, length);
}
if (len != 0) {
db.write(receiveBuffer, 0, len);
final String data = new String(receiveBuffer, 0, len);
message("lowlevelusb: " + data);
}
// Wakeup read()
synchronized (this) {
_readQueue.addElement(db.toArray());
_dataAvailable = true;
this.notify();
}
} catch (final IOException ex) {
throw new RuntimeException(ex.getMessage());
}
}
/**
* @see net.rim.device.api.system.IOPortListener#dataSent()
*/
public void dataSent() {
// Not implemented
}
/**
* @see net.rim.device.api.system.IOPortListener#patternReceived(byte[])
*/
public void patternReceived(final byte[] pattern) {
message("lowlevelusb: Got pattern " + new String(pattern)); // +
// pattern[0]
// + " "
// +
// pattern[1]
// + " "
// +
// pattern[2]
// + " "
// +
// pattern[3]
// );
}
// SystemListener2 methods
// --------------------------------------------------
// Used to detect a connection change
public void usbConnectionStateChange(final int state) {
switch (state) {
// USB cable is connected
case USB_STATE_CABLE_CONNECTED:
message("Cable connected");
break;
// USB cable is disconnected
case USB_STATE_CABLE_DISCONNECTED:
message("Cable disconnected");
if (_connected) {
abort();
}
break;
}
}
// Not implemented in this demo but need to be defined
public void backlightStateChange(final boolean on) {
}
public void cradleMismatch(final boolean mismatch) {
}
public void fastReset() {
}
public void powerOffRequested(final int reason) {
}
// SystemListener methods
// --------------------------------------------------
// Not implemented in this demo but need to be defined
public void batteryGood() {
}
public void batteryLow() {
}
public void batteryStatusChange(final int status) {
}
public void powerOff() {
}
public void powerUp() {
}
// Default constructor
public LowLevelUsbThread() {
}
}
/**
* Connects the USB to the desktop
*/
private void connect() {
_usbThread.start();
}
/**
* Displays a message by appending it to the screen
*
* @param msg
* The message to display
*/
private void message(final String msg) {
invokeLater(new Runnable() {
public void run() {
_output.setText(_output.getText() + "\n" + msg);
}
});
}
/**
* Closes the USB thread
*/
public void onExit() {
if (_usbThread != null) {
_usbThread.close();
}
}
}