com.google.appinventor.components.runtime.BlockyTalky.java Source code

Java tutorial

Introduction

Here is the source code for com.google.appinventor.components.runtime.BlockyTalky.java

Source

// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2009-2011 Google, All Rights reserved
// Copyright 2011-2012 MIT, All rights reserved
// Released under the MIT License https://raw.github.com/mit-cml/app-inventor/master/mitlicense.txt

package com.google.appinventor.components.runtime;

import com.google.appinventor.components.annotations.DesignerComponent;
import com.google.appinventor.components.annotations.DesignerProperty;
import com.google.appinventor.components.annotations.SimpleEvent;
import com.google.appinventor.components.annotations.SimpleFunction;
import com.google.appinventor.components.annotations.SimpleObject;
import com.google.appinventor.components.annotations.SimpleProperty;
import com.google.appinventor.components.annotations.UsesLibraries;
import com.google.appinventor.components.annotations.UsesPermissions;
import com.google.appinventor.components.common.ComponentCategory;
import com.google.appinventor.components.common.PropertyTypeConstants;
import com.google.appinventor.components.common.YaVersion;
import com.google.appinventor.components.runtime.util.ErrorMessages;
import com.google.appinventor.components.runtime.util.MediaUtil;
import com.google.appinventor.components.runtime.util.YailList;

import com.google.appinventor.components.annotations.PropertyCategory;

import android.util.Log;
import android.os.Handler;
import android.os.Looper;
import android.app.Activity;
import android.os.AsyncTask;
import android.net.wifi.WifiManager;
import android.net.DhcpInfo;
import android.content.Context;

import java.io.IOException;
import java.net.DatagramSocket;
import java.net.MulticastSocket;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.logging.LogRecord;
import java.net.NetworkInterface;
import java.util.Collections;
import java.util.Date;

import org.apache.http.conn.util.InetAddressUtils;

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft;
import org.java_websocket.drafts.Draft_10;
import org.java_websocket.handshake.ServerHandshake;
import org.json.JSONObject;
import org.json.JSONException;

import java.awt.dnd.DragGestureEvent;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.Exception;
import java.lang.String;
import java.util.ArrayList;
import java.util.List;

import java.net.InetAddress;
import java.net.URI;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.List;
import com.google.appinventor.components.runtime.collect.Lists;

@DesignerComponent(version = 1, description = "Component needed to connect to BlockyTalky system", category = ComponentCategory.CONNECTIVITY, nonVisible = true, iconName = "images/bt.png")
@SimpleObject
@UsesPermissions(permissionNames = "android.permission.INTERNET, "
        + "android.permission.CHANGE_WIFI_MULTICAST_STATE, " + "android.permission.ACCESS_NETWORK_STATE, "
        + "android.permission.ACCESS_WIFI_STATE")
@UsesLibraries(libraries = "java_websocket.jar")
public class BlockyTalky extends AndroidNonvisibleComponent implements Component {
    private static String LOG_TAG = "BLOCKYTALKY";
    private ConcurrentHashMap<String, LocalBTData> localBTs;
    private HashMap<String, String> headers;
    private Handler handler;
    private Context mContext;
    private Handler announcerHandler;
    private String receivedMessage = "";
    private String receivedMessageFrom = "";
    private String nodeName = "BlockyTalky";
    private EmptyClient client = null;
    private final String blockyTalkyMessageRouter = "ws://btrouter.getdown.org:8005/dax";
    private DatagramSocket ds;
    private MulticastSocket broadcastSocket;
    final int UNICAST_PORT = 8675;
    final int MULTICAST_PORT = 8676;
    private final int MAX_PACKET_SIZE = 65535;
    private final String MULTICAST_ADDRESS = "224.0.0.1";

    private final Handler androidUIHandler;

