Sei sulla pagina 1di 70

BlackBerry Java SDK

Data Storage
Version: 6.0

Development Guide
Published: 2010-11-30
SWD-1232721-1130030151-001
Contents
1 Data storage overview................................................................................................................................................................ 4
Data storage features..................................................................................................................................................................... 4
Considerations for choosing a data storage approach...................................................................................................... 5
Storage locations............................................................................................................................................................................ 5
Access to memory................................................................................................................................................................... 6

2 Storing files in the file system................................................................................................................................................... 7


Code sample: Creating a folder..................................................................................................................................................... 7
Code sample: Creating a file......................................................................................................................................................... 8
Code sample: Writing text to a file................................................................................................................................................ 9
Code sample: Reading sections of a binary file........................................................................................................................... 9
Code sample: Displaying the path to the video folder using System.getProperty()............................................................... 11
Code sample: Retrieving a list of mounted roots........................................................................................................................ 12

3 Storing data in SQLite databases............................................................................................................................................. 13


Viewing SQLite databases............................................................................................................................................................. 13
Simulate a media card........................................................................................................................................................... 13
Security of SQLite databases......................................................................................................................................................... 14
Code sample: Creating an encrypted SQLite database..................................................................................................... 15
Performance of SQLite databases................................................................................................................................................. 16
Best practice: Optimizing SQLite database performance................................................................................................. 16
Creating and deleting SQLite databases..................................................................................................................................... 18
SQLite database files............................................................................................................................................................. 18
Character encoding................................................................................................................................................................ 18
Create an SQLite database................................................................................................................................................... 19
Code sample: Creating an SQLite database....................................................................................................................... 20
Code sample: Adding a schema to an SQLite database.................................................................................................... 21
Code sample: Deleting an SQLite database....................................................................................................................... 22
Working with SQLite databases.................................................................................................................................................... 23
Using transactions................................................................................................................................................................. 24
Using SQL parameters........................................................................................................................................................... 25
Using foreign key constraints............................................................................................................................................... 29
Code sample: Inserting table data....................................................................................................................................... 29
Code sample: Retrieving table data..................................................................................................................................... 30
Code sample: Deleting table data........................................................................................................................................ 32
Code sample: Updating table data...................................................................................................................................... 33
Code sample: Listing database tables................................................................................................................................. 34
SQLite sample application............................................................................................................................................................. 36
Overview.................................................................................................................................................................................. 36
Files in the sample application............................................................................................................................................. 36
Featured interfaces................................................................................................................................................................ 37
Featured classes..................................................................................................................................................................... 37
Install the sample application............................................................................................................................................... 38
Run the sample application................................................................................................................................................... 38

4 Storing objects persistently....................................................................................................................................................... 39


Security of persistent objects........................................................................................................................................................ 39
Restricting access to persistent objects.............................................................................................................................. 40
Performance of the persistent store............................................................................................................................................. 40
Best practice: Using efficient data structure selection...................................................................................................... 40
Best practice: Conserving object handles........................................................................................................................... 41
Cleanup of persistent objects............................................................................................................................................... 42
Creating a persistent store............................................................................................................................................................ 42
Create a persistent data store............................................................................................................................................... 42
Store persistent data.............................................................................................................................................................. 42
Store an object in a batch transaction................................................................................................................................. 43
Working with the persistent store................................................................................................................................................. 43
Retrieve persistent data......................................................................................................................................................... 43
Remove persistent data......................................................................................................................................................... 44
Remove specific persistent data from a BlackBerry Java Application.............................................................................. 44
Retrieve a collection from persistent storage..................................................................................................................... 44

5 Storing objects nonpersistently................................................................................................................................................ 46


Common uses of the runtime store............................................................................................................................................... 46
Security of the runtime store......................................................................................................................................................... 46
Protect runtime store data using code signing keys.......................................................................................................... 47
Add an object to the runtime store............................................................................................................................................... 47
Replace an object in the runtime store........................................................................................................................................ 47
Retrieve the runtime store............................................................................................................................................................. 48
Retrieve a registered runtime object............................................................................................................................................ 48
Retrieve an unregistered runtime object..................................................................................................................................... 48
Code sample: Storing a String in the runtime store................................................................................................................... 49
Code sample: Getting a stored String from the runtime store.................................................................................................. 49
Code sample: Creating a singleton using the RuntimeStore API............................................................................................. 50

6 Storing data in the record store................................................................................................................................................ 51


