Implementing the MIDlet functionality

FileSelector class

This class contains the bulk of the MIDlet. It creates the UI for navigating the device file system and selecting images to display. It also provides support for file management operations such as delete, rename, and directory creation.

FileSelector includes a list that is filled by the contents of the directory being currently explored. When invoked, FileSelector checks whether the images directory is available on the device and opens the file browser in that directory. Otherwise, FileSelector displays a list of available roots. The current directory is stored in the currentRoot variable. A null value indicates the roots set. When the user opens a directory, its contents are searched for subdirectories and for BMP, JPG, and PNG image files, which are displayed as the directory's contents.

FileSelector uses the FileConnection API for all file operations.

To detect IOExceptions and SecurityExceptions, file operations are enclosed in try-catch blocks.

To create this class:

  1. Create the FileSelector.java class file.

  2. Import the required packages.

    import java.io.*;
    import java.util.*;
    import javax.microedition.io.*;
    import javax.microedition.io.file.*;
    import javax.microedition.lcdui.*;
  3. Set the FileSelector class to extend the List class and to implement the CommandListener and FileSystemListener interfaces. Create the Commands used later to manage the device file system.

    class FileSelector
            extends List
            implements CommandListener, FileSystemListener {
    
        private final static Image ROOT_IMAGE =
                ImageViewerMIDlet.makeImage("/root_1.png");
        private final static Image FOLDER_IMAGE =
                ImageViewerMIDlet.makeImage("/folder1.png");
        private final static Image FILE_IMAGE =
                ImageViewerMIDlet.makeImage("/file1.png");
        private final OperationsQueue queue = new OperationsQueue();
        private final static String FILE_SEPARATOR =
                (System.getProperty("file.separator") != null) ? System.getProperty("file.separator") : "/";
        private final static String UPPER_DIR = "..";
        private final ImageViewerMIDlet midlet;
        private final Command openCommand =
                new Command("Open", Command.ITEM, 1);
        private final Command createDirCommand =
                new Command("Create new directory", Command.ITEM, 2);
        private final Command deleteCommand =
                new Command("Delete", Command.ITEM, 3);
        private final Command renameCommand =
                new Command("Rename", Command.ITEM, 4);
        private final Command exitCommand =
                new Command("Exit", Command.EXIT, 1);
        private final static int RENAME_OP = 0;
        private final static int MKDIR_OP = 1;
        private final static int INIT_OP = 2;
        private final static int OPEN_OP = 3;
        private final static int DELETE_OP = 4;
    
  4. The current directory is stored in the currentRoot variable. A null value indicates the roots set. Here, the class also registers itself in the FileSystemRegistry to listen to new file systems being added or removed, in which case it will restart itself.

        private Vector rootsList = new Vector();
        // Stores the current root, if null we are showing all the roots
        private FileConnection currentRoot = null;
        private Ticker ticker = new Ticker("Image Viewer");
    
        FileSelector(ImageViewerMIDlet midlet) {
            super("Image Viewer", List.IMPLICIT);
            setTicker(ticker);
            this.midlet = midlet;
            addCommand(openCommand);
            addCommand(createDirCommand);
            addCommand(deleteCommand);
            addCommand(renameCommand);
            addCommand(exitCommand);
            setSelectCommand(openCommand);
            setCommandListener(this);
        }
    
        void initialize() {
            queue.enqueueOperation(new ImageViewerOperations(INIT_OP));
            FileSystemRegistry.addFileSystemListener(FileSelector.this);
        }
    
        void stop() {
            queue.abort();
            FileSystemRegistry.removeFileSystemListener(this);
        }
    
        void inputReceived(String input, int code) {
            switch (code) {
                case RENAME_OP:
                    queue.enqueueOperation(new ImageViewerOperations(
                            input,
                            RENAME_OP));
                    break;
                case MKDIR_OP:
                    queue.enqueueOperation(new ImageViewerOperations(
                            input,
                            MKDIR_OP));
                    break;
            }
        }
    
        public void commandAction(Command c, Displayable d) {
            if (c == openCommand) {
                queue.enqueueOperation(new ImageViewerOperations(OPEN_OP));
            } else if (c == renameCommand) {
                queue.enqueueOperation(new ImageViewerOperations(RENAME_OP));
            } else if (c == deleteCommand) {
                queue.enqueueOperation(new ImageViewerOperations(DELETE_OP));
            } else if (c == createDirCommand) {
                queue.enqueueOperation(new ImageViewerOperations(MKDIR_OP));
            } else if (c == exitCommand) {
                midlet.fileSelectorExit();
            }
        }
    
    
        // Listen for changes in the roots
        public void rootChanged(int state, String rootName) {
            queue.enqueueOperation(new ImageViewerOperations(INIT_OP));
        }
  5. Use the displayAllRoots method to display a list of all available roots.

        private void displayAllRoots() {
            ticker.setString("Image Viewer - [Roots]");
            deleteAll();
            Enumeration roots = rootsList.elements();
            while (roots.hasMoreElements()) {
                String root = (String) roots.nextElement();
                root = root.replace('/', FILE_SEPARATOR.charAt(0));
                append(root.substring(1), ROOT_IMAGE);
            }
            currentRoot = null;
        }
  6. Use the createNewDir method to create a new directory under the current directory. The FileSystemRegistry class provides the listRoots method, which returns an enumeration of the roots in the device file system. This includes both logical and virtual roots.

        private void createNewDir() {
            if (currentRoot == null) {
                midlet.showMsg("Is not possible to create a new root");
            } else {
                midlet.requestInput("New dir name", "", MKDIR_OP);
            }
        }
    
        private void createNewDir(String newDirURL) {
            if (currentRoot != null) {
                try {
                    FileConnection newDir =
                            (FileConnection) Connector.open(
                            currentRoot.getURL() + newDirURL,
                            Connector.WRITE);
                    newDir.mkdir();
                } catch (IOException e) {
                    midlet.showError(e);
                }
                displayCurrentRoot();
            }
        }
    
        private void loadRoots() {
            if (!rootsList.isEmpty()) {
                rootsList.removeAllElements();
            }
            try {
                Enumeration roots = FileSystemRegistry.listRoots();
                while (roots.hasMoreElements()) {
                    rootsList.addElement("/" +
                            (String) roots.nextElement());
                }
            } catch (Throwable e) {
                midlet.showMsg(e.getMessage());
            }
    
        }
  7. Use the deleteCurrent method to delete the currently selected file.

        private void deleteCurrent() {
            if (currentRoot == null) {
                midlet.showMsg("Is not possible to delete a root");
            } else {
                int selectedIndex = getSelectedIndex();
                if (selectedIndex >= 0) {
                    String selectedFile = getString(selectedIndex);
                    if (selectedFile.equals(UPPER_DIR)) {
                        midlet.showMsg("Is not possible to delete an upper dir");
                    } else {
                        try {
                            String tmp = selectedFile.replace(FILE_SEPARATOR.charAt(0), '/');
                            FileConnection fileToDelete =
                                    (FileConnection) Connector.open(
                                    currentRoot.getURL() + tmp,
                                    Connector.READ_WRITE);
                            if (!fileToDelete.exists()) {
                                midlet.showMsg("File " + fileToDelete.getName() + " does not exists");
                            } else {
                                if (getConfirmation("Do you really want to delete " + tmp + "?")) {
                                    fileToDelete.delete();
                                    midlet.showMsg(tmp + " deleted.");
                                } else {
                                    midlet.showMsg("Operation cancelled.");
                                }
                            }
                        } catch (IOException e) {
                            midlet.showError(e);
                        } catch (SecurityException e) {
                            midlet.showError(e);
                        }
                        displayCurrentRoot();
                    }
                }
            }
        }
    
        boolean getConfirmation(String message) {
            Object lock = new Object();
            Confirm prompt = new Confirm(message, lock, this);
            synchronized (lock) {
                try {
                    lock.wait();
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
            }
            // set current displayable, when notification arrives
            Display.getDisplay(midlet).setCurrent(this);
            return prompt.getResponse();
        }
  8. Use the renameCurrent method to change the name of the currently selected file.

        private void renameCurrent() {
            if (currentRoot == null) {
                midlet.showMsg("Is not possible to rename a root");
            } else {
                int selectedIndex = getSelectedIndex();
                if (selectedIndex >= 0) {
                    String selectedFile = getString(selectedIndex);
                    if (selectedFile.equals(UPPER_DIR)) {
                        midlet.showMsg("Is not possible to rename the upper dir");
                    } else {
                        midlet.requestInput("New name", selectedFile, RENAME_OP);
                    }
                }
            }
        }
    
        private void renameCurrent(String newName) {
            if (currentRoot == null) {
                midlet.showMsg("Is not possible to rename a root");
            } else {
                int selectedIndex = getSelectedIndex();
                if (selectedIndex >= 0) {
                    String selectedFile = getString(selectedIndex);
                    if (selectedFile.equals(UPPER_DIR)) {
                        midlet.showMsg("Is not possible to rename the upper dir");
                    } else {
                        try {
                            String tmp = selectedFile.replace(FILE_SEPARATOR.charAt(0), '/');
                            FileConnection fileToRename =
                                    (FileConnection) Connector.open(
                                    currentRoot.getURL() + tmp,
                                    Connector.READ_WRITE);
                            if (fileToRename.exists()) {
                                newName = newName.replace(FILE_SEPARATOR.charAt(0), '/');
                                fileToRename.rename(newName);
                            } else {
                                midlet.showMsg("File " + fileToRename.getName() + " does not exists");
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                            midlet.showError(e);
                        } catch (SecurityException e) {
                            e.printStackTrace();
                            midlet.showError(e);
                        }
                        displayCurrentRoot();
                    }
                }
            }
        }
  9. Use the openSelected method to update the list of files displayed on the screen. The setFileConnection method resets the FileConnection object to point to another file or directory, thus allowing the reuse of the FileConnection object for directory traversal.

        private void openSelected() {
    
            int selectedIndex = getSelectedIndex();
            if (selectedIndex >= 0) {
                String selectedFile = getString(selectedIndex);
                if (selectedFile.endsWith(FILE_SEPARATOR)) {
                    try {
                        String tmp = selectedFile.replace(FILE_SEPARATOR.charAt(0), '/');
                        if (currentRoot == null) {
                            currentRoot = (FileConnection) Connector.open(
                                    "file:///" + tmp, Connector.READ);
                        } else {
                            currentRoot.setFileConnection(tmp);
                        }
                        displayCurrentRoot();
                    } catch (IOException e) {
                        midlet.showError(e);
                    } catch (SecurityException e) {
                        midlet.showError(e);
                    }
                } else if (selectedFile.equals(UPPER_DIR)) {
                    if (rootsList.contains(currentRoot.getPath() + currentRoot.getName())) {
                        displayAllRoots();
                    } else {
                        try {
                            currentRoot.setFileConnection(UPPER_DIR);
                            displayCurrentRoot();
                        } catch (IOException e) {
                            midlet.showError(e);
                        }
                    }
                } else {
                    String url = currentRoot.getURL() + selectedFile;
                    midlet.displayImage(url);
                }
            }
        }
  10. Use the displayCurrentRoot method to read the contents of the current directory and display the contents on the screen.

        private void displayCurrentRoot() {
            try {
                ticker.setString("Image Viewer - [" + currentRoot.getURL() + "]");
                // open the root
                deleteAll();
                append(UPPER_DIR, FOLDER_IMAGE);
                // list all dirs
                Enumeration listOfDirs = currentRoot.list("*", false);
                while (listOfDirs.hasMoreElements()) {
                    String currentDir = (String) listOfDirs.nextElement();
                    if (currentDir.endsWith("/")) {
                        String tmp = currentDir.replace('/', FILE_SEPARATOR.charAt(0));
                        append(tmp, FOLDER_IMAGE);                    // always display the platform specific seperator to the user
    
                    }
                }
                // list all png files and dont show hidden files
                Enumeration listOfFiles = currentRoot.list("*.png", false);
                while (listOfFiles.hasMoreElements()) {
                    String currentFile = (String) listOfFiles.nextElement();
                    if (currentFile.endsWith(FILE_SEPARATOR)) {
                        append(currentFile, FOLDER_IMAGE);
                    } else {
                        append(currentFile, FILE_IMAGE);
                    }
                }
                listOfFiles = currentRoot.list("*.jpg", false);
                while (listOfFiles.hasMoreElements()) {
                    String currentFile = (String) listOfFiles.nextElement();
                    if (currentFile.endsWith(FILE_SEPARATOR)) {
                        append(currentFile, FOLDER_IMAGE);
                    } else {
                        append(currentFile, FILE_IMAGE);
                    }
                }
                listOfFiles = currentRoot.list("*.bmp", false);
                while (listOfFiles.hasMoreElements()) {
                    String currentFile = (String) listOfFiles.nextElement();
                    if (currentFile.endsWith(FILE_SEPARATOR)) {
                        append(currentFile, FOLDER_IMAGE);
                    } else {
                        append(currentFile, FILE_IMAGE);
                    }
                }
                //Making the top item visible.
                setSelectedIndex(0, true);
            } catch (IOException e) {
                midlet.showError(e);
            } catch (SecurityException e) {
                midlet.showError(e);
            }
        }
  11. Set up a new class that implements the Operation interface. In most cases, this class is used to invoke the private methods of FileSelector.

        private class ImageViewerOperations implements Operation {
    
            private final String parameter;
            private final int operationCode;
    
            ImageViewerOperations(int operationCode) {
                this.parameter = null;
                this.operationCode = operationCode;
            }
    
            ImageViewerOperations(String parameter, int operationCode) {
                this.parameter = parameter;
                this.operationCode = operationCode;
            }
    
            public void execute() {
                switch (operationCode) {
                    case INIT_OP:
                        String initDir = System.getProperty("fileconn.dir.photos");
                        loadRoots();
                        if (initDir != null) {
                            try {
                                currentRoot =
                                        (FileConnection) Connector.open(
                                        initDir,
                                        Connector.READ);
                                displayCurrentRoot();
                            } catch (Exception e) {
                                midlet.showError(e);
                                displayAllRoots();
                            }
                        } else {
                            displayAllRoots();
                        }
                        break;
                    case OPEN_OP:
                        openSelected();
                        break;
                    case DELETE_OP:
                        deleteCurrent();
                        break;
                    case RENAME_OP:
                        if (parameter != null) {
                            renameCurrent(parameter);
                        } else {
                            renameCurrent();
                        }
                        break;
                    case MKDIR_OP:
                        if (parameter != null) {
                            createNewDir(parameter);
                        } else {
                            createNewDir();
                        }
                }
            }
        }
  12. Set up a new class that extends the Alert class and implements the CommandListener interface. This class is used by the getConfirmation method, which in turn is used by the deleteCurrent method to verify that the user really wants to delete the selected file or directory.

        private class Confirm extends Alert implements CommandListener {
    
            private Command okCommand = new Command("Yes", Command.OK, 0);
            private Command nokCommand = new Command("No", Command.EXIT, 0);
            private boolean isOkResponse;
            private Object waitLock;
    
            Confirm(String message, Object lock, Displayable next) {
                super("Image Viewer", message, midlet.getLogo(), AlertType.CONFIRMATION);
                addCommand(okCommand);
                addCommand(nokCommand);
                setCommandListener(this);
                waitLock = lock;
                Display.getDisplay(midlet).setCurrent(this, next);
            }
    
            public void commandAction(Command command, Displayable display) {
                if (command == okCommand) {
                    isOkResponse = true;
                } else if (command == nokCommand) {
                    isOkResponse = false;
                }
                synchronized (waitLock) {
                    waitLock.notifyAll();
                }
            }
    
            boolean getResponse() {
                return isOkResponse;
            }
        }
    }

OperationsQueue class

This is a simple utility class that runs in a separate thread executing commands serially. OperationsQueue takes objects implementing the Operation interface, making it independent of the kinds of commands executed. Operation implementers are expected to handle their exceptions locally, since OperationsQueue discards any exceptions thrown.

To create this class:

  1. Create the OperationsQueue.java class file.

  2. Import the required classes.

    import java.util.*;
    
  3. Implement the OperationsQueue class. Use the run method to execute the commands in the queue.

    class OperationsQueue implements Runnable
    {
    
      private volatile boolean running = true;
      private final Vector operations = new Vector();
    
    
      OperationsQueue()
      {
        // Notice that all operations will be done in another
        // thread to avoid deadlocks with GUI thread
        new Thread(this).start();
      }
    
    
      void enqueueOperation(Operation nextOperation)
      {
        operations.addElement(nextOperation);
        synchronized (this)
        {
          notify();
        }
      }
    
    
      // stop the thread
      void abort()
      {
        running = false;
        synchronized (this)
        {
          notify();
        }
      }
    
    
      public void run()
      {
        while (running)
        {
          while (operations.size() > 0)
          {
            try
            {
              // execute the first operation on the queue
              ((Operation) operations.firstElement()).execute();
            }
            catch (Exception e)
            {
              // Nothing to do. It is expected that each operations handle
              // their own locally exception but this block is to ensure
              // that the queue continues to operate
            }
            operations.removeElementAt(0);
          }
          synchronized (this)
          {
            try
            {
              wait();
            }
            catch (InterruptedException e)
            {
              // it doesn't matter
            }
          }
        }
      }
    
    }

Operation interface

This class defines the Operation interface used by OperationsQueue.

To create this interface:

  1. Create the Operation.java class file.

  2. Define the interface.

    interface Operation
    {
      // Implement here the operation to be executed
      void execute();
    }

Now that you have implemented the MIDlet functionality, implement the UI classes.