    /**
     * Creates a new BlockyTalky component.
     */
    public BlockyTalky(ComponentContainer container) {
        super(container.$form());
        androidUIHandler = new Handler();

        localBTs = new ConcurrentHashMap<String, LocalBTData>();
        headers = new HashMap<String, String>() {
            {
                put("Sec-WebSocket-Protocol", "echo-protocol");
            }
        };
        handler = new Handler();
        announcerHandler = new Handler();
        Log.d(LOG_TAG, "Done with BlockyTalky constructor.");
        String addr = getIPAddress(true);
        if (addr.equals("")) {
            addr = getIPAddress(false);
        }

        try {
            mContext = container.$context();
            InetAddress ip = InetAddress.getByName(addr); //Android ip
            InetAddress bip = InetAddress.getByName(MULTICAST_ADDRESS);
            ds = new DatagramSocket(UNICAST_PORT);
            //ds.setReuseAddress(true);
            broadcastSocket = new MulticastSocket(MULTICAST_PORT);
            broadcastSocket.joinGroup(bip);
            broadcastSocket.setBroadcast(true);
            MessageListener listener = new MessageListener();
            Thread listenerThread = new Thread(listener);
            listenerThread.start();
            BroadcastListener broadcastListener = new BroadcastListener();
            Thread broadcastThread = new Thread(broadcastListener);
            broadcastThread.start();
            aquireMulticastLock();
            BroadcastAnnouncer announcer = new BroadcastAnnouncer();
            Thread announcerThread = new Thread(announcer);
            announcerThread.start();
        } catch (SocketException e) {
            Log.d(LOG_TAG,
                    "Caught SocketException while trying to initialize BlockyTalky messaging: " + e.getMessage());
            e.printStackTrace();
        } catch (UnknownHostException e) {
            Log.d(LOG_TAG, "Caught UnknownHostException while trying to reach Blockytalky messaging router: "
                    + e.getMessage());
        } catch (Exception e) {
            Log.d(LOG_TAG, "Exception while initializing BlockyTalky messaging: " + e);
            e.printStackTrace();
        }
        connectToMessagingRouter();

    }

    private void connectToMessagingRouter() {
        try {
            Log.d(LOG_TAG, "Opening connection to BlockyTalky messaging router");
            client = new EmptyClient(this.nodeName, new URI(blockyTalkyMessageRouter), new Draft_10(), headers,
                    10000);
            client.connect();
        } catch (Exception e) {
            Log.d(LOG_TAG, "Exception Caught while trying to connect to messasge router: " + e);
        }
    }

    private void aquireMulticastLock() {
        WifiManager wifi = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
        if (wifi != null) {
            WifiManager.MulticastLock lock = wifi.createMulticastLock(LOG_TAG);
            lock.acquire();
        } else {
            Log.d(LOG_TAG, "WIFI MANAGER NULL");
        }
    }

    /*
    * Gets IP Address for android phone
    */
    private static String getIPAddress(boolean useIPv4) {
        try {
            List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
            for (NetworkInterface intf : interfaces) {
                List<InetAddress> addrs = Collections.list(intf.getInetAddresses());
                for (InetAddress addr : addrs) {
                    if (!addr.isLoopbackAddress()) {
                        String sAddr = addr.getHostAddress().toUpperCase();
                        boolean isIPv4 = InetAddressUtils.isIPv4Address(sAddr);
                        if (useIPv4) {
                            if (isIPv4)
                                return sAddr;
                        } else {
                            if (!isIPv4) {
                                int delim = sAddr.indexOf('%'); // drop ip6 port suffix
                                return delim < 0 ? sAddr : sAddr.substring(0, delim);
                            }
                        }
                    }
                }
            }
        } catch (Exception ex) {
        } // for now eat exceptions
        return "";
    }

    private void sendString(String message, String destination) {
        BlockyTalkyMessage btMessage = new BlockyTalkyMessage(this.nodeName, destination, message);
        Log.d(LOG_TAG, "Sending to: " + destination);
        if (localBTs.get(destination) == null) {
            if (client != null && client.isOpen()) {
                client.send(btMessage.toJson());
            } else {
                //reopen client
                connectToMessagingRouter();
                client.send(btMessage.toJson()); //@fixme: message will likely be dropped on the floor, since registration will not be complete when this statement executes.
            }

        } else {
            new SendMessageTask().execute(btMessage.toJson(), destination);
        }
    }

    @SimpleFunction(description = "Sends a message to a BlockyTalky")
    public void SendMessage(String message, String destination) {
        this.sendString(message, destination);
        // new SendMessageTask().execute(message,destination);
    }

