code:
aMaze
=====

aMaze is a 3D maze game for Nokia Asha software platform and Series 40 full
touch phones. This Nokia example game demonstrates the use of Mobile 3D Graphics
API, gestures, and accelerometer sensor. The concept of the game is classic and
simple: guide the marble through the maze by tilting the phone until the time
runs out. The gestures utilised in the game are pinch-to-zoom and double tap.

This example application is hosted in Nokia Developer Projects:
- https://projects.developer.nokia.com/amaze
- https://projects.developer.nokia.com/amaze/wiki

What's new in version 1.1
-------------------------
- Nokia Asha software platform 1.0 support added.
- Game speed better normalised for phones with different processing capacity.
- Minor performance improvements.
- GameModel and InteractionManager classes removed and their implementation was
  merged to MazeCanvas.
- Some bug fixes.

1. Project structure and implementation
-------------------------------------------------------------------------------

1.1 Folders
-----------

 |                  The root folder contains the project files, the Application
 |                  Descriptor file, the license information and this file
 |                  (release notes).
 |
 |- binaries        Contains the installable binaries.
 |
 |- res             Contains the application resources (e.g. graphics assets).
 |
 |- src             Contains the application source code.

1.2 Important files and classes
-------------------------------

Note that some files/classes are omitted from the following tree.

 src/ com.nokia.example.amaze
 |- Main.java...........................The MIDlet main class.
 |- SplashScreen.java...................The splash screen implementation.
 |
 |- com.nokia.example.amaze.gestures
 |  |- SafeGestureInteractiveZone.java......The rectangle where the gestures are
 |  |                                       recognised.
 |  |- SafeGestureListener.java.............The interface for gesture listeners.
 |  |- SafeGestureRegistrationManager.java..The intelligent manager for
 |                                          registering gesture listeners.
 |
 |- com.nokia.example.amaze.model
 |  |- MarbleModel.java.................The model of the marble.
 |  |- Maze.java........................The model of the maze.
 |  |- MyTimer.java.....................A generic, easy-to-use timer.
 |
 |- com.nokia.example.amaze.sensors
 |  |- AccelerationProvider.java........The interface that provides
 |  |                                   accelerometer sensor readings.
 |  |- AccelerationProviderImpl.java....The actual implementation of the
 |                                      accelerometer sensor readings provider.
 |
 |- com.nokia.example.amaze.ui
 |  |- CameraAnimator.java..............A utility class for camera animations.
 |  |- DoubleTapDetector.java...........Listens to touch events and notifies the
 |  |                                   listener if a double tap event is
 |  |                                   detected.
 |  |- MazeCanvas.java..................The game view that ties everything
 |  |                                   together. The view also acts as the game
 |  |                                   engine running the main loop. All the
 |  |                                   touch interaction and command handling
 |  |                                   is implemented in this class.
 |  |- Menu.java........................A generic menu with support for
 |  |                                   customisable menu items.
 |  |- WorldBuilder.java................Contains the methods for creating the
 |                                      game levels.

1.3 Used J2ME and Nokia UI API classes
--------------------------------------

Note: Some, less meaningful packages and classes omitted.

- javax.microedition.lcdui
  - Canvas
  - Display
  - Displayable
  - Font
  - Graphics
  - Image

- javax.microedition.lcdui.game
  - GameCanvas

- javax.microedition.m3g
  - Appearance
  - Background
  - Camera
  - Graphics3D
  - Mesh
  - Node
  - RayIntersection
  - Transform
  - World

- com.nokia.mid.ui
  - DeviceControl

- com.nokia.mid.ui.orientation
  - Orientation
  - OrientationListener

2. Compatibility
-------------------------------------------------------------------------------

Compatible with Nokia Asha software platform 1.0 and Series 40 full touch
phones. Developed with Nokia Asha SDK 1.0. Tested with Nokia Asha 501, 311 and
306.

2.1 Known issues
----------------

- The performance is satisfactory only with Nokia 311.
- Collision detection is not perfect:
  - Sometimes the marble gets momentarily stuck on the edges of maze walls.
  - Sometimes the marble can pass through maze walls.
- Only partial landscape support implemented.
- No sound effects implemented.

3. Building, installing, and running the application
-------------------------------------------------------------------------------

3.1 Preparations
----------------

Check that you have Nokia Asha SDK 1.0 installed.

3.2 Packaging the application using Nokia Asha SDK 1.0
------------------------------------------------------

You cannot install the application on the device with the IDE, but you can
package the application: After you have imported the project, locate the
Application Descriptor in the Package Explorer window and open it. Open the
Overview tab (by default it is the first tab on the left) and click Create
package. Select the destination directory and click Finish.

3.3 Installing application binary to phone
------------------------------------------

Connect the phone to the computer with USB cable or Bluetooth. Locate the
application binary (.jar file). Copy the file to your phone, locate it and tap
to install. With Series 40 phone you can install the file using Nokia Suite:
Drag the file from the file explorer on top of the connected phone image in the
Nokia Suite window.

After the application is installed, locate the application icon from the
application menu and launch the application by selecting the icon.

4. License
-------------------------------------------------------------------------------

See the license text file delivered with this project. The license file is also
available online at
https://projects.developer.nokia.com/amaze/browser/amaze/trunk/Licence.txt

5. Related documentation
-------------------------------------------------------------------------------
- Getting started with Mobile 3D Graphics API:
  - http://www.oracle.com/technetwork/systems/3dgraphics-155829.html

6. Version history
-------------------------------------------------------------------------------

1.1 Nokia Asha software platform 1.0 optimisation
1.0 Initial release

repo_name: snapshot/amaze

license: unknown


code:
/**
 * Copyright (c) 2012-2013 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.amaze;

public class ArrayUtils {

    public static double getLast(double[] array) {
        if (array == null) {
            throw new IllegalArgumentException("array cannot be null");
        }
        return array[array.length - 1];
    }

    public static int getLast(int[] array) {
        if (array == null) {
            throw new IllegalArgumentException("array cannot be null");
        }
        return array[array.length - 1];
    }

    public static Object getLast(Object[] array) {
        if (array == null) {
            throw new IllegalArgumentException("array cannot be null");
        }
        return array[array.length - 1];
    }
}


code:
/**
 * Copyright (c) 2012-2013 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.amaze;

import javax.microedition.io.ConnectionNotFoundException;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Image;
import javax.microedition.midlet.MIDlet;

import com.nokia.example.amaze.ui.MazeCanvas;
import com.nokia.mid.ui.VirtualKeyboard;

/**
 * Main class defining the MIDlet.
 */