Create a record store...................................................................................................................................................................... 51
Add a record to a record store....................................................................................................................................................... 51
Code sample: Adding a record to the record store............................................................................................................ 52
Retrieve a record from a record store........................................................................................................................................... 52
Retrieve all records from a record store....................................................................................................................................... 52
Code sample: Storing and retrieving data with the record store.............................................................................................. 53

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

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

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

10 Glossary......................................................................................................................................................................................... 64

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

12 Legal notice.................................................................................................................................................................................. 66
Development Guide Data storage overview

Data storage overview 1


RIM provides a variety of approaches for you to store, share, and manage your application data:

Data storage approach Description and API


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

Data storage features


The following table compares the data storage approaches.

Features File system SQLite Persistent store Runtime store Record store
database
Data format Any Relational Java® object Java object Serialized
database file
Storage locations Application External Application Application Application
storage, external media card, storage storage storage
media card, built- built-in media
in media storage storage
Maximum storage limit Size of partitions Size of Available Available Differs according
the user has partitions the application application to BlackBerry®
access to user has storage storage Device Software
access to version
BlackBerry Device 4.2 or later 5.0 or later All 3.6 or later All
Software support (FileConnection
API)
Persists across device Yes Yes Yes No Yes
restarts

4
Development Guide Storage locations

Features File system SQLite Persistent store Runtime store Record store
database
Applications can share Yes Yes Yes Yes Yes
data

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 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.
External media card This storage location is a microSD card that BlackBerry device users can insert to extend the amount
storage 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.
Built-in media storage 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.

5
Development Guide Storage locations

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 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
Development Guide Storing files in the file system

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 defined by JSR 75 and is built on the Generic Connection Framework. The main component of the
FileConnection API is the FileConnection class. Unlike other Generic Connection Framework connections,
FileConnection objects can be successfully returned from the 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.
The FileConnection API is implemented in the javax.microedition.io.file package. In addition, RIM provides
extensions to the FileConnection API. The net.rim.device.api.io.file package includes the following classes:
• 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 net.rim.device.api.system.Application;
import javax.microedition.io.*;
import javax.microedition.io.file.*;
import java.io.IOException;

public class CreateFolderApp extends Application

7
Development Guide Code sample: Creating a file

{
public static void main(String[] args)
{
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 javax.microedition.io.*;
import java.io.IOException;
import javax.microedition.io.file.*;
import 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)

8
Development Guide Code sample: Writing text to a file

{
System.out.println(ioe.getMessage() );
}
}
}

Code sample: Writing text to a file


import net.rim.device.api.system.Application;
import javax.microedition.io.*;
import javax.microedition.io.file.*;
import java.io.IOException;
import 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.

9
Development Guide Code sample: Reading sections of a binary file

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

public class RandomFileAccess extends UiApplication


{
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);
}

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

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();
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
}
}

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);
}
}

11
Development Guide Code sample: Retrieving a list of mounted roots

Code sample: Retrieving a list of mounted roots


import java.util.Enumeration.*;
import javax.microedition.io.file.FileSystemRegistry,*;
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 ListMountedRoots extends UiApplication


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

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

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. The Database API was introduced with BlackBerry® Device Software
5.0.
BlackBerry Device Software 6.0 uses SQLite version 3.6.21. BlackBerry Device Software 5.0 uses SQLite version 3.6.16.

Note: 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. Create a folder on your computer to store emulation files for the media card.
2. On the Simulate menu, click Change SD Card.
3. Click Add Directory.
4. Navigate to and click the folder you created.
5. Click OK.
6. Click Close.

13
Development Guide Security of SQLite databases

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
• 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.

14
Development Guide Security of SQLite databases

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.
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


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 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);

15
Development Guide Performance of SQLite databases

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);
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:

16
Development Guide Performance of SQLite databases

Best practice Description


Store as little data as Most of the processing time of SQLite® databases is taken up by reading and writing to storage.
possible 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 executed and
transactions 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.
Create efficient Indexes can greatly reduce the time required to scan a table. Consider the following guidelines:
indexes
• 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.
Minimize the size of If you have a very wide column, consider putting it in a separate table.
rows
Store BLOBs If your data includes BLOBs, consider storing each BLOB in a separate table. If the BLOBs are very
appropriately 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.
Consider using If you do not need the data to be available following a restart of the BlackBerry devices, use the
temporary tables CREATE TEMP TABLE statement instead of CREATE TABLE.
Use SQL parameters 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.
Avoid subqueries In some cases, the SQLite database engine stores subquery results in a temporary file, which can
slow down processing.
Defragment the Use the SQLite VACUUM command to defragment the database. This process also reduces the size
database of the database file.

