[*] 地理位置

This commit is contained in:
acgist
2023-03-23 08:11:36 +08:00
parent d635b5a2a3
commit 1fb36da847
13 changed files with 234 additions and 99 deletions

View File

@@ -25,6 +25,8 @@ android {
} }
dependencies { dependencies {
implementation 'org.apache.commons:commons-lang3:3.12.0'
implementation 'org.apache.commons:commons-collections4:4.4'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.2' implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.2'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.2' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.2'
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'

View File

@@ -39,7 +39,7 @@ public class Header implements Serializable {
} }
public String getV() { public String getV() {
return v; return this.v;
} }
public void setV(String v) { public void setV(String v) {
@@ -47,7 +47,7 @@ public class Header implements Serializable {
} }
public Long getId() { public Long getId() {
return id; return this.id;
} }
public void setId(Long id) { public void setId(Long id) {
@@ -55,10 +55,11 @@ public class Header implements Serializable {
} }
public String getSignal() { public String getSignal() {
return signal; return this.signal;
} }
public void setSignal(String signal) { public void setSignal(String signal) {
this.signal = signal; this.signal = signal;
} }
} }

View File

@@ -1,12 +1,14 @@
package com.acgist.taoyao.boot.model; package com.acgist.taoyao.boot.model;
import com.acgist.taoyao.boot.utils.JSONUtils;
import com.fasterxml.jackson.annotation.JsonIncludeProperties;
import org.apache.commons.lang3.StringUtils;
import java.io.Serializable; import java.io.Serializable;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import com.acgist.taoyao.boot.utils.JSONUtils;
import com.fasterxml.jackson.annotation.JsonIncludeProperties;
/** /**
* 消息 * 消息
@@ -36,13 +38,6 @@ public class Message implements Cloneable, Serializable {
*/ */
private Object body; private Object body;
/**
* @param code 状态编码
*/
public void setCode(String code) {
this.code = code;
}
/** /**
* @param code 状态编码 * @param code 状态编码
*/ */
@@ -57,7 +52,7 @@ public class Message implements Cloneable, Serializable {
*/ */
public Message setCode(MessageCode code, String message) { public Message setCode(MessageCode code, String message) {
this.code = code.getCode(); this.code = code.getCode();
this.message = message == null || message.isEmpty() ? code.getMessage() : message; this.message = StringUtils.isEmpty(message) ? code.getMessage() : message;
return this; return this;
} }
@@ -186,11 +181,15 @@ public class Message implements Cloneable, Serializable {
} }
public String getCode() { public String getCode() {
return code; return this.code;
}
public void setCode(String code) {
this.code = code;
} }
public String getMessage() { public String getMessage() {
return message; return this.message;
} }
public void setMessage(String message) { public void setMessage(String message) {
@@ -198,7 +197,7 @@ public class Message implements Cloneable, Serializable {
} }
public Header getHeader() { public Header getHeader() {
return header; return this.header;
} }
public void setHeader(Header header) { public void setHeader(Header header) {
@@ -206,7 +205,7 @@ public class Message implements Cloneable, Serializable {
} }
public Object getBody() { public Object getBody() {
return body; return this.body;
} }
public void setBody(Object body) { public void setBody(Object body) {

View File

@@ -84,14 +84,15 @@ public enum MessageCode {
} }
public String getCode() { public String getCode() {
return code; return this.code;
} }
public Integer getStatus() { public Integer getStatus() {
return status; return this.status;
} }
public String getMessage() { public String getMessage() {
return message; return this.message;
} }
} }

View File

@@ -68,4 +68,8 @@ public class MessageCodeException extends RuntimeException {
this.code = code; this.code = code;
} }
public MessageCode getCode() {
return this.code;
}
} }

View File

@@ -1,4 +1,4 @@
plugins { plugins {
id 'com.android.library' version '7.4.2' apply false id 'com.android.library' version '7.4.2' apply false
id 'com.android.application' version '7.4.2' apply false id 'com.android.application' version '7.4.2' apply false
} }

