Monday, May 25, 2009

How To : Create the single ".ALX" file from the multiple ".ALX" files

1 comments

How To <files >
file1.cod
file2.cod
file3.cod
</files>

The above code is used to add more files into the project.

If you want to import more applications using single .alx file, then you need to include separate application tag for each and every individual application/project you need to import.

For example :

<application id="myApp"><name ></name><description > myApp </description><version >1.0</version><vendor >Research In Motion Limited</vendor><copyright >Copyright (c) 2009 Research In Motion Limited</copyright><fileset Java="1.38"><directory ></directory><files > myApp.cod</files></fileset></application>

You need to add a new <application> tag entry for each of the applications you need to import.

Monday, May 11, 2009

Code : Convert a Bitmap to a PNG and then return a byte array

3 comments

Code Convert a Bitmap to a PNG and then return a byte array of the resulting PNG, which should be generally useful in many cases.

Code:

import java.io.ByteArrayOutputStream;
import java.io.IOException;

import net.rim.device.api.compress.ZLibOutputStream;
import net.rim.device.api.system.Bitmap;
import net.rim.device.api.util.CRC32;

/**
* PNGEncoder takes a Java Image object and creates a byte string which can be saved as a PNG file.
* The Image is presumed to use the DirectColorModel.
*
*/

public class PNGEncoder {

    /** Constant specifying that alpha channel should be encoded. */
    public static final boolean ENCODE_ALPHA = true;

    /** Constant specifying that alpha channel should not be encoded. */
    public static final boolean NO_ALPHA = false;

    /** Constants for filter (NONE) */
    public static final int FILTER_NONE = 0;

    /** Constants for filter (SUB) */
    public static final int FILTER_SUB = 1;

    /** Constants for filter (UP) */
    public static final int FILTER_UP = 2;

    /** Constants for filter (LAST) */
    public static final int FILTER_LAST = 2;
    /** IHDR tag. */
    protected static final byte IHDR[] = {73, 72, 68, 82};
    /** IDAT tag. */
    protected static final byte IDAT[] = {73, 68, 65, 84};
    /** IEND tag. */
    protected static final byte IEND[] = {73, 69, 78, 68};

    /** The png bytes. */
    protected byte[] pngBytes;

    /** The prior row. */
    protected byte[] priorRow;

    /** The left bytes. */
    protected byte[] leftBytes;

    /** The image. */
    protected Bitmap image;

    /** The width. */
    protected int width, height;

    /** The byte position. */
    protected int bytePos, maxPos;

    /** The CRC value. */
    protected int crcValue;

    /** Encode alpha? */
    protected boolean encodeAlpha;

    /** The filter type. */
    protected int filter;

    /** The bytes-per-pixel. */
    protected int bytesPerPixel;

    /** The compression level. */
    protected int compressionLevel;

    /**
     * Class constructor
     */
    public PNGEncoder() {
        this(null, false, FILTER_NONE, 0);
    }

    /**
     * Class constructor specifying Image to encode, with no alpha channel encoding.
     *
     * @param image A Java Image object which uses the DirectColorModel
     */
    public PNGEncoder(Bitmap image) {
        this(image, false, FILTER_NONE, 0);
    }

    /**
     * Class constructor specifying Image to encode, and whether to encode alpha.
     *
     * @param image A Java Image object which uses the DirectColorModel
     * @param encodeAlpha Encode the alpha channel? false=no; true=yes
     */
    public PNGEncoder(Bitmap image, boolean encodeAlpha) {
        this(image, encodeAlpha, FILTER_NONE, 0);
    }

    /**
     * Class constructor specifying Image to encode, whether to encode alpha, and filter to use.
     *
     * @param image A Java Image object which uses the DirectColorModel
     * @param encodeAlpha Encode the alpha channel? false=no; true=yes
     * @param whichFilter 0=none, 1=sub, 2=up
     */
    public PNGEncoder(Bitmap image, boolean encodeAlpha, int whichFilter) {
        this(image, encodeAlpha, whichFilter, 0);
    }

    /**
     * Class constructor specifying Image source to encode, whether to encode alpha, filter to use,
     * and compression level.
     *
     * @param image A Java Image object
     * @param encodeAlpha Encode the alpha channel? false=no; true=yes
     * @param whichFilter 0=none, 1=sub, 2=up
     * @param compLevel 0..9
     */
    public PNGEncoder(Bitmap image, boolean encodeAlpha, int whichFilter, int compLevel) {
        this.image = image;
        this.encodeAlpha = encodeAlpha;
        setFilter(whichFilter);
        if (compLevel >= 0 && compLevel <= 9) {
            this.compressionLevel = compLevel;
        }
    }

    /**
     * Set the image to be encoded
     *
     * @param image A Java Image object which uses the DirectColorModel
     */
    public void setImage(Bitmap image) {
        this.image = image;
        pngBytes = null;
    }

    /**
     * Creates an array of bytes that is the PNG equivalent of the current image, specifying
     * whether to encode alpha or not.
     *
     * @param encodeAlpha boolean false=no alpha, true=encode alpha
     * @return an array of bytes, or null if there was a problem
     */
    public byte[] encode(boolean encodeAlpha) throws IOException {
        byte[]  pngIdBytes = {-119, 80, 78, 71, 13, 10, 26, 10};

        if (image == null) {
            return null;
        }
        width = image.getWidth();
        height = image.getHeight();

        /*
         * start with an array that is big enough to hold all the pixels
         * (plus filter bytes), and an extra 200 bytes for header info
         */
        pngBytes = new byte[((width + 1) * height * 3) + 200];

        /*
         * keep track of largest byte written to the array
         */
        maxPos = 0;

        bytePos = writeBytes(pngIdBytes, 0);
        writeHeader();

        if (writeImageData()) {
            writeEnd();
            pngBytes = resizeByteArray(pngBytes, maxPos);
        }
        else {
            pngBytes = null;
        }
        return pngBytes;
    }

