// 客户端部分
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Android远程控制@hexf00</title>
<script src="{{screen_uri}}/jquery.js"></script>
<style>
#screen {
position: relative;
}
#screen img {
position: absolute;
top: 0;
left: 0;
}
.hide {
width: 0;
height: 0;
}
#screen2 {
position: absolute;
}
#tool{
float:left
}
</style>
</head>
<body onload="bodyLoad()">
<div id="screen">
<img alt="屏幕" src="{{screen_uri}}/screen">
<img alt="屏幕" src="{{screen_uri}}/screen">
</div>
<div id="screen2">
</div>
<script>
var imgSrc = "{{screen_uri}}/screen?"
function getNext() {
$("#screen").append('<img onload="getNext();" alt="连接已经断开请重新连接" src="' + imgSrc + new Date().getTime() +
Math.random() + '">')
setTimeout(() => {
$("img")[0].remove()
}, 500)
}
var bodyLoad = () => {
$("#screen2").width($("img").width() + "px")
$("#screen2").height($("img").height() + "px")
getNext()
}
var isDown = false
var downEvent
$("#screen2").on("mousedown", (e) => {
isDown = true
downEvent = e
})
$("#screen2").on("mouseup", (e) => {
isDown = false
duration = Math.ceil(e.timeStamp - downEvent.timeStamp)
x1 = downEvent.offsetX * 2
y1 = downEvent.offsetY * 2
x2 = e.offsetX * 2
y2 = e.offsetY * 2
$.ajax({
url: "{{screen_uri}}/swipe",
type: "get",
dataType: "jsonp",
data: {
duration,
x1,
y1,
x2,
y2
}
});
})
</script>
</body>
</html>
// 服务端部分
# Activity
package com.example.chinaso.appcrawlermaster;
import android.app.Activity;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.NotificationCompat;
import android.view.View;
import android.content.Intent;
import android.widget.Button;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.ServiceConnection;
import android.content.ComponentName;
import android.widget.Toast;
import android.view.WindowManager;
import android.view.KeyEvent;
import android.util.DisplayMetrics;
import android.media.projection.MediaProjectionManager;
import android.app.NotificationManager;
import android.app.NotificationChannel;
import android.os.Build;
import java.net.*;
import java.util.*;
public class MainActivity extends Activity {
private Intent broadcast_intent = null;
private String local_ip;
// 与service通信有关
private Intent service_intent = null;
HttpServiceConn http_service_conn;
HttpService.ServiceBinder binder = null;
// 与截屏有关
private static final int REQUEST_MEDIA_PROJECTION = 1;
private int mScreenDensity;
private int mResultCode;
private int screen_width;
private int screen_height;
private Intent mResultData;
private MediaProjectionManager mMediaProjectionManager;
private boolean is_started_service = false;
// 与无障碍有关
private Vector<SwipeCommand> action_queue = new Vector<>();
private NotificationManager notification_manager = null;
NotificationCompat.Builder builder = null;
NotificationChannel channel = null;
// 无障碍动作执行线程
public class ActionThread extends Thread {
private int sleep_time;
private boolean is_running = true;
public ActionThread(int sleep_time) {
this.sleep_time = sleep_time;
}
@Override
public void run() {
try {
while(is_running) {
if (action_queue.size() > 0) {
SwipeCommand cmd = action_queue.remove(0);
BarrierFreeService.setCurrentCommand(cmd);
System.out.println("线程正在执行滑动命令: " + cmd.getDuration());
cmd = null;
notification_manager.notify(1, builder.build());
sleep(20);
notification_manager.cancel(1);
}
sleep(sleep_time);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void exit(boolean is_running) {
this.is_running = is_running;
}
}
ActionThread action_thread = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
local_ip = getIP();
notification_manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
getNotificationBuilder();
getNotificationChannel();
action_thread = new ActionThread(50);
action_thread.start();
initScreenCapture();
mMediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
if (is_started_service == false) {
startActivityForResult(
mMediaProjectionManager.createScreenCaptureIntent(),
REQUEST_MEDIA_PROJECTION);
is_started_service = true;
}
setOnClick();
initBroadCastIntent();
}
// 初始化截屏
public void initScreenCapture() {
WindowManager wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics metrics = new DisplayMetrics();
//wm.getDefaultDisplay().getMetrics(metrics);
wm.getDefaultDisplay().getRealMetrics(metrics);
System.out.println("手机物理宽高: " + metrics.widthPixels + ", " + metrics.heightPixels);
screen_width = metrics.widthPixels;
screen_height = metrics.heightPixels;
mScreenDensity = metrics.densityDpi;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_MEDIA_PROJECTION) {
if (resultCode != Activity.RESULT_OK) {
Toast.makeText(MainActivity.this, "user cancelled", Toast.LENGTH_SHORT).show();
return;
}
mResultCode = resultCode;
mResultData = data;
service_intent = new Intent(this, HttpService.class);
service_intent.putExtra("screen_density", mScreenDensity);
service_intent.putExtra("screen_width", screen_width);
service_intent.putExtra("screen_height", screen_height);
service_intent.putExtra("result_code", mResultCode);
System.out.println("Activity屏幕宽高: " + screen_width + ", " + screen_height);
// 启动 http service
startService(service_intent);
// 绑定 http service
http_service_conn = new HttpServiceConn();
Intent bind_intent = new Intent(this, HttpService.class);
bindService(bind_intent, http_service_conn, Context.BIND_AUTO_CREATE);
}
}
@Override
public void onDestroy() {
super.onDestroy();
action_thread.exit(false);
// 解绑Service
if (binder != null) {
unbindService(http_service_conn);
stopService(service_intent);
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode==KeyEvent.KEYCODE_BACK){
moveTaskToBack(true);
return false;
}
return super.onKeyDown(keyCode, event);
}
// 与service绑定绑定相关类
class HttpServiceConn implements ServiceConnection {
// 服务被绑定成功之后执行
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// IBinder service为onBind方法返回的Service实例
binder = (HttpService.ServiceBinder) service;
if (binder != null) {
System.out.println("binder不空!");
}
binder.setScreenCaptureIntent(mResultData);
binder.getService().setCallback(new HttpService.Callback() {
@Override
public void dispatchCommand(Command cmd) {
Message msg = new Message();
Bundle bundle = new Bundle();
SwipeCommand real_cmd = (SwipeCommand)cmd;
bundle.putString("tag", "swipe");
bundle.putInt("x1", real_cmd.getUpLeftX());
bundle.putInt("y1", real_cmd.getUpLeftY());
bundle.putInt("x2", real_cmd.getDownRightX());
bundle.putInt("y2", real_cmd.getDownRightY());
bundle.putInt("duration", real_cmd.getDuration());
msg.setData(bundle);
//发送通知
handler.sendMessage(msg);
}
});
}
@SuppressLint("HandlerLeak")
Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
Bundle bundle = msg.getData();
SwipeCommand cmd = null;
switch (bundle.getString("tag")) {
case "swipe":
int x1 = bundle.getInt("x1");
int y1 = bundle.getInt("y1");
int x2 = bundle.getInt("x2");
int y2 = bundle.getInt("y2");
int duration = bundle.getInt("duration");
cmd = new SwipeCommand("swipe", x1, y1, x2, y2, duration);
System.out.println("发送滑动命令到线程: " + x1 + ", " + y1 + ", " + x2 + ", " + y2 + ", " + duration);
action_queue.add(cmd);
break;
}
}
};
// 服务奔溃或者被杀掉执行
@Override
public void onServiceDisconnected(ComponentName name) {
binder = null;
}
}
// 获取本机IP地址
public String getIP(){
try {
for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
NetworkInterface intf = en.nextElement();
for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) {
InetAddress inetAddress = enumIpAddr.nextElement();
if (!inetAddress.isLoopbackAddress() && (inetAddress instanceof Inet4Address)) {
return inetAddress.getHostAddress().toString();
}
}
}
} catch (SocketException ex){
ex.printStackTrace();
}
return null;
}
// 初始化广播Intent
public void initBroadCastIntent() {
broadcast_intent = new Intent();
broadcast_intent.setAction("aaa");
// 突破 Android 7.0/8.0 中的隐式广播限制
broadcast_intent.addFlags(0x01000000);
}
// 广播自定义意图
public void broadcastIntent() {
sendBroadcast(broadcast_intent);
broadcast_intent.removeExtra("msg");
}
// 生成通知渠道
public void getNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
"com.chinaso.AppCrawlerMaster",
"app-crawler-client",
NotificationManager.IMPORTANCE_DEFAULT
);
notification_manager.createNotificationChannel(channel);
}
}
// 生成通知创建者
public void getNotificationBuilder() {
builder = new NotificationCompat.Builder(getBaseContext(), "com.chinaso.AppCrawlerMaster")
.setContentTitle("app-crawler-client")
.setAutoCancel(true)
.setTimeoutAfter(100)
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.ic_launcher_foreground);
}
// 为按钮绑定监听器
public void setOnClick() {
Button button_broadcast = findViewById(R.id.button_broadcast);
button_broadcast.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
broadcast_intent.putExtra("msg", "hello world");
broadcastIntent();
}
});
Button button_get_ip = findViewById(R.id.button_get_ip);
button_get_ip.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String ip;
ip = getIP();
Toast.makeText(MainActivity.this, "本机ip是: " + ip, Toast.LENGTH_LONG).show();
}
});
Button button_start_service = findViewById(R.id.button_start_service);
button_start_service.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent startIntent = new Intent(MainActivity.this, HttpService.class);
startService(startIntent);
}
});
Button button_stop_service = findViewById(R.id.button_stop_service);
button_stop_service.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent stopIntent = new Intent(MainActivity.this, HttpService.class);
stopService(stopIntent);
}
});
Button notification_send_service = findViewById(R.id.button_send_notification);
notification_send_service.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//notification_manager.notify(1, builder.build());
}
});
}
}
// 命令基类Bean
package com.example.chinaso.appcrawlermaster;
public class Command {
private String tag = null;
private String response = null;
private int id = 0;
public Command(String tag) {
this.tag = tag;
}
public String getTag() {
return this.tag;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getResponse() {
return response;
}
public void setResponse(String response) {
this.response = response;
}
}
// 截屏命令Bean
package com.example.chinaso.appcrawlermaster;
class ScreenCommand extends Command {
private byte[] bytes = null;
public ScreenCommand(String tag) {
super(tag);
}
public void setBytes(byte[] b) {
this.bytes = b;
}
public byte[] getBytes() {
return bytes;
}
}
// 滑动命令Bean
package com.example.chinaso.appcrawlermaster;
class SwipeCommand extends Command {
private int x1;
private int y1;
private int x2;
private int y2;
private int duration;
public SwipeCommand(String tag, int x1, int y1, int x2, int y2, int duration) {
super(tag);
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.duration = duration;
}
public int getUpLeftX() {
return x1;
}
public void setUpLeftX(int x1) {
this.x1 = x1;
}
public int getUpLeftY() {
return y1;
}
public void setUpLeftY(int y1) {
this.y1 = y1;
}
public int getDownRightX() {
return x2;
}
public void setDownRightX(int x2) {
this.x2 = x2;
}
public int getDownRightY() {
return y2;
}
public void setDownRightY(int y2) {
this.y2 = y2;
}
public int getDuration() {
return duration;
}
public void setDuration(int duration) {
this.duration = duration;
}
}
// http服务器
package com.example.chinaso.appcrawlermaster;
import java.util.*;
import java.io.IOException;
import org.nanohttpd.protocols.http.NanoHTTPD;
import org.nanohttpd.protocols.http.response.Response;
import org.nanohttpd.protocols.http.IHTTPSession;
import org.nanohttpd.protocols.http.response.Status;
import java.net.ServerSocket;
public class HttpServer extends NanoHTTPD {
private static final String TAG = "Http";
private static int command_id = 0;
private static int wait_res_sleep_time = 1;
private HttpService http_service;
public HttpServer(String host, int port) {
super(host, port);
}
@Override
public ServerSocket getMyServerSocket() {
return super.getMyServerSocket();
}
@Override
public Response serve(IHTTPSession session) {
try {
// 这句必须有,否则获取不到数据
session.parseBody(new HashMap<String, String>());
} catch (IOException e) {
System.out.println("HttpServer IOException");
e.printStackTrace();
} catch (ResponseException e) {
System.out.println("HttpServer ResponseException");
e.printStackTrace();
}
Response response = null;
String uri = session.getUri();
System.out.println("Uri is: " + uri);
// 获取当前命令标识
int id = updateId();
Command cmd;
String miniType = null;
if (uri.equals("/screen")) {
// 截图
cmd = new ScreenCommand("screen");
cmd.setId(id);
miniType = "image/jpeg";
} else if (uri.equals("/swipe")) {
// 滑动
Map<String, List<String>> params = session.getParameters();
int x1 = Integer.parseInt(params.get("x1").get(0));
int y1 = Integer.parseInt(params.get("y1").get(0));
int x2 = Integer.parseInt(params.get("x2").get(0));
int y2 = Integer.parseInt(params.get("y2").get(0));
int duration = Integer.parseInt(params.get("duration").get(0));
cmd = new SwipeCommand("swipe", x1, y1, x2, y2, duration);
cmd.setResponse("200");
cmd.setId(id);
System.out.println("滑动参数: " + x1 + ", " + y1 + ", " + x2 + ", " + y2 + ", " + duration);
} else if (uri.equals("/jquery.js")) {
cmd = new Command("jquery.js");
cmd.setId(id);
miniType = "application/x-javascript";
} else {
cmd = new Command("remote.html");
cmd.setId(id);
miniType = "text/html";
}
// 将命令加入命令接收队列
http_service.addCommand(cmd);
// 等待命令执行结果
if (cmd.getTag() != "screen") {
while(cmd.getResponse() == null) {
try{
Thread.sleep(wait_res_sleep_time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} else {
ScreenCommand screen_cmd = (ScreenCommand)cmd;
while(screen_cmd.getBytes() == null) {
try{
Thread.sleep(wait_res_sleep_time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
screen_cmd = null;
}
if (cmd.getTag() != "screen") {
System.out.println("响应长度: " + cmd.getResponse().length());
response = Response.newFixedLengthResponse(cmd.getResponse());
} else {
ScreenCommand screen_cmd = (ScreenCommand)cmd;
System.out.println("响应长度: " + screen_cmd.getBytes().length);
response = Response.newFixedLengthResponse(Status.OK, "image/jpeg", screen_cmd.getBytes());
screen_cmd = null;
}
response.setMimeType(miniType);
http_service.removeCommand(cmd);
return response;
//Map<String, List<String>> params = session.getParameters();
// 获取发送数据时的key
//String data = params.get("json").get(0);
//data_list.add(data);
//System.out.println("接收的数据为: " + data);
//return Response.newFixedLengthResponse(data);
}
public void registerService(HttpService http_service) {
this.http_service = http_service;
}
public synchronized int updateId() {
command_id++;
return command_id;
}
}