View File

@@ -34,6 +34,8 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0' implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3' implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'org.apache.commons:commons-lang3:3.12.0'
implementation 'org.apache.commons:commons-collections4:4.4'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.2' implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.2'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.2' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.2'
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'

View File

@@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application <application
android:allowBackup="true" android:allowBackup="true"

View File

@@ -1,4 +0,0 @@
package com.acgist.taoyao.client.media;
public class MediaRecorder {
}

View File

@@ -0,0 +1,28 @@
package com.acgist.taoyao.client.media;
/**
* 录像机
*
* @author acgist
*/
public final class Recorder {
/**
* 是否正在录像
*/
private boolean active;
private static final Recorder INSTANCE = new Recorder();
public static final Recorder getInstance() {
return INSTANCE;
}
/**
* @return 是否正在录像
*/
public boolean isActive() {
return this.active;
}
}

View File

@@ -1,12 +1,20 @@
package com.acgist.taoyao.client.signal; package com.acgist.taoyao.client.signal;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationManager;
import android.net.wifi.WifiManager; import android.net.wifi.WifiManager;
import android.os.BatteryManager; import android.os.BatteryManager;
import android.util.Log;
import com.acgist.taoyao.boot.model.Header; import com.acgist.taoyao.boot.model.Header;
import com.acgist.taoyao.boot.model.Message; import com.acgist.taoyao.boot.model.Message;
import com.acgist.taoyao.boot.utils.CloseableUtils; import com.acgist.taoyao.boot.utils.CloseableUtils;
import com.acgist.taoyao.boot.utils.JSONUtils; import com.acgist.taoyao.boot.utils.JSONUtils;
import com.acgist.taoyao.client.media.Recorder;
import com.acgist.taoyao.client.utils.IdUtils;
import org.apache.commons.lang3.ArrayUtils;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
@@ -15,7 +23,6 @@ import java.net.Socket;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.time.LocalDateTime;
import java.util.Base64; import java.util.Base64;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -33,11 +40,7 @@ import javax.crypto.spec.SecretKeySpec;
* *
* @author acgist * @author acgist
*/ */
public class Taoyao { public final class Taoyao {
// private static final HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 0, "[信令]");
private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(8);
/** /**
* 端口 * 端口
@@ -47,10 +50,24 @@ public class Taoyao {
* 地址 * 地址
*/ */
private final String host; private final String host;
/**
* 终端ID
*/
private final String clientId; private final String clientId;
private final String clientType = "camera";
/**
* 终端名称
*/
private final String name; private final String name;
/**
* 桃夭帐号
*/
private final String username; private final String username;
/**
* 桃夭密码
*/
private final String password; private final String password;
private String version;
/** /**
* Socket * Socket
*/ */
@@ -66,10 +83,15 @@ public class Taoyao {
private final Cipher decrypt; private final Cipher decrypt;
private final WifiManager wifiManager; private final WifiManager wifiManager;
private final BatteryManager batteryManager; private final BatteryManager batteryManager;
private final LocationManager locationManager;
// 线程池
private final ExecutorService executor = Executors.newFixedThreadPool(8);
// 定时任务线程池
private final ExecutorService scheduled = Executors.newScheduledThreadPool(2);
public Taoyao( public Taoyao(
int port, String host, String algo, String secret, String clientId, String name, String username, String password, int port, String host, String algo, String secret, String clientId, String name, String username, String password,
WifiManager wifiManager, BatteryManager batteryManager WifiManager wifiManager, BatteryManager batteryManager, LocationManager locationManager
) { ) {
this.port = port; this.port = port;
this.host = host; this.host = host;
@@ -89,7 +111,9 @@ public class Taoyao {
this.password = password; this.password = password;
this.wifiManager = wifiManager; this.wifiManager = wifiManager;
this.batteryManager = batteryManager; this.batteryManager = batteryManager;
EXECUTOR.submit(this::read); this.locationManager = locationManager;
executor.submit(this::read);
scheduled.submit(this::heartbeat);
} }
private Cipher buildCipher(int mode, String name, String secret) { private Cipher buildCipher(int mode, String name, String secret) {
@@ -128,6 +152,10 @@ public class Taoyao {
} }
} }
private void heartbeat() {
}
private void read() { private void read() {
int length = 0; int length = 0;
short messageLength = 0; short messageLength = 0;
@@ -167,8 +195,12 @@ public class Taoyao {
buffer.get(message); buffer.get(message);
buffer.compact(); buffer.compact();
final String content = new String(this.decrypt.doFinal(message)); final String content = new String(this.decrypt.doFinal(message));
EXECUTOR.submit(() -> { executor.submit(() -> {
Taoyao.this.on(content); try {
Taoyao.this.on(content);
} catch (Exception e) {
Log.e(Taoyao.class.getSimpleName(), "处理信令异常:" + content, e);
}
}); });
} }
} }
@@ -224,25 +256,9 @@ public class Taoyao {
return null; return null;
} }
private void register() { /**
final Header header = new Header(); * 释放连接
this.push(this.buildMessage( */
"client::register",
"clientId", this.clientId,
"name", this.name,
"clientType", "camera",
"username", this.username,
"password", this.password,
"signal", this.wifiManager.getMaxSignalLevel(),
"batter", this.batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY),
"charging", this.batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_STATUS)
));
}
private void heartbeat() {
}
private void close() { private void close() {
this.connect = false; this.connect = false;
CloseableUtils.close(this.input); CloseableUtils.close(this.input);
@@ -253,46 +269,25 @@ public class Taoyao {
this.socket = null; this.socket = null;
} }
/**
* 关闭信令
*/
private void shutdown() { private void shutdown() {
this.close(); this.close();
this.close = true; this.close = true;
EXECUTOR.shutdownNow(); executor.shutdownNow();
scheduled.shutdownNow();
} }
/** /**
* 当前索引 * @param signal 信令
* @param args 消息主体内容
*
* @return 消息
*/ */
private int index; public Message buildMessage(String signal, Object ... args) {
/**
* 当前终端索引
*/
private int clientIndex = 99999;
private static final int MAX_INDEX = 999;
public long buildId() {
int index;
synchronized (this) {
if (++this.index > MAX_INDEX) {
this.index = 0;
}
index = this.index;
}
final LocalDateTime time = LocalDateTime.now();
return
100000000000000L * time.getDayOfMonth() +
1000000000000L * time.getHour() +
10000000000L * time.getMinute() +
100000000L * time.getSecond() +
1000000L * this.clientIndex +
index;
}
private String version;
public Message buildMessage(String signal, Object... args) {
final Map<Object, Object> map = new HashMap<>(); final Map<Object, Object> map = new HashMap<>();
if (args != null) { if (ArrayUtils.isNotEmpty(args)) {
for (int index = 0; index < args.length; index += 2) { for (int index = 0; index < args.length; index += 2) {
map.put(args[index], args[index + 1]); map.put(args[index], args[index + 1]);
} }
@@ -300,21 +295,28 @@ public class Taoyao {
return this.buildMessage(signal, map); return this.buildMessage(signal, map);
} }
/**
* @param signal 信令
* @param body 消息主体
*
* @return 消息
*/
public Message buildMessage(String signal, Object body) { public Message buildMessage(String signal, Object body) {
final Header header = new Header(); final Header header = new Header();
header.setV(this.version == null ? "1.0.0" : this.version); header.setV(this.version == null ? "1.0.0" : this.version);
header.setId(this.buildId()); header.setId(IdUtils.buildId());
header.setSignal(signal); header.setSignal(signal);
final Message message = new Message(); final Message message = new Message();
message.setHeader(header); message.setHeader(header);
message.setBody(body == null ? new HashMap<>() : body); message.setBody(body == null ? Map.of() : body);
return message; return message;
} }
/**
* @param content 信令消息
*/
private void on(String content) { private void on(String content) {
// TODO日志 Log.d(Taoyao.class.getSimpleName(), "收到消息:" + content);
// log.debug("收到消息:{}", new String(this.decrypt.doFinal(message)));
System.out.println(content);
final Message message = JSONUtils.toJava(content, Message.class); final Message message = JSONUtils.toJava(content, Message.class);
if (message == null) { if (message == null) {
return; return;
@@ -325,18 +327,61 @@ public class Taoyao {
} }
final Map<String, Object> body = message.body(); final Map<String, Object> body = message.body();
switch (header.getSignal()) { switch (header.getSignal()) {
case "client::register": case "client::register" -> this.register(message, body);
this.register(message, body); default -> Log.i(Taoyao.class.getSimpleName(), "没有适配信令:" + content);
break;
default:
break;
} }
} }
/**
* 注册
*/
private void register() {
final Location location = this.location();
this.push(this.buildMessage(
"client::register",
"username", this.username,
"password", this.password,
"name", this.name,
"clientId", this.clientId,
"clientType", this.clientType,
"latitude", location == null ? -1 : location.getLatitude(),
"longitude", location == null ? -1 : location.getLongitude(),
"signal", this.wifiManager == null ? -1 : this.wifiManager.getMaxSignalLevel(),
"batter", this.batteryManager == null ? -1 : this.batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY),
"charging", this.batteryManager == null ? -1 : this.batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_STATUS),
"recording", Recorder.getInstance().isActive()
));
}
/**
* @param message 消息
* @param body 消息主体
*/
private void register(Message message, Map<String, Object> body) { private void register(Message message, Map<String, Object> body) {
final Integer index = (Integer) body.get("index"); final Integer index = (Integer) body.get("index");
this.clientIndex = index; IdUtils.setClientIndex(index);
System.out.println(clientIndex); }
/**
* @return 位置
*/
private Location location() {
if(this.locationManager == null) {
return null;
}
final Criteria criteria = new Criteria();
// 耗电
criteria.setCostAllowed(false);
// 不要海拔
criteria.setAltitudeRequired(false);
// 不要方位
criteria.setBearingRequired(false);
// 精度
criteria.setAccuracy(Criteria.ACCURACY_FINE);
// 低功耗
criteria.setPowerRequirement(Criteria.POWER_LOW);
final String provider = locationManager.getBestProvider(criteria, true);
return this.locationManager.getLastKnownLocation(provider);
} }
} }

View File

@@ -0,0 +1,53 @@
package com.acgist.taoyao.client.utils;
import java.time.LocalDateTime;
/**
* ID工具
*
* @author acgist
*/
public final class IdUtils {
/**
* 当前索引
*/
private static int index;
/**
* 当前终端索引
*/
private static int clientIndex = 99999;
/**
* 最大索引
*/
private static final int MAX_INDEX = 999;
/**
* @return 消息ID
*/
public static final long buildId() {
int index;
synchronized (IdUtils.class) {
if (++IdUtils.index > IdUtils.MAX_INDEX) {
IdUtils.index = 0;
}
index = IdUtils.index;
}
final LocalDateTime time = LocalDateTime.now();
return
100000000000000L * time.getDayOfMonth() +
1000000000000L * time.getHour() +
10000000000L * time.getMinute() +
100000000L * time.getSecond() +
1000000L * IdUtils.clientIndex +
index;
}
/**
* @param clientIndex 当前终端索引
*/
public static final void setClientIndex(int clientIndex) {
IdUtils.clientIndex = clientIndex;
}
}

View File

@@ -48,6 +48,8 @@ android {
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.a', '*.so', '*.jar']) implementation fileTree(dir: 'libs', include: ['*.a', '*.so', '*.jar'])
implementation 'org.apache.commons:commons-lang3:3.12.0'
implementation 'org.apache.commons:commons-collections4:4.4'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.2' implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.2'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.2' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.2'
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'