    /**
     * Creates an array of bytes that is the PNG equivalent of the current image.
     * Alpha encoding is determined by its setting in the constructor.
     *
     * @return an array of bytes, or null if there was a problem
     */
    public byte[] encode() throws IOException {
        return encode(encodeAlpha);
    }

    /**
     * Set the alpha encoding on or off.
     *
     * @param encodeAlpha  false=no, true=yes
     */
    public void setEncodeAlpha(boolean encodeAlpha) {
        this.encodeAlpha = encodeAlpha;
    }

    /**
     * Retrieve alpha encoding status.
     *
     * @return boolean false=no, true=yes
     */
    public boolean getEncodeAlpha() {
        return encodeAlpha;
    }

    /**
     * Set the filter to use
     *
     * @param whichFilter from constant list
     */
    public void setFilter(int whichFilter) {
        this.filter = FILTER_NONE;
        if (whichFilter <= FILTER_LAST) {
            this.filter = whichFilter;
        }
    }

    /**
     * Retrieve filtering scheme
     *
     * @return int (see constant list)
     */
    public int getFilter() {
        return filter;
    }

    /**
     * Set the compression level to use
     *
     * @param level 0 through 9
     */
    public void setCompressionLevel(int level) {
        if (level >= 0 && level <= 9) {
            this.compressionLevel = level;
        }
    }

    /**
     * Retrieve compression level
     *
     * @return int in range 0-9
     */
    public int getCompressionLevel() {
        return compressionLevel;
    }

    /**
     * Increase or decrease the length of a byte array.
     *
     * @param array The original array.
     * @param newLength The length you wish the new array to have.
     * @return Array of newly desired length. If shorter than the
     *         original, the trailing elements are truncated.
     */
    protected byte[] resizeByteArray(byte[] array, int newLength) {
        byte[]  newArray = new byte[newLength];
        int     oldLength = array.length;

        System.arraycopy(array, 0, newArray, 0, Math.min(oldLength, newLength));
        return newArray;
    }

    /**
     * Write an array of bytes into the pngBytes array.
     * Note: This routine has the side effect of updating
     * maxPos, the largest element written in the array.
     * The array is resized by 1000 bytes or the length
     * of the data to be written, whichever is larger.
     *
     * @param data The data to be written into pngBytes.
     * @param offset The starting point to write to.
     * @return The next place to be written to in the pngBytes array.
     */
    protected int writeBytes(byte[] data, int offset) {
        maxPos = Math.max(maxPos, offset + data.length);
        if (data.length + offset > pngBytes.length) {
            pngBytes = resizeByteArray(pngBytes, pngBytes.length + Math.max(1000, data.length));
        }
        System.arraycopy(data, 0, pngBytes, offset, data.length);
        return offset + data.length;
    }

    /**
     * Write an array of bytes into the pngBytes array, specifying number of bytes to write.
     * Note: This routine has the side effect of updating
     * maxPos, the largest element written in the array.
     * The array is resized by 1000 bytes or the length
     * of the data to be written, whichever is larger.
     *
     * @param data The data to be written into pngBytes.
     * @param nBytes The number of bytes to be written.
     * @param offset The starting point to write to.
     * @return The next place to be written to in the pngBytes array.
     */
    protected int writeBytes(byte[] data, int nBytes, int offset) {
        maxPos = Math.max(maxPos, offset + nBytes);
        if (nBytes + offset > pngBytes.length) {
            pngBytes = resizeByteArray(pngBytes, pngBytes.length + Math.max(1000, nBytes));
        }
        System.arraycopy(data, 0, pngBytes, offset, nBytes);
        return offset + nBytes;
    }

    /**
     * Write a two-byte integer into the pngBytes array at a given position.
     *
     * @param n The integer to be written into pngBytes.
     * @param offset The starting point to write to.
     * @return The next place to be written to in the pngBytes array.
     */
    protected int writeInt2(int n, int offset) {
        byte[] temp = {(byte) ((n >> 8) & 0xff), (byte) (n & 0xff)};
        return writeBytes(temp, offset);
    }

    /**
     * Write a four-byte integer into the pngBytes array at a given position.
     *
     * @param n The integer to be written into pngBytes.
     * @param offset The starting point to write to.
     * @return The next place to be written to in the pngBytes array.
     */
    protected int writeInt4(int n, int offset) {
        byte[] temp = {(byte) ((n >> 24) & 0xff),
                       (byte) ((n >> 16) & 0xff),
                       (byte) ((n >> 8) & 0xff),
                       (byte) (n & 0xff)};
        return writeBytes(temp, offset);
    }

    /**
     * Write a single byte into the pngBytes array at a given position.
     *
     * @param b The integer to be written into pngBytes.
     * @param offset The starting point to write to.
     * @return The next place to be written to in the pngBytes array.
     */
    protected int writeByte(int b, int offset) {
        byte[] temp = {(byte) b};
        return writeBytes(temp, offset);
    }

    /**
     * Write a PNG "IHDR" chunk into the pngBytes array.
     */
    protected void writeHeader() {
        int startPos;

        startPos = bytePos = writeInt4(13, bytePos);
        bytePos = writeBytes(IHDR, bytePos);
        width = image.getWidth();
        height = image.getHeight();
        bytePos = writeInt4(width, bytePos);
        bytePos = writeInt4(height, bytePos);
        bytePos = writeByte(8, bytePos); // bit depth
        bytePos = writeByte((encodeAlpha) ? 6 : 2, bytePos); // direct model
        bytePos = writeByte(0, bytePos); // compression method
        bytePos = writeByte(0, bytePos); // filter method
        bytePos = writeByte(0, bytePos); // no interlace
        crcValue = CRC32.update(CRC32.INITIAL_VALUE, pngBytes, startPos, bytePos - startPos);
        bytePos = writeInt4(crcValue, bytePos);
    }

