Java Question Help with threading and timeout in an existing B4A library

Kevin

Well-Known Member
Licensed User
Longtime User
I figured this might get a bit more exposure in this forum section:

In my post here, I am trying to make a change to an existing library that someone was kind enough to make a while back. While the library works (according to my customers), the issue I have is that the library was based off of freely available code, and that code (and thus the library) causes a NetworkOnMainThread exception when the API target is 14(?) or higher. Until now I have just targeted a lower API in my manifest but I have other issues that can only really be fixed by targeting the higher API level, which then breaks the library. :eek:

The post in the link explains what I was trying to do. In short, I need that library to run on its own thread and I also can't seem to get a timeout working properly. As it is now, it basically locks up my app for a minute if it can't reach the IP address.

Thanks! :)
 

Kevin

Well-Known Member
Licensed User
Longtime User
Below are the two main java files. I'm not exactly sure where or how I would use BA.SubmitRunnable. I think normally the "Runnable" option would be used in the original java class, but this is a wrapper so the original java class doesn't reference the BA object. So could I use the BA.SubmitRunnable thing in the BA Wrapper class? And I'm still not sure how to implement a timeout.

The older I get, the sharper I'm not. o_O

B4X:
// Original wrapper class:
package b4a.tillekesoft.samsungtv;

import java.io.IOException;
import java.net.UnknownHostException;
import java.util.concurrent.TimeoutException;

import android.util.Log;
import anywheresoftware.b4a.AbsObjectWrapper;
import anywheresoftware.b4a.BA.Author;
import anywheresoftware.b4a.BA.Version;
import anywheresoftware.b4a.BA;



@Author("moster67")
@BA.ShortName("RemoteSession")
@Version(1.4f)


public class b4aRemoteSession extends AbsObjectWrapper<RemoteSession> {

    private String applicationName;
    private String uniqueId;
    private String host;
    private int port;
    private String myword;
    private String b4amyword;
 
    private RemoteSession session;
 
    private static final String TAG = "TAG_B4AWrapper";
 
 
    /**
    * Initialize the RemoteSession object
    * @throws TimeoutException
    * @throws ConnectionDeniedException
    * @throws IOException
    */
    public void Initialize(String b4aapplicationName, String b4auniqueId, String b4ahost, int b4aport) throws IOException, ConnectionDeniedException, TimeoutException{
          this.applicationName = b4aapplicationName;
        this.uniqueId = b4auniqueId;
        if (uniqueId == null) {
            uniqueId = "";
        }
        this.host = b4ahost;
        this.port = b4aport;;
        this.b4amyword = "anything";
        this.myword = "anything";
     
        Log.v(TAG,"Initvalues are " + b4aapplicationName + "-" + b4auniqueId + "-" + b4ahost + "-" + b4aport);
     
        setObject(new RemoteSession(b4aapplicationName, b4auniqueId,b4ahost,b4aport,b4amyword));
     
   
    }

    public void Connect() throws IOException, ConnectionDeniedException, TimeoutException{
        Log.v(TAG,"Connecting");
        RemoteSession.create(applicationName, uniqueId, host, port, myword);
     
    }
 
     
    public void sendCode(String mykey) throws UnknownHostException, IOException, InterruptedException, ConnectionDeniedException, TimeoutException {
        Log.v(TAG,"Before sendcode values are " + applicationName + "-" + uniqueId + "-" + host + "-" + port);
        Log.v(TAG,"Session value is " + session);
        if ( session == null) Initialize(applicationName, uniqueId, host, port);
         
            if (mykey != null) {
                Log.v(TAG,"Launching sendkey");
                getObject().sendKey(mykey);
             
            }
     
    }
 
 
    public void uninitialize() {
        Log.v(TAG, "Uninitializing C-Series Sender...");
        if (session != null) {
            getObject().destroy();
            Log.v(TAG, "...Uninitialized C-Series Sender");
        } else {
            Log.v(TAG, "...Nothing to uninitialize, session is null");
        }
    }

}


------------------------------------------------------------------------------------------------------------
// Original Java class:
/*
*  Copyright (C) 2011  Tom Quist
*
*  This program is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation; either version 2 of the License, or
*  (at your option) any later version.
*
*  This program 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 for more details.
*
*  You can get the GNU General Public License at
*  http://www.gnu.org/licenses/gpl.html
*/
package b4a.tillekesoft.samsungtv;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.concurrent.TimeoutException;

import android.util.Log;


public class RemoteSession {

    private static final String TAG = "TAG_B4A";
    public static final String REPORT_TAG = "report";
 
    private static final String APP_STRING = "iphone.iapp.samsung";
    private static final String TV_APP_STRING = "iphone..iapp.samsung";
 
