Java tutorial
/** * The MIT License * ------------------------------------------------------------- * Copyright (c) 2008, Rob Ellis, Brock Whitten, Brian Leroux, Joe Bowser, Dave Johnson, Nitobi * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.phonegap.api.impl; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import javax.microedition.io.Connector; import javax.microedition.io.HttpConnection; import javax.microedition.io.StreamConnection; import javax.microedition.io.file.FileConnection; import net.rim.device.api.io.Base64OutputStream; import net.rim.device.api.system.CoverageInfo; import net.rim.device.api.system.DeviceInfo; import net.rim.device.api.system.RadioInfo; import net.rim.device.api.ui.UiApplication; import org.json.me.JSONObject; import com.phonegap.PhoneGap; import com.phonegap.api.Command; public class NetworkCommand implements Command { private static final int REACHABLE_COMMAND = 0; private static final int XHR_UPLOAD_COMMAND = 1; private static final int XHR_COMMAND = 2; private static final String CODE = "PhoneGap=network"; private static final int NOT_REACHABLE = 0; private static final int REACHABLE_VIA_CARRIER_DATA_NETWORK = 1; private static final int REACHABLE_VIA_WIFI_NETWORK = 2; public PhoneGap berryGap; private ConnectionThread connThread = new ConnectionThread(); public NetworkCommand(PhoneGap gap) { berryGap = gap; connThread.start(); } /** * Determines whether the specified instruction is accepted by the command. * * @param instruction * The string instruction passed from JavaScript via cookie. * @return true if the Command accepts the instruction, false otherwise. */ public boolean accept(String instruction) { return instruction != null && instruction.startsWith(CODE); } public String execute(String instruction) { JSONObject fileData = null; String reqURL = null; switch (getCommand(instruction)) { case REACHABLE_COMMAND: // Determine the active Wireless Access Families // WiFi will have precedence over carrier data. int service = RadioInfo.getActiveWAFs(); int reachability = NOT_REACHABLE; if ((service & RadioInfo.WAF_3GPP) != 0 || (service & RadioInfo.WAF_CDMA) != 0 || (service & RadioInfo.WAF_IDEN) != 0) { reachability = REACHABLE_VIA_CARRIER_DATA_NETWORK; } if ((service & RadioInfo.WAF_WLAN) != 0) { reachability = REACHABLE_VIA_WIFI_NETWORK; } return ";navigator.network.lastReachability = " + reachability + ";if (navigator.network.isReachable_success) navigator.network.isReachable_success(" + reachability + ");"; case XHR_UPLOAD_COMMAND: reqURL = instruction.substring(CODE.length() + 11); int tildaIndex = instruction.lastIndexOf('~'); try { JSONObject fileDetails = new JSONObject(instruction.substring(tildaIndex + 1)); instruction = instruction.substring(0, tildaIndex); String filePath = fileDetails.getString("filePath"); LogCommand.DEBUG("Reading " + filePath + " for uploading"); String loggedinUser = fileDetails.getString("user"); fileData = readFileData(filePath); String fileTargetPath = fileDetails.getString("targetPath") + "/" + fileData.getString("filename"); fileData.put("uid", loggedinUser); fileData.put("filepath", fileTargetPath); } catch (Exception e) { LogCommand.DEBUG("Error while reading image file for uploading. " + e.getMessage()); return ";if (navigator.network.XHR_error) { navigator.network.XHR_error('Error occured while reading file.'); };"; } case XHR_COMMAND: reqURL = reqURL == null ? instruction.substring(CODE.length() + 5) : reqURL; String POSTdata = null; int pipeIndex = reqURL.indexOf("|"); if (pipeIndex > -1) { POSTdata = reqURL.substring(pipeIndex + 1); reqURL = reqURL.substring(0, pipeIndex); } LogCommand.DEBUG("Calling service " + reqURL); if (fileData != null) { POSTdata += "&file=" + urlEncode(fileData.toString()); } // Something that is used by the BlackBerry Enterprise Server for // the BES Push apps. We want to initiate a direct TCP connection, // so this parameter needs to be specified. if (!DeviceInfo.isSimulator()) { reqURL += ";deviceside=true"; } // Check for WIFI connectivity, optionally append the interface=wifi // parameter to the end of URL. // If you have data disabled and WIFI enabled, but you cannot access // the network, then check that // the device is not configured to connect to a VPN network. // WIFI Connection > WIFI Options > Select the active network > Edit // > Set VPN to None if ((RadioInfo.getActiveWAFs() & RadioInfo.WAF_WLAN) != 0) { if (CoverageInfo.isCoverageSufficient(CoverageInfo.COVERAGE_DIRECT, RadioInfo.WAF_WLAN, true)) { reqURL += ";interface=wifi"; } } connThread.fetch(reqURL, POSTdata); reqURL = null; POSTdata = null; break; } return null; } private int getCommand(String instruction) { String command = instruction.substring(CODE.length() + 1); if (command.startsWith("reach")) return REACHABLE_COMMAND; if (command.startsWith("xhrupload")) return XHR_UPLOAD_COMMAND; if (command.startsWith("xhr")) return XHR_COMMAND; return -1; } /** * Adds the specified text to the PhoneGap response queue. For use by * asynchronous XHR requests. */ private void updateContent(final String text) { UiApplication.getUiApplication().invokeLater(new Runnable() { public void run() { berryGap.pendingResponses.addElement(text); } }); } public void stopXHR() { connThread._stop = true; } private class ConnectionThread extends Thread { private static final int TIMEOUT = 500; // ms private String _theUrl; private String _POSTdata; private volatile boolean _fetchStarted = false; public volatile boolean _stop = false; // Retrieve the URL. private synchronized String getUrl() { return _theUrl; } private synchronized String getPOSTdata() { return _POSTdata; } // Fetch a page. // Synchronized so that we don't miss requests. private void fetch(String url, String POSTdata) { synchronized (this) { _fetchStarted = true; _theUrl = url; _POSTdata = POSTdata; } } public void run() { for (;;) { _stop = false; // Thread control while (!_fetchStarted && !_stop) { // Sleep for a bit so we don't spin. try { sleep(TIMEOUT); } catch (InterruptedException e) { System.err.println(e.toString()); } } // Exit condition if (_stop) { continue; } // This entire block is synchronized. This ensures we won't miss // fetch requests // made while we process a page. synchronized (this) { String content = ""; HttpConnection httpConn = null; StreamConnection s = null; String postData = getPOSTdata(); // Open the connection and extract the data. try { if (postData != null) { s = (StreamConnection) Connector.open(getUrl(), Connector.READ_WRITE); } else { s = (StreamConnection) Connector.open(getUrl()); } httpConn = (HttpConnection) s; httpConn.setRequestMethod((postData != null) ? HttpConnection.POST : HttpConnection.GET); // === SET HTTP REQUEST HEADERS HERE === // Set the user agent string. Could try to parse out // device models/numbers, but do I really want to? Yes, // I do, according to this KB article: // http://www.blackberry.com/knowledgecenterpublic/livelink.exe/fetch/2000/348583/800451/800563/How_To_-_Use_reliable_push_without_making_a_BlackBerry_Browser_request.html?nodeid=1222784&vernum=0 httpConn.setRequestProperty("user-agent", "BlackBerry" + DeviceInfo.getDeviceName() + "/" + DeviceInfo.getSoftwareVersion()); // Also need to set profile header according to above // article. httpConn.setRequestProperty("profile", "http://www.blackberry.net/go/mobile/profiles/uaprof/" + DeviceInfo.getDeviceName() + "/" + DeviceInfo.getSoftwareVersion() + ".rdf"); httpConn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); // Here's an example of setting the Accept header to a // particular subset of MIME types. By the HTTP spec, if // none is specified the assumed value is 'all' types // are accepted. // httpConn.setRequestProperty("Accept","text/plain,text/html,application/rss+xml,text/javascript,text/xml"); // Setting the accepted character set. Same as above, // default is all, so don't have to set it. // httpConn.setRequestProperty("Accept-Charset","UTF-8,*"); // === WRITE OUT POST DATA HERE === if (postData != null) { httpConn.setRequestProperty("Content-length", String.valueOf(postData.length())); DataOutputStream dos = httpConn.openDataOutputStream(); byte[] postBytes = postData.getBytes(); for (int i = 0; i < postBytes.length; i++) { dos.writeByte(postBytes[i]); } dos.flush(); dos.close(); dos = null; } int status = httpConn.getResponseCode(); // Tip: If you're not getting the expected response from // an XHR call, pop a breakpoint here and see if the // HTTP response code is 200. If you're getting a 406 // (Not Acceptable), the Accept header might be not set // to some satisfactory value by the server. if (status == HttpConnection.HTTP_OK) { InputStream input = s.openInputStream(); byte[] data = new byte[256]; int len = 0; int size = 0; StringBuffer raw = new StringBuffer(); while (-1 != (len = input.read(data))) { raw.append(new String(data, 0, len)); size += len; } content = raw.toString(); raw = null; input.close(); input = null; } if (_stop) continue; updateContent(";if (navigator.network.XHR_success) { navigator.network.XHR_success(" + (!content.equals("") ? content : "{error:true,message:'Bad server response.',httpcode:" + status + "}") + "); };"); s.close(); } catch (IOException e) { if (_stop) continue; String resp = e.getMessage().toLowerCase(); if (content.equals("")) { if (resp.indexOf("tunnel") > -1 || resp.indexOf("Tunnel") > -1 || resp.indexOf("apn") > -1 || resp.indexOf("APN") > -1) { resp = "{error:true,message:'There was a communication error. Are your APN settings configured (BlackBerry menu -> Options -> Advanced Options -> TCP/IP). Contact your service provider for details on how to set up your APN settings.'}"; } else { resp = "{error:true,message:'IOException during HTTP request: " + e.getMessage().replace('\'', ' ') + "',httpcode:null}"; } } else { resp = content; } LogCommand.LOG("Error while service call " + resp); updateContent(";if (navigator.network.XHR_error) { navigator.network.XHR_error(" + resp + "); };"); resp = null; } finally { content = null; s = null; httpConn = null; postData = null; } // We're finished with the operation so reset the start // state. _fetchStarted = false; } } } } private JSONObject readFileData(String filePath) throws Exception { FileConnection fileConnection = null; try { fileConnection = (FileConnection) Connector.open(filePath, Connector.READ); long fileSize = fileConnection.fileSize(); long lastModified = fileConnection.lastModified(); String fileName = fileConnection.getName(); InputStream fileStream = fileConnection.openInputStream(); ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Base64OutputStream base64OutStream = new Base64OutputStream(outStream); int byteRead = 0; while ((byteRead = fileStream.read()) != -1) { base64OutStream.write(byteRead); } base64OutStream.flush(); base64OutStream.close(); outStream.flush(); String base64Data = outStream.toString(); outStream.close(); JSONObject fileData = new JSONObject(); fileData.put("file", base64Data); fileData.put("filename", fileName); fileData.put("filesize", fileSize); fileData.put("timestamp", lastModified); fileData.put("filemime", getMimeType(fileName)); LogCommand.DEBUG( "Successfully read file " + filePath + ". Base 64 Data length is " + base64Data.length()); return fileData; } catch (Exception e) { LogCommand.LOG("Fail to read file " + filePath + ". " + e.getMessage()); throw e; } finally { if (fileConnection != null && fileConnection.isOpen()) { try { fileConnection.close(); } catch (IOException e) { } } } } private String urlEncode(String value) { value = PhoneGap.replace(value, "+", "%2B"); value = PhoneGap.replace(value, "=", "%3D"); value = PhoneGap.replace(value, "/", "%2F"); return value; } private String getMimeType(String fileName) { int dotPos = fileName.lastIndexOf('.'); if (dotPos == -1) return "image/unknown"; return "image/" + fileName.toLowerCase().substring(dotPos + 1); } }