    /**
     * Perform "sub" filtering on the given row.
     * Uses temporary array leftBytes to store the original values
     * of the previous pixels.  The array is 16 bytes long, which
     * will easily hold two-byte samples plus two-byte alpha.
     *
     * @param pixels The array holding the scan lines being built
     * @param startPos Starting position within pixels of bytes to be filtered.
     * @param width Width of a scanline in pixels.
     */
    protected void filterSub(byte[] pixels, int startPos, int width) {
        int i;
        int offset = bytesPerPixel;
        int actualStart = startPos + offset;
        int nBytes = width * bytesPerPixel;
        int leftInsert = offset;
        int leftExtract = 0;

        for (i = actualStart; i < startPos + nBytes; i++) {
            leftBytes[leftInsert] =  pixels[i];
            pixels[i] = (byte) ((pixels[i] - leftBytes[leftExtract]) % 256);
            leftInsert = (leftInsert + 1) % 0x0f;
            leftExtract = (leftExtract + 1) % 0x0f;
        }
    }

    /**
     * Perform "up" filtering on the given row.
     * Side effect: refills the prior row with current row
     *
     * @param pixels The array holding the scan lines being built
     * @param startPos Starting position within pixels of bytes to be filtered.
     * @param width Width of a scanline in pixels.
     */
    protected void filterUp(byte[] pixels, int startPos, int width) {
        int     i, nBytes;
        byte    currentByte;

        nBytes = width * bytesPerPixel;

        for (i = 0; i < nBytes; i++) {
            currentByte = pixels[startPos + i];
            pixels[startPos + i] = (byte) ((pixels[startPos  + i] - priorRow[i]) % 256);
            priorRow[i] = currentByte;
        }
    }

    /**
     * Write the image data into the pngBytes array.
     * This will write one or more PNG "IDAT" chunks. In order
     * to conserve memory, this method grabs as many rows as will
     * fit into 32K bytes, or the whole image; whichever is less.
     *
     *
     * @return true if no errors; false if error grabbing pixels
     */
    protected boolean writeImageData() throws IOException {
        int rowsLeft = height;  // number of rows remaining to write
        int startRow = 0;       // starting row to process this time through
        int nRows;              // how many rows to grab at a time

        byte[] scanLines;       // the scan lines to be compressed
        int scanPos;            // where we are in the scan lines
        int startPos;           // where this line's actual pixels start (used for filtering)

        byte[] compressedLines; // the resultant compressed lines
        int nCompressed;        // how big is the compressed area?
        bytesPerPixel = (encodeAlpha) ? 4 : 3;

        ByteArrayOutputStream outBytes = new ByteArrayOutputStream(1024);

        ZLibOutputStream compBytes = new ZLibOutputStream(outBytes);

        while (rowsLeft > 0) {
            nRows = Math.min(32767 / (width * (bytesPerPixel + 1)), rowsLeft);
            nRows = Math.max( nRows, 1 );

            int[] pixels = new int[width * nRows];

            image.getARGB(pixels, 0, width, 0, startRow, width, nRows);
            /*
             * Create a data chunk. scanLines adds "nRows" for
             * the filter bytes.
             */
            scanLines = new byte[width * nRows * bytesPerPixel + nRows];

            if (filter == FILTER_SUB) {
                leftBytes = new byte[16];
            }
            if (filter == FILTER_UP) {
                priorRow = new byte[width * bytesPerPixel];
            }

            scanPos = 0;
            startPos = 1;
            for (int i = 0; i < width * nRows; i++) {
                if (i % width == 0) {
                    scanLines[scanPos++] = (byte) filter;
                    startPos = scanPos;
                }
                scanLines[scanPos++] = (byte) ((pixels[i] >> 16) & 0xff);
                scanLines[scanPos++] = (byte) ((pixels[i] >>  8) & 0xff);
                scanLines[scanPos++] = (byte) ((pixels[i]) & 0xff);
                if (encodeAlpha) {
                    scanLines[scanPos++] = (byte) ((pixels[i] >> 24) & 0xff);
                }
                if ((i % width == width - 1) && (filter != FILTER_NONE)) {
                    if (filter == FILTER_SUB) {
                        filterSub(scanLines, startPos, width);
                    }
                    if (filter == FILTER_UP) {
                        filterUp(scanLines, startPos, width);
                    }
                }
            }

            /*
             * Write these lines to the output area
             */
             compBytes.write(scanLines, 0, scanPos);

            startRow += nRows;
            rowsLeft -= nRows;
        }
        compBytes.close();

        /*
         * Write the compressed bytes
         */
        compressedLines = outBytes.toByteArray();
        nCompressed = compressedLines.length;

        bytePos = writeInt4(nCompressed, bytePos);
        bytePos = writeBytes(IDAT, bytePos);
        crcValue = CRC32.update(CRC32.INITIAL_VALUE, IDAT);

        bytePos = writeBytes(compressedLines, nCompressed, bytePos);
        crcValue = CRC32.update(crcValue, compressedLines, 0, nCompressed);

        bytePos = writeInt4(crcValue, bytePos);
        return true;
    }

    /**
     * Write a PNG "IEND" chunk into the pngBytes array.
     */
    protected void writeEnd() {
        bytePos = writeInt4(0, bytePos);
        bytePos = writeBytes(IEND, bytePos);
        crcValue = CRC32.update(CRC32.INITIAL_VALUE, IEND);

        bytePos = writeInt4(crcValue, bytePos);
    }

}