    private static final char[] ALLOWED_BYTES = new char[] {0x64, 0x00, 0x01, 0x00};
    private static final char[] DENIED_BYTES = new char[] {0x64, 0x00, 0x00, 0x00};
    private static final char[] TIMEOUT_BYTES = new char[] {0x65, 0x00};

    public static final String ALLOWED = "ALLOWED";
    public static final String DENIED = "DENIED";
    public static final String TIMEOUT = "TIMEOUT";
    //private static final String TAG = RemoteSession.class.getSimpleName();
 
    private String applicationName;
    private String uniqueId;
    private String host;
    private int port;
    private String myword;

    private Socket socket;
    private String mykey;

    private InputStreamReader reader;

    private BufferedWriter writer;
    //private Loggable logger;

    RemoteSession(String applicationName, String uniqueId, String host, int port,String myword) {
        this.applicationName = applicationName;
        this.uniqueId = uniqueId;
        if (uniqueId == null) {
            uniqueId = "";
        }
        this.host = host;
        this.port = port;
        this.myword = myword;
        //this.logger = logger;
    }
 
    public static RemoteSession create(String applicationName, String uniqueId, String host, int port, String myword) throws IOException, ConnectionDeniedException, TimeoutException {
        Log.v(TAG,"createsub 1st part");
        RemoteSession session = new RemoteSession(applicationName, uniqueId, host, port, myword);
        Log.v(TAG,"createsub 2nd part");
        String result = session.initialize();
        Log.v(TAG,"getting result from initialize");
        if (result.equals(ALLOWED)) {
            Log.v(TAG,"result from initialize was ALLOWED");
            return session;
        } else if (result.equals(DENIED)) {
            Log.v(TAG,"result from initialize was DENIED");
            throw new ConnectionDeniedException();
        } else if (result.equals(TIMEOUT)) {
            Log.v(TAG,"result from initialize was TIMEOUT");
            throw new TimeoutException();
        } else {
            Log.v(TAG,"result from initialize was ELSE-probably connected");
            return session; // TODO For now we just assume to be connected
            //throw new UnknownError("TV returned " + result);
        }
    }
 
    public static RemoteSession create(String applicationName, String uniqueId, String host, int port) throws IOException, ConnectionDeniedException, TimeoutException {
        return create(applicationName, uniqueId, host, port, null);
    }

    private String initialize() throws UnknownHostException, IOException {
        socket = new Socket(host, port);
        InetAddress localAddress = socket.getLocalAddress();
        Log.v(TAG,"init-locAddress is " + localAddress);
        writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        writer.append((char)0x00);
        writeText(writer, APP_STRING);
        writeText(writer, getRegistrationPayload(localAddress.getHostAddress()));
        writer.flush();
     
        InputStream in = socket.getInputStream();
        reader = new InputStreamReader(in);
        String result = readRegistrationReply(reader);
        //sendPart2();
        int i;
        while ((i = in.available()) > 0) {
            in.skip(i);
        }
        return result;
    }
 
    private void sendPart2() throws IOException {
        writer.append((char)0x00);
        writeText(writer, TV_APP_STRING);
        writeText(writer, new String(new char[] {0xc8, 0x00}));
    }
 
    private void checkConnection() throws UnknownHostException, IOException {
        Log.v(TAG, "verifying CheckConnetion routine");
        //Log.v(TAG,"Socket status is " + socket.isClosed() + " other " + socket.getLocalAddress());
             
        if (socket==null ) {
            Log.v(TAG, "socket value is null");
            initialize();
        }
     
        if (socket.isClosed() || !socket.isConnected()) {
            Log.v(TAG, "Connection closed, trying to reconnect...");
            try {
                socket.close();
            } catch (UnknownHostException u) {
                Log.v(TAG,"connection error " + u);
         
            } catch (IOException e) {
                Log.v(TAG,"connection exception");
                // Ignore any exception
            }
            Log.v(TAG,"we are in CheckConnetion routine. just before initialize. ");
            initialize();
         
        }
    }
 
    public void destroy() {
        try {
            socket.close();
        } catch (IOException e) {
            // Ignore exception
        }
    }
 
    private String readRegistrationReply(Reader reader) throws IOException {
        reader.read(); // Unknown byte 0x02
        String text1 = readText(reader); // Read "unknown.livingroom.iapp.samsung" for new RC and "iapp.samsung" for already registered RC
        char[] result = readCharArray(reader); // Read result sequence
        if (Arrays.equals(result, ALLOWED_BYTES)) {
            Log.v(TAG, "allowed");
            return ALLOWED;
        } else if (Arrays.equals(result, DENIED_BYTES)) {
            Log.v(TAG, "denied");
            return DENIED;
        } else if (Arrays.equals(result, TIMEOUT_BYTES)) {
            Log.v(TAG, "timeout");
            return TIMEOUT;
        } else {
            StringBuilder sb = new StringBuilder();
            for (char c : result) {
                sb.append(Integer.toHexString(c));
                sb.append(' ');
            }
            String hexReturn = sb.toString();
            Log.v(TAG, "returning hexreturn");
            return hexReturn;
        }
    }
 
