/* BluetoothBallsMultiserverMIDlet.java Copyright (c) Kari Laitinen http://www.naturalprogramming.com/ 2008-12-03 File created. 2008-12-10 Last modification. This program is an improved version of BluetoothBallsMIDlet.java. The Balls Server of this program is able to simultaneously serve several Ball Clients. This is achieved by using a separate thread that serves each Ball Client. See more comments at the beginning of BluetoothBallsMIDlet.java WARNINGS: This program is not very widely tested in real mobile phones. It has been found to work in Sun Wireless Toolkit and in Nokia Series 60 (S60) mobile phones. A rather large display is required to see all the balls the program is showing. */ 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 ; // The following dynamic array will contain the thread // objects that serve the Balls Clients Vector threads_serving_clients = new Vector(); // The following is an inner class whose objects will serve // the clients that contact the server. public class ClientServingThread extends Thread { private L2CAPConnection connection_to_client ; public ClientServingThread( L2CAPConnection given_connection_to_client ) { connection_to_client = given_connection_to_client; } public void run() { int joke_index = 0; while ( server_thread_should_be_run == true ) { try { // 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." ) ; } } } catch ( IOException caught_ioexception ) { } try { Thread.sleep( 500 ) ; } catch ( InterruptedException caught_interrupted_exception ) { } } } // end of the run() method of the inner class } // end of inner class ClientServingThread 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( BluetoothBallsMultiserverMIDlet given_midlet ) { super( given_midlet ) ; create_new_ball_objects() ; setTitle( "Balls Multiserver" ) ; 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 ) ; while ( server_thread_should_be_run == true ) { set_message_text( "Waiting client ..." ) ; // The following statement will block this thread for as long // as a client requests service. L2CAPConnection connection_to_client = L2CAP_connection_notifier.acceptAndOpen() ; // A client has made a connection. Let's create and start // a thread for that client. ClientServingThread thread_for_new_client = new ClientServingThread( connection_to_client ) ; threads_serving_clients.addElement( thread_for_new_client ) ; thread_for_new_client.start() ; // After a thread for the newest client has been activated, // this thread resumes waiting another client. // 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( BluetoothBallsMultiserverMIDlet 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 BluetoothBallsMultiserverMIDlet 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 BluetoothBallsMultiserverMIDlet() { 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() ; } } }