public class Main
      extends MIDlet
      implements SplashScreen.Listener
{
    // Constants
    public static final boolean HAS_ONE_KEY_BACK;
    private static final int SPLASH_SCREEN_BG_COLOR = 0xff00b4ec;
    private static final String SPLASH_SCREEN_IMAGE =
            "/graphics/splash-screen-image.png";
    private static final int SPLASH_SCREEN_DELAY = 3000; // Milliseconds

    // Members
    private static Main _instance = null;
    private SplashScreen _splashScreen = null;
    private MazeCanvas _mazeCanvas = null;

    /**
     * Static constructor.
     */
    static {
        String keyboardType = System.getProperty("com.nokia.keyboard.type");

        if (keyboardType  != null && keyboardType.equalsIgnoreCase("OnekeyBack")) {
            HAS_ONE_KEY_BACK = true;
        }
        else {
            HAS_ONE_KEY_BACK = false;
        }
    }

    /**
     * Constructor.
     */
    public Main() {
        _instance = this;

        // Hide the command for opening the virtual keypad
        try {
            Class.forName("com.nokia.mid.ui.VirtualKeyboard");
            VirtualKeyboard.hideOpenKeypadCommand(true);
        }
        catch (ClassNotFoundException e) {}

        _splashScreen = new SplashScreen(this, SPLASH_SCREEN_BG_COLOR);
        _splashScreen.loadImage(SPLASH_SCREEN_IMAGE);
    }

    /**
     * From MIDlet.
     */
    public void startApp() {
        final Display display = Display.getDisplay(this);
        Displayable current = display.getCurrent();

        if (current == null) {
            // Check that the API is available
            boolean isApiAvailable =
                    (System.getProperty("microedition.m3g.version") != null);

            if (!isApiAvailable) {
                quitApp();
            }
            else {
                _mazeCanvas = new MazeCanvas(this);

                if (_splashScreen != null) {
                    _splashScreen.show(display, _mazeCanvas, SPLASH_SCREEN_DELAY);
                    _mazeCanvas.init();
                }
                else {
                    _mazeCanvas.init();
                    display.setCurrent(_mazeCanvas);
                }
            }
        }
        else {
            // In case the MIDlet has been hidden
            if (current == _mazeCanvas) {
                _mazeCanvas.setRunning(true);
            }

            Display.getDisplay(this).setCurrent(current);
        }
    }

    /**
     * From MIDlet.
     */
    public void pauseApp() {
        _mazeCanvas.pause();
        _mazeCanvas.setRunning(false);
    }

    /**
     * From MIDlet.
     */
    public void destroyApp(boolean unconditional) {
        _mazeCanvas.setRunning(false);
    }

    /**
     * From SplashScreen.Listener.
     */
    public void onFinished() {
        // Splash screen is no longer required
        _splashScreen = null;
    }

    /**
     * Quits the app but first stops the game thread.
     */
    public void quitApp() {
        _mazeCanvas.setRunning(false);
        notifyDestroyed();
    }

    /**
     * Loads the given image by filename.
     * @param filename The path of the image file.
     * @return The created image or null in case of a failure.
     */
    public static Image makeImage(String filename) {
        Image image = null;

        try {
            image = Image.createImage(filename);
        }
        catch (Exception e) {
            // Use a null image instead
            System.out.println("Failed to create image: " + filename);
        }

        return image;
    }

    /**
     * Tries to open the given URL. If successful, this app is be terminated.
     * @param url The URL to open.
     */
    public static void openLink(String url) {
        System.out.println("Main.openLink(): " + url);

        try {
            if (_instance.platformRequest(url)) {
                _instance.quitApp();
            }
        }
        catch (ConnectionNotFoundException e) {
            System.out.println("Main.openLink(): " + e.toString());
        }
        catch (Exception e) {}
    }
}


code:
/**
 * Copyright (c) 2012-2013 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.amaze;

import java.io.IOException;

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;

/**
 * The splash screen.
 */
public class SplashScreen extends Canvas {
    // Members
    private final Listener _listener;
    private Image _image;
    private final int _backgroundColor;
    private int _centerX;
    private int _centerY;

    /**
     * Creates a splash screen to display the specified image and fills the
     * background with the specified color.
     *
     * @param backgroundColor The background color.
     */
    public SplashScreen(Listener listener, final int backgroundColor) {
        setFullScreenMode(true);
        _listener = listener;
        _backgroundColor = backgroundColor;
    }

    /**
     * From Canvas.
     */
    protected void paint(Graphics graphics) {
        graphics.setColor(_backgroundColor);
        graphics.fillRect(0x00, 0x00, getWidth(), getHeight());

        if (_image != null) {
            graphics.drawImage(_image, _centerX, _centerY, 0x00);
        }
    }

    /**
     * Loads the splash screen image.
     * @param image The path of the image to load.
     */
    public void loadImage(String image) {
        try {
            _image = Image.createImage(image);

            // Calculate the center position
            _centerX = (getWidth() - _image.getWidth()) / 2;
            _centerY = (getHeight() - _image.getHeight()) / 2;
        }
        catch (IOException e) {}
    }

    /**
     * Shows the splash and changes to the specified displayable after the
     * specified number of milliseconds.
     *
     * @param display Current Display.
     * @param next new Displayable.
     * @param millis splash time in milliseconds.
     */
    public void show(final Display display,
                     final Displayable next,
                     final long millis)
    {
        display.setCurrent(this);

        // Schedule next Displayable
        Thread t = new Thread(new Runnable() {
            public void run() {
                try {
                    Thread.sleep(millis);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    display.setCurrent(next);

                    if (_listener != null) {
                        _listener.onFinished();
                    }
                }
            }
        });
        t.start();
    }

    /**
     * Interface for MIDlet so that it can know when the splash screen can be
     * released for garbage collection.
     */
    public interface Listener {
        void onFinished();
    }
}


code:
/**
 * Copyright (c) 2012-2013 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.amaze.gestures;

/**
 * Helper class for detecting double taps.
 */
public class DoubleTapDetector {
    // Constants
    private static final int DOUBLE_TAP_TIMEOUT = 400; // Milliseconds
    private static final int JITTER_THRESHOLD = 20;

    // Members
    private final Listener _listener;
    private long _lastTapTime = 0;
    private int[] _lastTapCoordinate = new int[2];

    /**
     * Constructor.
     * @param listener The listener.
     */
    public DoubleTapDetector(Listener listener) {
         _listener = listener;
         _lastTapCoordinate[0] = -1;
         _lastTapCoordinate[1] = -1;
    }

    /**
     * Checks if a double tap has occured. If not, then saves the details of
     * this tap.
     * @param x X coordinate of the tap event.
     * @param y Y coordinate of the tap event.
     * @return True if the double tap was detected, false otherwise.
     */
    public boolean onTapped(int x, int y) {
        long currentTime = System.currentTimeMillis();

        if (currentTime - _lastTapTime < DOUBLE_TAP_TIMEOUT
                && Math.abs(_lastTapCoordinate[0] - x) < JITTER_THRESHOLD
                && Math.abs(_lastTapCoordinate[1] - y) < JITTER_THRESHOLD)
        {
            // Double tap!
            if (_listener != null) {
                // Notify the listener
                _listener.onDoubleTapDetected();
            }

             _lastTapCoordinate[0] = -1;
             _lastTapCoordinate[1] = -1;
             return true;
        }

        // Store the current time and tap coordinates
        _lastTapTime = currentTime;
        _lastTapCoordinate[0] = x;
        _lastTapCoordinate[1] = y;
        return false;
    }

    /**
     * Interface for the listener.
     */
    public interface Listener {
        void onDoubleTapDetected();
    }
}


