ti.modules.titanium.network.NetworkModule.java Source code

Java tutorial

Introduction

Here is the source code for ti.modules.titanium.network.NetworkModule.java

Source

/**
 * Appcelerator Titanium Mobile
 * Copyright (c) 2009-2012 by Appcelerator, Inc. All Rights Reserved.
 * Licensed under the terms of the Apache Public License
 * Please see the LICENSE included with this distribution for details.
 */
package ti.modules.titanium.network;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.http.client.CookieStore;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.appcelerator.kroll.KrollDict;
import org.appcelerator.kroll.KrollModule;
import org.appcelerator.kroll.KrollProxy;
import org.appcelerator.kroll.annotations.Kroll;
import org.appcelerator.kroll.common.Log;
import org.appcelerator.titanium.TiApplication;
import org.appcelerator.titanium.TiC;
import org.appcelerator.titanium.TiContext;
import org.appcelerator.titanium.util.TiConvert;

import android.app.Activity;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;

@Kroll.module
public class NetworkModule extends KrollModule {

    private static final String TAG = "TiNetwork";
    private static CookieStore httpCookieStore;

    public static final String EVENT_CONNECTIVITY = "change";
    public static final String NETWORK_USER_AGENT = System.getProperties().getProperty("http.agent");

    @Kroll.constant
    public static final int NETWORK_NONE = 0;
    @Kroll.constant
    public static final int NETWORK_WIFI = 1;
    @Kroll.constant
    public static final int NETWORK_MOBILE = 2;
    @Kroll.constant
    public static final int NETWORK_LAN = 3;
    @Kroll.constant
    public static final int NETWORK_UNKNOWN = 4;

    public enum State {
        UNKNOWN,

        /** This state is returned if there is connectivity to any network **/
        CONNECTED,
        /**
         * This state is returned if there is no connectivity to any network. This is set
         * to true under two circumstances:
         * <ul>
         * <li>When connectivity is lost to one network, and there is no other available
         * network to attempt to switch to.</li>
         * <li>When connectivity is lost to one network, and the attempt to switch to
         * another network fails.</li>
         */
        NOT_CONNECTED
    }

    class NetInfo {
        public State state;
        public boolean failover;
        public String typeName;
        public int type;
        public String reason;

        public NetInfo() {
            state = State.UNKNOWN;
            failover = false;
            typeName = "NONE";
            type = -1;
            reason = "";
        }
    };

    private NetInfo lastNetInfo;

    private boolean isListeningForConnectivity;
    private TiNetworkListener networkListener;
    private ConnectivityManager connectivityManager;

    private Handler messageHandler = new Handler() {
        public void handleMessage(Message msg) {
            Bundle b = msg.getData();

            boolean connected = b.getBoolean(TiNetworkListener.EXTRA_CONNECTED);
            int type = b.getInt(TiNetworkListener.EXTRA_NETWORK_TYPE);
            String typeName = b.getString(TiNetworkListener.EXTRA_NETWORK_TYPE_NAME);
            boolean failover = b.getBoolean(TiNetworkListener.EXTRA_FAILOVER);
            String reason = b.getString(TiNetworkListener.EXTRA_REASON);

            // Set last state
            synchronized (lastNetInfo) {
                if (connected) {
                    lastNetInfo.state = State.CONNECTED;
                } else {
                    lastNetInfo.state = State.NOT_CONNECTED;
                }
                lastNetInfo.type = type;
                lastNetInfo.typeName = typeName;
                lastNetInfo.failover = failover;
                lastNetInfo.reason = reason;
            }

            KrollDict data = new KrollDict();
            data.put("online", connected);
            int titaniumType = networkTypeToTitanium(connected, type);
            data.put("networkType", titaniumType);
            data.put("networkTypeName", networkTypeToTypeName(titaniumType));
            data.put("reason", reason);
            fireEvent(EVENT_CONNECTIVITY, data);
        }
    };

    public NetworkModule() {
        super();

        this.lastNetInfo = new NetInfo();
        this.isListeningForConnectivity = false;
    }

    public NetworkModule(TiContext tiContext) {
        this();
    }

    @Override
    public void handleCreationArgs(KrollModule createdInModule, Object[] args) {
        super.handleCreationArgs(createdInModule, args);

        setProperty("userAgent",
                NETWORK_USER_AGENT + " Titanium/" + TiApplication.getInstance().getTiBuildVersion());
    }