    private String getRegistrationPayload(String ip) throws IOException {
        StringWriter writer = new StringWriter();
        writer.append((char)0x64);
        writer.append((char) 0x00);
        writeBase64Text(writer, ip);
        writeBase64Text(writer, uniqueId);
        writeBase64Text(writer, applicationName);
        writer.flush();
        return writer.toString();
    }
 
    private static String readText(Reader reader) throws IOException {
        char[] buffer = readCharArray(reader);
        return new String(buffer);
    }
 
    private static char[] readCharArray(Reader reader) throws IOException {
        if (reader.markSupported()) reader.mark(1024);
        int length = reader.read();
        int delimiter = reader.read();
        if (delimiter != 0) {
            if (reader.markSupported()) reader.reset();
            throw new IOException("Unsupported reply exception");
        }
        char[] buffer = new char[length];
        reader.read(buffer);
        return buffer;
    }
 
    private static Writer writeText(Writer writer, String text) throws IOException {
        return writer.append((char)text.length()).append((char) 0x00).append(text);
    }
 
    private static Writer writeBase64Text(Writer writer, String text) throws IOException {
        String b64 = Base64.encodeBytes(text.getBytes());
        return writeText(writer, b64);
    }
 
    private void internalSendKey(String mykey) throws IOException {
        Log.v(TAG,"mykey is " + mykey);
        writer.append((char)0x00);
        writeText(writer, TV_APP_STRING);
        writeText(writer, getKeyPayload(mykey));
        writer.flush();
        int i = reader.read(); // Unknown byte 0x00
        String t = readText(reader);  // Read "iapp.samsung"
        char[] c = readCharArray(reader);
        System.out.println(i);
        System.out.println(t);
        for (char a : c) System.out.println(Integer.toHexString(a));
     
    }
 
    public void sendKey(String mykey) throws IOException {
        Log.v(TAG,"now we try checkConnetion routine");
        checkConnection();
        Log.v(TAG,"we got back from check connection");
        try {
            Log.v(TAG,"sending key internalsendkey");
            internalSendKey(mykey);
        } catch (SocketException e) {
            Log.v(TAG,"there was a socket-error. We are re-initialising");
            initialize();
            internalSendKey(mykey);
        }
     
    }

    private String getKeyPayload(String mykey) throws IOException {
        StringWriter writer = new StringWriter();
        writer.append((char)0x00);
        writer.append((char)0x00);
        writer.append((char)0x00);
        writeBase64Text(writer, mykey);
        writer.flush();
        return writer.toString();
    }
 
    private void internalSendText(String text) throws IOException {
        writer.append((char)0x01);
        writeText(writer, TV_APP_STRING);
        writeText(writer, getTextPayload(text));
        writer.flush();
        if (!reader.ready()) {
            return;
        }
        int i = reader.read(); // Unknown byte 0x02
        System.out.println(i);
        String t = readText(reader); // Read "iapp.samsung"
        char[] c = readCharArray(reader);
        System.out.println(i);
        System.out.println(t);
        for (char a : c) System.out.println(Integer.toHexString(a));
    }
 
    public void sendText(String text) throws IOException {
        checkConnection();
        try {
            internalSendText(text);
        } catch (SocketException e) {
            initialize();
            internalSendText(text);
        }
     
    }

    private String getTextPayload(String text) throws IOException {
        StringWriter writer = new StringWriter();
        writer.append((char)0x01);
        writer.append((char)0x00);
        writeBase64Text(writer, text);
        writer.flush();
        return writer.toString();
    }

}
 

Kevin

Well-Known Member
Licensed User
Longtime User
Any thoughts from the Java gurus on this? Being that it uses the object wrapper I'm not sure whether I should use BA.submitRunnable in the wrapper class or the normal java "Runnable" code in the original class.
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
Note that you shouldn't extend AbsObjectWrapper. As a thumb rule, if your class has one or more instance fields then it shouldn't be a wrapper.

Which code do you want to run in the background? The connect code?

You can do something like:
B4X:
public void Connect(BA ba) throws IOException, ConnectionDeniedException, TimeoutException{
     ba.submitRunnable(new Runnable() {

       @Override
       public void run() {
         RemoteSession.create(applicationName, uniqueId, host, port, myword);
         ba.raiseEventFromDifferentThread(null, null, 0, eventName + "_event", false, null);
       }
     
     }, null, 0);
   
  }
 
Top