code:
/**
 * Copyright (c) 2012-2013 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.amaze.gestures;

public class SafeGestureEvent {
    private int dragDistanceX = 0;
    private int dragDistanceY = 0;
    private float flickDirection = 0;
    private int flickSpeed = 0;
    private int flickSpeedX = 0;
    private int flickSpeedY = 0;
    private int pinchCenterChangeX = 0;
    private int pinchCenterChangeY = 0;
    private int pinchCenterX = 0;
    private int pinchCenterY = 0;
    private int pinchDistanceChange = 0;
    private int pinchDistanceCurrent = 0;
    private int pinchDistanceStarting = 0;
    private int startX = 0;
    private int startY = 0;
    private int type = 0;

    public int getDragDistanceX() {
        return dragDistanceX;
    }

    public int getDragDistanceY() {
        return dragDistanceY;
    }

    public float getFlickDirection() {
        return flickDirection;
    }

    public int getFlickSpeed() {
        return flickSpeed;
    }

    public int getFlickSpeedX() {
        return flickSpeedX;
    }

    public int getFlickSpeedY() {
        return flickSpeedY;
    }

    public int getPinchCenterChangeX() {
        return pinchCenterChangeX;
    }

    public int getPinchCenterChangeY() {
        return pinchCenterChangeY;
    }

    public int getPinchCenterX() {
        return pinchCenterX;
    }

    public int getPinchCenterY() {
        return pinchCenterY;
    }

    public int getPinchDistanceChange() {
        return pinchDistanceChange;
    }

    public int getPinchDistanceCurrent() {
        return pinchDistanceCurrent;
    }

    public int getPinchDistanceStarting() {
        return pinchDistanceStarting;
    }

    public int getStartX() {
        return startX;
    }

    public int getStartY() {
        return startY;
    }

    public int getType() {
        return type;
    }

    public void setDragDistanceX(int dragDistanceX) {
        this.dragDistanceX = dragDistanceX;
    }

    public void setDragDistanceY(int dragDistanceY) {
        this.dragDistanceY = dragDistanceY;
    }

    public void setFlickDirection(float flickDirection) {
        this.flickDirection = flickDirection;
    }

    public void setFlickSpeed(int flickSpeed) {
        this.flickSpeed = flickSpeed;
    }

    public void setFlickSpeedX(int flickSpeedX) {
        this.flickSpeedX = flickSpeedX;
    }

    public void setFlickSpeedY(int flickSpeedY) {
        this.flickSpeedY = flickSpeedY;
    }

    public void setPinchCenterChangeX(int pinchCenterChangeX) {
        this.pinchCenterChangeX = pinchCenterChangeX;
    }

    public void setPinchCenterChangeY(int pinchCenterChangeY) {
        this.pinchCenterChangeY = pinchCenterChangeY;
    }

    public void setPinchCenterX(int pinchCenterX) {
        this.pinchCenterX = pinchCenterX;
    }

    public void setPinchCenterY(int pinchCenterY) {
        this.pinchCenterY = pinchCenterY;
    }

    public void setPinchDistanceChange(int pinchDistanceChange) {
        this.pinchDistanceChange = pinchDistanceChange;
    }

    public void setPinchDistanceCurrent(int pinchDistanceCurrent) {
        this.pinchDistanceCurrent = pinchDistanceCurrent;
    }

    public void setPinchDistanceStarting(int pinchDistanceStarting) {
        this.pinchDistanceStarting = pinchDistanceStarting;
    }

    public void setStartX(int startX) {
        this.startX = startX;
    }

    public void setStartY(int startY) {
        this.startY = startY;
    }

    public void setType(int type) {
        this.type = type;
    }
}


code:
/**
 * Copyright (c) 2012-2013 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.amaze.gestures;

public class SafeGestureInteractiveZone {
    public static final int GESTURE_ALL = 49279;
    public static final int GESTURE_DRAG = 4;
    public static final int GESTURE_DROP = 8;
    public static final int GESTURE_FLICK = 16;
    public static final int GESTURE_LONG_PRESS = 2;
    public static final int GESTURE_LONG_PRESS_REPEATED = 32;
    public static final int GESTURE_PINCH = 64;
    public static final int GESTURE_RECOGNITION_END = 32768;
    public static final int GESTURE_RECOGNITION_START = 16384;
    public static final int GESTURE_TAP = 1;

    private int gestures;
    private int x;
    private int y;
    private int width;
    private int height;
    private Object nativeZone;

    public SafeGestureInteractiveZone() {

    }

    public SafeGestureInteractiveZone(int gestures) {
        this.gestures = gestures;
    }

    public void setGesture(int gesture) {
        this.gestures = gesture;
    }

    public int getGesture() {
        return gestures;
    }

    public void setRectangle(int x, int y, int width, int height) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public int getWidth() {
        return width;
    }

    public int getHeight() {
        return height;
    }

    public Object getNativeZone() {
        return nativeZone;
    }

    public void setNativeZone(Object nativeZone) {
        this.nativeZone = nativeZone;
    }
}


code:
/**
 * Copyright (c) 2012-2013 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.amaze.gestures;

public interface SafeGestureListener {
    public void gestureAction(Object container,
        SafeGestureInteractiveZone gestureInteractiveZone,
        SafeGestureEvent gestureEvent);
}


code:
/**
 * Copyright (c) 2012-2013 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.amaze.gestures;

public class SafeGestureListenerImpl
    implements com.nokia.mid.ui.gestures.GestureListener {
    private SafeGestureListener listener;

    public SafeGestureListenerImpl(SafeGestureListener listener) {
        this.listener = listener;
    }

    public void gestureAction(Object container,
            com.nokia.mid.ui.gestures.GestureInteractiveZone gzone,
            com.nokia.mid.ui.gestures.GestureEvent gevent) {

        SafeGestureInteractiveZone zone = new SafeGestureInteractiveZone();
        zone.setGesture(gzone.getGestures());
        zone.setRectangle(gzone.getX(), gzone.getY(), gzone.getWidth(), gzone.getHeight());

        SafeGestureEvent event = new SafeGestureEvent();
        event.setType(gevent.getType());
        event.setStartX(gevent.getStartX());
        event.setStartY(gevent.getStartY());

        switch (gevent.getType()) {
            case com.nokia.mid.ui.gestures.GestureInteractiveZone.GESTURE_PINCH:
                event.setPinchCenterChangeX(gevent.getPinchCenterChangeX());
                event.setPinchCenterChangeY(gevent.getPinchCenterChangeY());
                event.setPinchCenterX(gevent.getPinchCenterX());
                event.setPinchCenterY(gevent.getPinchCenterY());
                event.setPinchDistanceChange(gevent.getPinchDistanceChange());
                event.setPinchDistanceCurrent(gevent.getPinchDistanceCurrent());
                event.setPinchDistanceStarting(gevent.getPinchDistanceStarting());
                break;
            case com.nokia.mid.ui.gestures.GestureInteractiveZone.GESTURE_DRAG:
                event.setDragDistanceX(gevent.getDragDistanceX());
                event.setDragDistanceY(gevent.getDragDistanceY());
                break;
            case com.nokia.mid.ui.gestures.GestureInteractiveZone.GESTURE_FLICK:
                event.setFlickDirection(gevent.getFlickDirection());
                event.setFlickSpeed(gevent.getFlickSpeed());
                event.setFlickSpeedX(gevent.getFlickSpeedX());
                event.setFlickSpeedY(gevent.getFlickSpeedY());
                break;
            case com.nokia.mid.ui.gestures.GestureInteractiveZone.GESTURE_DROP:
            case com.nokia.mid.ui.gestures.GestureInteractiveZone.GESTURE_LONG_PRESS:
            case com.nokia.mid.ui.gestures.GestureInteractiveZone.GESTURE_TAP:
            default:
                break;
        }

        listener.gestureAction(container, zone, event);
    }
}


code:
/**
 * Copyright (c) 2012-2013 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.amaze.gestures;

public class SafeGestureRegistrationManager {

    public static void setListener(Object container, SafeGestureListener listener) {
        try {
            Class.forName("com.nokia.mid.ui.gestures.GestureRegistrationManager"); //Try to produce the exception
            SafeGestureListenerImpl listenerImpl = new SafeGestureListenerImpl(listener);
            com.nokia.mid.ui.gestures.GestureRegistrationManager.setListener(container, listenerImpl);
        } catch (Exception e) {
        }
    }

    public static boolean register(Object container, SafeGestureInteractiveZone zone) {
        try {
            Class.forName("com.nokia.mid.ui.gestures.GestureRegistrationManager"); //Try to produce the exception
            com.nokia.mid.ui.gestures.GestureInteractiveZone gzone = new
                com.nokia.mid.ui.gestures.GestureInteractiveZone(zone.getGesture());
            gzone.setRectangle(zone.getX(), zone.getY(), zone.getWidth(), zone.getHeight());
            zone.setNativeZone(gzone);
            return com.nokia.mid.ui.gestures.GestureRegistrationManager.register(container, gzone);
        } catch (Exception e) {
            return false;
        }
    }

    public static void unregister(Object container, SafeGestureInteractiveZone zone) {
        try {
            Class.forName("com.nokia.mid.ui.gestures.GestureRegistrationManager"); //Try to produce the exception
            com.nokia.mid.ui.gestures.GestureInteractiveZone gzone =
                (com.nokia.mid.ui.gestures.GestureInteractiveZone) zone.getNativeZone();
            com.nokia.mid.ui.gestures.GestureRegistrationManager.unregister(container, gzone);
        } catch (Exception e) {
        }
    }
}


code:
/**
 * Copyright (c) 2012-2013 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.amaze.model;

import com.nokia.example.amaze.ui.MazeCanvas;

/**
 * Model of the marble.
 */
