FileDocCategorySizeDatePackage
AutomatedEventHandler.javaAPI DocJ2ME MIDP 2.015867Thu Nov 07 12:02:22 GMT 2002com.sun.midp.lcdui

AutomatedEventHandler.java

/*
 * @(#)AutomatedEventHandler.java	1.33 02/08/01 @(#)
 *
 * Copyright (c) 1999-2002 Sun Microsystems, Inc.  All rights reserved.
 * PROPRIETARY/CONFIDENTIAL
 * Use is subject to license terms.
 */

package com.sun.midp.lcdui;

/**
 * This is the automated event handler for the LCDUI.
 */
public class AutomatedEventHandler extends DefaultEventHandler
             implements AutomationHandler {

    // constructor

    public AutomatedEventHandler() {
	super();

	// we initialize all hotkeys to an invalid key code
	hotKey_StartRecording  = hotKey_StopRecording  = 
	                         hotKey_CaptureScreen  = 0;

	/** FIXME */ // check that the above keycode(0) is invalid.


	// set the static variable that points to ourself.
	thisReference = this;


    }

    // from interface AutomationHandler

    /**
     * Start recording an event sequence. Any event sequence currently
     * being recorded will be destroyed and a new event sequence will
     * be started.
     */
    public void startEventSequence() {

	initializeEventSequence();

	recordingInProgress = true;

	// set the clock
	this.millis = System.currentTimeMillis();
    }

    /**
     * Stop recording an event sequence. This will stop recording the
     * current event sequence and capture the current contents of the
     * screen. It will then return an EventSequence object which is
     * the representation of the entire event sequence as well as the
     * screen capture resulting at the end of the sequence. If no events
     * occurred during the capture, the sequence will simply be a timed
     * delay with a screen capture. The timed delay will be the time delay
     * between the call to startEventSequence() and stopEventSequence().
     *
     * If stopEventSequence() is called without previously calling
     * startEventSequence(), an EventSequence will be returned which is
     * essentially "empty" and only contains the current contents of
     * the captured screen.
     *
     * @return EventSequence The sequence of events recorded as well as
     *                       the screen capture recorded at the end of
     *                       the sequence of events.
     */
    public EventSequence stopEventSequence() {

	if (recordingInProgress != true) {

	    // If stopEventSequence() is called without previously
	    // calling startEventSequence(), an EventSequence 
	    // will be returned which is essentially "empty" 

	    initializeEventSequence();
	}

	int time = (int)(System.currentTimeMillis() - this.millis);
	
	// this is a delay event
	RecordedEvent event = new RecordedEvent(time);
	eventSequence.appendEvent(event);
	
	// no need to set the clock
	// we won't we using it in recording anymore
	
	recordingInProgress = false;

	// append screen capture to event sequence
	byte[] s_cap = (ScreenGrabber.getInstance()).getData();
	eventSequence.setScreenCapture(s_cap);
	
	return eventSequence;
    }


    /**
     * Establish a SequenceHandler to handle the completion of EventSequences.
     * and screen captures which occur as a result of a 'hotkey' press.
     * If a sequence handler is set, that handler will be called when any
     * event sequence is completed, either as a result of stopEventSequence()
     * being called, or as a result of some "hot key" being pressed which
     * the event handler recognizes as a signal to stop event recording.
     * The EventSequence object passed to the handler will be the same as
     * the return result of the stopEventSequence() method. If a handler has
     * already been set, and this method is called again the old handler will
     * be discarded and the new handler will be used. If 'null' is passed,
     * any previous handler will be discarded.
     *
     * @param handler The SequenceHandler which will get the callback
     *                whenever an event sequence is completed.
     * @throws IllegalArgumentException If this particular port or platform
     *                                  cannot support hotkey activation, this
     *                                  method will throw an exception
     */
    public void registerSequenceHandler(SequenceHandler handler)
        throws IllegalArgumentException {
	sequenceHandler = handler;
    }


    /**
     * Establish a specific key code for the given action to serve as
     * a 'hotkey'. The action must be one of START_RECORDING, STOP_RECORDING,
     * or CAPTURE_SCREEN.
     *
     * @param action The action to register the hotkey for, one of
     *               START_RECORDING, STOP_RECORDING, or CAPTURE_SCREEN.
     * @param keyCode The key code of the new hotkey
     * @throws IllegalArgumentException If this particular port or platform
     *                                  cannot support hotkey activation, this
     *                                  method will throw an exception. An
     *                                  exception will also be thrown
     *                                  if the requested action or key code is
     *                                  invalid.
     */
    public void registerHotKey(int action, int keyCode)
        throws IllegalArgumentException {

	switch (action) {
	case START_RECORDING : 
	    hotKey_StartRecording = keyCode;
	    break;
	case STOP_RECORDING  :
	    hotKey_StopRecording  = keyCode;
            break;
        case CAPTURE_SCREEN  :
	    hotKey_CaptureScreen  = keyCode;
            break;
	}

    }


    /**
     * Replay a given event sequence. This will replay a sequence of
     * events represented in the given EventSequence object. It will
     * then capture the contents of the screen at the end of the event
     * sequence and compare it to the screen capture that is included
     * in the EventSequence object.
     *
     * @param sequence The EventSequence object to replay and test
     * @return True if the screen capture at the end of replaying
     *         the given event sequence matches that which is stored
     *         in the given EventSequence object.
     */
    public boolean replayEventSequence(EventSequence sequence) {

	// NOTE: Do we check if an event sequence is currently being recorded
	// replayed, before starting replay?
	sequence.initializeReplay();

	EventPlaybackThread eventPlaybackThread 
	    = new EventPlaybackThread(sequence, 100);

	eventPlaybackThread.start();

	// hang around until replay is finished.
	synchronized (thisReference) {
            try {
		thisReference.wait();
            } catch (InterruptedException e) {
		// ERROR!
                e.printStackTrace();
            }
	} 

	return lastScreenCaptureComparisonTrue;
    }

    class EventPlaybackThread extends Thread {
	EventSequence sequence;
	int           timeFactor;

	public EventPlaybackThread(EventSequence s, int tf) {
	    this.sequence   = s;
	    this.timeFactor = tf;
	}

	public void run() {
	    boolean playbackDone = false;
	    int t;

	    while (!playbackDone) {

		int nextObjectType = sequence.getNextObjectType();
	
		switch (nextObjectType) {
		case EventSequence.EVENT_START_MARKER :
	    
		    RecordedEvent event = sequence.getNextEvent();
		    t = event.getTimeDelay();

		    synchronized (this) {
			try {
			    sleep((t * timeFactor)/100);
			} catch (Exception e) {
			    System.out.println("Exception while sleep()-ing ");
			}
		    }

		    // get type of event
		    switch (event.getType()) {
		    case RecordedEvent.KEY_EVENT:
			keyEvent(event.val1, null, event.val2);
			break;
		    case RecordedEvent.INPUT_METHOD_EVENT:
			keyEvent(IME, event.inputString, 0);
			break;
		    case RecordedEvent.MENU_EVENT:

			break;
		    case RecordedEvent.PEN_EVENT:
			pointerEvent(event.val1, event.val2, event.val3);
			break;
		    case RecordedEvent.COMMAND_EVENT:
			commandEvent(event.val1);
			break;
		    case RecordedEvent.DELAY_DUMMY_EVENT:
			// This is a dummy event.
			// since the implementation has already delayed
			// (outside the switch .. case block),
			// do nothing.
			break;
		    default:
			System.out.println("unknown event type");
			// Unknown event type.
			// Should throw Exception?
			// Should stop replay?
			// Should continue with replay?
			break;
		    }

		    break;

		case EventSequence.CAPTURE_START_MARKER :

		    byte[] stored_s_cap = sequence.getCapture();

		    byte[] this_s_cap = 
			(ScreenGrabber.getInstance()).getData();

		    if (byteMatch(this_s_cap, stored_s_cap)) {
			// screen captures match
			lastScreenCaptureComparisonTrue = true;
		    } else {
			// screen captures did not match
			lastScreenCaptureComparisonTrue = false;
			// finish playback
			playbackDone = true;
		    }

		    break;

		case EventSequence.END_OF_EVENT_SEQUENCE : 
		    playbackDone = true;
		    break;

		default : 
		    // unknown error.
		    playbackDone = true;
		}

	    } 

	    synchronized (thisReference) {
		thisReference.notify();
	    }

	}
	
    }

    /**
     * Replay a given event sequence. This will replay a sequence of
     * events represented in the given EventSequence object. It will
     * then capture the contents of the screen at the end of the event
     * sequence and compare it to the screen capture that is included
     * in the EventSequence object.
     *
     * @param sequence The EventSequence object to replay and test
     * @param speed The factor by which to modify the replay speed of
     *              the event sequence. This is on a scale of 100,
     *              meaning a value of 100 would be normal speed,
     *              50 would be half speed, and 200 would be double
     *              speed.
     *
     * @return True if the screen capture at the end of replaying
     *         the given event sequence matches that which is stored
     *         in the given EventSequence object.
     */
    public boolean replayEventSequence(EventSequence sequence,
                                                int speed) {

	// NOTE: Do we check if an event sequence is currently being recorded/
	// replayed, before starting replay?
	sequence.initializeReplay();

	EventPlaybackThread eventPlaybackThread 
	    = new EventPlaybackThread(sequence, speed);

	eventPlaybackThread.start();

	// hang around until replay is finished.
	synchronized (thisReference) {
            try {
		thisReference.wait();
            } catch (InterruptedException e) {
		// ERROR!
                e.printStackTrace();
            }
	} 

	return lastScreenCaptureComparisonTrue;
    }


    /**
     * Update the screen capture for this event sequence. Over time, the
     * screen captures in a stored event sequence may become out of date.
     * Calling this method will run the given event sequence, capture
     * the resulting screen, and return the same EventSequence object
     * with the updated screen value.
     *
     * @param sequence The EventSequence to run and update the screen for
     * @return The updated EventSequence with the new captured screen value
     */
    public EventSequence updateScreenForSequence(
                                  EventSequence sequence) {

	return eventSequence;
    }


    /**
     * Capture the current contents of the physical display in the form of a
     * byte array. The byte array may be some reduced form of the display, such
     * as a checksum or hash, but must be guaranteed unique for the display
     * such that no two differing displays will have the same return value
     *
     * @return The byte[] of the captured screen contents
     */
    public byte[] captureScreen() {
	byte x[] = (ScreenGrabber.getInstance()).getData();

	return x;
    }


    // method to get this AutomatedEventHandler's reference
    public static AutomationHandler getAutomationHandler() {
	return thisReference;
    }

    
    // override DefaultEventHandler's implementation

    // these methods do what is needed by AutomatedEventHandler
    // and then call super's method.

    /**
     * Process a key event
     *
     * @param type The type of key event
     * @param str The String associated with an input method event
     * @param code The keycode of the key event
     */
    void keyEvent(int type, String str, int code) {

	// we have a key event
	// do we need to take a hot key action on this?
	if (type == 1 && // key press
            (code == hotKey_StartRecording ||
            code == hotKey_StopRecording   ||
	    code == hotKey_CaptureScreen)) {

		// process the hot key

		if (code == hotKey_StartRecording) {
		    startEventSequence();
		}

		if (code == hotKey_StopRecording) {
		    stopEventSequence();
		    // note : this method also performs a screen capture
		    // does this have any implications?
		    if (sequenceHandler != null) {
			sequenceHandler.handleEventSequence(eventSequence);
		    }
		}

		if (code == hotKey_CaptureScreen) {
		    byte s_cap[] = captureScreen();
		    if (sequenceHandler != null) {
			sequenceHandler.handleScreenCapture(s_cap);
		    }
		}

	} else {

	    if (recordingInProgress) {

		if (type == IME) {

		    int time = (int)(System.currentTimeMillis() - this.millis);

		    // this is an input method event
		    RecordedEvent event = new RecordedEvent(time, str);
		    eventSequence.appendEvent(event);

		    // set the clock
		    this.millis = System.currentTimeMillis();

		} else {

		    int time = (int)(System.currentTimeMillis() - this.millis);

		    // this is a KEY_EVENT
		    RecordedEvent event = new RecordedEvent(time, type, code);
		    eventSequence.appendEvent(event);

		    // set the clock
		    this.millis = System.currentTimeMillis();

		}

	    }

	}

	super.keyEvent(type, str, code);

    }

    /**
     * Process a pointer event
     *
     * @param type The type of pointer event
     * @param x The x coordinate location of the event
     * @param y The y coordinate location of the event
     */
    void pointerEvent(int type, int x, int y) {

	if (recordingInProgress) {

	    int time = (int)(System.currentTimeMillis() - this.millis);

	    RecordedEvent event = new RecordedEvent(time, type, x, y);
	    eventSequence.appendEvent(event);

	    // set the clock
	    this.millis = System.currentTimeMillis();

	}

	super.pointerEvent(type, x, y);

    }

    /**
     * Process a command event
     *
     * @param type The type of Command event to process
     */
    void commandEvent(int type) {
	if (recordingInProgress) {

	    int time = (int)(System.currentTimeMillis() - this.millis);

	    RecordedEvent event = new RecordedEvent(time, type);
	    eventSequence.appendEvent(event);

	    // set the clock
	    this.millis = System.currentTimeMillis();

	}

	super.commandEvent(type);
    }


    // -- private ---

    /**
     * Initialize the event sequence for recording.
     * Currently, this allocates a new EventSequence
     * 
     */
    private void initializeEventSequence() {
	eventSequence = new EventSequence();
    }

    /**
     * Checks if two byte arrays match.
     * <P />
     * @param a first byte array
     * @param b second byte array
     * @return true if the sequence of bytes in a matches those in b,
     *         false otherwise
     */ 
    private static boolean byteMatch(byte[] a, byte[] b) {
        if (a.length != b.length)
	    return false;

        int len = a.length;

        for (int i = 0; i < len; i++) {
            if (a[i] != b[i])
                return false;
        }

        return true;
    }


    // reference to ourself
    static AutomationHandler thisReference;

    EventSequence eventSequence;

    SequenceHandler sequenceHandler;

    int hotKey_StartRecording;
    int hotKey_StopRecording;
    int hotKey_CaptureScreen;

    // was the last screen capture a true match?
    boolean lastScreenCaptureComparisonTrue;

    // are we recording?
    boolean recordingInProgress;

    // time
    long millis;
}