BlackBerry_Java_SDK-Development_Guide--1232721-0730084309-001-6.0-US

Page 1

BlackBerry Java SDK Data Storage Version: 6.0 Development Guide


Published: 2011-02-22 SWD-1232721-0222041208-001


Contents 1 Data storage overview...................................................................................................................................... Data storage features....................................................................................................................................... Considerations for choosing a data storage approach.............................................................................. Storage locations.............................................................................................................................................. Access to memory.....................................................................................................................................

4 4 5 5 5

2 Storing files in the file system........................................................................................................................... Code sample: Creating a folder......................................................................................................................... Code sample: Creating a file............................................................................................................................. Code sample: Writing text to a file................................................................................................................... Code sample: Reading sections of a binary file................................................................................................ Code sample: Displaying the path to the video folder using System.getProperty()......................................... Code sample: Retrieving a list of mounted roots.............................................................................................

7 7 8 9 9 11 11

3 Storing data in SQLite databases...................................................................................................................... Viewing SQLite databases................................................................................................................................. Simulate a media card............................................................................................................................... Security of SQLite databases............................................................................................................................. Code sample: Creating an encrypted SQLite database.............................................................................. Performance of SQLite databases..................................................................................................................... Best practice: Optimizing SQLite database performance.......................................................................... Creating and deleting SQLite databases........................................................................................................... SQLite database files................................................................................................................................. Character encoding.................................................................................................................................... Create an SQLite database........................................................................................................................ Code sample: Creating an SQLite database............................................................................................... Code sample: Adding a schema to an SQLite database............................................................................. Code sample: Deleting an SQLite database............................................................................................... Working with SQLite databases........................................................................................................................ Using transactions..................................................................................................................................... Using SQL parameters............................................................................................................................... Using foreign key constraints.................................................................................................................... Code sample: Inserting table data............................................................................................................. Code sample: Retrieving table data........................................................................................................... Code sample: Deleting table data.............................................................................................................. Code sample: Updating table data............................................................................................................ Code sample: Listing database tables........................................................................................................

13 13 13 13 15 16 16 17 18 18 18 20 21 22 23 23 24 28 28 29 30 32 33


SQLite sample application................................................................................................................................ Overview.................................................................................................................................................... Files in the sample application.................................................................................................................. Featured interfaces................................................................................................................................... Featured classes........................................................................................................................................ Install the sample application.................................................................................................................... Run the sample application.......................................................................................................................

34 34 34 35 36 36 36

4 Storing objects persistently.............................................................................................................................. Security of persistent objects........................................................................................................................... Restricting access to persistent objects..................................................................................................... Performance of the persistent store................................................................................................................ Best practice: Using efficient data structure selection.............................................................................. Best practice: Conserving object handles.................................................................................................. Cleanup of persistent objects.................................................................................................................... Creating a persistent store............................................................................................................................... Create a persistent data store................................................................................................................... Store persistent data................................................................................................................................. Store an object in a batch transaction....................................................................................................... Working with the persistent store.................................................................................................................... Retrieve persistent data............................................................................................................................ Retrieve a collection from persistent storage...........................................................................................

38 38 39 42 42 42 43 43 43 44 44 44 44 45

5 Storing objects nonpersistently........................................................................................................................ Common uses of the runtime store.................................................................................................................. Security of the runtime store............................................................................................................................ Restrict access to runtime store data using code signing keys.................................................................. Add an object to the runtime store.................................................................................................................. Replace an object in the runtime store............................................................................................................ Retrieve the runtime store............................................................................................................................... Retrieve a registered runtime object................................................................................................................ Retrieve an unregistered runtime object......................................................................................................... Code sample: Storing a String in the runtime store.......................................................................................... Code sample: Getting a stored String from the runtime store......................................................................... Code sample: Creating a singleton using the RuntimeStore API......................................................................

47 47 47 47 48 48 48 49 49 49 50 51

6 Storing data in the record store........................................................................................................................ Create a record store........................................................................................................................................ Add a record to a record store..........................................................................................................................

52 52 52


Code sample: Adding a record to the record store................................................................................... Retrieve a record from a record store.............................................................................................................. Retrieve all records from a record store........................................................................................................... Code sample: Storing and retrieving data with the record store.....................................................................

53 53 53 54

7 Managing data.................................................................................................................................................. Best practice: Minimizing memory use............................................................................................................. Removing sensitive data................................................................................................................................... Using the Garbage Collector............................................................................................................................. Full garbage collection on a BlackBerry device.......................................................................................... Managing low memory..................................................................................................................................... Identifying low memory availability on a BlackBerry device..................................................................... Backing up data.................................................................................................................................................

59 59 59 60 60 61 61 61

8 Find more information......................................................................................................................................

63

9 Provide feedback..............................................................................................................................................

64

10 Glossary............................................................................................................................................................

65

11 Document revision history................................................................................................................................

67

12 Legal notice.......................................................................................................................................................

68


Data storage overview

Development Guide

Data storage overview

1

There are several ways you can store, share, and manage data for your BlackBerry® Java® applications: Data storage approach File system SQLite® database Persistent store Runtime store

Description and API Store data in files and folders using the FileConnection API. Store data in relational databases using the Database API. Save objects across device restarts using the PersistentStore API. Save objects nonpersistently, which is useful for sharing data between applications and creating system-wide singletons, using the RuntimeStore API. Store data in the MIDP Record Management System using the RMS API.

Record store

Data storage features The following table compares each approach. Features

File system

Data format

Any

Storage locations

Application storage, external media card, built-in media storage Size of partitions the user has access to

Maximum storage limit

BlackBerry Device Software support

4.2 or later (FileConnection API) Yes

Persists across device restarts Applications can share Yes data

4

SQLite Persistent store database Relational Java® object database file External Application media card, storage built-in media storage Size of Available partitions application the user has storage access to

Runtime store

Record store

Java object

Serialized

Application storage

Application storage

Available application storage

5.0 or later

All

3.6 or later

Differs according to BlackBerry® Device Software version All

Yes

Yes

No

Yes

Yes

Yes

Yes

Yes


Storage locations

Development Guide

Considerations for choosing a data storage approach • • • • • •

The file system is typically the most efficient storage location for large, read-only files such as videos or large graphics. For storing data other than large, read-only files, SQLite® databases are a scalable data storage option. Memory on wireless devices can be very limited, so you should consider not storing all data on the device. BlackBerry® devices are frequently connected so your application can access data when needed. In many cases, the best approach is to store data across device restarts only for data that is frequently accessed. When you consider where to store essential data, keep in mind that microSD cards can be removed. There is more latency in writing to application storage than there is in reading from it. For example, reading from the persistent store is relatively fast while commits are relatively slow. The file system and record store are standards-based approaches, while the persistent store and runtime store are specific to BlackBerry devices. If you want your application to run on other Java® ME compatible devices, you should consider a standards-based approach.

Storage locations Different BlackBerry® devices support different places to store data. The following storage locations are available, depending on the BlackBerry device model: Application storage

External media card storage

Built-in media storage

This storage location is internal to the BlackBerry device. It contains the operating system, the BlackBerry® Java® Virtual Machine, and an internal file system. Application storage is also called flash memory and on-board memory. Application storage is the only place on a BlackBerry device from which applications can be run. All BlackBerry devices have application storage. This storage location is a microSD card that BlackBerry device users can insert to extend the amount of storage on their devices. It is optional and removable. A FAT file system is mounted on the media card. MicroSD cards are supported on all devices running BlackBerry® Device Software 4.2 or later, with the exception of the BlackBerry® 8700 Series. This storage location is an embedded multimedia card called eMMC. It is not removable. A FAT file system is mounted on the built-in media card. Built-in media storage is also called internal media memory and on-board device memory. Built-in media storage is included on some BlackBerry device models.

Access to memory The BlackBerry® Java® environment is designed to prevent applications from causing problems accidentally or maliciously in other applications or on the BlackBerry device. Applications can write only to the BlackBerry device memory that the BlackBerry® Java® Virtual Machine uses; they cannot access the virtual memory or the persistent storage of other applications (unless they are specifically granted access to do so). Custom applications can only

5


Development Guide

Storage locations

access persistent storage or user data, or communicate with other applications, through specific APIs. Research In Motion must digitally sign applications that use certain BlackBerry APIs to provide an audit trail of applications that use sensitive APIs.

6


Storing files in the file system

Development Guide

Storing files in the file system

2

You can programmatically create and manage the files and folders on BlackBerry® devices with the FileConnection API. The FileConnection API was introduced with BlackBerry® Device Software 4.2. The FileConnection API is implemented in the javax.microedition.io.file package. The FileConnection API is defined by JSR 75 and is built on the Generic Connection Framework. The main component of the FileConnection API is the javax.microedition.io.file.FileConnection class. Unlike other Generic Connection Framework connections, FileConnection objects can be successfully returned from the javax.microedition.io.Connector.open() method without referencing an existing file or folder. This behavior allows for the creation of new files and folders on a file system. In addition to RIM documentation, there are many sources of information about JSR 75 and the Generic Connection Framework. In addition, RIM provides extensions to the FileConnection API. The net.rim.device.api.io.file package includes the following class and interfaces: • •

FileSystemJournal and FileSystemJournalListener provide a way to detect changes to the file

system.

ExtendedFileConnection allows the encryption and protection of files.

You can access the file system on internal storage and external media card storage: Internal storage

Internal storage is application storage or built-in media storage. All devices have internal storage. To access internal storage, use the path file:///store. For example, FileConnection fc = (FileConnection)Connector.open("file:///Store")

External storage

You can access external media card storage only on devices with microSD cards. To access external media card storage, use the path file:///SDCard. For example, FileConnection fc = (FileConnection)Connector.open("file:///SDCard")

Files created by your application are not automatically deleted when your application is uninstalled. Devices that have built-in media storage have a file system partition called System. In BlackBerry Device Software 5.0 and later, the system partition is reserved for system use and is read-only. In BlackBerry Device Software versions earlier than 5.0, the system partition is read/write. You can access this partition with the path file:///system.

Code sample: Creating a folder import import import import

net.rim.device.api.system.Application; javax.microedition.io.*; javax.microedition.io.file.*; java.io.IOException;