Here’s how to Use it :

PNGEncoder encoder = new PNGEncoder(bitmap, true);
byte[] imageBytes = encoder.encode(true);

if you then want an EncodedImage do this:

EncodedImage fullImage = EncodedImage.createEncodedImage(imageBytes, 0, imageBytes.length);

Enjoy!

Saturday, May 9, 2009

Code : How to split String

0 comments

Code While programming it may requires to split the string into words, to accomplish this one can use ‘StringUtilities’ class like :

StringUtilities su = null;
String[] abc = su.stringToWords("Xyz XYZ xyz xyZ");

Output : abc[0] = “Xyz”, abc[1] = “XYZ”, abc[2] = “xyz”, abc[3] = “xyZ”.

Or you can use following helper method to do the same :

public static final String[] splitString(final String data,
     final char splitChar, final boolean allowEmpty) {
    Vector v = new Vector();
    int indexStart = 0;
    int indexEnd = data.indexOf(splitChar);
    if (indexEnd != -1) {
        while (indexEnd != -1) {
            String s = data.substring(indexStart, indexEnd);
            if (allowEmpty || s.length() > 0) {
                    v.addElement(s);
            }
            indexStart = indexEnd + 1;
            indexEnd = data.indexOf(splitChar, indexStart);
         }

         if (indexStart != data.length()) {
            // Add the rest of the string
            String s = data.substring(indexStart);
            if (allowEmpty || s.length() > 0) {
                 v.addElement(s);
            }
          }

    } else {
         if (allowEmpty || data.length() > 0) {
             v.addElement(data);
         }
    }

    String[] result = new String[v.size()];
    v.copyInto(result);
    return result;
}

It has the ability to skip any blank lines ( typically split on \n), so it also has the ability to remove any blank sequences it encounters.

Saturday, April 25, 2009

How To : Access device event log on Storm 4.7

1 comments

How To Your BlackBerry's Event Log displays your system's recently run events and processes. If you're experiencing a problem with your BlackBerry or having an issue with a specific application or service, information from the Event Log can be helpful for troubleshooting. And it can be a good BlackBerry hygiene to clear out the log, to keep your device running smoothly.

To delete your list of events, hit the BlackBerry MENU key while any event is highlighted and then click "Clear Log." A dialogue box will then pop up asking if you're sure you want to delete the log. Once you confirm the deletion, your log will be cleared.

To access your Event Log on BB Storm 4.7 simulator, Press and hold the "?123" key to lock the numeric keyboard, the press ", 5 , 5"     ie.(comma 5 comma 5)

To access your Event Log in your bb device, go to your Home Screen, hold down the ALT key and then type "LGLG."

Thursday, April 23, 2009

Code : Create your own VirtualKeyboard for BBStorm

4 comments

Problem :

Code using component pack 4.7.0.39 and the Storm simulator 2.11.0.72. I have created an EditField with the FILTER_REAL_NUMERIC flag (and no other flags) and when the field has focus the virtual keyboard pops up as expected but it is missing the number '1'. I didn't add any other filters to the field.

Missing number 1

I have another EditField using the FILTER_NUMERIC flag instead, and it works fine. It seems that for real numbers it just replaces the '1' with the '.' key and leaves no way to insert that number, and if i tilt the simulator sideways the full keyboard is shown and this one has all the numbers.

Solution :

Well I have finally resolved this by developing my own home grown virtual keyboard.  This should work until RIM fixes the bug with their virtual keyboard.  In case anyone else is having the same problem, I have provide the code for it here:

 

public class MyVirtualKeyboard extends VerticalFieldManager
{
    // 1---2---3
    // 4---5---6
    // 7---8---9
    // .---0---c
    private ButtonField[] numbers = new ButtonField[]
    {
        new ButtonField("1", ButtonField.CONSUME_CLICK),
        new ButtonField("2", ButtonField.CONSUME_CLICK),
        new ButtonField("3", ButtonField.CONSUME_CLICK),
        new ButtonField("4", ButtonField.CONSUME_CLICK),
        new ButtonField("5", ButtonField.CONSUME_CLICK),
        new ButtonField("6", ButtonField.CONSUME_CLICK),
        new ButtonField("7", ButtonField.CONSUME_CLICK),
        new ButtonField("8", ButtonField.CONSUME_CLICK),
        new ButtonField("9", ButtonField.CONSUME_CLICK),
        new ButtonField(".", ButtonField.CONSUME_CLICK),
        new ButtonField("0", ButtonField.CONSUME_CLICK),
        new ButtonField("c", ButtonField.CONSUME_CLICK),
    };
    private ButtonField close = new ButtonField("Close",
        ButtonField.CONSUME_CLICK);
    private FieldChangeListener listener = new FieldChangeListener()
    {
        public void fieldChanged(Field f, int ctx)
        {
            if (ctx == FieldChangeListener.PROGRAMMATIC)
                return;
            String val = ((ButtonField) f).getLabel();
            if (Character.isDigit(val.charAt(0)) || val.equals("."))
                caller.setCharacter(val);
            else if (val.equals("c"))
                caller.removeLastCharacter();
        }
    };
    private Caller caller;
    public MyVirtualKeyboard(Caller caller)
    {
        this.caller = caller;
        // 4 rows of buttons will be displayed
        int buttonIndex = 0;
        for (int i = 0; i < 4; i++)
        {
            HorizontalFieldManager row = new HorizontalFieldManager();
            for (int j = 0; j < 3; j++)
            {
                numbers[buttonIndex].setChangeListener(listener);
                row.add(numbers[buttonIndex++]);
            }
            add(row);
        }
        close.setChangeListener(new FieldChangeListener()
        {
            public void fieldChanged(Field field, int ctx)
            {
                MyVirtualKeyboard.this.caller.closeMe();
            }
        });
        add(close);
    }
}

