/*
* MemoryDemoMainScreen.java
*
* Copyright � 1998-2011 Research In Motion Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Note: For the sake of simplicity, this sample application may not leverage
* resource bundles and resource strings. However, it is STRONGLY recommended
* that application developers make use of the localization features available
* within the BlackBerry development platform to ensure a seamless application
* experience across a variety of languages and geographies. For more information
* on localizing your application, please refer to the BlackBerry Java Development
* Environment Development Guide associated with this release.
*/
package com.rim.samples.device.memorydemo;
import java.util.Calendar;
import java.util.Date;
import net.rim.device.api.command.Command;
import net.rim.device.api.command.CommandHandler;
import net.rim.device.api.command.ReadOnlyCommandMetadata;
import net.rim.device.api.lowmemory.LowMemoryListener;
import net.rim.device.api.lowmemory.LowMemoryManager;
import net.rim.device.api.system.Characters;
import net.rim.device.api.system.Display;
import net.rim.device.api.ui.ContextMenu;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.Graphics;
import net.rim.device.api.ui.MenuItem;
import net.rim.device.api.ui.UiApplication;
import net.rim.device.api.ui.component.Dialog;
import net.rim.device.api.ui.component.GaugeField;
import net.rim.device.api.ui.component.LabelField;
import net.rim.device.api.ui.component.ListField;
import net.rim.device.api.ui.component.ListFieldCallback;
import net.rim.device.api.ui.container.DialogFieldManager;
import net.rim.device.api.ui.container.MainScreen;
import net.rim.device.api.ui.container.PopupScreen;
import net.rim.device.api.util.StringProvider;
/**
* The main screen for the application.
*/
public final class MemoryDemoMainScreen extends MainScreen implements
ListFieldCallback, LowMemoryListener {
private final OrderList _orderList;
private final OrderListField _orderListField;
private final UiApplication _app;
private ProgressBarDialog _progressDialog;
private static final int MAX_RECORDS = 1000;
/**
* Creates a new MemoryDemoMainScreen object
*/
public MemoryDemoMainScreen() {
setTitle("Order Records");
_app = UiApplication.getUiApplication();
// Get and display the order list.
_orderList = OrderList.getInstance();
_orderListField = new OrderListField(_orderList.getNumOrderRecords());
_orderListField.setCallback(this);
add(_orderListField);
LowMemoryManager.addLowMemoryListener(this);
}
/**
* @see net.rim.device.api.ui.Screen#onClose()
*/
public boolean onClose() {
// Remove this screen as a low memory listener
LowMemoryManager.removeLowMemoryListener(this);
// Commit the order list to persistent store
_orderList.commit();
return super.onClose();
}
/**
* @see net.rim.device.api.ui.Screen#invokeAction(int)
*/
protected boolean invokeAction(final int action) {
if (action == ACTION_INVOKE) {
viewRecord(_orderListField.getSelectedIndex());
return true;
}
return super.invokeAction(action);
}
/**
* @see net.rim.device.api.ui.Screen#keyChar(char,int,int)
*/
protected boolean keyChar(final char key, final int status, final int time) {
if (key == Characters.ENTER) {
viewRecord(_orderListField.getSelectedIndex());
return true;
}
return super.keyChar(key, status, time);
}
/**
* Displays selected record in view mode
*/
private void viewRecord(final int index) {
OrderRecord orderRecord =
(OrderRecord) /* outer. */get(_orderListField, index);
final MemoryDemoOrderScreen screen =
new MemoryDemoOrderScreen(orderRecord, false);
_app.pushModalScreen(screen);
orderRecord = screen.getUpdatedOrderRecord();
if (orderRecord != null) {
_orderList.replaceOrderRecordAt(_orderListField.getSelectedIndex(),
orderRecord);
}
}
// ListFieldCallback methods
// -------------------------------------------------------------------
/**
* @see net.rim.device.api.ui.component.ListFieldCallback#drawListRow(ListField,Graphics,int,int,int)
*/
public void drawListRow(final ListField listField, final Graphics graphics,
final int index, final int y, final int width) {
final Object object = get(listField, index);
graphics.drawText(object.toString(), 0, y, 0, width);
}
/**
* @see net.rim.device.api.ui.component.ListFieldCallback#getPreferredWidth(ListField)
*/
public int getPreferredWidth(final ListField listField) {
return Display.getWidth();
}
/**
* @see net.rim.device.api.ui.component.ListFieldCallback#get(ListField ,
* int)
*/
public Object get(final ListField listField, final int index) {
return _orderList.getOrderRecordAt(index);
}
/**
* @see net.rim.device.api.ui.component.ListFieldCallback#indexOfList(ListField
* , String , int)
*/
public int indexOfList(final ListField listField, final String prefix,
final int start) {
return -1; // Not implemented.
}
// LowMemoryListener methods
// -------------------------------------------------------------------
/**
* This method is called when the system is running low on memory.
* Applications should mark some objects as recoverable, depending on the
* priority, in order to free up some memory.
*
* @param priority
* The priority of the memory cleanup.
*
* @return True if any objects were marked as recoverable; otherwise false.
*
* @see net.rim.device.api.lowmemory.LowMemoryListener#freeStaleObject(int)
*/
public boolean freeStaleObject(final int priority) {
boolean freedData = false;
switch (priority) {
case LowMemoryListener.LOW_PRIORITY: {
// Low priority; application should consider releasing transitory
// variables and
// any variables that are currently unnecessary for complete
// functionality, such as
// cached data. This sample application does not cache any
// variables, so no data
// is recovered.
break;
}
case LowMemoryListener.MEDIUM_PRIORITY: {
// Medium priority; application should consider removing stale data,
// such as
// very old email messages or old calendar appointments. This
// application traverses
// the list of order records, and removes any that occurred more
// than 15 years ago,
// i.e., very old (stale) order records.
final int numYearsAgo = 15;
final Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
final int year = calendar.get(Calendar.YEAR);
calendar.set(Calendar.YEAR, year - numYearsAgo);
freedData =
_orderList.removeStaleOrderRecords(calendar.getTime()
.getTime());
_orderListField.setSize(_orderList.getNumOrderRecords());
break;
}
case LowMemoryListener.HIGH_PRIORITY: {
// High priority; application should remove objects on a Least
// Recently Used basis.
// The application should remove *all* its stale objects to reduce
// the amount of
// memory consumed on the handheld. This application traverses the
// list of customer
// records, and removes any that haven't been accessed in the last
// 10 years,
// i.e., the least recently used customer records.
final int numYearsAgo = 10;
final Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
final int year = calendar.get(Calendar.YEAR);
calendar.set(Calendar.YEAR, year - numYearsAgo);
freedData =
_orderList.removeStaleOrderRecords(calendar.getTime()
.getTime());
_orderListField.setSize(_orderList.getNumOrderRecords());
break;
}
}
return freedData;
}
/**
* List field that has a custom context menu
*/
private final class OrderListField extends ListField {
OrderListField(final int numEntries) {
super(numEntries);
}
/**
* Displays this list field's custom context menu. If there is at least
* one item in the list, there are options to act upon the selected item
* or run LowMemoryManager tasks. If there are less than the maximum
* number of records, there is an option to fill up the list with random
* data.
*
* @return The newly-created context menu
*/
public ContextMenu getContextMenu() {
final ContextMenu contextMenu = super.getContextMenu();
if (getSize() > 0) {
final int index = getSelectedIndex();
contextMenu.addItem(new View(index));
contextMenu.addItem(new Edit(index));
contextMenu.addItem(new Delete(index));
contextMenu.addItem(new DeleteAll());
contextMenu.addItem(new SimulateLmmLow());
contextMenu.addItem(new SimulateLmmMedium());
contextMenu.addItem(new SimulateLmmHigh());
}
if (getSize() < /* outer. */MAX_RECORDS) {
contextMenu.addItem(new Populate());
}
return contextMenu;
}
}
/**
* A menu item to view a record
*/
private final class View extends MenuItem {
private final int _index;
private View(final int index) {
super(new StringProvider("View"), 0x230020, 1);
_index = index;
this.setCommand(new Command(new CommandHandler() {
/**
* @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata,
* Object)
*/
public void execute(final ReadOnlyCommandMetadata metadata,
final Object context) {
viewRecord(_index);
}
}));
}
}
/**
* A menu item to edit a record
*/
private final class Edit extends MenuItem {
private final int _index;
private Edit(final int index) {
super(new StringProvider("Edit"), 0x230030, 2);
_index = index;
this.setCommand(new Command(new CommandHandler() {
/**
* @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata,
* Object)
*/
public void execute(final ReadOnlyCommandMetadata metadata,
final Object context) {
OrderRecord orderRecord =
(OrderRecord) /* outer. */get(_orderListField,
_index);
final MemoryDemoOrderScreen screen =
new MemoryDemoOrderScreen(orderRecord, true);
_app.pushModalScreen(screen);
orderRecord = screen.getUpdatedOrderRecord();
if (orderRecord != null) {
_orderList.replaceOrderRecordAt(_index, orderRecord);
}
}
}));
}
}
/**
* A menu item to delete a record
*/
private final class Delete extends MenuItem {
private final int _index;
private Delete(final int index) {
super(new StringProvider("Delete"), 0x230040, 3);
_index = index;
this.setCommand(new Command(new CommandHandler() {
/**
* @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata,
* Object)
*/
public void execute(final ReadOnlyCommandMetadata metadata,
final Object context) {
if (Dialog.ask(Dialog.D_DELETE) == Dialog.DELETE) {
final OrderRecord orderRecord =
(OrderRecord) get(_orderListField, _index);
_orderList.deleteOrderRecord(orderRecord);
_orderListField
.setSize(_orderList.getNumOrderRecords());
}
}
}));
}
}
/**
* A menu item to delete all records in the list field
*/
private final class DeleteAll extends MenuItem {
private DeleteAll() {
super(new StringProvider("Delete All"), 0x230050, 4);
this.setCommand(new Command(new CommandHandler() {
/**
* @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata,
* Object)
*/
public void execute(final ReadOnlyCommandMetadata metadata,
final Object context) {
if (Dialog.ask(Dialog.D_DELETE) == Dialog.DELETE) {
_orderList.deleteAllOrderRecords();
_orderListField.setSize(0);
}
}
}));
}
}
/**
* A menu item to populate the list field
*/
private final class Populate extends MenuItem {
private Populate() {
super(new StringProvider("Populate"), 0x230010, 0);
this.setCommand(new Command(new CommandHandler() {
/**
* @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata,
* Object)
*/
public void execute(final ReadOnlyCommandMetadata metadata,
final Object context) {
_progressDialog =
new ProgressBarDialog(
"Generating order records...", _orderList
.getNumRecordsToAdd(MAX_RECORDS));
new Thread(new Runnable() {
public void run() {
_orderList.populate(MAX_RECORDS, _progressDialog);
UiApplication.getUiApplication().invokeLater(
new Runnable() {
public void run() {
_orderListField.setSize(_orderList
.getNumOrderRecords());
}
});
}
}).start();
}
}));
}
}
/**
* A clas that creates a popup dialog box containing a gauge field to
* display the progress as the list is populated.
*/
static class ProgressBarDialog implements CountAndSortListener {
private final DialogFieldManager _manager;
private final PopupScreen _popupScreen;
private final GaugeField _gaugeField;
private final LabelField _lbfield;
private final int _max;
private final int _stepSize;
/**
* Creates a new ProgressBarDialog object
*
* @param title
* Text to display on _popupScreen.
* @param max
* Maximum value of the range _gaugeField can display.
*/
private ProgressBarDialog(final String title, final int max) {
_max = max;
// Make sure that step size is at least one
_stepSize = Math.max(_max / 100, 1);
_manager = new DialogFieldManager();
_popupScreen = new PopupScreen(_manager);
_gaugeField = new GaugeField(null, 0, max, 0, GaugeField.PERCENT);
_lbfield = new LabelField(title, Field.USE_ALL_WIDTH);
_manager.addCustomField(_lbfield);
_manager.addCustomField(_gaugeField);
UiApplication.getUiApplication().pushScreen(_popupScreen);
}
/**
* @see com.rim.samples.device.memorydemo.CountAndSortListener#counterUpdated(int)
*/
public void counterUpdated(final int counter) {
// Update _gaugeField if at least one percent of the records have
// been processed
// since the last time _gaugeField was updated e.g. if we have
// 60,000 records
// to add , _gaugeField is updated once every 600 calls to
// counterUpdated().
// Similarly , if we have 50 records to add , an update occurs once
// every
// 50/100 = 0.5 calls = every call to counterUpdated().
// This is done to optimize the code, when the progress dialog is
// called.
if (counter % _stepSize == 0) {
_gaugeField.setValue(counter + 1);
}
}
/**
* @see com.rim.samples.device.memorydemo.CountAndSortListener#sortingStarted()
*/
public void sortingStarted() {
// Remove _gaugeField and change the text displayed on _popupScreen
// to
// "Sorting records..." .
UiApplication.getUiApplication().invokeLater(new Runnable() {
public void run() {
_manager.deleteCustomField(_gaugeField);
_lbfield.setText("Sorting records...");
}
});
}
/**
* @see com.rim.samples.device.memorydemo.CountAndSortListener#sortingStarted()
*/
public void sortingFinished() {
// Remove _popupScreen from the stack
UiApplication.getUiApplication().invokeLater(new Runnable() {
public void run() {
UiApplication.getUiApplication().popScreen(_popupScreen);
}
});
}
}
/**
* A menu item to simulate the execution of the Low Memory Manager with Low
* priority.
*/
private final class SimulateLmmLow extends MenuItem {
/**
* Creates a new SimulateLmmLow object
*/
private SimulateLmmLow() {
super(new StringProvider("Simulate LMM Low"), 0x330000, 5);
this.setCommand(new Command(new CommandHandler() {
/**
* @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata,
* Object)
*/
public void execute(final ReadOnlyCommandMetadata metadata,
final Object context) {
freeStaleObject(LowMemoryListener.LOW_PRIORITY);
}
}));
}
}
/**
* A menu item to simulate the execution of the Low Memory Manager with
* Medium priority.
*/
private final class SimulateLmmMedium extends MenuItem {
/**
* Creates a new SimulateLmmMedium object
*/
private SimulateLmmMedium() {
super(new StringProvider("Simulate LMM Medium"), 0x330010, 6);
this.setCommand(new Command(new CommandHandler() {
/**
* @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata,
* Object)
*/
public void execute(final ReadOnlyCommandMetadata metadata,
final Object context) {
freeStaleObject(LowMemoryListener.MEDIUM_PRIORITY);
}
}));
}
}
/**
* A menu item to simulate the execution of the Low Memory Manager with
* Highpriority.
*/
private final class SimulateLmmHigh extends MenuItem {
/**
* Creates a new SimulateLmmHigh object
*/
private SimulateLmmHigh() {
super(new StringProvider("Simulate LMM High"), 0x330020, 7);
this.setCommand(new Command(new CommandHandler() {
/**
* @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata,
* Object)
*/
public void execute(final ReadOnlyCommandMetadata metadata,
final Object context) {
freeStaleObject(LowMemoryListener.HIGH_PRIORITY);
}
}));
}
}
}
/**
* Listener for when a count is updated, when sorting has started and when
* sorting had finished.
*/
interface CountAndSortListener {
/**
* Called when the counter is updated
*
* @param counter
* The new counter
*/
public void counterUpdated(int counter);
/**
* Called when sorting is started
*/
public void sortingStarted();
/**
* Called when sorting is finished
*/
public void sortingFinished();
}