create testable LocalHTTPDManager for controlling the webserver
The RxJava tricks were a nightmare...
This commit is contained in:
		
							parent
							
								
									5b610798c2
								
							
						
					
					
						commit
						79e7e78e7f
					
				
							
								
								
									
										372
									
								
								app/src/androidTest/java/org/fdroid/fdroid/Netstat.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										372
									
								
								app/src/androidTest/java/org/fdroid/fdroid/Netstat.java
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,372 @@
 | 
			
		||||
package org.fdroid.fdroid;
 | 
			
		||||
 | 
			
		||||
import java.io.BufferedReader;
 | 
			
		||||
import java.io.FileReader;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.regex.Matcher;
 | 
			
		||||
import java.util.regex.Pattern;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Replacer for the netstat utility, by reading the /proc filesystem it can find out the
 | 
			
		||||
 * open connections of the system
 | 
			
		||||
 * From http://www.ussg.iu.edu/hypermail/linux/kernel/0409.1/2166.html :
 | 
			
		||||
 * It will first list all listening TCP sockets, and next list all established
 | 
			
		||||
 * TCP connections. A typical entry of /proc/net/tcp would look like this (split
 | 
			
		||||
 * up into 3 parts because of the length of the line):
 | 
			
		||||
 * <p>
 | 
			
		||||
 * 46: 010310AC:9C4C 030310AC:1770 01
 | 
			
		||||
 * | | | | | |--> connection state
 | 
			
		||||
 * | | | | |------> remote TCP port number
 | 
			
		||||
 * | | | |-------------> remote IPv4 address
 | 
			
		||||
 * | | |--------------------> local TCP port number
 | 
			
		||||
 * | |---------------------------> local IPv4 address
 | 
			
		||||
 * |----------------------------------> number of entry
 | 
			
		||||
 * <p>
 | 
			
		||||
 * 00000150:00000000 01:00000019 00000000
 | 
			
		||||
 * | | | | |--> number of unrecovered RTO timeouts
 | 
			
		||||
 * | | | |----------> number of jiffies until timer expires
 | 
			
		||||
 * | | |----------------> timer_active (see below)
 | 
			
		||||
 * | |----------------------> receive-queue
 | 
			
		||||
 * |-------------------------------> transmit-queue
 | 
			
		||||
 * <p>
 | 
			
		||||
 * 1000 0 54165785 4 cd1e6040 25 4 27 3 -1
 | 
			
		||||
 * | | | | | | | | | |--> slow start size threshold,
 | 
			
		||||
 * | | | | | | | | | or -1 if the treshold
 | 
			
		||||
 * | | | | | | | | | is >= 0xFFFF
 | 
			
		||||
 * | | | | | | | | |----> sending congestion window
 | 
			
		||||
 * | | | | | | | |-------> (ack.quick<<1)|ack.pingpong
 | 
			
		||||
 * | | | | | | |---------> Predicted tick of soft clock
 | 
			
		||||
 * | | | | | | (delayed ACK control data)
 | 
			
		||||
 * | | | | | |------------> retransmit timeout
 | 
			
		||||
 * | | | | |------------------> location of socket in memory
 | 
			
		||||
 * | | | |-----------------------> socket reference count
 | 
			
		||||
 * | | |-----------------------------> inode
 | 
			
		||||
 * | |----------------------------------> unanswered 0-window probes
 | 
			
		||||
 * |---------------------------------------------> uid
 | 
			
		||||
 *
 | 
			
		||||
 * @author Ciprian Dobre
 | 
			
		||||
 */
 | 
			
		||||
public class Netstat {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Possible values for states in /proc/net/tcp
 | 
			
		||||
     */
 | 
			
		||||
    private static final String[] STATES = {
 | 
			
		||||
            "ESTBLSH", "SYNSENT", "SYNRECV", "FWAIT1", "FWAIT2", "TMEWAIT",
 | 
			
		||||
            "CLOSED", "CLSWAIT", "LASTACK", "LISTEN", "CLOSING", "UNKNOWN",
 | 
			
		||||
    };
 | 
			
		||||
    /**
 | 
			
		||||
     * Pattern used when parsing through /proc/net/tcp
 | 
			
		||||
     */
 | 
			
		||||
    private static final Pattern NET_PATTERN = Pattern.compile(
 | 
			
		||||
            "\\d+:\\s+([\\dA-F]+):([\\dA-F]+)\\s+([\\dA-F]+):([\\dA-F]+)\\s+([\\dA-F]+)\\s+" +
 | 
			
		||||
                    "[\\dA-F]+:[\\dA-F]+\\s+[\\dA-F]+:[\\dA-F]+\\s+[\\dA-F]+\\s+([\\d]+)\\s+[\\d]+\\s+([\\d]+)");
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Utility method that converts an address from a hex representation as founded in /proc to String representation
 | 
			
		||||
     */
 | 
			