    @Override
    protected void eventListenerAdded(String event, int count, KrollProxy proxy) {
        super.eventListenerAdded(event, count, proxy);
        if ("change".equals(event)) {
            if (!isListeningForConnectivity) {
                manageConnectivityListener(true);
            }
        }
    }

    @Override
    protected void eventListenerRemoved(String event, int count, KrollProxy proxy) {
        super.eventListenerRemoved(event, count, proxy);
        if ("change".equals(event) && count == 0) {
            manageConnectivityListener(false);
        }
    }

    @Kroll.getProperty
    @Kroll.method
    public boolean getOnline() {
        boolean result = false;

        ConnectivityManager cm = getConnectivityManager();
        if (cm != null) {
            NetworkInfo ni = getConnectivityManager().getActiveNetworkInfo();

            if (ni != null && ni.isAvailable() && ni.isConnected()) {
                result = true;
            }
        } else {
            Log.w(TAG, "ConnectivityManager was null", Log.DEBUG_MODE);
        }
        return result;
    }

    protected int networkTypeToTitanium(boolean online, int androidType) {
        int type = NetworkModule.NETWORK_UNKNOWN;
        if (online) {
            switch (androidType) {
            case ConnectivityManager.TYPE_WIFI:
                type = NetworkModule.NETWORK_WIFI;
                break;
            case ConnectivityManager.TYPE_MOBILE:
                type = NetworkModule.NETWORK_MOBILE;
                break;
            default:
                type = NetworkModule.NETWORK_UNKNOWN;
            }
        } else {
            type = NetworkModule.NETWORK_NONE;
        }
        return type;
    }

    @Kroll.getProperty
    @Kroll.method
    public int getNetworkType() {
        int type = NETWORK_UNKNOWN;

        // start event needs network type. So get it if we don't have it.
        if (connectivityManager == null) {
            connectivityManager = getConnectivityManager();
        }

        try {
            NetworkInfo ni = connectivityManager.getActiveNetworkInfo();
            if (ni != null && ni.isAvailable() && ni.isConnected()) {
                type = networkTypeToTitanium(true, ni.getType());
            } else {
                type = NetworkModule.NETWORK_NONE;
            }
        } catch (SecurityException e) {
            Log.w(TAG, "Permission has been removed. Cannot determine network type: " + e.getMessage());
        }
        return type;
    }

    @Kroll.getProperty
    @Kroll.method
    public String getNetworkTypeName() {
        return networkTypeToTypeName(getNetworkType());
    }

    private String networkTypeToTypeName(int type) {
        switch (type) {
        case 0:
            return "NONE";
        case 1:
            return "WIFI";
        case 2:
            return "MOBILE";
        case 3:
            return "LAN";
        default:
            return "UNKNOWN";
        }
    }

    @Kroll.method
    @Kroll.topLevel
    public String encodeURIComponent(String component) {
        return Uri.encode(component);
    }

    @Kroll.method
    @Kroll.topLevel
    public String decodeURIComponent(String component) {
        return Uri.decode(component);
    }

    protected void manageConnectivityListener(boolean attach) {
        if (attach) {
            if (!isListeningForConnectivity) {
                if (hasListeners(EVENT_CONNECTIVITY)) {
                    if (networkListener == null) {
                        networkListener = new TiNetworkListener(messageHandler);
                    }
                    networkListener.attach(TiApplication.getInstance().getApplicationContext());
                    isListeningForConnectivity = true;
                    Log.d(TAG, "Adding connectivity listener", Log.DEBUG_MODE);
                }
            }
        } else {
            if (isListeningForConnectivity) {
                networkListener.detach();
                isListeningForConnectivity = false;
                Log.d(TAG, "Removing connectivity listener.", Log.DEBUG_MODE);
            }
        }
    }

    private ConnectivityManager getConnectivityManager() {
        ConnectivityManager cm = null;

        Context a = TiApplication.getInstance();
        if (a != null) {
            cm = (ConnectivityManager) a.getSystemService(Context.CONNECTIVITY_SERVICE);
        } else {
            Log.w(TAG, "Activity is null when trying to retrieve the connectivity service", Log.DEBUG_MODE);
        }

        return cm;
    }

    @Override
    public void onDestroy(Activity activity) {
        super.onDestroy(activity);
        manageConnectivityListener(false);
        connectivityManager = null;
    }