Here is the interface I created callled 'Caller':

public interface Caller
{
    public void closeMe();
    public void setCharacter(String character);
    public void removeLastCharacter();
}

 

Here is how I invoke it (this code is in my MainScreen subclass, which is implementing FocusChangeListener and my call back interface Caller):

public void focusChanged(Field field, int ctx)    {
        currentlyEditing = null;
        if (field != checkAmt && field != taxAmt)
            return;
        if (ctx == FocusChangeListener.FOCUS_GAINED)
        {
            currentlyEditing = (BasicEditField) field;
            MyVirtualKeyboard keyboard = new MyVirtualKeyboard(this);
            popup = new PopupScreen(keyboard);
            getUiEngine().pushScreen(popup);
        }
    }

Here is the callback methods on the main screen class that take the input from the popup keyboard:

public void setCharacter(String character)
    {
        String currText = currentlyEditing.getText();
        currText += character;
        currentlyEditing.setText(currText);
    }
    public void removeLastCharacter()
    {
        String currText = currentlyEditing.getText();
        if (currText.length() == 0)
            return;
        currentlyEditing.setText(currText.substring(0, currText.length() - 1));
    }

  public void closeMe()
    {
        getUiEngine().popScreen(popup);
    }

Also, I have a menu item where if the user needs to display the keyboard, they can:

showKeyboard = new MenuItem("Show Keyboard", 3, 12)       {
            public void run()
            {
                MyVirtualKeyboard keyboard = new MyVirtualKeyboard(
                    MyScreen.this);
                popup = new PopupScreen(keyboard);
                getUiEngine().pushScreen(popup);
            }
        };

 

Enjoy!

Monday, April 20, 2009

What Is - Best practices for designing GPS apps on CDMA networks

1 comments

What Is The BlackBerry 8130 smartphone, BlackBerry 8330 smartphone and BlackBerry 8830 smartphone use the Qualcomm gpsOne chipset. This chip can operate in several modes, some of which are quite different than the modes supported by BlackBerry smartphones operating on the Global System for Mobile communications (GSM) network.

This article explains the modes supported by BlackBerry smartphones operating on the Code Division Multiple Access (CDMA) network and the best practices for writing location-based services applications for the Qualcomm gpsOne chip.

Modes

Depending on the criteria set by an application, a Qualcomm gpsOne chip can be in any of the following modes:

Standalone

  • MS (Mobile Station or Device) operates in standalone-only mode
  • No network or Position Determination Entity (PDE) for position-location-related activity
  • Data demodulation (if required) occurs without an ephemeris download from the PDE
  • Recommended for outdoor use only; requires a clear view of the sky
  • Time To First Fix (TTFF) can be affected by cloud cover and/or urban canyons

MS-based

  • MS-operation in MS-based mode
  • Only seed position is calculated using MS-assisted mode
  • Suitable for applications requiring frequent fixes
  • Operates outdoors and indoors with a partial view of the sky

MS-assisted

  • MS operates in MS-assisted/PDE-based position calculation mode only
  • No MS-based position location fixes for the active sessions
  • No ephemeris downloads from PDE and no data demodulation
  • Suitable for single fixes

Note: Applications that need frequent fixes should not operate in this mode because this may result in data charges from the wireless service provider

  • Operates anywhere with a network connection

There are three more modes that use a combination of MS-Based and MS-Assisted to achieve a location fix.

Speed optimal

  • MS-based position calculation is preferred
  • PDE-based fix/MS-assisted mode is done only if MS-based position calculation fix fails
  • Ephemeris download from PDE is preferred as compared with data demodulation

Accuracy optimal

  • PDE-based position calculation/MS-assisted mode is a preferred option MS-based position calculation only if PDE-based fix failsEphemeris download from PDE is preferred as compared with data demodulation

Data optimal

  • Standalone mode is the preferred option and minimum PDE/network access is allowed
  • Data demodulation is preferred as compared with ephemeris download from PDE
  • MS-assisted position fix if MS-based position calculation fails to generate a fix
  • Data optimal mode disables SA download

Finally, another mode that simply returns the location of the cellular transmission site in contact with the BlackBerry smartphone is known as CellSite. This mode is not part of Qualcomm gpsOne but is worth mentioning. Accuracy of this mode is obviously low: 400m to 2500m in urban areas and will vary by location. CellSite is recommended when accuracy is of least concern.

Java Specification Request (JSR) 179 and Qualcomm gpsOne

JSR179 (also know as Location application programming interface [API]) includes the necessary APIs to use location-based services capabilities of a BlackBerry smartphone. The most important class in JSR179 is the LocationProvider, which is used to obtain location data from the Global Positioning System (GPS) module. An instance is obtained by calling LocationProvider.getInstance(Criteria c). Depending on the Criteria object, the API returns a LocationProvider in one of the above-mentioned modes. It is important to understand what criteria results in which mode. The following table can be used as a reference to determine the mapping between different sets of criteria and Qualcomm gpsOne modes.

Set Horizontal Accuracy()

Set Vertical Accuracy()

Is Allowed To Cost()

set Preferred Power Consumption()

set Preferred Response Time()

Frequency of fix in set Location Listener()

Mapped to QOS > Accuracy threshold

meters

not allowed

not applicable

any

single/multiple

not required

not required

not allowed

medium, high or no requirement

any

single/multiple

not required

not required

allowed

medium, high or no requirement

any

single/multiple

Mapped to QOS > Accuracy threshold

meters

allowed

High

Mapped to QOS > performance

multiple

Mapped to QOS > Accuracy threshold

meters

allowed

medium or no requirement

Mapped to QOS > performance