public class MarbleModel {
    // Constants
    public static final float DEFAULT_SIZE = 4.0f; // Marble width and height
    public static final float DEFAULT_SIZE_HALVED = DEFAULT_SIZE / 2;
    private static final float DEFAULT_Y = 10.0f;
    public static final float HYPOTENUSE = (float)Math.sqrt(DEFAULT_SIZE_HALVED * DEFAULT_SIZE_HALVED + DEFAULT_SIZE_HALVED * DEFAULT_SIZE_HALVED);
    private static final float DEFAULT_DRAG = 0.1f; // Acts as a mass
    public static final float DEFAULT_BOUNCE_DRAG = DEFAULT_DRAG * 0.8f;
    private static final float MAX_VELOCITY_PER_AXIS = 3.5f;
    private static final float FRICTION = 0.2f;

    // Members
    private float[] _position = null;
    private float[] _velocity = null;
    private float[] _newVelocity = null;
    private float _drag = DEFAULT_DRAG;

    /**
     * Constructor.
     */
    public MarbleModel() {
        _position = new float[3];
        _position[1] = DEFAULT_Y;
        _velocity = new float[3];
        _newVelocity = new float[3];
    }

    /**
     * @param drag The drag to set ]0.0, 1.0]. This value defines how much the
     *                marble resists the acceleration.
     */
    public void setDrag(final float drag) {
        if (drag > 0 && drag <= 1.0) {
            // Subtract the given mass from 100 so that we don't have to do it
            // later in the calculations
            _drag = drag;
        }
    }

    /**
     * @return The drag.
     */
    public final float drag() {
        return _drag;
    }

    /**
     * @param velocity The velocity to set.
     */
    public final void setVelocity(final float[] velocity) {
        _velocity = velocity;
    }

    /**
     * @return The velocity.
     */
    public final float[] velocity() {
        return _velocity;
    }

    /**
     * Sets the marble position.
     * @param position The new position.
     */
    public final void setPosition(final float[] position) {
        _position = position;
        _position[1] = DEFAULT_Y;
    }

    /**
     * Sets the marble position.
     * @param x X coordinate
     * @param y Y coordinate
     * @param z Z coordinate
     */
    public final void setPosition(final float x, final float y, final float z) {
        _position[0] = x;
        //_position[1] = y;
        _position[2] = z;
    }

    /**
     * Moves the marble based on the current velocity and the given ticks.
     * @param ticks The milliseconds since last time the method was called.
     */
    public final void move(final int ticks) {
        final float ticksCoefficient = (float)ticks / MazeCanvas.TICKS_COEFFICIENT;
        _position[0] += _velocity[0] * ticksCoefficient;
        _position[2] += _velocity[2] * ticksCoefficient;
    }

    /**
     * Moves the marble.
     * @param x X coordinate
     * @param y Y coordinate
     * @param z Z coordinate
     */
    public final void move(final float x, final float y, final float z) {
        _position[0] += x;
        //_position[1] += y;
        _position[2] += z;
    }

    /**
     * @return The marble position.
     */
    public final float[] position() {
        return _position;
    }

    /**
     * Calculates the marble velocity based on the current velocity and the
     * given values of acceleration.
     * @param accelerationX
     * @param accelerationY
     * @param ticks The milliseconds since last time the method was called.
     * @return The modified velocity.
     */
    public final float[] calculateVelocity(final double accelerationX,
                                           final double accelerationY,
                                           final int ticks)
    {
        final float coefficient = _drag * (float)ticks / MazeCanvas.TICKS_COEFFICIENT;
        _newVelocity[1] = _velocity[1];
        _newVelocity[0] = _velocity[0] - (float)accelerationX * coefficient;
        _newVelocity[2] = _velocity[2] + (float)accelerationY * coefficient; // Note: Y => Z

        for (int i = 0; i < 3; ++i) {
            if (i == 1) {
                continue;
            }

            final float absVelocity = Math.abs(_newVelocity[i]);

            // Apply friction
            if (absVelocity < FRICTION) {
                _newVelocity[i] = 0;
            }
            else {
                _newVelocity[i] -= FRICTION * (_newVelocity[i] / absVelocity);
            }

            // Limit the velocity based on maximum
            if (absVelocity > MAX_VELOCITY_PER_AXIS) {
                _newVelocity[i] = MAX_VELOCITY_PER_AXIS
                        * (_newVelocity[i] / absVelocity);
            }
        }

        return _newVelocity;
    }
}


code:
/**
 * Copyright (c) 2012-2013 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.amaze.model;

import java.util.Random;

/**
 * The model of the maze.
 */
public class Maze {
    // Constants
    private final static int UP = 0;
    private final static int DOWN = 1;
    private final static int LEFT = 2;
    private final static int RIGHT = 3;
    private final static int MAX_SIZE = 31;

    // Members
    private long[] _maze = null; // Maze rows are encoded as bits in a long field
    private final float _sideLength;
    private final float _height;

    // Utility variables
    private final float _origin;
    private float _spaceBetweenPlanes = 0f;

    // Store the location on X of the start and end
    private int _startX = -1;
    private int _endX = -1;

    /**
     * Constructor.
     * @param corridorCount
     * @param mazeSideLength
     * @param mazeHeight
     */
    public Maze(final int corridorCount,
                final float mazeSideLength,
                final float mazeHeight)
    {
        if (corridorCount > MAX_SIZE) {
            throw new IllegalArgumentException("Maze is too big!");
        }

        _sideLength = mazeSideLength;
        _height = mazeHeight;
        _origin = -_sideLength / 2;
    }

