feat(push): 支持解绑手机号与设备策略推送
- 新增 contentType=18 解绑标记,上报 otherInfo 时不再携带 SIM 卡手机号 - 新增 contentType=19 设备策略解析并落库到 MMKV - 屏幕采集服务改为按需启动,远程会话结束后停止并释放资源 - 增加通知心跳刷新,避免静止画面不推帧导致黑屏
This commit is contained in:
@@ -73,4 +73,53 @@ public class CommonConfig {
|
|||||||
@Deprecated
|
@Deprecated
|
||||||
public static final String CURRENT_LOCATION_ID_KEY = "current_map_location_ID";
|
public static final String CURRENT_LOCATION_ID_KEY = "current_map_location_ID";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备解绑标记(1=已解除手机号绑定,0=未解绑)。
|
||||||
|
* <p>
|
||||||
|
* 由服务端下发「解除绑定」推送(contentType = 18)置为 1。
|
||||||
|
* 置为 1 后,设备上报 {@code otherInfo} 时不再携带手机号,
|
||||||
|
* 从而避免服务端把 SIM 卡手机号重新写回 {@code sys_sn.snMobile}。
|
||||||
|
*/
|
||||||
|
public static final String KEY_DEVICE_UNBIND_MOBILE = "device_unbind_mobile_key";
|
||||||
|
|
||||||
|
// ===================== 设备策略开关(由服务端策略推送 contentType=19 下发) =====================
|
||||||
|
/** USB偏好设置(1开 0关) */
|
||||||
|
public static final String KEY_POLICY_USB_DATA = "policy_usb_data_key";
|
||||||
|
/** 时间设置管控(1开 0关) */
|
||||||
|
public static final String KEY_POLICY_TIME_SETTING = "policy_time_setting_key";
|
||||||
|
/** 存储卡(1开 0关) */
|
||||||
|
public static final String KEY_POLICY_STORAGE_CARD = "policy_storage_card_key";
|
||||||
|
/** 恢复出厂(1开 0关) */
|
||||||
|
public static final String KEY_POLICY_FACTORY_RESET = "policy_factory_reset_key";
|
||||||
|
/** WiFi热点开关(1开 0关) */
|
||||||
|
public static final String KEY_POLICY_WIFI_HOTSPOT = "policy_wifi_hotspot_key";
|
||||||
|
/** 蓝牙开关(1开 0关) */
|
||||||
|
public static final String KEY_POLICY_BLUETOOTH_SWITCH = "policy_bluetooth_switch_key";
|
||||||
|
/** 壁纸功能(1开 0关) */
|
||||||
|
public static final String KEY_POLICY_WALLPAPER = "policy_wallpaper_key";
|
||||||
|
/** 通知栏显示(1开 0关) */
|
||||||
|
public static final String KEY_POLICY_NOTIFICATION_BAR = "policy_notification_bar_key";
|
||||||
|
/** 状态栏下拉开关(1开 0关) */
|
||||||
|
public static final String KEY_POLICY_STATUS_BAR_PULL_DOWN = "policy_status_bar_pull_down_key";
|
||||||
|
/** 系统导航条显示(1开 0关) */
|
||||||
|
public static final String KEY_POLICY_SYSTEM_NAV_BAR_SHOW = "policy_system_nav_bar_show_key";
|
||||||
|
/** 系统导航栏设置(1开 0关) */
|
||||||
|
public static final String KEY_POLICY_SYSTEM_NAV_BAR_SETTING = "policy_system_nav_bar_setting_key";
|
||||||
|
/** 导航条选项(1开 0关) */
|
||||||
|
public static final String KEY_POLICY_NAV_BAR_OPTIONS = "policy_nav_bar_options_key";
|
||||||
|
/** 蓝牙功能开关(1开 0关) */
|
||||||
|
public static final String KEY_POLICY_BLUETOOTH_FUNCTION = "policy_bluetooth_function_key";
|
||||||
|
/** OTA升级管控(1开 0关) */
|
||||||
|
public static final String KEY_POLICY_OTA_UPGRADE = "policy_ota_upgrade_key";
|
||||||
|
/** 应用安装开关(1开 0关) */
|
||||||
|
public static final String KEY_POLICY_APP_INSTALL = "policy_app_install_key";
|
||||||
|
/** 自动旋转(1开 0关) */
|
||||||
|
public static final String KEY_POLICY_AUTO_ROTATE = "policy_auto_rotate_key";
|
||||||
|
/** 自动亮度(1开 0关) */
|
||||||
|
public static final String KEY_POLICY_AUTO_BRIGHTNESS = "policy_auto_brightness_key";
|
||||||
|
/** 护眼模式(1开 0关) */
|
||||||
|
public static final String KEY_POLICY_EYE_PROTECTION = "policy_eye_protection_key";
|
||||||
|
/** 深色模式(1开 0关) */
|
||||||
|
public static final String KEY_POLICY_DARK_MODE = "policy_dark_mode_key";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,9 +34,11 @@ import com.ttstd.dialer.bean.req.SnNetworkInfoReq;
|
|||||||
import com.ttstd.dialer.bean.req.SnOtherInfoReq;
|
import com.ttstd.dialer.bean.req.SnOtherInfoReq;
|
||||||
import com.ttstd.dialer.bean.req.SnSecurityInfoReq;
|
import com.ttstd.dialer.bean.req.SnSecurityInfoReq;
|
||||||
import com.ttstd.dialer.bean.req.SnSystemInfoReq;
|
import com.ttstd.dialer.bean.req.SnSystemInfoReq;
|
||||||
|
import com.ttstd.dialer.config.CommonConfig;
|
||||||
import com.ttstd.dialer.utils.Logger;
|
import com.ttstd.dialer.utils.Logger;
|
||||||
import com.ttstd.dialer.utils.SystemUtils;
|
import com.ttstd.dialer.utils.SystemUtils;
|
||||||
|
|
||||||
|
import com.tencent.mmkv.MMKV;
|
||||||
import cn.jpush.android.api.JPushInterface;
|
import cn.jpush.android.api.JPushInterface;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
@@ -625,7 +627,10 @@ public class DeviceInfoCollector {
|
|||||||
(TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
|
(TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
|
||||||
if (tm != null) {
|
if (tm != null) {
|
||||||
req.setIccid(safeString(tm::getSimSerialNumber));
|
req.setIccid(safeString(tm::getSimSerialNumber));
|
||||||
req.setPhoneNumber(safeString(tm::getLine1Number));
|
// 已解除绑定:不再上报 SIM 卡手机号,避免服务端重新写回 sys_sn.snMobile
|
||||||
|
if (!isUnbindMobile()) {
|
||||||
|
req.setPhoneNumber(safeString(tm::getLine1Number));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
@@ -728,6 +733,22 @@ public class DeviceInfoCollector {
|
|||||||
// 工具方法
|
// 工具方法
|
||||||
// ==================================================================
|
// ==================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否已解除手机号绑定。
|
||||||
|
* <p>
|
||||||
|
* 由服务端「解除绑定」推送(contentType = 18)在 {@link CommonConfig#KEY_DEVICE_UNBIND_MOBILE}
|
||||||
|
* 写入 1。置位后上报 {@code otherInfo} 不再携带 SIM 卡手机号。
|
||||||
|
*/
|
||||||
|
private boolean isUnbindMobile() {
|
||||||
|
try {
|
||||||
|
MMKV mmkv = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
|
||||||
|
return mmkv.decodeInt(CommonConfig.KEY_DEVICE_UNBIND_MOBILE, 0) == 1;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
Logger.e(TAG, "isUnbindMobile failed: " + t.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 供 lambda 使用的取值接口,用于统一包裹异常。
|
* 供 lambda 使用的取值接口,用于统一包裹异常。
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -91,6 +91,8 @@ public class PushExecutor {
|
|||||||
private static final String DEVICE_CLEAN = "15";
|
private static final String DEVICE_CLEAN = "15";
|
||||||
private static final String DEVICE_MIRROR = "16";
|
private static final String DEVICE_MIRROR = "16";
|
||||||
private static final String DEVICE_SCREEN_LOCK = "17";
|
private static final String DEVICE_SCREEN_LOCK = "17";
|
||||||
|
private static final String DEVICE_UNBIND_MOBILE = "18";
|
||||||
|
private static final String DEVICE_POLICY = "19";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -198,6 +200,12 @@ public class PushExecutor {
|
|||||||
case DEVICE_SCREEN_LOCK:
|
case DEVICE_SCREEN_LOCK:
|
||||||
screenLock();
|
screenLock();
|
||||||
break;
|
break;
|
||||||
|
case DEVICE_UNBIND_MOBILE:
|
||||||
|
unbindMobile();
|
||||||
|
break;
|
||||||
|
case DEVICE_POLICY:
|
||||||
|
setDevicePolicy(extra);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
Logger.e(TAG, "setPushMessage: default");
|
Logger.e(TAG, "setPushMessage: default");
|
||||||
}
|
}
|
||||||
@@ -710,7 +718,7 @@ public class PushExecutor {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (acceptKey == 1 || isAutoRemoteAssistanceEnabled()) {
|
if (acceptKey == 1) {
|
||||||
// 自动接收并采集窗口串流(推送指定自动,或本地已开启自动接收开关)
|
// 自动接收并采集窗口串流(推送指定自动,或本地已开启自动接收开关)
|
||||||
Logger.d(TAG, "mirror: 自动接收远程协助,启动采集");
|
Logger.d(TAG, "mirror: 自动接收远程协助,启动采集");
|
||||||
ScreenCaptureActivity.start(mContext);
|
ScreenCaptureActivity.start(mContext);
|
||||||
@@ -754,18 +762,6 @@ public class PushExecutor {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 读取本地"自动接收远程协助"开关(与设置页 siAutoRemoteAssistance 同步)。
|
|
||||||
*/
|
|
||||||
private boolean isAutoRemoteAssistanceEnabled() {
|
|
||||||
try {
|
|
||||||
MMKV mmkv = MMKV.mmkvWithID("MMKV_DEFAULT");
|
|
||||||
return mmkv != null && mmkv.decodeBool("AutoRemoteAssistanceEnable", false);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 屏幕锁定(contentType = 17, screenLock)。
|
* 屏幕锁定(contentType = 17, screenLock)。
|
||||||
* <p>
|
* <p>
|
||||||
@@ -776,6 +772,156 @@ public class PushExecutor {
|
|||||||
Logger.e(TAG, "screenLock: 收到屏幕锁定推送,功能待实现");
|
Logger.e(TAG, "screenLock: 收到屏幕锁定推送,功能待实现");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解除设备手机号绑定(contentType = 18, unbindMobile)。
|
||||||
|
* <p>
|
||||||
|
* 由后端 {@code DeviceService.unbindMobile} 下发。收到后在本地标记「已解绑」,
|
||||||
|
* 之后上报 {@code otherInfo} 不再携带 SIM 卡手机号,避免服务端重新建立绑定。
|
||||||
|
*/
|
||||||
|
private void unbindMobile() {
|
||||||
|
Logger.e(TAG, "unbindMobile: 收到解除绑定推送");
|
||||||
|
try {
|
||||||
|
mMMKV.encode(CommonConfig.KEY_DEVICE_UNBIND_MOBILE, 1);
|
||||||
|
Logger.e(TAG, "unbindMobile: 已本地标记解除绑定");
|
||||||
|
} catch (Throwable t) {
|
||||||
|
Logger.e(TAG, "unbindMobile failed: " + t.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置设备策略开关(contentType = 19, policy)。
|
||||||
|
* <p>
|
||||||
|
* 由后端在策略全量保存 / 单独设置后推送,extra 承载全部策略开关状态(0关闭 1开启)。
|
||||||
|
* 解析后写入本地 MMKV,并对已有设备控制能力的开关即时应用;暂无能力的开关先落库并记录日志。
|
||||||
|
*/
|
||||||
|
private void setDevicePolicy(String extra) {
|
||||||
|
Logger.e(TAG, "setDevicePolicy: 收到策略推送, extra=" + extra);
|
||||||
|
if (TextUtils.isEmpty(extra)) {
|
||||||
|
Logger.e(TAG, "setDevicePolicy: extra 为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
JsonObject json = JsonParser.parseString(extra).getAsJsonObject();
|
||||||
|
DeviceManagerService mdm = DeviceManagerService.getInstance();
|
||||||
|
|
||||||
|
// USB偏好设置
|
||||||
|
int usbData = getIntField(json, "usbData", 1);
|
||||||
|
mMMKV.encode(CommonConfig.KEY_POLICY_USB_DATA, usbData);
|
||||||
|
|
||||||
|
// 时间设置管控
|
||||||
|
int timeSetting = getIntField(json, "timeSetting", 1);
|
||||||
|
mMMKV.encode(CommonConfig.KEY_POLICY_TIME_SETTING, timeSetting);
|
||||||
|
|
||||||
|
// 存储卡
|
||||||
|
int storageCard = getIntField(json, "storageCard", 1);
|
||||||
|
mMMKV.encode(CommonConfig.KEY_POLICY_STORAGE_CARD, storageCard);
|
||||||
|
|
||||||
|
// 恢复出厂
|
||||||
|
int factoryReset = getIntField(json, "factoryReset", 1);
|
||||||
|
mMMKV.encode(CommonConfig.KEY_POLICY_FACTORY_RESET, factoryReset);
|
||||||
|
|
||||||
|
// WiFi热点开关
|
||||||
|
int wifiHotspot = getIntField(json, "wifiHotspot", 1);
|
||||||
|
mMMKV.encode(CommonConfig.KEY_POLICY_WIFI_HOTSPOT, wifiHotspot);
|
||||||
|
|
||||||
|
// 蓝牙开关
|
||||||
|
int bluetoothSwitch = getIntField(json, "bluetoothSwitch", 1);
|
||||||
|
mMMKV.encode(CommonConfig.KEY_POLICY_BLUETOOTH_SWITCH, bluetoothSwitch);
|
||||||
|
|
||||||
|
// 壁纸功能
|
||||||
|
int wallpaper = getIntField(json, "wallpaper", 1);
|
||||||
|
mMMKV.encode(CommonConfig.KEY_POLICY_WALLPAPER, wallpaper);
|
||||||
|
|
||||||
|
// 通知栏显示
|
||||||
|
int notificationBar = getIntField(json, "notificationBar", 1);
|
||||||
|
mMMKV.encode(CommonConfig.KEY_POLICY_NOTIFICATION_BAR, notificationBar);
|
||||||
|
|
||||||
|
// 状态栏下拉开关
|
||||||
|
int statusBarPullDown = getIntField(json, "statusBarPullDown", 1);
|
||||||
|
mMMKV.encode(CommonConfig.KEY_POLICY_STATUS_BAR_PULL_DOWN, statusBarPullDown);
|
||||||
|
|
||||||
|
// 系统导航条显示
|
||||||
|
int systemNavBarShow = getIntField(json, "systemNavBarShow", 1);
|
||||||
|
mMMKV.encode(CommonConfig.KEY_POLICY_SYSTEM_NAV_BAR_SHOW, systemNavBarShow);
|
||||||
|
|
||||||
|
// 系统导航栏设置
|
||||||
|
int systemNavBarSetting = getIntField(json, "systemNavBarSetting", 1);
|
||||||
|
mMMKV.encode(CommonConfig.KEY_POLICY_SYSTEM_NAV_BAR_SETTING, systemNavBarSetting);
|
||||||
|
|
||||||
|
// 导航条选项
|
||||||
|
int navBarOptions = getIntField(json, "navBarOptions", 1);
|
||||||
|
mMMKV.encode(CommonConfig.KEY_POLICY_NAV_BAR_OPTIONS, navBarOptions);
|
||||||
|
|
||||||
|
// 蓝牙功能开关
|
||||||
|
int bluetoothFunction = getIntField(json, "bluetoothFunction", 1);
|
||||||
|
mMMKV.encode(CommonConfig.KEY_POLICY_BLUETOOTH_FUNCTION, bluetoothFunction);
|
||||||
|
|
||||||
|
// OTA升级管控
|
||||||
|
int otaUpgrade = getIntField(json, "otaUpgrade", 1);
|
||||||
|
mMMKV.encode(CommonConfig.KEY_POLICY_OTA_UPGRADE, otaUpgrade);
|
||||||
|
|
||||||
|
// 应用安装开关(对应 deviceController.installPackage / uninstallPackage 的放行控制)
|
||||||
|
int appInstall = getIntField(json, "appInstall", 1);
|
||||||
|
mMMKV.encode(CommonConfig.KEY_POLICY_APP_INSTALL, appInstall);
|
||||||
|
|
||||||
|
// 自动旋转
|
||||||
|
int autoRotate = getIntField(json, "autoRotate", 1);
|
||||||
|
mMMKV.encode(CommonConfig.KEY_POLICY_AUTO_ROTATE, autoRotate);
|
||||||
|
|
||||||
|
// 自动亮度
|
||||||
|
int autoBrightness = getIntField(json, "autoBrightness", 1);
|
||||||
|
mMMKV.encode(CommonConfig.KEY_POLICY_AUTO_BRIGHTNESS, autoBrightness);
|
||||||
|
|
||||||
|
// 护眼模式
|
||||||
|
int eyeProtection = getIntField(json, "eyeProtection", 1);
|
||||||
|
mMMKV.encode(CommonConfig.KEY_POLICY_EYE_PROTECTION, eyeProtection);
|
||||||
|
|
||||||
|
// 深色模式
|
||||||
|
int darkMode = getIntField(json, "darkMode", 1);
|
||||||
|
mMMKV.encode(CommonConfig.KEY_POLICY_DARK_MODE, darkMode);
|
||||||
|
|
||||||
|
Logger.e(TAG, "setDevicePolicy: 策略解析完成并落库, usbData=" + usbData
|
||||||
|
+ ", timeSetting=" + timeSetting
|
||||||
|
+ ", storageCard=" + storageCard
|
||||||
|
+ ", factoryReset=" + factoryReset
|
||||||
|
+ ", wifiHotspot=" + wifiHotspot
|
||||||
|
+ ", bluetoothSwitch=" + bluetoothSwitch
|
||||||
|
+ ", wallpaper=" + wallpaper
|
||||||
|
+ ", notificationBar=" + notificationBar
|
||||||
|
+ ", statusBarPullDown=" + statusBarPullDown
|
||||||
|
+ ", systemNavBarShow=" + systemNavBarShow
|
||||||
|
+ ", systemNavBarSetting=" + systemNavBarSetting
|
||||||
|
+ ", navBarOptions=" + navBarOptions
|
||||||
|
+ ", bluetoothFunction=" + bluetoothFunction
|
||||||
|
+ ", otaUpgrade=" + otaUpgrade
|
||||||
|
+ ", appInstall=" + appInstall
|
||||||
|
+ ", autoRotate=" + autoRotate
|
||||||
|
+ ", autoBrightness=" + autoBrightness
|
||||||
|
+ ", eyeProtection=" + eyeProtection
|
||||||
|
+ ", darkMode=" + darkMode);
|
||||||
|
|
||||||
|
// TODO: 针对各策略开关的 OS 级能力(导航栏/通知栏/WiFi热点/蓝牙/壁纸等)已具备落地数据,
|
||||||
|
// 接入对应 DeviceManagerService / DeviceController 能力后在此即时应用。
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.e(TAG, "setDevicePolicy: 解析策略失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从推送 extra JSON 中读取整型字段;字段缺失或解析失败时返回默认值。
|
||||||
|
*/
|
||||||
|
private int getIntField(JsonObject json, String field, int defaultValue) {
|
||||||
|
try {
|
||||||
|
if (json.has(field)) {
|
||||||
|
return json.get(field).getAsInt();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.e(TAG, "getIntField: " + field + " 解析失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重启(重启系统)(contentType = 12, reboot)。
|
* 重启(重启系统)(contentType = 12, reboot)。
|
||||||
* 通过 {@code reboot} 命令实现,需要设备具备 root / shell 权限。
|
* 通过 {@code reboot} 命令实现,需要设备具备 root / shell 权限。
|
||||||
|
|||||||
@@ -281,7 +281,8 @@ public class ScreenCaptureService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isInitialized) {
|
if (isInitialized) {
|
||||||
return START_STICKY;
|
// 服务已在运行:不自动重启(远程控制会话结束后应保持停止,等待下一次命令拉起)
|
||||||
|
return START_NOT_STICKY;
|
||||||
}
|
}
|
||||||
|
|
||||||
int resultCode = (sResultCode != Activity.RESULT_CANCELED)
|
int resultCode = (sResultCode != Activity.RESULT_CANCELED)
|
||||||
@@ -301,7 +302,9 @@ public class ScreenCaptureService extends Service {
|
|||||||
|
|
||||||
isInitialized = true;
|
isInitialized = true;
|
||||||
startScreenCapture(resultCode, resultData);
|
startScreenCapture(resultCode, resultData);
|
||||||
return START_STICKY;
|
// 按需启动:远程控制会话结束(onControllerDisconnected)即 stopSelf,
|
||||||
|
// 且不随系统重启自动复活,避免空转占用前台服务与 MediaProjection。
|
||||||
|
return START_NOT_STICKY;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startScreenCapture(int resultCode, Intent resultData) {
|
private void startScreenCapture(int resultCode, Intent resultData) {
|
||||||
@@ -365,6 +368,11 @@ public class ScreenCaptureService extends Service {
|
|||||||
});
|
});
|
||||||
|
|
||||||
webRtcClient.setVideoCapturer(screenCapturer, width, height, fps);
|
webRtcClient.setVideoCapturer(screenCapturer, width, height, fps);
|
||||||
|
|
||||||
|
// 启动心脏起搏器:周期刷新前台通知,强制 MediaProjection 持续产帧,
|
||||||
|
// 避免静止画面(无触摸/无变化)下不推帧导致控制端黑屏。
|
||||||
|
mainHandler.removeCallbacks(heartbeatRunnable);
|
||||||
|
mainHandler.post(heartbeatRunnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 解析信令服务器地址(ws:// 或 wss://)。 */
|
/** 解析信令服务器地址(ws:// 或 wss://)。 */
|
||||||
@@ -496,9 +504,16 @@ public class ScreenCaptureService extends Service {
|
|||||||
|
|
||||||
private void onSignalServerDisconnected() {
|
private void onSignalServerDisconnected() {
|
||||||
if (isShuttingDown) return;
|
if (isShuttingDown) return;
|
||||||
Log.i(TAG, "Signal server disconnected, will auto-reconnect");
|
Log.i(TAG, "Signal server disconnected");
|
||||||
updateNotification("信令断开,正在重连...");
|
|
||||||
if (stateListener != null) stateListener.onSignalDisconnected();
|
if (stateListener != null) stateListener.onSignalDisconnected();
|
||||||
|
// 信令已断开:若当前无控制端在观看,采集服务已无意义,直接停止;
|
||||||
|
// 全局信令单例会自行重连,待下次命令再拉起本服务。
|
||||||
|
if (controllingName == null || controllingName.isEmpty()) {
|
||||||
|
Log.i(TAG, "No active controller, stopping screen capture service");
|
||||||
|
stopSelf();
|
||||||
|
} else {
|
||||||
|
updateNotification("信令断开,正在重连...");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onControllerDisconnected(String controllerId) {
|
private void onControllerDisconnected(String controllerId) {
|
||||||
@@ -506,9 +521,11 @@ public class ScreenCaptureService extends Service {
|
|||||||
String name = (controllingName != null && !controllingName.isEmpty())
|
String name = (controllingName != null && !controllingName.isEmpty())
|
||||||
? controllingName : controllerId;
|
? controllingName : controllerId;
|
||||||
Log.i(TAG, "Remote controller disconnected: " + name);
|
Log.i(TAG, "Remote controller disconnected: " + name);
|
||||||
updateNotification("远程控制服务正在运行");
|
|
||||||
if (stateListener != null) stateListener.onControllerDisconnected(name);
|
if (stateListener != null) stateListener.onControllerDisconnected(name);
|
||||||
controllingName = null;
|
controllingName = null;
|
||||||
|
// 远程控制会话已结束:停止前台采集服务,释放 MediaProjection / WebRTC 等资源。
|
||||||
|
// 全局信令 WebSocket 由 siRemoteAssistance 开关控制,不受本服务停止影响。
|
||||||
|
stopSelf();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendConnectionRejected(String controllerId) {
|
private void sendConnectionRejected(String controllerId) {
|
||||||
@@ -717,6 +734,8 @@ public class ScreenCaptureService extends Service {
|
|||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
isShuttingDown = true;
|
isShuttingDown = true;
|
||||||
|
// 停止心脏起搏器
|
||||||
|
mainHandler.removeCallbacks(heartbeatRunnable);
|
||||||
sResultCode = Activity.RESULT_CANCELED;
|
sResultCode = Activity.RESULT_CANCELED;
|
||||||
sResultData = null;
|
sResultData = null;
|
||||||
if (instance == this) instance = null;
|
if (instance == this) instance = null;
|
||||||
@@ -754,6 +773,40 @@ public class ScreenCaptureService extends Service {
|
|||||||
manager.notify(NOTIFICATION_ID, createNotification(contentText));
|
manager.notify(NOTIFICATION_ID, createNotification(contentText));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 心脏起搏器:周期刷新前台通知,强制 MediaProjection 持续产帧,
|
||||||
|
* 解决静止画面(无触摸/无变化的屏幕)下 MediaProjection 不推帧、
|
||||||
|
* 控制端看到黑屏或"等待画面"的问题。
|
||||||
|
*
|
||||||
|
* 优化点(避免打扰用户):
|
||||||
|
* - 通知渠道为 IMPORTANCE_LOW + setSilent(true):周期刷新不响铃/不振铃/不弹横幅。
|
||||||
|
* - 仅在「有远端控制端正在观看」时(controllingName != null)才刷新通知扰动推帧;
|
||||||
|
* 空闲(无控制端连接)时不扰动、也不反复刷新,既省电又安静。
|
||||||
|
* - 每 1 秒刷新一次通知文案(叠加动态圆点),状态栏变化即触发采集管线产出一帧。
|
||||||
|
*/
|
||||||
|
private final Runnable heartbeatRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (!isShuttingDown) {
|
||||||
|
updateNotificationHeartbeat();
|
||||||
|
mainHandler.postDelayed(this, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private int heartbeatCount = 0;
|
||||||
|
|
||||||
|
private void updateNotificationHeartbeat() {
|
||||||
|
// 空闲时(无控制端在观看)不做任何扰动,避免无谓刷新/打扰。
|
||||||
|
if (controllingName == null || controllingName.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StringBuilder dots = new StringBuilder();
|
||||||
|
for (int i = 0; i < (heartbeatCount % 3) + 1; i++) dots.append(".");
|
||||||
|
heartbeatCount++;
|
||||||
|
updateNotification("远程控制服务运行中" + dots);
|
||||||
|
}
|
||||||
|
|
||||||
private Notification createNotification() {
|
private Notification createNotification() {
|
||||||
return createNotification("远程控制服务正在运行");
|
return createNotification("远程控制服务正在运行");
|
||||||
}
|
}
|
||||||
@@ -768,16 +821,21 @@ public class ScreenCaptureService extends Service {
|
|||||||
.setContentText(contentText)
|
.setContentText(contentText)
|
||||||
.setSmallIcon(android.R.drawable.ic_menu_camera)
|
.setSmallIcon(android.R.drawable.ic_menu_camera)
|
||||||
.setContentIntent(pendingIntent)
|
.setContentIntent(pendingIntent)
|
||||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
// 静默:即便周期刷新也不响铃/不振铃/不弹横幅(setOnlyAlertOnce 保证重复 notify 不再提醒)
|
||||||
|
.setSilent(true)
|
||||||
|
.setOnlyAlertOnce(true)
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createNotificationChannel() {
|
private void createNotificationChannel() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
// IMPORTANCE_LOW:静默通知,不响铃/不振铃/不弹横幅,仅在通知栏安静展示,
|
||||||
|
// 避免"心脏起搏器"周期刷新通知时反复打扰用户。
|
||||||
NotificationChannel channel = new NotificationChannel(
|
NotificationChannel channel = new NotificationChannel(
|
||||||
CHANNEL_ID, "屏幕共享服务", NotificationManager.IMPORTANCE_DEFAULT);
|
CHANNEL_ID, "屏幕共享服务", NotificationManager.IMPORTANCE_LOW);
|
||||||
channel.setDescription("远程控制屏幕采集前台服务");
|
channel.setDescription("远程控制屏幕采集前台服务");
|
||||||
|
channel.setShowBadge(false);
|
||||||
NotificationManager manager = getSystemService(NotificationManager.class);
|
NotificationManager manager = getSystemService(NotificationManager.class);
|
||||||
manager.createNotificationChannel(channel);
|
manager.createNotificationChannel(channel);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user