    @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING, defaultValue = "BlockyTalky")
    @SimpleProperty(description = "Name of message sender")
    public void NodeName(String name) {

        //
        // if ( (this.nodeName != null) && (this.nodeName != name) && (this.client.isOpen())) { //name change
        //   this.client.close();
        //   this.client = null;
        // }
        this.nodeName = name;
        if (client == null || !client.isOpen()) {
            connectToMessagingRouter();
        }

    }

    // @SimpleProperty(description = "List of Local BlockyTalkies")
    // public List<String> LocalBlockyTalkies(){
    //     List<String> localBTNames;
    //     localBTNames = Lists.newArrayList();
    //     localBTNames.add("TEST");
    //     for(String key : localBTs.keySet()){
    //         localBTNames.add(key);
    //     }
    //     return localBTNames;
    // }

    @SimpleProperty(description = "Contents of message received from WebSocket.")
    public String Message() {
        return receivedMessage;
    }
    /*
        @SimpleProperty(description = "Sender of message received from WebSocket.")
        public String Sender() { return receivedMessageFrom; }
    */

    @SimpleEvent(description = "Message was received from WebSocket.")
    public void OnMessageReceived() {
        Log.d(LOG_TAG, "inside BlockyTalky.OnMessageReceived");
        androidUIHandler.post(new Runnable() {
            public void run() {
                EventDispatcher.dispatchEvent(BlockyTalky.this, "OnMessageReceived");
            }
        });
    }

    public class LocalBTData {
        private InetAddress ip;
        private int port;
        private long lastReceived;

        public LocalBTData(InetAddress ip, int port, Date lastReceived) {
            this.ip = ip;
            this.lastReceived = lastReceived.getTime();
            this.port = port;
        }

        public InetAddress getIP() {
            return this.ip;
        }

        public int getPort() {
            return this.port;
        }

        public long getLastReceived() {
            return this.lastReceived;
        }

        public void setIP(InetAddress ip) {
            this.ip = ip;
        }

        public void setPort(int port) {
            this.port = port;
        }

        public void setLastReceived(Date newDate) {
            this.lastReceived = newDate.getTime();
        }

    }

    class SendMessageTask extends AsyncTask<String, Void, String> {

        @Override
        protected String doInBackground(String... strings) {
            try {
                String msg = strings[0];
                String dest = strings[1];
                InetAddress ip = localBTs.get(dest).getIP();
                int port = localBTs.get(dest).getPort();
                byte[] data = msg.getBytes();
                int len = data.length;
                DatagramPacket dp = new DatagramPacket(data, len, ip, port);
                ds.send(dp);
            } catch (IOException e) {
                Log.d(LOG_TAG, "Caught IO Exception: " + e.getMessage());
            }
            return "Success";
        }
    }

    class MessageListener implements Runnable {
        @Override
        public void run() {
            byte[] data = new byte[MAX_PACKET_SIZE];
            DatagramPacket dp = new DatagramPacket(data, data.length);
            while (true) {
                try {
                    Log.d(LOG_TAG, "BlockyTalky MessageListener about to block on receive()");
                    ds.receive(dp);
                    String json = new String(dp.getData(), 0, dp.getLength());
                    BlockyTalkyMessage btMessage = new BlockyTalkyMessage(json);
                    receivedMessage = btMessage.content;
                    receivedMessageFrom = btMessage.source;
                    Log.d(LOG_TAG, "Received unicast UDP message: " + receivedMessage);
                    handler.post(new Runnable() {
                        public void run() {
                            OnMessageReceived();
                        }
                    });
                } catch (IOException e) {
                    System.err.println("Caught IO Exception: " + e.getMessage());
                }
            }
        }
    }

    class BroadcastListener implements Runnable {

        @Override
        public void run() {
            byte[] data = new byte[1024];
            DatagramPacket dp = new DatagramPacket(data, 1024);
            while (true) {
                try {
                    broadcastSocket.receive(dp);
                    String message = new String(dp.getData(), 0, dp.getLength());
                    BlockyTalkyMessage btMessage = new BlockyTalkyMessage(message);
                    String btName = btMessage.source;
                    String btContent = btMessage.content;
                    String btDest = btMessage.destination;
                    if (btDest.equals("announce")) {
                        Log.d(LOG_TAG, "RECIEVED ANNOUNCEMENT");
                        Date timeStamp = new Date();
                        LocalBTData btData = new LocalBTData(dp.getAddress(), dp.getPort(), timeStamp);
                        localBTs.put(btName, btData);
                        Log.d(LOG_TAG, "**** Mapping " + btName + " -> " + dp.getAddress());
                    }
                    // if(localBTs.containsKey(btName){
                    // }else{
                    // }
                    System.out.println(message);
                } catch (IOException e) {
                    Log.d(LOG_TAG, "IO EXCEPTION IN BROADCAST LISTENER");
                    System.err.println("Caught IO Exception: " + e.getMessage());
                } catch (Exception e) {
                    Log.d(LOG_TAG, "EXCEPTION IN BROADCAST LISTENER " + e);
                    e.printStackTrace();
                }
            }
        }
    }

    private void filterLocalBTs() {
        Date now = new Date();
        long curMilSeconds = now.getTime();
        for (String key : localBTs.keySet()) {
            long lastMilSeconds = localBTs.get(key).getLastReceived();
            if (curMilSeconds - lastMilSeconds > 60000) {
                Log.d(LOG_TAG, "REMOVING " + key);
                localBTs.remove(key);
            }
        }
    }

    private class BroadcastAnnouncer implements Runnable {
        @Override
        public void run() {
            //Testing print out of localBTs
            Log.d(LOG_TAG, "BTUNITS: ");
            for (String key : localBTs.keySet()) {
                Date now = new Date();
                long curMilSeconds = now.getTime();
                long lastMilSeconds = localBTs.get(key).getLastReceived();
                Log.d(LOG_TAG, key);
                Log.d(LOG_TAG, "AGE : " + (curMilSeconds - lastMilSeconds) / 1000);
            }
            filterLocalBTs();
            //Send Announcement
            try {
                BlockyTalkyMessage announcement = new BlockyTalkyMessage(nodeName, "announce", "announce");
                String msg = announcement.toJson();
                byte[] data = msg.getBytes();
                int len = data.length;
                DatagramPacket dp = new DatagramPacket(data, len, InetAddress.getByName(MULTICAST_ADDRESS),
                        MULTICAST_PORT);
                ds.send(dp);
            } catch (Exception e) {
                Log.d(LOG_TAG, "Exception sending broadcast announcement :" + e);
                e.printStackTrace();
            }

            announcerHandler.postDelayed(this, 3000);
        }
    }

    public class BlockyTalkyMessage {
        private String source;
        private String destination;
        private String content;
        private String jsonFormatString = "{\"py/object\": \"__main__.Message\", \"channel\": \"Message\", \"content\": \"%s\", \"destination\": \"%s\", \"source\": \"%s\"}";

        public BlockyTalkyMessage(String source, String destination, String content) {
            this.source = source;
            this.destination = destination;
            this.content = content;
        }

        public BlockyTalkyMessage(String json) {
            JSONObject message;
            try {
                message = new JSONObject(json);
                this.source = message.getString("source");
                this.destination = message.getString("destination");
                this.content = message.getString("content");
            } catch (Exception e) {
                Log.d(LOG_TAG, "Exception while parsing json");
                e.printStackTrace();
            }
        }

        public String toJson() {
            return String.format(this.jsonFormatString, this.content, this.destination, this.source);
        }
    }

    private class EmptyClient extends WebSocketClient {

        private boolean isReadyForUse = false;
        private String nodeName = null;

        public EmptyClient(String nodeName, URI serverUri, Draft draft, HashMap<String, String> protocol,
                int timeout) {
            super(serverUri, draft, protocol, timeout);
            this.nodeName = nodeName;
        }

        public EmptyClient(String nodeName, URI serverUri, Draft draft) {
            super(serverUri, draft);
            this.nodeName = nodeName;
        }

        public EmptyClient(String nodeName, URI serverURI) {
            super(serverURI);
            this.nodeName = nodeName;
        }

        public boolean readyToCommunicate() {
            boolean ret = false;
            synchronized (this) {
                ret = this.isReadyForUse;
            }
            return ret;
        }

        @Override
        public void onOpen(ServerHandshake handshakedata) {
            Log.d(LOG_TAG, "BlockyTalky message router connection opened... attempting registration");
            BlockyTalkyMessage registerMessage = new BlockyTalkyMessage(this.nodeName, "dax", "");
            super.send(registerMessage.toJson());
            Log.d(LOG_TAG, "Registered with BlockyTalky message router as " + this.nodeName);
            synchronized (this) {
                isReadyForUse = true;
            }
        }

        @Override
        public void send(String message) {
            if (readyToCommunicate()) {
                super.send(message);
            } else {
                Log.d(LOG_TAG, "Could not send message via messaging router because it is not connected and ready: "
                        + message);
            }
        }

        @Override
        public void onClose(int code, String reason, boolean remote) {
            Log.d(LOG_TAG, "closed with exit code " + code + " additional info: " + reason);
            client = null;
        }

        @Override
        public void onMessage(String json) {
            BlockyTalkyMessage message = new BlockyTalkyMessage(json);
            receivedMessage = message.content;
            receivedMessageFrom = message.source;
            Log.d(LOG_TAG, "*****received message: " + message.toJson());
            handler.post(new Runnable() {
                public void run() {
                    OnMessageReceived();
                }
            });
        }

        @Override
        public void onError(Exception ex) {
            Log.d(LOG_TAG, "an error occured:" + ex);
        }
    }
}