    /**
     * @return The maze height.
     */
    public final float height() {
        return _height;
    }

    /**
     * @return The maze origin.
     */
    public final float origin() {
        return _origin;
    }

    /**
     * @return The space between planes.
     */
    public final float spaceBetweenPlanes() {
        return _spaceBetweenPlanes;
    }

    /**
     * @return The maze content as an array.
     */
    public final long[] array() {
        return _maze;
    }

    /**
     * @return The X coordinate of the start position in the maze.
     */
    public final int startX() {
        return _startX;
    }

    /**
     * @return The X coordinate of the goal in the maze.
     */
    public final int goalX() {
        return _endX;
    }

    /**
     * Resolves and returns the start position.
     * @return The start position.
     */
    public float[] startPosition() {
        float[] position = new float[3];
        position[0] = _origin + _startX * _spaceBetweenPlanes;
        position[1] = _height / 2; // Not used, hopefully
        position[2] = _origin + _spaceBetweenPlanes;
        return position;
    }

    /**
     * Checks if the given position (x, z) is near enough of the end (goal)
     * position of the maze. The "near enough" is determined by the given
     * tolerance.
     * @param x
     * @param z
     * @param tolerance
     * @return
     */
    public final boolean isAtTheEnd(final float x,
                                    final float z,
                                    final float tolerance)
    {
        // Use linear distances
        return Math.abs(x - (_origin + _endX * _spaceBetweenPlanes))
                < tolerance
                && Math.abs(z + _origin) < tolerance;
    }

    /**
     * Creates a new random maze.
     * @param mazeSize The size of the maze array to create.
     */
    public void createNew(int mazeSize) {
        _startX = -1;
        _endX = -1;
        Random random = new Random();
        int totalSize = mazeSize * 2 + 1;
        _maze = new long[totalSize];

        for (int i = 0; i < _maze.length; i++) {
            _maze[i] = -1;
        }

        int backtrack[] = new int[mazeSize * mazeSize];
        int backtrackIndex = 0;
        int x = 1;
        int y = 1;

        // Traverse the maze finding unconnected spots
        while (true) {
            _maze[x] &= ~(0x1 << y);
            int directions[] = new int[4];
            int availableSpaces = 0;

            if (y - 2 > 0 && (_maze[x] & (0x1 << (y - 2))) != 0) {
                directions[availableSpaces++] = UP;
            }

            if (y + 2 < totalSize && (_maze[x] & (0x1 << (y + 2))) != 0) {
                directions[availableSpaces++] = DOWN;
            }

            if (x + 2 < totalSize && (_maze[x + 2] & (0x1 << y)) != 0) {
                directions[availableSpaces++] = LEFT;
            }

            if (x - 2 > 0 && (_maze[x - 2] & (0x1 << y)) != 0) {
                directions[availableSpaces++] = RIGHT;
            }

            if (availableSpaces > 0) {
                int chosenDirection =
                        directions[(random.nextInt() >>> 1) % availableSpaces];
                backtrack[backtrackIndex++] = chosenDirection;

                if (chosenDirection == UP) {
                    _maze[x] &= ~(0x1 << (y - 1));
                    y -= 2;
                }
                else if (chosenDirection == DOWN) {
                    _maze[x] &= ~(0x1 << (y + 1));
                    y += 2;
                }
                else if (chosenDirection == LEFT) {
                    _maze[x + 1] &= ~(0x1 << y);
                    x += 2;
                }
                else if (chosenDirection == RIGHT) {
                    _maze[x - 1] &= ~(0x1 << y);
                    x -= 2;
                }
            }
            else { // If nothing is not visited in the neigborhood backtrack
                if (backtrackIndex == 0) {
                    // end of algorithm
                    break;
                }

                backtrackIndex--;

                if (backtrack[backtrackIndex] == UP) {
                    y += 2;
                }
                else if (backtrack[backtrackIndex] == DOWN) {
                    y -= 2;
                }
                else if (backtrack[backtrackIndex] == LEFT) {
                    x -= 2;
                }
                else if (backtrack[backtrackIndex] == RIGHT) {
                    x += 2;
                }
            }
        }

        // Find start and end points
        while (true) {
            int pos = (random.nextInt() >>> 1) % totalSize;

            if (_startX < 0 && (_maze[1] & (0x1 << pos)) == 0) {
                _startX = pos;
                //_maze[0] &= ~(0x1 << pos); // Make a hole to the wall
            }

            if (_endX < 0 && (_maze[_maze.length - 2] & (0x1 << pos)) == 0) {
                _endX = pos;
                _maze[_maze.length - 1] &= ~(0x1 << pos); // Make a hole to the wall
            }

            if (_endX >= 0 && _startX >= 0) {
                break;
            }
        }

        _spaceBetweenPlanes = _sideLength / (_maze.length - 1);
    }

    /**
     * Releases the resources but keeps the meta information such as the start
     * and the end of the maze last created.
     */
    public void clear() {
        _maze = null;
    }
}


code:
/**
 * Copyright (c) 2012-2013 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.amaze.model;

/**
 * A general, easy-to-use timer.
 */
public class MyTimer {
    // Constants
    private static final int MAX_LISTENER_COUNT = 5;

    // Members
    private static MyTimer _instance = null;
    private MyListener[] _listeners = null;
    private long _time = 0;

    /**
     * @return The singleton instance of MyTimer.
     */
    static public MyTimer instance() {
        if (_instance == null) {
            _instance = new MyTimer();
        }

        return _instance;
    }

    /**
     * Constructor.
     */
    private MyTimer() {
        _listeners = new MyListener[MAX_LISTENER_COUNT];
    }

    /**
     * Updates the internal time of the timer. If enough time has passed, the
     * timer will trigger (call the listener(s)).
     * @param time The current time.
     */
    public void update(final long time) {
        _time = time;
        MyListener listener = null;

        for (int i = 0; i < MAX_LISTENER_COUNT; ++i) {
            listener = _listeners[i];

            if (listener == null) {
                continue;
            }

            if (_time - listener._lastTime >= listener._interval) {
                // Notify the listener
                listener._object.onTimeout();

                if (listener._repeat) {
                    listener._lastTime = _time;
                }
                else {
                    // Not repetitive. Thus, remove the listener
                    _listeners[i] = null;
                }
            }
        }
    }

    /**
     * For convenience.
     */
    public void update() {
        update(System.currentTimeMillis());
    }

    /**
     * Adds the given listener.
     * @param listener The listener to add.
     * @param repeat If true, the timer will call the listener repeatedly.
     * @param interval The interval (or delay) for calling the listener.
     * @return True if the listener was added successfully, false otherwise.
     */
    public boolean addListener(Listener listener,
                               boolean repeat,
                               int interval)
    {
        if (listener == null || interval <= 0) {
            return false;
        }

        for (int i = 0; i < MAX_LISTENER_COUNT; ++i) {
            if (_listeners[i] != null
                    && _listeners[i]._object == listener)
            {
                // Listener already exists
                System.out.println("MyTimer::addListener(): Listener already exists.");
                return false;
            }
        }

        for (int i = 0; i < MAX_LISTENER_COUNT; ++i) {
            if (_listeners[i] == null) {
                MyListener myListener = new MyListener();
                myListener._object = listener;
                myListener._repeat = repeat;
                myListener._interval = interval;
                myListener._lastTime = System.currentTimeMillis();
                _listeners[i] = myListener;
                return true;
            }
        }

        return false;
    }

