diff --git a/app/src/main/java/com/ttstd/dialer/bean/req/SnHardwareInfoReq.java b/app/src/main/java/com/ttstd/dialer/bean/req/SnHardwareInfoReq.java
index 3070395..407c964 100644
--- a/app/src/main/java/com/ttstd/dialer/bean/req/SnHardwareInfoReq.java
+++ b/app/src/main/java/com/ttstd/dialer/bean/req/SnHardwareInfoReq.java
@@ -4,138 +4,290 @@ import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
+/**
+ * 设备硬件信息上传请求。
+ *
+ * 对应后端 {@code POST /api/v1/sn/update_hardware_info},
+ * 字段与后端 {@code SnHardwareInfoReq} 一一对应。
+ *
+ * 使用包装类型(Integer)而非基本类型,未采集到的字段保持 null,
+ * Gson 默认不序列化 null,可避免用 0 覆盖服务端已有数据。
+ */
public class SnHardwareInfoReq implements Serializable {
- private static final long serialVersionUID = 2728925136862797858L;
+ private static final long serialVersionUID = 1L;
- @SerializedName("sn_imei")
- private String imei;
+ @SerializedName("cpu_abi")
+ private String cpuAbi;
- @SerializedName("sn_imsi")
- private String imsi;
+ @SerializedName("cpu_abi2")
+ private String cpuAbi2;
- @SerializedName("sn_wlan_mac")
- private String wlanMac;
+ @SerializedName("cpu_min")
+ private String cpuMin;
- @SerializedName("sn_device_mac")
- private String deviceMac;
+ @SerializedName("cpu_model")
+ private String cpuModel;
- @SerializedName("sn_bluetooth_mac")
- private String bluetoothMac;
+ @SerializedName("cpu_cores")
+ private Integer cpuCores;
- @SerializedName("sn_model")
- private String model;
+ @SerializedName("cpu_max_freq")
+ private String cpuMaxFreq;
- @SerializedName("sn_brand")
- private String brand;
+ @SerializedName("cpu_min_freq")
+ private String cpuMinFreq;
- @SerializedName("sn_board")
- private String board;
+ @SerializedName("total_ram")
+ private String totalRam;
- @SerializedName("sn_android_version")
- private String androidVersion;
+ @SerializedName("available_ram")
+ private String availableRam;
- @SerializedName("sn_android_api")
- private int androidApi;
+ @SerializedName("internal_storage")
+ private String internalStorage;
- @SerializedName("sn_build_id")
- private String buildId;
+ @SerializedName("available_storage")
+ private String availableStorage;
- @SerializedName("sn_build_display_id")
- private String buildDisplayId;
+ @SerializedName("external_storage")
+ private String externalStorage;
- public String getImei() {
- return imei;
+ @SerializedName("battery_capacity")
+ private String batteryCapacity;
+
+ @SerializedName("battery_level")
+ private Integer batteryLevel;
+
+ @SerializedName("battery_status")
+ private String batteryStatus;
+
+ @SerializedName("battery_health")
+ private String batteryHealth;
+
+ @SerializedName("battery_temp")
+ private String batteryTemp;
+
+ @SerializedName("battery_voltage")
+ private String batteryVoltage;
+
+ @SerializedName("battery_technology")
+ private String batteryTechnology;
+
+ @SerializedName("gpu_renderer")
+ private String gpuRenderer;
+
+ @SerializedName("gpu_vendor")
+ private String gpuVendor;
+
+ @SerializedName("gpu_version")
+ private String gpuVersion;
+
+ @SerializedName("sensors")
+ private String sensors;
+
+ @SerializedName("support_abi")
+ private String supportAbi;
+
+ @SerializedName("support_abi64")
+ private String supportAbi64;
+
+ public String getCpuAbi() {
+ return cpuAbi;
}
- public void setImei(String imei) {
- this.imei = imei;
+ public void setCpuAbi(String cpuAbi) {
+ this.cpuAbi = cpuAbi;
}
- public String getImsi() {
- return imsi;
+ public String getCpuAbi2() {
+ return cpuAbi2;
}
- public void setImsi(String imsi) {
- this.imsi = imsi;
+ public void setCpuAbi2(String cpuAbi2) {
+ this.cpuAbi2 = cpuAbi2;
}
- public String getWlanMac() {
- return wlanMac;
+ public String getCpuMin() {
+ return cpuMin;
}
- public void setWlanMac(String wlanMac) {
- this.wlanMac = wlanMac;
+ public void setCpuMin(String cpuMin) {
+ this.cpuMin = cpuMin;
}
- public String getDeviceMac() {
- return deviceMac;
+ public String getCpuModel() {
+ return cpuModel;
}
- public void setDeviceMac(String deviceMac) {
- this.deviceMac = deviceMac;
+ public void setCpuModel(String cpuModel) {
+ this.cpuModel = cpuModel;
}
- public String getBluetoothMac() {
- return bluetoothMac;
+ public Integer getCpuCores() {
+ return cpuCores;
}
- public void setBluetoothMac(String bluetoothMac) {
- this.bluetoothMac = bluetoothMac;
+ public void setCpuCores(Integer cpuCores) {
+ this.cpuCores = cpuCores;
}
- public String getModel() {
- return model;
+ public String getCpuMaxFreq() {
+ return cpuMaxFreq;
}
- public void setModel(String model) {
- this.model = model;
+ public void setCpuMaxFreq(String cpuMaxFreq) {
+ this.cpuMaxFreq = cpuMaxFreq;
}
- public String getBrand() {
- return brand;
+ public String getCpuMinFreq() {
+ return cpuMinFreq;
}
- public void setBrand(String brand) {
- this.brand = brand;
+ public void setCpuMinFreq(String cpuMinFreq) {
+ this.cpuMinFreq = cpuMinFreq;
}
- public String getBoard() {
- return board;
+ public String getTotalRam() {
+ return totalRam;
}
- public void setBoard(String board) {
- this.board = board;
+ public void setTotalRam(String totalRam) {
+ this.totalRam = totalRam;
}
- public String getAndroidVersion() {
- return androidVersion;
+ public String getAvailableRam() {
+ return availableRam;
}
- public void setAndroidVersion(String androidVersion) {
- this.androidVersion = androidVersion;
+ public void setAvailableRam(String availableRam) {
+ this.availableRam = availableRam;
}
- public int getAndroidApi() {
- return androidApi;
+ public String getInternalStorage() {
+ return internalStorage;
}
- public void setAndroidApi(int androidApi) {
- this.androidApi = androidApi;
+ public void setInternalStorage(String internalStorage) {
+ this.internalStorage = internalStorage;
}
- public String getBuildId() {
- return buildId;
+ public String getAvailableStorage() {
+ return availableStorage;
}
- public void setBuildId(String buildId) {
- this.buildId = buildId;
+ public void setAvailableStorage(String availableStorage) {
+ this.availableStorage = availableStorage;
}
- public String getBuildDisplayId() {
- return buildDisplayId;
+ public String getExternalStorage() {
+ return externalStorage;
}
- public void setBuildDisplayId(String buildDisplayId) {
- this.buildDisplayId = buildDisplayId;
+ public void setExternalStorage(String externalStorage) {
+ this.externalStorage = externalStorage;
+ }
+
+ public String getBatteryCapacity() {
+ return batteryCapacity;
+ }
+
+ public void setBatteryCapacity(String batteryCapacity) {
+ this.batteryCapacity = batteryCapacity;
+ }
+
+ public Integer getBatteryLevel() {
+ return batteryLevel;
+ }
+
+ public void setBatteryLevel(Integer batteryLevel) {
+ this.batteryLevel = batteryLevel;
+ }
+
+ public String getBatteryStatus() {
+ return batteryStatus;
+ }
+
+ public void setBatteryStatus(String batteryStatus) {
+ this.batteryStatus = batteryStatus;
+ }
+
+ public String getBatteryHealth() {
+ return batteryHealth;
+ }
+
+ public void setBatteryHealth(String batteryHealth) {
+ this.batteryHealth = batteryHealth;
+ }
+
+ public String getBatteryTemp() {
+ return batteryTemp;
+ }
+
+ public void setBatteryTemp(String batteryTemp) {
+ this.batteryTemp = batteryTemp;
+ }
+
+ public String getBatteryVoltage() {
+ return batteryVoltage;
+ }
+
+ public void setBatteryVoltage(String batteryVoltage) {
+ this.batteryVoltage = batteryVoltage;
+ }
+
+ public String getBatteryTechnology() {
+ return batteryTechnology;
+ }
+
+ public void setBatteryTechnology(String batteryTechnology) {
+ this.batteryTechnology = batteryTechnology;
+ }
+
+ public String getGpuRenderer() {
+ return gpuRenderer;
+ }
+
+ public void setGpuRenderer(String gpuRenderer) {
+ this.gpuRenderer = gpuRenderer;
+ }
+
+ public String getGpuVendor() {
+ return gpuVendor;
+ }
+
+ public void setGpuVendor(String gpuVendor) {
+ this.gpuVendor = gpuVendor;
+ }
+
+ public String getGpuVersion() {
+ return gpuVersion;
+ }
+
+ public void setGpuVersion(String gpuVersion) {
+ this.gpuVersion = gpuVersion;
+ }
+
+ public String getSensors() {
+ return sensors;
+ }
+
+ public void setSensors(String sensors) {
+ this.sensors = sensors;
+ }
+
+ public String getSupportAbi() {
+ return supportAbi;
+ }
+
+ public void setSupportAbi(String supportAbi) {
+ this.supportAbi = supportAbi;
+ }
+
+ public String getSupportAbi64() {
+ return supportAbi64;
+ }
+
+ public void setSupportAbi64(String supportAbi64) {
+ this.supportAbi64 = supportAbi64;
}
}
diff --git a/app/src/main/java/com/ttstd/dialer/bean/req/SnNetworkInfoReq.java b/app/src/main/java/com/ttstd/dialer/bean/req/SnNetworkInfoReq.java
new file mode 100644
index 0000000..dc5dabe
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/bean/req/SnNetworkInfoReq.java
@@ -0,0 +1,293 @@
+package com.ttstd.dialer.bean.req;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.io.Serializable;
+
+/**
+ * 设备网络信息上传请求。
+ *
+ * 对应后端 {@code POST /api/v1/sn/update_network_info},
+ * 字段与后端 {@code SnNetworkInfoReq} 一一对应。
+ *
+ * 布尔语义字段(is_connected/is_vpn/is_proxy)后端为 Integer,
+ * 这里同样用 Integer 表示 0/1,未知时为 null。
+ */
+public class SnNetworkInfoReq implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @SerializedName("network_type")
+ private String networkType;
+
+ @SerializedName("network_subtype")
+ private String networkSubtype;
+
+ @SerializedName("is_connected")
+ private Integer isConnected;
+
+ @SerializedName("wifi_ssid")
+ private String wifiSsid;
+
+ @SerializedName("wifi_bssid")
+ private String wifiBssid;
+
+ @SerializedName("wifi_ip")
+ private String wifiIp;
+
+ @SerializedName("wifi_mac")
+ private String wifiMac;
+
+ @SerializedName("wifi_gateway")
+ private String wifiGateway;
+
+ @SerializedName("wifi_netmask")
+ private String wifiNetmask;
+
+ @SerializedName("wifi_dns")
+ private String wifiDns;
+
+ @SerializedName("wifi_rssi")
+ private Integer wifiRssi;
+
+ @SerializedName("wifi_speed")
+ private String wifiSpeed;
+
+ @SerializedName("mobile_ip")
+ private String mobileIp;
+
+ @SerializedName("mobile_mac")
+ private String mobileMac;
+
+ @SerializedName("mobile_imei")
+ private String mobileImei;
+
+ @SerializedName("mobile_imsi")
+ private String mobileImsi;
+
+ @SerializedName("mobile_operator")
+ private String mobileOperator;
+
+ @SerializedName("mobile_sim_serial")
+ private String mobileSimSerial;
+
+ @SerializedName("mobile_network_type")
+ private String mobileNetworkType;
+
+ @SerializedName("bluetooth_mac")
+ private String bluetoothMac;
+
+ @SerializedName("bluetooth_name")
+ private String bluetoothName;
+
+ @SerializedName("is_vpn")
+ private Integer isVpn;
+
+ @SerializedName("is_proxy")
+ private Integer isProxy;
+
+ @SerializedName("proxy_host")
+ private String proxyHost;
+
+ @SerializedName("proxy_port")
+ private String proxyPort;
+
+ public String getNetworkType() {
+ return networkType;
+ }
+
+ public void setNetworkType(String networkType) {
+ this.networkType = networkType;
+ }
+
+ public String getNetworkSubtype() {
+ return networkSubtype;
+ }
+
+ public void setNetworkSubtype(String networkSubtype) {
+ this.networkSubtype = networkSubtype;
+ }
+
+ public Integer getIsConnected() {
+ return isConnected;
+ }
+
+ public void setIsConnected(Integer isConnected) {
+ this.isConnected = isConnected;
+ }
+
+ public String getWifiSsid() {
+ return wifiSsid;
+ }
+
+ public void setWifiSsid(String wifiSsid) {
+ this.wifiSsid = wifiSsid;
+ }
+
+ public String getWifiBssid() {
+ return wifiBssid;
+ }
+
+ public void setWifiBssid(String wifiBssid) {
+ this.wifiBssid = wifiBssid;
+ }
+
+ public String getWifiIp() {
+ return wifiIp;
+ }
+
+ public void setWifiIp(String wifiIp) {
+ this.wifiIp = wifiIp;
+ }
+
+ public String getWifiMac() {
+ return wifiMac;
+ }
+
+ public void setWifiMac(String wifiMac) {
+ this.wifiMac = wifiMac;
+ }
+
+ public String getWifiGateway() {
+ return wifiGateway;
+ }
+
+ public void setWifiGateway(String wifiGateway) {
+ this.wifiGateway = wifiGateway;
+ }
+
+ public String getWifiNetmask() {
+ return wifiNetmask;
+ }
+
+ public void setWifiNetmask(String wifiNetmask) {
+ this.wifiNetmask = wifiNetmask;
+ }
+
+ public String getWifiDns() {
+ return wifiDns;
+ }
+
+ public void setWifiDns(String wifiDns) {
+ this.wifiDns = wifiDns;
+ }
+
+ public Integer getWifiRssi() {
+ return wifiRssi;
+ }
+
+ public void setWifiRssi(Integer wifiRssi) {
+ this.wifiRssi = wifiRssi;
+ }
+
+ public String getWifiSpeed() {
+ return wifiSpeed;
+ }
+
+ public void setWifiSpeed(String wifiSpeed) {
+ this.wifiSpeed = wifiSpeed;
+ }
+
+ public String getMobileIp() {
+ return mobileIp;
+ }
+
+ public void setMobileIp(String mobileIp) {
+ this.mobileIp = mobileIp;
+ }
+
+ public String getMobileMac() {
+ return mobileMac;
+ }
+
+ public void setMobileMac(String mobileMac) {
+ this.mobileMac = mobileMac;
+ }
+
+ public String getMobileImei() {
+ return mobileImei;
+ }
+
+ public void setMobileImei(String mobileImei) {
+ this.mobileImei = mobileImei;
+ }
+
+ public String getMobileImsi() {
+ return mobileImsi;
+ }
+
+ public void setMobileImsi(String mobileImsi) {
+ this.mobileImsi = mobileImsi;
+ }
+
+ public String getMobileOperator() {
+ return mobileOperator;
+ }
+
+ public void setMobileOperator(String mobileOperator) {
+ this.mobileOperator = mobileOperator;
+ }
+
+ public String getMobileSimSerial() {
+ return mobileSimSerial;
+ }
+
+ public void setMobileSimSerial(String mobileSimSerial) {
+ this.mobileSimSerial = mobileSimSerial;
+ }
+
+ public String getMobileNetworkType() {
+ return mobileNetworkType;
+ }
+
+ public void setMobileNetworkType(String mobileNetworkType) {
+ this.mobileNetworkType = mobileNetworkType;
+ }
+
+ public String getBluetoothMac() {
+ return bluetoothMac;
+ }
+
+ public void setBluetoothMac(String bluetoothMac) {
+ this.bluetoothMac = bluetoothMac;
+ }
+
+ public String getBluetoothName() {
+ return bluetoothName;
+ }
+
+ public void setBluetoothName(String bluetoothName) {
+ this.bluetoothName = bluetoothName;
+ }
+
+ public Integer getIsVpn() {
+ return isVpn;
+ }
+
+ public void setIsVpn(Integer isVpn) {
+ this.isVpn = isVpn;
+ }
+
+ public Integer getIsProxy() {
+ return isProxy;
+ }
+
+ public void setIsProxy(Integer isProxy) {
+ this.isProxy = isProxy;
+ }
+
+ public String getProxyHost() {
+ return proxyHost;
+ }
+
+ public void setProxyHost(String proxyHost) {
+ this.proxyHost = proxyHost;
+ }
+
+ public String getProxyPort() {
+ return proxyPort;
+ }
+
+ public void setProxyPort(String proxyPort) {
+ this.proxyPort = proxyPort;
+ }
+}
diff --git a/app/src/main/java/com/ttstd/dialer/bean/req/SnOtherInfoReq.java b/app/src/main/java/com/ttstd/dialer/bean/req/SnOtherInfoReq.java
new file mode 100644
index 0000000..b44f86b
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/bean/req/SnOtherInfoReq.java
@@ -0,0 +1,261 @@
+package com.ttstd.dialer.bean.req;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.io.Serializable;
+
+/**
+ * 设备其他信息上传请求。
+ *
+ * 对应后端 {@code POST /api/v1/sn/update_other_info},
+ * 字段与后端 {@code SnOtherInfoReq} 一一对应。
+ *
+ * 注意:后端 install_time / update_time_app 为 {@code LocalDateTime},
+ * Jackson 默认接受 "yyyy-MM-dd'T'HH:mm:ss" 格式的字符串,
+ * 因此这里用 String 承载,由采集器负责格式化,避免时间戳数字反序列化失败。
+ */
+public class SnOtherInfoReq implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @SerializedName("app_version")
+ private String appVersion;
+
+ @SerializedName("app_version_code")
+ private Integer appVersionCode;
+
+ @SerializedName("install_time")
+ private String installTime;
+
+ @SerializedName("update_time_app")
+ private String updateTimeApp;
+
+ @SerializedName("package_name")
+ private String packageName;
+
+ @SerializedName("channel")
+ private String channel;
+
+ @SerializedName("device_id")
+ private String deviceId;
+
+ @SerializedName("gaid")
+ private String gaid;
+
+ @SerializedName("oaid")
+ private String oaid;
+
+ @SerializedName("phone_number")
+ private String phoneNumber;
+
+ @SerializedName("iccid")
+ private String iccid;
+
+ @SerializedName("imsi")
+ private String imsi;
+
+ @SerializedName("camera_info")
+ private String cameraInfo;
+
+ @SerializedName("sensor_count")
+ private Integer sensorCount;
+
+ @SerializedName("app_list_md5")
+ private String appListMd5;
+
+ @SerializedName("is_charging")
+ private Integer isCharging;
+
+ @SerializedName("charging_type")
+ private String chargingType;
+
+ @SerializedName("temperature")
+ private String temperature;
+
+ @SerializedName("free_storage")
+ private String freeStorage;
+
+ @SerializedName("total_storage")
+ private String totalStorage;
+
+ @SerializedName("free_ram")
+ private String freeRam;
+
+ @SerializedName("total_ram")
+ private String totalRam;
+
+ public String getAppVersion() {
+ return appVersion;
+ }
+
+ public void setAppVersion(String appVersion) {
+ this.appVersion = appVersion;
+ }
+
+ public Integer getAppVersionCode() {
+ return appVersionCode;
+ }
+
+ public void setAppVersionCode(Integer appVersionCode) {
+ this.appVersionCode = appVersionCode;
+ }
+
+ public String getInstallTime() {
+ return installTime;
+ }
+
+ public void setInstallTime(String installTime) {
+ this.installTime = installTime;
+ }
+
+ public String getUpdateTimeApp() {
+ return updateTimeApp;
+ }
+
+ public void setUpdateTimeApp(String updateTimeApp) {
+ this.updateTimeApp = updateTimeApp;
+ }
+
+ public String getPackageName() {
+ return packageName;
+ }
+
+ public void setPackageName(String packageName) {
+ this.packageName = packageName;
+ }
+
+ public String getChannel() {
+ return channel;
+ }
+
+ public void setChannel(String channel) {
+ this.channel = channel;
+ }
+
+ public String getDeviceId() {
+ return deviceId;
+ }
+
+ public void setDeviceId(String deviceId) {
+ this.deviceId = deviceId;
+ }
+
+ public String getGaid() {
+ return gaid;
+ }
+
+ public void setGaid(String gaid) {
+ this.gaid = gaid;
+ }
+
+ public String getOaid() {
+ return oaid;
+ }
+
+ public void setOaid(String oaid) {
+ this.oaid = oaid;
+ }
+
+ public String getPhoneNumber() {
+ return phoneNumber;
+ }
+
+ public void setPhoneNumber(String phoneNumber) {
+ this.phoneNumber = phoneNumber;
+ }
+
+ public String getIccid() {
+ return iccid;
+ }
+
+ public void setIccid(String iccid) {
+ this.iccid = iccid;
+ }
+
+ public String getImsi() {
+ return imsi;
+ }
+
+ public void setImsi(String imsi) {
+ this.imsi = imsi;
+ }
+
+ public String getCameraInfo() {
+ return cameraInfo;
+ }
+
+ public void setCameraInfo(String cameraInfo) {
+ this.cameraInfo = cameraInfo;
+ }
+
+ public Integer getSensorCount() {
+ return sensorCount;
+ }
+
+ public void setSensorCount(Integer sensorCount) {
+ this.sensorCount = sensorCount;
+ }
+
+ public String getAppListMd5() {
+ return appListMd5;
+ }
+
+ public void setAppListMd5(String appListMd5) {
+ this.appListMd5 = appListMd5;
+ }
+
+ public Integer getIsCharging() {
+ return isCharging;
+ }
+
+ public void setIsCharging(Integer isCharging) {
+ this.isCharging = isCharging;
+ }
+
+ public String getChargingType() {
+ return chargingType;
+ }
+
+ public void setChargingType(String chargingType) {
+ this.chargingType = chargingType;
+ }
+
+ public String getTemperature() {
+ return temperature;
+ }
+
+ public void setTemperature(String temperature) {
+ this.temperature = temperature;
+ }
+
+ public String getFreeStorage() {
+ return freeStorage;
+ }
+
+ public void setFreeStorage(String freeStorage) {
+ this.freeStorage = freeStorage;
+ }
+
+ public String getTotalStorage() {
+ return totalStorage;
+ }
+
+ public void setTotalStorage(String totalStorage) {
+ this.totalStorage = totalStorage;
+ }
+
+ public String getFreeRam() {
+ return freeRam;
+ }
+
+ public void setFreeRam(String freeRam) {
+ this.freeRam = freeRam;
+ }
+
+ public String getTotalRam() {
+ return totalRam;
+ }
+
+ public void setTotalRam(String totalRam) {
+ this.totalRam = totalRam;
+ }
+}
diff --git a/app/src/main/java/com/ttstd/dialer/bean/req/SnSecurityInfoReq.java b/app/src/main/java/com/ttstd/dialer/bean/req/SnSecurityInfoReq.java
new file mode 100644
index 0000000..3546567
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/bean/req/SnSecurityInfoReq.java
@@ -0,0 +1,246 @@
+package com.ttstd.dialer.bean.req;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.io.Serializable;
+
+/**
+ * 设备安全信息上传请求。
+ *
+ * 对应后端 {@code POST /api/v1/sn/update_security_info},
+ * 字段与后端 {@code SnSecurityInfoReq} 一一对应。
+ */
+public class SnSecurityInfoReq implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @SerializedName("is_root")
+ private Integer isRoot;
+
+ @SerializedName("is_jailbreak")
+ private Integer isJailbreak;
+
+ @SerializedName("root_status")
+ private Integer rootStatus;
+
+ @SerializedName("has_su_binary")
+ private Integer hasSuBinary;
+
+ @SerializedName("has_busybox")
+ private Integer hasBusybox;
+
+ @SerializedName("is_emulator")
+ private Integer isEmulator;
+
+ @SerializedName("is_debuggable")
+ private Integer isDebuggable;
+
+ @SerializedName("is_usb_debug")
+ private Integer isUsbDebug;
+
+ @SerializedName("is_unknown_sources")
+ private Integer isUnknownSources;
+
+ @SerializedName("is_verify_apps")
+ private Integer isVerifyApps;
+
+ @SerializedName("is_encrypted")
+ private Integer isEncrypted;
+
+ @SerializedName("lock_screen_type")
+ private String lockScreenType;
+
+ @SerializedName("has_password")
+ private Integer hasPassword;
+
+ @SerializedName("password_type")
+ private String passwordType;
+
+ @SerializedName("security_patch")
+ private String securityPatch;
+
+ @SerializedName("google_play_protect")
+ private Integer googlePlayProtect;
+
+ @SerializedName("adb_status")
+ private Integer adbStatus;
+
+ @SerializedName("is_vpn_active")
+ private Integer isVpnActive;
+
+ @SerializedName("is_device_owner")
+ private Integer isDeviceOwner;
+
+ @SerializedName("android_version")
+ private String androidVersion;
+
+ @SerializedName("kernel_version")
+ private String kernelVersion;
+
+ public Integer getIsRoot() {
+ return isRoot;
+ }
+
+ public void setIsRoot(Integer isRoot) {
+ this.isRoot = isRoot;
+ }
+
+ public Integer getIsJailbreak() {
+ return isJailbreak;
+ }
+
+ public void setIsJailbreak(Integer isJailbreak) {
+ this.isJailbreak = isJailbreak;
+ }
+
+ public Integer getRootStatus() {
+ return rootStatus;
+ }
+
+ public void setRootStatus(Integer rootStatus) {
+ this.rootStatus = rootStatus;
+ }
+
+ public Integer getHasSuBinary() {
+ return hasSuBinary;
+ }
+
+ public void setHasSuBinary(Integer hasSuBinary) {
+ this.hasSuBinary = hasSuBinary;
+ }
+
+ public Integer getHasBusybox() {
+ return hasBusybox;
+ }
+
+ public void setHasBusybox(Integer hasBusybox) {
+ this.hasBusybox = hasBusybox;
+ }
+
+ public Integer getIsEmulator() {
+ return isEmulator;
+ }
+
+ public void setIsEmulator(Integer isEmulator) {
+ this.isEmulator = isEmulator;
+ }
+
+ public Integer getIsDebuggable() {
+ return isDebuggable;
+ }
+
+ public void setIsDebuggable(Integer isDebuggable) {
+ this.isDebuggable = isDebuggable;
+ }
+
+ public Integer getIsUsbDebug() {
+ return isUsbDebug;
+ }
+
+ public void setIsUsbDebug(Integer isUsbDebug) {
+ this.isUsbDebug = isUsbDebug;
+ }
+
+ public Integer getIsUnknownSources() {
+ return isUnknownSources;
+ }
+
+ public void setIsUnknownSources(Integer isUnknownSources) {
+ this.isUnknownSources = isUnknownSources;
+ }
+
+ public Integer getIsVerifyApps() {
+ return isVerifyApps;
+ }
+
+ public void setIsVerifyApps(Integer isVerifyApps) {
+ this.isVerifyApps = isVerifyApps;
+ }
+
+ public Integer getIsEncrypted() {
+ return isEncrypted;
+ }
+
+ public void setIsEncrypted(Integer isEncrypted) {
+ this.isEncrypted = isEncrypted;
+ }
+
+ public String getLockScreenType() {
+ return lockScreenType;
+ }
+
+ public void setLockScreenType(String lockScreenType) {
+ this.lockScreenType = lockScreenType;
+ }
+
+ public Integer getHasPassword() {
+ return hasPassword;
+ }
+
+ public void setHasPassword(Integer hasPassword) {
+ this.hasPassword = hasPassword;
+ }
+
+ public String getPasswordType() {
+ return passwordType;
+ }
+
+ public void setPasswordType(String passwordType) {
+ this.passwordType = passwordType;
+ }
+
+ public String getSecurityPatch() {
+ return securityPatch;
+ }
+
+ public void setSecurityPatch(String securityPatch) {
+ this.securityPatch = securityPatch;
+ }
+
+ public Integer getGooglePlayProtect() {
+ return googlePlayProtect;
+ }
+
+ public void setGooglePlayProtect(Integer googlePlayProtect) {
+ this.googlePlayProtect = googlePlayProtect;
+ }
+
+ public Integer getAdbStatus() {
+ return adbStatus;
+ }
+
+ public void setAdbStatus(Integer adbStatus) {
+ this.adbStatus = adbStatus;
+ }
+
+ public Integer getIsVpnActive() {
+ return isVpnActive;
+ }
+
+ public void setIsVpnActive(Integer isVpnActive) {
+ this.isVpnActive = isVpnActive;
+ }
+
+ public Integer getIsDeviceOwner() {
+ return isDeviceOwner;
+ }
+
+ public void setIsDeviceOwner(Integer isDeviceOwner) {
+ this.isDeviceOwner = isDeviceOwner;
+ }
+
+ public String getAndroidVersion() {
+ return androidVersion;
+ }
+
+ public void setAndroidVersion(String androidVersion) {
+ this.androidVersion = androidVersion;
+ }
+
+ public String getKernelVersion() {
+ return kernelVersion;
+ }
+
+ public void setKernelVersion(String kernelVersion) {
+ this.kernelVersion = kernelVersion;
+ }
+}
diff --git a/app/src/main/java/com/ttstd/dialer/bean/req/SnSystemInfoReq.java b/app/src/main/java/com/ttstd/dialer/bean/req/SnSystemInfoReq.java
new file mode 100644
index 0000000..f2571e1
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/bean/req/SnSystemInfoReq.java
@@ -0,0 +1,151 @@
+package com.ttstd.dialer.bean.req;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.io.Serializable;
+
+/**
+ * 设备基本信息上传请求。
+ *
+ * 对应后端 {@code POST /api/v1/sn/update_system_info},
+ * 字段与后端 {@code SnSystemInfoReq} 一一对应。
+ *
+ * 注意:该类原名为 SnHardwareInfoReq,但其字段(sn_imei/sn_model/sn_brand 等)
+ * 实际对应的是后端的「基本信息」而非「硬件信息」,故更名以避免与新增的
+ * {@link SnHardwareInfoReq}(CPU/内存/电池/GPU)混淆。
+ */
+public class SnSystemInfoReq implements Serializable {
+ private static final long serialVersionUID = 2728925136862797858L;
+
+ @SerializedName("sn_imei")
+ private String imei;
+
+ @SerializedName("sn_imsi")
+ private String imsi;
+
+ @SerializedName("sn_wlan_mac")
+ private String wlanMac;
+
+ @SerializedName("sn_device_mac")
+ private String deviceMac;
+
+ @SerializedName("sn_bluetooth_mac")
+ private String bluetoothMac;
+
+ @SerializedName("sn_model")
+ private String model;
+
+ @SerializedName("sn_brand")
+ private String brand;
+
+ @SerializedName("sn_board")
+ private String board;
+
+ @SerializedName("sn_android_version")
+ private String androidVersion;
+
+ @SerializedName("sn_android_api")
+ private int androidApi;
+
+ @SerializedName("sn_build_id")
+ private String buildId;
+
+ @SerializedName("sn_build_display_id")
+ private String buildDisplayId;
+
+ public String getImei() {
+ return imei;
+ }
+
+ public void setImei(String imei) {
+ this.imei = imei;
+ }
+
+ public String getImsi() {
+ return imsi;
+ }
+
+ public void setImsi(String imsi) {
+ this.imsi = imsi;
+ }
+
+ public String getWlanMac() {
+ return wlanMac;
+ }
+
+ public void setWlanMac(String wlanMac) {
+ this.wlanMac = wlanMac;
+ }
+
+ public String getDeviceMac() {
+ return deviceMac;
+ }
+
+ public void setDeviceMac(String deviceMac) {
+ this.deviceMac = deviceMac;
+ }
+
+ public String getBluetoothMac() {
+ return bluetoothMac;
+ }
+
+ public void setBluetoothMac(String bluetoothMac) {
+ this.bluetoothMac = bluetoothMac;
+ }
+
+ public String getModel() {
+ return model;
+ }
+
+ public void setModel(String model) {
+ this.model = model;
+ }
+
+ public String getBrand() {
+ return brand;
+ }
+
+ public void setBrand(String brand) {
+ this.brand = brand;
+ }
+
+ public String getBoard() {
+ return board;
+ }
+
+ public void setBoard(String board) {
+ this.board = board;
+ }
+
+ public String getAndroidVersion() {
+ return androidVersion;
+ }
+
+ public void setAndroidVersion(String androidVersion) {
+ this.androidVersion = androidVersion;
+ }
+
+ public int getAndroidApi() {
+ return androidApi;
+ }
+
+ public void setAndroidApi(int androidApi) {
+ this.androidApi = androidApi;
+ }
+
+ public String getBuildId() {
+ return buildId;
+ }
+
+ public void setBuildId(String buildId) {
+ this.buildId = buildId;
+ }
+
+ public String getBuildDisplayId() {
+ return buildDisplayId;
+ }
+
+ public void setBuildDisplayId(String buildDisplayId) {
+ this.buildDisplayId = buildDisplayId;
+ }
+}
diff --git a/app/src/main/java/com/ttstd/dialer/mdm/DeviceInfoCollector.java b/app/src/main/java/com/ttstd/dialer/mdm/DeviceInfoCollector.java
new file mode 100644
index 0000000..64cc502
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/mdm/DeviceInfoCollector.java
@@ -0,0 +1,876 @@
+package com.ttstd.dialer.mdm;
+
+import android.Manifest;
+import android.app.ActivityManager;
+import android.app.admin.DevicePolicyManager;
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ApplicationInfo;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.net.ConnectivityManager;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.BatteryManager;
+import android.os.Build;
+import android.os.Environment;
+import android.os.StatFs;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+
+import androidx.core.content.ContextCompat;
+
+import com.ttstd.dialer.bean.req.SnHardwareInfoReq;
+import com.ttstd.dialer.bean.req.SnNetworkInfoReq;
+import com.ttstd.dialer.bean.req.SnOtherInfoReq;
+import com.ttstd.dialer.bean.req.SnSecurityInfoReq;
+import com.ttstd.dialer.bean.req.SnSystemInfoReq;
+import com.ttstd.dialer.utils.Logger;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * 设备信息采集器。
+ *
+ * 负责采集后端 5 个上传接口所需的全部信息:
+ *
+ * - {@link #collectSystemInfo()} —— 基本信息
+ * - {@link #collectHardwareInfo()} —— 硬件信息(CPU/内存/存储/电池/传感器)
+ * - {@link #collectNetworkInfo()} —— 网络信息(WiFi/移动网络/蓝牙/代理)
+ * - {@link #collectSecurityInfo()} —— 安全信息(Root/调试/加密/补丁)
+ * - {@link #collectOtherInfo()} —— 其他信息(App 版本/存储/电池快照)
+ *
+ *
+ * 设计要点:
+ *
+ * - 每个字段的采集都独立 try/catch,单个字段失败不影响整体上报;
+ * - 采集不到的字段保持 null,Gson 默认不序列化 null,避免覆盖服务端已有数据;
+ * - 涉及权限的调用(IMEI/IMSI/ICCID/手机号)均做权限检查,无权限时静默跳过;
+ * - 存在文件读取与 shell 探测,耗时较长,务必在子线程调用。
+ *
+ */
+public class DeviceInfoCollector {
+
+ private static final String TAG = "DeviceInfoCollector";
+
+ /**
+ * 后端 LocalDateTime 默认接受的 ISO 格式。
+ */
+ private static final String DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss";
+
+ /**
+ * 常见的 su 二进制路径,用于 Root 检测。
+ */
+ private static final String[] SU_PATHS = {
+ "/system/bin/su",
+ "/system/xbin/su",
+ "/sbin/su",
+ "/system/su",
+ "/vendor/bin/su",
+ "/su/bin/su",
+ "/data/local/xbin/su",
+ "/data/local/bin/su",
+ "/system/sd/xbin/su"
+ };
+
+ /**
+ * 常见的 busybox 路径。
+ */
+ private static final String[] BUSYBOX_PATHS = {
+ "/system/bin/busybox",
+ "/system/xbin/busybox",
+ "/sbin/busybox",
+ "/vendor/bin/busybox",
+ "/data/local/xbin/busybox"
+ };
+
+ private final Context mContext;
+
+ public DeviceInfoCollector(Context context) {
+ this.mContext = context.getApplicationContext();
+ }
+
+ // ==================================================================
+ // 基本信息
+ // ==================================================================
+
+ /**
+ * 采集设备基本信息。
+ */
+ public SnSystemInfoReq collectSystemInfo() {
+ SnSystemInfoReq req = new SnSystemInfoReq();
+ DeviceManagerService dms = DeviceManagerService.getInstance();
+
+ req.setImei(safeString(dms::getImei));
+ req.setImsi(safeString(dms::getImsi));
+ req.setWlanMac(safeString(dms::getWlanMac));
+ req.setDeviceMac(safeString(dms::getDeviceMac));
+ req.setBluetoothMac(safeString(dms::getBluetoothMac));
+
+ req.setModel(Build.MODEL);
+ req.setBrand(Build.BRAND);
+ req.setBoard(Build.BOARD);
+ req.setAndroidVersion(Build.VERSION.RELEASE);
+ req.setAndroidApi(Build.VERSION.SDK_INT);
+ req.setBuildId(Build.ID);
+ req.setBuildDisplayId(Build.DISPLAY);
+ return req;
+ }
+
+ // ==================================================================
+ // 硬件信息
+ // ==================================================================
+
+ /**
+ * 采集设备硬件信息。
+ */
+ public SnHardwareInfoReq collectHardwareInfo() {
+ SnHardwareInfoReq req = new SnHardwareInfoReq();
+
+ // ---- CPU ----
+ try {
+ req.setCpuAbi(Build.SUPPORTED_ABIS.length > 0 ? Build.SUPPORTED_ABIS[0] : null);
+ req.setCpuAbi2(Build.SUPPORTED_ABIS.length > 1 ? Build.SUPPORTED_ABIS[1] : null);
+ req.setSupportAbi(join(Build.SUPPORTED_32_BIT_ABIS));
+ req.setSupportAbi64(join(Build.SUPPORTED_64_BIT_ABIS));
+ } catch (Throwable t) {
+ Logger.e(TAG, "collect cpu abi failed: " + t.getMessage());
+ }
+
+ try {
+ req.setCpuCores(Runtime.getRuntime().availableProcessors());
+ } catch (Throwable t) {
+ Logger.e(TAG, "collect cpu cores failed: " + t.getMessage());
+ }
+
+ req.setCpuModel(readCpuModel());
+ req.setCpuMin(Build.HARDWARE);
+ req.setCpuMaxFreq(readCpuFreq("cpuinfo_max_freq"));
+ req.setCpuMinFreq(readCpuFreq("cpuinfo_min_freq"));
+
+ // ---- 内存 ----
+ try {
+ ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ if (am != null) {
+ ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
+ am.getMemoryInfo(mi);
+ req.setTotalRam(formatSize(mi.totalMem));
+ req.setAvailableRam(formatSize(mi.availMem));
+ }
+ } catch (Throwable t) {
+ Logger.e(TAG, "collect ram failed: " + t.getMessage());
+ }
+
+ // ---- 存储 ----
+ try {
+ File data = Environment.getDataDirectory();
+ StatFs stat = new StatFs(data.getPath());
+ long total = stat.getBlockCountLong() * stat.getBlockSizeLong();
+ long available = stat.getAvailableBlocksLong() * stat.getBlockSizeLong();
+ req.setInternalStorage(formatSize(total));
+ req.setAvailableStorage(formatSize(available));
+ } catch (Throwable t) {
+ Logger.e(TAG, "collect storage failed: " + t.getMessage());
+ }
+
+ try {
+ if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
+ File ext = Environment.getExternalStorageDirectory();
+ StatFs stat = new StatFs(ext.getPath());
+ long total = stat.getBlockCountLong() * stat.getBlockSizeLong();
+ req.setExternalStorage(formatSize(total));
+ }
+ } catch (Throwable t) {
+ Logger.e(TAG, "collect external storage failed: " + t.getMessage());
+ }
+
+ // ---- 电池 ----
+ try {
+ Intent battery = mContext.registerReceiver(null,
+ new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+ if (battery != null) {
+ int level = battery.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
+ int scale = battery.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
+ if (level >= 0 && scale > 0) {
+ req.setBatteryLevel(level * 100 / scale);
+ }
+ req.setBatteryStatus(batteryStatusName(
+ battery.getIntExtra(BatteryManager.EXTRA_STATUS, -1)));
+ req.setBatteryHealth(batteryHealthName(
+ battery.getIntExtra(BatteryManager.EXTRA_HEALTH, -1)));
+
+ int temp = battery.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, -1);
+ if (temp > 0) {
+ // 系统返回的是 0.1℃ 为单位的整数
+ req.setBatteryTemp(String.valueOf(temp / 10.0f));
+ }
+ int voltage = battery.getIntExtra(BatteryManager.EXTRA_VOLTAGE, -1);
+ if (voltage > 0) {
+ req.setBatteryVoltage(voltage + "mV");
+ }
+ req.setBatteryTechnology(
+ battery.getStringExtra(BatteryManager.EXTRA_TECHNOLOGY));
+ }
+ req.setBatteryCapacity(readBatteryCapacity());
+ } catch (Throwable t) {
+ Logger.e(TAG, "collect battery failed: " + t.getMessage());
+ }
+
+ // ---- 传感器 ----
+ try {
+ SensorManager sm = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
+ if (sm != null) {
+ List sensors = sm.getSensorList(Sensor.TYPE_ALL);
+ StringBuilder sb = new StringBuilder();
+ for (Sensor s : sensors) {
+ if (sb.length() > 0) {
+ sb.append(",");
+ }
+ sb.append(s.getName());
+ }
+ req.setSensors(sb.length() > 0 ? sb.toString() : null);
+ }
+ } catch (Throwable t) {
+ Logger.e(TAG, "collect sensors failed: " + t.getMessage());
+ }
+
+ // GPU 信息需要 GL 上下文,普通 Service 中无法可靠获取,此处留空由服务端容错。
+ return req;
+ }
+
+ // ==================================================================
+ // 网络信息
+ // ==================================================================
+
+ /**
+ * 采集设备网络信息。
+ */
+ public SnNetworkInfoReq collectNetworkInfo() {
+ SnNetworkInfoReq req = new SnNetworkInfoReq();
+
+ ConnectivityManager cm =
+ (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+
+ // ---- 连接状态与网络类型 ----
+ try {
+ if (cm != null) {
+ Network active = cm.getActiveNetwork();
+ NetworkCapabilities caps = active == null ? null : cm.getNetworkCapabilities(active);
+ boolean connected = caps != null
+ && caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+ req.setIsConnected(connected ? 1 : 0);
+
+ if (caps != null) {
+ if (caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+ req.setNetworkType("WIFI");
+ } else if (caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
+ req.setNetworkType("MOBILE");
+ } else if (caps.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
+ req.setNetworkType("ETHERNET");
+ } else {
+ req.setNetworkType("OTHER");
+ }
+ req.setIsVpn(caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN) ? 1 : 0);
+ }
+
+ // DNS 与网关
+ LinkProperties lp = active == null ? null : cm.getLinkProperties(active);
+ if (lp != null) {
+ StringBuilder dns = new StringBuilder();
+ for (InetAddress addr : lp.getDnsServers()) {
+ if (dns.length() > 0) {
+ dns.append(",");
+ }
+ dns.append(addr.getHostAddress());
+ }
+ req.setWifiDns(dns.length() > 0 ? dns.toString() : null);
+
+ for (LinkAddress la : lp.getLinkAddresses()) {
+ if (la.getAddress() instanceof Inet4Address) {
+ req.setWifiNetmask(String.valueOf(la.getPrefixLength()));
+ break;
+ }
+ }
+ }
+ }
+ } catch (Throwable t) {
+ Logger.e(TAG, "collect network state failed: " + t.getMessage());
+ }
+
+ // ---- WiFi ----
+ try {
+ WifiManager wm = (WifiManager) mContext.getApplicationContext()
+ .getSystemService(Context.WIFI_SERVICE);
+ if (wm != null) {
+ WifiInfo info = wm.getConnectionInfo();
+ if (info != null) {
+ String ssid = info.getSSID();
+ if (!TextUtils.isEmpty(ssid)) {
+ // 系统返回的 SSID 常带引号
+ req.setWifiSsid(ssid.replace("\"", ""));
+ }
+ req.setWifiBssid(info.getBSSID());
+ req.setWifiRssi(info.getRssi());
+ req.setWifiSpeed(info.getLinkSpeed() + "Mbps");
+
+ int ip = info.getIpAddress();
+ if (ip != 0) {
+ req.setWifiIp(intToIp(ip));
+ }
+ }
+ // Android 6.0+ 出于隐私限制,getMacAddress 返回固定值,改用 MDM 接口
+ req.setWifiMac(safeString(() -> DeviceManagerService.getInstance().getWlanMac()));
+
+ android.net.DhcpInfo dhcp = wm.getDhcpInfo();
+ if (dhcp != null && dhcp.gateway != 0) {
+ req.setWifiGateway(intToIp(dhcp.gateway));
+ }
+ }
+ } catch (Throwable t) {
+ Logger.e(TAG, "collect wifi failed: " + t.getMessage());
+ }
+
+ // ---- 移动网络 ----
+ try {
+ TelephonyManager tm =
+ (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ if (tm != null) {
+ req.setMobileOperator(tm.getNetworkOperatorName());
+ req.setMobileNetworkType(networkTypeName(tm));
+ if (hasPermission(Manifest.permission.READ_PHONE_STATE)) {
+ req.setMobileSimSerial(safeString(tm::getSimSerialNumber));
+ }
+ }
+ // IMEI/IMSI 走 MDM 接口,规避高版本系统的权限限制
+ req.setMobileImei(safeString(() -> DeviceManagerService.getInstance().getImei()));
+ req.setMobileImsi(safeString(() -> DeviceManagerService.getInstance().getImsi()));
+ } catch (Throwable t) {
+ Logger.e(TAG, "collect mobile failed: " + t.getMessage());
+ }
+
+ // ---- 蓝牙 ----
+ try {
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ if (adapter != null) {
+ req.setBluetoothName(safeString(adapter::getName));
+ }
+ req.setBluetoothMac(safeString(() -> DeviceManagerService.getInstance().getBluetoothMac()));
+ } catch (Throwable t) {
+ Logger.e(TAG, "collect bluetooth failed: " + t.getMessage());
+ }
+
+ // ---- 代理 ----
+ try {
+ String host = System.getProperty("http.proxyHost");
+ String port = System.getProperty("http.proxyPort");
+ boolean hasProxy = !TextUtils.isEmpty(host);
+ req.setIsProxy(hasProxy ? 1 : 0);
+ if (hasProxy) {
+ req.setProxyHost(host);
+ req.setProxyPort(port);
+ }
+ } catch (Throwable t) {
+ Logger.e(TAG, "collect proxy failed: " + t.getMessage());
+ }
+
+ return req;
+ }
+
+ // ==================================================================
+ // 安全信息
+ // ==================================================================
+
+ /**
+ * 采集设备安全信息。
+ */
+ public SnSecurityInfoReq collectSecurityInfo() {
+ SnSecurityInfoReq req = new SnSecurityInfoReq();
+
+ // ---- Root 检测 ----
+ int hasSu = anyFileExists(SU_PATHS) ? 1 : 0;
+ int hasBusybox = anyFileExists(BUSYBOX_PATHS) ? 1 : 0;
+ int testKeys = Build.TAGS != null && Build.TAGS.contains("test-keys") ? 1 : 0;
+ int rooted = (hasSu == 1 || hasBusybox == 1 || testKeys == 1) ? 1 : 0;
+
+ req.setHasSuBinary(hasSu);
+ req.setHasBusybox(hasBusybox);
+ req.setIsRoot(rooted);
+ req.setIsJailbreak(rooted);
+ req.setRootStatus(rooted);
+
+ // ---- 模拟器检测 ----
+ req.setIsEmulator(isEmulator() ? 1 : 0);
+
+ // ---- 调试相关 ----
+ try {
+ ApplicationInfo ai = mContext.getApplicationInfo();
+ req.setIsDebuggable((ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0 ? 1 : 0);
+ } catch (Throwable t) {
+ Logger.e(TAG, "collect debuggable failed: " + t.getMessage());
+ }
+
+ try {
+ int adb = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.ADB_ENABLED, 0);
+ req.setIsUsbDebug(adb);
+ req.setAdbStatus(adb);
+ } catch (Throwable t) {
+ Logger.e(TAG, "collect adb failed: " + t.getMessage());
+ }
+
+ try {
+ // INSTALL_NON_MARKET_APPS 在 Android 8.0 后被弃用,改为按应用授权
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ req.setIsUnknownSources(Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.INSTALL_NON_MARKET_APPS, 0));
+ } else {
+ req.setIsUnknownSources(
+ mContext.getPackageManager().canRequestPackageInstalls() ? 1 : 0);
+ }
+ } catch (Throwable t) {
+ Logger.e(TAG, "collect unknown sources failed: " + t.getMessage());
+ }
+
+ try {
+ req.setIsVerifyApps(Settings.Global.getInt(mContext.getContentResolver(),
+ "package_verifier_enable", 0));
+ req.setGooglePlayProtect(req.getIsVerifyApps());
+ } catch (Throwable t) {
+ Logger.e(TAG, "collect verify apps failed: " + t.getMessage());
+ }
+
+ // ---- 加密与锁屏 ----
+ try {
+ DevicePolicyManager dpm =
+ (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ if (dpm != null) {
+ int status = dpm.getStorageEncryptionStatus();
+ boolean encrypted =
+ status == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE
+ || status == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER
+ || status == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY;
+ req.setIsEncrypted(encrypted ? 1 : 0);
+ req.setIsDeviceOwner(dpm.isDeviceOwnerApp(mContext.getPackageName()) ? 1 : 0);
+ }
+ } catch (Throwable t) {
+ Logger.e(TAG, "collect encryption failed: " + t.getMessage());
+ }
+
+ try {
+ android.app.KeyguardManager km =
+ (android.app.KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+ if (km != null) {
+ boolean secure = km.isDeviceSecure();
+ req.setHasPassword(secure ? 1 : 0);
+ req.setLockScreenType(secure ? "SECURE" : "NONE");
+ req.setPasswordType(secure ? "SECURE" : "NONE");
+ }
+ } catch (Throwable t) {
+ Logger.e(TAG, "collect keyguard failed: " + t.getMessage());
+ }
+
+ // ---- VPN ----
+ try {
+ ConnectivityManager cm =
+ (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ if (cm != null) {
+ Network active = cm.getActiveNetwork();
+ NetworkCapabilities caps = active == null ? null : cm.getNetworkCapabilities(active);
+ req.setIsVpnActive(caps != null
+ && caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN) ? 1 : 0);
+ }
+ } catch (Throwable t) {
+ Logger.e(TAG, "collect vpn failed: " + t.getMessage());
+ }
+
+ // ---- 版本信息 ----
+ req.setAndroidVersion(Build.VERSION.RELEASE);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ req.setSecurityPatch(Build.VERSION.SECURITY_PATCH);
+ }
+ req.setKernelVersion(System.getProperty("os.version"));
+ return req;
+ }
+
+ // ==================================================================
+ // 其他信息
+ // ==================================================================
+
+ /**
+ * 采集设备其他信息。
+ */
+ public SnOtherInfoReq collectOtherInfo() {
+ SnOtherInfoReq req = new SnOtherInfoReq();
+
+ // ---- App 自身信息 ----
+ try {
+ PackageManager pm = mContext.getPackageManager();
+ String pkg = mContext.getPackageName();
+ PackageInfo pi = pm.getPackageInfo(pkg, 0);
+
+ req.setPackageName(pkg);
+ req.setAppVersion(pi.versionName);
+ req.setAppVersionCode(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
+ ? (int) pi.getLongVersionCode()
+ : pi.versionCode);
+ req.setInstallTime(formatDateTime(pi.firstInstallTime));
+ req.setUpdateTimeApp(formatDateTime(pi.lastUpdateTime));
+ } catch (Throwable t) {
+ Logger.e(TAG, "collect app info failed: " + t.getMessage());
+ }
+
+ // ---- 设备标识 ----
+ try {
+ req.setDeviceId(Settings.Secure.getString(
+ mContext.getContentResolver(), Settings.Secure.ANDROID_ID));
+ } catch (Throwable t) {
+ Logger.e(TAG, "collect android id failed: " + t.getMessage());
+ }
+
+ req.setImsi(safeString(() -> DeviceManagerService.getInstance().getImsi()));
+
+ // ---- SIM 相关(需要权限,无权限则跳过)----
+ try {
+ if (hasPermission(Manifest.permission.READ_PHONE_STATE)) {
+ TelephonyManager tm =
+ (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ if (tm != null) {
+ req.setIccid(safeString(tm::getSimSerialNumber));
+ req.setPhoneNumber(safeString(tm::getLine1Number));
+ }
+ }
+ } catch (Throwable t) {
+ Logger.e(TAG, "collect sim info failed: " + t.getMessage());
+ }
+
+ // ---- 相机与传感器 ----
+ try {
+ PackageManager pm = mContext.getPackageManager();
+ boolean back = pm.hasSystemFeature(PackageManager.FEATURE_CAMERA);
+ boolean front = pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT);
+ req.setCameraInfo("back=" + back + ",front=" + front);
+ } catch (Throwable t) {
+ Logger.e(TAG, "collect camera failed: " + t.getMessage());
+ }
+
+ try {
+ SensorManager sm = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
+ if (sm != null) {
+ req.setSensorCount(sm.getSensorList(Sensor.TYPE_ALL).size());
+ }
+ } catch (Throwable t) {
+ Logger.e(TAG, "collect sensor count failed: " + t.getMessage());
+ }
+
+ // ---- 电池快照 ----
+ try {
+ Intent battery = mContext.registerReceiver(null,
+ new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+ if (battery != null) {
+ int status = battery.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
+ boolean charging = status == BatteryManager.BATTERY_STATUS_CHARGING
+ || status == BatteryManager.BATTERY_STATUS_FULL;
+ req.setIsCharging(charging ? 1 : 0);
+
+ int plugged = battery.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
+ req.setChargingType(chargingTypeName(plugged));
+
+ int temp = battery.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, -1);
+ if (temp > 0) {
+ req.setTemperature(String.valueOf(temp / 10.0f));
+ }
+ }
+ } catch (Throwable t) {
+ Logger.e(TAG, "collect battery snapshot failed: " + t.getMessage());
+ }
+
+ // ---- 存储与内存快照 ----
+ try {
+ StatFs stat = new StatFs(Environment.getDataDirectory().getPath());
+ req.setTotalStorage(formatSize(stat.getBlockCountLong() * stat.getBlockSizeLong()));
+ req.setFreeStorage(formatSize(stat.getAvailableBlocksLong() * stat.getBlockSizeLong()));
+ } catch (Throwable t) {
+ Logger.e(TAG, "collect storage snapshot failed: " + t.getMessage());
+ }
+
+ try {
+ ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ if (am != null) {
+ ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
+ am.getMemoryInfo(mi);
+ req.setTotalRam(formatSize(mi.totalMem));
+ req.setFreeRam(formatSize(mi.availMem));
+ }
+ } catch (Throwable t) {
+ Logger.e(TAG, "collect ram snapshot failed: " + t.getMessage());
+ }
+
+ return req;
+ }
+
+ // ==================================================================
+ // 工具方法
+ // ==================================================================
+
+ /**
+ * 供 lambda 使用的取值接口,用于统一包裹异常。
+ */
+ private interface StringSupplier {
+ String get() throws Throwable;
+ }
+
+ /**
+ * 安全取值:任何异常都返回 null,避免单个字段导致整体采集失败。
+ */
+ private String safeString(StringSupplier supplier) {
+ try {
+ return supplier.get();
+ } catch (Throwable t) {
+ Logger.e(TAG, "safeString failed: " + t.getMessage());
+ return null;
+ }
+ }
+
+ private boolean hasPermission(String permission) {
+ try {
+ return ContextCompat.checkSelfPermission(mContext, permission)
+ == PackageManager.PERMISSION_GRANTED;
+ } catch (Throwable t) {
+ return false;
+ }
+ }
+
+ private static String join(String[] arr) {
+ if (arr == null || arr.length == 0) {
+ return null;
+ }
+ return TextUtils.join(",", arr);
+ }
+
+ /**
+ * 字节数格式化为可读字符串,保留两位小数。
+ */
+ private static String formatSize(long bytes) {
+ if (bytes <= 0) {
+ return null;
+ }
+ String[] units = {"B", "KB", "MB", "GB", "TB"};
+ int idx = 0;
+ double size = bytes;
+ while (size >= 1024 && idx < units.length - 1) {
+ size /= 1024;
+ idx++;
+ }
+ return String.format(Locale.US, "%.2f%s", size, units[idx]);
+ }
+
+ private static String formatDateTime(long millis) {
+ if (millis <= 0) {
+ return null;
+ }
+ try {
+ return new SimpleDateFormat(DATE_TIME_PATTERN, Locale.US).format(new Date(millis));
+ } catch (Throwable t) {
+ return null;
+ }
+ }
+
+ private static String intToIp(int ip) {
+ return (ip & 0xFF) + "." + ((ip >> 8) & 0xFF) + "."
+ + ((ip >> 16) & 0xFF) + "." + ((ip >> 24) & 0xFF);
+ }
+
+ private static boolean anyFileExists(String[] paths) {
+ for (String path : paths) {
+ try {
+ if (new File(path).exists()) {
+ return true;
+ }
+ } catch (Throwable ignored) {
+ // 无权限访问时视为不存在
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 从 /proc/cpuinfo 读取 CPU 型号。
+ */
+ private static String readCpuModel() {
+ try (BufferedReader reader = new BufferedReader(new FileReader("/proc/cpuinfo"))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (line.startsWith("Hardware") || line.startsWith("model name")) {
+ int idx = line.indexOf(':');
+ if (idx >= 0 && idx + 1 < line.length()) {
+ return line.substring(idx + 1).trim();
+ }
+ }
+ }
+ } catch (Throwable t) {
+ Logger.e(TAG, "readCpuModel failed: " + t.getMessage());
+ }
+ return null;
+ }
+
+ /**
+ * 读取 CPU 频率(单位 KHz),转换为 MHz 字符串。
+ */
+ private static String readCpuFreq(String fileName) {
+ String path = "/sys/devices/system/cpu/cpu0/cpufreq/" + fileName;
+ try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
+ String value = reader.readLine();
+ if (!TextUtils.isEmpty(value)) {
+ long khz = Long.parseLong(value.trim());
+ return (khz / 1000) + "MHz";
+ }
+ } catch (Throwable t) {
+ // 部分设备该节点不可读,属正常情况
+ Logger.e(TAG, "readCpuFreq " + fileName + " failed: " + t.getMessage());
+ }
+ return null;
+ }
+
+ /**
+ * 通过反射读取电池设计容量(框架隐藏 API,失败返回 null)。
+ */
+ private String readBatteryCapacity() {
+ try {
+ Object profile = Class.forName("com.android.internal.os.PowerProfile")
+ .getConstructor(Context.class)
+ .newInstance(mContext);
+ Object capacity = Class.forName("com.android.internal.os.PowerProfile")
+ .getMethod("getBatteryCapacity")
+ .invoke(profile);
+ if (capacity instanceof Double) {
+ return String.format(Locale.US, "%.0fmAh", (Double) capacity);
+ }
+ } catch (Throwable t) {
+ Logger.e(TAG, "readBatteryCapacity failed: " + t.getMessage());
+ }
+ return null;
+ }
+
+ private static boolean isEmulator() {
+ try {
+ return Build.FINGERPRINT.startsWith("generic")
+ || Build.FINGERPRINT.startsWith("unknown")
+ || Build.MODEL.contains("google_sdk")
+ || Build.MODEL.contains("Emulator")
+ || Build.MODEL.contains("Android SDK built for x86")
+ || Build.MANUFACTURER.contains("Genymotion")
+ || (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"))
+ || "google_sdk".equals(Build.PRODUCT);
+ } catch (Throwable t) {
+ return false;
+ }
+ }
+
+ private static String batteryStatusName(int status) {
+ switch (status) {
+ case BatteryManager.BATTERY_STATUS_CHARGING:
+ return "CHARGING";
+ case BatteryManager.BATTERY_STATUS_DISCHARGING:
+ return "DISCHARGING";
+ case BatteryManager.BATTERY_STATUS_FULL:
+ return "FULL";
+ case BatteryManager.BATTERY_STATUS_NOT_CHARGING:
+ return "NOT_CHARGING";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ private static String batteryHealthName(int health) {
+ switch (health) {
+ case BatteryManager.BATTERY_HEALTH_GOOD:
+ return "GOOD";
+ case BatteryManager.BATTERY_HEALTH_OVERHEAT:
+ return "OVERHEAT";
+ case BatteryManager.BATTERY_HEALTH_DEAD:
+ return "DEAD";
+ case BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE:
+ return "OVER_VOLTAGE";
+ case BatteryManager.BATTERY_HEALTH_COLD:
+ return "COLD";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ private static String chargingTypeName(int plugged) {
+ switch (plugged) {
+ case BatteryManager.BATTERY_PLUGGED_AC:
+ return "AC";
+ case BatteryManager.BATTERY_PLUGGED_USB:
+ return "USB";
+ case BatteryManager.BATTERY_PLUGGED_WIRELESS:
+ return "WIRELESS";
+ case 0:
+ return "NONE";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ /**
+ * 获取移动网络制式名称,无权限时返回 null。
+ */
+ private String networkTypeName(TelephonyManager tm) {
+ try {
+ int type;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ if (!hasPermission(Manifest.permission.READ_PHONE_STATE)) {
+ return null;
+ }
+ type = tm.getDataNetworkType();
+ } else {
+ type = tm.getNetworkType();
+ }
+ switch (type) {
+ case TelephonyManager.NETWORK_TYPE_GPRS:
+ case TelephonyManager.NETWORK_TYPE_EDGE:
+ case TelephonyManager.NETWORK_TYPE_CDMA:
+ case TelephonyManager.NETWORK_TYPE_1xRTT:
+ case TelephonyManager.NETWORK_TYPE_IDEN:
+ return "2G";
+ case TelephonyManager.NETWORK_TYPE_UMTS:
+ case TelephonyManager.NETWORK_TYPE_EVDO_0:
+ case TelephonyManager.NETWORK_TYPE_EVDO_A:
+ case TelephonyManager.NETWORK_TYPE_HSDPA:
+ case TelephonyManager.NETWORK_TYPE_HSUPA:
+ case TelephonyManager.NETWORK_TYPE_HSPA:
+ case TelephonyManager.NETWORK_TYPE_EVDO_B:
+ case TelephonyManager.NETWORK_TYPE_EHRPD:
+ case TelephonyManager.NETWORK_TYPE_HSPAP:
+ return "3G";
+ case TelephonyManager.NETWORK_TYPE_LTE:
+ return "4G";
+ case TelephonyManager.NETWORK_TYPE_NR:
+ return "5G";
+ default:
+ return "UNKNOWN";
+ }
+ } catch (Throwable t) {
+ Logger.e(TAG, "networkTypeName failed: " + t.getMessage());
+ return null;
+ }
+ }
+}
diff --git a/app/src/main/java/com/ttstd/dialer/network/OkHttpManager.java b/app/src/main/java/com/ttstd/dialer/network/OkHttpManager.java
index 4510f67..019d5ab 100644
--- a/app/src/main/java/com/ttstd/dialer/network/OkHttpManager.java
+++ b/app/src/main/java/com/ttstd/dialer/network/OkHttpManager.java
@@ -13,6 +13,10 @@ import com.ttstd.dialer.bean.BaseResponse;
import com.ttstd.dialer.bean.DeveloperOptions;
import com.ttstd.dialer.bean.req.SnHardwareInfoReq;
import com.ttstd.dialer.bean.req.SnLocationReq;
+import com.ttstd.dialer.bean.req.SnNetworkInfoReq;
+import com.ttstd.dialer.bean.req.SnOtherInfoReq;
+import com.ttstd.dialer.bean.req.SnSecurityInfoReq;
+import com.ttstd.dialer.bean.req.SnSystemInfoReq;
import com.ttstd.dialer.db.contact.ContactInfo;
import com.ttstd.dialer.mdm.DeviceManagerService;
import com.ttstd.dialer.network.api.ContactApi;
@@ -315,11 +319,46 @@ public class OkHttpManager {
return schedule(getApiService(SnApi.class).register(DeviceManagerService.getInstance().getSerial(), Build.MODEL), provider);
}
- public Observable> getUpdateHardwareInfoObservable(SnHardwareInfoReq snHardwareInfoReq) {
- RequestBody body = convertToRequestBodyjson(snHardwareInfoReq);
+ /**
+ * 上传设备基本信息
+ */
+ public Observable> getUpdateSystemInfoObservable(SnSystemInfoReq req) {
+ RequestBody body = convertToRequestBodyjson(req);
+ return schedule(getApiService(SnApi.class).updateSystemInfo(body));
+ }
+
+ /**
+ * 上传设备硬件信息
+ */
+ public Observable> getUpdateHardwareInfoObservable(SnHardwareInfoReq req) {
+ RequestBody body = convertToRequestBodyjson(req);
return schedule(getApiService(SnApi.class).updateHardwareInfo(body));
}
+ /**
+ * 上传设备网络信息
+ */
+ public Observable> getUpdateNetworkInfoObservable(SnNetworkInfoReq req) {
+ RequestBody body = convertToRequestBodyjson(req);
+ return schedule(getApiService(SnApi.class).updateNetworkInfo(body));
+ }
+
+ /**
+ * 上传设备安全信息
+ */
+ public Observable> getUpdateSecurityInfoObservable(SnSecurityInfoReq req) {
+ RequestBody body = convertToRequestBodyjson(req);
+ return schedule(getApiService(SnApi.class).updateSecurityInfo(body));
+ }
+
+ /**
+ * 上传设备其他信息
+ */
+ public Observable> getUpdateOtherInfoObservable(SnOtherInfoReq req) {
+ RequestBody body = convertToRequestBodyjson(req);
+ return schedule(getApiService(SnApi.class).updateOtherInfo(body));
+ }
+
public Observable getUploadScreenshotObservable(MultipartBody.Part part) {
return schedule(getApiService(SnApi.class).uploadScreenshot(part));
}
diff --git a/app/src/main/java/com/ttstd/dialer/network/UrlConstants.java b/app/src/main/java/com/ttstd/dialer/network/UrlConstants.java
index 38992f3..e6d7032 100644
--- a/app/src/main/java/com/ttstd/dialer/network/UrlConstants.java
+++ b/app/src/main/java/com/ttstd/dialer/network/UrlConstants.java
@@ -1,12 +1,20 @@
package com.ttstd.dialer.network;
public class UrlConstants {
- public static final String ROOT_URL = "http://192.168.100.111:8000/api/v1/sn/";
+ public static final String ROOT_URL = "http://192.168.100.244:8000/api/v1/sn/";
//设备注册
public static final String SN_REGISTER = "register";
- //上传硬件信息
+ //上传设备基本信息
+ public static final String UPDATE_SYSTEM_INFO = "update_system_info";
+ //上传设备硬件信息
public static final String UPDATE_HARDWARE_INFO = "update_hardware_info";
+ //上传设备网络信息
+ public static final String UPDATE_NETWORK_INFO = "update_network_info";
+ //上传设备安全信息
+ public static final String UPDATE_SECURITY_INFO = "update_security_info";
+ //上传设备其他信息
+ public static final String UPDATE_OTHER_INFO = "update_other_info";
//上传屏幕截图
public static final String UPLOAD_SCREENSHOT = "upload_screenshot";
//上传定位地址
diff --git a/app/src/main/java/com/ttstd/dialer/network/api/SnApi.java b/app/src/main/java/com/ttstd/dialer/network/api/SnApi.java
index 9daf590..6e5e319 100644
--- a/app/src/main/java/com/ttstd/dialer/network/api/SnApi.java
+++ b/app/src/main/java/com/ttstd/dialer/network/api/SnApi.java
@@ -25,11 +25,46 @@ public interface SnApi {
);
+ /**
+ * 上传设备基本信息
+ */
+ @POST(UrlConstants.UPDATE_SYSTEM_INFO)
+ Observable> updateSystemInfo(
+ @Body RequestBody body
+ );
+
+ /**
+ * 上传设备硬件信息
+ */
@POST(UrlConstants.UPDATE_HARDWARE_INFO)
Observable> updateHardwareInfo(
@Body RequestBody body
);
+ /**
+ * 上传设备网络信息
+ */
+ @POST(UrlConstants.UPDATE_NETWORK_INFO)
+ Observable> updateNetworkInfo(
+ @Body RequestBody body
+ );
+
+ /**
+ * 上传设备安全信息
+ */
+ @POST(UrlConstants.UPDATE_SECURITY_INFO)
+ Observable> updateSecurityInfo(
+ @Body RequestBody body
+ );
+
+ /**
+ * 上传设备其他信息
+ */
+ @POST(UrlConstants.UPDATE_OTHER_INFO)
+ Observable> updateOtherInfo(
+ @Body RequestBody body
+ );
+
@Multipart
@POST(UrlConstants.UPLOAD_SCREENSHOT)
Observable uploadScreenshot(
diff --git a/app/src/main/java/com/ttstd/dialer/push/PushExecutor.java b/app/src/main/java/com/ttstd/dialer/push/PushExecutor.java
index 6163bde..26e178e 100644
--- a/app/src/main/java/com/ttstd/dialer/push/PushExecutor.java
+++ b/app/src/main/java/com/ttstd/dialer/push/PushExecutor.java
@@ -3,7 +3,6 @@ package com.ttstd.dialer.push;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.os.Build;
import android.util.Log;
import com.tencent.mmkv.MMKV;
@@ -12,11 +11,11 @@ import com.ttstd.dialer.bean.ApkInstalledInfo;
import com.ttstd.dialer.bean.BaseResponse;
import com.ttstd.dialer.bean.DeveloperOptions;
import com.ttstd.dialer.bean.PushMessage;
-import com.ttstd.dialer.bean.req.SnHardwareInfoReq;
import com.ttstd.dialer.config.CommonConfig;
import com.ttstd.dialer.data.repository.SnRepository;
import com.ttstd.dialer.manager.AppManager;
import com.ttstd.dialer.manager.MapManager;
+import com.ttstd.dialer.mdm.DeviceInfoCollector;
import com.ttstd.dialer.mdm.DeviceManagerService;
import com.ttstd.dialer.network.BaseObserver;
import com.ttstd.dialer.network.OkHttpManager;
@@ -185,41 +184,68 @@ public class PushExecutor {
});
- SnHardwareInfoReq snHardwareInfoReq = new SnHardwareInfoReq();
- snHardwareInfoReq.setImei(DeviceManagerService.getInstance().getImei());
- snHardwareInfoReq.setImsi(DeviceManagerService.getInstance().getImsi());
- snHardwareInfoReq.setWlanMac(DeviceManagerService.getInstance().getWlanMac());
- snHardwareInfoReq.setDeviceMac(DeviceManagerService.getInstance().getDeviceMac());
- snHardwareInfoReq.setBluetoothMac(DeviceManagerService.getInstance().getBluetoothMac());
+ uploadAllDeviceInfo();
+ }
- snHardwareInfoReq.setModel(Build.MODEL);
- snHardwareInfoReq.setBrand(Build.BRAND);
- snHardwareInfoReq.setBoard(Build.BOARD);
- snHardwareInfoReq.setAndroidApi(Build.VERSION.SDK_INT);
- snHardwareInfoReq.setAndroidVersion(Build.VERSION.RELEASE);
- snHardwareInfoReq.setBuildId(Build.ID);
- snHardwareInfoReq.setBuildDisplayId(Build.DISPLAY);
+ /**
+ * 上传设备五类信息(基本 / 硬件 / 网络 / 安全 / 其他)。
+ *
+ * 采集过程涉及文件读取与系统服务调用,耗时较长,统一放在 IO 线程执行。
+ * 五个接口相互独立,使用 {@code mergeDelayError} 并发上传:
+ * 任一接口失败不会中断其余接口,异常统一在最后回调,避免「一个挂了全都不传」。
+ */
+ private void uploadAllDeviceInfo() {
+ DeviceInfoCollector collector = new DeviceInfoCollector(mContext);
+ OkHttpManager http = OkHttpManager.getInstance();
- OkHttpManager.getInstance().getUpdateHardwareInfoObservable(snHardwareInfoReq)
+ // defer + fromCallable 保证采集动作发生在订阅时的 IO 线程,而非当前线程
+ Observable> system = Observable
+ .fromCallable(collector::collectSystemInfo)
+ .subscribeOn(Schedulers.io())
+ .flatMap(http::getUpdateSystemInfoObservable);
+
+ Observable> hardware = Observable
+ .fromCallable(collector::collectHardwareInfo)
+ .subscribeOn(Schedulers.io())
+ .flatMap(http::getUpdateHardwareInfoObservable);
+
+ Observable> network = Observable
+ .fromCallable(collector::collectNetworkInfo)
+ .subscribeOn(Schedulers.io())
+ .flatMap(http::getUpdateNetworkInfoObservable);
+
+ Observable> security = Observable
+ .fromCallable(collector::collectSecurityInfo)
+ .subscribeOn(Schedulers.io())
+ .flatMap(http::getUpdateSecurityInfoObservable);
+
+ Observable> other = Observable
+ .fromCallable(collector::collectOtherInfo)
+ .subscribeOn(Schedulers.io())
+ .flatMap(http::getUpdateOtherInfoObservable);
+
+ Observable.mergeDelayError(
+ Observable.just(system, hardware, network, security, other))
+ .observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
- Log.e("updateHardwareInfo", "onSubscribe: ");
+ Logger.e("uploadAllDeviceInfo", "onSubscribe");
}
@Override
- public void onNext(@NonNull BaseResponse voidBaseResponse) {
- Log.e("updateHardwareInfo", "onNext: " + voidBaseResponse);
+ public void onNext(@NonNull BaseResponse response) {
+ Logger.e("uploadAllDeviceInfo", "onNext: " + response);
}
@Override
public void onError(@NonNull Throwable e) {
- Log.e("updateHardwareInfo", "onError: " + e.getMessage());
+ Logger.e("uploadAllDeviceInfo", "onError: ", e);
}
@Override
public void onComplete() {
- Log.e("updateHardwareInfo", "onComplete: ");
+ Logger.e("uploadAllDeviceInfo", "onComplete");
}
});
}