diff --git a/app/src/main/java/com/ttstd/dialer/bean/DeviceStatus.java b/app/src/main/java/com/ttstd/dialer/bean/DeviceStatus.java new file mode 100644 index 0000000..720e39d --- /dev/null +++ b/app/src/main/java/com/ttstd/dialer/bean/DeviceStatus.java @@ -0,0 +1,73 @@ +package com.ttstd.dialer.bean; + +import com.google.gson.annotations.SerializedName; + +/** + * MQTT 设备状态上报实体(心跳 / 屏幕亮灭)。 + *

+ * 序列化为 JSON:{"sn":"...","status":"online","ip":"...","screenOn":true|false} + */ +public class DeviceStatus { + @SerializedName("sn") + private String sn; + + @SerializedName("online") + private boolean online; + + @SerializedName("ip") + private String ip; + + @SerializedName("rssi") + private Integer rssi; + + @SerializedName("screenOn") + private boolean screenOn; + + public DeviceStatus(String sn, boolean online, String ip, Integer rssi, boolean screenOn) { + this.sn = sn; + this.online = online; + this.ip = ip; + this.rssi = rssi; + this.screenOn = screenOn; + } + + public String getSn() { + return sn; + } + + public void setSn(String sn) { + this.sn = sn; + } + + public boolean isOnline() { + return online; + } + + public void setOnline(boolean online) { + this.online = online; + } + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public Integer getRssi() { + return rssi; + } + + public void setRssi(Integer rssi) { + this.rssi = rssi; + } + + public boolean isScreenOn() { + return screenOn; + } + + public void setScreenOn(boolean screenOn) { + this.screenOn = screenOn; + } +} diff --git a/app/src/main/java/com/ttstd/dialer/manager/MqttManager.java b/app/src/main/java/com/ttstd/dialer/manager/MqttManager.java index a7d556e..3194d25 100644 --- a/app/src/main/java/com/ttstd/dialer/manager/MqttManager.java +++ b/app/src/main/java/com/ttstd/dialer/manager/MqttManager.java @@ -9,8 +9,15 @@ import android.text.TextUtils; import com.hivemq.client.mqtt.mqtt5.Mqtt5AsyncClient; import com.hivemq.client.mqtt.mqtt5.Mqtt5Client; import com.hivemq.client.mqtt.mqtt5.Mqtt5ClientBuilder; +import com.hivemq.client.mqtt.lifecycle.MqttClientConnectedContext; +import com.hivemq.client.mqtt.lifecycle.MqttClientDisconnectedContext; +import com.hivemq.client.mqtt.lifecycle.MqttDisconnectSource; +import com.hivemq.client.mqtt.mqtt5.lifecycle.Mqtt5ClientConnectedContext; +import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAck; import com.jeremyliao.liveeventbus.LiveEventBus; +import com.ttstd.dialer.bean.DeviceStatus; import com.ttstd.dialer.config.CommonConfig; +import com.ttstd.dialer.gson.GsonUtils; import com.ttstd.dialer.mdm.DeviceManagerService; import com.ttstd.dialer.utils.Logger; import com.ttstd.dialer.utils.SystemUtils; @@ -24,6 +31,7 @@ import java.util.Collections; import java.util.Enumeration; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; /** * MQTT 消息管理 @@ -39,9 +47,9 @@ public class MqttManager { /*MQTT 配置*/ public static final String MQTT_URL = "mqtt://175.178.213.60:1883"; - public static final String MQTT_CLIENT_ID = "ttstd-dialer-android"; - public static final String MQTT_USERNAME = ""; - public static final String MQTT_PASSWORD = ""; + // public static final String MQTT_CLIENT_ID = "ttstd-dialer-android"; + public static final String MQTT_USERNAME = "tt"; + public static final String MQTT_PASSWORD = "fanhuitong"; /*订阅主题*/ public static final String MQTT_TOPIC_COMMAND = "ttstd/device/command"; /*发布主题*/ @@ -62,16 +70,6 @@ public class MqttManager { */ private static final long HEARTBEAT_INTERVAL_MS = 30_000L; - /** - * 重连退避初始间隔(毫秒) - */ - private static final long RECONNECT_DELAY_INITIAL_MS = 1_000L; - - /** - * 重连退避间隔上限(毫秒),超过此值不再翻倍 - */ - private static final long RECONNECT_DELAY_MAX_MS = 64_000L; - @SuppressLint("StaticFieldLeak") private static volatile MqttManager INSTANCE; @@ -79,6 +77,11 @@ public class MqttManager { private Mqtt5AsyncClient mMqttClient; private volatile boolean mConnected; + /** + * 正在连接标记,防止并发调用 connect() 时重复 build 多个相同 clientId 的 client 互相踢 + */ + private volatile boolean mConnecting; + /** * 主动断开标记,避免主动断开时触发自动重连 */ @@ -89,16 +92,6 @@ public class MqttManager { */ private final Handler mHeartbeatHandler = new Handler(Looper.getMainLooper()); - /** - * 重连定时器(主线程 Handler) - */ - private final Handler mReconnectHandler = new Handler(Looper.getMainLooper()); - - /** - * 重连退避间隔(毫秒),随失败次数指数增长 - */ - private long mReconnectDelayMs = RECONNECT_DELAY_INITIAL_MS; - private boolean mHeartbeatRunning; /** @@ -146,7 +139,8 @@ public class MqttManager { if (TextUtils.isEmpty(sn)) { sn = ""; } - return ("{\"sn\":\"" + sn + "\",\"status\":\"offline\"}").getBytes(StandardCharsets.UTF_8); + return GsonUtils.toJSONString(new DeviceStatus(sn, true, getLocalIpAddress(), SystemUtils.getCurrentNetworkRssi(mContext), false)) + .getBytes(StandardCharsets.UTF_8); } /** @@ -157,8 +151,14 @@ public class MqttManager { if (mMqttClient != null && mConnected) { return; } + // 已在连接中(上一次 connect() 的异步回调尚未返回),避免并发重复建连导致相同 clientId 互踢 + if (mConnecting) { + Logger.e(TAG, "connect: 已有连接在进行中,跳过重复 connect"); + return; + } // 主动发起连接,清除主动断开标记,允许后续自动重连 mManualDisconnect = false; + mConnecting = true; if (mMqttClient != null) { try { mMqttClient.disconnect(); @@ -172,46 +172,43 @@ public class MqttManager { int port = uri.getPort() > 0 ? uri.getPort() : 1883; Mqtt5ClientBuilder builder = Mqtt5Client.builder() - .identifier(MQTT_CLIENT_ID) + .identifier(SystemUtils.getSerial()) .serverHost(host) .serverPort(port) - .addDisconnectedListener(context -> onDisconnected()); - if (!TextUtils.isEmpty(MQTT_USERNAME)) { - builder.simpleAuth() - .username(MQTT_USERNAME) - .password(MQTT_PASSWORD.getBytes(StandardCharsets.UTF_8)) - .applySimpleAuth(); - } + // HiveMQ 自带断线自动重连(内置指数退避),避免与自定义重连叠加导致重复连接 + .automaticReconnectWithDefaultConfig() + .addDisconnectedListener(this::onDisconnected) + .addConnectedListener(this::onConnected); + + builder.simpleAuth() + .username(MQTT_USERNAME) + .password(MQTT_PASSWORD.getBytes(StandardCharsets.UTF_8)) + .applySimpleAuth(); mMqttClient = builder.buildAsync(); mMqttClient.connectWith() .keepAlive(30) + .cleanStart(false) // 复用历史会话 + .sessionExpiryInterval(TimeUnit.DAYS.toSeconds(1)) // 断开后会话保留 .willPublish() - .topic(MQTT_TOPIC_STATUS) - .payload(buildWillPayload()) - .qos(com.hivemq.client.mqtt.datatypes.MqttQos.AT_LEAST_ONCE) - .retain(false) - .applyWillPublish() + .topic(MQTT_TOPIC_STATUS) + .payload(buildWillPayload()) + .qos(com.hivemq.client.mqtt.datatypes.MqttQos.AT_LEAST_ONCE) + .retain(false) + .applyWillPublish() .send() .whenComplete((connAck, throwable) -> { + // 首次 connect 异步结果返回,复位连接中标记 + mConnecting = false; if (throwable != null) { String msg = throwable.getMessage(); - Logger.e(TAG, "connect: 连接失败 " + msg - + ",将自动重试连接 " + host + ":" + port); + Logger.e(TAG, "connect: 首次连接失败 " + msg + + ",HiveMQ 将自动重连 " + host + ":" + port); setConnected(false); - // 首次连接失败也安排重连(网络不可达等场景) - scheduleReconnect(); } else { - Logger.e(TAG, "connect: 连接成功"); - mReconnectDelayMs = RECONNECT_DELAY_INITIAL_MS; - setConnected(true); - // 连接成功后重新订阅之前订阅的主题 - reSubscribeAll(); - // 连接成功后启动心跳上报 - startHeartbeat(); - // 连接成功后上报一次当前屏幕开关状态 - reportScreenStateOnConnect(); + // 首次连接成功(后续重连成功会走 onConnected 回调) + onConnected(); } }); } catch (Throwable e) { @@ -221,55 +218,71 @@ public class MqttManager { } /** - * 断线回调:若非主动断开,则按指数退避安排重连 + * 连接成功回调(首次 connect 异步结果返回时调用,无 context 信息) */ - private void onDisconnected() { + private void onConnected() { + onConnected(null); + } + + /** + * 连接成功回调(首次连接与 HiveMQ 自动重连成功都会触发) + */ + private void onConnected(MqttClientConnectedContext context) { + boolean sessionPresent = false; + if (context instanceof Mqtt5ClientConnectedContext) { + Mqtt5ConnAck connAck = ((Mqtt5ClientConnectedContext) context).getConnAck(); + sessionPresent = connAck != null && connAck.isSessionPresent(); + } + Logger.e(TAG, "onConnected: 连接成功 sessionPresent=" + sessionPresent); + setConnected(true); + // 连接成功后重新订阅之前订阅的主题 + reSubscribeAll(); + // 连接成功后启动心跳上报 + startHeartbeat(); + // 连接成功后上报一次当前屏幕开关状态 + reportScreenStateOnConnect(); + } + + /** + * 断线回调:仅做状态清理与心跳停止,重连交由 HiveMQ 自动重连处理, + * 不再自行调用 connect(),避免与 HiveMQ 的重连叠加导致重复连接。 + * 同时打印 Broker 返回的断开原因,便于定位反复断开的根因。 + */ + private void onDisconnected(MqttClientDisconnectedContext context) { if (mManualDisconnect) { Logger.e(TAG, "onDisconnected: 主动断开,不重连"); return; } - Logger.e(TAG, "onDisconnected: 连接断开,准备自动重连"); + // 解析断开来源与原因,区分"服务端踢"与"网络/客户端问题" + String source = context.getSource() != null ? context.getSource().name() : "UNKNOWN"; + String cause = context.getCause() != null ? context.getCause().toString() : "null"; + if (context.getSource() == MqttDisconnectSource.SERVER) { + // 服务端主动断开(如 clientId 冲突、鉴权失败、协议违规),reason code 有价值 + Logger.e(TAG, "onDisconnected: 服务端主动断开 source=" + source + + " cause=" + cause + + ",等待 HiveMQ 自动重连"); + } else { + Logger.e(TAG, "onDisconnected: 连接断开 source=" + source + + " cause=" + cause + + ",等待 HiveMQ 自动重连"); + } setConnected(false); stopHeartbeat(); - scheduleReconnect(); } - /** - * 安排一次延迟重连(指数退避,上限 30s) - */ - private void scheduleReconnect() { - mReconnectHandler.removeCallbacks(mReconnectRunnable); - Logger.e(TAG, "scheduleReconnect: " + (mReconnectDelayMs / 1000) + "s 后重连"); - mReconnectHandler.postDelayed(mReconnectRunnable, mReconnectDelayMs); - // 下次退避间隔翻倍,封顶 30s - mReconnectDelayMs = Math.min(mReconnectDelayMs * 2, RECONNECT_DELAY_MAX_MS); - } - - private final Runnable mReconnectRunnable = new Runnable() { - @Override - public void run() { - if (mManualDisconnect) { - return; - } - Logger.e(TAG, "reconnect: 尝试重新连接..."); - connect(); - } - }; - /** * 断开 MQTT 连接(主动断开,先发离线消息再断连,不会触发遗言) */ public void disconnect() { - // 标记主动断开,重连任务与断线监听均据此跳过重连 + // 标记主动断开,断线监听据此跳过重连 mManualDisconnect = true; - mReconnectHandler.removeCallbacks(mReconnectRunnable); stopHeartbeat(); try { if (mMqttClient != null && mConnected) { // 主动上报离线消息,确保服务端即时感知设备下线 String sn = DeviceManagerService.getInstance().getSerial(); if (!TextUtils.isEmpty(sn)) { - String offlinePayload = "{\"sn\":\"" + sn + "\",\"status\":\"offline\"}"; + String offlinePayload = GsonUtils.toJSONString(new DeviceStatus(sn, false, getLocalIpAddress(), SystemUtils.getCurrentNetworkRssi(mContext), false)); publish(MQTT_TOPIC_STATUS, offlinePayload); } mMqttClient.disconnect(); @@ -427,12 +440,11 @@ public class MqttManager { } /** - * 构建设备状态上报 JSON:{"sn":"...","status":"online","ip":"...","screenOn":true|false} + * 构建设备状态上报 JSON:{"sn":"...","online":"online","ip":"...","screenOn":true|false} */ private String buildStatusPayload(String sn, boolean screenOn) { - String ip = getLocalIpAddress(); - return "{\"sn\":\"" + sn + "\",\"status\":\"online\",\"ip\":\"" + (ip == null ? "" : ip) - + "\",\"screenOn\":" + screenOn + "}"; + DeviceStatus status = new DeviceStatus(sn, true, getLocalIpAddress(), SystemUtils.getCurrentNetworkRssi(mContext), screenOn); + return GsonUtils.toJSONString(status); } /** @@ -452,7 +464,7 @@ public class MqttManager { } catch (Throwable e) { Logger.e(TAG, "getLocalIpAddress: " + e.getMessage()); } - return ""; + return "127.0.0.1"; } private void onMessageReceived(com.hivemq.client.mqtt.mqtt5.message.publish.Mqtt5Publish publish) { diff --git a/app/src/main/java/com/ttstd/dialer/utils/SystemUtils.java b/app/src/main/java/com/ttstd/dialer/utils/SystemUtils.java index 3bcccc4..4c4bdce 100644 --- a/app/src/main/java/com/ttstd/dialer/utils/SystemUtils.java +++ b/app/src/main/java/com/ttstd/dialer/utils/SystemUtils.java @@ -18,6 +18,8 @@ import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.graphics.Bitmap; import android.graphics.Rect; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.Build; @@ -26,9 +28,13 @@ import android.os.IBinder; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; +import android.os.SystemProperties; import android.os.UserHandle; import android.provider.MediaStore; import android.provider.Settings; +import android.telephony.CellInfo; +import android.telephony.CellSignalStrength; +import android.telephony.SignalStrength; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; @@ -78,15 +84,14 @@ public class SystemUtils { * @return */ public static String getProperty(String key, String defaultValue) { - String value = defaultValue; +// SystemProperties.get(); try { Class c = Class.forName("android.os.SystemProperties"); Method get = c.getMethod("get", String.class, String.class); - value = (String) (get.invoke(c, key, Build.UNKNOWN)); + return (String) (get.invoke(c, key, defaultValue)); } catch (Exception e) { e.printStackTrace(); - } finally { - return value; + return defaultValue; } } @@ -366,6 +371,135 @@ public class SystemUtils { return ""; } + /** + * 获取当前蜂窝信号强度等级(0~4,对应 ASU 的粗略分级,4 最强)。 + * 该值可用于状态栏信号格显示,与 {@link android.telephony.SignalStrength#getLevel()} 一致。 + * + * @param context 上下文 + * @return 信号等级 0~4;无 SIM / 非蜂窝网络 / 获取失败返回 -1 + */ + @SuppressLint("MissingPermission") + public static int getSignalLevel(Context context) { + try { + TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + if (tm == null) { + return -1; + } + // Android 9 (API 28) 及以下无 getAllCellInfo/SignalStrength 的公开 getLevel, + // 这里统一用 getAllCellInfo 取最强小区的 level,覆盖绝大多数系统版本。 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + List cellInfos = tm.getAllCellInfo(); + if (cellInfos != null) { + int maxLevel = -1; + for (CellInfo info : cellInfos) { + if (info == null || !info.isRegistered()) { + continue; + } + CellSignalStrength strength = info.getCellSignalStrength(); + if (strength != null) { + maxLevel = Math.max(maxLevel, strength.getLevel()); + } + } + if (maxLevel >= 0) { + return maxLevel; + } + } + } + // 兜底:API 29+ 可直接取 SignalStrength 的 level + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + SignalStrength signalStrength = tm.getSignalStrength(); + if (signalStrength != null) { + return signalStrength.getLevel(); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return -1; + } + + /** + * 获取当前蜂窝信号强度(dBm,如 -75 表示较强,-113 接近无信号)。 + * 优先使用各制式 CellInfo 的 dBm,找不到则回退到 ASU 换算(asu - 113 - 2 * (asu & 1))。 + * + * @param context 上下文 + * @return 信号强度 dBm;无信号 / 获取失败返回 {@code null} + */ + @SuppressLint("MissingPermission") + public static Integer getSignalDbm(Context context) { + try { + TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + if (tm == null) { + return null; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + List cellInfos = tm.getAllCellInfo(); + if (cellInfos != null) { + int bestDbm = Integer.MIN_VALUE; + for (CellInfo info : cellInfos) { + if (info == null || !info.isRegistered()) { + continue; + } + CellSignalStrength strength = info.getCellSignalStrength(); + if (strength != null) { + int dbm = strength.getDbm(); + if (dbm != Integer.MAX_VALUE && dbm > bestDbm) { + bestDbm = dbm; + } + } + } + if (bestDbm != Integer.MIN_VALUE) { + return bestDbm; + } + } + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + SignalStrength signalStrength = tm.getSignalStrength(); + if (signalStrength != null) { + int dbm = signalStrength.getDbm(); + if (dbm != Integer.MAX_VALUE && dbm != Integer.MIN_VALUE) { + return dbm; + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + /** + * 根据当前活跃网络获取信号强度 (RSSI/dBm) + * 优先返回 Wi-Fi 信号强度,如果未连接 Wi-Fi 则返回移动网络信号强度 + * + * @param context 上下文 + * @return 信号强度 (dBm);获取失败返回 null + */ + public static Integer getCurrentNetworkRssi(Context context) { + try { + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (cm == null) return null; + + NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); + if (activeNetwork == null || !activeNetwork.isConnected()) { + return null; + } + + if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) { + WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE); + WifiInfo info = wifiManager.getConnectionInfo(); + if (info != null) { + return info.getRssi(); + } + } else if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) { + return getSignalDbm(context); + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + private static String getNetworkTypeName(int networkType) { switch (networkType) { case TelephonyManager.NETWORK_TYPE_GPRS: @@ -504,11 +638,11 @@ public class SystemUtils { } public static String getBuildId() { - return getProperty(Build.ID, Build.UNKNOWN); + return getProperty("ro.build.id", Build.ID); } public static String getBuildDisplayId() { - return getProperty(Build.DISPLAY, Build.UNKNOWN); + return getProperty("ro.build.display.id", Build.DISPLAY); } // ================================================================== @@ -527,7 +661,7 @@ public class SystemUtils { } public static String getDeviceManufacturer() { - return getProperty(Build.MANUFACTURER, Build.UNKNOWN); + return getProperty("ro.product.manufacturer", Build.MANUFACTURER); } public static String getHardware() { @@ -535,7 +669,7 @@ public class SystemUtils { } public static String getHost() { - return getProperty(Build.HOST, Build.UNKNOWN); + return getProperty("ro.build.host", Build.HOST); } public static String getAndroidId(Context context) { @@ -547,23 +681,23 @@ public class SystemUtils { } public static String getBuildFingerprint() { - return getProperty(Build.FINGERPRINT, Build.UNKNOWN); + return getProperty("ro.build.fingerprint", Build.FINGERPRINT); } public static String getBuildType() { - return getProperty(Build.TYPE, Build.UNKNOWN); + return getProperty("ro.build.type", Build.TYPE); } public static String getBuildUser() { - return getProperty(Build.USER, Build.UNKNOWN); + return getProperty("ro.build.user", Build.USER); } public static String getBuildHost() { - return getProperty(Build.HOST, Build.UNKNOWN); + return getProperty("ro.build.host", Build.HOST); } public static String getBuildTags() { - return getProperty(Build.TAGS, Build.UNKNOWN); + return getProperty("ro.build.tags", Build.TAGS); } public static long getBuildTime() { @@ -600,13 +734,30 @@ public class SystemUtils { public static String getScreenResolution(Context context) { try { - android.util.DisplayMetrics dm = context.getResources().getDisplayMetrics(); + android.util.DisplayMetrics dm = getRealDisplayMetrics(context); return dm.widthPixels + "x" + dm.heightPixels; } catch (Throwable t) { return ""; } } + /** + * 获取设备真实屏幕显示参数(包含状态栏、导航栏,不被窗口裁切)。 + * 优先使用 getRealMetrics(显示设备真实分辨率),低版本无此 API 时回退 getMetrics。 + */ + private static android.util.DisplayMetrics getRealDisplayMetrics(Context context) { + android.util.DisplayMetrics dm = new android.util.DisplayMetrics(); + android.view.WindowManager wm = + (android.view.WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + android.view.Display display = wm.getDefaultDisplay(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + display.getRealMetrics(dm); + } else { + display.getMetrics(dm); + } + return dm; + } + public static int getScreenDensity(Context context) { try { return (int) Math.round(context.getResources().getDisplayMetrics().density); @@ -635,7 +786,7 @@ public class SystemUtils { } public static String getProductName() { - return getProperty(Build.PRODUCT, Build.UNKNOWN); + return getProperty("ro.product.name", Build.PRODUCT); } public static String getProductModel() { @@ -682,7 +833,7 @@ public class SystemUtils { } public static String getBootloader() { - return getProperty(Build.BOOTLOADER, Build.UNKNOWN); + return getProperty("ro.bootloader", Build.BOOTLOADER); } public static String getBasebandVersion() { @@ -699,7 +850,7 @@ public class SystemUtils { public static String getScreenSize(Context context) { try { - android.util.DisplayMetrics dm = context.getResources().getDisplayMetrics(); + android.util.DisplayMetrics dm = getRealDisplayMetrics(context); double diagonal = Math.sqrt(Math.pow(dm.widthPixels / dm.xdpi, 2) + Math.pow(dm.heightPixels / dm.ydpi, 2)); return String.format(Locale.US, "%.1f", diagonal); @@ -710,7 +861,7 @@ public class SystemUtils { public static double getScreenInch(Context context) { try { - android.util.DisplayMetrics dm = context.getResources().getDisplayMetrics(); + android.util.DisplayMetrics dm = getRealDisplayMetrics(context); double diagonal = Math.sqrt(Math.pow(dm.widthPixels / dm.xdpi, 2) + Math.pow(dm.heightPixels / dm.ydpi, 2)); return Math.round(diagonal * 10.0) / 10.0; diff --git a/app/src/test/java/com/ttstd/dialer/bean/DeviceStatusTest.java b/app/src/test/java/com/ttstd/dialer/bean/DeviceStatusTest.java new file mode 100644 index 0000000..701d9cd --- /dev/null +++ b/app/src/test/java/com/ttstd/dialer/bean/DeviceStatusTest.java @@ -0,0 +1,69 @@ +package com.ttstd.dialer.bean; + +import com.google.gson.Gson; +import org.junit.Assert; +import org.junit.Test; + +public class DeviceStatusTest { + + @Test + public void testConstructorAndGetters() { + String sn = "SN123456"; + boolean online = true; + String ip = "192.168.1.100"; + Integer rssi = -65; + boolean screenOn = true; + + DeviceStatus status = new DeviceStatus(sn, online, ip, rssi, screenOn); + + Assert.assertEquals(sn, status.getSn()); + Assert.assertEquals(online, status.isOnline()); + Assert.assertEquals(ip, status.getIp()); + Assert.assertEquals(rssi, status.getRssi()); + Assert.assertEquals(screenOn, status.isScreenOn()); + } + + @Test + public void testSetters() { + DeviceStatus status = new DeviceStatus(null, false, null, null, false); + + status.setSn("NEW_SN"); + status.setOnline(true); + status.setIp("10.0.0.1"); + status.setRssi(-70); + status.setScreenOn(true); + + Assert.assertEquals("NEW_SN", status.getSn()); + Assert.assertTrue(status.isOnline()); + Assert.assertEquals("10.0.0.1", status.getIp()); + Assert.assertEquals(Integer.valueOf(-70), status.getRssi()); + Assert.assertTrue(status.isScreenOn()); + } + + @Test + public void testJsonSerialization() { + DeviceStatus status = new DeviceStatus("SN123", true, "1.1.1.1", -50, true); + Gson gson = new Gson(); + String json = gson.toJson(status); + + // 验证 JSON 字符串中包含正确的字段和值 + Assert.assertTrue(json.contains("\"sn\":\"SN123\"")); + Assert.assertTrue(json.contains("\"online\":true")); + Assert.assertTrue(json.contains("\"ip\":\"1.1.1.1\"")); + Assert.assertTrue(json.contains("\"rssi\":-50")); + Assert.assertTrue(json.contains("\"screenOn\":true")); + } + + @Test + public void testJsonDeserialization() { + String json = "{\"sn\":\"SN999\",\"online\":false,\"ip\":\"8.8.8.8\",\"rssi\":-80,\"screenOn\":false}"; + Gson gson = new Gson(); + DeviceStatus status = gson.fromJson(json, DeviceStatus.class); + + Assert.assertEquals("SN999", status.getSn()); + Assert.assertFalse(status.isOnline()); + Assert.assertEquals("8.8.8.8", status.getIp()); + Assert.assertEquals(Integer.valueOf(-80), status.getRssi()); + Assert.assertFalse(status.isScreenOn()); + } +} diff --git a/screenMatch.properties b/screenMatch.properties index 16c3eb4..0c3001d 100644 --- a/screenMatch.properties +++ b/screenMatch.properties @@ -19,7 +19,7 @@ base_dp=360 # Also need to match the phone screen of [match_dp]. # If you have another dp values. # System default values is 240,320,384,392,400,410,411,480,533,592,600,640,662,720,768,800,811,820,960,961,1024,1280,1365 -match_dp= +match_dp=240,260,280,300,320,360,362.2641,380,384,390,392.7272,400,410,411.4285,430,432,451.7647,480,533,540,592,600,640,662,720,768,800,811,820,960,961,1024,1280,1365 # If you not wanna to match dp values above. Write some above values here, append value with "," . # For example: 811,961,1365 ignore_dp=