/*
* Copyright 2008 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package com.codename1.impl.blackberry;
import com.codename1.codescan.CodeScanner;
import com.codename1.db.Database;
import com.codename1.impl.blackberry.codescan.AdvancedMultimediaManager;
import com.codename1.impl.blackberry.codescan.CodeScannerImpl;
import com.codename1.impl.blackberry.codescan.MultimediaManager;
import com.codename1.io.BufferedOutputStream;
import com.codename1.io.FileSystemStorage;
import com.codename1.io.Util;
import com.codename1.push.PushCallback;
import com.codename1.ui.BrowserComponent;
import com.codename1.ui.Component;
import com.codename1.ui.Display;
import com.codename1.ui.Image;
import com.codename1.ui.PeerComponent;
import com.codename1.ui.events.ActionEvent;
import com.codename1.ui.events.ActionListener;
import com.codename1.ui.util.EventDispatcher;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Hashtable;
import javax.microedition.io.HttpConnection;
import javax.microedition.io.StreamConnection;
import net.rim.blackberry.api.invoke.CameraArguments;
import net.rim.blackberry.api.invoke.Invoke;
import net.rim.blackberry.api.messagelist.ApplicationIcon;
import net.rim.blackberry.api.messagelist.ApplicationIndicator;
import net.rim.blackberry.api.messagelist.ApplicationIndicatorRegistry;
import net.rim.blackberry.api.push.PushApplicationDescriptor;
import net.rim.blackberry.api.push.PushApplicationRegistry;
import net.rim.blackberry.api.push.PushApplicationStatus;
import net.rim.device.api.database.DatabaseIOException;
import net.rim.device.api.database.DatabasePathException;
import net.rim.device.api.io.http.PushInputStream;
import net.rim.device.api.script.ScriptEngine;
import net.rim.device.api.system.Branding;
import org.w3c.dom.Document;
// requires signing
import net.rim.device.api.browser.field2.BrowserField;
import net.rim.device.api.browser.field2.BrowserFieldListener;
import net.rim.device.api.database.DatabaseFactory;
import net.rim.device.api.io.URI;
import net.rim.device.api.io.file.FileSystemJournal;
import net.rim.device.api.io.file.FileSystemJournalEntry;
import net.rim.device.api.io.file.FileSystemJournalListener;
import net.rim.device.api.script.Scriptable;
import net.rim.device.api.system.ApplicationDescriptor;
import net.rim.device.api.system.Bitmap;
import net.rim.device.api.system.Characters;
import net.rim.device.api.system.DeviceInfo;
import net.rim.device.api.system.EventInjector;
import net.rim.device.api.system.JPEGEncodedImage;
import net.rim.device.api.system.PNGEncodedImage;
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.picker.FilePicker;
/**
* Implementation class for newer blackberry devices
*
* @author Shai Almog, Thorsten Schemm
*/
public class BlackBerryOS5Implementation extends BlackBerryImplementation {
private static PushCallback pushCallback;
private static int unreadCount = 0;
BlackBerryCanvas createCanvas() {
return new BlackBerryTouchSupport(this);
}
public void init(Object m) {
super.init(m);
if(unreadCount > 0) {
unreadCount = 0;
updateIndicator(0);
}
unreadCount = 0;
}
public void nativeEdit(final Component cmp, final int maxSize, final int constraint, String text, int keyCode) {
BlackBerryVirtualKeyboard.blockFolding = true;
super.nativeEdit(cmp, maxSize, constraint, text, keyCode);
}
protected void disableBlockFolding() {
BlackBerryVirtualKeyboard.blockFolding = false;
}
public int getKeyboardType() {
int keyT = Keypad.getHardwareLayout();
switch (keyT) {
case Keypad.HW_LAYOUT_TOUCHSCREEN_12:
case Keypad.HW_LAYOUT_TOUCHSCREEN_24:
case Keypad.HW_LAYOUT_TOUCHSCREEN_29:
return Display.KEYBOARD_TYPE_VIRTUAL;
default:
return super.getKeyboardType();
}
}
public String getProperty(String key, String defaultValue) {
if ("User-Agent".equals(key)) {
return "Blackberry" + DeviceInfo.getDeviceName() + "/" + DeviceInfo.getSoftwareVersion()
+ " Profile/" + System.getProperty("microedition.profiles")
+ " Configuration/"
+ System.getProperty("microedition.configuration")
+ " VendorID/" + Branding.getVendorId();
}
return super.getProperty(key, defaultValue);
}
public void copyToClipboard(Object obj) {
if (obj instanceof String || obj instanceof StringBuffer) {
net.rim.device.api.system.Clipboard.getClipboard().put(obj);
super.copyToClipboard(null);
} else {
net.rim.device.api.system.Clipboard.getClipboard().put(null);
super.copyToClipboard(obj);
}
}
public Object getPasteDataFromClipboard() {
Object o = net.rim.device.api.system.Clipboard.getClipboard().get();
if (o != null) {
return o;
}
return super.getPasteDataFromClipboard();
}
public boolean canForceOrientation() {
return true;
}
public void lockOrientation(boolean portrait) {
net.rim.device.api.ui.UiEngineInstance ue;
ue = net.rim.device.api.ui.Ui.getUiEngineInstance();
if (portrait) {
ue.setAcceptableDirections(net.rim.device.api.system.Display.DIRECTION_PORTRAIT);
} else {
ue.setAcceptableDirections(net.rim.device.api.system.Display.DIRECTION_LANDSCAPE);
}
}
public void unlockOrientation() {
net.rim.device.api.ui.UiEngineInstance ue;
ue = net.rim.device.api.ui.Ui.getUiEngineInstance();
ue.setAcceptableDirections(net.rim.device.api.system.Display.DIRECTION_PORTRAIT | net.rim.device.api.system.Display.DIRECTION_LANDSCAPE);
}
public boolean isNativeBrowserComponentSupported() {
return BlackBerryImplementation.nativeBrowser;
}
public PeerComponent createBrowserComponent(Object browserComponent) {
synchronized (UiApplication.getEventLock()) {
BrowserField bff = new BrowserField();
final BrowserComponent cmp = (BrowserComponent) browserComponent;
bff.addListener(new BrowserFieldListener() {
public void documentError(BrowserField browserField, Document document) throws Exception {
cmp.fireWebEvent("onError", new ActionEvent(document.getDocumentURI()));
super.documentError(browserField, document);
}
public void documentCreated(BrowserField browserField, ScriptEngine scriptEngine, Document document) throws Exception {
cmp.fireWebEvent("onStart", new ActionEvent(document.getDocumentURI()));
super.documentCreated(browserField, scriptEngine, document);
}
public void documentLoaded(BrowserField browserField, Document document) throws Exception {
cmp.fireWebEvent("onLoad", new ActionEvent(document.getDocumentURI()));
super.documentLoaded(browserField, document);
}
});
return PeerComponent.create(bff);
}
}
public void setBrowserProperty(PeerComponent browserPeer, String key, Object value) {
}
public String getBrowserTitle(PeerComponent browserPeer) {
synchronized (UiApplication.getEventLock()) {
return ((BrowserField) browserPeer.getNativePeer()).getDocumentTitle();
}
}
public String getBrowserURL(PeerComponent browserPeer) {
synchronized (UiApplication.getEventLock()) {
return ((BrowserField) browserPeer.getNativePeer()).getDocumentUrl();
}
}
public void setBrowserURL(PeerComponent browserPeer, String url) {
if (url.startsWith("jar://")) {
//ApplicationDescriptor ad = ApplicationDescriptor.currentApplicationDescriptor();
//url = "cod://" + ad.getModuleName() + url.substring(6);
//super.setBrowserURL(browserPeer, url);
//url = "local://" + url.substring(6);
// load from jar:// URL's
try {
InputStream i = Display.getInstance().getResourceAsStream(getClass(), url.substring(6));
if (i == null) {
System.out.println("Local resource not found: " + url);
return;
}
byte[] buffer = new byte[4096];
ByteArrayOutputStream bo = new ByteArrayOutputStream();
int size = i.read(buffer);
while (size > -1) {
bo.write(buffer, 0, size);
size = i.read(buffer);
}
i.close();
bo.close();
String htmlText = new String(bo.toByteArray(), "UTF-8");
int pos = url.lastIndexOf('/');
if (pos > 6) {
url = url.substring(6, pos);
} else {
url = "/";
}
String baseUrl = "local://" + url;
setBrowserPage(browserPeer, htmlText, baseUrl);
return;
} catch (IOException ex) {
ex.printStackTrace();
}
return;
}
synchronized (UiApplication.getEventLock()) {
((BrowserField) browserPeer.getNativePeer()).requestContent(url);
}
}
public void browserReload(PeerComponent browserPeer) {
synchronized (UiApplication.getEventLock()) {
((BrowserField) browserPeer.getNativePeer()).refresh();
}
}
public boolean browserHasBack(PeerComponent browserPeer) {
return ((BrowserField) browserPeer.getNativePeer()).getHistory().canGoBack();
}
public boolean browserHasForward(PeerComponent browserPeer) {
synchronized (UiApplication.getEventLock()) {
return ((BrowserField) browserPeer.getNativePeer()).getHistory().canGoForward();
}
}
public void browserBack(PeerComponent browserPeer) {
synchronized (UiApplication.getEventLock()) {
((BrowserField) browserPeer.getNativePeer()).back();
}
}
public void browserForward(PeerComponent browserPeer) {
synchronized (UiApplication.getEventLock()) {
((BrowserField) browserPeer.getNativePeer()).forward();
}
}
public void browserClearHistory(PeerComponent browserPeer) {
synchronized (UiApplication.getEventLock()) {
((BrowserField) browserPeer.getNativePeer()).getHistory().clearHistory();
}
}
public void setBrowserPage(PeerComponent browserPeer, String html, String baseUrl) {
synchronized (UiApplication.getEventLock()) {
((BrowserField) browserPeer.getNativePeer()).displayContent(html, baseUrl);
}
}
public void browserExecute(PeerComponent browserPeer, String javaScript) {
synchronized (UiApplication.getEventLock()) {
((BrowserField) browserPeer.getNativePeer()).executeScript(javaScript);
}
}
protected int getFieldWidth(Field fld) {
return Math.max(fld.getWidth(), fld.getPreferredWidth()) + fld.getPaddingLeft() + fld.getPaddingRight();
}
protected int getFieldHeight(Field fld) {
return Math.max(fld.getHeight(), fld.getPreferredHeight()) + fld.getPaddingBottom() + fld.getPaddingTop();
}
public void browserExposeInJavaScript(PeerComponent browserPeer, Object o, String name) {
synchronized (UiApplication.getEventLock()) {
try {
((BrowserField) browserPeer.getNativePeer()).extendScriptEngine(name, (Scriptable) o);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
public void captureVideo(ActionListener response) {
captureCallback = new EventDispatcher();
captureCallback.addListener(response);
UiApplication.getUiApplication().addFileSystemJournalListener(new FileSystemJournalListener() {
private long lastUSN;
private String videoPath;
public void fileJournalChanged() {
// next sequence number file system will use
long USN = FileSystemJournal.getNextUSN();
for (long i = USN - 1; i >= lastUSN && i < USN; --i) {
FileSystemJournalEntry entry = FileSystemJournal.getEntry(i);
if (entry == null) {
break;
}
String path = entry.getPath();
if (entry.getEvent() == FileSystemJournalEntry.FILE_ADDED
&& videoPath == null) {
int index = path.indexOf(".3GP");
if (index != -1) {
videoPath = path;
}
} else if (entry.getEvent() == FileSystemJournalEntry.FILE_RENAMED) {
if (path != null && path.equals(videoPath)) {
//close the camera
UiApplication.getUiApplication().removeFileSystemJournalListener(this);
try {
EventInjector.KeyEvent inject = new EventInjector.KeyEvent(EventInjector.KeyEvent.KEY_DOWN, Characters.ESCAPE, 0, 200);
inject.post();
inject.post();
} catch (Exception e) {
//try to close the camera
}
captureCallback.fireActionEvent(new ActionEvent("file://" + path));
captureCallback = null;
videoPath = null;
break;
}
}
}
lastUSN = USN;
}
});
app.setWaitingForReply(true);
synchronized (UiApplication.getEventLock()) {
Invoke.invokeApplication(Invoke.APP_TYPE_CAMERA, new CameraArguments(CameraArguments.ARG_VIDEO_RECORDER));
}
}
private com.codename1.ui.util.ImageIO imIO;
/**
* @inheritDoc
*/
public com.codename1.ui.util.ImageIO getImageIO() {
if(imIO == null) {
imIO = new com.codename1.ui.util.ImageIO() {
public void save(InputStream image, OutputStream response, String format, int width, int height, float quality) throws IOException {
Image img = Image.createImage(image).scaled(width, height);
if(width < 0) {
width = img.getWidth();
}
if(height < 0) {
width = img.getHeight();
}
Bitmap bitmap = (Bitmap) img.getImage();
if(format == FORMAT_JPEG) {
JPEGEncodedImage enc = JPEGEncodedImage.encode(bitmap, (int)(quality*100));
response.write(enc.getData(), 0, enc.getData().length);
}else{
PNGEncodedImage enc = PNGEncodedImage.encode(bitmap);
response.write(enc.getData(), 0, enc.getData().length);
}
}
protected void saveImage(Image img, OutputStream response, String format, float quality) throws IOException {
Bitmap bitmap = (Bitmap) img.getImage();
if(format == FORMAT_JPEG) {
JPEGEncodedImage enc = JPEGEncodedImage.encode(bitmap, (int)(quality*100));
response.write(enc.getData(), 0, enc.getData().length);
}else{
PNGEncodedImage enc = PNGEncodedImage.encode(bitmap);
response.write(enc.getData(), 0, enc.getData().length);
}
}
public boolean isFormatSupported(String format) {
return format == FORMAT_JPEG || format == FORMAT_PNG;
}
};
}
return imIO;
}
public Database openOrCreateDB(String databaseName) throws IOException {
try {
URI dbURI = URI.create(getDBDir() + databaseName);
net.rim.device.api.database.Database db = DatabaseFactory.openOrCreate(dbURI);
return new BBDatabase(db);
} catch (Exception ex) {
ex.printStackTrace();
throw new IOException(ex.getMessage());
}
}
public void deleteDB(String databaseName) throws IOException {
try {
URI dbURI = URI.create(getDBDir() + databaseName);
DatabaseFactory.delete(dbURI);
} catch (Exception ex) {
ex.printStackTrace();
throw new IOException(ex.getMessage());
}
}
public boolean existsDB(String databaseName) {
try {
URI dbURI = URI.create(getDBDir() + databaseName);
return DatabaseFactory.exists(dbURI);
} catch (Exception ex) {
ex.printStackTrace();
}
return false;
}
public String getDatabasePath(String databaseName) {
return getDBDir() + databaseName;
}
private String getDBDir() {
String[] roots = listFilesystemRoots();
// iOS doesn't have an SD card
String file = null;
for (int i = 0; i < roots.length; i++) {
if (roots[i].indexOf("SDCard") > -1) {
file = roots[i];
break;
}
}
//no sd card try a different location
if (file == null) {
for (int i = 0; i < roots.length; i++) {
if (getRootType(roots[i]) == FileSystemStorage.ROOT_TYPE_SDCARD) {
file = roots[i];
break;
}
}
}
if(file == null){
file = roots[0];
}
file += "Databases/";
if (!exists(file)) {
mkdir(file);
}
return file;
}
/**
* (non-Javadoc)
*
* @see
* com.codename1.impl.blackberry.BlackBerryImplementation#openOutputStream(java.lang.Object)
*/
public OutputStream openOutputStream(Object connection) throws IOException {
if (connection instanceof String) {
return super.openOutputStream(connection);
}
OutputStream os = ((HttpConnection) connection).openOutputStream();
// getSoftwareVersion() not available in legacy port,introduced at API 4.3.0
int majorVersion = DeviceInfo.getSoftwareVersion().charAt(0) - '0';
// in version 7, BBOS started supporting HTTP 1.1, so facade not required.
if (majorVersion < 7) {
os = new BlackBerryOutputStream(os);
}
return new BufferedOutputStream(os, ((HttpConnection) connection).getURL());
}
public CodeScanner getCodeScanner() {
return new CodeScannerImpl(new AdvancedMultimediaManager());
}
public boolean isTrueTypeSupported() {
return true;
}
public Object loadTrueTypeFont(String fontName, String fileName) {
if (FontManager.getInstance().load(fileName, fontName, FontManager.APPLICATION_FONT) == FontManager.SUCCESS) {
FontFamily typeface;
try {
typeface = FontFamily.forName(fontName);
Font font = typeface.getFont(Font.PLAIN, 50);
return font;
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
return null;
}
} else{
return Font.getDefault();
}
}
public Object deriveTrueTypeFont(Object font, float size, int weight) {
return ((Font)font).derive(weight, (int)size);
}
private void openParentImageGallery(final ActionListener response) {
super.openImageGallery(response);
}
public void openImageGallery(final ActionListener response) {
app.invokeLater(new Runnable() {
public void run() {
try {
final FilePicker picker = FilePicker.getInstance();
picker.setFilter(".jpg");
final String file = picker.show();
Display.getInstance().callSerially(new Runnable() {
public void run() {
if (file != null) {
response.actionPerformed(new ActionEvent(file));
} else {
response.actionPerformed(null);
}
}
});
} catch (Throwable e) {
Display.getInstance().callSerially(new Runnable() {
public void run() {
BlackBerryOS5Implementation.this.openParentImageGallery(response);
}
});
}
}
});
}
/**
* @inheritDoc
*/
public String [] getAvailableRecordingMimeTypes(){
//The PCM format is not supported by BlackBerry devices on the CDMA network.
if(isCDMA()){
return new String[]{"audio/amr", "qcelp"};
}
return new String[]{"audio/amr", "audio/basic", "qcelp"};
}
private static void updateIndicator( final int inc ) {
UiApplication.getApplication().invokeLater(new Runnable() {
public void run() {
ApplicationIndicatorRegistry indicatorRegistry = ApplicationIndicatorRegistry.getInstance();
ApplicationIndicator indicator = indicatorRegistry.getApplicationIndicator();
if( indicator == null ) {
ApplicationIcon icon = new ApplicationIcon(net.rim.device.api.system.EncodedImage.getEncodedImageResource("push_small_size_icon.png"));
indicator = indicatorRegistry.register( icon, false, true );
}
unreadCount = inc;
indicator.setValue( unreadCount );
if( unreadCount > 0 ) {
indicator.setVisible( true );
} else {
indicator.setVisible( false );
}
}
});
}
public static void onMessage(final PushInputStream stream, final StreamConnection sc) {
if(pushCallback != null) {
new Thread() {
public void run() {
try {
final byte[] buffer = Util.readInputStream(stream);
try {
stream.accept();
Util.cleanup(stream);
sc.close();
} catch(Throwable t) {}
updateIndicator(unreadCount + 1);
Display.getInstance().callSerially(new Runnable() {
public void run() {
pushCallback.push(new String(buffer));
}
});
} catch(IOException err) {
err.printStackTrace();
}
}
}.start();
}
}
public static void onStatusChange(PushApplicationStatus pushAppStatus) {
if(pushCallback == null) {
return;
}
byte bpsStatus = pushAppStatus.getStatus();
final byte regReason = pushAppStatus.getReason();
final String error = pushAppStatus.getError();
boolean simChanged = false;
String logStatus = "Unknown " + bpsStatus;
switch( bpsStatus ) {
case PushApplicationStatus.STATUS_ACTIVE:
logStatus = "Active";
if(!instance.registerServerPush(Integer.toHexString(DeviceInfo.getDeviceId()), instance.getApplicationKey(), (byte)3,
Display.getInstance().getProperty("UDID", ""), Display.getInstance().getProperty("package_name", ""))) {
Display.getInstance().callSerially(new Runnable() {
public void run() {
pushCallback.pushRegistrationError("Server registration error", 1);
}
});
return;
}
final String str = Integer.toHexString(DeviceInfo.getDeviceId());
Display.getInstance().callSerially(new Runnable() {
public void run() {
pushCallback.registeredForPush(str);
}
});
break;
case PushApplicationStatus.STATUS_FAILED:
Display.getInstance().callSerially(new Runnable() {
public void run() {
pushCallback.pushRegistrationError(error, regReason);
}
});
logStatus = "Failed";
break;
case PushApplicationStatus.STATUS_NOT_REGISTERED:
logStatus = "Not Registered";
switch( pushAppStatus.getReason() ) {
case PushApplicationStatus.REASON_SIM_CHANGE:
simChanged = true;
break;
case PushApplicationStatus.REASON_API_CALL:
// should not happen, even if called then we already unregistered
break;
case PushApplicationStatus.REASON_REJECTED_BY_SERVER:
case PushApplicationStatus.REASON_NETWORK_ERROR:
case PushApplicationStatus.REASON_INVALID_PARAMETERS:
// registration failed
break;
}
final String finalLogStat = logStatus;
Display.getInstance().callSerially(new Runnable() {
public void run() {
pushCallback.pushRegistrationError(finalLogStat + ": " + error, regReason);
}
});
break;
case PushApplicationStatus.STATUS_PENDING:
logStatus = "Pending";
break;
}
}
public void registerPush(Hashtable metaData, boolean noFallback) {
int port = Integer.parseInt(Display.getInstance().getProperty("$CN1Port", "100"));
String appId = Display.getInstance().getProperty("$CN1AppId", "");
String bpsUrl = Display.getInstance().getProperty("$CN1BpsURL", "");
ApplicationDescriptor ad = ApplicationDescriptor.currentApplicationDescriptor();
boolean isEnterprise = Display.getInstance().getProperty("$CN1Enterprise", "false").equals("true");
// server type depends whether we get pushes through BES or BIS
byte serverType = isEnterprise ? PushApplicationDescriptor.SERVER_TYPE_NONE : PushApplicationDescriptor.SERVER_TYPE_BPAS;
// if enterprise network then there is no server URL
bpsUrl = isEnterprise ? null : bpsUrl;
PushApplicationDescriptor pad = new PushApplicationDescriptor( appId, port, bpsUrl, serverType, ad );
// check whether already registered or registration pending
PushApplicationStatus pushApplicationStatus = PushApplicationRegistry.getStatus( pad );
byte pasStatus = pushApplicationStatus.getStatus();
if( pasStatus == PushApplicationStatus.STATUS_ACTIVE ) {
// we already registered, update the statuses
onStatusChange(pushApplicationStatus);
return;
} else if( pasStatus == PushApplicationStatus.STATUS_PENDING ) {
// we already scheduled registration, wait for its result
} else {
// not registered yet, register
PushApplicationRegistry.registerApplication( pad );
}
}
public static void setPushCallback(PushCallback callback) {
pushCallback = callback;
}
}