com.phonegap.api.impl.NetworkCommand.java Source code

Java tutorial

Introduction

Here is the source code for com.phonegap.api.impl.NetworkCommand.java

Source

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