17
Development Guide Creating and deleting SQLite databases

Best practice Description


Consider the order of The order of columns in a table declaration affects performance, especially in the absence of an
columns in table index, because the SQLite database engine scans the columns in the order defined in the table
declarations 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.

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.

18
Development Guide Creating and deleting SQLite databases

Create an SQLite database


1. Import the required libraries.
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.*;
2. 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.
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."));
}
}

19
Development Guide Creating and deleting SQLite databases

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:
• 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


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.*;
import 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());

20
Development Guide Creating and deleting SQLite databases

}
}

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();
}
}
}

Code sample: Adding a schema to an SQLite database


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 CreateDatabaseSchema extends UiApplication


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

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

21
Development Guide Creating and deleting SQLite databases

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() );
e.printStackTrace();
}

}
}

Code sample: Deleting an SQLite database


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 DeleteDatabase extends UiApplication


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

22
Development Guide Working with SQLite databases

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();
}
}
}

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, DETACH, FTS2, PRAGMA, and RTREE.
The following steps outline the basic procedure for running statements:

1. Create an SQL statement by invoking Database.createStatement().


2. Prepare the statement to run by invoking Statement.prepare().
3. Run the statement. If the statement might return results, run it by invoking Statement.getCursor(). Otherwise, use
Statement.execute().

23
Development Guide Working with SQLite databases

4. 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 class, which works in all circumstances but is forward-only. For birectional cursor movement, but only for small
result sets, use BufferedCursor.

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()
{

}
}

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."));

24
Development Guide Working with SQLite databases

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:
• 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.
You can use the 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.

25
Development Guide Working with SQLite databases

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 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.*;
import 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()
{
LabelField title = new LabelField("SQLite Parameterized Update Sample",
LabelField.ELLIPSIS |
LabelField.USE_ALL_WIDTH);

26
Development Guide Working with SQLite databases

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 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.*;
import java.util.*;

public class ParameterizedInsert extends UiApplication

27
Development Guide Working with SQLite databases

{
public static void main(String[] args)
{
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();
}

28
Development Guide Working with SQLite databases

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

}
catch ( Exception e )
{
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 a database option called
foreign_key_constraints.
You can set this option when you create the database. To be effective, you must set it again each time you open the database.
This feature was added in BlackBerry® Device Software 6.0.

Code sample: Enforcing the use of foreign keys


The following code sample shows how to enforce foreign 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 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 InsertData extends UiApplication


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

public InsertData()

29
Development Guide Working with SQLite databases

{
pushScreen(new InsertDataScreen());
}
}

class InsertDataScreen extends MainScreen


{
Database d;
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 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 ReadData extends UiApplication


{

30
Development Guide Working with SQLite databases

public static void main(String[] args)


{
ReadData theApp = new ReadData();
theApp.enterEventDispatcher();
}

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

class ReadDataScreen extends MainScreen


{
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();

31
Development Guide Working with SQLite databases

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.
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 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);

32
Development Guide Working with SQLite databases

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();
}

}
}

Code sample: Updating table data


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 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
{

33
Development Guide Working with SQLite databases

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();
}

}
}

Code sample: Listing database tables


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 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 |

34
Development Guide

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)
{
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();
}

}
}

35
Development Guide SQLite sample application

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 Description


SQLiteDemo.java • 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.
• This file creates and displays an instance of SQLiteDemoScreen.
SQLiteDemoScreen.java • 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.

36
Development Guide SQLite sample application

File name Description


• This file contains menu items for adding, updating, and deleting categories
and directory items.
SQLManager.java This file contains all the methods for adding, updating, and deleting categories and
directory items (for instance, all of the database manipulation methods).
ItemScreen.java This file contains the screen, which contains fields for adding and changing
categories and directory items.
Category.java • This file defines the Category entries in the database.
• This file provides accessor methods for the Category fields.
DirectoryItems.java • This file defines the DirectoryItems entries in the database.
• This file provides accessor methods for the DirectoryItems fields.
SQLiteDemoDirectory • 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.

Featured classes
net.rim.device.api.database.Row