		||||
    private static String getAddress(final String hexa) {
 | 
			
		||||
        try {
 | 
			
		||||
            // first let's convert the address to Integer
 | 
			
		||||
            final long v = Long.parseLong(hexa, 16);
 | 
			
		||||
            // in /proc the order is little endian and java uses big endian order we also need to invert the order
 | 
			
		||||
            final long adr = (v >>> 24) | (v << 24) |
 | 
			
		||||
                    ((v << 8) & 0x00FF0000) | ((v >> 8) & 0x0000FF00);
 | 
			
		||||
            // and now it's time to output the result
 | 
			
		||||
            return ((adr >> 24) & 0xff) + "." + ((adr >> 16) & 0xff) + "." + ((adr >> 8) & 0xff) + "." + (adr & 0xff);
 | 
			
		||||
        } catch (Exception ex) {
 | 
			
		||||
            ex.printStackTrace();
 | 
			
		||||
            return "0.0.0.0";  // NOPMD
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static int getInt16(final String hexa) {
 | 
			
		||||
        try {
 | 
			
		||||
            return Integer.parseInt(hexa, 16);
 | 
			
		||||
        } catch (Exception ex) {
 | 
			
		||||
            ex.printStackTrace();
 | 
			
		||||
            return -1;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    private static String getPName(final int pid) {
 | 
			
		||||
        final Pattern pattern = Pattern.compile("Name:\\s*(\\S+)");
 | 
			
		||||
        try {
 | 
			
		||||
            BufferedReader in = new BufferedReader(new FileReader("/proc/" + pid + "/status"));
 | 
			
		||||
            String line;
 | 
			
		||||
            while ((line = in.readLine()) != null) {
 | 
			
		||||
                final Matcher matcher = pattern.matcher(line);
 | 
			
		||||
                if (matcher.find()) {
 | 
			
		||||
                    return matcher.group(1);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            in.close();
 | 
			
		||||
        } catch (Throwable t) {
 | 
			
		||||
            // ignored
 | 
			
		||||
        }
 | 
			
		||||
        return "UNKNOWN";
 | 
			
		||||
    }
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method used to question for the connections currently openned
 | 
			
		||||
     *
 | 
			
		||||
     * @return The list of connections (as Connection objects)
 | 
			
		||||
     */
 | 
			
		||||
    public static List<Connection> getConnections() {
 | 
			
		||||
 | 
			
		||||
        final ArrayList<Connection> net = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
        // read from /proc/net/tcp the list of currently openned socket connections
 | 
			
		||||
        try {
 | 
			
		||||
            BufferedReader in = new BufferedReader(new FileReader("/proc/net/tcp"));
 | 
			
		||||
            String line;
 | 
			
		||||
            while ((line = in.readLine()) != null) {
 | 
			
		||||
                Matcher matcher = NET_PATTERN.matcher(line);
 | 
			
		||||
                if (matcher.find()) {
 | 
			
		||||
                    final Connection c = new Connection();
 | 
			
		||||
                    c.setProtocol(Connection.TCP_CONNECTION);
 | 
			
		||||
                    net.add(c);
 | 
			
		||||
                    final String localPortHexa = matcher.group(2);
 | 
			
		||||
                    final String remoteAddressHexa = matcher.group(3);
 | 
			
		||||
                    final String remotePortHexa = matcher.group(4);
 | 
			
		||||
                    final String statusHexa = matcher.group(5);
 | 
			
		||||
                    //final String uid = matcher.group(6);
 | 
			
		||||
                    //final String inode = matcher.group(7);
 | 
			
		||||
                    c.setLocalPort(getInt16(localPortHexa));
 | 
			
		||||
                    c.setRemoteAddress(getAddress(remoteAddressHexa));
 | 
			
		||||
                    c.setRemotePort(getInt16(remotePortHexa));
 | 
			
		||||
                    try {
 | 
			
		||||
                        c.setStatus(STATES[Integer.parseInt(statusHexa, 16) - 1]);
 | 
			
		||||
                    } catch (Exception ex) {
 | 
			
		||||
                        c.setStatus(STATES[11]); // unknwon
 | 
			
		||||
                    }
 | 
			
		||||
                    c.setPID(-1); // unknown
 | 
			
		||||
                    c.setPName("UNKNOWN");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            in.close();
 | 
			
		||||
        } catch (Throwable t) { // NOPMD
 | 
			
		||||
            // ignored
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // read from /proc/net/udp the list of currently openned socket connections
 | 
			
		||||
        try {
 | 
			
		||||
            BufferedReader in = new BufferedReader(new FileReader("/proc/net/udp"));
 | 
			
		||||
            String line;
 | 
			
		||||
            while ((line = in.readLine()) != null) {
 | 
			
		||||
                Matcher matcher = NET_PATTERN.matcher(line);
 | 
			
		||||
                if (matcher.find()) {
 | 
			
		||||
                    final Connection c = new Connection();
 | 
			
		||||
                    c.setProtocol(Connection.UDP_CONNECTION);
 | 
			
		||||
                    net.add(c);
 | 
			
		||||
                    final String localPortHexa = matcher.group(2);
 | 
			
		||||
                    final String remoteAddressHexa = matcher.group(3);
 | 
			
		||||
                    final String remotePortHexa = matcher.group(4);
 | 
			
		||||
                    final String statusHexa = matcher.group(5);
 | 
			
		||||
                    //final String uid = matcher.group(6);
 | 
			
		||||
                    //final String inode = matcher.group(7);
 | 
			
		||||
                    c.setLocalPort(getInt16(localPortHexa));
 | 
			
		||||
                    c.setRemoteAddress(getAddress(remoteAddressHexa));
 | 
			
		||||
                    c.setRemotePort(getInt16(remotePortHexa));
 | 
			
		||||
                    try {
 | 
			
		||||
                        c.setStatus(STATES[Integer.parseInt(statusHexa, 16) - 1]);
 | 
			
		||||
                    } catch (Exception ex) {
 | 
			
		||||
                        c.setStatus(STATES[11]); // unknwon
 | 
			
		||||
                    }
 | 
			
		||||
                    c.setPID(-1); // unknown
 | 
			
		||||
                    c.setPName("UNKNOWN");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            in.close();
 | 
			
		||||
        } catch (Throwable t) { // NOPMD
 | 
			
		||||
            // ignored
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // read from /proc/net/raw the list of currently openned socket connections
 | 
			
		||||
        try {
 | 
			
		||||
            BufferedReader in = new BufferedReader(new FileReader("/proc/net/raw"));
 | 
			
		||||
            String line;
 | 
			
		||||
            while ((line = in.readLine()) != null) {
 | 
			
		||||
                Matcher matcher = NET_PATTERN.matcher(line);
 | 
			
		||||
                if (matcher.find()) {
 | 
			
		||||
                    final Connection c = new Connection();
 | 
			
		||||
                    c.setProtocol(Connection.RAW_CONNECTION);
 | 
			
		||||
                    net.add(c);
 | 
			
		||||
                    //final String localAddressHexa = matcher.group(1);
 | 
			
		||||
                    final String localPortHexa = matcher.group(2);
 | 
			
		||||
                    final String remoteAddressHexa = matcher.group(3);
 | 
			
		||||
                    final String remotePortHexa = matcher.group(4);
 | 
			
		||||
                    final String statusHexa = matcher.group(5);
 | 
			
		||||
                    //final String uid = matcher.group(6);
 | 
			
		||||
                    //final String inode = matcher.group(7);
 | 
			
		||||
                    c.setLocalPort(getInt16(localPortHexa));
 | 
			
		||||
                    c.setRemoteAddress(getAddress(remoteAddressHexa));
 | 
			
		||||
                    c.setRemotePort(getInt16(remotePortHexa));
 | 
			
		||||
                    try {
 | 
			
		||||
                        c.setStatus(STATES[Integer.parseInt(statusHexa, 16) - 1]);
 | 
			
		||||
                    } catch (Exception ex) {
 | 
			
		||||
                        c.setStatus(STATES[11]); // unknwon
 | 
			
		||||
                    }
 | 
			
		||||
                    c.setPID(-1); // unknown
 | 
			
		||||
                    c.setPName("UNKNOWN");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            in.close();
 | 
			
		||||
        } catch (Throwable t) { // NOPMD
 | 
			
		||||
            // ignored
 | 
			
		||||
        }
 | 
			
		||||
        return net;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Informations about a given connection
 | 
			
		||||
     *
 | 
			
		||||
     * @author Ciprian Dobre
 | 
			
		||||
     */
 | 
			
		||||
    public static class Connection {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Types of connection protocol
 | 
			
		||||
         ***/
 | 
			
		||||
        public static final byte TCP_CONNECTION = 0;
 | 
			
		||||
        public static final byte UDP_CONNECTION = 1;
 | 
			
		||||
        public static final byte RAW_CONNECTION = 2;
 | 
			
		||||
        /**
 | 
			
		||||
         * <code>serialVersionUID</code>
 | 
			
		||||
         */
 | 
			
		||||
        private static final long serialVersionUID = 1988671591829311032L;
 | 
			
		||||
        /**
 | 
			
		||||
         * The protocol of the connection (can be tcp, udp or raw)
 | 
			
		||||
         */
 | 
			
		||||
        protected byte protocol;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The owner of the connection (username)
 | 
			
		||||
         */
 | 
			
		||||
        protected String powner;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The pid of the owner process
 | 
			
		||||
         */
 | 
			
		||||
        protected int pid;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The name of the program owning the connection
 | 
			
		||||
         */
 | 
			
		||||
        protected String pname;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Local port
 | 
			
		||||
         */
 | 
			
		||||
        protected int localPort;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Remote address of the connection
 | 
			
		||||
         */
 | 
			
		||||
        protected String remoteAddress;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Remote port
 | 
			
		||||
         */
 | 
			
		||||
        protected int remotePort;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Status of the connection
 | 
			
		||||
         */
 | 
			
		||||
        protected String status;
 | 
			
		||||
 | 
			
		||||
        public final byte getProtocol() {
 | 
			
		||||
            return protocol;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public final void setProtocol(final byte protocol) {
 | 
			
		||||
            this.protocol = protocol;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public final String getProtocolAsString() {
 | 
			
		||||
            switch (protocol) {
 | 
			
		||||
                case TCP_CONNECTION:
 | 
			
		||||
                    return "TCP";
 | 
			
		||||
                case UDP_CONNECTION:
 | 
			
		||||
                    return "UDP";
 | 
			
		||||
                case RAW_CONNECTION:
 | 
			
		||||
                    return "RAW";
 | 
			
		||||
            }
 | 
			
		||||
            return "UNKNOWN";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public final String getPOwner() {
 | 
			
		||||
            return powner;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public final void setPOwner(final String owner) {
 | 
			
		||||
            this.powner = owner;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public final int getPID() {
 | 
			
		||||
            return pid;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public final void setPID(final int pid) {
 | 
			
		||||
            this.pid = pid;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public final String getPName() {
 | 
			
		||||
            return pname;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public final void setPName(final String pname) {
 | 
			
		||||
            this.pname = pname;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public final int getLocalPort() {
 | 
			
		||||
            return localPort;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public final void setLocalPort(final int localPort) {
 | 
			
		||||
            this.localPort = localPort;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public final String getRemoteAddress() {
 | 
			
		||||
            return remoteAddress;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public final void setRemoteAddress(final String remoteAddress) {
 | 
			
		||||
            this.remoteAddress = remoteAddress;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public final int getRemotePort() {
 | 
			
		||||
            return remotePort;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public final void setRemotePort(final int remotePort) {
 | 
			
		||||
            this.remotePort = remotePort;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public final String getStatus() {
 | 
			
		||||
            return status;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public final void setStatus(final String status) {
 | 
			
		||||
            this.status = status;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public String toString() {
 | 
			
		||||
            StringBuffer buf = new StringBuffer();
 | 
			
		||||
            buf.append("[Prot=").append(getProtocolAsString());
 | 
			
		||||
            buf.append(",POwner=").append(powner);
 | 
			
		||||
            buf.append(",PID=").append(pid);
 | 
			
		||||
            buf.append(",PName=").append(pname);
 | 
			
		||||
            buf.append(",LPort=").append(localPort);
 | 
			
		||||
            buf.append(",RAddress=").append(remoteAddress);
 | 
			
		||||
            buf.append(",RPort=").append(remotePort);
 | 
			
		||||
            buf.append(",Status=").append(status);
 | 
			
		||||
            buf.append("]");
 | 
			
		||||
            return buf.toString();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,187 @@
 | 
			
		||||
package org.fdroid.fdroid.localrepo;
 | 
			
		||||
 | 
			
		||||
import android.content.BroadcastReceiver;
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.content.Intent;
 | 
			
		||||
import android.content.IntentFilter;
 | 
			
		||||
import android.support.test.InstrumentationRegistry;
 | 
			
		||||
import android.support.test.runner.AndroidJUnit4;
 | 
			
		||||
import android.support.v4.content.LocalBroadcastManager;
 | 
			
		||||
import android.util.Log;
 | 
			
		||||
import org.fdroid.fdroid.FDroidApp;
 | 
			
		||||
import org.fdroid.fdroid.Netstat;
 | 
			
		||||
import org.fdroid.fdroid.Utils;
 | 
			
		||||
import org.junit.After;
 | 
			
		||||
import org.junit.Before;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.junit.runner.RunWith;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.net.ServerSocket;
 | 
			
		||||
import java.util.concurrent.CountDownLatch;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
 | 
			
		||||
import static org.junit.Assert.assertFalse;
 | 
			
		||||
import static org.junit.Assert.assertNotEquals;
 | 
			
		||||
import static org.junit.Assert.assertTrue;
 | 
			
		||||
import static org.junit.Assert.fail;
 | 
			
		||||
 | 
			
		||||
@RunWith(AndroidJUnit4.class)
 | 
			
		||||
public class LocalHTTPDManagerTest {
 | 
			
		||||
    private static final String TAG = "LocalHTTPDManagerTest";
 | 
			
		||||
 | 
			
		||||
    private Context context;
 | 
			
		||||
    private LocalBroadcastManager lbm;
 | 
			
		||||
 | 
			
		||||
    private static final String LOCALHOST = "localhost";
 | 
			
		||||
    private static final int PORT = 8888;
 | 
			
		||||
 | 
			
		||||
    @Before
 | 
			
		||||
    public void setUp() {
 | 
			
		||||
        context = InstrumentationRegistry.getTargetContext();
 | 
			
		||||
        lbm = LocalBroadcastManager.getInstance(context);
 | 
			
		||||
 | 
			
		||||
        FDroidApp.ipAddressString = LOCALHOST;
 | 
			
		||||
        FDroidApp.port = PORT;
 | 
			
		||||
 | 
			
		||||
        for (Netstat.Connection connection : Netstat.getConnections()) { // NOPMD
 | 
			
		||||
            Log.i("LocalHTTPDManagerTest", "connection: " + connection.toString());
 | 
			
		||||
        }
 | 
			
		||||
        assertFalse(Utils.isServerSocketInUse(PORT));
 | 
			
		||||
        LocalHTTPDManager.stop(context);
 | 
			
		||||
 | 
			
		||||
        for (Netstat.Connection connection : Netstat.getConnections()) { // NOPMD
 | 
			
		||||
            Log.i("LocalHTTPDManagerTest", "connection: " + connection.toString());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @After
 | 
			
		||||
    public void tearDown() {
 | 
			
		||||
        lbm.unregisterReceiver(startedReceiver);
 | 
			
		||||
        lbm.unregisterReceiver(stoppedReceiver);
 | 
			
		||||
        lbm.unregisterReceiver(errorReceiver);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testStartStop() throws InterruptedException {
 | 
			
		||||
        Log.i(TAG, "testStartStop");
 | 
			
		||||
 | 
			
		||||
        final CountDownLatch startLatch = new CountDownLatch(1);
 | 
			
		||||
        BroadcastReceiver latchReceiver = new BroadcastReceiver() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onReceive(Context context, Intent intent) {
 | 
			
		||||
                startLatch.countDown();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        lbm.registerReceiver(latchReceiver, new IntentFilter(LocalHTTPDManager.ACTION_STARTED));
 | 
			
		||||
        lbm.registerReceiver(stoppedReceiver, new IntentFilter(LocalHTTPDManager.ACTION_STOPPED));
 | 
			
		||||
        lbm.registerReceiver(errorReceiver, new IntentFilter(LocalHTTPDManager.ACTION_ERROR));
 | 
			
		||||
        LocalHTTPDManager.start(context, false);
 | 
			
		||||
        assertTrue(startLatch.await(30, TimeUnit.SECONDS));
 | 
			
		||||
        assertTrue(Utils.isServerSocketInUse(PORT));
 | 
			
		||||
        assertTrue(Utils.canConnectToSocket(LOCALHOST, PORT));
 | 
			
		||||
        lbm.unregisterReceiver(latchReceiver);
 | 
			
		||||
        lbm.unregisterReceiver(stoppedReceiver);
 | 
			
		||||
        lbm.unregisterReceiver(errorReceiver);
 | 
			
		||||
 | 
			
		||||
        final CountDownLatch stopLatch = new CountDownLatch(1);
 | 
			
		||||
        latchReceiver = new BroadcastReceiver() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onReceive(Context context, Intent intent) {
 | 
			
		||||
                stopLatch.countDown();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        lbm.registerReceiver(startedReceiver, new IntentFilter(LocalHTTPDManager.ACTION_STARTED));
 | 
			
		||||
        lbm.registerReceiver(latchReceiver, new IntentFilter(LocalHTTPDManager.ACTION_STOPPED));
 | 
			
		||||
        lbm.registerReceiver(errorReceiver, new IntentFilter(LocalHTTPDManager.ACTION_ERROR));
 | 
			
		||||
        LocalHTTPDManager.stop(context);
 | 
			
		||||
        assertTrue(stopLatch.await(30, TimeUnit.SECONDS));
 | 
			
		||||
        assertFalse(Utils.isServerSocketInUse(PORT));
 | 
			
		||||
        assertFalse(Utils.canConnectToSocket(LOCALHOST, PORT)); // if this is flaky, just remove it
 | 
			
		||||
        lbm.unregisterReceiver(latchReceiver);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testError() throws InterruptedException, IOException {
 | 
			
		||||
        Log.i("LocalHTTPDManagerTest", "testError");
 | 
			
		||||
        ServerSocket blockerSocket = new ServerSocket(PORT);
 | 
			
		||||
 | 
			
		||||
        final CountDownLatch latch = new CountDownLatch(1);
 | 
			
		||||
        BroadcastReceiver latchReceiver = new BroadcastReceiver() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onReceive(Context context, Intent intent) {
 | 
			
		||||
                latch.countDown();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        lbm.registerReceiver(startedReceiver, new IntentFilter(LocalHTTPDManager.ACTION_STARTED));
 | 
			
		||||
        lbm.registerReceiver(stoppedReceiver, new IntentFilter(LocalHTTPDManager.ACTION_STOPPED));
 | 
			
		||||
        lbm.registerReceiver(latchReceiver, new IntentFilter(LocalHTTPDManager.ACTION_ERROR));
 | 
			
		||||
        LocalHTTPDManager.start(context, false);
 | 
			
		||||
        assertTrue(latch.await(30, TimeUnit.SECONDS));
 | 
			
		||||
        assertTrue(Utils.isServerSocketInUse(PORT));
 | 
			
		||||
        assertNotEquals(PORT, FDroidApp.port);
 | 
			
		||||
        assertFalse(Utils.isServerSocketInUse(FDroidApp.port));
 | 
			
		||||
        lbm.unregisterReceiver(latchReceiver);
 | 
			
		||||
        blockerSocket.close();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testRestart() throws InterruptedException, IOException {
 | 
			
		||||
        Log.i("LocalHTTPDManagerTest", "testRestart");
 | 
			
		||||
        assertFalse(Utils.isServerSocketInUse(PORT));
 | 
			
		||||
        final CountDownLatch startLatch = new CountDownLatch(1);
 | 
			
		||||
        BroadcastReceiver latchReceiver = new BroadcastReceiver() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onReceive(Context context, Intent intent) {
 | 
			
		||||
                startLatch.countDown();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        lbm.registerReceiver(latchReceiver, new IntentFilter(LocalHTTPDManager.ACTION_STARTED));
 | 
			
		||||
        lbm.registerReceiver(stoppedReceiver, new IntentFilter(LocalHTTPDManager.ACTION_STOPPED));
 | 
			
		||||
        lbm.registerReceiver(errorReceiver, new IntentFilter(LocalHTTPDManager.ACTION_ERROR));
 | 
			
		||||
        LocalHTTPDManager.start(context, false);
 | 
			
		||||
        assertTrue(startLatch.await(30, TimeUnit.SECONDS));
 | 
			
		||||
        assertTrue(Utils.isServerSocketInUse(PORT));
 | 
			
		||||
        lbm.unregisterReceiver(latchReceiver);
 | 
			
		||||
        lbm.unregisterReceiver(stoppedReceiver);
 | 
			
		||||
 | 
			
		||||
        final CountDownLatch restartLatch = new CountDownLatch(1);
 | 
			
		||||
        latchReceiver = new BroadcastReceiver() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onReceive(Context context, Intent intent) {
 | 
			
		||||
                restartLatch.countDown();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        lbm.registerReceiver(latchReceiver, new IntentFilter(LocalHTTPDManager.ACTION_STARTED));
 | 
			
		||||
        LocalHTTPDManager.restart(context, false);
 | 
			
		||||
        assertTrue(restartLatch.await(30, TimeUnit.SECONDS));
 | 
			
		||||
        lbm.unregisterReceiver(latchReceiver);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private final BroadcastReceiver startedReceiver = new BroadcastReceiver() {
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onReceive(Context context, Intent intent) {
 | 
			
		||||
            String message = intent.getStringExtra(Intent.EXTRA_TEXT);
 | 
			
		||||
            Log.i(TAG, "startedReceiver: " + message);
 | 
			
		||||
            fail();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private final BroadcastReceiver stoppedReceiver = new BroadcastReceiver() {
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onReceive(Context context, Intent intent) {
 | 
			
		||||
            String message = intent.getStringExtra(Intent.EXTRA_TEXT);
 | 
			
		||||
            Log.i(TAG, "stoppedReceiver: " + message);
 | 
			
		||||
            fail();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private final BroadcastReceiver errorReceiver = new BroadcastReceiver() {
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onReceive(Context context, Intent intent) {
 | 
			
		||||
            String message = intent.getStringExtra(Intent.EXTRA_TEXT);
 | 
			
		||||
            Log.i(TAG, "errorReceiver: " + message);
 | 
			
		||||
            fail();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
@ -78,6 +78,7 @@
 | 
			
		||||
                android:exported="false"/>
 | 
			
		||||
        <service android:name=".localrepo.SwapService"/>
 | 
			
		||||
 | 
			
		||||
        <service android:name=".localrepo.LocalHTTPDManager"/>
 | 
			
		||||
        <service
 | 
			
		||||
                android:name=".localrepo.LocalRepoService"
 | 
			
		||||
                android:exported="false"/>
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,129 @@
 | 
			
		||||
package org.fdroid.fdroid.localrepo;
 | 
			
		||||
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.content.Intent;
 | 
			
		||||
import android.os.Handler;
 | 
			
		||||
import android.os.HandlerThread;
 | 
			
		||||
import android.os.Message;
 | 
			
		||||
import android.os.Process;
 | 
			
		||||
import android.support.v4.content.LocalBroadcastManager;
 | 
			
		||||
import android.util.Log;
 | 
			
		||||
import org.fdroid.fdroid.FDroidApp;
 | 
			
		||||
import org.fdroid.fdroid.Preferences;
 | 
			
		||||
import org.fdroid.fdroid.net.LocalHTTPD;
 | 
			
		||||
import org.fdroid.fdroid.net.WifiStateChangeService;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.net.BindException;
 | 
			
		||||
import java.util.Random;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Manage {@link LocalHTTPD} in a {@link HandlerThread};
 | 
			
		||||
 */
 | 
			
		||||
public class LocalHTTPDManager {
 | 
			
		||||
    private static final String TAG = "LocalHTTPDManager";
 | 
			
		||||
 | 
			
		||||
    public static final String ACTION_STARTED = "LocalHTTPDStarted";
 | 
			
		||||
    public static final String ACTION_STOPPED = "LocalHTTPDStopped";
 | 
			
		||||
    public static final String ACTION_ERROR = "LocalHTTPDError";
 | 
			
		||||
 | 
			
		||||
    private static final int STOP = 5709;
 | 
			
		||||
 | 
			
		||||
    private static Handler handler;
 | 
			
		||||
    private static volatile HandlerThread handlerThread;
 | 
			
		||||
    private static LocalHTTPD localHttpd;
 | 
			
		||||
 | 
			
		||||
    public static void start(Context context) {
 | 
			
		||||
        start(context, Preferences.get().isLocalRepoHttpsEnabled());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Testable version, not for regular use.
 | 
			
		||||
     *
 | 
			
		||||
     * @see #start(Context)
 | 
			
		||||
     */
 | 
			
		||||
    static void start(final Context context, final boolean useHttps) {
 | 
			
		||||
        if (handlerThread != null && handlerThread.isAlive()) {
 | 
			
		||||
            Log.w(TAG, "handlerThread is already running, doing nothing!");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        handlerThread = new HandlerThread("LocalHTTPD", Process.THREAD_PRIORITY_LESS_FAVORABLE) {
 | 
			
		||||
            @Override
 | 
			
		||||
            protected void onLooperPrepared() {
 | 
			
		||||
                localHttpd = new LocalHTTPD(
 | 
			
		||||
                        context,
 | 
			
		||||
                        FDroidApp.ipAddressString,
 | 
			
		||||
                        FDroidApp.port,
 | 
			
		||||
                        context.getFilesDir(),
 | 
			
		||||
                        useHttps);
 | 
			
		||||
                try {
 | 
			
		||||
                    localHttpd.start();
 | 
			
		||||
                    Intent intent = new Intent(ACTION_STARTED);
 | 
			
		||||
                    LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
 | 
			
		||||
                } catch (BindException e) {
 | 
			
		||||
                    int prev = FDroidApp.port;
 | 
			
		||||
                    FDroidApp.port = FDroidApp.port + new Random().nextInt(1111);
 | 
			
		||||
                    WifiStateChangeService.start(context, null);
 | 
			
		||||
                    Intent intent = new Intent(ACTION_ERROR);
 | 
			
		||||
                    intent.putExtra(Intent.EXTRA_TEXT,
 | 
			
		||||
                            "port " + prev + " occupied, trying on " + FDroidApp.port + ": ("
 | 
			
		||||
                                    + e.getLocalizedMessage() + ")");
 | 
			
		||||
                    LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
 | 
			
		||||
                } catch (IOException e) {
 | 
			
		||||
                    e.printStackTrace();
 | 
			
		||||
                    Intent intent = new Intent(ACTION_ERROR);
 | 
			
		||||
                    intent.putExtra(Intent.EXTRA_TEXT, e.getLocalizedMessage());
 | 
			
		||||
                    LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        handlerThread.start();
 | 
			
		||||
        handler = new Handler(handlerThread.getLooper()) {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void handleMessage(Message msg) {
 | 
			
		||||
                localHttpd.stop();
 | 
			
		||||
                handlerThread.quit();
 | 
			
		||||
                handlerThread = null;
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void stop(Context context) {
 | 
			
		||||
        if (handler == null || handlerThread == null || !handlerThread.isAlive()) {
 | 
			
		||||
            Log.w(TAG, "handlerThread is already stopped, doing nothing!");
 | 
			
		||||
            handlerThread = null;
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        handler.sendEmptyMessage(STOP);
 | 
			
		||||
        Intent stoppedIntent = new Intent(ACTION_STOPPED);
 | 
			
		||||
        LocalBroadcastManager.getInstance(context).sendBroadcast(stoppedIntent);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Run {@link #stop(Context)}, wait for it to actually stop, then run
 | 
			
		||||
     * {@link #start(Context)}.
 | 
			
		||||
     */
 | 
			
		||||
    public static void restart(Context context) {
 | 
			
		||||
        restart(context, Preferences.get().isLocalRepoHttpsEnabled());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Testable version, not for regular use.
 | 
			
		||||
     *
 | 
			
		||||
     * @see #restart(Context)
 | 
			
		||||
     */
 | 
			
		||||
    static void restart(Context context, boolean useHttps) {
 | 
			
		||||
        stop(context);
 | 
			
		||||
        try {
 | 
			
		||||
            handlerThread.join(10000);
 | 
			
		||||
        } catch (InterruptedException | NullPointerException e) {
 | 
			
		||||
            // ignored
 | 
			
		||||
        }
 | 
			
		||||
        start(context, useHttps);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static boolean isAlive() {
 | 
			
		||||
        return handlerThread != null && handlerThread.isAlive();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -133,23 +133,17 @@ public class SwapService extends Service {
 | 
			
		||||
        if (getPeer() == null) {
 | 
			
		||||
            throw new IllegalStateException("Cannot connect to peer, no peer has been selected.");
 | 
			
		||||
        }
 | 
			
		||||
        connectTo(getPeer(), getPeer().shouldPromptForSwapBack());
 | 
			
		||||
        connectTo(getPeer());
 | 
			
		||||
        if (isEnabled() && getPeer().shouldPromptForSwapBack()) {
 | 
			
		||||
            askServerToSwapWithUs(peerRepo);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void connectTo(@NonNull Peer peer, boolean requestSwapBack) {
 | 
			
		||||
    public void connectTo(@NonNull Peer peer) {
 | 
			
		||||
        if (peer != this.peer) {
 | 
			
		||||
            Log.e(TAG, "Oops, got a different peer to swap with than initially planned.");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        peerRepo = ensureRepoExists(peer);
 | 
			
		||||
 | 
			
		||||
        // Only ask server to swap with us, if we are actually running a local repo service.
 | 
			
		||||
        // It is possible to have a swap initiated without first starting a swap, in which
 | 
			
		||||
        // case swapping back is pointless.
 | 
			
		||||
        if (isEnabled() && requestSwapBack) {
 | 
			
		||||
            askServerToSwapWithUs(peerRepo);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        UpdateService.updateRepoNow(this, peer.getRepoAddress());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -184,7 +178,9 @@ public class SwapService extends Service {
 | 
			
		||||
                    intent.putExtra(Downloader.EXTRA_ERROR_MESSAGE, e.getLocalizedMessage());
 | 
			
		||||
                    LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
 | 
			
		||||
                } finally {
 | 
			
		||||
                    conn.disconnect();
 | 
			
		||||
                    if (conn != null) {
 | 
			
		||||
                        conn.disconnect();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
@ -362,7 +358,7 @@ public class SwapService extends Service {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean isEnabled() {
 | 
			
		||||
        return bluetoothSwap.isConnected() || wifiSwap.isConnected();
 | 
			
		||||
        return bluetoothSwap.isConnected() || LocalHTTPDManager.isAlive();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ==========================================
 | 
			
		||||
@ -492,6 +488,7 @@ public class SwapService extends Service {
 | 
			
		||||
            bluetoothAdapter.disable();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        LocalHTTPDManager.stop(this);
 | 
			
		||||
        if (wifiManager != null && !wasWifiEnabledBeforeSwap()) {
 | 
			
		||||
            wifiManager.setWifiEnabled(false);
 | 
			
		||||
        }
 | 
			
		||||
@ -548,7 +545,7 @@ public class SwapService extends Service {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void run() {
 | 
			
		||||
                if (peer != null) {
 | 
			
		||||
                    connectTo(peer, false);
 | 
			
		||||
                    connectTo(peer);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
@ -1,18 +1,12 @@
 | 
			
		||||
package org.fdroid.fdroid.localrepo.type;
 | 
			
		||||
 | 
			
		||||
import android.annotation.SuppressLint;
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.net.wifi.WifiManager;
 | 
			
		||||
import android.os.Handler;
 | 
			
		||||
import android.os.Looper;
 | 
			
		||||
import android.os.Message;
 | 
			
		||||
import android.util.Log;
 | 
			
		||||
import org.fdroid.fdroid.FDroidApp;
 | 
			
		||||
import org.fdroid.fdroid.Preferences;
 | 
			
		||||
import org.fdroid.fdroid.Utils;
 | 
			
		||||
import org.fdroid.fdroid.localrepo.LocalHTTPDManager;
 | 
			
		||||
import org.fdroid.fdroid.localrepo.SwapService;
 | 
			
		||||
import org.fdroid.fdroid.net.LocalHTTPD;
 | 
			
		||||
import org.fdroid.fdroid.net.WifiStateChangeService;
 | 
			
		||||
import rx.Single;
 | 
			
		||||
import rx.SingleSubscriber;
 | 
			
		||||
import rx.android.schedulers.AndroidSchedulers;
 | 
			
		||||
@ -20,17 +14,11 @@ import rx.functions.Action1;
 | 
			
		||||
import rx.functions.Func2;
 | 
			
		||||
import rx.schedulers.Schedulers;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.net.BindException;
 | 
			
		||||
import java.util.Random;
 | 
			
		||||
 | 
			
		||||
@SuppressWarnings("LineLength")
 | 
			
		||||
public class WifiSwap extends SwapType {
 | 
			
		||||
 | 
			
		||||
    private static final String TAG = "WifiSwap";
 | 
			
		||||
 | 
			
		||||
    private Handler webServerThreadHandler;
 | 
			
		||||
    private LocalHTTPD localHttpd;
 | 
			
		||||
    private final BonjourBroadcast bonjourBroadcast;
 | 
			
		||||
    private final WifiManager wifiManager;
 | 
			
		||||
 | 
			
		||||
@ -50,10 +38,10 @@ public class WifiSwap extends SwapType {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void start() {
 | 
			
		||||
        sendBroadcast(SwapService.EXTRA_STARTING);
 | 
			
		||||
        wifiManager.setWifiEnabled(true);
 | 
			
		||||
 | 
			
		||||
        Utils.debugLog(TAG, "Preparing swap webserver.");
 | 
			
		||||
        sendBroadcast(SwapService.EXTRA_STARTING);
 | 
			
		||||
        LocalHTTPDManager.start(context);
 | 
			
		||||
 | 
			
		||||
        if (FDroidApp.ipAddressString == null) {
 | 
			
		||||
            Log.e(TAG, "Not starting swap webserver, because we don't seem to be connected to a network.");
 | 
			
		||||
@ -110,65 +98,17 @@ public class WifiSwap extends SwapType {
 | 
			
		||||
    private Single.OnSubscribe<Boolean> getWebServerTask() {
 | 
			
		||||
        return new Single.OnSubscribe<Boolean>() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void call(final SingleSubscriber<? super Boolean> singleSubscriber) {
 | 
			
		||||
                new Thread(new Runnable() {
 | 
			
		||||
                    // Tell Eclipse this is not a leak because of Looper use.
 | 
			
		||||
                    @SuppressLint("HandlerLeak")
 | 
			
		||||
                    @Override
 | 
			
		||||
                    public void run() {
 | 
			
		||||
                        localHttpd = new LocalHTTPD(
 | 
			
		||||
                            context,
 | 
			
		||||
                            FDroidApp.ipAddressString,
 | 
			
		||||
                            FDroidApp.port,
 | 
			
		||||
                            context.getFilesDir(),
 | 
			
		||||
                            Preferences.get().isLocalRepoHttpsEnabled());
 | 
			
		||||
 | 
			
		||||
                        Looper.prepare(); // must be run before creating a Handler
 | 
			
		||||
                        webServerThreadHandler = new Handler() {
 | 
			
		||||
                            @Override
 | 
			
		||||
                            public void handleMessage(Message msg) {
 | 
			
		||||
                                Log.i(TAG, "we've been asked to stop the webserver: " + msg.obj);
 | 
			
		||||
                                localHttpd.stop();
 | 
			
		||||
                                Looper looper = Looper.myLooper();
 | 
			
		||||
                                if (looper == null) {
 | 
			
		||||
                                    Log.e(TAG, "Looper.myLooper() was null for sum reason while shutting down the swap webserver.");
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    looper.quit();
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        };
 | 
			
		||||
                        try {
 | 
			
		||||
                            Utils.debugLog(TAG, "Starting swap webserver...");
 | 
			
		||||
                            localHttpd.start();
 | 
			
		||||
                            Utils.debugLog(TAG, "Swap webserver started.");
 | 
			
		||||
                            singleSubscriber.onSuccess(true);
 | 
			
		||||
                        } catch (BindException e) {
 | 
			
		||||
                            int prev = FDroidApp.port;
 | 
			
		||||
                            FDroidApp.port = FDroidApp.port + new Random().nextInt(1111);
 | 
			
		||||
                            WifiStateChangeService.start(context, null);
 | 
			
		||||
                            singleSubscriber.onError(new Exception("port " + prev + " occupied, trying on " + FDroidApp.port + "!"));
 | 
			
		||||
                        } catch (IOException e) {
 | 
			
		||||
                            Log.e(TAG, "Could not start local repo HTTP server", e);
 | 
			
		||||
                            singleSubscriber.onError(e);
 | 
			
		||||
                        }
 | 
			
		||||
                        Looper.loop(); // start the message receiving loop
 | 
			
		||||
                    }
 | 
			
		||||
                }).start();
 | 
			
		||||
            public void call(SingleSubscriber<? super Boolean> singleSubscriber) {
 | 
			
		||||
                singleSubscriber.onSuccess(true);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void stop() {
 | 
			
		||||
        sendBroadcast(SwapService.EXTRA_STOPPING);
 | 
			
		||||
        if (webServerThreadHandler == null) {
 | 
			
		||||
            Log.i(TAG, "null handler in stopWebServer");
 | 
			
		||||
        } else {
 | 
			
		||||
            Utils.debugLog(TAG, "Sending message to swap webserver to stop it.");
 | 
			
		||||
            Message msg = webServerThreadHandler.obtainMessage();
 | 
			
		||||
            msg.obj = webServerThreadHandler.getLooper().getThread().getName() + " says stop";
 | 
			
		||||
            webServerThreadHandler.sendMessage(msg);
 | 
			
		||||
        }
 | 
			
		||||
        sendBroadcast(SwapService.EXTRA_STOPPING); // This needs to be per-SwapType
 | 
			
		||||
        Utils.debugLog(TAG, "Sending message to swap webserver to stop it.");
 | 
			
		||||
        LocalHTTPDManager.stop(context);
 | 
			
		||||
 | 
			
		||||
        // Stop the Bonjour stuff after asking the webserver to stop. This is not required in this
 | 
			
		||||
        // order, but it helps. In practice, the Bonjour stuff takes a second or two to stop. This
 | 
			
		||||
 | 
			
		||||
@ -49,6 +49,7 @@ import java.io.FilenameFilter;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.InputStream;
 | 
			
		||||
import java.io.UnsupportedEncodingException;
 | 
			
		||||
import java.lang.ref.WeakReference;
 | 
			
		||||
import java.net.URLEncoder;
 | 
			
		||||
import java.text.DateFormat;
 | 
			
		||||
import java.text.SimpleDateFormat;
 | 
			
		||||
@ -80,7 +81,7 @@ public class LocalHTTPD extends NanoHTTPD {
 | 
			
		||||
     */
 | 
			
		||||
    public static final String[] INDEX_FILE_NAMES = {"index.html"};
 | 
			
		||||
 | 
			
		||||
    private final Context context;
 | 
			
		||||
    private final WeakReference<Context> context;
 | 
			
		||||
 | 
			
		||||
    protected List<File> rootDirs;
 | 
			
		||||
 | 
			
		||||
@ -101,7 +102,7 @@ public class LocalHTTPD extends NanoHTTPD {
 | 
			
		||||
    public LocalHTTPD(Context context, String hostname, int port, File webRoot, boolean useHttps) {
 | 
			
		||||
        super(hostname, port);
 | 
			
		||||
        rootDirs = Collections.singletonList(webRoot);
 | 
			
		||||
        this.context = context.getApplicationContext();
 | 
			
		||||
        this.context = new WeakReference<>(context.getApplicationContext());
 | 
			
		||||
        if (useHttps) {
 | 
			
		||||
            enableHTTPS();
 | 
			
		||||
        }
 | 
			
		||||
@ -370,7 +371,7 @@ public class LocalHTTPD extends NanoHTTPD {
 | 
			
		||||
                    return newFixedLengthResponse(Response.Status.BAD_REQUEST, MIME_PLAINTEXT,
 | 
			
		||||
                            "Requires 'repo' parameter to be posted.");
 | 
			
		||||
                }
 | 
			
		||||
                SwapWorkflowActivity.requestSwap(context, session.getParms().get("repo"));
 | 
			
		||||
                SwapWorkflowActivity.requestSwap(context.get(), session.getParms().get("repo"));
 | 
			
		||||
                return newFixedLengthResponse(Response.Status.OK, MIME_PLAINTEXT, "Swap request received.");
 | 
			
		||||
        }
 | 
			
		||||
        return newFixedLengthResponse("");
 | 
			
		||||
@ -491,7 +492,7 @@ public class LocalHTTPD extends NanoHTTPD {
 | 
			
		||||
 | 
			
		||||
    private void enableHTTPS() {
 | 
			
		||||
        try {
 | 
			
		||||
            LocalRepoKeyStore localRepoKeyStore = LocalRepoKeyStore.get(context);
 | 
			
		||||
            LocalRepoKeyStore localRepoKeyStore = LocalRepoKeyStore.get(context.get());
 | 
			
		||||
            SSLServerSocketFactory factory = NanoHTTPD.makeSSLSocketFactory(
 | 
			
		||||
                    localRepoKeyStore.getKeyStore(),
 | 
			
		||||
                    localRepoKeyStore.getKeyManagers());
 | 
			
		||||
 | 
			
		||||
@ -63,6 +63,9 @@ import java.io.FileOutputStream;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.InputStream;
 | 
			
		||||
import java.io.OutputStream;
 | 
			
		||||
import java.net.InetSocketAddress;
 | 
			
		||||
import java.net.ServerSocket;
 | 
			
		||||
import java.net.Socket;
 | 
			
		||||
import java.nio.charset.Charset;
 | 
			
		||||
import java.security.MessageDigest;
 | 
			
		||||
import java.security.NoSuchAlgorithmException;
 | 
			
		||||
@ -887,4 +890,26 @@ public final class Utils {
 | 
			
		||||
        theme.resolveAttribute(R.attr.colorPrimary, typedValue, true);
 | 
			
		||||
        swipeLayout.setColorSchemeColors(typedValue.data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static boolean canConnectToSocket(String host, int port) {
 | 
			
		||||
        try {
 | 
			
		||||
            Socket socket = new Socket();
 | 
			
		||||
            socket.connect(new InetSocketAddress(host, port), 5);
 | 
			
		||||
            socket.close();
 | 
			
		||||
            return true;
 | 
			
		||||
        } catch (IOException e) {
 | 
			
		||||
            // Could not connect.
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static boolean isServerSocketInUse(int port) {
 | 
			
		||||
        try {
 | 
			
		||||
            (new ServerSocket(port)).close();
 | 
			
		||||
            return false;
 | 
			
		||||
        } catch (IOException e) {
 | 
			
		||||
            // Could not connect.
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,72 @@
 | 
			
		||||
package org.fdroid.fdroid.localrepo;
 | 
			
		||||
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import org.fdroid.fdroid.FDroidApp;
 | 
			
		||||
import org.fdroid.fdroid.Utils;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.junit.runner.RunWith;
 | 
			
		||||
import org.robolectric.RobolectricTestRunner;
 | 
			
		||||
import org.robolectric.RuntimeEnvironment;
 | 
			
		||||
import org.robolectric.shadows.ShadowLog;
 | 
			
		||||
 | 
			
		||||
import java.util.concurrent.CountDownLatch;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
 | 
			
		||||
import static org.junit.Assert.assertFalse;
 | 
			
		||||
import static org.junit.Assert.assertTrue;
 | 
			
		||||
import static org.junit.Assert.fail;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@RunWith(RobolectricTestRunner.class)
 | 
			
		||||
public class LocalHTTPDManagerTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testStartStop() throws InterruptedException {
 | 
			
		||||
        ShadowLog.stream = System.out;
 | 
			
		||||
        Context context = RuntimeEnvironment.application;
 | 
			
		||||
 | 
			
		||||
        final String host = "localhost";
 | 
			
		||||
        final int port = 8888;
 | 
			
		||||
        assertFalse(Utils.isServerSocketInUse(port));
 | 
			
		||||
        LocalHTTPDManager.stop(context);
 | 
			
		||||
 | 
			
		||||
        FDroidApp.ipAddressString = host;
 | 
			
		||||
        FDroidApp.port = port;
 | 
			
		||||
 | 
			
		||||
        LocalHTTPDManager.start(context, false);
 | 
			
		||||
        final CountDownLatch startLatch = new CountDownLatch(1);
 | 
			
		||||
        new Thread(new Runnable() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void run() {
 | 
			
		||||
                while (!Utils.isServerSocketInUse(port)) {
 | 
			
		||||
                    try {
 | 
			
		||||
                        Thread.sleep(500);
 | 
			
		||||
                    } catch (InterruptedException e) {
 | 
			
		||||
                        fail();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                startLatch.countDown();
 | 
			
		||||
            }
 | 
			
		||||
        }).start();
 | 
			
		||||
        assertTrue(startLatch.await(30, TimeUnit.SECONDS));
 | 
			
		||||
        assertTrue(Utils.isServerSocketInUse(port));
 | 
			
		||||
        assertTrue(Utils.canConnectToSocket(host, port));
 | 
			
		||||
 | 
			
		||||
        LocalHTTPDManager.stop(context);
 | 
			
		||||
        final CountDownLatch stopLatch = new CountDownLatch(1);
 | 
			
		||||
        new Thread(new Runnable() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void run() {
 | 
			
		||||
                while (!Utils.isServerSocketInUse(port)) {
 | 
			
		||||
                    try {
 | 
			
		||||
                        Thread.sleep(500);
 | 
			
		||||
                    } catch (InterruptedException e) {
 | 
			
		||||
                        fail();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                stopLatch.countDown();
 | 
			
		||||
            }
 | 
			
		||||
        }).start();
 | 
			
		||||
        assertTrue(stopLatch.await(10, TimeUnit.SECONDS));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user