/* * 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; } }