    public static CookieStore getHTTPCookieStoreInstance() {
        if (httpCookieStore == null) {
            httpCookieStore = new BasicCookieStore();
        }
        return httpCookieStore;
    }

    /**
     * Adds a cookie to the HTTPClient cookie store. Any existing cookie with the same domain and name will be replaced with
     * the new cookie. This seems like a bug in org.apache.http.impl.client.BasicCookieStore because based on RFC6265
     * (http://tools.ietf.org/html/rfc6265#section-4.1.2), an existing cookie with the same cookie-name, domain-value and
     * path-value with the new cookie will be evicted and replaced.
     * @param cookieProxy the cookie to add
     */
    @Kroll.method
    public void addHTTPCookie(CookieProxy cookieProxy) {
        BasicClientCookie cookie = cookieProxy.getHTTPCookie();
        if (cookie != null) {
            getHTTPCookieStoreInstance().addCookie(cookie);
        }
    }

    /**
     * Gets all the cookies with the domain, path and name matched with the given values. If name is null, gets all the cookies with
     * the domain and path matched.
     * @param domain the domain of the cookie to get. It is case-insensitive.
     * @param path the path of the cookie to get. It is case-sensitive.
     * @param name the name of the cookie to get. It is case-sensitive.
     * @return an array of cookies. If name is null, returns all the cookies with the domain and path matched.
     */
    @Kroll.method
    public CookieProxy[] getHTTPCookies(String domain, String path, String name) {
        if (domain == null || domain.length() == 0) {
            if (Log.isDebugModeEnabled()) {
                Log.e(TAG, "Unable to get the HTTP cookies. Need to provide a valid domain.");
            }
            return null;
        }
        if (path == null || path.length() == 0) {
            path = "/";
        }
        ArrayList<CookieProxy> cookieList = new ArrayList<CookieProxy>();
        List<Cookie> cookies = getHTTPCookieStoreInstance().getCookies();
        for (Cookie cookie : cookies) {
            String cookieName = cookie.getName();
            String cookieDomain = cookie.getDomain();
            String cookiePath = cookie.getPath();
            if ((name == null || cookieName.equals(name)) && domainMatch(cookieDomain, domain)
                    && pathMatch(cookiePath, path)) {
                cookieList.add(new CookieProxy(cookie));
            }
        }
        if (!cookieList.isEmpty()) {
            return cookieList.toArray(new CookieProxy[cookieList.size()]);
        }
        return null;
    }

    /**
     * Gets all the cookies with the domain matched with the given value.
     * @param domain the domain of the cookie to get. It is case-insensitive.
     * @return an array of cookies with the domain matched.
     */
    @Kroll.method
    public CookieProxy[] getHTTPCookiesForDomain(String domain) {
        if (domain == null || domain.length() == 0) {
            if (Log.isDebugModeEnabled()) {
                Log.e(TAG, "Unable to get the HTTP cookies. Need to provide a valid domain.");
            }
            return null;
        }
        ArrayList<CookieProxy> cookieList = new ArrayList<CookieProxy>();
        List<Cookie> cookies = getHTTPCookieStoreInstance().getCookies();
        for (Cookie cookie : cookies) {
            String cookieDomain = cookie.getDomain();
            if (domainMatch(cookieDomain, domain)) {
                cookieList.add(new CookieProxy(cookie));
            }
        }
        if (!cookieList.isEmpty()) {
            return cookieList.toArray(new CookieProxy[cookieList.size()]);
        }
        return null;
    }

    /** Removes the cookie with the domain, path and name exactly the same as the given values.
     * @param domain the domain of the cookie to remove. It is case-insensitive.
     * @param path the path of the cookie to remove. It is case-sensitive.
     * @param name the name of the cookie to remove. It is case-sensitive.
     */
    @Kroll.method
    public void removeHTTPCookie(String domain, String path, String name) {
        if (domain == null || name == null) {
            if (Log.isDebugModeEnabled()) {
                Log.e(TAG, "Unable to remove the HTTP cookie. Need to provide a valid domain / name.");
            }
            return;
        }
        CookieStore cookieStore = getHTTPCookieStoreInstance();
        List<Cookie> cookies = new ArrayList<Cookie>(cookieStore.getCookies());
        cookieStore.clear();
        for (Cookie cookie : cookies) {
            String cookieName = cookie.getName();
            String cookieDomain = cookie.getDomain();
            String cookiePath = cookie.getPath();
            if (!(name.equals(cookieName) && stringEqual(domain, cookieDomain, false)
                    && stringEqual(path, cookiePath, true))) {
                cookieStore.addCookie(cookie);
            }
        }
    }

