Merge "8u121-b13 integration for java.net - Document Android changes"
diff --git a/ojluni/src/main/java/java/net/Socket.java b/ojluni/src/main/java/java/net/Socket.java
index f509bcd..e36d15b 100644
--- a/ojluni/src/main/java/java/net/Socket.java
+++ b/ojluni/src/main/java/java/net/Socket.java
@@ -122,7 +122,7 @@
         Proxy p = proxy == Proxy.NO_PROXY ? Proxy.NO_PROXY
                                           : sun.net.ApplicationProxy.create(proxy);
         Proxy.Type type = p.type();
-        // Android-changed: Removed HTTP proxy suppport.
+        // Android-changed: Removed HTTP proxy support.
         // if (type == Proxy.Type.SOCKS || type == Proxy.Type.HTTP) {
         if (type == Proxy.Type.SOCKS) {
             SecurityManager security = System.getSecurityManager();
@@ -214,6 +214,7 @@
     public Socket(String host, int port)
         throws UnknownHostException, IOException
     {
+        // Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735
         this(InetAddress.getAllByName(host), port, (SocketAddress) null, true);
     }
 
@@ -245,6 +246,7 @@
      * @see        SecurityManager#checkConnect
      */
     public Socket(InetAddress address, int port) throws IOException {
+        // Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735
         this(nonNullAddress(address), port, (SocketAddress) null, true);
     }
 
@@ -286,6 +288,7 @@
      */
     public Socket(String host, int port, InetAddress localAddr,
                   int localPort) throws IOException {
+        // Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735
         this(InetAddress.getAllByName(host), port,
              new InetSocketAddress(localAddr, localPort), true);
     }
@@ -327,6 +330,7 @@
      */
     public Socket(InetAddress address, int port, InetAddress localAddr,
                   int localPort) throws IOException {
+        // Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735
         this(nonNullAddress(address), port,
              new InetSocketAddress(localAddr, localPort), true);
     }
@@ -374,6 +378,7 @@
      */
     @Deprecated
     public Socket(String host, int port, boolean stream) throws IOException {
+        // Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735
         this(InetAddress.getAllByName(host), port, (SocketAddress) null, stream);
     }
 
@@ -415,9 +420,11 @@
      */
     @Deprecated
     public Socket(InetAddress host, int port, boolean stream) throws IOException {
+        // Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735
         this(nonNullAddress(host), port, new InetSocketAddress(0), stream);
     }
 
+    // BEGIN Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735
     private static InetAddress[] nonNullAddress(InetAddress address) {
         // backward compatibility
         if (address == null)
@@ -426,8 +433,6 @@
         return new InetAddress[] { address };
     }
 
-    // Android-changed: Socket ctor should try all addresses
-    // b/30007735
     private Socket(InetAddress[] addresses, int port, SocketAddress localAddr,
             boolean stream) throws IOException {
         if (addresses == null || addresses.length == 0) {
@@ -446,9 +451,8 @@
                 break;
             } catch (IOException | IllegalArgumentException | SecurityException e) {
                 try {
-                    // Android-changed:
-                    // Do not call #close, classes that extend this class may do not expect a call
-                    // to #close coming from the superclass constructor.
+                    // Android-changed: Let ctor call impl.close() instead of overridable close().
+                    // Subclasses may not expect a call to close() coming from this constructor.
                     impl.close();
                     closed = true;
                 } catch (IOException ce) {
@@ -468,6 +472,7 @@
             closed = false;
         }
     }
+    // END Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735
 
     /**
      * Creates the socket implementation.
@@ -1768,7 +1773,7 @@
         /* Not implemented yet */
     }
 
-    // Android-added: for testing and internal use.
+    // Android-added: getFileDescriptor$() method for testing and internal use.
     /**
      * @hide internal use only
      */
diff --git a/ojluni/src/main/java/java/net/SocketException.java b/ojluni/src/main/java/java/net/SocketException.java
index 286bc42..64ae771 100644
--- a/ojluni/src/main/java/java/net/SocketException.java
+++ b/ojluni/src/main/java/java/net/SocketException.java
@@ -54,6 +54,7 @@
     public SocketException() {
     }
 
+    // BEGIN Android-added: SocketException ctor with cause for internal use.
     /** @hide */
     public SocketException(Throwable cause) {
         super(cause);
@@ -63,4 +64,5 @@
     public SocketException(String msg, Throwable cause) {
         super(msg, cause);
     }
+    // END Android-added: SocketException ctor with cause for internal use.
 }
diff --git a/ojluni/src/main/java/java/net/SocketImpl.java b/ojluni/src/main/java/java/net/SocketImpl.java
index c0db070..ade2630 100644
--- a/ojluni/src/main/java/java/net/SocketImpl.java
+++ b/ojluni/src/main/java/java/net/SocketImpl.java
@@ -227,6 +227,7 @@
         return fd;
     }
 