    /**
     * Removes the given listener.
     * @param listener The listener to remove.
     * @return True if the listener was found and removed, false otherwise.
     */
    public boolean removeListener(Listener listener) {
        if (listener != null) {
            for (int i = 0; i < MAX_LISTENER_COUNT; ++i) {
                if (_listeners[i] != null && _listeners[i]._object == listener) {
                    _listeners[i] = null;
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Interface for the listeners.
     */
    public interface Listener {
        void onTimeout();
    }

    /**
     * Internal class for reserving the meta information related to a listener.
     */
    private class MyListener {
        private Listener _object;
        private boolean _repeat = false;
        private int _interval = 0; // Milliseconds
        private long _lastTime = 0;
    }
}


code:
/**
 * Copyright (c) 2012-2013 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.amaze.sensors;

public abstract class AccelerationProvider {

    private Listener listener;

    /**
     * Callback for subclasses
     * @param ax
     * @param ay
     * @param az
     */
    protected final void dataReceived(double ax, double ay, double az) {
        if (listener != null) {
            listener.onDataReceived(ax, ay, az);
        }
    }

    /**
     * Closes the sensor connection
     */
    public abstract void close();

    public static AccelerationProvider getProvider(Listener listener) {
        AccelerationProvider provider = null;
        try {
            if (sensorsSupported()) {
                Class c = Class.forName("com.nokia.example.amaze.sensors."
                        + "AccelerationProviderImpl");
                provider = (AccelerationProvider) c.newInstance();
                provider.listener = listener;
            }
        }
        catch (Exception e) {
            System.out.println("Failed to create acceleration provider instance!");
        }

        return provider;
    }

    public static boolean sensorsSupported() {
        try {
            Class.forName("javax.microedition.sensor.SensorConnection");
            return true;
        }
        catch (Exception e) {
            return false;
        }
    }

    public interface Listener {
        void onDataReceived(double ax, double ay, double az);
    }
}


code:
/**
 * Copyright (c) 2012-2013 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.amaze.sensors;

import com.nokia.example.amaze.ArrayUtils;
import java.io.IOException;
import javax.microedition.io.Connector;
import javax.microedition.sensor.ChannelInfo;
import javax.microedition.sensor.Data;
import javax.microedition.sensor.DataListener;
import javax.microedition.sensor.SensorConnection;
import javax.microedition.sensor.SensorInfo;
import javax.microedition.sensor.SensorManager;

/**
 * Implementation for AccelerationProvider
 */
class AccelerationProviderImpl
        extends AccelerationProvider
        implements DataListener {

    private SensorConnection sensor;
    private int dataType;

    public AccelerationProviderImpl()
            throws IOException {
        initConnection();
    }

    /**
     * Close the sensor connection
     */
    public void close() {
        sensor.removeDataListener();
        try {
            sensor.close();
            sensor = null;
        }
        catch (IOException ioe) {
        }
    }

    /**
     * Handle sensor received data
     */
    public void dataReceived(SensorConnection sc, Data[] data, boolean isDataLost) {
        switch (dataType) {
            case ChannelInfo.TYPE_DOUBLE:
                dataReceived(ArrayUtils.getLast(data[0].getDoubleValues()),
                             ArrayUtils.getLast(data[1].getDoubleValues()),
                             ArrayUtils.getLast(data[2].getDoubleValues()));
                break;
            default:
                break;
        }
    }

    /**
     * Initialize connection to acceleration sensors
     */
    private void initConnection() throws IOException {
        SensorInfo infos[] = SensorManager.findSensors("acceleration", null);
        if (infos.length > 0) {
            dataType = infos[0].getChannelInfos()[0].getDataType();
            try {
                sensor = (SensorConnection) Connector.open(infos[0].getUrl());
                sensor.setDataListener(this, 1);
            }
            catch (SecurityException se) {
                throw new IOException("could not open sensor");
            }
            catch (IOException ioe) {
                throw new IOException("could not open sensor");
            }
            catch (IllegalArgumentException iae) {
                throw new IOException("could not open sensor");
            }
        }
        else {
            throw new IOException("acceleration sensor not found");
        }
    }
}


code:
/**
 * Copyright (c) 2012-2013 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.amaze.ui;

import javax.microedition.m3g.Camera;
import javax.microedition.m3g.Transform;
import javax.microedition.m3g.World;

/**
 * Class for pause and transition camera animations.
 */
public class CameraAnimator {
    // Constants
    private static final float NUM_OF_TRANSITION_STEPS = 30;
    private static final float MIN_TRANSITION_STEP = 0.01f;
    private static final float PAUSE_ANIMATION_ROTATION_SPEED = 0.2f;

    // Animation types
    public static final int PAUSE_ANIMATION = 0;
    public static final int TRANSITION_ANIMATION_TO_POV = 1;
    public static final int TRANSITION_ANIMATION_TO_TOP = 2;
    public static final int LEVEL_RESET_ANIMATION_STEP = 3;

    // Members
    private Listener _listener = null;
    private World _world = null;
    private Camera _camera = null;
    private Transform _currentTransform = null;
    private Transform _targetTransform = null;
    private float[] _currentMatrix = null;
    private float[] _targetMatrix = null;
    private float[] _transitionMatrix = null;
    private float[] _currentOrientation = null;
    private float[] _targetOrientation = null;
    private int _animationType = -1;
    private int _stepCount = 0;
    private boolean _running = false;

    /**
     * Constructor.
     * @param listener The listener which is the maze canvas.
     */
    public CameraAnimator(Listener listener, World world, Camera camera) {
        if (listener == null || world == null || camera == null) {
            throw new IllegalArgumentException("None of the arguments can be null!");
        }

        _listener = listener;
        _world = world;
        _camera = camera;
    }

    /**
     * Starts the animation of the given type.
     *
     * Note that after calling this method only "_currentTransform" of the
     * members is guaranteed not to be null. If the given target transform is
     * not null, the following memebers are guaranteed not be null:
     * - _currentTransform,
     * - _targetTransform,
     * - _targetMatrix and
     * - _currentMatrix.
     *
     * @param type The animation type.
     * @param targetTransform The target transform. Can be null.
     * @param targetOrientation The target orientation. Can be null.
     */
    public void startAnimation(int type,
                               Transform targetTransform,
                               float[] targetOrientation)
    {
        System.out.println("CameraAnimator::startAnimation(): " + type);

        if (_running) {
            releaseResources();
            _running = false;
        }

        _animationType = type;
        _stepCount = 0;

        if (targetTransform != null) {
            _targetTransform = targetTransform;
            _targetMatrix = new float[16];
            _targetTransform.get(_targetMatrix);
        }

        _currentTransform = new Transform();
        Camera activeCamera = _world.getActiveCamera();

        if (activeCamera == null) {
            // No active camera set, do set it now
            System.out.println("CameraAnimator::startAnimation(): No active camera set.");
            _world.setActiveCamera(_camera);
            _camera.getTransform(_currentTransform);
        }
        else {
            activeCamera.getTransform(_currentTransform);

            if (_camera != activeCamera) {
                System.out.println("CameraAnimator::startAnimation(): Switching the camera.");
                _camera.setTransform(_currentTransform);
                _world.setActiveCamera(_camera);
            }
        }

        if (targetOrientation != null) {
            _targetOrientation = targetOrientation;
            _currentOrientation = new float[4];
            _camera.getOrientation(_currentOrientation);
        }

        if (targetTransform != null) {
            _currentMatrix = new float[16];
            _currentTransform.get(_currentMatrix);

            _transitionMatrix = calculateTransitionMatrix(_currentMatrix,
                                                          _targetMatrix);
        }

        _running = true;
    }

    /**
     * For convenience.
     * @param type
     * @param targetCamera
     */
    public void startAnimation(int type, Camera targetCamera) {
        if (targetCamera == null) {
            return;
        }

        Transform transform = new Transform();
        targetCamera.getTransform(transform);
        float[] orientation = new float[4];
        targetCamera.getOrientation(orientation);
        startAnimation(type, transform, orientation);
    }

    /**
     * For convenience.
     * @param type
     */
    public void startAnimation(int type) {
        startAnimation(type, null, null);
    }

    /**
     * Stops the animation.
     */
    public void stopAnimation() {
        System.out.println("CameraAnimator::stopAnimation()");
        releaseResources();
        _running = false;
        _listener.onAnimationFinished(_animationType);
    }

    /**
     * @return True if the animator running, false otherwise.
     */
    public final boolean running() {
        return _running;
    }

    /**
     * @return The type of the current animation.
     */
    public final int animationType() {
        return _animationType;
    }

    /**
     * Takes one update step in the animation.
     * @param ticks The milliseconds since last time the method was called.
     */
    public final void update(final int ticks) {
        final float coefficient = (float)ticks / MazeCanvas.TICKS_COEFFICIENT;

        if (_targetTransform != null) {
            if (!takeTransitionStepTowardsTarget(coefficient)) {
                System.out.println("CameraAnimator::update(): No more transition steps required.");

                if (_animationType == TRANSITION_ANIMATION_TO_POV
                    || _animationType == TRANSITION_ANIMATION_TO_TOP)
                {
                    // We're done here
                    _running = false;
                    stopAnimation();
                }
                else {
                    releaseResources();
                }
            }

            if (_currentTransform != null) {
                _camera.setTransform(_currentTransform);
            }

            if (_targetOrientation != null) {
                _camera.setOrientation(_currentOrientation[0],
                                       _currentOrientation[1],
                                       _currentOrientation[2],
                                       _currentOrientation[3]);
            }
        }
        else if (_animationType == PAUSE_ANIMATION) {
            _camera.postRotate(PAUSE_ANIMATION_ROTATION_SPEED * coefficient, 0f, 1f, 0f);
        }
        else if (_animationType == LEVEL_RESET_ANIMATION_STEP) {
            _camera.translate(0f, 12.0f * coefficient, 12.0f * coefficient);
            _stepCount++;

            if (_stepCount > 60) {
                stopAnimation();
            }
        }
        else {
            stopAnimation();
        }
    }

    /**
     * Prints the matrix of the given transform. For debugging purposes.
     * @param transform The transform to print.
     */
    public static final void printTransform(Transform transform) {
        if (transform == null) {
            return;
        }

        final float[] matrix = new float[16];
        transform.get(matrix);

        for (int i = 0; i < 16; ++i) {
            if ((i + 1) % 4 == 0) {
                System.out.println(matrix[i]);
            }
            else {
                System.out.print(matrix[i] + "\t");
            }
        }
    }

    /**
     * Prints the values of the properties of the given camera.
     * @param camera The camera of which values to print.
     */
    public static final void printCameraValues(Camera camera) {
        if (camera != null) {
            Transform transform = new Transform();
            camera.getTransform(transform);
            printTransform(transform);

            // Print orientation
            final float[] orientation = new float[4];
            camera.getOrientation(orientation);
            System.out.print("Orientation: [");
            System.out.println(orientation[0] + ", "
                               + orientation[1] + ", "
                               + orientation[2] + ", "
                               + orientation[3] + "]");
        }
    }

    /**
     * Modifies the value of "current" by the value of "diff" towards "target".
     * @param current The initial value.
     * @param target The target value.
     * @param diff The difference.
     * @return The modified value.
     */
    private final float newValue(float current, float target, float diff) {
        if (diff < 0) {
            diff = Math.abs(diff);
        }

        if (Math.abs(current - target) < diff) {
            return target;
        }

        if (target > current) {
            return current + diff;
        }

        return current - diff;
    }

    /**
     * Takes a step towards the target camera transition.
     * @param coefficient Defines the relative size of the step.
     * @return True if the matrix was modified, false otherwise.
     */
    private final boolean takeTransitionStepTowardsTarget(final float coefficient) {
        if (_currentMatrix == null || _targetMatrix == null) {
            System.out.println("CameraAnimator::takeTransitionStepTowardsTarget(): Null matrices!");
            return false;
        }

        final int matrixLength = _currentMatrix.length;
        final int maxChangesCount = (_targetOrientation == null)
                ? matrixLength : _transitionMatrix.length;
        int noChangesCount = 0;

        for (int i = 0; i < matrixLength; ++i) {
            if (_currentMatrix[i] == _targetMatrix[i]) {
                noChangesCount++;
                continue;
            }

            _currentMatrix[i] = newValue(_currentMatrix[i],
                                         _targetMatrix[i],
                                         _transitionMatrix[i] * coefficient);
        }

        if (_currentOrientation != null && _targetOrientation != null) {
            for (int i = 0; i < 4; ++i) {
                if (_currentOrientation[i] == _targetOrientation[i]) {
                    noChangesCount++;
                    continue;
                }

                _currentOrientation[i] =
                        newValue(_currentOrientation[i],
                                 _targetOrientation[i],
                                 _transitionMatrix[i + matrixLength] * coefficient);
            }
        }

        _currentTransform.set(_currentMatrix);
        return (noChangesCount != maxChangesCount);
    }

    /**
     * Creates the transition matrix whose values represent a single transition
     * step (delta) from the initial matrix to the target matrix.
     * @param from The initial matrix.
     * @param to The target matrix.
     * @return The transition (step delta) matrix.
     */
    private float[] calculateTransitionMatrix(float[] from, float[] to) {
        if (from == null || to == null || from.length != to.length){
            return null;
        }

        final int length = from.length;
        float[] transitionMatrix = new float[length + 4];

        for (int i = 0; i < length; ++i) {
            if (to[i] == from[i]) {
                continue;
            }

            transitionMatrix[i] = (to[i] - from[i]) / NUM_OF_TRANSITION_STEPS;

            if (transitionMatrix[i] == 0) {
                transitionMatrix[i] = MIN_TRANSITION_STEP;
            }
        }

        if (_targetOrientation != null) {
            // Calculate the transition steps for the orientation
            for (int i = 0; i < 4; ++i) {
                transitionMatrix[i + length] =
                        (_targetOrientation[i] - _currentOrientation[i])
                        / NUM_OF_TRANSITION_STEPS;

                if (transitionMatrix[i + length] == 0) {
                    transitionMatrix[i + length] = MIN_TRANSITION_STEP;
                }
            }
        }

        return transitionMatrix;
    }

    /**
     * Releases resources.
     */
    private void releaseResources() {
        _currentTransform = null;
        _targetTransform = null;
        _targetMatrix = null;
        _currentMatrix = null;
        _currentOrientation = null;
        _targetOrientation = null;
        _transitionMatrix = null;
    }

    /**
     * Listener interface for events of this class.
     */
    public interface Listener {
        /**
         * Called when an animation is finished.
         * @param animationType The type of the animation which was finished.
         */
        void onAnimationFinished(int animationType);
    }
}


code:
/**
 * Copyright (c) 2012-2013 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.amaze.ui;

import java.io.IOException;

import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.game.Sprite;

/**
 * Simple icon button.
 */
public class IconButton {
    // Constants
    // Button types
    public static final int EXIT = 0;
    public static final int INFO = 1;
    public static final int PAUSE = 2;
    public static final int VIEW_MODE = 3;

    // Icon filenames
    private static final String EXIT_NORMAL = "/graphics/exit-button-normal-30.png";
    private static final String EXIT_PRESSED = "/graphics/exit-button-pressed-30.png";
    private static final String INFO_NORMAL = "/graphics/info-button-normal-30.png";
    private static final String INFO_PRESSED = "/graphics/info-button-pressed-30.png";
    private static final String PAUSE_NORMAL = "/graphics/pause-button-normal-30.png";
    private static final String PAUSE_PRESSED = "/graphics/pause-button-pressed-30.png";
    private static final String VIEW_MODE_NORMAL = "/graphics/view-mode-button-normal-30.png";
    private static final String VIEW_MODE_PRESSED = "/graphics/view-mode-button-pressed-30.png";

    // Members
    private Image _normalImage = null;
    private Image _pressedImage = null;
    private final int _type;
    private boolean _pressed = false;

    /**
     * Constructor.
     * @param type The type of the image.
     */
    public IconButton(int type) {
        _type = type;

        // Load the images based on the type
        createImages(true);
    }

    /**
     * @param pressed
     */
    public void setPressed(final boolean pressed) {
        _pressed = pressed;
    }

    /**
     * @return True if the button is pressed, false otherwise.
     */
    public final boolean pressed() {
        return _pressed;
    }

    /**
     * Rotates the button image based on the orientation.
     * @param isPortrait If false, will rotate the images 90 degrees.
     */
    public void setIsPortrait(final boolean isPortrait) {
        createImages(isPortrait);
    }

    /**
     * Paints the button.
     * @param graphics
     * @param x
     * @param y
     */
    public void paint(Graphics graphics, int x, int y) {
        if (_pressed && _pressedImage != null) {
            graphics.drawImage(_pressedImage, x,  y, Graphics.TOP | Graphics.LEFT);
        }
        else if (_normalImage != null) {
            graphics.drawImage(_normalImage, x,  y, Graphics.TOP | Graphics.LEFT);
        }
    }

    /**
     * Creates and returns a new image or null in case of failure.
     * @param source The image filename.
     * @param isPortrait If false, will rotate the image 90 degrees.
     * @return The newly created image or null in case of failure.
     */
    private Image createImage(final String source,
                              final boolean isPortrait)
    {
        Image image = null;

        try {
            image = Image.createImage(source);

            if (!isPortrait && image != null) {
                // Rotate the image
                image = Image.createImage(image, 0, 0,
                            image.getWidth(), image.getHeight(),
                            Sprite.TRANS_ROT90);
            }
        }
        catch (IOException e) {}

        return image;
    }

    /**
     * Loads the button images based on the type.
     * @param isPortrait
     */
    private final void createImages(final boolean isPortrait) {
        switch (_type) {
        case EXIT: {
            _normalImage = createImage(EXIT_NORMAL, isPortrait);
            _pressedImage = createImage(EXIT_PRESSED, isPortrait);
            break;
        }
        case INFO: {
            _normalImage = createImage(INFO_NORMAL, isPortrait);
            _pressedImage = createImage(INFO_PRESSED, isPortrait);
            break;
        }
        case PAUSE: {
            _normalImage = createImage(PAUSE_NORMAL, isPortrait);
            _pressedImage = createImage(PAUSE_PRESSED, isPortrait);
            break;
        }
        case VIEW_MODE: {
            _normalImage = createImage(VIEW_MODE_NORMAL, isPortrait);
            _pressedImage = createImage(VIEW_MODE_PRESSED, isPortrait);
            break;
        }
        }
    }
}


code:
/**
 * Copyright (c) 2012-2013 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.amaze.ui;

import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.midlet.MIDlet;

import com.nokia.example.amaze.Main;

/**
 * A dialog for showing information about this application.
 */
public class InfoDialog {
    // Constants
    public static final String LINK_URL = "http://developer.nokia.com";
    private static final int TEXT_WIDTH = 240 - MazeCanvas.MARGIN * 2;

    // Members
    private Image _backgroundImage = null;
    private Font _infoTextFont = null;
    private TextWrapper _textWrapper = null;
    private MIDlet _midlet = null;
    private final String _infoText
        = "This is a Nokia example game application which demonstrates the use "
        + "of Mobile 3D Graphics framework with accelerometer sensor and "
        + "gestures. Guide the marble through the maze by tilting the phone "
        + "before the time runs out.\n\n"
        + "For more information, visit:";
    private int _linkTextY = 0;
    private int _linkTextHeight = 0;

    /**
     * Constructor.
     */
    public InfoDialog(MIDlet midlet) {
        _midlet = midlet;
        _backgroundImage = Main.makeImage("/graphics/dimmed-bg.png");

        _infoTextFont = Font.getFont(
            Font.getDefaultFont().getFace(),
            Font.getDefaultFont().getStyle(),
            Main.HAS_ONE_KEY_BACK ? Font.SIZE_SMALL : Font.SIZE_MEDIUM);

        _textWrapper = TextWrapper.instance(_infoTextFont, TEXT_WIDTH);
    }

    /**
     * Paints the info dialog.
     * @param graphics
     */
    public void paint(Graphics graphics) {
        final int topLeft = Graphics.TOP | Graphics.LEFT;

        graphics.drawImage(_backgroundImage, 0, 0, topLeft);
        graphics.setColor(MazeCanvas.TEXT_COLOR);

        int y = MazeCanvas.MARGIN + 20;

        final Font titleFont = Font.getFont(
            Font.getDefaultFont().getFace(),
            Font.STYLE_BOLD,
            Font.SIZE_LARGE);
        graphics.setFont(titleFont);
        graphics.drawString("aMaze " + _midlet.getAppProperty("MIDlet-Version"),
                            MazeCanvas.MARGIN, y, topLeft);

        graphics.setFont(_infoTextFont);
        _textWrapper.setWidth(TEXT_WIDTH);

        try {
            _textWrapper.setText(_infoText);
            y += MazeCanvas.MARGIN * 2 + 20;

            while (_textWrapper.hasMoreLines()) {
                graphics.drawString(_textWrapper.nextLine(),
                        MazeCanvas.MARGIN, y, topLeft);
                y += _textWrapper.lineHeight();
            }
        }
        catch (IllegalArgumentException e) {}

        _linkTextY = y + MazeCanvas.MARGIN;

        final Font linkFont = Font.getFont(
            _infoTextFont.getFace(),
            Font.STYLE_UNDERLINED,
            _infoTextFont.getSize());
        _linkTextHeight = linkFont.getHeight();
        graphics.setFont(linkFont);
        graphics.drawString(LINK_URL, MazeCanvas.MARGIN, _linkTextY, topLeft);
    }

    /**
     * @return The Y coordinate of the top of the link text.
     */
    public int linkTextY() {
        return _linkTextY;
    }

    /**
     * @return The height of the link text in pixels.
     */
    public int linkTextHeight() {
        return _linkTextHeight;
    }
}