/* BluetoothBallsMIDlet.java Copyright (c) Kari Laitinen http://www.naturalprogramming.com/ 2008-10-29 File created. 2008-12-10 Last modification. This program is a Java midlet that shows the basics of Bluetooth communications between two phones. This application is similar to the BluetoothJokesMIDlet.java program. You can run this program either as a Balls Server or Balls Client. The server is able to send a Ball object to the client whenever the client requests one. The balls are objects of class Ball that is declared at the beginning of this program. Serialization is the process of transforming an object into a byte stream before sending it across a communications channel (e.g. a Bluetooth connection). On arrival, the byte stream is reassembled into an object and used locally. This program is an example of "serialization" of Java objects. The "serialized" Ball objects can be transferred between two mobile phones. Because Java ME does not support real serialization of objects, the serialization is implemented here in a somewhat rudimentary manner. (Java SE supports real serialization of objects as there is the Serializable interface.) See program BluetoothBallsMultiserverMIDlet.java for more information. */ import javax.microedition.midlet.*; import javax.microedition.lcdui.*; import java.io.IOException; import java.io.ByteArrayOutputStream ; import java.io.DataOutputStream ; import java.io.ByteArrayInputStream ; import java.io.DataInputStream ; import java.util.Vector ; // a Vector is a growable array of objects import java.util.Random ; import javax.bluetooth.BluetoothStateException; import javax.bluetooth.DeviceClass; import javax.bluetooth.DiscoveryAgent; import javax.bluetooth.DiscoveryListener; import javax.bluetooth.L2CAPConnection; import javax.bluetooth.L2CAPConnectionNotifier; import javax.bluetooth.LocalDevice; import javax.bluetooth.RemoteDevice; import javax.bluetooth.ServiceRecord; import javax.bluetooth.UUID; import javax.microedition.io.Connector; class Ball { int ball_center_point_x = 0 ; int ball_center_point_y = 0 ; int ball_color = 0x00FF0000 ; // red is the initial color int ball_diameter = 60 ; boolean this_ball_is_activated = false ; public Ball( int given_position_x, int given_position_y, int given_color ) { ball_center_point_x = given_position_x ; ball_center_point_y = given_position_y ; ball_color = given_color ; } // The following constructor "deserializes" a "serialized" // Ball object. public Ball( byte[] array_containing_ball_data ) { ByteArrayInputStream this_object_as_bytes = new ByteArrayInputStream( array_containing_ball_data ) ; DataInputStream stream_to_deserialize_individual_data_fields = new DataInputStream( this_object_as_bytes ) ; try { ball_center_point_x = stream_to_deserialize_individual_data_fields.readInt() ; ball_center_point_y = stream_to_deserialize_individual_data_fields.readInt() ; ball_diameter = stream_to_deserialize_individual_data_fields.readInt() ; ball_color = stream_to_deserialize_individual_data_fields.readInt() ; this_ball_is_activated = stream_to_deserialize_individual_data_fields.readBoolean() ; } catch ( IOException caught_exception ) { // The data fields contain default values if something // goes wrong here. } } public void activate_ball() { this_ball_is_activated = true ; } public void deactivate_ball() { this_ball_is_activated = false ; } public int get_ball_center_point_x() { return ball_center_point_x ; } public int get_ball_center_point_y() { return ball_center_point_y ; } public int get_ball_diameter() { return ball_diameter ; } public void move_right() { ball_center_point_x += 3 ; } public void move_left() { ball_center_point_x -= 3 ; } public void move_up() { ball_center_point_y -= 3 ; } public void move_down() { ball_center_point_y += 3 ; } public void move_this_ball( int movement_in_direction_x, int movement_in_direction_y ) { ball_center_point_x = ball_center_point_x + movement_in_direction_x ; ball_center_point_y = ball_center_point_y + movement_in_direction_y ; } public void move_to_position( int new_position_x, int new_position_y ) { ball_center_point_x = new_position_x ; ball_center_point_y = new_position_y ; } public void shrink() { // The if-construct ensures that the ball does not become // too small. if ( ball_diameter > 10 ) { ball_diameter -= 6 ; } } public void enlarge() { ball_diameter += 6 ; } public void set_diameter( int new_diameter ) { if ( new_diameter > 5 ) { ball_diameter = new_diameter ; } } public void set_color( int new_color ) { ball_color = new_color ; } /**** public boolean contains_point( int given_point_x, int given_point_y ) { int ball_radius = ball_diameter / 2 ; // Here we use the Pythagorean theorem to calculate the distance // from the given point to the center point of the ball. // See the note at the end of this file. int distance_from_given_point_to_ball_center = (int) Math.sqrt( Math.pow( ball_center_point_x - given_point_x, 2 ) + Math.pow( ball_center_point_y - given_point_y, 2 ) ) ; return ( distance_from_given_point_to_ball_center <= ball_radius ) ; } public boolean intersects_rectangle( Rectangle2D given_rectangle ) { Area this_ball_area = new Area( new Ellipse2D.Float( ball_position_x, ball_position_y, ball_diameter, ball_diameter ) ) ; return this_ball_area.intersects( given_rectangle ) ; } ***/ // The following method "serializes" this ball object by // storing its data fields in an array of bytes. public byte[] to_bytes() { ByteArrayOutputStream this_object_as_bytes = new ByteArrayOutputStream() ; DataOutputStream stream_to_serialize_individual_data_fields = new DataOutputStream( this_object_as_bytes ) ; try { stream_to_serialize_individual_data_fields. writeInt( ball_center_point_x ) ; stream_to_serialize_individual_data_fields. writeInt( ball_center_point_y ) ; stream_to_serialize_individual_data_fields. writeInt( ball_diameter ) ; stream_to_serialize_individual_data_fields. writeInt( ball_color ) ; stream_to_serialize_individual_data_fields. writeBoolean( this_ball_is_activated ) ; stream_to_serialize_individual_data_fields.flush() ; } catch ( IOException caught_exception ) { // What should I do here? } // Now the data fields have been written to the ByteArrayOutputStream // via the DataOutputStream. return this_object_as_bytes.toByteArray() ; } public void draw( Graphics graphics ) { graphics.setColor( ball_color ) ; graphics.fillArc( ball_center_point_x - ball_diameter / 2, ball_center_point_y - ball_diameter / 2, ball_diameter, ball_diameter, 0, 360 ) ; graphics.setColor( 0X00000000 ) ; // Black graphics.drawArc( ball_center_point_x - ball_diameter / 2, ball_center_point_y - ball_diameter / 2, ball_diameter, ball_diameter, 0, 360 ) ; // If this ball is activated, it will have a thick black edge if ( this_ball_is_activated == true ) { graphics.drawArc( ball_center_point_x - ball_diameter / 2 + 1, ball_center_point_y - ball_diameter / 2 + 1, ball_diameter - 2, ball_diameter - 2, 0, 360 ) ; graphics.drawArc( ball_center_point_x - ball_diameter / 2 + 2, ball_center_point_y - ball_diameter / 2 + 2, ball_diameter - 4, ball_diameter - 4, 0, 360 ) ; } } } class BallsCanvas extends Canvas { MIDlet master_midlet ; Vector balls_to_be_shown = new Vector() ; String message_on_this_canvas = "No activities." ; Font font_for_text = Font.getFont( Font.FACE_PROPORTIONAL, Font.STYLE_PLAIN, Font.SIZE_SMALL ) ; public BallsCanvas( MIDlet given_master_midlet ) { master_midlet = given_master_midlet ; } protected void set_message_text( String new_message_text ) { message_on_this_canvas = new_message_text ; System.out.print( "\n Message " + message_on_this_canvas ) ; repaint() ; } protected void paint( Graphics graphics ) { graphics.setColor( 0, 0, 0 ) ; // black graphics.fillRect( 0, 0, getWidth(), getHeight() ) ; for ( int ball_index = 0 ; ball_index < balls_to_be_shown.size() ; ball_index ++ ) { ( (Ball) balls_to_be_shown.elementAt( ball_index ) ).draw( graphics ) ; } graphics.setColor( 255, 255, 255 ) ; // white graphics.setFont( font_for_text ) ; graphics.drawString( message_on_this_canvas, 10, getHeight() - 1, Graphics.BOTTOM | Graphics.LEFT ) ; } } class BallsServerCanvas extends BallsCanvas implements Runnable { private Thread thread_to_run_balls_server ; private boolean server_thread_should_be_run = true ; private LocalDevice local_device ; private String local_device_name ; private L2CAPConnection connection_to_client ; private void create_new_ball_objects() { balls_to_be_shown.removeAllElements() ; // Let's create balls that have a random color. Random random_number_generator = new Random() ; // The following loops create 9 balls when the default // ball diameter is 60. for ( int ball_position_y = 38 ; ball_position_y < 200 ; ball_position_y += 68 ) { for ( int ball_position_x = 38 ; ball_position_x < 200 ; ball_position_x += 68 ) { Ball new_ball = new Ball( ball_position_x, ball_position_y, random_number_generator.nextInt( 0xFFFFFF ) ) ; balls_to_be_shown.addElement( new_ball ) ; } } } public BallsServerCanvas( BluetoothBallsMIDlet given_midlet ) { super( given_midlet ) ; // The following lines were used to test the "serialization" // of Ball objects. // Ball first_ball = (Ball) balls_to_be_shown.elementAt( 0 ); // Ball test_ball = new Ball( first_ball.to_bytes() ) ; // test_ball.move_right() ; // balls_to_be_shown.addElement( test_ball ) ; // first_ball.activate_ball() ; create_new_ball_objects() ; setTitle( "Balls Server" ) ; thread_to_run_balls_server = new Thread( this ) ; thread_to_run_balls_server.start() ; } public void stop_balls_server() { if ( thread_to_run_balls_server != null ) { thread_to_run_balls_server.interrupt() ; } server_thread_should_be_run = false ; } public void run() { set_message_text( "Starting server..." ) ; try { local_device = LocalDevice.getLocalDevice() ; local_device.setDiscoverable( DiscoveryAgent.GIAC ) ; String service_UUID = "00000000000010008000006678039B07"; local_device_name = local_device.getFriendlyName(); String url = "btl2cap://localhost:" + service_UUID + ";name=" + local_device_name; L2CAPConnectionNotifier L2CAP_connection_notifier = (L2CAPConnectionNotifier)Connector.open( url ) ; set_message_text( "Waiting client ..." ) ; connection_to_client = L2CAP_connection_notifier.acceptAndOpen() ; while ( server_thread_should_be_run == true ) { // When the L2CAPConnection method ready() returns true, // the subsequent call to the receive() method will not // block this thread. if ( connection_to_client.ready() ) { byte[] received_bytes = new byte[ 1000 ] ; int number_of_received_bytes = connection_to_client.receive( received_bytes ) ; String received_data_as_string = new String( received_bytes, 0, number_of_received_bytes ) ; set_message_text( "FROM CLIENT: " + received_data_as_string ) ; Random random_number_generator = new Random() ; int random_ball_index = random_number_generator.nextInt( balls_to_be_shown.size() ) ; Ball ball_to_be_sent = (Ball) balls_to_be_shown.elementAt( random_ball_index ) ; balls_to_be_shown.removeElementAt( random_ball_index ) ; connection_to_client.send( ball_to_be_sent.to_bytes() ) ; if ( balls_to_be_shown.size() == 0 ) { // All balls have been sent. Let's create new balls. create_new_ball_objects() ; set_message_text( "New balls created." ) ; } } try { Thread.sleep( 500 ) ; } catch ( InterruptedException caught_interrupted_exception ) { } // System.out.print( " S" ) ; } } catch( BluetoothStateException caught_bluetooth_state_exception ) { System.out.println( caught_bluetooth_state_exception ) ; } catch( IOException caught_ioexception ) { System.out.println( caught_ioexception ); } set_message_text( "Server thread terminated." ) ; } } class BallsClientCanvas extends BallsCanvas implements Runnable, DiscoveryListener { private Vector found_bluetooth_devices = new Vector() ; // The ServiceRecord interface describes characteristics // of a Bluetooth service. private ServiceRecord bluetooth_service_record ; private boolean client_thread_should_be_run = true; private String local_device_name; private L2CAPConnection connection_to_server ; boolean balls_service_discovered = false ; private boolean ball_requesting_enabled = false ; public BallsClientCanvas( BluetoothBallsMIDlet given_midlet ) { super( given_midlet ) ; setTitle( "Balls Client" ) ; Thread thread_to_run_balls_client = new Thread( this ) ; thread_to_run_balls_client.start(); } public void enable_ball_requesting() { ball_requesting_enabled = true ; } // Called when a device is found during an inquiry. // An inquiry searches for devices that are discoverable. // The same device may be returned multiple times. public void deviceDiscovered( RemoteDevice discovered_bluetooth_device, DeviceClass class_of_device ) { if( ! found_bluetooth_devices.contains( discovered_bluetooth_device ) ) { found_bluetooth_devices.addElement( discovered_bluetooth_device ) ; try { set_message_text( "FOUND: " + discovered_bluetooth_device.getFriendlyName( false ) ) ; } catch ( IOException caught_ioexception ) { set_message_text( "Found unidentified remote device ???!!!" ) ; } } } public void inquiryCompleted( int discovery_type ) { synchronized( this ) { this.notify() ; } } // Called when service(s) are found during a service search public void servicesDiscovered( int transaction_ID_of_service_search, ServiceRecord[] list_of_services_found ) { bluetooth_service_record = list_of_services_found[ 0 ] ; balls_service_discovered = true ; } public void serviceSearchCompleted( int transaction_ID_of_service_search, int response_code ) { synchronized( this ) { this.notify() ; } } public void run() { set_message_text( "Starting client..." ) ; balls_service_discovered = false ; try { LocalDevice local_device = LocalDevice.getLocalDevice() ; DiscoveryAgent discovery_agent = local_device.getDiscoveryAgent() ; local_device.setDiscoverable( DiscoveryAgent.GIAC ) ; synchronized( this ) { discovery_agent.startInquiry( DiscoveryAgent.GIAC, this ) ; try { wait() ; } catch ( InterruptedException e ) { } } if ( found_bluetooth_devices.size() == 0 ) { set_message_text( "No Bluetooth devices discovered." ) ; } UUID[] set_of_UUIDs_that_will_be_searched_for = { new UUID( "00000000000010008000006678039B07", false ) }; int set_of_attributes[] = { 0x0100 }; int bluetooth_device_index = 0 ; while( balls_service_discovered == false && bluetooth_device_index < found_bluetooth_devices.size() ) { synchronized( this ) { RemoteDevice bluetooth_device_on_list = (RemoteDevice) found_bluetooth_devices.elementAt( bluetooth_device_index ) ; discovery_agent.searchServices( set_of_attributes, set_of_UUIDs_that_will_be_searched_for, bluetooth_device_on_list, this ) ; try { wait() ; } catch ( InterruptedException e ) { } } bluetooth_device_index ++ ; } } catch ( BluetoothStateException caught_bluetooth_state_exception ) { System.out.println( caught_bluetooth_state_exception ) ; } if ( balls_service_discovered == true ) { // A Balls Server was found in one of the Bluetooth devices. // Now we can make a connection and start requesting jokes. set_message_text( "Balls Service Discovered." ) ; try { String url; url = bluetooth_service_record.getConnectionURL( 0, false ) ; local_device_name = LocalDevice.getLocalDevice().getFriendlyName() ; connection_to_server = (L2CAPConnection) Connector.open( url ) ; while ( client_thread_should_be_run == true ) { if ( ball_requesting_enabled == true ) { send_to_server( local_device_name + " wants a ball." ) ; byte[] received_bytes = new byte[ 1000 ] ; // The L2CAPConnection method receive() will wait here // until the server sends the joke text. int number_of_received_bytes = connection_to_server.receive( received_bytes ) ; Ball new_ball_from_server = new Ball( received_bytes ) ; if ( balls_to_be_shown.size() >= 9 ) { // We'll delete the old balls. balls_to_be_shown.removeAllElements() ; } balls_to_be_shown.addElement( new_ball_from_server ) ; set_message_text( "FROM SERVER: " + new_ball_from_server ) ; ball_requesting_enabled = false ; } else { // No ball requesting command has been given // Let's wait a little bit try { Thread.sleep( 500 ) ; } catch ( InterruptedException caught_interrupted_exception ) { } } // System.out.print( " C" ) ; } } catch ( IOException caught_ioexception ) { System.out.println( caught_ioexception ) ; } } set_message_text( "Client thread terminated." ) ; } public void stop_balls_client() { client_thread_should_be_run = false ; } private void send_to_server( String string_to_send ) { byte[] bytes_to_send = string_to_send.getBytes() ; try { connection_to_server.send( bytes_to_send ) ; } catch( IOException caught_ioexception ) { System.out.println( caught_ioexception ) ; } } } public class BluetoothBallsMIDlet extends MIDlet implements CommandListener { Display midlet_display = Display.getDisplay( this ) ; String[] selectable_operation_modes = { "Balls Server", "Balls Client" } ; List operation_modes_list = new List( "SELECT OPERATION MODE:", List.IMPLICIT, selectable_operation_modes, null ) ; Command get_ball_command = new Command( "Get Ball", Command.SCREEN, 1 ) ; Command exit_command = new Command( "Exit", Command.EXIT, 1 ) ; BallsServerCanvas balls_server_canvas ; BallsClientCanvas balls_client_canvas ; public BluetoothBallsMIDlet() { operation_modes_list.setCommandListener( this ) ; } public void startApp() { midlet_display.setCurrent( operation_modes_list ) ; } public void pauseApp() { } public void destroyApp( boolean unconditional_destruction_required ) { } public void commandAction( Command given_command, Displayable display_content ) { if ( given_command == List.SELECT_COMMAND ) { if ( operation_modes_list.getSelectedIndex() == 0 ) { balls_server_canvas = new BallsServerCanvas( this ) ; balls_server_canvas.addCommand( exit_command ) ; balls_server_canvas.setCommandListener( this ) ; midlet_display.setCurrent( balls_server_canvas ) ; } else if ( operation_modes_list.getSelectedIndex() == 1 ) { balls_client_canvas = new BallsClientCanvas( this ) ; balls_client_canvas.addCommand( get_ball_command ) ; balls_client_canvas.addCommand( exit_command ) ; balls_client_canvas.setCommandListener( this ) ; midlet_display.setCurrent( balls_client_canvas ) ; } } else if ( given_command == get_ball_command ) { if ( balls_client_canvas != null ) { balls_client_canvas.enable_ball_requesting() ; } } else if ( given_command == exit_command ) { if ( balls_server_canvas != null ) { balls_server_canvas.stop_balls_server() ; } if ( balls_client_canvas != null ) { balls_client_canvas.stop_balls_client() ; } destroyApp( true ) ; notifyDestroyed() ; } } }