    /**
     * Removes all the cookies with the domain matched with the given value.
     * @param domain the domain of the cookie to remove. It is case-insensitive.
     */
    @Kroll.method
    public void removeHTTPCookiesForDomain(String domain) {
        CookieStore cookieStore = getHTTPCookieStoreInstance();
        List<Cookie> cookies = new ArrayList<Cookie>(cookieStore.getCookies());
        cookieStore.clear();
        for (Cookie cookie : cookies) {
            String cookieDomain = cookie.getDomain();
            if (!(domainMatch(cookieDomain, domain))) {
                cookieStore.addCookie(cookie);
            }
        }
    }

    /**
     * Removes all the cookies in the HTTPClient cookie store.
     */
    @Kroll.method
    public void removeAllHTTPCookies() {
        CookieStore cookieStore = getHTTPCookieStoreInstance();
        cookieStore.clear();
    }

    /**
     * Adds a cookie to the system cookie store. Any existing cookie with the same domain, path and name will be replaced with
     * the new cookie. The cookie being set must not have expired, otherwise it will be ignored.
     * @param cookieProxy the cookie to add
     */
    @Kroll.method
    public void addSystemCookie(CookieProxy cookieProxy) {
        BasicClientCookie cookie = cookieProxy.getHTTPCookie();
        String cookieString = cookie.getName() + "=" + cookie.getValue();
        String domain = cookie.getDomain();
        if (domain == null) {
            Log.w(TAG, "Unable to add system cookie. Need to provide domain.");
            return;
        }
        cookieString += "; domain=" + domain;

        String path = cookie.getPath();
        Date expiryDate = cookie.getExpiryDate();
        boolean secure = cookie.isSecure();
        boolean httponly = TiConvert.toBoolean(cookieProxy.getProperty(TiC.PROPERTY_HTTP_ONLY), false);
        if (path != null) {
            cookieString += "; path=" + path;
        }
        if (expiryDate != null) {
            cookieString += "; expires=" + CookieProxy.systemExpiryDateFormatter.format(expiryDate);
        }
        if (secure) {
            cookieString += "; secure";
        }
        if (httponly) {
            cookieString += " httponly";
        }
        CookieSyncManager.createInstance(TiApplication.getInstance().getRootOrCurrentActivity());
        CookieManager cookieManager = CookieManager.getInstance();
        cookieManager.setCookie(domain, cookieString);
        CookieSyncManager.getInstance().sync();
    }

    /**
     * Gets all the cookies with the domain, path and name matched with the given values. If name is null, gets all the cookies with
     * the domain and path matched.
     * @param domain the domain of the cookie to get. It is case-insensitive.
     * @param path the path of the cookie to get. It is case-sensitive.
     * @param name the name of the cookie to get. It is case-sensitive.
     * @return an array of cookies only with name and value specified. If name is null, returns all the cookies with the domain and path matched.
     */
    @Kroll.method
    public CookieProxy[] getSystemCookies(String domain, String path, String name) {
        if (domain == null || domain.length() == 0) {
            if (Log.isDebugModeEnabled()) {
                Log.e(TAG, "Unable to get the HTTP cookies. Need to provide a valid domain.");
            }
            return null;
        }
        if (path == null || path.length() == 0) {
            path = "/";
        }

        ArrayList<CookieProxy> cookieList = new ArrayList<CookieProxy>();
        CookieSyncManager.createInstance(TiApplication.getInstance().getRootOrCurrentActivity());
        CookieManager cookieManager = CookieManager.getInstance();
        String url = domain.toLowerCase() + path;
        String cookieString = cookieManager.getCookie(url); // The cookieString is in the format of NAME=VALUE[;
        // NAME=VALUE]
        if (cookieString != null) {
            String[] cookieValues = cookieString.split("; ");
            for (int i = 0; i < cookieValues.length; i++) {
                String[] pair = cookieValues[i].split("=", 2);
                String cookieName = pair[0];
                String value = pair.length == 2 ? pair[1] : null;
                if (name == null || cookieName.equals(name)) {
                    cookieList.add(new CookieProxy(cookieName, value, null, null));
                }
            }
        }
        if (!cookieList.isEmpty()) {
            return cookieList.toArray(new CookieProxy[cookieList.size()]);
        }
        return null;
    }