37
Development Guide SQLite sample application

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. In Eclipse®, on the File menu, click Import.
2. In the Import dialog box, expand the BlackBerry folder.
3. Click Import BlackBerry Samples.
4. Click Next.
5. 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. In the BlackBerry Projects section, click Deselect All.
7. Select the check box beside the SQLiteDemo project.
8. Click Finish.

Run the sample application


1. In Eclipse®, in the Navigator pane, right-click the SQLiteDemo folder.
2. Click Run As > BlackBerry Simulator.
3. If the BlackBerry® Smartphone Simulator did not display the message "Media card inserted" on startup, you must Simulate
a media card.
4. If necessary, on the Home screen of the BlackBerry® Smartphone Simulator, click the Downloads folder.
5. Click the SQLite Demo icon.

38
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 ControlledAccess class and code signing keys.
• Encrypt and decrypt objects with the PersistentContent class.
Depending on the needs of your application, you could use both, one, or neither of these approaches.

39
Development Guide Performance of the persistent store

Restricting access to persistent objects


If you want to permit only specific, authorized applications to access your application data, you should use the
ControlledAccess class in conjunction with key generation and a key-signing procedure. To restrict access to your data,
you can:
• Create a signing key using the BlackBerry® Signing Authority Tool.
• Wrap the PersistentObject in a ControlledAccess object that is associated with the signing key.
• Use the same signing key for each application that you want to allow to access the protected data.
For detailed instructions, see Protect persistent objects from access by unauthorized applications.

Protect persistent data using code signing keys


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.
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.

40
Development Guide Performance of the persistent store

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.
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 .

41
Development Guide Creating a persistent store

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.

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 net.rim.device.api.system.PersistentObject;
import net.rim.device.api.system.PersistentStore;
import java.lang.String;
import net.rim.device.api.ui.component.Dialog;
2. To create a unique long key, in the BlackBerry® Integrated Development Environment, type a string value. For exmaple,
com.rim.samples.docs.userinfo
3. Right-click the string and click Convert ‘com.rim.samples.docs.userinfo’ to long.
4. Include a comment in your code to indicate the string that you used to generate the unique long key.
5. To create a persistent data store, create a single static PersistentObject and invoke
PersistentStore.getPersistentObject, using the unique long key as a parameter.
static PersistentObject store;
static {
store = PersistentStore.getPersistentObject( 0xa1a569278238dad2L );
}

Store persistent data


1. Import the required classes and interfaces.

42
Development Guide Working with the persistent store

import net.rim.device.api.system.PersistentObject;
import net.rim.device.api.system.PersistentStore;
2. Invoke setContents() on a PersistentObject. This method replaces the existing content with the new content.
3. 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. 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.
2. Synchronize on the object.
3. 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. Import the required classes and interfaces.
import net.rim.device.api.system.PersistentObject;
import net.rim.device.api.ui.component.Dialog;
2. Invoke getContents() on a PersistentObject.
3. To convert to your desired format, perform an explicit cast on the object that PersistentObject.getContents()
returns.

43
Development Guide Working with the persistent store

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]);
}
}

Remove persistent data


If you delete the .cod file that defines a PersistentStore, then all persistent objects that the .cod file created are deleted.
1. Import the required classes and interfaces.
import net.rim.device.api.system.PersistentObject;
import net.rim.device.api.system.PersistentStore;
2. To remove all persistent data from a BlackBerry® device application, invoke
PersistentStore.destroyPersistentObject(), providing as a parameter a unique key for the
PersistentObject.
3. To remove individual data, treat the data as normal objects, and remove references to it. A garbage collection operation
removes the data.

Remove specific persistent data from a BlackBerry Java Application


To delete individual data, treat the data as normal objects, and remove references to it. A garbage collected operation
removes the data.

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 );

44
Development Guide Working with the persistent store

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();
_persist.setContents( _contacts );
_persist.commit();
}

45
Development Guide Storing objects nonpersistently

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 For example, an application suite could be made up of multiple applications, all of which use
applications 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.
Store a reference to an object For example, an application that allows a BlackBerry device user to add and remove an
for later use ApplicationMenuItem could use the runtime store to store a reference to an
ApplicationMenuItem it has registered. After the application is closed and re-opened,
the ApplicationMenuItem can be accessed and unregistered.
Implement system-wide An application might require one or more singleton objects to be accessed from within the
singletons 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.

46
Development Guide Add an object to the runtime store

Protect runtime store data using code signing keys