public class CreateFolderApp extends Application { public static void main(String[] args) { 7


Development Guide

Code sample: Creating a file

CreateFolderApp app = new CreateFolderApp(); app.setAcceptEvents(false); try { // the final slash in the folder path is required FileConnection fc = (FileConnection)Connector.open("file:///SDCard/ testfolder/"); // If no exception is thrown, the URI is valid but the folder may not exist. if (!fc.exists()) { fc.mkdir(); // create the folder if it doesn't exist } fc.close(); } catch (IOException ioe) { System.out.println(ioe.getMessage() ); } } }

Code sample: Creating a file import import import import

javax.microedition.io.*; java.io.IOException; javax.microedition.io.file.*; net.rim.device.api.system.Application.*;

public class CreateFileApp extends Application { public static void main(String[] args) { CreateFileApp app = new CreateFileApp(); app.setAcceptEvents(false); try { FileConnection fc = (FileConnection)Connector.open("file:///store/home/ user/newfile.txt"); // If no exception is thrown, then the URI is valid, but the file may or may not exist. if (!fc.exists()) { fc.create(); // create the file if it doesn't exist } fc.close(); } catch (IOException ioe) { System.out.println(ioe.getMessage() ); } } }

8


Development Guide

Code sample: Writing text to a file

Code sample: Writing text to a file import import import import import

net.rim.device.api.system.Application; javax.microedition.io.*; javax.microedition.io.file.*; java.io.IOException; java.io.OutputStream;

public class AddFileContent extends Application { public static void main(String[] args) { AddFileContent app = new AddFileContent(); app.setAcceptEvents(false); try { FileConnection fc = (FileConnection)Connector.open("file:///store/home/user/ newfile.txt"); // If no exception is thrown, then the URI is valid, but the file may or may not exist. if (!fc.exists()) { fc.create(); // create the file if it doesn't exist } OutputStream outStream = fc.openOutputStream(); outStream.write("test content".getBytes()); outStream.close(); fc.close(); } catch (IOException ioe) { System.out.println(ioe.getMessage() ); } } }

Code sample: Reading sections of a binary file This code sample demonstrates how to read sections of a binary file by reading header information from a .gif file. The application reads the width and height of the image from the header. To run the code sample you must place a .gif file in the root folder of a media card in a BlackBerry速 device. import import import import import import import

net.rim.device.api.ui.*; net.rim.device.api.io.*; javax.microedition.io.file.*; javax.microedition.io.*; java.io.*; net.rim.device.api.ui.component.*; net.rim.device.api.ui.container.*;

public class RandomFileAccess extends UiApplication 9


Code sample: Reading sections of a binary file

Development Guide

{

}

public static void main(String[] args) { RandomFileAccess app = new RandomFileAccess(); app.enterEventDispatcher(); } public RandomFileAccess() { pushScreen(new HomeScreen()); }

class HomeScreen extends MainScreen { public HomeScreen() { setTitle("Random File Access Sample"); try { FileConnection fc = (FileConnection)Connector.open("file:///SDCard/ test.gif"); boolean bFileExists = fc.exists(); if (!bFileExists) { Dialog.alert("Cannot find specified GIF file."); System.exit(0); } DataInputStream in = fc.openDataInputStream(); byte[] widthBytes = new byte[2]; byte[] heightBytes = new byte[2]; if ( in instanceof Seekable ) { ((Seekable) in).setPosition(6); in.read(widthBytes,0,2);

}

((Seekable) in).setPosition(8); in.read(heightBytes,0,2);

int widthPixels = widthBytes[0] + 256 * widthBytes[1]; int heightPixels = heightBytes[0] + 256 * heightBytes[1]; add(new LabelField("Width: " + widthPixels + "\nHeight: " + heightPixels)); in.close(); fc.close();

}

10

}

} catch (IOException ioe) { ioe.printStackTrace(); }


Development Guide

Code sample: Displaying the path to the video folder using System.getProperty()

Code sample: Displaying the path to the video folder using System.getProperty() import net.rim.device.api.ui.component.LabelField.*; import net.rim.device.api.ui.container.MainScreen.*; import net.rim.device.api.ui.UiApplication.*; public class GetVidDir extends UiApplication { public static void main(String args[]) { GetVidDir app = new GetVidDir(); app.enterEventDispatcher(); }

}

public GetVidDir() { HomeScreen hs = new HomeScreen(); pushScreen(hs); }

class HomeScreen extends MainScreen { public HomeScreen() { LabelField msg = new LabelField(System.getProperty("fileconn.dir.videos")); add(msg); } }

Code sample: Retrieving a list of mounted roots import import import import import

java.util.Enumeration.*; javax.microedition.io.file.FileSystemRegistry,*; net.rim.device.api.ui.component.LabelField.*; net.rim.device.api.ui.container.MainScreen.*; net.rim.device.api.ui.UiApplication.*;

public class ListMountedRoots extends UiApplication { public static void main(String[] args) { ListMountedRoots app = new ListMountedRoots(); app.enterEventDispatcher(); }

}

public ListMountedRoots() { pushScreen(new HomeScreen()); }

11


Development Guide

Code sample: Retrieving a list of mounted roots

class HomeScreen extends MainScreen { public HomeScreen() { StringBuffer msg = new StringBuffer( “The mounted roots are:\n”); Enumeration e = FileSystemRegistry.listRoots(); while (e.hasMoreElements()) { msg.append( e.nextElement() ); msg.append( ‘\n’ ); } add(new LabelField(msg)); } }

12


Development Guide

Storing data in SQLite databases

Storing data in SQLite databases

3

SQLite® databases require no configuration or administration. Other than schema and data, the database footprint is very small (around 300 KB). To create and use SQLite databases in a Java application, you must use the Database API. The classes required for SQLite databases are in the net.rim.device.api.database package. BlackBerry Device Software 6.0 uses SQLite version 3.6.21. Note: This guide describes how to use SQLite databases in Java® applications. There are other ways to use SQLite databases on a BlackBerry device. They are BlackBerry® WebWorks™ applications, HTML5, and Google® Gears™. For more information, see docs.blackberry.com.

Viewing SQLite databases SQLite® database viewers are available from third-party vendors. These viewers can be useful aids to your database development process. Database viewers are especially useful for viewing changes to a database. When you run an SQL statement, you can see the result in the database viewer immediately. An SQLite database viewer runs on your computer, not on the BlackBerry® device. To use the viewer, configure the BlackBerry Smartphone Simulator to emulate a microSD card. Then when you run your application, the database is stored in a directory on your desktop computer and the database viewer can read it. SQLite database viewers cannot work on encrypted databases. You can encrypt the database when your SQLite application is finished.

Simulate a media card To view SQLite® databases in a database viewer, you might have to configure the BlackBerry® Smartphone Simulator to emulate a media card. By default, database files are stored on a media card. 1. 2. 3. 4. 5. 6.

Create a folder on your computer to store emulation files for the media card. On the Simulate menu, click Change SD Card. Click Add Directory. Navigate to and click the folder you created. Click OK. Click Close.

Security of SQLite databases Your SQLite® database can have the following security settings: • •

Not encrypted, accessible from any application on the BlackBerry® device Encrypted, accessible from any application on the device

13


Development Guide

•

Security of SQLite databases

Encrypted and protected, accessible only from applications on the device that are signed with the code signing key

There is no way to create a non-encrypted database and restrict its usage to only one application. That is because there are other ways (using file I/O operations) to read a non-encrypted database file from other applications. You implement both encryption and protection with the DatabaseSecurityOptions class.

Encryption The algorithm used to implement SQLite encryption is AES 256. An encrypted database cannot be moved to another device: it can be opened only on the device where it was originally created. To transfer an encrypted database to another device, you must first decrypt it. An application can open or create an encrypted database only when the device is unlocked. If a database is open when a device is locked, the database continues to be readable and writable. Encryption does not protect your database from being accessible to other applications on the device. To restrict access, you must sign the database with a code signing key. The following code sample creates a database that is encrypted but not signed. It creates a DatabaseSecurityOptions object called dbso that passes true as the single parameter value: try {

URI myURI = URI.create("file:///SDCard/Databases/SQLite_Guide/" + "MyEncryptedDatabase.db"); DatabaseSecurityOptions dbso = new DatabaseSecurityOptions(true); d = DatabaseFactory.create(myURI,dbso); d.close();

} catch ( Exception e ) { System.out.println( e.getMessage() ); e.printStackTrace(); }

Encryption and protection If you want to restrict a database so that it can be accessed only by the application it is a part of, you should sign the database with a code signing key. To restrict access to one application, you should use a unique key that you generate using the Signing Authority tool. This signing is separate from the code signing you do for controlled APIs. You can also use the code signing key to share access to the database with other specific applications. When multiple applications are signed with the same key, they all have access to the database. To specify that a database is encrypted and signed, you have a choice of two identical constructors. The following code sample encrypts and protects an existing database. First, the code sample retrieves the code signing key from a file called XYZ. It then encrypts and signs the database. If the database is already encrypted, the method exits gracefully.

14


Development Guide

Security of SQLite databases

CodeSigningKey codeSigningKey = CodeSigningKey.get(CodeModuleManager.getModuleHandle( "SQLiteDemo" ), "XYZ"); try {

DatabaseFactory.encrypt(uri, new DatabaseSecurityOptions(codeSigningKey)); } catch(DatabaseException dbe) { errorDialog("Encryption failed - " + dbe.toString()); }

Code sample: Creating an encrypted SQLite database By default, database files are stored on a media card. If you are using a BlackBerry速 Smartphone Simulator, you might need to Simulate a media card. import import import import import

net.rim.device.api.ui.*; net.rim.device.api.ui.component.*; net.rim.device.api.ui.container.*; net.rim.device.api.database.*; net.rim.device.api.io.*;

public class CreateEncryptedDatabase extends UiApplication { public static void main(String[] args) { CreateEncryptedDatabase theApp = new CreateEncryptedDatabase(); theApp.enterEventDispatcher(); }

}

public CreateEncryptedDatabase() { pushScreen(new CreateEncryptedDatabaseScreen()); }

class CreateEncryptedDatabaseScreen extends MainScreen { Database d; public CreateEncryptedDatabaseScreen() { LabelField title = new LabelField("SQLite Create Encrypted Database Sample", LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH); setTitle(title); add(new RichTextField("Creating an encrypted database called " + "MyEncryptedDatabase.db on the SDCard.")); try { URI myURI = URI.create("file:///SDCard/Databases/SQLite_Guide/" + "MyEncryptedDatabase.db"); DatabaseSecurityOptions dbso = new DatabaseSecurityOptions(true); d = DatabaseFactory.create(myURI,dbso); 15


Development Guide

}

}

Performance of SQLite databases

d.close(); } catch ( Exception e ) { System.out.println( e.getMessage() ); e.printStackTrace(); }

Performance of SQLite databases Compared to a computer, a smartphone provides a very constrained environment for an SQLite速 database. To achieve optimal performance on a BlackBerry速 device, you should build your database with these constraints in mind. On a BlackBerry device, only one read-write database connection to an SQLite database can be made at a time. Other database connections are read-only. There is a limit of 16 concurrent database connections, and three to six of those are used by the media application. There are constraints on the amount of RAM available to an SQLite database for storing internal data structures for schemas and transactions, and to support binding BLOBs into a table. In BlackBerry速 Device Software 5.0, the limit is 512 KB. In BlackBerry Device Software 6.0, the limit is 5 MB. The entire database schema is loaded into memory when an SQLite database is opened and persists until the database is closed. The schema for each table, trigger, index, and so on takes up a certain amount of RAM. In addition, the memory limit for the binding of BLOBs is significantly less than the overall memory limit and will vary depending on what is left over after allocating memory for SQLite data structures, schemas, and transactions in progress. If you plan to create a database with a large schema or insert large BLOBs, you should test the database on your target BlackBerry devices to make sure that the devices have adequate memory. The maximum SQL query length is 4 KB.

Best practice: Optimizing SQLite database performance Consider the following guidelines: Best practice Description Store as little data as Most of the processing time of SQLite速 databases is taken up by reading and writing to possible storage. Less data generally means fewer reads and writes. The SQLite database engine caches frequently accessed database pages. By storing less data, you can increase the probability that the SQLite database engine retrieves requested data more quickly from the cache instead of from the relatively slow storage access. Use explicit If you do not use explicit transactions, a transaction begins before each statement is transactions executed and ends after the statement is executed. This default behavior is inefficient. It requires the opening, reopening, writing to, and closing of the journal file for each statement. With explicit transactions, you can group statements.

16


Creating and deleting SQLite databases

Development Guide

Best practice Create efficient indexes

Description Indexes can greatly reduce the time required to scan a table. Consider the following guidelines: •

Minimize the size of rows Store BLOBs appropriately Consider using temporary tables Use SQL parameters

Avoid subqueries Defragment the database Consider the order of columns in table declarations

Indexes can improve performance for read-only queries such as SELECT, but they can reduce performance for queries that change rows. Consider only indexing tables that are infrequently updated. • The order of columns in an index affects performance. Columns that are typically used in WHERE clauses should be placed first, followed by columns that are typically used in ORDER BY clauses. • For columns containing data that is retrieved, create a covering index. • Avoid duplicate indexes. The SQLite database engine automatically creates indexes for columns that have UNIQUE or PRIMARY KEY constraints. If you have a very wide column, consider putting it in a separate table. If your data includes BLOBs, consider storing each BLOB in a separate table. If the BLOBs are very large, you can store them as files outside the database (and store the path to each file in the database), but this practice introduces overhead for filename lookups. If you do not need the data to be available following a restart of the BlackBerry devices, use the CREATE TEMP TABLE statement instead of CREATE TABLE. To execute a set of statements of the same format, first prepare a generic statement that uses SQL parameters. You can execute the statement by iterating through the variable values and binding the values to the named variables in each iteration. In some cases, the SQLite database engine stores subquery results in a temporary file, which can slow down processing. Use the SQLite VACUUM command to defragment the database. This process also reduces the size of the database file. The order of columns in a table declaration affects performance, especially in the absence of an index, because the SQLite database engine scans the columns in the order defined in the table declaration. Columns that contain small amounts of data that is frequently accessed should be placed before columns that contain large amounts of data that is infrequently accessed.

Creating and deleting SQLite databases You can create temporary or permanent databases. The CREATE TABLE statement creates a permanent, or regular, database. When you do not need to store data across device resets, you should use the CREATE TEMP TABLE statement to create temporary tables, as they are more efficient. The temporary tables that are created are stored in a temporary database along with all associated indexes, triggers, and views. The temporary database file is deleted automatically when the database connection is closed. When your application is removed, permanent databases associated with your application are not automatically deleted. 17


Development Guide

Creating and deleting SQLite databases

SQLite database files Each SQLite速 database is stored in a single file. If you specify only the database name as the parameter value to

DatabaseFactory.create(), the database file is created in external media card storage. The default location for

the database file is /SDCard/databases/application_name/. The name of the application that creates the database is included in the default path to avoid name collisions. You cannot store SQLite databases in application storage.

External media card storage is the preferred storage location for databases if the BlackBerry速 device supports it. On devices that support external media card storage, you can create databases in external media card storage by specifying the path /SDcard/. If your application is designed to store your SQLite database in built-in media storage, you should implement your application so that it is easy to modify the code to change the storage location of the database. On devices that support built-in media storage, you can create databases in built-in media storage by specifying the path /store/ . When your application is uninstalled, the SQLite databases associated with it are not automatically removed.

Character encoding The Database API uses UTF-8 character encoding. Java速 stores strings internally in UTF-8, so you don't need to do any encoding or conversion. The SQLite statement PRAGMA is not supported in the Database API, so you can't use PRAGMA ENCODING to set another encoding. You must use UTF-8 supported characters in your SQLite database.

Create an SQLite database Before you begin: By default, database files are stored on a media card. If you are using a BlackBerry速 Smartphone Simulator, you might need to Simulate a media card. 1.

Import the required libraries. import import import import import

2.

18

net.rim.device.api.ui.*; net.rim.device.api.ui.component.*; net.rim.device.api.ui.container.*; net.rim.device.api.database.*; net.rim.device.api.io.*;

Create the framework for the application by extending the UiApplication class. This class represents your application. Provide a main() method for the new class. In the main() method, create an instance of the new class and invoke the enterEventDispatcher() method to enable the application to receive events. Provide a constructor for the new class. In the constructor, invoke the pushScreen method to display the custom screen for the application.


Development Guide

Creating and deleting SQLite databases

public class CreateDatabase extends UiApplication { public static void main(String[] args) { CreateDatabase theApp = new CreateDatabase(); theApp.enterEventDispatcher(); }

}

public CreateDatabase() { pushScreen(new CreateDatabaseScreen()); }

3.

Create the screen for the application by extending the MainScreen class. Provide a constructor for the new class. In the constructor, create the title for the screen with a LabelField object and display it by invoking the setTitle() method. Invoke the add() method to display a text field on the screen. class CreateDatabaseScreen extends MainScreen { public CreateDatabaseScreen() { LabelField title = new LabelField("SQLite Create Database Sample", LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH); setTitle(title); add(new RichTextField("Creating a database called " + "MyTestDatabase.db on the SDCard.")); } }

4.

Create a URI that represents the database file by invoking the static create() method of the URI class. Invoke the create() method of the DatabaseFactory class to create the database that corresponds to the URI that you created. This creates an SQLite速 database on the microSD card of the BlackBerry速 device. If you do not specify a full path, the database is created in a folder named after your application. try {

URI myURI = URI.create("file:///SDCard/Databases/SQLite_Guide/" + "MyTestDatabase.db"); Database db = DatabaseFactory.create(myURI);

} catch ( Exception e ) { System.out.println( e.getMessage() ); e.printStackTrace(); }

5.

When you are finished using the database, it is good practice to close it. db.close();

After you finish: After creating a database, verify that the database file was created. You can do this in any of the following ways: 19


Development Guide

• • •

Creating and deleting SQLite databases

View the database in a database viewer Look in the file system for the database file, and verify that it is not zero size Invoke DatabaseFactory.exists()

Code sample: Creating an SQLite database By default, database files are stored on a media card. If you are using a BlackBerry® Smartphone Simulator, you might need to Simulate a media card. import import import import import

net.rim.device.api.ui.component.*; net.rim.device.api.ui.container.*; net.rim.device.api.database.*; net.rim.device.api.io.*; net.rim.device.api.ui.*;

public class CreateDatabase extends UiApplication { public static void main(String[] args) { CreateDatabase theApp = new CreateDatabase(); theApp.enterEventDispatcher(); }

}

public CreateDatabase() { pushScreen(new CreateDatabaseScreen()); }

class CreateDatabaseScreen extends MainScreen { Database d; public CreateDatabaseScreen() { LabelField title = new LabelField("SQLite Create Database Sample", LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH); setTitle(title); add(new RichTextField("Creating a database called " + "MyTestDatabase.db on the SDCard.")); try { URI myURI = URI.create("file:///SDCard/Databases/SQLite_Guide/" + "MyTestDatabase.db"); d = DatabaseFactory.create(myURI); d.close(); } catch ( Exception e ) { System.out.println( e.getMessage() ); e.printStackTrace();

20


Creating and deleting SQLite databases

Development Guide

}

}

}

Code sample: Adding a schema to an SQLite database import import import import import

net.rim.device.api.ui.*; net.rim.device.api.ui.component.*; net.rim.device.api.ui.container.*; net.rim.device.api.database.*; net.rim.device.api.io.*;

public class CreateDatabaseSchema extends UiApplication { public static void main(String[] args) { CreateDatabaseSchema theApp = new CreateDatabaseSchema(); theApp.enterEventDispatcher(); }

}

public CreateDatabaseSchema() { pushScreen(new CreateDatabaseSchemaScreen()); }

class CreateDatabaseSchemaScreen extends MainScreen { Database d; public CreateDatabaseSchemaScreen() { LabelField title = new LabelField("SQLite Create " + "Database Schema Sample", LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH); setTitle(title); add(new RichTextField("Adding a table to a database called " + "MyTestDatabase.db on the SDCard.")); try { URI myURI = URI.create("/SDCard/Databases/SQLite_Guide/" + "MyTestDatabase.db"); d = DatabaseFactory.open(myURI); Statement st = d.createStatement( "CREATE TABLE 'People' ( " + "'Name' TEXT, " + "'Age' INTEGER )" ); st.prepare(); st.execute(); st.close(); d.close();

} catch ( Exception e ) { System.out.println( e.getMessage() ); 21


Creating and deleting SQLite databases

Development Guide

} }

e.printStackTrace();

}

Code sample: Deleting an SQLite database import import import import import

net.rim.device.api.ui.*; net.rim.device.api.ui.component.*; net.rim.device.api.ui.container.*; net.rim.device.api.database.*; net.rim.device.api.io.*;

public class DeleteDatabase extends UiApplication { public static void main(String[] args) { DeleteDatabase theApp = new DeleteDatabase(); theApp.enterEventDispatcher(); }

}

public DeleteDatabase() { pushScreen(new DeleteDatabaseScreen()); }

class DeleteDatabaseScreen extends MainScreen { Database d; public DeleteDatabaseScreen() { LabelField title = new LabelField("SQLite Delete Database Sample", LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH); setTitle(title); add(new RichTextField("Deleting a database called " + "MyTestDatabase.db on the SDCard.")); try { URI myURI = URI.create("file:///SDCard/Databases/SQLite_Guide/" + "MyTestDatabase.db"); DatabaseFactory.delete(myURI); } catch ( Exception e ) { System.out.println( e.getMessage() ); e.printStackTrace(); } } }

22


Development Guide

Working with SQLite databases

Working with SQLite databases Once you have created an SQLite速 database, you can use SQL statements to add data, retrieve data, and modify the database. The list of supported SQL statements and their syntax is available on the SQLite web site. The Database API does not support the following SQLite statements: ATTACH DATABASE, DETACH DATABASE, and PRAGMA. In addition, FTS2 and RTREE are not supported. The following steps outline the basic procedure for running statements: 1. 2. 3. 4.

Create an SQL statement by invoking Database.createStatement() . Prepare the statement to run by invoking Statement.prepare() . Run the statement. If the statement might return results, run it by invoking Statement.getCursor() . Otherwise, use Statement.execute() . If the statement returns a result set, retrieve the result set by iterating over the returned cursor row by row. Do this using the Cursor interface, which works in all circumstances but is forward-only. For birectional cursor movement, but only for small result sets, use the BufferedCursor class.

Using transactions SQLite速 statements always run in transactions. If the statement runs successfully, the transaction is automatically committed. If the statement fails, the transaction is rolled back. By default, a separate transaction is created for each SQLite statement, which is less efficient than running multiple statements in one transaction. You can usually improve performance by explicitly specifying transactions for groups of statements. You can run multiple statements in one transaction using Database.beginTransaction() and Database.commitTransaction() around groups of statements. Nested transactions are not supported.

Code sample: Using transactions 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.database.*; import net.rim.device.api.io.*; public class UsingTransactions extends UiApplication { public static void main(String[] args) { UsingTransactions theApp = new UsingTransactions(); theApp.enterEventDispatcher(); } public UsingTransactions() { } 23


Development Guide

Working with SQLite databases

} class UsingTransactionsScreen extends MainScreen { Database d; public UsingTransactionsScreen() { LabelField title = new LabelField("SQLite Using Transactions Sample", LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH); setTitle(title); add(new RichTextField("Updating data in one transaction in MyTestDatabase.db.")); try { URI myURI = URI.create("file:///SDCard/Databases/SQLite_Guide/" + "MyTestDatabase.db"); d = DatabaseFactory.open(myURI); d.beginTransaction(); Statement st = d.createStatement("UPDATE People SET Age=7 " + "WHERE Name='Sophie'"); st.prepare(); st.execute(); st.reset(); st = d.createStatement("UPDATE People SET Age=4 " + "WHERE Name='Karen'"); st.prepare(); st.execute(); d.commitTransaction(); st.close(); d.close();

}

} catch ( Exception e ) { System.out.println( e.getMessage() ); e.printStackTrace(); } }

Using SQL parameters When you create an SQL statement, you can create SQL parameters in order to reuse the statement with different values. This practice can provide performance benefits. Prepare generic statements that use named variables, and then execute the statements when they are required by iterating through the variable values, binding the values to the named variables in each iteration. You can choose from the following ways to number the parameters: • •

24

A question mark (?) in the statement causes each parameter to be numbered sequentially, starting from 1. A question mark followed by an integer (?NNN) in the statement provides each parameter with the number NNN.


Development Guide

Working with SQLite databases

You can use the Statement.bind() method to provide names for SQL parameters. The bind() method takes the number of the parameter and the value to be bound to it. If you use a number outside of the allowed range, a DatabaseException is thrown. All bindings can be reset using Statement.reset() . Here's an example of a statement that uses parameters to create an upper bound and lower bound that can be defined each time the statement is run. This example numbers the parameters sequentially. Statement s = Database.createStatement("SELECT * FROM T WHERE a < ? AND a > ?"); s.prepare(); s.bind(1, upperBound); s.bind(2, lowerBound); Cursor c = s.getCursor();

Here's an example of the same statement, except that explicit numbers are specified for the parameters: Statement s = Database.createStatement("SELECT * FROM T WHERE a < ?5 AND a > ?12"); s.prepare(); s.bind(5, upperBound); s.bind(12, lowerBound);

The getFormalName() method converts a parameter index to an SQL parameter name. For getFormalName() to be able to return the parameter name, you must provide a name in the query. For example, when you call getFormalName(1), the statement "SELECT * FROM T WHERE a = :a" returns :a. When parameters such as a question mark (?) are used as placeholders, getFormalName() cannot return a parameter name. For example, getFormalName(1) will not return the name for the parameter in this statement: "SELECT * FROM T WHERE a = ?"

Code sample: Creating a parameterized update import import import import import import

net.rim.device.api.ui.*; net.rim.device.api.ui.component.*; net.rim.device.api.ui.container.*; net.rim.device.api.database.*; net.rim.device.api.io.*; java.util.*;

public class ParameterizedUpdate extends UiApplication { public static void main(String[] args) { ParameterizedUpdate theApp = new ParameterizedUpdate(); theApp.enterEventDispatcher(); } public ParameterizedUpdate() { pushScreen(new ParameterizedUpdateScreen()); } } class ParameterizedUpdateScreen extends MainScreen { Database d; public ParameterizedUpdateScreen() { 25


Working with SQLite databases

Development Guide

LabelField title = new LabelField("SQLite Parameterized Update Sample", LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH); setTitle(title); add(new RichTextField("Attempting to update data in " + "MyTestDatabase.db on the SDCard.")); try { URI myURI = URI.create("file:///SDCard/Databases/SQLite_Guide/" + "MyTestDatabase.db"); d = DatabaseFactory.open(myURI); Statement st = d.createStatement("UPDATE People SET Age=? WHERE Name=?"); st.prepare(); Hashtable ht = new Hashtable(2); ht.put("Sophie", new Integer(10)); ht.put("Karen", new Integer(7)); Enumeration names = ht.keys(); Enumeration ages = ht.elements(); while (names.hasMoreElements()) { Integer iAge = (Integer)ages.nextElement(); String strName = (String)names.nextElement(); st.bind(1,iAge.intValue()); st.bind(2,strName); st.execute(); st.reset(); } st.close(); d.close();

}

}

} catch ( Exception e ) { System.out.println( e.getMessage() ); e.printStackTrace(); }

Code sample: Creating a parameterized insert import import import import import import

net.rim.device.api.ui.*; net.rim.device.api.ui.component.*; net.rim.device.api.ui.container.*; net.rim.device.api.database.*; net.rim.device.api.io.*; java.util.*;

public class ParameterizedInsert extends UiApplication { public static void main(String[] args)

26


Development Guide

{

Working with SQLite databases

ParameterizedInsert theApp = new ParameterizedInsert(); theApp.enterEventDispatcher();

} public ParameterizedInsert() { pushScreen(new ParameterizedInsertScreen()); }

} class ParameterizedInsertScreen extends MainScreen { Database d; public ParameterizedInsertScreen() { LabelField title = new LabelField("SQLite Insert Data " + "Schema Sample", LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH); setTitle(title); add(new RichTextField("Attempting to insert data into " + "MyTestDatabase.db on the SDCard.")); try { URI myURI = URI.create("file:///SDCard/Databases/SQLite_Guide/" + "MyTestDatabase.db"); d = DatabaseFactory.open(myURI); Statement st = d.createStatement("INSERT INTO People(Name,Age) " + "VALUES (?,?)"); st.prepare(); Hashtable ht = new Hashtable(4); ht.put("Sophie", new Integer(6)); ht.put("Karen", new Integer(3)); ht.put("Kevin", new Integer(82)); ht.put("Cindy", new Integer(12)); Enumeration names = ht.keys(); Enumeration ages = ht.elements(); while (names.hasMoreElements()) { String strName = (String)names.nextElement(); Integer iAge = (Integer)ages.nextElement(); st.bind(1,strName); st.bind(2,iAge.intValue()); st.execute(); st.reset(); } st.close(); d.close(); } catch ( Exception e ) {

27


Development Guide

} }

}

Working with SQLite databases

System.out.println( e.getMessage() ); e.printStackTrace();

Using foreign key constraints If you create foreign keys for your database, you can enforce the use of them by setting the

foreign_key_constraints option in the DatabaseOptions class.

You can set this option when you create the database. For the foreign key constraints to be in effect, you must set the option each time you open the database. You can check whether foreign key constraints are on with the DatabaseOptions.foreignKeyConstraintsOn method. Code sample The following code sample shows how to enforce foreign key constraints before opening or creating a database. The code sample creates the DatabaseOptions object with foreign_key_constraints set to on. DatabaseOptions dbo = new DatabaseOptions(); dbo.set("foreign_key_constraints","on"); Database d = DatabaseFactory.openOrCreate("test.db", dbo);

Code sample: Inserting table data import import import import import

net.rim.device.api.ui.*; net.rim.device.api.ui.component.*; net.rim.device.api.ui.container.*; net.rim.device.api.database.*; net.rim.device.api.io.*;

public class InsertData extends UiApplication { public static void main(String[] args) { InsertData theApp = new InsertData(); theApp.enterEventDispatcher(); }

}

public InsertData() { pushScreen(new InsertDataScreen()); }

class InsertDataScreen extends MainScreen { Database d;

28


Working with SQLite databases

Development Guide

public InsertDataScreen() { LabelField title = new LabelField("SQLite Insert Data " + "Schema Sample", LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH); setTitle(title); add(new RichTextField("Attempting to insert data into " + "MyTestDatabase.db on the SDCard.")); try { URI myURI = URI.create("file:///SDCard/Databases/SQLite_Guide/" + "MyTestDatabase.db"); d = DatabaseFactory.open(myURI); Statement st = d.createStatement("INSERT INTO People(Name,Age) " + "VALUES ('John',37)"); st.prepare(); st.execute(); st.close(); d.close(); } catch ( Exception e ) { System.out.println( e.getMessage() ); e.printStackTrace(); } }

}

Code sample: Retrieving table data import import import import import

net.rim.device.api.ui.*; net.rim.device.api.ui.component.*; net.rim.device.api.ui.container.*; net.rim.device.api.database.*; net.rim.device.api.io.*;

public class ReadData extends UiApplication { public static void main(String[] args) { ReadData theApp = new ReadData(); theApp.enterEventDispatcher(); }

}

public ReadData() { pushScreen(new ReadDataScreen()); }

class ReadDataScreen extends MainScreen 29


Working with SQLite databases

Development Guide

{

Database d; public ReadDataScreen() { LabelField title = new LabelField("SQLite Read Table Data Sample", LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH); setTitle(title); add(new RichTextField("Attempting to retrieve data from " + "MyTestDatabase.db on the SDCard.")); try { URI myURI = URI.create("file:///SDCard/Databases/SQLite_Guide/" + "MyTestDatabase.db"); d = DatabaseFactory.open(myURI); Statement st = d.createStatement("SELECT Name,Age FROM People"); st.prepare(); net.rim.device.api.database.Cursor c = st.getCursor(); Row r; int i = 0; while(c.next()) { r = c.getRow(); i++; add(new RichTextField(i + ". Name = " + r.getString(0) + " , " + "Age = " + r.getInteger(1))); } if (i==0) { add(new RichTextField("No data in the People table.")); } st.close(); d.close();

}

}

} catch ( Exception e ) { System.out.println( e.getMessage() ); e.printStackTrace(); }

Code sample: Deleting table data To delete data in a table, use the DELETE statement. To delete a table and its schema, use the DROP TABLE statement. The following example shows the DELETE statement.

30


Working with SQLite databases

Development Guide

import import import import import

net.rim.device.api.ui.*; net.rim.device.api.ui.component.*; net.rim.device.api.ui.container.*; net.rim.device.api.database.*; net.rim.device.api.io.*;

public class DeleteData extends UiApplication { public static void main(String[] args) { DeleteData theApp = new DeleteData(); theApp.enterEventDispatcher(); }

}

public DeleteData() { pushScreen(new DeleteDataScreen()); }

class DeleteDataScreen extends MainScreen { Database d; public DeleteDataScreen() { LabelField title = new LabelField("SQLite Delete Database Data", LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH); setTitle(title); add(new RichTextField("Attempting to delete data from " + "MyTestDatabase.db on the SDCard.")); try { URI myURI = URI.create("file:///SDCard/Databases/SQLite_Guide/" + "MyTestDatabase.db"); d = DatabaseFactory.open(myURI); Statement st = d.createStatement("DELETE FROM People"); st.prepare(); st.execute(); st.close(); d.close(); } catch ( Exception e ) { System.out.println( e.getMessage() ); e.printStackTrace(); } }

}

31


Development Guide

Working with SQLite databases

Code sample: Updating table data import import import import import

net.rim.device.api.ui.*; net.rim.device.api.ui.component.*; net.rim.device.api.ui.container.*; net.rim.device.api.database.*; net.rim.device.api.io.*;

public class UpdateData extends UiApplication { public static void main(String[] args) { UpdateData theApp = new UpdateData(); theApp.enterEventDispatcher(); }

}

public UpdateData() { pushScreen(new UpdateDataScreen()); }

class UpdateDataScreen extends MainScreen { Database d; public UpdateDataScreen() { LabelField title = new LabelField("SQLite Update Data Sample", LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH); setTitle(title); add(new RichTextField("Trying to update data in MyTestDatabase.db.")); try { URI myURI = URI.create("file:///SDCard/Databases/SQLite_Guide/" + "MyTestDatabase.db"); d = DatabaseFactory.open(myURI); Statement st = d.createStatement("UPDATE People SET Age=38 " + "WHERE Name='John'"); st.prepare(); st.execute(); st.close(); d.close(); } catch ( Exception e ) { System.out.println( e.getMessage() ); e.printStackTrace(); } }

32

}


Working with SQLite databases

Development Guide

Code sample: Listing database tables import import import import import

net.rim.device.api.ui.*; net.rim.device.api.ui.component.*; net.rim.device.api.ui.container.*; net.rim.device.api.database.*; net.rim.device.api.io.*;

public class ListTables extends UiApplication { public static void main(String[] args) { ListTables theApp = new ListTables(); theApp.enterEventDispatcher(); }

}

public ListTables() { pushScreen(new ListTablesScreen()); }

class ListTablesScreen extends MainScreen { Database d; public ListTablesScreen() { LabelField title = new LabelField("SQLite List Database Tables", LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH); setTitle(title); add(new RichTextField("Attempting to list tables in " + "MyTestDatabase.db on the SDCard.")); try { URI myURI = URI.create("file:///SDCard/Databases/SQLite_Guide/" + "MyTestDatabase.db"); d = DatabaseFactory.open(myURI); Statement st = d.createStatement("SELECT name FROM " + " sqlite_master " + "WHERE type='table'" + "ORDER BY name"); st.prepare(); net.rim.device.api.database.Cursor c = st.getCursor(); Row r; int i = 0; while(c.next()) { r = c.getRow(); i++; add(new RichTextField(i + ". Table: " + r.getString(0))); } if (i==0) 33


SQLite sample application

Development Guide

{

add(new RichTextField("There are no tables " + " in the MyTestDatabase database."));

} st.close(); d.close();

} catch ( Exception e ) { System.out.println( e.getMessage() ); e.printStackTrace(); } }

}

SQLite sample application Overview The SQLite® database sample application demonstrates how to create a persistent relational database that is stored on the BlackBerry® device and how to change the entries in the database. The database contains two tables that are called Category and DirectoryItems. The DirectoryItems table contains items that simulate entries in a business directory list. Each DirectoryItem entry also contains a CategoryID field that must match a category_id entry in the Category table (for instance, Category.category_id is a foreign key for DirectoryItem.categoryID). The sample application displays the entries in the tables as a collapsible tree structure with Category entries as parent nodes and DirectoryItem entries as child nodes of the Category nodes that they are associated with. The sample application provides menu items for adding a new category or directory item and for changing or deleting the directory item. This sample application uses APIs that are designed to be secure.The application must be signed before it can be run. For more information about code signing, see the BlackBerry Signing Authority Tool Administration Guide.

Files in the sample application File name SQLiteDemo.java

34

Description • •

This file contains the application entry point. This file contains the application's constructor, which creates an encrypted database on the media card of the BlackBerry® device if it is inserted or in device memory. If a media card is not inserted and the BlackBerry device supports storing the database in device memory, the encrypted database is created in device memory.


SQLite sample application

Development Guide

File name

Description

SQLiteDemoScreen.java

• •

SQLManager.java

ItemScreen.java Category.java DirectoryItems.java SQLiteDemoDirectory

This file creates and displays an instance of SQLiteDemoScreen. This file contains the application's main screen, which displays the database in a collapsible tree structure with Category entries as parent nodes and associated DirectoryItems as their children. • This file contains helper methods to populate the tree when the screen is initialized. • This file contains menu items for adding, updating, and deleting categories and directory items. This file contains all the methods for adding, updating, and deleting categories and directory items (for instance, all of the database manipulation methods). This file contains the screen, which contains fields for adding and changing categories and directory items. • This file defines the Category entries in the database. • This file provides accessor methods for the Category fields. • This file defines the DirectoryItems entries in the database. • This file provides accessor methods for the DirectoryItems fields. • This file contains the database. • This file comes prepopulated with sample entries.

Featured interfaces net.rim.device.api.Database The Database interface represents the database. This interface provides methods for changing a database on the BlackBerry device, including methods for creating SQL statements to add, delete, or update records in the database and methods for committing and canceling transactions in the database. This interface also provides methods for setting and retrieving the database's metadata. To create the database, you can use one of the methods of the DatabaseFactory class. The sample application uses this interface to create statements to insert, delete, and update records in the database.

net.rim.device.api.Statement The Statement interface represents an SQL statement. You can use the Statement interface to retrieve, add, delete, or change entries in the database.

net.rim.device.api.Cursor The Cursor interface provides methods for traversing a result set that is retrieved using the Statement object. A Statement object creates the Cursor object using the Statement.getCursor() method as part of its query process. Therefore, a Cursor is always associated with a specific query.

35


Development Guide

SQLite sample application

Featured classes net.rim.device.api.database.Row The Row class represents a row in a result set that a SELECT query returns. This class provides methods for retrieving column indices in the Row object and the values stored in the Row object's columns. You create this class using the Cursor.getRow() method.

net.rim.device.api.database.DatabaseFactory The DatabaseFactory class provides methods for creating, opening, configuring, and deleting a new or existing Database. Configuration options include persistent or nonpersistent storage and encryption. To create a new database, you must use the DatabaseFactory.openOrCreate() method or the DatabaseFactory.create() method. If you have already created the database, the DatabaseFactory.openOrCreate() method returns a reference to the database. Create the database on a media card instead of in device memory. Only certain BlackBerry® devices support storing an SQLite database in device memory.

Install the sample application 1. 2. 3. 4. 5.

In Eclipse®, on the File menu, click Import. In the Import dialog box, expand the BlackBerry folder. Click Import BlackBerry Samples. Click Next. Perform one of the following actions: • To specify a specific JRE™, select the Use a project specific JRE option. • To specify the default JRE in the workspace, select the Use default JRE option.

6. 7. 8.

In the BlackBerry Projects section, click Deselect All. Select the check box beside the SQLiteDemo project. Click Finish.

Run the sample application 1. 2. 3. 4.

36

In Eclipse®, in the Navigator pane, right-click the SQLiteDemo folder. Click Run As > BlackBerry Simulator. If the BlackBerry® Smartphone Simulator does not display the message "Media card inserted", you must Simulate a media card. If necessary, on the Home screen of the BlackBerry® Smartphone Simulator, click the Downloads folder.


Development Guide

5.

SQLite sample application

Click the SQLite Demo icon.

37


Development Guide

Storing objects persistently

Storing objects persistently

4

The persistent store lets you save objects to persistent memory. The objects are retained in memory after a BlackBerry® device restarts. The persistent store is included in all versions of BlackBerry® Device Software. With the Persistent Store API, you can save entire Java® objects to memory without having to serialize the data first. When your application starts, it can retrieve the Java object from memory and process the data. The Persistent Store API does not provide a relational database model. You must create an effective object model and manage the relationships between objects, as necessary, using indices and hash tables. The Persistent Store API is implemented in the PersistentObject class, PersistentStore class, and EventLogger class, all of which are provided in the net.rim.device.api.system package, and the Persistable interface, which is provided in the net.rim.device.api.util package. Data is stored as instances of PersistentObject. PersistentObject can be any object that implements the Persistable interface. The Persistent Store API allows the implicit persistence of classes, so the following data types automatically implement the Persistable interface and can also be stored in the persistent store: • • • • • • • • • •

java.lang.Boolean java.lang.Byte java.lang.Character java.lang.Integer java.lang.Long java.lang.Object java.lang.Short java.lang.String java.util.Vector java.util.Hashtable

The storage for each application is distinct because each object in the persistent store is associated with a 64-bit ID (type long).

Security of persistent objects BlackBerry® Device Software provides two main ways to secure objects in the persistent store: • •

Restrict access to the objects with the net.rim.device.api.system.ControlledAccess class and code signing keys. Encrypt and decrypt objects with the net.rim.device.api.system.PersistentContent class.

Depending on the needs of your application, you could use both, one, or neither of these approaches.

38


Development Guide

Security of persistent objects

Restricting access to persistent objects Objects that are stored using the persistent store are identified by a unique key, which has a value that is of primitive type long. To retrieve an object from the persistent store, the key that was associated with the object when it was stored must be supplied in method calls. For example: long key = 0x52834c0559055c3L; PersistentObject rec = PersistentStore.getPersistentObject(key); String stored_value = (String) rec.getContents();

Relying on the key value being unknown may not provide adequate privacy for stored information. If you want to permit only specific, authorized applications to access your application data, you should wrap the PersistentObject in a net.rim.device.api.system.ControlledAccess class in conjunction with key generation and a key-signing procedure: • • • •

Create a signing key using the BlackBerry® Signing Authority Tool. Associate a ControlledAccess object with the signing key. Wrap the PersistentObject in the ControlledAccess object. Sign applications that require access to the protected data using the same signing key that protects the data.

Here is an example of how to retrieve data that is protected with the ControlledAccess class: long key = 0x52834c0559055c3L; try { PersistentObject rec = PersistentStore.getPersistentObject(key); int moduleHandle = ApplicationDescriptor.currentApplicationDescriptor ().getModuleHandle(); CodeSigningKey codeSigningKey = CodeSigningKey.get( moduleHandle, "MDW" ); String b = (String) rec.getContents( codeSigningKey ); Dialog.inform("Read PersistentObject. Value="+b); } catch (ControlledAccessException e) { Dialog.alert("ControlledAccessException - not authorised to read this data"); }

For more information about restricting access to persistent objects, see Protect persistent objects from access by unauthorized applications. For more information about code signing, see the BlackBerry Java SDK Security Development Guide, available at www.blackberry.com/go/devguides. Code sample The .cod file for the following code must be signed using the key that is used as the CodeSigningKey to protect the data. import import import import import

net.rim.device.api.system.ApplicationDescriptor; net.rim.device.api.system.CodeSigningKey; net.rim.device.api.system.ControlledAccess; net.rim.device.api.system.PersistentObject; net.rim.device.api.system.PersistentStore; 39


Development Guide

Security of persistent objects

import net.rim.device.api.ui.*; public class ControlledAccessCreator extends UiApplication { public static void main(String[] args) { ControlledAccessCreator cac = new ControlledAccessCreator(); cac.enterEventDispatcher(); } private ControlledAccessCreator() { ControlledAccessCreatorScreen screen = new ControlledAccessCreatorScreen(); pushScreen(screen); } } class ControlledAccessCreatorScreen extends MainScreen { private MenuItem _initialiseMenuItem = new MenuItem("Initialise", 100, 10) { public void run() { String a = "Hello"; long key = 0x52834c0559055c3L; PersistentObject rec = PersistentStore.getPersistentObject(key); int moduleHandle = ApplicationDescriptor.currentApplicationDescriptor().getModuleHandle(); CodeSigningKey codeSigningKey = CodeSigningKey.get( moduleHandle, "MDW" ); rec.setContents(new ControlledAccess(a,codeSigningKey)); rec.commit(); String b = (String) rec.getContents( codeSigningKey ); Dialog.inform("Initialised... now try to acccess the data from another app"); invalidate(); } }; ControlledAccessCreatorScreen() { setTitle(new LabelField("ControlledAccess Demo", LabelField.USE_ALL_WIDTH)); addMenuItem(_initialiseMenuItem); } public boolean onSavePrompt() { return true; } public void close() { super.close(); } }

The .cod file for the following code must be signed using the key that was used for the sample code above. import import import import import import import

net.rim.device.api.system.ApplicationDescriptor; net.rim.device.api.system.CodeSigningKey; net.rim.device.api.system.ControlledAccess; net.rim.device.api.system.ControlledAccessException; net.rim.device.api.system.PersistentObject; net.rim.device.api.system.PersistentStore; net.rim.device.api.ui.*;

public class ControlledAccessAuthorisedUser extends UiApplication { public static void main(String[] args) { ControlledAccessAuthorisedUser caau = new ControlledAccessAuthorisedUser(); caau.enterEventDispatcher(); 40


Development Guide

Security of persistent objects

} private ControlledAccessAuthorisedUser() { ControlledAccessAuthorisedUserScreen screen = new ControlledAccessAuthorisedUserScreen(); pushScreen(screen); } } class ControlledAccessAuthorisedUserScreen extends MainScreen { private MenuItem _readMenuItem = new MenuItem("Read protected data", 100, 10) { public void run() { long key = 0x52834c0559055c3L; try { PersistentObject rec = PersistentStore.getPersistentObject(key); int moduleHandle = ApplicationDescriptor.currentApplicationDescriptor().getModuleHandle(); CodeSigningKey codeSigningKey = CodeSigningKey.get( moduleHandle, "MDW" ); String b = (String) rec.getContents( codeSigningKey ); Dialog.inform("Read PersistentObject. Value="+b); } catch (ControlledAccessException e) { Dialog.alert("ControlledAccessException - not authorised to read this data"); } } }; ControlledAccessAuthorisedUserScreen() { setTitle(new LabelField("ControlledAccess Demo", LabelField.USE_ALL_WIDTH)); addMenuItem(_readMenuItem); }

}

public boolean onSavePrompt() { return true; } public void close() { super.close(); }

Restrict access to persistent store data using code signing keys Code signing keys can be used to control access to the persistent store. This is a way to restrict or share access with other apps on a BlackBerry速 device. 1.

Import the required classes and interfaces. import java.util.Hashtable; import net.rim.device.api.system.PersistentObject;

2.

Create a hash ID for the object you want to store in a persistent object. long MY_DATA_ID = 0x33abf322367f9018L; Hashtable myHashtable = new Hashtable();

3.

Store the object in the persistent object and protect the object with the CodeSigningKey object. For example, after a BlackBerry device application runs the following line of code, only .cod files that are signed with the RSAE .key file can read or overwrite the object in the persistent object. 41


Development Guide

Performance of the persistent store

persistentObject.setContents( new ControlledAccess( myHashtable, key ) );

4.

Make sure that the object is protected, and invoke getContents using the CodeSigningKey object as a parameter. Hashtable myHashtable = (Hashtable) persistentObject.getContents( key );

Performance of the persistent store The persistent store exists in application storage. There is more latency in writing to application storage than there is in reading from it. Reading from the persistent store is relatively fast while commits are relatively slow.

Best practice: Using efficient data structure selection Data structure selection defines how many object handles and how much flash memory a BlackBerry® Java Application consumes. Improper data structure selection can consume key resources without improving the BlackBerry Java Application functionality or the BlackBerry device user experience. Consider the following guidelines: • • •

The data structure should consist of the least possible number of objects, especially when you use high-level objects like a Vector or a Hashtable. These classes provide significant functionality but are not efficient storage mechanisms and you should avoid using them in the persistent store if possible. When possible, use primitives instead of objects, because primitives reduce the number of object handles that are consumed on the BlackBerry device. An array of primitives is an object and consumes an object handle. String objects are as efficient as byte arrays. A String object consumes only one object handle and is equivalent if your application stores all of the characters as a byte. In other words, the value of each character is less than or equal to the decimal value of 255. If your application cannot store characters as a byte, you can store the characters as a String, which is equivalent to storing a char array.

Best practice: Conserving object handles One of the most common errors that application developers encounter is an exhaustion of persistent object handles. The amount of application storage space on the BlackBerry® device determines the fixed number of persistent object handles that are available in the system. Depending on the data structure selection, stored records can quickly exhaust the number of persistent object handles. A persistent object consumes a persistent object handle and an object handle. A transient object consumes only an object handle. For example, a record that contains ten String fields, which represent items such as a name, a phone number, and an address, consumes 11 persistent object handles, one for the record object and one for each String. If an application persists 3000 records, the application consumes 33,000 persistent object handles, which exceeds the number of persistent object handles available on a BlackBerry device with 16 MB of flash memory. You can use the net.rim.device.api.system.ObjectGroup class to consolidate the object handles for an object into one group. Using the example in the previous paragraph, if you group the record, the record consumes one persistent object handle instead of 11. The object handles for the String fields consolidate under the record object handle. 42


Development Guide

Creating a persistent store

When you consolidate object handles into one group, the object handle is read-only. You must ungroup the object before you can change it. After you complete the changes, group the object again. If you attempt to change a grouped object without first ungrouping it, an ObjectGroupReadOnlyException is thrown. Ungrouping an object has a performance impact. The system creates a copy of the grouped object and allocates handles to each of the objects inside that group. Therefore, objects should only be ungrouped when necessary. It is possible for commits to the persistent store to occur during garbage collection without an explicit commit(), so grouping of objects should always occur before calls to setContents() or commit(). For more information about object grouping, see net.rim.device.api.system.ObjectGroup .

Cleanup of persistent objects When an application is removed from a BlackBerry® device, persistent objects that are defined within the application are automatically deleted. This is because each persistent object has a class type that is defined in the application. When the application is removed, the class type is deleted, so the persistent objects are deleted. To ensure cleanup of the persistent storage you use, you should always store your instances of your own classes or your own extensions of provided classes. To delete individual data, treat the data as normal objects, and remove references to it. A garbage collection operation removes the data.

Creating a persistent store To create a persistent store, you create at least one PersistentObject. Each PersistentObject has a unique key of type long.

Create a persistent data store Each PersistentObject has a unique long key. 1.

Import the required classes and interfaces. import import import import

2. 3. 4. 5.

net.rim.device.api.system.PersistentObject; net.rim.device.api.system.PersistentStore; java.lang.String; net.rim.device.api.ui.component.Dialog;

To create a unique long key, in the BlackBerry® Integrated Development Environment, type a string value. For exmaple, com.rim.samples.docs.userinfo Right-click the string and click Convert ‘com.rim.samples.docs.userinfo’ to long. Include a comment in your code to indicate the string that you used to generate the unique long key. To create a persistent data store, create a single static PersistentObject and invoke PersistentStore.getPersistentObject, using the unique long key as a parameter.

43


Development Guide

Working with the persistent store

static PersistentObject store; static { store = PersistentStore.getPersistentObject( 0xa1a569278238dad2L ); }

Store persistent data 1.

Import the required classes and interfaces. import net.rim.device.api.system.PersistentObject; import net.rim.device.api.system.PersistentStore;

2. 3.

Invoke setContents() on a PersistentObject. This method replaces the existing content with the new content. To save the new content to the persistent store, invoke commit(). String[] userinfo = {username, password}; synchronized(store) { store.setContents(userinfo); store.commit(); }

4.

To use a batch transaction to commit objects to the persistent store, invoke PersistentStore.getSynchObject(). This method retrieves the persistent store monitor that locks the object. a. Synchronize on the object. b. Invoke commit() as necessary. If any commit in the batch fails, the entire batch transaction fails.

5.

To commit a monitor object separately from a batch transaction, invoke forceCommit() while synchronizing the monitor object.

Store an object in a batch transaction 1.

2. 3.

To use a batch transaction to commit objects to the persistent store, invoke PersistentStore.getSynchObject(). This method retrieves the persistent store monitor that locks the object. Synchronize on the object. Invoke commit() as necessary. If any commit in the batch fails, the entire batch transaction fails.

Working with the persistent store You can retrieve and remove objects and collections from the persistent store.

Retrieve persistent data 1. 44

Import the required classes and interfaces.


Development Guide

Working with the persistent store

import net.rim.device.api.system.PersistentObject; import net.rim.device.api.ui.component.Dialog;

2. 3.

Invoke getContents() on a PersistentObject. To convert to your desired format, perform an explicit cast on the object that PersistentObject.getContents() returns. synchronized(store) { String[] currentinfo = (String[])store.getContents(); if(currentinfo == null) { Dialog.alert(_resources.getString(APP_ERROR)); } else { currentusernamefield.setText(currentinfo[0]); currentpasswordfield.setText(currentinfo[1]); } }

Retrieve a collection from persistent storage 1.

Import the required classes and interfaces. import java.util.Vector; import net.rim.device.api.system.PersistentStore; import net.rim.device.api.synchronization.SyncCollection;

2.

To provide the BlackBerry速 device application with access to the newest SyncCollection data from the PersistentStore, invoke the PersistentStore.getPersistentObject() method using the ID of the SyncCollection. private PersistentObject _persist; private Vector _contacts; private static final long PERSISTENT_KEY = 0x266babf899b20b56L; _persist = PersistentStore.getPersistentObject( PERSISTENT_KEY );

3.

Store the returned data in a vector object. _contacts = (Vector)_persist.getContents();

4.

Create a method to provide the BlackBerry device application with the newest SyncCollection data before a wireless data backup session begins. public void beginTransaction() { _persist = PersistentStore.getPersistentObject(PERSISTENT_KEY); _contacts = (Vector)_persist.getContents(); }

5.

Create code to manage the case where the SyncCollection you retrieve from the PersistentStore is empty. if( _contacts == null ) { _contacts = new Vector();

45


Development Guide

_persist.setContents( _contacts ); _persist.commit(); }

46

Working with the persistent store


Storing objects nonpersistently

Development Guide

Storing objects nonpersistently

5

The runtime store provides a central location for applications to store and share information on a BlackBerry® device. Data in the runtime store is not saved when the BlackBerry device is restarted. The RuntimeStore API was introduced with BlackBerry® Device Software 3.6. The runtime store is implemented in the net.rim.device.api.system.RuntimeStore class. Objects are stored using a key-value pair. When you store an object in the runtime store, you assign the object a unique ID of type long and later use the ID to retrieve the object from the store. You can generate the unique ID in the Eclipse® editor by right-clicking the fully-qualified class name and clicking Convert 'name' to long. Note: Before your application closes, remove objects from the runtime store that your application no longer requires. If you add an object instance to the runtime store and don't remove it, you could create a memory leak.

Common uses of the runtime store You can use the runtime store to store any object, and you can retrieve the object from a different process or a different application. You can also restrict access to data. Here are some common uses of the runtime store: Share data between two applications

Store a reference to an object for later use

Implement system-wide singletons

For example, an application suite could be made up of multiple applications, all of which use data that is pushed to the device. One of the applications receives all the push data and shares it with the other applications by temporarily storing the data in the runtime store. The runtime store could also be used to set up communication between a listener (such as a PushListener) and a running application. For example, an application that allows a BlackBerry device user to add and remove an ApplicationMenuItem could use the runtime store to store a reference to an ApplicationMenuItem it has registered. After the application is closed and reopened, the ApplicationMenuItem can be accessed and unregistered. An application might require one or more singleton objects to be accessed from within the application itself or by other applications.

Security of the runtime store By default, only BlackBerry® device applications that Research In Motion digitally signs can access data in the runtime store.

Restrict access to runtime store data using code signing keys Code signing keys can be used to control access to the runtime store. This is a way to restrict or share access with other apps on a BlackBerry® device. 1.

Import the required classes and interfaces. 47


Development Guide

Add an object to the runtime store

import java.util.Hashtable; import net.rim.device.api.system.RuntimeStore;

2.

Create a hash ID for the object you want to store in the runtime store. long MY_DATA_ID = 0x33abf322367f9018L; Hashtable myHashtable = new Hashtable();

3.

Store the object in the runtime store and protect the object with the CodeSigningKey object. Only applications signed with the key can read or change the object. RuntimeStore.put( MY_DATA_ID, new ControlledAccess( myHashtable, key ) );

4.

Make sure that the object is protected with a particular code signing key, and invoke RuntimeStore.get, providing as parameters the hash ID for the object and the CodeSigningKey object.

Add an object to the runtime store 1. 2.

Invoke RuntimeStore.put(long, String) and provide as parameters a unique long ID and the runtime object to store. Create a try/catch block to manage the IllegalArgumentException that put() throws if a runtime object with the same ID exists. RuntimeStore store = RuntimeStore.getRuntimeStore(); String msg = "Some shared text"; long ID = 0x60ac754bc0867248L; try { store.put( ID, msg ); } catch(IllegalArgumentException e) { }

Replace an object in the runtime store 1. 2.

Invoke replace(). Create a try/catch block to manage the ControlledAccessException that replace() throws if the runtime object with the specified ID does not exist. RuntimeStore store = RuntimeStore.getRuntimeStore(); String newmsg = "Some new text"; try { Object obj = store.replace( 0x60ac754bc0867248L, newmsg); } catch(ControlledAccessException e) { } not exist.

Retrieve the runtime store 1.

48

Import the required classes and interfaces.


Development Guide

import import import import import import

2.

Retrieve a registered runtime object

java.lang.IllegalArgumentException; java.lang.RuntimeException; java.lang.String; net.rim.device.api.system.ControlledAccessException; net.rim.device.api.system.RuntimeStore; net.rim.device.api.util.ListenerUtilities;

Invoke RuntimeStore.getRuntimeStore(). RuntimeStore store = RuntimeStore.getRuntimeStore();

Retrieve a registered runtime object 1. 2.

Invoke RuntimeStore.get() and provide as a parameter the runtime object ID. Create a try/catch block to manage the ControlledAccessException that get() throws if the application does not have read access to the specified runtime object. RuntimeStore store = RuntimeStore.getRuntimeStore(); try { Object obj = store.get(0x60ac754bc0867248L); } catch(ControlledAccessException e) { }

Retrieve an unregistered runtime object 1. 2.

Invoke RuntimeStore.waitFor() to wait for registration of a runtime object to complete. If the runtime object with the specified ID does not exist, waitFor() blocks for a maximum of MAX_WAIT_MILLIS. Create code for handling exceptions. RuntimeStore store = RuntimeStore.getRuntimeStore(); try { Object obj = store.waitFor(0x60ac754bc0867248L); } catch(ControlledAccessException e) { } catch(RuntimeException e) { }

Code sample: Storing a String in the runtime store For simplicity, this example does not show how to create the unique ID. import net.rim.device.api.system.Application; import net.rim.device.api.system.RuntimeStore; public class RuntimeSet extends Application { public static void main(String[] args) { RuntimeSet app = new RuntimeSet(); System.exit(0); 49


Development Guide

Code sample: Getting a stored String from the runtime store

}

}

public RuntimeSet() { RuntimeStore rts = RuntimeStore.getRuntimeStore(); long ID = 0x60ac754bc0867248L; //just a unique ID - generate any way you want rts.put(ID, "Shared Message"); }

Code sample: Getting a stored String from the runtime store For simplicity, this example does not show how to create the unique ID. import import import import import

net.rim.device.api.system.RuntimeStore; net.rim.device.api.ui.UiApplication; net.rim.device.api.ui.component.Dialog; net.rim.device.api.ui.component.LabelField; net.rim.device.api.ui.container.MainScreen;

public class RuntimeGet extends UiApplication { public static void main(String[] args) { RuntimeGet app = new RuntimeGet(); app.enterEventDispatcher(); } public RuntimeGet() { RuntimeStore rts = RuntimeStore.getRuntimeStore(); long ID = 0x60ac754bc0867248L; //just a unique ID - generate any way you want String msg = (String)rts.get(ID); pushScreen(new HomeScreen(msg)); } } class HomeScreen extends MainScreen { public HomeScreen(String msg) { add(new LabelField(msg)); } }

50


Development Guide

Code sample: Creating a singleton using the RuntimeStore API

Code sample: Creating a singleton using the RuntimeStore API The following example creates a singleton using the runtime store. In this example, the static variable _instance is initialized to null for each process running on the system, so getInstance() must check the _instance variable each time it is invoked. For simplicity, this example does not show how to create the unique ID. import net.rim.device.api.system.*; class MySingleton { private static MySingleton _instance; private static final long GUID = 0xab4dd61c5d004c18L; // constructor MySingleton() {} public static MySingleton getInstance() { if (_instance == null) { _instance = (MySingleton)RuntimeStore.getRuntimeStore().get(GUID); if (_instance == null) { MySingleton singleton = new MySingleton();

}

RuntimeStore.getRuntimeStore().put(GUID, singleton); _instance = singleton; }

return _instance; }

}

51


Storing data in the record store

Development Guide

Storing data in the record store

6

The MIDP specification provides persistent storage for MIDlets. This mechanism is called the MIDP Record Management System (RMS), or record store. It is modeled after a simple record-oriented database. The record store is the MIDP equivalent of the RIM PersistentStore API and is available on all MIDP devices. The RMS API is implemented in the javax.microedition.rms class. While it is designed for MIDlets, the record store can also be used in BlackBerry速 device applications. The record store provides a simple record management system that allows you to create a data store object and persist a series of records within that object. Each record is a byte array, so you must serialize your data into a byte array format before storing it locally. Each byte array is assigned an integer ID that you use later to retrieve the byte array. Retrieval is done by enumerating over the records. The RMS API does not provide any inherent indexing or relationships between records. Applications that use the record store can either make data private or allow sharing. The record store is frequently used to share data between applications. Data that an application saves in a record store is automatically deleted when the application is removed. When you upgrade an application that uses the record store, the data is retained. Here are the maximum storage sizes for the record store: BlackBerry速 Device Software version Maximum individual record store size Earlier than 4.1 64 KB 4.1 to 4.5 64 KB 4.6 or later 512 KB

Maximum total record store size (cumulative for all applications) 64 KB Available device memory Available device memory

Create a record store 1. 2.

Import the javax.microedition.rms.RecordStore class. Invoke openRecordStore(), and specify true to indicate that the method should create the record store if the record store does not exist. RecordStore store = RecordStore.openRecordStore("Contacts", true);

Add a record to a record store 1. 2.

Import the javax.microedition.rms.RecordStore class. Invoke addRecord(). int id = store.addRecord(_data.getBytes(), 0, _data.length());

52


Development Guide

Retrieve a record from a record store

Code sample: Adding a record to the record store The following code sample shows you how to add a byte array using the RMS API. int authMode = RecordStore.AUTHMODE_ANY; boolean bWrite = true; rs = RecordStore.openRecordStore( "rs", true, authMode, bWrite ); byte[] pi = new byte[]{ 3, 1, 4, 1, 5, 9 }; int recordID; recordID = rs.addRecord(pi, 0, pi.length);

Retrieve a record from a record store 1.

Import the required classes and interfaces. import java.lang.String; import javax.microedition.rms.RecordStore;

2.

Invoke getRecord(int, byte[], int). Pass the following parameters: • a record ID • a byte array • an offset byte[] data = new byte[store.getRecordSize(id)]; store.getRecord(id, data, 0); String dataString = new String(data);

Retrieve all records from a record store 1.

Import the required classes and interfaces. import import import import

2. 3.

javax.microedition.rms.RecordComparator; javax.microedition.rms.RecordEnumeration; javax.microedition.rms.RecordFilter; javax.microedition.rms.RecordStore;

Invoke openRecordStore(). Invoke enumerateRecords(). Pass the following parameters: • filter: specifies a RecordFilter object to retrieve a subset of record store records (if null, the method returns all records) • comparator: specifies a RecordComparator object to determine the order in which the method returns the records (if null, the method returns the records in any order) • keepUpdated: determines if the method keeps the enumeration current with the changes to the record store 53


Development Guide

Code sample: Storing and retrieving data with the record store

RecordStore store = RecordStore.openRecordStore("Contacts", false); RecordEnumeration e = store.enumerateRecords(null, null, false);

Code sample: Storing and retrieving data with the record store This example uses the RMS API to store and retrieve high scores for a game. In the example, high scores are stored in separate records, and sorted when necessary using a RecordEnumeration. import import import import import import import

javax.microedition.rms.*; java.io.ByteArrayInputStream; java.io.ByteArrayOutputStream; java.io.DataInputStream; java.io.DataOutputStream; java.io.EOFException; java.io.IOException;

/** * A class used for storing and showing game scores. */ public class RMSGameScores implements RecordFilter, RecordComparator { /* * The RecordStore used for storing the game scores. */ private RecordStore recordStore = null; /* * The player name to use when filtering. */ public static String playerNameFilter = null; /* * Part of the RecordFilter interface. */ public boolean matches(byte[] candidate) throws IllegalArgumentException { // If no filter set, nothing can match it. if (this.playerNameFilter == null) { return false; } ByteArrayInputStream bais = new ByteArrayInputStream(candidate); DataInputStream inputStream = new DataInputStream(bais); String name = null; try { int score = inputStream.readInt(); name = inputStream.readUTF(); } catch (EOFException eofe) { 54


Code sample: Storing and retrieving data with the record store

Development Guide

System.out.println(eofe); eofe.printStackTrace();

}

} catch (IOException eofe) { System.out.println(eofe); eofe.printStackTrace(); } return (this.playerNameFilter.equals(name));

/* * Part of the RecordComparator interface. */ public int compare(byte[] rec1, byte[] rec2) { // Construct DataInputStreams for extracting the scores from // the records. ByteArrayInputStream bais1 = new ByteArrayInputStream(rec1); DataInputStream inputStream1 = new DataInputStream(bais1); ByteArrayInputStream bais2 = new ByteArrayInputStream(rec2); DataInputStream inputStream2 = new DataInputStream(bais2); int score1 = 0; int score2 = 0; try { // Extract the scores. score1 = inputStream1.readInt(); score2 = inputStream2.readInt(); } catch (EOFException eofe) { System.out.println(eofe); eofe.printStackTrace(); } catch (IOException eofe) { System.out.println(eofe); eofe.printStackTrace(); }

}

// Sort by score if (score1 < score2) { return RecordComparator.PRECEDES; } else if (score1 > score2) { return RecordComparator.FOLLOWS; } else { return RecordComparator.EQUIVALENT; }

/** * The constructor opens the underlying record store, * creating it if necessary. */ public RMSGameScores() { //

55


Development Guide

}

Code sample: Storing and retrieving data with the record store

// Create a new record store for this example // try { recordStore = RecordStore.openRecordStore("scores", true); } catch (RecordStoreException rse) { System.out.println(rse); rse.printStackTrace(); }

/** * Add a new score to the storage. * * @param score the score to store. * @param playerName the name of the play achieving this score. */ public void addScore(int score, String playerName) { // // Each score is stored in a separate record, formatted with // the score, followed by the player name. // ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream outputStream = new DataOutputStream(baos); try { // Push the score into a byte array. outputStream.writeInt(score); // Then push the player name. outputStream.writeUTF(playerName); } catch (IOException ioe) { System.out.println(ioe); ioe.printStackTrace(); }

}

// Extract the byte array byte[] b = baos.toByteArray(); // Add it to the record store try { recordStore.addRecord(b, 0, b.length); } catch (RecordStoreException rse) { System.out.println(rse); rse.printStackTrace(); }

/** * A helper method for the printScores methods. */ private void printScoresHelper(RecordEnumeration re) { try { while(re.hasNextElement()) { int id = re.nextRecordId();

56


Development Guide

Code sample: Storing and retrieving data with the record store

ByteArrayInputStream bais = new ByteArrayInputStream(recordStore.getRecord(id)); DataInputStream inputStream = new DataInputStream(bais); try { int score = inputStream.readInt(); String playerName = inputStream.readUTF(); System.out.println(playerName + " = " + score); } catch (EOFException eofe) { System.out.println(eofe); eofe.printStackTrace(); } } } catch (RecordStoreException rse) { System.out.println(rse); rse.printStackTrace(); } catch (IOException ioe) { System.out.println(ioe); ioe.printStackTrace(); } } /** * This method prints all of the scores sorted by game score. */ public void printScores() { try { // Enumerate the records using the comparator implemented // above to sort by game score. RecordEnumeration re = recordStore.enumerateRecords(null, this, true); printScoresHelper(re); } catch (RecordStoreException rse) { System.out.println(rse); rse.printStackTrace(); } } /** * This method prints all of the scores for a given player, * sorted by game score. */ public void printScores(String playerName) { try { // Enumerate the records using the comparator and filter // implemented above to sort by game score. RecordEnumeration re = recordStore.enumerateRecords(this, this, true); printScoresHelper(re); } catch (RecordStoreException rse) {

57


Code sample: Storing and retrieving data with the record store

Development Guide

}

}

58

}

System.out.println(rse); rse.printStackTrace();

public static void main(String[] args) { RMSGameScores rmsgs = new RMSGameScores(); rmsgs.addScore(100, "Megan"); rmsgs.addScore(120, "Enrico"); rmsgs.addScore(80, "Andrea"); rmsgs.addScore(40, "Stephen"); rmsgs.addScore(200, "Sherisse"); rmsgs.addScore(110, "Acheson"); rmsgs.addScore(220, "Acheson"); System.out.println("All scores"); rmsgs.printScores(); System.out.println("Acheson's scores"); RMSGameScores.playerNameFilter = "Acheson"; rmsgs.printScores("Acheson"); }


Development Guide

Managing data

Managing data

7

The BlackBerry® Java® Virtual Machine manages memory usage on the BlackBerry device. The BlackBerry JVM allocates memory, performs garbage collection, and automatically swaps data between random access memory and application storage. BlackBerry® Device Software provides a number of tools to help you manage data in your applications. These tools provide ways to back up data, remove sensitive data, remove data that is no longer needed, and free up memory when needed.

Best practice: Minimizing memory use To minimize runtime memory, consider the following guidelines: • • • • • •

Use primitive types (such as int or Boolean) instead of objects (such as String or Integer). Do not depend entirely on the garbage collector. Avoid creating many objects quickly. Set object references to null when you are finished using them. Reuse objects as much as possible. Move heavy processing to the server. For example, you can filter or sort data before sending it to the BlackBerry® device.

Removing sensitive data The memory cleaner can delete sensitive data that is stored in memory on a BlackBerry® device. Specific events trigger the memory cleaner to clear various caches and perform secure garbage collection. The memory cleaner is not on by default, but is turned on automatically when you enable encryption. To manually turn on the memory cleaner, the device user clicks Options > Security Options > Advanced Security Options > Memory Cleaning and sets the status to Enabled. The Memory Cleaner API is implemented in the net.rim.device.api.memorycleaner package. Users can configure which events trigger a memory cleaning. You can register your application to be notified if one of those events occurs. To do so, implement the MemoryCleanerListener interface and register it using one of the static methods MemoryCleanerDaemon.addListener() or MemoryCleanerDaemon.addWeakListener(). The MemoryCleanerListener interface has two methods, cleanNow() and getDescription(). The cleanNow() method is invoked by the memory cleaner when a user configurable event occurs. The memory cleaner passes an event parameter when it calls cleanNow() to indicate the event that initiated the memory clean request. The getDescription() method is invoked by the memory cleaner if the memory cleaner must display information about the applications that are registered cleaners. This functionality is required, for example, on the Memory Cleaning option screen.

59


Development Guide

Using the Garbage Collector

Using the Garbage Collector The BlackBerry® Java® Virtual Machine includes the Garbage Collector, which runs periodically to remove unreferenced and weakly referenced objects from memory. Do not call the Garbage Collector directly, but release resources by setting their reference to null after use.

Full garbage collection on a BlackBerry device The full garbage collection operation executes for 1 second on average and should take less than 2 seconds to complete. The full garbage collection operation performs the following actions: • • •

It performs a RAM garbage collection operation. It marks objects in flash memory that are no longer referenced or no longer persisted. It releases any nonpersistent object handles in RAM and flash memory.

The system might initiate a full garbage collection operation in the following situations: • • • •

The BlackBerry® Java® Virtual Machine cannot allocate an object because of a lack of available space in RAM. A process is about to exceed its currently allocated heap size. The BlackBerry JVM cannot allocate a new object because the object handles are not available. The BlackBerry device is idle.

RAM garbage collection on a BlackBerry device The BlackBerry® Java® Virtual Machine initiates a RAM garbage collection operation only when the BlackBerry JVM cannot allocate an object because of a lack of space in RAM. The RAM garbage collection operation typically takes 500 to 600 milliseconds to execute. The garbage collection operation removes any freshly allocated variables that are no longer referenced in RAM. To make sure that the lack of a reference in RAM is a sufficient condition for removing the object, a RAM garbage collection operation can only be performed when objects have not been paged out to flash memory.

Idle garbage collection on a BlackBerry device Garbage collection does not occur every time the BlackBerry® device idles. It occurs only when the system considers a garbage collection operation to be beneficial for optimal system performance and maximized battery performance. To improve performance without impacting the BlackBerry device user experience, the system attempts to perform the following garbage collection operations when the BlackBerry device idles: • •

60

A full garbage collection operation can occur when the BlackBerry device idles for a relatively short period of time. A thorough garbage collection operation can occur when the BlackBerry device idles for a significant period of time.


Development Guide

Managing low memory

Managing low memory When the available memory on a BlackBerry® device falls below the threshold that the device requires to function correctly, the Low Memory Manager attempts to make more memory available. The Low Memory Manager prioritizes objects in memory and marks the less critical objects for deletion by the BlackBerry® Java® Virtual Machine. Opened messages and older calendar entries are typically deleted first. The Low Memory Manager API is implemented in net.rim.device.api.lowmemory . You should design your application to work with the Low Memory Manager to make available as much memory as possible when the device is low on memory resources. To do so, implement the LowMemoryListener interface and register it with the Low Memory Manager by calling the static LowMemoryManager.addLowMemoryListener() method. The LowMemoryListener interface has a single method, freeStaleObject(), that is invoked by the Low Memory Manager when it needs to make memory available. When it invokes freeStaleObject(), the Low Memory Manager passes a priority parameter to indicate that it is initiating a high, medium, or low memory recovery request. Be careful to return true from freeStaleObject() if you freed any resources and false otherwise. This is important because the Low Memory Manager needs an accurate accounting of the memory freeing progress.

Identifying low memory availability on a BlackBerry device The following conditions can cause the Low Memory Manager to attempt to free memory resources: • • •

The amount of available memory on the BlackBerry® device falls below a certain threshold. The threshold depends on the amount of free RAM in the system. The memory threshold ranges from 400 KB to 800 KB. The number of persistent object handles that are available on the BlackBerry device falls below 1000. The number of object handles that are available on the BlackBerry device falls below 1000.

Backing up data The BlackBerry® Device Manager provides a backup and restore tool that a BlackBerry device user can use to save BlackBerry device data to a file on a computer and to restore data to the BlackBerry device. With the Synchronization API, you can create applications that integrate with the BlackBerry® Desktop Manager or BlackBerry® Enterprise Server to back up data from a BlackBerry device. The Synchronization API is implemented in the net.rim.device.api.synchronization package. When an application uses the Synchronization API, the BlackBerry Desktop Manager can back up and restore the application database at the same time as other BlackBerry device databases. You can use the Synchronization API to create data archives or to populate application databases the first time the BlackBerry device connects to the BlackBerry device user's computer.

61


Development Guide

Backing up data

To synchronize data to remote data sources, you must build the synchronization logic into your application. Most applications send data to a server-side application using standard HTTP or TCP/IP protocols over the wireless network and the Internet or an intranet. You can use XML APIs to generate and parse XML-formatted data to send and receive over the wireless network. However, your client-side and server-side applications must read and write the data properly and acknowledge successful transmission of the data. Your BlackBerry device application might connect to an application on a computer to send the data over a USB connection with the Synchronization API and the BlackBerry Desktop Manager. In this case, the desktop application must be able to read the data from your BlackBerry device application through an add-in task for the BlackBerry Desktop Manager. The BlackBerry device user must manually execute the synchronization by running the BlackBerry Desktop Manager add-in, which notifies the application on the BlackBerry device to send the data to the computer application. You can also write data to the desktop application using the native USB protocols. To enable an application to back up data, you can implement the following Synchronization interfaces and use the SyncManager class to register your application for synchronization. Interface SyncConverter SyncCollection SyncObject

Description Converts data between the SyncObject format that is required on the BlackBerry device and the serialized format that is required on the computer. Represents the collection of synchronization objects for an application. Represents an object that can be backed up and restored.

The following sample applications are included with the BlackBerry速 Java速 Development Environment: SyncDemo, OTASyncDemo, and OTABackupRestoreDemo. To back up and restore a small amount of data such as application configuration options, you do not have to implement all of these interfaces. Instead, you can extend the SyncItem class and implement its abstract methods. The SyncItem class implements the SyncCollection, SyncConverter, and SyncObject interfaces for you. For more information, see Backup and restore small amounts of data using SyncItem.

62


Development Guide

Find more information • • • • •

Find more information

8

www.blackberry.com/go/apiref: View the latest version of the API reference for the BlackBerry® Java® SDK. www.blackberry.com/go/devguides: Find development guides, release notes, and sample application overviews for the BlackBerry Java SDK. www.blackberry.com/developers: Visit the BlackBerry® Developer Zone for resources on developing BlackBerry device applications. www.blackberry.com/go/developerkb: View knowledge base articles on the BlackBerry Development Knowledge Base. www.blackberry.com/developers/downloads: Find the latest development tools and downloads for developing BlackBerry device applications.

63


Development Guide

Provide feedback To provide feedback on this deliverable, visit www.blackberry.com/docsfeedback.

64

Provide feedback

9


Development Guide

Glossary

Glossary

10

application storage Application storage is internal to the BlackBerry device. It contains the operating system, BlackBerry速 JVM, and an internal file system. Application storage is also known as flash memory and onboard memory. Applications on a BlackBerry device can be run only from the application storage. All BlackBerry devices have application storage. built-in media storage Built-in media storage is a storage location on an eMMC. It is not removable. A FAT file system is mounted on the built-in media card. Built-in media storage is also called internal media memory and onboard device memory. eMMC embedded MultiMediaCard FAT File Allocation Table flash memory The flash memory is an internal file system on a BlackBerry device that stores application data and user data. JSR Java速 Specification Request media card storage Media card storage is a storage location on a microSD card that a BlackBerry device user can use to extend the amount of storage on a device. The media card storage is optional and removable. A FAT file system is mounted on the media card. MIDP Mobile Information Device Profile persistent store in flash memory The persistent store in flash memory stores data for a BlackBerry device. By default, third-party applications cannot access the persistent store. When it deletes all device data, the BlackBerry device deletes the data in the persistent store. RIM signing authority system The RIM速 signing authority system is a collection of servers that sign the boot ROM code for a BlackBerry device during the manufacturing process. SQL Structured Query Language

65


Development Guide

UTF-8 8-bit UCS/Unicode Transformation Format

66

Glossary


Document revision history

Development Guide

Document revision history Date 22 February 2011 29 November 2010 26 November 2010 16 August 2010

11

Description Deleted topic: Remove persistent data Added topic: Using foreign key constraints Widespread edits Initial version

67


Development Guide

Legal notice

Legal notice

12

息2011 Research In Motion Limited. All rights reserved. BlackBerry速, RIM速, Research In Motion速, and related trademarks, names, and logos are the property of Research In Motion Limited and are registered and/or used in the U.S. and countries around the world. Java is a trademark of Oracle America, Inc. SQLite is a trademark of Hipp, Wyrick & Company, Inc. All other trademarks are the property of their respective owners. This documentation including all documentation incorporated by reference herein such as documentation provided or made available at www.blackberry.com/go/docs is provided or made accessible "AS IS" and "AS AVAILABLE" and without condition, endorsement, guarantee, representation, or warranty of any kind by Research In Motion Limited and its affiliated companies ("RIM") and RIM assumes no responsibility for any typographical, technical, or other inaccuracies, errors, or omissions in this documentation. In order to protect RIM proprietary and confidential information and/or trade secrets, this documentation may describe some aspects of RIM technology in generalized terms. RIM reserves the right to periodically change information that is contained in this documentation; however, RIM makes no commitment to provide any such changes, updates, enhancements, or other additions to this documentation to you in a timely manner or at all. This documentation might contain references to third-party sources of information, hardware or software, products or services including components and content such as content protected by copyright and/or third-party web sites (collectively the "Third Party Products and Services"). RIM does not control, and is not responsible for, any Third Party Products and Services including, without limitation the content, accuracy, copyright compliance, compatibility, performance, trustworthiness, legality, decency, links, or any other aspect of Third Party Products and Services. The inclusion of a reference to Third Party Products and Services in this documentation does not imply endorsement by RIM of the Third Party Products and Services or the third party in any way. EXCEPT TO THE EXTENT SPECIFICALLY PROHIBITED BY APPLICABLE LAW IN YOUR JURISDICTION, ALL CONDITIONS, ENDORSEMENTS, GUARANTEES, REPRESENTATIONS, OR WARRANTIES OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION, ANY CONDITIONS, ENDORSEMENTS, GUARANTEES, REPRESENTATIONS OR WARRANTIES OF DURABILITY, FITNESS FOR A PARTICULAR PURPOSE OR USE, MERCHANTABILITY, MERCHANTABLE QUALITY, NON-INFRINGEMENT, SATISFACTORY QUALITY, OR TITLE, OR ARISING FROM A STATUTE OR CUSTOM OR A COURSE OF DEALING OR USAGE OF TRADE, OR RELATED TO THE DOCUMENTATION OR ITS USE, OR PERFORMANCE OR NON-PERFORMANCE OF ANY SOFTWARE, HARDWARE, SERVICE, OR ANY THIRD PARTY PRODUCTS AND SERVICES REFERENCED HEREIN, ARE HEREBY EXCLUDED. YOU MAY ALSO HAVE OTHER RIGHTS THAT VARY BY STATE OR PROVINCE. SOME JURISDICTIONS MAY NOT ALLOW THE EXCLUSION OR LIMITATION OF IMPLIED WARRANTIES AND CONDITIONS. TO THE EXTENT PERMITTED BY LAW, ANY IMPLIED WARRANTIES OR CONDITIONS RELATING TO THE DOCUMENTATION TO THE EXTENT THEY CANNOT BE EXCLUDED AS SET OUT ABOVE, BUT CAN BE LIMITED, ARE HEREBY LIMITED TO NINETY (90) DAYS FROM THE DATE YOU FIRST ACQUIRED THE DOCUMENTATION OR THE ITEM THAT IS THE SUBJECT OF THE CLAIM. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW IN YOUR JURISDICTION, IN NO EVENT SHALL RIM BE LIABLE FOR ANY TYPE OF DAMAGES RELATED TO THIS DOCUMENTATION OR ITS USE, OR PERFORMANCE OR NONPERFORMANCE OF ANY SOFTWARE, HARDWARE, SERVICE, OR ANY THIRD PARTY PRODUCTS AND SERVICES REFERENCED HEREIN INCLUDING WITHOUT LIMITATION ANY OF THE FOLLOWING DAMAGES: DIRECT, CONSEQUENTIAL, EXEMPLARY, INCIDENTAL, INDIRECT, SPECIAL, PUNITIVE, OR AGGRAVATED DAMAGES, DAMAGES 68


Development Guide

Legal notice

FOR LOSS OF PROFITS OR REVENUES, FAILURE TO REALIZE ANY EXPECTED SAVINGS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, LOSS OF BUSINESS OPPORTUNITY, OR CORRUPTION OR LOSS OF DATA, FAILURES TO TRANSMIT OR RECEIVE ANY DATA, PROBLEMS ASSOCIATED WITH ANY APPLICATIONS USED IN CONJUNCTION WITH RIM PRODUCTS OR SERVICES, DOWNTIME COSTS, LOSS OF THE USE OF RIM PRODUCTS OR SERVICES OR ANY PORTION THEREOF OR OF ANY AIRTIME SERVICES, COST OF SUBSTITUTE GOODS, COSTS OF COVER, FACILITIES OR SERVICES, COST OF CAPITAL, OR OTHER SIMILAR PECUNIARY LOSSES, WHETHER OR NOT SUCH DAMAGES WERE FORESEEN OR UNFORESEEN, AND EVEN IF RIM HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW IN YOUR JURISDICTION, RIM SHALL HAVE NO OTHER OBLIGATION, DUTY, OR LIABILITY WHATSOEVER IN CONTRACT, TORT, OR OTHERWISE TO YOU INCLUDING ANY LIABILITY FOR NEGLIGENCE OR STRICT LIABILITY. THE LIMITATIONS, EXCLUSIONS, AND DISCLAIMERS HEREIN SHALL APPLY: (A) IRRESPECTIVE OF THE NATURE OF THE CAUSE OF ACTION, DEMAND, OR ACTION BY YOU INCLUDING BUT NOT LIMITED TO BREACH OF CONTRACT, NEGLIGENCE, TORT, STRICT LIABILITY OR ANY OTHER LEGAL THEORY AND SHALL SURVIVE A FUNDAMENTAL BREACH OR BREACHES OR THE FAILURE OF THE ESSENTIAL PURPOSE OF THIS AGREEMENT OR OF ANY REMEDY CONTAINED HEREIN; AND (B) TO RIM AND ITS AFFILIATED COMPANIES, THEIR SUCCESSORS, ASSIGNS, AGENTS, SUPPLIERS (INCLUDING AIRTIME SERVICE PROVIDERS), AUTHORIZED RIM DISTRIBUTORS (ALSO INCLUDING AIRTIME SERVICE PROVIDERS) AND THEIR RESPECTIVE DIRECTORS, EMPLOYEES, AND INDEPENDENT CONTRACTORS. IN ADDITION TO THE LIMITATIONS AND EXCLUSIONS SET OUT ABOVE, IN NO EVENT SHALL ANY DIRECTOR, EMPLOYEE, AGENT, DISTRIBUTOR, SUPPLIER, INDEPENDENT CONTRACTOR OF RIM OR ANY AFFILIATES OF RIM HAVE ANY LIABILITY ARISING FROM OR RELATED TO THE DOCUMENTATION. Prior to subscribing for, installing, or using any Third Party Products and Services, it is your responsibility to ensure that your airtime service provider has agreed to support all of their features. Some airtime service providers might not offer Internet browsing functionality with a subscription to the BlackBerry速 Internet Service. Check with your service provider for availability, roaming arrangements, service plans and features. Installation or use of Third Party Products and Services with RIM's products and services may require one or more patent, trademark, copyright, or other licenses in order to avoid infringement or violation of third party rights. You are solely responsible for determining whether to use Third Party Products and Services and if any third party licenses are required to do so. If required you are responsible for acquiring them. You should not install or use Third Party Products and Services until all necessary licenses have been acquired. Any Third Party Products and Services that are provided with RIM's products and services are provided as a convenience to you and are provided "AS IS" with no express or implied conditions, endorsements, guarantees, representations, or warranties of any kind by RIM and RIM assumes no liability whatsoever, in relation thereto. Your use of Third Party Products and Services shall be governed by and subject to you agreeing to the terms of separate licenses and other agreements applicable thereto with third parties, except to the extent expressly covered by a license or other agreement with RIM. Certain features outlined in this documentation require a minimum version of BlackBerry速 Enterprise Server, BlackBerry速 Desktop Software, and/or BlackBerry速 Device Software.

69


Development Guide

Legal notice

The terms of use of any RIM product or service are set out in a separate license or other agreement with RIM applicable thereto. NOTHING IN THIS DOCUMENTATION IS INTENDED TO SUPERSEDE ANY EXPRESS WRITTEN AGREEMENTS OR WARRANTIES PROVIDED BY RIM FOR PORTIONS OF ANY RIM PRODUCT OR SERVICE OTHER THAN THIS DOCUMENTATION.

Research In Motion Limited 295 Phillip Street Waterloo, ON N2L 3W8 Canada Research In Motion UK Limited Centrum House 36 Station Road Egham, Surrey TW20 9LF United Kingdom Published in Canada

70


Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.