    /**
     * Removes the cookie with the domain, path and name exactly the same as the given values.
     * @param domain the domain of the cookie to remove. It is case-insensitive.
     * @param path the path of the cookie to remove. It is case-sensitive.
     * @param name the name of the cookie to remove. It is case-sensitive.
     */
    @Kroll.method
    public void removeSystemCookie(String domain, String path, String name) {
        if (domain == null || name == null) {
            if (Log.isDebugModeEnabled()) {
                Log.e(TAG, "Unable to remove the system cookie. Need to provide a valid domain / name.");
            }
            return;
        }
        String lower_domain = domain.toLowerCase();
        String cookieString = name + "=; domain=" + lower_domain + "; path=" + path + "; expires="
                + CookieProxy.systemExpiryDateFormatter.format(new Date(0));
        CookieSyncManager.createInstance(TiApplication.getInstance().getRootOrCurrentActivity());
        CookieManager cookieManager = CookieManager.getInstance();
        cookieManager.setCookie(lower_domain, cookieString);
        CookieSyncManager.getInstance().sync();
    }

    /**
     * Removes all the cookies in the system cookie store.
     */
    @Kroll.method
    public void removeAllSystemCookies() {
        CookieSyncManager.createInstance(TiApplication.getInstance().getRootOrCurrentActivity());
        CookieManager cookieManager = CookieManager.getInstance();
        cookieManager.removeAllCookie();
        CookieSyncManager.getInstance().sync();
    }

    /**
     * Helper method to decide whether the domain matches the cookie's domain. If the both domains are null, return true.
     * The domain matching follows RFC6265 (http://tools.ietf.org/html/rfc6265#section-5.1.3).
     * @param cookieDomain cookie's domain
     * @param domain domain to match
     * @return true if the domain matches cookieDomain; false otherwise. If the both domains are null, return true.
     */
    private boolean domainMatch(String cookieDomain, String domain) {
        if (cookieDomain == null && domain == null) {
            return true;
        }
        if (cookieDomain == null || domain == null) {
            return false;
        }

        String lower_cookieDomain = cookieDomain.toLowerCase();
        String lower_domain = domain.toLowerCase();
        if (lower_cookieDomain.startsWith(".")) {
            if (lower_domain.endsWith(lower_cookieDomain.substring(1))) {
                int cookieLen = lower_cookieDomain.length();
                int domainLen = lower_domain.length();
                if (domainLen > cookieLen - 1) {
                    // make sure bar.com doesn't match .ar.com
                    return lower_domain.charAt(domainLen - cookieLen) == '.';
                }
                return true;
            }
            return false;
        } else {
            return lower_domain.equals(lower_cookieDomain);
        }
    }

    /**
     * Helper method to decide whether the path matches the cookie's path. If the cookie's path is null or an empty string, return true.
     * If the path is null or an empty string, use "/" as the default value. The path matching follows RFC6265 (http://tools.ietf.org/html/rfc6265#section-5.1.4).
     * @param cookiePath cookie's path
     * @param path path to match
     * @return true if the path matches cookiePath; false otherwise. If cookiePath is null or an empty string, return true.
     */
    private boolean pathMatch(String cookiePath, String path) {
        if (cookiePath == null || cookiePath.length() == 0) {
            return true;
        }
        if (path == null || path.length() == 0) {
            path = "/";
        }

        if (path.startsWith(cookiePath)) {
            int cookieLen = cookiePath.length();
            int pathLen = path.length();
            if (cookiePath.charAt(cookieLen - 1) != '/' && pathLen > cookieLen) {
                // make sure /wee doesn't match /we
                return path.charAt(cookieLen) == '/';
            }
            return true;
        }
        return false;
    }

    /**
     * Helper method to determine whether two strings are equal.
     * @param s1 string to compare
     * @param s2 string to compare
     * @param isCaseSensitive true if using case-sensitive comparison; false if using case-insensitive comparison.
     * @return true if the two strings are both null or they are equal.
     */
    private boolean stringEqual(String s1, String s2, boolean isCaseSensitive) {
        if (s1 == null && s2 == null) {
            return true;
        }
        if (s1 != null && s2 != null) {
            if ((isCaseSensitive && s1.equals(s2))
                    || (!isCaseSensitive && s1.toLowerCase().equals(s2.toLowerCase()))) {
                return true;
            }
        }
        return false;
    }

    @Override
    public String getApiName() {
        return "Ti.Network";
    }
}