1. Import the required classes and interfaces.
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. Invoke RuntimeStore.put(long, String) and provide as parameters a unique long ID and the runtime object to
store.
2. 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. Invoke replace().
2. 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 {

47
Development Guide Retrieve the runtime store

Object obj = store.replace( 0x60ac754bc0867248L, newmsg);


} catch(ControlledAccessException e) {
} not exist.

Retrieve the runtime store


1. Import the required classes and interfaces.
import java.lang.IllegalArgumentException;
import java.lang.RuntimeException;
import java.lang.String;
import net.rim.device.api.system.ControlledAccessException;
import net.rim.device.api.system.RuntimeStore;
import net.rim.device.api.util.ListenerUtilities;
2. Invoke RuntimeStore.getRuntimeStore().
RuntimeStore store = RuntimeStore.getRuntimeStore();

Retrieve a registered runtime object


1. Invoke RuntimeStore.get() and provide as a parameter the runtime object ID.
2. 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. 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.
2. Create code for handling exceptions.
RuntimeStore store = RuntimeStore.getRuntimeStore();
try {
Object obj = store.waitFor(0x60ac754bc0867248L);
} catch(ControlledAccessException e) {
} catch(RuntimeException e) {
}

48
Development Guide Code sample: Storing a String in the runtime store

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);
}

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 net.rim.device.api.system.RuntimeStore;
import net.rim.device.api.ui.UiApplication;
import net.rim.device.api.ui.component.Dialog;
import net.rim.device.api.ui.component.LabelField;
import 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));

49
Development Guide Code sample: Creating a singleton using the RuntimeStore API

class HomeScreen extends MainScreen


{
public HomeScreen(String msg)
{
add(new LabelField(msg));
}
}

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;

}
}

50
Development Guide Storing data in the record store

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 Maximum total record store size
(cumulative for all applications)
Earlier than 4.1 64 KB 64 KB
4.1 to 4.5 64 KB Available device memory
4.6 or later 512 KB Available device memory

Create a record store


1. Import the javax.microedition.rms.RecordStore class.
2. 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. Import the javax.microedition.rms.RecordStore class.

51
Development Guide Retrieve a record from a record store

2. Invoke addRecord().
int id = store.addRecord(_data.getBytes(), 0, _data.length());

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 javax.microedition.rms.RecordComparator;
import javax.microedition.rms.RecordEnumeration;
import javax.microedition.rms.RecordFilter;
import javax.microedition.rms.RecordStore;
2. Invoke openRecordStore().

52
Development Guide Code sample: Storing and retrieving data with the record store

3. 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
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 javax.microedition.rms.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import 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;

53
Development Guide Code sample: Storing and retrieving data with the record store

ByteArrayInputStream bais = new ByteArrayInputStream(candidate);


DataInputStream inputStream = new DataInputStream(bais);
String name = null;

try {
int score = inputStream.readInt();
name = inputStream.readUTF();
}
catch (EOFException eofe) {
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;
}

54
Development Guide Code sample: Storing and retrieving data with the record store

else if (score1 > score2) {


return RecordComparator.FOLLOWS;
}
else {
return RecordComparator.EQUIVALENT;
}
}

/**
* The constructor opens the underlying record store,
* creating it if necessary.
*/
public RMSGameScores()
{
//
// 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

55
Development Guide Code sample: Storing and retrieving data with the record store

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();
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,

56
Development Guide Code sample: Storing and retrieving data with the record store

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) {
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");
}
}

57
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.

58
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:
• 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.

59
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.

60
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 Description
SyncConverter Converts data between the SyncObject format that is required on the BlackBerry device
and the serialized format that is required on the computer.
SyncCollection Represents the collection of synchronization objects for an application.
SyncObject 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.

61
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.

62
Development Guide Provide feedback

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

63
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

UTF-8
8-bit UCS/Unicode Transformation Format

64
Development Guide Document revision history

Document revision history 11

Date Description
29 November 2010 Added topic: Using foreign key constraints
26 November 2010 Widespread edits
16 August 2010 Initial version

65
Development Guide Legal notice

Legal notice 12
©2010 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 NON-
PERFORMANCE 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 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

66
Development Guide Legal notice

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.
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

67
Development Guide Legal notice

Canada

Research In Motion UK Limited


Centrum House
36 Station Road
Egham, Surrey TW20 9LF
United Kingdom

Published in Canada

68

Potrebbero piacerti anche