multiple

Mapped to QOS > Accuracy threshold

meters

allowed

High

Mapped to QOS > performance

Single

Mapped to QOS > Accuracy threshold

meters

allowed

medium or no requirement

Mapped to QOS > performance

Single

Mapped to QOS > Accuracy threshold

meters

allowed

medium or no requirement

QOS == Zero

Single

not required

not required

allowed

low

any

any

Based on the preceding table, the following algorithm is used in the BlackBerry smartphone API implementation:

-if costAllowed = FALSE, mode is standalone
-else if costAllowed=TRUE,
       -if (Sprint or Bell), mode is MS-Based
       -if horizontalAccuracy = 0, mode is Data Optimal
       -if horizontalAccuracy > 0,
             -if multipled fixes requested,
                   -if Telus, mode is MS-based
                   -otherwise,
                         -if powerUsage = HIGH, mode is Speed Optimal;
                         -if powerUsage != HIGH, mode is MS-based
             -else if single fix requested,
                   -if powerUsage = HIGH, mode is Accuracy Optimal;
                  -if powerUsage != HIGH, mode is PDE Calculate
                   -if powerUsage = MEDIUM and preferredResponseTime = 0,
                         mode is AFLT
                   -if powerUsage = LOW mode is Cellsite

Note: Verizon does not support the Standalone mode. For BlackBerry Device Software 4.7 and later, Standalone mode is open to applications that are signed by Research In Motion.

The following is an example obtaining a LocationProvider instance in Standalone mode:

Criteria c = new Criteria();
c.setCostAllowed(false);
LocationProvider provider = LocationProvider.getInstance(c);

Once a LocationProvider instance is obtained, read the location information from the BlackBerry smartphone. There are two ways to read location information from the BlackBerry smartphone:

  1. Call LocationProvider.getLocation(int timeout). If this method is used, a single fix will be returned. The timeout parameter is specified in seconds. A value of -1 indicates that the implementation should use its default timeout value for this provider.

Location loc = provider.getLocation(100);

This method is strongly recommended when frequent fixes are not required.

  1. Implement a LocationListener. To obtain multiple fixes at fixed intervals, implement the LocationListener interface of JSR179. This interface has the following two methods:
    1. locationUpdated(LocationProvider provider,Location location)

Called by the LocationProvider that this listener is registered to. This method will be called periodically according to the interval defined when registering the listener to provide updates of the current location. The provider parameter is the source of the event. The location parameter is the Location object that contains location information of the new fix.

    1. providerStateChanged(LocationProvider provider, int newState)

Called by the LocationProvider that this listener is registered to, if the state of the LocationProvider has changed. The value of the newState parameter can be xLocationProvider.AVAILABLE, LocationProvider.OUT_OF_SERVICE or LocationProvider.TEMPORARILY_UNAVAILABLE.

After implementing a LocationListener , an instance must be created and registered with a LocationProvider object. To register a LocationListener instance, call LocationProvider.setLocationListener(LocationListener listener, int interval, int timeout, int maxAge). The parameters are defined as follows:

    • listener - The LocationListener instance to register
    • interval - The interval in seconds to obtain location data
    • timeout - The timeout value in seconds
    • maxAge - The maximum age of the returned location in seconds

-1 can be passed to use the default value for the interval, maxAge and timeout parameters. The following code demonstrates how to register a LocationListener with default values:

LocationListener locListener = new LocationListenerImpl();
provider.setLocationListener(locListener, -1, -1, -1);

Once a LocationListener is registered, the locationUpdated method should be called automatically at the defined interval.

Provider states

As stated in the preceding text, the system automatically updates the state of a LocationProvider by calling the providerStateChanged() method of the registered LocationListener. The following description indicates how these status codes are interpreted in the BlackBerry smartphone API implementation:

  • TEMPORARILY_UNAVAILABLE - The GPS chipset has stopped looking for a fix. LocationProvider will no longer provide a fix to the application.
  • OUT_OF_SERVICE - Indicates when there is an IT Policy sent to the BlackBerry smartphone to disable GPS.
  • AVAILABLE - Never sent to the application.

Setting up PDE information

CDMA wireless service providers usually provide a PDE server which helps the BlackBerry smartphone in obtaining and computing location information.

Note: All Qualcomm gpsOne modes, except StandAlone and Cellsite, require a connection to a PDE server. To enable a connection to a PDE server, the Internet Protocol (IP) address and the port of the server must be configured using GPSSettings.setPDEInfo(String ip, int port). The following is an example:

// You must replace these values with values provided by your carrier.
GPSSettings.setPDEInfo("127.0.0.1", 80);

Resetting the LocationProvider

A LocationProvider may stop returning fixes when network coverage deteriorates or the BlackBerry smartphone is in an environment that is not suitable for receiving GPS data (for example, indoors). In these scenarios, the GPS chip will go completely cold to preserve battery power and throw a TEMPORARILY_UNAVAILABLE event by calling providerStateChanged()of the registered LocationListener instance. If this event is triggered, an application can reset the provider to restore the GPS chip. The following code demonstrates how to reset a LocationProvider:

provider.setLocationListener(null, 0, 0, 0);
provider.reset();
provider = null;
provider = LocationProvider.getInstance(criteria);
provider.setLocationListener(new LocationListenerImpl(),interval, timeout, maxAge);

After a reset, the application can try to get a fix again; however, if the GPS chip fails to get a fix (if the network coverage or environment has not improved), it will again go cold and will stop getting fixes. It is expected that the application will decide when to reset the provider. It is strongly recommended not to reset the provider at an interval less than three minutes since it can take up to three minutes to get a fix from a cold start.