+    // Android-added: getFD$() for testing.
     /**
      * @hide used by java.nio tests
      */
diff --git a/ojluni/src/main/java/java/net/SocketInputStream.java b/ojluni/src/main/java/java/net/SocketInputStream.java
index f5c4c8f..8d0e0c5 100644
--- a/ojluni/src/main/java/java/net/SocketInputStream.java
+++ b/ojluni/src/main/java/java/net/SocketInputStream.java
@@ -44,6 +44,11 @@
  */
 class SocketInputStream extends FileInputStream
 {
+    // Android-removed: Android doesn't need to call native init.
+    // static {
+    //    init();
+    //}
+
     private boolean eof;
     private AbstractPlainSocketImpl impl = null;
     private byte temp[];
@@ -166,6 +171,7 @@
         // acquire file descriptor and do the read
         FileDescriptor fd = impl.acquireFD();
         try {
+            // Android-added: Check BlockGuard policy in read().
             BlockGuard.getThreadPolicy().onNetwork();
             n = socketRead(fd, b, off, length, timeout);
             if (n > 0) {
@@ -289,4 +295,11 @@
      * Overrides finalize, the fd is closed by the Socket.
      */
     protected void finalize() {}
+
+    // Android-removed: Android doesn't need native init.
+    /*
+     * Perform class load-time initializations.
+     *
+    private native static void init();
+    */
 }
diff --git a/ojluni/src/main/java/java/net/SocketOutputStream.java b/ojluni/src/main/java/java/net/SocketOutputStream.java
index c0173f0..9fe9eac 100644
--- a/ojluni/src/main/java/java/net/SocketOutputStream.java
+++ b/ojluni/src/main/java/java/net/SocketOutputStream.java
@@ -43,6 +43,11 @@
  */
 class SocketOutputStream extends FileOutputStream
 {
+    // Android-removed: Android doesn't need to call native init.
+    // static {
+    //    init();
+    //}
+
     private AbstractPlainSocketImpl impl = null;
     private byte temp[] = new byte[1];
     private Socket socket = null;
@@ -105,6 +110,7 @@
 
         FileDescriptor fd = impl.acquireFD();
         try {
+            // Android-added: Check BlockGuard policy in socketWrite.
             BlockGuard.getThreadPolicy().onNetwork();
             socketWrite0(fd, b, off, len);
         } catch (SocketException se) {
@@ -174,4 +180,11 @@
      * Overrides finalize, the fd is closed by the Socket.
      */
     protected void finalize() {}
+
+    // Android-removed: Android doesn't need native init.
+    /*
+     * Perform class load-time initializations.
+     *
+    private native static void init();
+    */
 }
diff --git a/ojluni/src/main/java/java/net/SocksSocketImpl.java b/ojluni/src/main/java/java/net/SocksSocketImpl.java
index a81e219..0d9d8f5 100644
--- a/ojluni/src/main/java/java/net/SocksSocketImpl.java
+++ b/ojluni/src/main/java/java/net/SocksSocketImpl.java
@@ -347,12 +347,91 @@
                                       epoint.getPort());
         }
         if (server == null) {
-            // Android-removed: Logic to establish proxy connection based on default ProxySelector
+            // Android-removed: Logic to establish proxy connection based on default ProxySelector.
+            // Removed code that tried to establish proxy connection if ProxySelector#getDefault()
+            // is not null. This was never the case in previous Android releases, was causing
+            // issues and therefore was removed.
             /*
-             * Removed code that tried to establish proxy connection if
-             * ProxySelector#getDefault() is not null.
-             * This was never the case in previous android releases, was causing
-             * issues and therefore was removed.
+            // This is the general case
+            // server is not null only when the socket was created with a
+            // specified proxy in which case it does bypass the ProxySelector
+            ProxySelector sel = java.security.AccessController.doPrivileged(
+                new java.security.PrivilegedAction<ProxySelector>() {
+                    public ProxySelector run() {
+                            return ProxySelector.getDefault();
+                        }
+                    });
+            if (sel == null) {
+                /*
+                 * No default proxySelector --> direct connection
+                 *
+                super.connect(epoint, remainingMillis(deadlineMillis));
+                return;
+            }
+            URI uri;
+            // Use getHostString() to avoid reverse lookups
+            String host = epoint.getHostString();
+            // IPv6 litteral?
+            if (epoint.getAddress() instanceof Inet6Address &&
+                (!host.startsWith("[")) && (host.indexOf(":") >= 0)) {
+                host = "[" + host + "]";
+            }
+            try {
+                uri = new URI("socket://" + ParseUtil.encodePath(host) + ":"+ epoint.getPort());
+            } catch (URISyntaxException e) {
+                // This shouldn't happen
+                assert false : e;
+                uri = null;
+            }
+            Proxy p = null;
+            IOException savedExc = null;
+            java.util.Iterator<Proxy> iProxy = null;
+            iProxy = sel.select(uri).iterator();
+            if (iProxy == null || !(iProxy.hasNext())) {
+                super.connect(epoint, remainingMillis(deadlineMillis));
+                return;
+            }
+            while (iProxy.hasNext()) {
+                p = iProxy.next();
+                if (p == null || p.type() != Proxy.Type.SOCKS) {
+                    super.connect(epoint, remainingMillis(deadlineMillis));
+                    return;
+                }
+
+                if (!(p.address() instanceof InetSocketAddress))
+                    throw new SocketException("Unknown address type for proxy: " + p);
+                // Use getHostString() to avoid reverse lookups
+                server = ((InetSocketAddress) p.address()).getHostString();
+                serverPort = ((InetSocketAddress) p.address()).getPort();
+                if (p instanceof SocksProxy) {
+                    if (((SocksProxy)p).protocolVersion() == 4) {
+                        useV4 = true;
+                    }
+                }
+
+                // Connects to the SOCKS server
+                try {
+                    privilegedConnect(server, serverPort, remainingMillis(deadlineMillis));
+                    // Worked, let's get outta here
+                    break;
+                } catch (IOException e) {
+                    // Ooops, let's notify the ProxySelector
+                    sel.connectFailed(uri,p.address(),e);
+                    server = null;
+                    serverPort = -1;
+                    savedExc = e;
+                    // Will continue the while loop and try the next proxy
+                }
+            }
+
+            /*
+             * If server is still null at this point, none of the proxy
+             * worked
+             *
+            if (server == null) {
+                throw new SocketException("Can't connect to SOCKS proxy:"
+                                          + savedExc.getMessage());
+            }
              */
             super.connect(epoint, remainingMillis(deadlineMillis));
             return;
@@ -508,6 +587,458 @@
         external_address = epoint;
     }
 
+    // Android-removed: Dead code. bindV4, socksBind, acceptFrom methods.
+    /*
+    private void bindV4(InputStream in, OutputStream out,
+                        InetAddress baddr,
+                        int lport) throws IOException {
+        if (!(baddr instanceof Inet4Address)) {
+            throw new SocketException("SOCKS V4 requires IPv4 only addresses");
+        }
+        super.bind(baddr, lport);
+        byte[] addr1 = baddr.getAddress();
+        /* Test for AnyLocal *
+        InetAddress naddr = baddr;
+        if (naddr.isAnyLocalAddress()) {
+            naddr = AccessController.doPrivileged(
+                        new PrivilegedAction<InetAddress>() {
+                            public InetAddress run() {
+                                return cmdsock.getLocalAddress();
+
+                            }
+                        });
+            addr1 = naddr.getAddress();
+        }
+        out.write(PROTO_VERS4);
+        out.write(BIND);
+        out.write((super.getLocalPort() >> 8) & 0xff);
+        out.write((super.getLocalPort() >> 0) & 0xff);
+        out.write(addr1);
+        String userName = getUserName();
+        try {
+            out.write(userName.getBytes("ISO-8859-1"));
+        } catch (java.io.UnsupportedEncodingException uee) {
+            assert false;
+        }
+        out.write(0);
+        out.flush();
+        byte[] data = new byte[8];
+        int n = readSocksReply(in, data);
+        if (n != 8)
+            throw new SocketException("Reply from SOCKS server has bad length: " + n);
+        if (data[0] != 0 && data[0] != 4)
+            throw new SocketException("Reply from SOCKS server has bad version");
+        SocketException ex = null;
+        switch (data[1]) {
+        case 90:
+            // Success!
+            external_address = new InetSocketAddress(baddr, lport);
+            break;
+        case 91:
+            ex = new SocketException("SOCKS request rejected");
+            break;
+        case 92:
+            ex = new SocketException("SOCKS server couldn't reach destination");
+            break;
+        case 93:
+            ex = new SocketException("SOCKS authentication failed");
+            break;
+        default:
+            ex = new SocketException("Reply from SOCKS server contains bad status");
+            break;
+        }
+        if (ex != null) {
+            in.close();
+            out.close();
+            throw ex;
+        }
+
+    }
+
+    /**
+     * Sends the Bind request to the SOCKS proxy. In the SOCKS protocol, bind
+     * means "accept incoming connection from", so the SocketAddress is the
+     * the one of the host we do accept connection from.
+     *
+     * @param      saddr   the Socket address of the remote host.
+     * @exception  IOException  if an I/O error occurs when binding this socket.
+     *
+    protected synchronized void socksBind(InetSocketAddress saddr) throws IOException {
+        if (socket != null) {
+            // this is a client socket, not a server socket, don't
+            // call the SOCKS proxy for a bind!
+            return;
+        }
+
+        // Connects to the SOCKS server
+
+        if (server == null) {
+            // This is the general case
+            // server is not null only when the socket was created with a
+            // specified proxy in which case it does bypass the ProxySelector
+            ProxySelector sel = java.security.AccessController.doPrivileged(
+                new java.security.PrivilegedAction<ProxySelector>() {
+                    public ProxySelector run() {
+                            return ProxySelector.getDefault();
+                        }
+                    });
+            if (sel == null) {
+                /*
+                 * No default proxySelector --> direct connection
+                 *
+                return;
+            }
+            URI uri;
+            // Use getHostString() to avoid reverse lookups
+            String host = saddr.getHostString();
+            // IPv6 litteral?
+            if (saddr.getAddress() instanceof Inet6Address &&
+                (!host.startsWith("[")) && (host.indexOf(":") >= 0)) {
+                host = "[" + host + "]";
+            }
+            try {
+                uri = new URI("serversocket://" + ParseUtil.encodePath(host) + ":"+ saddr.getPort());
+            } catch (URISyntaxException e) {
+                // This shouldn't happen
+                assert false : e;
+                uri = null;
+            }
+            Proxy p = null;
+            Exception savedExc = null;
+            java.util.Iterator<Proxy> iProxy = null;
+            iProxy = sel.select(uri).iterator();
+            if (iProxy == null || !(iProxy.hasNext())) {
+                return;
+            }
+            while (iProxy.hasNext()) {
+                p = iProxy.next();
+                if (p == null || p.type() != Proxy.Type.SOCKS) {
+                    return;
+                }
+
+                if (!(p.address() instanceof InetSocketAddress))
+                    throw new SocketException("Unknown address type for proxy: " + p);
+                // Use getHostString() to avoid reverse lookups
+                server = ((InetSocketAddress) p.address()).getHostString();
+                serverPort = ((InetSocketAddress) p.address()).getPort();
+                if (p instanceof SocksProxy) {
+                    if (((SocksProxy)p).protocolVersion() == 4) {
+                        useV4 = true;
+                    }
+                }
+
+                // Connects to the SOCKS server
+                try {
+                    AccessController.doPrivileged(
+                        new PrivilegedExceptionAction<Void>() {
+                            public Void run() throws Exception {
+                                cmdsock = new Socket(new PlainSocketImpl());
+                                cmdsock.connect(new InetSocketAddress(server, serverPort));
+                                cmdIn = cmdsock.getInputStream();
+                                cmdOut = cmdsock.getOutputStream();
+                                return null;
+                            }
+                        });
+                } catch (Exception e) {
+                    // Ooops, let's notify the ProxySelector
+                    sel.connectFailed(uri,p.address(),new SocketException(e.getMessage()));
+                    server = null;
+                    serverPort = -1;
+                    cmdsock = null;
+                    savedExc = e;
+                    // Will continue the while loop and try the next proxy
+                }
+            }
+
+            /*
+             * If server is still null at this point, none of the proxy
+             * worked
+             *
+            if (server == null || cmdsock == null) {
+                throw new SocketException("Can't connect to SOCKS proxy:"
+                                          + savedExc.getMessage());
+            }
+        } else {
+            try {
+                AccessController.doPrivileged(
+                    new PrivilegedExceptionAction<Void>() {
+                        public Void run() throws Exception {
+                            cmdsock = new Socket(new PlainSocketImpl());
+                            cmdsock.connect(new InetSocketAddress(server, serverPort));
+                            cmdIn = cmdsock.getInputStream();
+                            cmdOut = cmdsock.getOutputStream();
+                            return null;
+                        }
+                    });
+            } catch (Exception e) {
+                throw new SocketException(e.getMessage());
+            }
+        }
+        BufferedOutputStream out = new BufferedOutputStream(cmdOut, 512);
+        InputStream in = cmdIn;
+        if (useV4) {
+            bindV4(in, out, saddr.getAddress(), saddr.getPort());
+            return;
+        }
+        out.write(PROTO_VERS);
+        out.write(2);
+        out.write(NO_AUTH);
+        out.write(USER_PASSW);
+        out.flush();
+        byte[] data = new byte[2];
+        int i = readSocksReply(in, data);
+        if (i != 2 || ((int)data[0]) != PROTO_VERS) {
+            // Maybe it's not a V5 sever after all
+            // Let's try V4 before we give up
+            bindV4(in, out, saddr.getAddress(), saddr.getPort());
+            return;
+        }
+        if (((int)data[1]) == NO_METHODS)
+            throw new SocketException("SOCKS : No acceptable methods");
+        if (!authenticate(data[1], in, out)) {
+            throw new SocketException("SOCKS : authentication failed");
+        }
+        // We're OK. Let's issue the BIND command.
+        out.write(PROTO_VERS);
+        out.write(BIND);
+        out.write(0);
+        int lport = saddr.getPort();
+        if (saddr.isUnresolved()) {
+            out.write(DOMAIN_NAME);
+            out.write(saddr.getHostName().length());
+            try {
+                out.write(saddr.getHostName().getBytes("ISO-8859-1"));
+            } catch (java.io.UnsupportedEncodingException uee) {
+                assert false;
+            }
+            out.write((lport >> 8) & 0xff);
+            out.write((lport >> 0) & 0xff);
+        } else if (saddr.getAddress() instanceof Inet4Address) {
+            byte[] addr1 = saddr.getAddress().getAddress();
+            out.write(IPV4);
+            out.write(addr1);
+            out.write((lport >> 8) & 0xff);
+            out.write((lport >> 0) & 0xff);
+            out.flush();
+        } else if (saddr.getAddress() instanceof Inet6Address) {
+            byte[] addr1 = saddr.getAddress().getAddress();
+            out.write(IPV6);
+            out.write(addr1);
+            out.write((lport >> 8) & 0xff);
+            out.write((lport >> 0) & 0xff);
+            out.flush();
+        } else {
+            cmdsock.close();
+            throw new SocketException("unsupported address type : " + saddr);
+        }
+        data = new byte[4];
+        i = readSocksReply(in, data);
+        SocketException ex = null;
+        int len, nport;
+        byte[] addr;
+        switch (data[1]) {
+        case REQUEST_OK:
+            // success!
+            switch(data[3]) {
+            case IPV4:
+                addr = new byte[4];
+                i = readSocksReply(in, addr);
+                if (i != 4)
+                    throw new SocketException("Reply from SOCKS server badly formatted");
+                data = new byte[2];
+                i = readSocksReply(in, data);
+                if (i != 2)
+                    throw new SocketException("Reply from SOCKS server badly formatted");
+                nport = ((int)data[0] & 0xff) << 8;
+                nport += ((int)data[1] & 0xff);
+                external_address =
+                    new InetSocketAddress(new Inet4Address("", addr) , nport);
+                break;
+            case DOMAIN_NAME:
+                len = data[1];
+                byte[] host = new byte[len];
+                i = readSocksReply(in, host);
+                if (i != len)
+                    throw new SocketException("Reply from SOCKS server badly formatted");
+                data = new byte[2];
+                i = readSocksReply(in, data);
+                if (i != 2)
+                    throw new SocketException("Reply from SOCKS server badly formatted");
+                nport = ((int)data[0] & 0xff) << 8;
+                nport += ((int)data[1] & 0xff);
+                external_address = new InetSocketAddress(new String(host), nport);
+                break;
+            case IPV6:
+                len = data[1];
+                addr = new byte[len];
+                i = readSocksReply(in, addr);
+                if (i != len)
+                    throw new SocketException("Reply from SOCKS server badly formatted");
+                data = new byte[2];
+                i = readSocksReply(in, data);
+                if (i != 2)
+                    throw new SocketException("Reply from SOCKS server badly formatted");
+                nport = ((int)data[0] & 0xff) << 8;
+                nport += ((int)data[1] & 0xff);
+                external_address =
+                    new InetSocketAddress(new Inet6Address("", addr), nport);
+                break;
+            }
+            break;
+        case GENERAL_FAILURE:
+            ex = new SocketException("SOCKS server general failure");
+            break;
+        case NOT_ALLOWED:
+            ex = new SocketException("SOCKS: Bind not allowed by ruleset");
+            break;
+        case NET_UNREACHABLE:
+            ex = new SocketException("SOCKS: Network unreachable");
+            break;
+        case HOST_UNREACHABLE:
+            ex = new SocketException("SOCKS: Host unreachable");
+            break;
+        case CONN_REFUSED:
+            ex = new SocketException("SOCKS: Connection refused");
+            break;
+        case TTL_EXPIRED:
+            ex =  new SocketException("SOCKS: TTL expired");
+            break;
+        case CMD_NOT_SUPPORTED:
+            ex = new SocketException("SOCKS: Command not supported");
+            break;
+        case ADDR_TYPE_NOT_SUP:
+            ex = new SocketException("SOCKS: address type not supported");
+            break;
+        }
+        if (ex != null) {
+            in.close();
+            out.close();
+            cmdsock.close();
+            cmdsock = null;
+            throw ex;
+        }
+        cmdIn = in;
+        cmdOut = out;
+    }
+
+    /**
+     * Accepts a connection from a specific host.
+     *
+     * @param      s   the accepted connection.
+     * @param      saddr the socket address of the host we do accept
+     *               connection from
+     * @exception  IOException  if an I/O error occurs when accepting the
+     *               connection.
+     *
+    protected void acceptFrom(SocketImpl s, InetSocketAddress saddr) throws IOException {
+        if (cmdsock == null) {
+            // Not a Socks ServerSocket.
+            return;
+        }
+        InputStream in = cmdIn;
+        // Sends the "SOCKS BIND" request.
+        socksBind(saddr);
+        in.read();
+        int i = in.read();
+        in.read();
+        SocketException ex = null;
+        int nport;
+        byte[] addr;
+        InetSocketAddress real_end = null;
+        switch (i) {
+        case REQUEST_OK:
+            // success!
+            i = in.read();
+            switch(i) {
+            case IPV4:
+                addr = new byte[4];
+                readSocksReply(in, addr);
+                nport = in.read() << 8;
+                nport += in.read();
+                real_end =
+                    new InetSocketAddress(new Inet4Address("", addr) , nport);
+                break;
+            case DOMAIN_NAME:
+                int len = in.read();
+                addr = new byte[len];
+                readSocksReply(in, addr);
+                nport = in.read() << 8;
+                nport += in.read();
+                real_end = new InetSocketAddress(new String(addr), nport);
+                break;
+            case IPV6:
+                addr = new byte[16];
+                readSocksReply(in, addr);
+                nport = in.read() << 8;
+                nport += in.read();
+                real_end =
+                    new InetSocketAddress(new Inet6Address("", addr), nport);
+                break;
+            }
+            break;
+        case GENERAL_FAILURE:
+            ex = new SocketException("SOCKS server general failure");
+            break;
+        case NOT_ALLOWED:
+            ex = new SocketException("SOCKS: Accept not allowed by ruleset");
+            break;
+        case NET_UNREACHABLE:
+            ex = new SocketException("SOCKS: Network unreachable");
+            break;
+        case HOST_UNREACHABLE:
+            ex = new SocketException("SOCKS: Host unreachable");
+            break;
+        case CONN_REFUSED:
+            ex = new SocketException("SOCKS: Connection refused");
+            break;
+        case TTL_EXPIRED:
+            ex =  new SocketException("SOCKS: TTL expired");
+            break;
+        case CMD_NOT_SUPPORTED:
+            ex = new SocketException("SOCKS: Command not supported");
+            break;
+        case ADDR_TYPE_NOT_SUP:
+            ex = new SocketException("SOCKS: address type not supported");
+            break;
+        }
+        if (ex != null) {
+            cmdIn.close();
+            cmdOut.close();
+            cmdsock.close();
+            cmdsock = null;
+            throw ex;
+        }
+
+        /**
+         * This is where we have to do some fancy stuff.
+         * The datastream from the socket "accepted" by the proxy will
+         * come through the cmdSocket. So we have to swap the socketImpls
+         *
+        if (s instanceof SocksSocketImpl) {
+            ((SocksSocketImpl)s).external_address = real_end;
+        }
+        if (s instanceof PlainSocketImpl) {
+            PlainSocketImpl psi = (PlainSocketImpl) s;
+            psi.setInputStream((SocketInputStream) in);
+            psi.setFileDescriptor(cmdsock.getImpl().getFileDescriptor());
+            psi.setAddress(cmdsock.getImpl().getInetAddress());
+            psi.setPort(cmdsock.getImpl().getPort());
+            psi.setLocalPort(cmdsock.getImpl().getLocalPort());
+        } else {
+            s.fd = cmdsock.getImpl().fd;
+            s.address = cmdsock.getImpl().address;
+            s.port = cmdsock.getImpl().port;
+            s.localport = cmdsock.getImpl().localport;
+        }
+
+        // Need to do that so that the socket won't be closed
+        // when the ServerSocket is closed by the user.
+        // It kinds of detaches the Socket because it is now
+        // used elsewhere.
+        cmdsock = null;
+    }
+    */
+
     /**
      * Returns the value of this socket's {@code address} field.
      *
diff --git a/ojluni/src/main/java/java/net/URLClassLoader.java b/ojluni/src/main/java/java/net/URLClassLoader.java
index fb62290..2a35ee6 100644
--- a/ojluni/src/main/java/java/net/URLClassLoader.java
+++ b/ojluni/src/main/java/java/net/URLClassLoader.java
@@ -456,12 +456,16 @@
             // Use (direct) ByteBuffer:
             CodeSigner[] signers = res.getCodeSigners();
             CodeSource cs = new CodeSource(url, signers);
+            // Android-removed: Android doesn't use sun.misc.PerfCounter.
+            // sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
             return defineClass(name, bb, cs);
         } else {
             byte[] b = res.getBytes();
             // must read certificates AFTER reading bytes.
             CodeSigner[] signers = res.getCodeSigners();
             CodeSource cs = new CodeSource(url, signers);
+            // Android-removed: Android doesn't use sun.misc.PerfCounter.
+            // sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
             return defineClass(name, b, 0, b.length, cs);
         }
     }
@@ -767,6 +771,7 @@
     }
 
     static {
+        // Android-removed: SharedSecrets.setJavaNetAccess call. Android doesn't use it.
         /*sun.misc.SharedSecrets.setJavaNetAccess (
             new sun.misc.JavaNetAccess() {
                 public URLClassPath getURLClassPath (URLClassLoader u) {