Java tutorial
/* * * MIT License * * Copyright (c) 2017 https://blog.yeetor.com * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * */ package com.yeetor.server; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.yeetor.adb.AdbDevice; import com.yeetor.adb.AdbServer; import com.yeetor.adb.AdbUtils; import com.yeetor.adb.IAdbServerListener; import com.yeetor.androidcontrol.client.LocalClient; import com.yeetor.minicap.Banner; import com.yeetor.minicap.Minicap; import com.yeetor.minicap.MinicapListener; import com.yeetor.minicap.ScreencapBase; import com.yeetor.minitouch.Minitouch; import com.yeetor.minitouch.MinitouchListener; import com.yeetor.protocol.BinaryProtocol; import com.yeetor.protocol.TextProtocol; import com.yeetor.server.handler.IWebsocketEvent; import com.yeetor.server.handler.WSHandler; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import org.apache.commons.lang3.ArrayUtils; import org.apache.log4j.Logger; import javax.xml.soap.Text; import java.lang.ref.PhantomReference; import java.util.HashMap; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public class WSServer implements IWebsocketEvent, MinicapListener, MinitouchListener, IAdbServerListener { private static Logger logger = Logger.getLogger(WSServer.class); /** * M_WAIT? */ AdbDevice bindedDevice = null; /** * Minicap */ Minicap capService = null; /** * Minitouch */ Minitouch eventService = null; Channel channel = null; /** * TODO ?? */ static final int DATA_TIMEOUT = 100; //ms private boolean isWaitting = false; private BlockingQueue<ImageData> dataQueue = new LinkedBlockingQueue<ImageData>(); WSServer() { AdbServer.server().addListener(this); } @Override public void onConnect(ChannelHandlerContext ctx) { } @Override public void onDisconnect(ChannelHandlerContext ctx) { if (eventService != null) { eventService.kill(); } if (capService != null) { capService.kill(); } } @Override public void onTextMessage(ChannelHandlerContext ctx, String text) { TextProtocol protocol = TextProtocol.ParseWithString(text); switch (protocol.getProtocolHeader()) { case TextProtocol.Header.M_DEVICES: onM_DEVICES(ctx, protocol); break; case TextProtocol.Header.M_WAIT: onM_WAIT(ctx, protocol); break; case TextProtocol.Header.M_START: onM_START(ctx, protocol); break; case TextProtocol.Header.M_WAITTING: onM_WAITTING(ctx, protocol); break; case TextProtocol.Header.M_TOUCH: onM_TOUCH(ctx, protocol); break; case TextProtocol.Header.M_KEYEVENT: onM_KEYEVENT(ctx, protocol); break; default: onInvalidProtocl(ctx, protocol); break; } } @Override public void onBinaryMessage(ChannelHandlerContext ctx, byte[] data) { } private void onM_DEVICES(ChannelHandlerContext ctx, TextProtocol protocol) { this.channel = ctx.channel(); String devicesJson = AdbUtils.devices2JSON(); TextProtocol p = TextProtocol.newProtocol(TextProtocol.Header.SM_DEVICES, devicesJson); sendProtocolResponse(p); } private void onM_WAIT(ChannelHandlerContext ctx, TextProtocol protocol) { this.channel = ctx.channel(); JSONObject obj = (JSONObject) JSON.parse(protocol.getProtocolBody()); String sn = obj.getString("sn"); bindedDevice = AdbServer.server().getDevice(sn); if (bindedDevice != null) { TextProtocol resPro = TextProtocol.newProtocol(TextProtocol.Header.SM_OPENED, ""); sendProtocolResponse(resPro); } else { // TODO ?? ctx.channel().close(); } } private void onM_START(ChannelHandlerContext ctx, TextProtocol protocol) { JSONObject obj = (JSONObject) JSON.parse(protocol.getProtocolBody()); String type = obj.getString("type"); if ("cap".equals(type)) { startCapService(obj); } else if ("event".equals(type)) { startEventService(); } } private void onM_WAITTING(ChannelHandlerContext ctx, TextProtocol protocol) { setWaitting(true); } private void onM_TOUCH(ChannelHandlerContext ctx, TextProtocol protocol) { eventService.sendEvent(protocol.getProtocolBody()); } private void onM_KEYEVENT(ChannelHandlerContext ctx, TextProtocol protocol) { try { int key = Integer.parseInt(protocol.getProtocolBody()); eventService.sendKeyEvent(key); } catch (Exception e) { e.printStackTrace(); } } private void onInvalidProtocl(ChannelHandlerContext ctx, TextProtocol protocol) { logger.warn("invalid protocol: " + protocol.getProtocolHeader()); } private void startCapService(JSONObject jsonObject) { if (capService != null) { capService.kill(); } Minicap cap = new Minicap(bindedDevice); this.capService = cap; cap.addEventListener(this); // ? Float scale = 0.3f; Integer rotate = 0; JSONObject obj = (JSONObject) jsonObject.get("config"); if (obj != null) { scale = obj.getFloat("scale"); rotate = obj.getInteger("rotate"); } cap.start(scale, rotate); } private void startEventService() { if (eventService != null) { eventService.kill(); } Minitouch minitouch = new Minitouch(bindedDevice); this.eventService = minitouch; minitouch.addEventListener(this); minitouch.start(); } private void sendProtocolResponse(TextProtocol protocol) { String text = String.format("%s://%s", protocol.getProtocolHeader(), protocol.getProtocolBody()); logger.info("ws text to: " + text); channel.writeAndFlush(new TextWebSocketFrame(text)); } /*****************************************************************************/ @Override public void onStartup(Minicap minicap, boolean success) { HashMap<String, String> map = new HashMap<>(); map.put("type", "cap"); map.put("stat", "open"); TextProtocol protocol = TextProtocol.newProtocol(TextProtocol.Header.SM_SERVICE_STATE, JSON.toJSONString(map)); sendProtocolResponse(protocol); } @Override public void onClose(Minicap minicap) { HashMap<String, String> map = new HashMap<>(); map.put("type", "cap"); map.put("stat", "close"); TextProtocol protocol = TextProtocol.newProtocol(TextProtocol.Header.SM_SERVICE_STATE, JSON.toJSONString(map)); sendProtocolResponse(protocol); } @Override public void onBanner(Minicap minicap, Banner banner) { } @Override public void onJPG(Minicap minicap, byte[] data) { if (isWaitting) { if (dataQueue.size() > 0) { dataQueue.add(new ImageData(data)); // ImageData d = getUsefulImage(); sendImage(d.data); } else { sendImage(data); } isWaitting = false; } else { clearObsoleteImage(); dataQueue.add(new ImageData(data)); } } public void setWaitting(boolean waitting) { isWaitting = waitting; trySendImage(); } private void trySendImage() { ImageData d = getUsefulImage(); if (d != null) { isWaitting = false; sendImage(d.data); } } private void clearObsoleteImage() { ImageData d = dataQueue.peek(); long curTS = System.currentTimeMillis(); while (d != null) { if (curTS - d.timesp < DATA_TIMEOUT) { dataQueue.poll(); d = dataQueue.peek(); } else { break; } } } private ImageData getUsefulImage() { long curTS = System.currentTimeMillis(); // ImageData d = null; while (true) { d = dataQueue.poll(); // ??? if (d == null || curTS - d.timesp < DATA_TIMEOUT || dataQueue.size() == 0) { break; } } return d; } public static byte[] toLH(int n) { byte[] b = new byte[4]; b[0] = (byte) (n & 0xff); b[1] = (byte) (n >> 8 & 0xff); b[2] = (byte) (n >> 16 & 0xff); b[3] = (byte) (n >> 24 & 0xff); return b; } public static byte[] toHH(int n) { byte[] b = new byte[4]; b[3] = (byte) (n & 0xff); b[2] = (byte) (n >> 8 & 0xff); b[1] = (byte) (n >> 16 & 0xff); b[0] = (byte) (n >> 24 & 0xff); return b; } private void sendImage(byte[] data) { byte[] head = new byte[2]; head[0] = (BinaryProtocol.Header.SM_JPG) & 0xff; head[1] = (BinaryProtocol.Header.SM_JPG >> 8) & 0xff; int len = data.length; byte[] lenbuf = new byte[4]; lenbuf[0] = (byte) ((len) & 0xff); lenbuf[1] = (byte) ((len >> 8) & 0xff); lenbuf[2] = (byte) ((len >> 16) & 0xff); lenbuf[3] = (byte) ((len >> 24) & 0xff); byte[] d = ArrayUtils.addAll(ArrayUtils.addAll(head, lenbuf), data); channel.writeAndFlush(new BinaryWebSocketFrame(Unpooled.copiedBuffer(d))); } /*********************************************************************************/ @Override public void onStartup(Minitouch minitouch, boolean success) { HashMap<String, String> map = new HashMap<>(); map.put("type", "event"); map.put("stat", "open"); TextProtocol protocol = TextProtocol.newProtocol(TextProtocol.Header.SM_SERVICE_STATE, JSON.toJSONString(map)); sendProtocolResponse(protocol); } @Override public void onClose(Minitouch minitouch) { HashMap<String, String> map = new HashMap<>(); map.put("type", "event"); map.put("stat", "close"); TextProtocol protocol = TextProtocol.newProtocol(TextProtocol.Header.SM_SERVICE_STATE, JSON.toJSONString(map)); sendProtocolResponse(protocol); } /*********************************************************************************/ @Override public void onAdbDeviceConnected(AdbDevice device) { if (this.channel != null) { String devicesJson = AdbUtils.devices2JSON(); TextProtocol p = TextProtocol.newProtocol(TextProtocol.Header.SM_DEVICES, devicesJson); sendProtocolResponse(p); } } @Override public void onAdbDeviceDisConnected(AdbDevice device) { if (this.channel != null) { String devicesJson = AdbUtils.devices2JSON(); TextProtocol p = TextProtocol.newProtocol(TextProtocol.Header.SM_DEVICES, devicesJson); sendProtocolResponse(p); } } /****************************************************************************/ public static class ImageData { ImageData(byte[] d) { timesp = System.currentTimeMillis(); data = d; } long timesp; byte[] data; } }