refactor(push): 重构设备信息上报为五类并发上传

将原来的单一硬件信息上报拆分为系统、硬件、网络、安全、其他五类信息,通过 mergeDelayError 并发上传,避免单点失败阻塞,并新增 DeviceInfoCollector 统一采集逻辑。
This commit is contained in:
TongTongStudio
2026-08-05 02:09:27 +08:00
parent bf36aa3303
commit 06bb744a79
10 changed files with 2185 additions and 98 deletions

View File

@@ -4,138 +4,290 @@ import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
/**
* 设备硬件信息上传请求。
* <p>
* 对应后端 {@code POST /api/v1/sn/update_hardware_info}
* 字段与后端 {@code SnHardwareInfoReq} 一一对应。
* <p>
* 使用包装类型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;
}
}

View File

@@ -0,0 +1,293 @@
package com.ttstd.dialer.bean.req;
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
/**
* 设备网络信息上传请求。
* <p>
* 对应后端 {@code POST /api/v1/sn/update_network_info}
* 字段与后端 {@code SnNetworkInfoReq} 一一对应。
* <p>
* 布尔语义字段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;
}
}

View File

@@ -0,0 +1,261 @@
package com.ttstd.dialer.bean.req;
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
/**
* 设备其他信息上传请求。
* <p>
* 对应后端 {@code POST /api/v1/sn/update_other_info}
* 字段与后端 {@code SnOtherInfoReq} 一一对应。
* <p>
* 注意:后端 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;
}
}

View File

@@ -0,0 +1,246 @@
package com.ttstd.dialer.bean.req;
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
/**
* 设备安全信息上传请求。
* <p>
* 对应后端 {@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;
}
}

View File

@@ -0,0 +1,151 @@
package com.ttstd.dialer.bean.req;
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
/**
* 设备基本信息上传请求。
* <p>
* 对应后端 {@code POST /api/v1/sn/update_system_info}
* 字段与后端 {@code SnSystemInfoReq} 一一对应。
* <p>
* 注意:该类原名为 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;
}
}

View File

@@ -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;
/**
* 设备信息采集器。
* <p>
* 负责采集后端 5 个上传接口所需的全部信息:
* <ul>
* <li>{@link #collectSystemInfo()} —— 基本信息</li>
* <li>{@link #collectHardwareInfo()} —— 硬件信息CPU/内存/存储/电池/传感器)</li>
* <li>{@link #collectNetworkInfo()} —— 网络信息WiFi/移动网络/蓝牙/代理)</li>
* <li>{@link #collectSecurityInfo()} —— 安全信息Root/调试/加密/补丁)</li>
* <li>{@link #collectOtherInfo()} —— 其他信息App 版本/存储/电池快照)</li>
* </ul>
* <p>
* 设计要点:
* <ol>
* <li>每个字段的采集都独立 try/catch单个字段失败不影响整体上报</li>
* <li>采集不到的字段保持 nullGson 默认不序列化 null避免覆盖服务端已有数据</li>
* <li>涉及权限的调用IMEI/IMSI/ICCID/手机号)均做权限检查,无权限时静默跳过;</li>
* <li>存在文件读取与 shell 探测,耗时较长,务必在子线程调用。</li>
* </ol>
*/
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<Sensor> 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;
}
}
}

View File

