();
188 | headers.put("Content-Type", "application/octet-stream");
189 | headers.put("Content-Length", "" + file.length());
190 | headers.put("Content-Disposition", "attachment;filename=\"" + file.getName() + "\"");
191 | writeHeaders(outputStream, headers);
192 |
193 | FileInputStream fis = null;
194 | try {
195 | fis = new FileInputStream(file);
196 | byte[] buff = new byte[8192];
197 | int len = -1;
198 | while ((len = fis.read(buff)) != -1) {
199 | outputStream.write(buff, 0, len);
200 | }
201 | outputStream.flush();
202 | } catch (IOException e) {
203 | e.printStackTrace();
204 | } finally {
205 | try {
206 | if (fis != null) {
207 | fis.close();
208 | }
209 | } catch (IOException e) {
210 | e.printStackTrace();
211 | }
212 | }
213 | }
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/remotelogcat/src/main/java/com/zzzmode/android/server/LineInputStream.java:
--------------------------------------------------------------------------------
1 | /*
2 | * LineInputStream.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package com.zzzmode.android.server;
20 |
21 | import java.io.ByteArrayOutputStream;
22 | import java.io.IOException;
23 | import java.io.InputStream;
24 |
25 |
26 | /**
27 | * LineInputStream decorates an InputStream for line-by-line reading.
28 | * It implements a readLine()
method, similar to
29 | * BufferedReader
, but does not need an intermediate
30 | * InputStreamReader
to translate.
31 | *
32 | * The reason we avoid an InputStreamReader
is that it
33 | * will consume as much of the input as it possibly can in order
34 | * to optimize character encoding from the input bytes.
35 | *
36 | *
In our case, the input will be switching protocols from an HTTP
37 | * Request to WebSocket frames. We want to consume the HTTP Request
38 | * line by line and process it for a WebSocket handshake, but the
39 | * data following that must be handled in a completely different way.
40 | *
41 | *
The ability to handle the data line-by-line like
42 | * BufferedReader
is able to do, is very useful for
43 | * parsing the HTTP Reqeust. Therefore we have ListInputStream to
44 | * provide this functionality as an InputStream decorator.
45 | *
46 | *
Unlike BufferedReader
, which handles three kinds of
47 | * line endings (CR, LF, CRLF), the HTTP protocol has defined the line
48 | * ending to be CRLF. Therefore, the readLine()
method of
49 | * this decorator requires a CRLF (or EOF) sequence to terminate a line.
50 | *
51 | * @author blinkdog
52 | */
53 | public class LineInputStream extends InputStream {
54 | /**
55 | * Constant defining Carriage Return (CR). Octet 13, Hex 0x0c.
56 | */
57 | public static final int CR = 13;
58 |
59 | /**
60 | * Constant defining the end of the stream (EOF). This is derived
61 | * from the InputStream API. Calls to read()
return -1
62 | * when the end of the stream is reached.
63 | */
64 | public static final int EOF = -1;
65 |
66 | /**
67 | * Constant defining Line Feed (LF). Octet 10, Hex 0x0a
68 | */
69 | public static final int LF = 10;
70 |
71 | /**
72 | * Constant defining the canonical name of the UTF-8 character encoding.
73 | */
74 | public static final String UTF_8 = "UTF-8";
75 |
76 | /**
77 | * Construct a LineInputStream to decorate the provided InputStream.
78 | * A NullPointerException
will be thrown if the provided
79 | * InputStream is null.
80 | * @param in InputStream to be decorated by this LineInputStream
81 | */
82 | public LineInputStream(final InputStream in) {
83 | //checkNotNull(in);
84 | this.inputStream = in;
85 | }
86 |
87 | /**
88 | * {@inheritDoc}
89 | * @return the next byte of data, or -1
if the end of the
90 | * stream is reached.
91 | * @throws IOException if an I/O error occurs
92 | */
93 | @Override
94 | public final int read() throws IOException {
95 | return inputStream.read();
96 | }
97 |
98 | /**
99 | * Reads a line of text. A line is considered to be terminated by a
100 | * carriage return ('\r') followed immediately by a linefeed ('\n').
101 | * This is per the HTTP specification.
102 | * @return String containing the contents of the line, not including
103 | * any line-termination characters, or null if the end of the
104 | * stream has been reached
105 | * @throws IOException if an I/O error occurs
106 | */
107 | public final String readLine() throws IOException {
108 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
109 | boolean inputTaken = false;
110 | while (true) {
111 | int data = inputStream.read();
112 | // if this is the end of the stream
113 | if (data == EOF) {
114 | // if we've taken some input already
115 | if (inputTaken) {
116 | // return that input
117 | return baos.toString(UTF_8);
118 | } else {
119 | // otherwise return null
120 | return null;
121 | }
122 | // otherwise, if this is a CR
123 | } else if (data == CR) {
124 | // it may be the end of a line
125 | lastWasCarriageReturn = true;
126 | // otherwise, if this is a LF
127 | } else if (data == LF) {
128 | // if we did follow a CR
129 | if (lastWasCarriageReturn) {
130 | // then this is the end of a line
131 | lastWasCarriageReturn = false;
132 | return baos.toString(UTF_8);
133 | } else {
134 | inputTaken = true;
135 | lastWasCarriageReturn = false;
136 | baos.write(LF);
137 | }
138 | // otherwise...
139 | } else {
140 | // if the last thing was a carriage return
141 | if (lastWasCarriageReturn) {
142 | // write that CR to our line
143 | baos.write(CR);
144 | }
145 | // add the data we just read to the line
146 | inputTaken = true;
147 | lastWasCarriageReturn = false;
148 | baos.write(data);
149 | }
150 | }
151 | }
152 |
153 | /**
154 | * InputStream to be decorated by this LineInputStream. This reference
155 | * is provided at construction time.
156 | */
157 | private final InputStream inputStream;
158 |
159 | /**
160 | * Flag: Is the last character we processed a Carriage Return (Octet 13)?
161 | * true: Yes, the last character was a CR
162 | * false: No, the last character was not a CR
163 | */
164 | private boolean lastWasCarriageReturn = false;
165 | }
166 |
--------------------------------------------------------------------------------
/remotelogcat/src/main/java/com/zzzmode/android/server/NetworkUtils.java:
--------------------------------------------------------------------------------
1 | package com.zzzmode.android.server;
2 |
3 | import java.net.*;
4 | import java.util.Enumeration;
5 | import java.util.List;
6 |
7 | /**
8 | * Created by zl on 16/1/26.
9 | */
10 | public class NetworkUtils {
11 |
12 | public static int prefixLengthToNetmaskInt(int prefixLength)
13 | throws IllegalArgumentException {
14 | if (prefixLength < 0 || prefixLength > 32) {
15 | throw new IllegalArgumentException("Invalid prefix length (0 <= prefix <= 32)");
16 | }
17 | int value = 0xffffffff << (32 - prefixLength);
18 | return Integer.reverseBytes(value);
19 | }
20 |
21 | public static int netmaskIntToPrefixLength(int netmask) {
22 | return Integer.bitCount(netmask);
23 | }
24 |
25 | public static int inetAddressToInt(Inet4Address inetAddr)
26 | throws IllegalArgumentException {
27 | byte [] addr = inetAddr.getAddress();
28 | return ((addr[3] & 0xff) << 24) | ((addr[2] & 0xff) << 16) |
29 | ((addr[1] & 0xff) << 8) | (addr[0] & 0xff);
30 | }
31 |
32 |
33 | public static InetAddress intToInetAddress(int hostAddress) {
34 | byte[] addressBytes = { (byte)(0xff & hostAddress),
35 | (byte)(0xff & (hostAddress >> 8)),
36 | (byte)(0xff & (hostAddress >> 16)),
37 | (byte)(0xff & (hostAddress >> 24)) };
38 | try {
39 | return InetAddress.getByAddress(addressBytes);
40 | } catch (UnknownHostException e) {
41 | e.printStackTrace();
42 | }
43 | return null;
44 | }
45 |
46 | public static int[] intToArray(int i) {
47 | return new int[]{i & 255, i >> 8 & 255, i >> 16 & 255, i >> 24 & 255};
48 | }
49 |
50 |
51 | public static InetAddress getLocalHostLANAddress() {
52 | try {
53 | InetAddress candidateAddress = null;
54 | for (Enumeration ifaces = NetworkInterface.getNetworkInterfaces(); ifaces.hasMoreElements();) {
55 | NetworkInterface iface = (NetworkInterface) ifaces.nextElement();
56 |
57 | for (Enumeration inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements();) {
58 | InetAddress inetAddr = (InetAddress) inetAddrs.nextElement();
59 | if (inetAddr instanceof Inet4Address && !inetAddr.isLoopbackAddress()) {
60 |
61 | if (inetAddr.isSiteLocalAddress()) {
62 | return inetAddr;
63 | }
64 | else if (candidateAddress == null) {
65 | candidateAddress = inetAddr;
66 | }
67 | }
68 | }
69 | }
70 | if (candidateAddress != null) {
71 | return candidateAddress;
72 | }
73 | return InetAddress.getLocalHost();
74 | }
75 | catch (Exception e) {
76 | e.printStackTrace();
77 | }
78 |
79 | return null;
80 | }
81 |
82 |
83 |
84 | public static int getNetworkPrefixLength(InetAddress address){
85 | try {
86 | final NetworkInterface networkInterface = NetworkInterface.getByInetAddress(address);
87 | final List interfaceAddresses = networkInterface.getInterfaceAddresses();
88 | for (InterfaceAddress addr:interfaceAddresses){
89 | int len=addr.getNetworkPrefixLength();
90 | if(len > 0 && len < 32){
91 | return len;
92 | }
93 | }
94 | } catch (SocketException e) {
95 | e.printStackTrace();
96 | }
97 | return 0;
98 | }
99 | }
--------------------------------------------------------------------------------
/remotelogcat/src/main/java/com/zzzmode/android/server/WebSocket.java:
--------------------------------------------------------------------------------
1 | package com.zzzmode.android.server;
2 |
3 | import java.io.IOException;
4 |
5 | /**
6 | * Created by zl on 16/1/31.
7 | */
8 | public interface WebSocket {
9 | public static final String REQUEST_LINE = "REQUEST_LINE";
10 |
11 | public static final int LENGTH_16 = 0x7E;
12 |
13 | public static final int LENGTH_16_MIN = 126;
14 |
15 |
16 | public static final int LENGTH_64 = 0x7F;
17 |
18 |
19 | public static final int LENGTH_64_MIN = 0x10000;
20 |
21 |
22 | public static final int MASK_HIGH_WORD_HIGH_BYTE_NO_SIGN = 0x7f000000;
23 |
24 |
25 | public static final int MASK_HIGH_WORD_LOW_BYTE = 0x00ff0000;
26 |
27 |
28 | public static final int MASK_LOW_WORD_HIGH_BYTE = 0x0000ff00;
29 |
30 |
31 | public static final int MASK_LOW_WORD_LOW_BYTE = 0x000000ff;
32 |
33 |
34 | public static final int OCTET_ONE = 8;
35 |
36 |
37 | public static final int OCTET_TWO = 16;
38 |
39 |
40 | public static final int OCTET_THREE = 24;
41 |
42 |
43 | public static final int OPCODE_FRAME_BINARY = 0x82;
44 |
45 |
46 | public static final int OPCODE_FRAME_CLOSE = 0x88;
47 |
48 |
49 | public static final int OPCODE_FRAME_PONG = 0x8A;
50 |
51 |
52 | public static final int OPCODE_FRAME_TEXT = 0x81;
53 |
54 | public static final String WEBSOCKET_ACCEPT_UUID =
55 | "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
56 |
57 |
58 | public static final String DEFAULT_CHARSET = "UTF-8";
59 |
60 |
61 | void send(byte[] bytes) throws IOException;
62 | void send(String text)throws IOException;
63 | byte[] readFrame()throws IOException;
64 | void close() throws IOException;
65 | boolean isClosed();
66 | boolean isConnected();
67 |
68 | void setWebSocketCallback(WebSocketCallback callback);
69 |
70 | public interface WebSocketCallback{
71 | void onReceivedFrame(byte[] bytes);
72 | void onClosed();
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/remotelogcat/src/main/java/com/zzzmode/android/server/WebSocketImpl.java:
--------------------------------------------------------------------------------
1 | package com.zzzmode.android.server;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.io.OutputStream;
6 | import java.io.UnsupportedEncodingException;
7 | import java.net.Socket;
8 | import java.util.Map;
9 |
10 | /**
11 | * Created by zl on 16/1/31.
12 | */
13 | class WebSocketImpl implements WebSocket {
14 |
15 | private Socket mSocket;
16 | private OutputStream outputStream;
17 | private InputStream inputStream;
18 |
19 | private boolean handshakeComplete = false;
20 | private boolean closed = false;
21 | private boolean closeSent = false;
22 |
23 | private WebSocketCallback mCallback;
24 |
25 | public WebSocketImpl(Socket socket,Map headers) throws IOException {
26 | this.mSocket = socket;
27 | init(headers);
28 | }
29 |
30 | private void init(Map headers) throws IOException {
31 | inputStream = mSocket.getInputStream();
32 | outputStream = mSocket.getOutputStream();
33 | shakeHands(headers);
34 | }
35 |
36 | //先处理websocket握手
37 | private void shakeHands(Map headers) throws IOException {
38 | //Map headers = parseHeader(inputStream);
39 | String requestLine = headers.get(REQUEST_LINE);
40 |
41 | handshakeComplete = checkStartsWith(requestLine, "GET /")
42 | && checkContains(requestLine, "HTTP/")
43 | && headers.get("Host") != null
44 | && checkContains(headers.get("upgrade"), "websocket")
45 | && checkContains(headers.get("connection"), "Upgrade")
46 | && "13".equals(headers.get("sec-websocket-version"))
47 | && headers.get("sec-websocket-key") != null;
48 | String nonce = headers.get("sec-websocket-key");
49 | //根据客户端请求的seckey生成一个acceptkey,
50 | // 具体做法是先提取seckey+[websocket公开的一个uuid,这个uuid是固定的]
51 | // 拼接成一个字符串计算sha1值
52 |
53 | if (handshakeComplete) {
54 | byte[] nonceBytes = Base64.decode(nonce);
55 | if (nonceBytes == null ||nonceBytes.length != 16) {
56 | handshakeComplete = false;
57 | }
58 | }
59 | // if we have met all the requirements
60 | if (handshakeComplete) {
61 | //返回请求头,表示握手完成,之后每次以frame为单位传输数据
62 | outputStream.write(asUTF8("HTTP/1.1 101 Switching Protocols\r\n"));
63 | outputStream.write(asUTF8("Upgrade: websocket\r\n"));
64 | outputStream.write(asUTF8("Connection: upgrade\r\n"));
65 | outputStream.write(asUTF8("Sec-WebSocket-Accept: "));
66 | byte[] hashByte = EncodeUtils.sha1(nonce, WEBSOCKET_ACCEPT_UUID);
67 | String acceptKey = Base64.encode(hashByte);
68 | outputStream.write(asUTF8(acceptKey));
69 | outputStream.write(asUTF8("\r\n\r\n"));
70 | }
71 |
72 | }
73 |
74 | private void readFully(byte[] b) throws IOException {
75 | int readen = 0;
76 | while (readen < b.length) {
77 | int r = inputStream.read(b, readen, b.length - readen);
78 | if (r == -1)
79 | break;
80 | readen += r;
81 | }
82 | }
83 |
84 | @Override
85 | public byte[] readFrame() throws IOException {
86 |
87 | int opcode = inputStream.read();
88 | //boolean whole = (opcode & 0b10000000) !=0;
89 | opcode = opcode & 0xF;
90 |
91 | if (opcode != 1) {
92 | finshSocket();
93 | throw new IOException("Wrong opcode: " + opcode);
94 | }
95 | //读取消息内容长度的有多种情况
96 | int len = inputStream.read();
97 | boolean encoded = (len >= 128);
98 |
99 | if (encoded) {
100 | len -= 128;
101 | }
102 |
103 | if (len == 127) {
104 | len = (inputStream.read() << 16) | (inputStream.read() << 8) | inputStream.read();
105 | } else if (len == 126) {
106 | len = (inputStream.read() << 8) | inputStream.read();
107 | }
108 |
109 | byte[] key = null;
110 |
111 | if (encoded) {
112 | key = new byte[4];
113 | readFully(key);
114 | }
115 |
116 | byte[] frame = new byte[len];
117 |
118 | readFully(frame);
119 |
120 | if (encoded) {
121 | for (int i = 0; i < frame.length; i++) {
122 | frame[i] = (byte) (frame[i] ^ key[i % 4]);
123 | }
124 | }
125 | if (mCallback != null) {
126 | mCallback.onReceivedFrame(frame);
127 | }
128 | return frame;
129 | }
130 |
131 |
132 | private void writeData(byte[] bytes, int opcode) throws IOException {
133 | try {
134 | int binLength = bytes.length;
135 | outputStream.write(opcode);
136 | if (binLength < LENGTH_16_MIN) {
137 | outputStream.write(binLength);
138 | } else if (binLength < LENGTH_64_MIN) {
139 | outputStream.write(LENGTH_16);
140 | outputStream.write(
141 | (binLength & MASK_LOW_WORD_HIGH_BYTE) >> OCTET_ONE);
142 | outputStream.write(binLength & MASK_LOW_WORD_LOW_BYTE);
143 | } else {
144 | outputStream.write(LENGTH_64);
145 | outputStream.write(0x00);
146 | outputStream.write(0x00);
147 | outputStream.write(0x00);
148 | outputStream.write(0x00);
149 | outputStream.write(
150 | (binLength & MASK_HIGH_WORD_HIGH_BYTE_NO_SIGN) >> OCTET_THREE);
151 | outputStream.write(
152 | (binLength & MASK_HIGH_WORD_LOW_BYTE) >> OCTET_TWO);
153 | outputStream.write(
154 | (binLength & MASK_LOW_WORD_HIGH_BYTE) >> OCTET_ONE);
155 | outputStream.write(binLength & MASK_LOW_WORD_LOW_BYTE);
156 | }
157 | outputStream.write(bytes);
158 | } catch (IOException e) {
159 | finshSocket();
160 | throw e;
161 | }
162 | }
163 |
164 | public final void writeClose() throws IOException {
165 | if (!closeSent) {
166 | closeSent = true;
167 | outputStream.write(new byte[]{
168 | (byte) OPCODE_FRAME_CLOSE, (byte) 0x00
169 | });
170 | }
171 | }
172 |
173 |
174 | @Override
175 | public void send(byte[] bytes) throws IOException {
176 | writeData(bytes, OPCODE_FRAME_BINARY);
177 | }
178 |
179 | @Override
180 | public void send(String text) throws IOException {
181 | byte[] utfBytes = asUTF8(text);
182 | writeData(utfBytes, OPCODE_FRAME_TEXT);
183 | }
184 |
185 | @Override
186 | public void close() {
187 | finshSocket();
188 | }
189 |
190 | public boolean isClosed() {
191 | return closed || closeSent;
192 | }
193 |
194 | @Override
195 | public boolean isConnected() {
196 | return handshakeComplete;
197 | }
198 |
199 | @Override
200 | public void setWebSocketCallback(WebSocketCallback callback) {
201 | mCallback = callback;
202 | }
203 |
204 |
205 | public final void finshSocket() {
206 | closed = true;
207 | handshakeComplete = false;
208 |
209 | if (mCallback != null) {
210 | mCallback.onClosed();
211 | }
212 |
213 | try {
214 | if (inputStream != null) {
215 | inputStream.close();
216 | }
217 | } catch (IOException e) {
218 | e.printStackTrace();
219 | }
220 | try {
221 | if (outputStream != null) {
222 | outputStream.close();
223 | }
224 | } catch (IOException e) {
225 | e.printStackTrace();
226 | }
227 | try {
228 | if (mSocket != null) {
229 | mSocket.close();
230 | }
231 | } catch (IOException e) {
232 | e.printStackTrace();
233 | }
234 | }
235 |
236 |
237 |
238 | public static byte[] asUTF8(final String s) {
239 | try {
240 | return s.getBytes(DEFAULT_CHARSET);
241 | } catch (UnsupportedEncodingException e) {
242 | e.printStackTrace();
243 | }
244 | return new byte[0];
245 | }
246 |
247 | public static boolean checkContains(final String s1, final String s2) {
248 | return s1 != null && s1.contains(s2);
249 | }
250 |
251 | public static boolean checkStartsWith(final String s1, final String s2) {
252 | return s1 != null && s1.startsWith(s2);
253 | }
254 |
255 | }
256 |
--------------------------------------------------------------------------------
/remotelogcat/src/main/java/com/zzzmode/android/server/WebSocketServer.java:
--------------------------------------------------------------------------------
1 | package com.zzzmode.android.server;
2 |
3 | import android.util.Log;
4 |
5 | import com.zzzmode.android.internel.ThreadUtil;
6 |
7 | import java.io.IOException;
8 | import java.io.InputStream;
9 | import java.lang.ref.WeakReference;
10 | import java.net.InetAddress;
11 | import java.net.ServerSocket;
12 | import java.net.Socket;
13 | import java.util.ArrayList;
14 | import java.util.Collections;
15 | import java.util.HashMap;
16 | import java.util.List;
17 | import java.util.Map;
18 |
19 | /**
20 | * Created by zl on 16/1/31.
21 | */
22 | public class WebSocketServer {
23 |
24 | private ServerSocket mServerSocket;
25 | private WebSocketServerCallback mCallback;
26 | private int port=0;
27 | private volatile boolean isActive=false;
28 |
29 | private boolean wsCanRead=true;
30 |
31 | private List> mListWebSocket=new ArrayList>();
32 | private String mWebSocketPrefix;
33 |
34 | public WebSocketServer(int port,String prefix) throws IOException {
35 | this.port=port;
36 | mWebSocketPrefix=prefix;
37 | }
38 |
39 | public void setWsCanRead(boolean b){
40 | wsCanRead=b;
41 | }
42 |
43 | public void setWebSocketServerCallback(WebSocketServerCallback mCallback) {
44 | this.mCallback = mCallback;
45 | }
46 |
47 |
48 | public void start(){
49 | if(isActive){
50 | return;
51 | }
52 |
53 | ThreadUtil.getExecutor().execute(new Runnable() {
54 | @Override
55 | public void run() {
56 | innerStart();
57 | }
58 | });
59 |
60 | }
61 |
62 | private void innerStart(){
63 | try {
64 | mServerSocket=new ServerSocket(port);
65 |
66 | final InetAddress hostLANAddress = NetworkUtils.getLocalHostLANAddress();
67 | if(hostLANAddress != null){
68 | Log.e("WebSocketServer", "Server start success! Connection IP : "+hostLANAddress.getHostAddress()+":"+port);
69 | }else {
70 | Log.e("WebSocketServer", "Server start success! But unknow local ip address !");
71 | }
72 |
73 | isActive=true;
74 | while (isActive){
75 | handleSocket(mServerSocket.accept());
76 | }
77 | } catch (Exception e) {
78 | if(mCallback != null){
79 | mCallback.onClosed();
80 | }
81 |
82 | e.printStackTrace();
83 | }
84 | }
85 |
86 |
87 | public void stop(){
88 | isActive=false;
89 | try {
90 | for (WeakReference webSockets:mListWebSocket){
91 | try{
92 | WebSocket socket = webSockets.get();
93 | if(socket != null){
94 | socket.close();
95 | }
96 | }catch (Exception e){
97 | }
98 | }
99 |
100 | if(mCallback != null){
101 | mCallback.onClosed();
102 | }
103 |
104 | if(mServerSocket!=null){
105 | mServerSocket.close();
106 | }
107 |
108 | } catch (IOException e) {
109 | e.printStackTrace();
110 | }
111 | }
112 |
113 | private void handleSocket(final Socket socket) {
114 | if(!isActive){
115 | return;
116 | }
117 | boolean handled=false;
118 | try {
119 | boolean isHttp=false;
120 | boolean isWebsocket=false;
121 | final Map headerMap=parseHeader(socket.getInputStream());
122 | String s = headerMap.get(WebSocket.REQUEST_LINE);
123 | if(s!= null && s.startsWith("GET /")){
124 | isHttp=true;
125 | isWebsocket=s.startsWith("GET "+mWebSocketPrefix);
126 | }
127 | final Map headers=headerMap;
128 |
129 | if(isWebsocket){
130 |
131 | ThreadUtil.getExecutor().execute(new Runnable() {
132 | @Override
133 | public void run() {
134 | handleAsWebsocket(socket,headers);
135 | }
136 | });
137 | handled=true;
138 | }else if(isHttp){
139 | ThreadUtil.getExecutor().execute(new Runnable() {
140 | @Override
141 | public void run() {
142 | handleAsHttp(socket,headers);
143 | }
144 | });
145 | handled=true;
146 | }
147 |
148 | } catch (IOException e) {
149 | e.printStackTrace();
150 | }finally {
151 | try {
152 | //未知协议,不处理
153 | if (!handled) {
154 | socket.close();
155 | }
156 | } catch (IOException e) {
157 | e.printStackTrace();
158 | }
159 | }
160 | }
161 |
162 |
163 | private void handleAsWebsocket(final Socket socket, final Map headerMap) {
164 |
165 | try {
166 |
167 | WebSocket webSocket = new WebSocketImpl(socket, headerMap);
168 | mListWebSocket.add(new WeakReference(webSocket));
169 |
170 | if (mCallback != null) {
171 | mCallback.onConnected(webSocket);
172 | }
173 |
174 | while (wsCanRead && !webSocket.isClosed()) {
175 | try {
176 | webSocket.readFrame();
177 | } catch (IOException e) {
178 | e.printStackTrace();
179 | }
180 | }
181 | } catch (IOException e) {
182 | e.printStackTrace();
183 | }
184 |
185 | }
186 |
187 |
188 | private void handleAsHttp(final Socket socket, final Map headerMap) {
189 |
190 | try {
191 | HttpResponse.handle(socket, headerMap);
192 | socket.getOutputStream().flush();
193 | } catch (IOException e) {
194 | e.printStackTrace();
195 | } finally {
196 | try {
197 | if (socket != null) {
198 | socket.close();
199 | }
200 | } catch (IOException e) {
201 | e.printStackTrace();
202 | }
203 | }
204 | }
205 |
206 |
207 | public static Map parseHeader(InputStream inputStream) {
208 | LineInputStream lis = new LineInputStream(inputStream);
209 | Map headerMap = new HashMap();
210 | try {
211 | String line = lis.readLine();
212 | while (line != null && line.isEmpty()) {
213 | line = lis.readLine();
214 | }
215 | headerMap.put(WebSocket.REQUEST_LINE, line);
216 | line = lis.readLine();
217 | while (line != null && !line.isEmpty()) {
218 | int firstColonPos = line.indexOf(":");
219 | if (firstColonPos > 0) {
220 | String key = line.substring(0, firstColonPos).trim();
221 | int length = line.length();
222 | String value = line.substring(firstColonPos + 1, length).trim();
223 | if (!key.isEmpty() && !value.isEmpty()) {
224 | headerMap.put(key, value);
225 | headerMap.put(key.toLowerCase(), value);
226 | }
227 | }
228 | line = lis.readLine();
229 | }
230 | } catch (IOException e) {
231 | e.printStackTrace();
232 | }
233 | return Collections.unmodifiableMap(headerMap);
234 | }
235 |
236 | public interface WebSocketServerCallback{
237 | void onConnected(WebSocket webSocket);
238 | void onClosed();
239 | }
240 | }
241 |
--------------------------------------------------------------------------------
/remotelogcat/src/test/java/com/zzzmode/android/remotelogcat/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.zzzmode.android.remotelogcat;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/remotelogcatsample/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/remotelogcatsample/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 27
5 | buildToolsVersion "27.0.3"
6 |
7 | defaultConfig {
8 | applicationId "com.zzzmode.android.remotelogcatsample"
9 | minSdkVersion 15
10 | targetSdkVersion 27
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | implementation fileTree(dir: 'libs', include: ['*.jar'])
24 | testImplementation 'junit:junit:4.12'
25 | implementation 'com.android.support:appcompat-v7:23.1.1'
26 | implementation project(':remotelogcat')
27 | }
28 |
--------------------------------------------------------------------------------
/remotelogcatsample/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/zl/develop/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/remotelogcatsample/src/androidTest/java/com/zzzmode/android/remotelogcatsample/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.zzzmode.android.remotelogcatsample;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/remotelogcatsample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/remotelogcatsample/src/main/java/com/zzzmode/android/remotelogcatsample/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.zzzmode.android.remotelogcatsample;
2 |
3 | import android.os.Bundle;
4 | import android.os.SystemClock;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.util.Log;
7 |
8 | import com.zzzmode.android.remotelogcat.LogcatRunner;
9 |
10 | import java.io.IOException;
11 | import java.util.Random;
12 |
13 | public class MainActivity extends AppCompatActivity {
14 |
15 | private static final String TAG = "MainActivity";
16 |
17 | private boolean logRunning = false;
18 |
19 | @Override
20 | protected void onCreate(Bundle savedInstanceState) {
21 | super.onCreate(savedInstanceState);
22 | setContentView(R.layout.activity_main);
23 |
24 | try {
25 | LogcatRunner.getInstance()
26 | .config(LogcatRunner.LogConfig.builder()
27 | .setWsCanReceiveMsg(false)
28 | .write2File(true))
29 | .with(getApplicationContext())
30 | .start();
31 | } catch (IOException e) {
32 | e.printStackTrace();
33 | }
34 | }
35 |
36 | private void startLog() {
37 | if (logRunning) {
38 | return;
39 | }
40 | logRunning = true;
41 | new Thread(new Runnable() {
42 | @Override
43 | public void run() {
44 | Random random = new Random();
45 | int i = 0;
46 | while (logRunning) {
47 | if (random.nextBoolean()) {
48 | Log.e("testlog", "run --> " + i);
49 | } else {
50 | Log.w("testlog", "run --> " + i);
51 |
52 | }
53 | // test();test();test();test();test();test();test();
54 | // test();test();test();test();test();test();test();
55 | SystemClock.sleep(random.nextInt(5000) + 100);
56 | i++;
57 | }
58 | }
59 | }).start();
60 |
61 | }
62 |
63 | private static void test(){
64 | try {
65 | throw new RuntimeException("----");
66 | }catch (Exception e){
67 | e.printStackTrace();
68 | }
69 | }
70 |
71 | @Override
72 | protected void onStart() {
73 | super.onStart();
74 | startLog();
75 | }
76 |
77 | @Override
78 | protected void onStop() {
79 | super.onStop();
80 | logRunning = false;
81 | }
82 |
83 |
84 | @Override
85 | protected void onDestroy() {
86 | super.onDestroy();
87 | LogcatRunner.getInstance().stop();
88 |
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/remotelogcatsample/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/remotelogcatsample/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/8enet/RemoteLogcatViewer/2c0a06a5ff3c2702854699b7db2d1d4b875b1697/remotelogcatsample/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/remotelogcatsample/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/8enet/RemoteLogcatViewer/2c0a06a5ff3c2702854699b7db2d1d4b875b1697/remotelogcatsample/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/remotelogcatsample/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/8enet/RemoteLogcatViewer/2c0a06a5ff3c2702854699b7db2d1d4b875b1697/remotelogcatsample/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/remotelogcatsample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/8enet/RemoteLogcatViewer/2c0a06a5ff3c2702854699b7db2d1d4b875b1697/remotelogcatsample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/remotelogcatsample/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/8enet/RemoteLogcatViewer/2c0a06a5ff3c2702854699b7db2d1d4b875b1697/remotelogcatsample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/remotelogcatsample/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
--------------------------------------------------------------------------------
/remotelogcatsample/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/remotelogcatsample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/remotelogcatsample/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 16dp
6 |
7 |
--------------------------------------------------------------------------------
/remotelogcatsample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | RemoteLogcatSample
3 | MainActivity
4 |
5 |
--------------------------------------------------------------------------------
/remotelogcatsample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/remotelogcatsample/src/test/java/com/zzzmode/android/remotelogcatsample/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.zzzmode.android.remotelogcatsample;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':remotelogcat', ':remotelogcatsample'
2 |
--------------------------------------------------------------------------------