Note: In some BlackBerry Device Software versions, the TEMPORARILY_UNAVAILABLE state was not always triggered as appropriate. In these cases, it is a good practice to keep track of the timestamp of the last valid fix and to reset the provider if the last valid fix is more than three minutes old.

Keeping the GPS chip ‘hot’

The GPS chip is considered ‘hot’ when it has an active connection with the satellites. A fix can be obtained almost instantly when the chip is hot. To preserve battery power, the chip is configured to go cold if the application does not ask for fixes for some time. From a cold start it may take much longer to get a fix. For applications that require frequent fixes, it is recommended that the application queries for fixes every 10 seconds. Therefore, the LocationListener should be registered with an interval of less than or equal to 10 seconds.

Setting up Verizon credentials

Verizon requires third-party applications to have a valid Client ID and Password for their Location Proxy Server (LPS) to access the Location API. To obtain these credentials, contact Verizon. Verizon credentials can be set in an application by calling GPSSettings.setPDEInfo(String ip, int port) as follows:

GPSSettings.setPDEInfo(";"+clientID+";"+password, 0);

A sample application that implements all the recommendations of this article can be found here.

Tuesday, April 14, 2009

Support : A Field’s font is displayed incorrectly when set in the paint method

0 comments

Support Problem

The Field.setFont(Font font) method should be used to set or change the font used by any field in the BlackBerry API set. A font should not be set or changed within the Field’s paint method. The following code example will NOT work:

LabelField myField = new LabelField("Hi there.")
{
   public void paint(Graphics graphics)
   {
      graphics.setFont(graphics.getFont().derive(Font.BOLD));
      super.paint(graphics);
   }
}

Resolution

The Field’s setFont method should be used in the following way:

LabelField myfield = new LabelField("Hi there.")
myField.setFont(myFont);

This allows for the font used within a specific field to be set. To change a font for the entire application, the Font.setDefaultFont(Font font) method should be used.

Note: This does not mean that the Graphics.setFont method should not be used. If you are performing custom drawing actions using the Graphics class on a custom Screen or Field, the setFont method can be used to change the font that is being drawn. Graphics.setFont should not be used to change the font being drawn by existing fields contained within the BlackBerry API set. Under this circumstance, the Field.setFont method should be used.

Wednesday, April 8, 2009

How To : Change fonts in a BlackBerry application

5 comments

How To The BlackBerry device supports multiple fonts in various sizes. These fonts can be used in custom BlackBerry applications and can be changed during runtime. The number of fonts available on a device depends on the model and the BlackBerry Device Software version installed.

The following example illustrates how to use fonts in a custom BlackBerry application and change the font during runtime:

import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;
import net.rim.device.api.system.*;
public final class FontChange extends UiApplication
{
    private Font font;
    //Get all fonts available on the BlackBerry
    private FontFamily fontFamily[] = FontFamily.getFontFamilies();
    public static void main(String[] args)
    {
            FontChange theApp = new FontChange();
            theApp.enterEventDispatcher();
    }
    public FontChange()
    {
        MainScreen mainScreen;
        FontChangeListener fontChangeListener = new FontChangeListener();
        //Set the current font to the first in the font list.
        font = fontFamily[0].getFont(FontFamily.SCALABLE_FONT, 10);
        Font.setDefaultFont(font);
        mainScreen = new MainScreen();
        //Set the title of the mainscreen.
        LabelField title = new LabelField("Font Test Example",
            LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH);
        mainScreen.setTitle(title);
        ObjectChoiceField pickFont = new ObjectChoiceField("Select a font:",
            fontFamily, 0);
        pickFont.setChangeListener(null);
        pickFont.setChangeListener(fontChangeListener);
        mainScreen.add(pickFont);
        mainScreen.add(new SeparatorField());
        //RichTextField that displays text in the specified colour.
        RichTextField fontText = new RichTextField
            ("The quick red fox jumps over the lazy brown dog.")
        {
            public void paint(Graphics graphics)
            {
                //Change the colour of the text in the RichTextField to green.
                graphics.setColor(0x00008800);
                super.paint(graphics);
            }
        };
        mainScreen.add(fontText);
        //Display the main screen
        pushScreen(mainScreen);
    }
    //Create a custom FieldChangeListener that will change the current font
    //to what is selected in the ObjectChangeField.
    class FontChangeListener implements FieldChangeListener
    {
        public void fieldChanged(Field field, int context)
        {
            //If the ObjectCoiceField has changed
            if (field instanceof ObjectChoiceField)
            {
                //Get the new font selected in the ObjectChoiceField and set the
                //current font to match what is selected.
                ObjectChoiceField choiceField = (ObjectChoiceField)field;
                font = fontFamily[choiceField.getSelectedIndex()].getFont
                    (FontFamily.SCALABLE_FONT,10);
                Font.setDefaultFont(font);
            }
        }
    }
}

Friday, April 3, 2009

How To : Change the text color of a field

3 comments

How To You can change the color of text in a field for applications that are designed to run on a color BlackBerry device. When you define a field, you can choose a color that will not change during the life of the field. To allow a field's color to change dynamically, extend one of the existing fields, or create your own field.

Below are samples that utilize the RichTextField and change the color of the text in the field. The color in Example 1 below will stay the same for the life of the field. The code in Example 2 below allows the color to be programmatically set, or set to a random value.

Example 1

//RichTextField that displays text in the specified color.
RichTextField fontText = new RichTextField("This text will be green.")
{
    public void paint(Graphics graphics)
    {
        graphics.setColor(0x00008800);
        super.paint(graphics);
    }
};

Example 2