@@ -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<BaseResponse<Void>> getUpdateHardwareInfoObservable(SnHardwareInfoReq snHardwareInfoReq) {
RequestBody body = convertToRequestBodyjson(snHardwareInfoReq);
/**
* 上传设备基本信息
*/
public Observable<BaseResponse<Void>> getUpdateSystemInfoObservable(SnSystemInfoReq req) {
RequestBody body = convertToRequestBodyjson(req);
return schedule(getApiService(SnApi.class).updateSystemInfo(body));
}
/**
* 上传设备硬件信息
*/
public Observable<BaseResponse<Void>> getUpdateHardwareInfoObservable(SnHardwareInfoReq req) {
RequestBody body = convertToRequestBodyjson(req);
return schedule(getApiService(SnApi.class).updateHardwareInfo(body));
}
/**
* 上传设备网络信息
*/
public Observable<BaseResponse<Void>> getUpdateNetworkInfoObservable(SnNetworkInfoReq req) {
RequestBody body = convertToRequestBodyjson(req);
return schedule(getApiService(SnApi.class).updateNetworkInfo(body));
}
/**
* 上传设备安全信息
*/
public Observable<BaseResponse<Void>> getUpdateSecurityInfoObservable(SnSecurityInfoReq req) {
RequestBody body = convertToRequestBodyjson(req);
return schedule(getApiService(SnApi.class).updateSecurityInfo(body));
}
/**
* 上传设备其他信息
*/
public Observable<BaseResponse<Void>> getUpdateOtherInfoObservable(SnOtherInfoReq req) {
RequestBody body = convertToRequestBodyjson(req);
return schedule(getApiService(SnApi.class).updateOtherInfo(body));
}
public Observable<BaseResponse> getUploadScreenshotObservable(MultipartBody.Part part) {
return schedule(getApiService(SnApi.class).uploadScreenshot(part));
}

View File

@@ -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";
//上传定位地址

View File

@@ -25,11 +25,46 @@ public interface SnApi {
);
/**
* 上传设备基本信息
*/
@POST(UrlConstants.UPDATE_SYSTEM_INFO)
Observable<BaseResponse<Void>> updateSystemInfo(
@Body RequestBody body
);
/**
* 上传设备硬件信息
*/
@POST(UrlConstants.UPDATE_HARDWARE_INFO)
Observable<BaseResponse<Void>> updateHardwareInfo(
@Body RequestBody body
);
/**
* 上传设备网络信息
*/
@POST(UrlConstants.UPDATE_NETWORK_INFO)
Observable<BaseResponse<Void>> updateNetworkInfo(
@Body RequestBody body
);
/**
* 上传设备安全信息
*/
@POST(UrlConstants.UPDATE_SECURITY_INFO)
Observable<BaseResponse<Void>> updateSecurityInfo(
@Body RequestBody body
);
/**
* 上传设备其他信息
*/
@POST(UrlConstants.UPDATE_OTHER_INFO)
Observable<BaseResponse<Void>> updateOtherInfo(
@Body RequestBody body
);
@Multipart
@POST(UrlConstants.UPLOAD_SCREENSHOT)
Observable<BaseResponse> uploadScreenshot(

View File

@@ -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);
/**
* 上传设备五类信息(基本 / 硬件 / 网络 / 安全 / 其他)。
* <p>
* 采集过程涉及文件读取与系统服务调用,耗时较长,统一放在 IO 线程执行。
* 五个接口相互独立,使用 {@code mergeDelayError} 并发上传:
* 任一接口失败不会中断其余接口,异常统一在最后回调,避免「一个挂了全都不传」。
*/
private void uploadAllDeviceInfo() {
DeviceInfoCollector collector = new DeviceInfoCollector(mContext);
OkHttpManager http = OkHttpManager.getInstance();
OkHttpManager.getInstance().getUpdateHardwareInfoObservable(snHardwareInfoReq)
// defer + fromCallable 保证采集动作发生在订阅时的 IO 线程,而非当前线程
Observable<BaseResponse<Void>> system = Observable
.fromCallable(collector::collectSystemInfo)
.subscribeOn(Schedulers.io())
.flatMap(http::getUpdateSystemInfoObservable);
Observable<BaseResponse<Void>> hardware = Observable
.fromCallable(collector::collectHardwareInfo)
.subscribeOn(Schedulers.io())
.flatMap(http::getUpdateHardwareInfoObservable);
Observable<BaseResponse<Void>> network = Observable
.fromCallable(collector::collectNetworkInfo)
.subscribeOn(Schedulers.io())
.flatMap(http::getUpdateNetworkInfoObservable);
Observable<BaseResponse<Void>> security = Observable
.fromCallable(collector::collectSecurityInfo)
.subscribeOn(Schedulers.io())
.flatMap(http::getUpdateSecurityInfoObservable);
Observable<BaseResponse<Void>> 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<BaseResponse<Void>>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
Log.e("updateHardwareInfo", "onSubscribe: ");
Logger.e("uploadAllDeviceInfo", "onSubscribe");
}
@Override
public void onNext(@NonNull BaseResponse<Void> voidBaseResponse) {
Log.e("updateHardwareInfo", "onNext: " + voidBaseResponse);
public void onNext(@NonNull BaseResponse<Void> 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");
}
});
}