/**
* Custom Field - FontField
*
*/
import net.rim.device.api.ui.*;
import net.rim.device.api.system.*;
import net.rim.device.api.ui.component.*;
import java.util.*;
public class FontField extends RichTextField
{
    //Set the initial text color to black.
    private int color = 0x00000000;
    //Initialize the random number generator.
    private Random randColor = new Random();
    //Only one RichTextField contstructor in this sample for brevity.
    public FontField(String text)
    {
        super(text);
    }
    //Set the color of the text in this field.
    public void setColor(int newColor)
    {
        color = newColor;
    }
    //Get the current text color.
    public int getColor()
    {
        return color;
    }
    //Randomly generate a new text color.
    public void randomizeColor()
    {
        int r = Math.abs(randColor.nextInt()) % 255;
        int g = Math.abs(randColor.nextInt()) % 255;
        int b = Math.abs(randColor.nextInt()) % 255;
        color = (255<<24) | (r<<16) | (g<<8) | b;
    }
    //Set the foreground and then call paint method of the parent class.
    public void paint(Graphics graphics)
    {
        graphics.setColor(color);
        super.paint(graphics);
    }
}

Monday, March 30, 2009

How To : Add an ApplicationMenuItem to BlackBerry Maps

0 comments

How To The BlackBerry Maps application allows the BlackBerry device user to view maps and receive directions on a BlackBerry device. BlackBerry Maps can also provide a visual route and navigational instructions to a specified destination.

BlackBerry Maps works with BlackBerry Device Software 4.1 and later. BlackBerry Maps is included with BlackBerry Device Software 4.2 and later. For use with BlackBerry Device Software 4.1, BlackBerry Maps can be downloaded from www.blackberry.com/maps.

With third-party applications, an ApplicationMenuItem can be added to the menu of BlackBerry Maps and pass a MapView object into the application.

This article focuses on adding an ApplicationMenuItem to BlackBerry Maps. Please see Here for information on the way to add a custom menu item to other BlackBerry device applications.

An ApplicationDescriptor is required for adding an ApplicationMenuItem to BlackBerry Maps. The code for registering a menu item with the BlackBerry Maps application is as follows:

 

ApplicationMenuItemRepository amir =
    ApplicationMenuItemRepository.getInstance();
ApplicationDescriptor ad_startup =
    ApplicationDescriptor.currentApplicationDescriptor();
ApplicationDescriptor ad_gui =
    new ApplicationDescriptor(ad_startup, "gui", null);
amir.addMenuItem(ApplicationMenuItemRepository.MENUITEM_MAPS,
new MapMenuItem(),ad_gui);

The menu item MapMenuItem() is then registered with the BlackBerry Maps application (MENUITEM_MAPS) to invoke the current application with ad_gui as the ApplicationDescriptor. The MapMenuItem()method contains the actual code for specifying the menu item as shown below. MapMenuItem implements the abstract class ApplicationMenuItem and implements the run and toString methods. The toString method specifies the text to display on the menu item.

private static class MapMenuItem extends ApplicationMenuItem {
     MapMenuItem() {
        super(20);
     }
     public String toString() {
        return "My Menu Item";
     }
     public Object run(Object context) {
        mv = (MapView)context;
        if (mv != null) {
            System.out.println("Latitude = " + mv.getLatitude());
            System.out.println("Longitude = " + mv.getLongitude());
            System.out.println("Zoom = " + mv.getZoom());
        }
        else {
             throw new IllegalStateException("Context is null, expected a MapView instance");
        }
        return null;
     }
}

Friday, March 27, 2009

How to : Create a custom layout manager for a screen

3 comments

How To You can create your own custom layout manager by extending the net.rim.device.api.ui.Manager class and customizing it to fit your needs. Custom layout managers enable you to customize the placement of fields on the screen and customize navigation within these fields. To do this, you need to implement at least the following three methods:

  1. public int getPreferredWidth()

This method returns the manager's preferred width.

  1. public int getPreferredHeight()

This method returns the manager's preferred height.

  1. public void sublayout(int height, int width)

This method is invoked by the manager's layout() method and instructs each child field how and where to be laid out.

This can be accomplished by calling Manager.setPositionChild(Field field, int x, int y) and LayoutChild(Field field, int width, int height) for each field contained in the manager.

Here is an example of how a screen can associate itself with a custom layout manager:

class LayoutManager extends Manager {
   public LayoutManager() {
       //construct a manager with vertical scrolling
       super(Manager.VERTICAL_SCROLL);
   }
   //overwrite the nextFocus method for custom navigation
   protected int nextFocus(int direction, boolean alt) {
       //retrieve the index of the current field that is selected
       int index= this.getFieldWithFocusIndex();
       if(alt) {
           if(direction 0){...}
               else{...}
       }
      // if we did not handle it, let the manager's parent class
      if (index == this.getFieldWithFocusIndex())
          return super.nextFocus(direction, alt);
      else
          return index;
   }
   protected void sublayout(int width, int height) {
       Field field;
       //get total number of fields within this manager
       int numberOfFields = getFieldCount();
       int x = 0;
       int y = 0;
       for (int i = 0;i < numberOfFields;i++) {
           field = getField(i); //get the field
           setPositionChild(field,x,y); //set the position for the field
           layoutChild(field, width, height); //lay out the field
           y += ...;
           ...
       }
       setExtent(width, height);
   }
   public int getPreferredWidth() {
       return 160;
   }
   public int getPreferredHeight() {
       int height= 0;
       int numberOfFields= getFieldCount();
       for (int i= 0; i < numberOfFields; i++) {
           height+= getField(i).getPreferredHeight();
           return height;
       }
   }

the main class :

   ...
   RichTextField myField = new RichTextField("Hello");
   RichTextField myOtherField = new RichTextField("World");
   LayoutManager myManager = new LayoutManager();
   MainScreen myScreen = new MainScreen();
   myScreen.add(myManager);
   myManager.add(myField);
   myManager.add(myOtherField);
   ...