refactor: 优化注释格式并修复WebSocket错误处理
This commit is contained in:
@@ -10,43 +10,40 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.ServiceInfo;
|
import android.content.pm.ServiceInfo;
|
||||||
import android.media.projection.MediaProjection;
|
import android.media.projection.MediaProjection;
|
||||||
import android.media.projection.MediaProjectionManager;
|
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.widget.Toast;
|
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.ttstd.control.Action;
|
||||||
|
import com.ttstd.control.ControlMessage;
|
||||||
import com.ttstd.controlled.R;
|
import com.ttstd.controlled.R;
|
||||||
|
import com.ttstd.controlled.accessibility.KeyboardAccessibilityService;
|
||||||
import com.ttstd.controlled.activity.connection.ConnectionRequestActivity;
|
import com.ttstd.controlled.activity.connection.ConnectionRequestActivity;
|
||||||
import com.ttstd.controlled.activity.main.MainActivity;
|
import com.ttstd.controlled.activity.main.MainActivity;
|
||||||
import com.ttstd.controlled.accessibility.KeyboardAccessibilityService;
|
|
||||||
import com.ttstd.controlled.input.AccessibilityInputUtils;
|
import com.ttstd.controlled.input.AccessibilityInputUtils;
|
||||||
import com.ttstd.controlled.input.InputCommandHandler;
|
import com.ttstd.controlled.input.InputCommandHandler;
|
||||||
import com.ttstd.controlled.input.InputExecutor;
|
import com.ttstd.controlled.input.InputExecutor;
|
||||||
import com.ttstd.controlled.input.RootShellInputUtils;
|
import com.ttstd.controlled.input.RootShellInputUtils;
|
||||||
import com.ttstd.controlled.input.ShellInputUtils;
|
import com.ttstd.controlled.input.ShellInputUtils;
|
||||||
import com.ttstd.controlled.input.SystemInputUtils;
|
import com.ttstd.controlled.input.SystemInputUtils;
|
||||||
import com.ttstd.controlled.utils.SignatureUtils;
|
|
||||||
import com.ttstd.controlled.utils.InputSettings;
|
|
||||||
import com.ttstd.controlled.signaling.SignalMessage;
|
import com.ttstd.controlled.signaling.SignalMessage;
|
||||||
import com.ttstd.controlled.utils.AuthSettings;
|
|
||||||
import com.ttstd.controlled.signaling.WebSocketClient;
|
import com.ttstd.controlled.signaling.WebSocketClient;
|
||||||
import com.ttstd.controlled.webrtc.WebRtcClient;
|
import com.ttstd.controlled.utils.AuthSettings;
|
||||||
|
import com.ttstd.controlled.utils.InputSettings;
|
||||||
|
import com.ttstd.controlled.utils.SignatureUtils;
|
||||||
import com.ttstd.controlled.webrtc.SelfCodecEncoder;
|
import com.ttstd.controlled.webrtc.SelfCodecEncoder;
|
||||||
import com.ttstd.control.Action;
|
import com.ttstd.controlled.webrtc.WebRtcClient;
|
||||||
import com.ttstd.control.ControlMessage;
|
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
|
|
||||||
import org.webrtc.EglBase;
|
import org.webrtc.EglBase;
|
||||||
import org.webrtc.IceCandidate;
|
import org.webrtc.IceCandidate;
|
||||||
@@ -63,7 +60,9 @@ public class ScreenCaptureService extends Service {
|
|||||||
public static final String EXTRA_SERVER_URL = "server_url";
|
public static final String EXTRA_SERVER_URL = "server_url";
|
||||||
public static final String EXTRA_DEVICE_ID = "device_id";
|
public static final String EXTRA_DEVICE_ID = "device_id";
|
||||||
|
|
||||||
/** 静态授权结果,解决部分 Android 10 设备跨进程/Intent 传递 Intent 时 Token 失效的问题 */
|
/**
|
||||||
|
* 静态授权结果,解决部分 Android 10 设备跨进程/Intent 传递 Intent 时 Token 失效的问题
|
||||||
|
*/
|
||||||
public static int sResultCode = Activity.RESULT_CANCELED;
|
public static int sResultCode = Activity.RESULT_CANCELED;
|
||||||
public static Intent sResultData = null;
|
public static Intent sResultData = null;
|
||||||
|
|
||||||
@@ -74,34 +73,52 @@ public class ScreenCaptureService extends Service {
|
|||||||
private EglBase eglBase;
|
private EglBase eglBase;
|
||||||
private final Gson gson = new Gson();
|
private final Gson gson = new Gson();
|
||||||
|
|
||||||
/** 标记服务是否已经初始化,避免 onStartCommand 多次调用导致重复创建 */
|
/**
|
||||||
|
* 标记服务是否已经初始化,避免 onStartCommand 多次调用导致重复创建
|
||||||
|
*/
|
||||||
private boolean isInitialized = false;
|
private boolean isInitialized = false;
|
||||||
|
|
||||||
/** 静态实例引用,供弹窗 Activity 在无需绑定的情况下回调本服务。 */
|
/**
|
||||||
|
* 静态实例引用,供弹窗 Activity 在无需绑定的情况下回调本服务。
|
||||||
|
*/
|
||||||
private static ScreenCaptureService instance;
|
private static ScreenCaptureService instance;
|
||||||
|
|
||||||
public static ScreenCaptureService getInstance() {
|
public static ScreenCaptureService getInstance() {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 本设备 ID(注册到信令服务器用),保存为字段以便弹窗回调使用。 */
|
/**
|
||||||
|
* 本设备 ID(注册到信令服务器用),保存为字段以便弹窗回调使用。
|
||||||
|
*/
|
||||||
private String deviceId;
|
private String deviceId;
|
||||||
/** 当前待处理的连接请求(OFFER)信息,供用户在弹窗中确认或拒绝。 */
|
/**
|
||||||
|
* 当前待处理的连接请求(OFFER)信息,供用户在弹窗中确认或拒绝。
|
||||||
|
*/
|
||||||
private String pendingControllerId;
|
private String pendingControllerId;
|
||||||
private String pendingControllerName;
|
private String pendingControllerName;
|
||||||
private String pendingOfferSdp;
|
private String pendingOfferSdp;
|
||||||
/** 当前正在控制本设备的控制端名称(用户名或设备 ID),用于断开提示。 */
|
/**
|
||||||
|
* 当前正在控制本设备的控制端名称(用户名或设备 ID),用于断开提示。
|
||||||
|
*/
|
||||||
private String controllingName;
|
private String controllingName;
|
||||||
/** 设备真实屏幕分辨率,用于分辨率切换时按宽高比计算采集尺寸。 */
|
/**
|
||||||
|
* 设备真实屏幕分辨率,用于分辨率切换时按宽高比计算采集尺寸。
|
||||||
|
*/
|
||||||
private int realScreenWidth;
|
private int realScreenWidth;
|
||||||
private int realScreenHeight;
|
private int realScreenHeight;
|
||||||
/** 当前实际采集分辨率与帧率(运行时可被本地/远端请求切换)。 */
|
/**
|
||||||
|
* 当前实际采集分辨率与帧率(运行时可被本地/远端请求切换)。
|
||||||
|
*/
|
||||||
private int currentCaptureWidth;
|
private int currentCaptureWidth;
|
||||||
private int currentCaptureHeight;
|
private int currentCaptureHeight;
|
||||||
private int currentCaptureFps;
|
private int currentCaptureFps;
|
||||||
/** 主线程 Handler,用于从 WebRTC 回调线程切回主线程执行 UI 提示。 */
|
/**
|
||||||
|
* 主线程 Handler,用于从 WebRTC 回调线程切回主线程执行 UI 提示。
|
||||||
|
*/
|
||||||
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||||
/** 标记服务是否正在关闭,避免重复停止/重复弹窗。 */
|
/**
|
||||||
|
* 标记服务是否正在关闭,避免重复停止/重复弹窗。
|
||||||
|
*/
|
||||||
private boolean isShuttingDown = false;
|
private boolean isShuttingDown = false;
|
||||||
|
|
||||||
// ---- 屏幕串流"自编码"模式 ----
|
// ---- 屏幕串流"自编码"模式 ----
|
||||||
@@ -190,7 +207,8 @@ public class ScreenCaptureService extends Service {
|
|||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
try {
|
try {
|
||||||
refreshRate = wm.getDefaultDisplay().getRefreshRate();
|
refreshRate = wm.getDefaultDisplay().getRefreshRate();
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
int fps = Math.round(refreshRate);
|
int fps = Math.round(refreshRate);
|
||||||
// 屏幕控制 30fps 已足够跟手,并显著降低 H264 硬编延迟(影响“跟手”体验)。
|
// 屏幕控制 30fps 已足够跟手,并显著降低 H264 硬编延迟(影响“跟手”体验)。
|
||||||
@@ -301,23 +319,30 @@ public class ScreenCaptureService extends Service {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 切换屏幕采集分辨率(本地 UI 调用)。
|
* 切换屏幕采集分辨率(本地 UI 调用)。
|
||||||
|
*
|
||||||
* @param longEdge 目标长边像素;<=0 表示使用设备原生分辨率(按屏幕宽高比缩放)。
|
* @param longEdge 目标长边像素;<=0 表示使用设备原生分辨率(按屏幕宽高比缩放)。
|
||||||
*/
|
*/
|
||||||
public void changeResolutionByLongEdge(int longEdge) {
|
public void changeResolutionByLongEdge(int longEdge) {
|
||||||
requestResolutionChange(longEdge, 0, 0, false);
|
requestResolutionChange(longEdge, 0, 0, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 返回当前采集分辨率(宽/高/帧率)。 */
|
/**
|
||||||
|
* 返回当前采集分辨率(宽/高/帧率)。
|
||||||
|
*/
|
||||||
public int[] getCurrentCaptureResolution() {
|
public int[] getCurrentCaptureResolution() {
|
||||||
return new int[]{ currentCaptureWidth, currentCaptureHeight, currentCaptureFps };
|
return new int[]{currentCaptureWidth, currentCaptureHeight, currentCaptureFps};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 返回设备原生分辨率的长边(像素),供 UI 生成“原始画质”选项。 */
|
/**
|
||||||
|
* 返回设备原生分辨率的长边(像素),供 UI 生成“原始画质”选项。
|
||||||
|
*/
|
||||||
public int getNativeLongEdge() {
|
public int getNativeLongEdge() {
|
||||||
return Math.max(realScreenWidth, realScreenHeight);
|
return Math.max(realScreenWidth, realScreenHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 控制端经 DataChannel 发来的 SET_RESOLUTION 指令入口。 */
|
/**
|
||||||
|
* 控制端经 DataChannel 发来的 SET_RESOLUTION 指令入口。
|
||||||
|
*/
|
||||||
private void onRemoteResolutionRequested(int width, int height, int fps) {
|
private void onRemoteResolutionRequested(int width, int height, int fps) {
|
||||||
requestResolutionChange(width, height, fps, true);
|
requestResolutionChange(width, height, fps, true);
|
||||||
}
|
}
|
||||||
@@ -330,9 +355,9 @@ public class ScreenCaptureService extends Service {
|
|||||||
* - 否则使用精确 width x height。
|
* - 否则使用精确 width x height。
|
||||||
* 最终宽高会偶数对齐(部分 H264 编码器要求),并触发一次关键帧刷新远端画面。
|
* 最终宽高会偶数对齐(部分 H264 编码器要求),并触发一次关键帧刷新远端画面。
|
||||||
*
|
*
|
||||||
* @param width 目标宽度/长边(像素)
|
* @param width 目标宽度/长边(像素)
|
||||||
* @param height 目标高度(像素,<=0 时按宽高比计算)
|
* @param height 目标高度(像素,<=0 时按宽高比计算)
|
||||||
* @param fps 目标帧率(<=0 时沿用当前帧率)
|
* @param fps 目标帧率(<=0 时沿用当前帧率)
|
||||||
* @param fromRemote 是否由远端控制端触发(仅用于 UI 提示区分)
|
* @param fromRemote 是否由远端控制端触发(仅用于 UI 提示区分)
|
||||||
*/
|
*/
|
||||||
public void requestResolutionChange(int width, int height, int fps, boolean fromRemote) {
|
public void requestResolutionChange(int width, int height, int fps, boolean fromRemote) {
|
||||||
@@ -368,7 +393,9 @@ public class ScreenCaptureService extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 根据指令参数解析出最终采集尺寸(保留屏幕宽高比、偶数对齐)。 */
|
/**
|
||||||
|
* 根据指令参数解析出最终采集尺寸(保留屏幕宽高比、偶数对齐)。
|
||||||
|
*/
|
||||||
private int[] resolveCaptureSize(int width, int height) {
|
private int[] resolveCaptureSize(int width, int height) {
|
||||||
int w, h;
|
int w, h;
|
||||||
if (width <= 0) {
|
if (width <= 0) {
|
||||||
@@ -388,12 +415,14 @@ public class ScreenCaptureService extends Service {
|
|||||||
if (h % 2 != 0) h--;
|
if (h % 2 != 0) h--;
|
||||||
if (w <= 0) w = 2;
|
if (w <= 0) w = 2;
|
||||||
if (h <= 0) h = 2;
|
if (h <= 0) h = 2;
|
||||||
return new int[]{ w, h };
|
return new int[]{w, h};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 屏幕串流"自编码"模式 ----
|
// ---- 屏幕串流"自编码"模式 ----
|
||||||
|
|
||||||
/** 控制端经 SET_STREAM_MODE 请求切换串流模式。 */
|
/**
|
||||||
|
* 控制端经 SET_STREAM_MODE 请求切换串流模式。
|
||||||
|
*/
|
||||||
private void onStreamModeRequested(int mode) {
|
private void onStreamModeRequested(int mode) {
|
||||||
setStreamMode(mode);
|
setStreamMode(mode);
|
||||||
}
|
}
|
||||||
@@ -402,7 +431,7 @@ public class ScreenCaptureService extends Service {
|
|||||||
* 切换屏幕串流模式:
|
* 切换屏幕串流模式:
|
||||||
* - {@link SelfCodecEncoder#STREAM_MODE_WEBRTC}:使用 WebRTC 默认媒体轨道(ScreenCapturerAndroid)。
|
* - {@link SelfCodecEncoder#STREAM_MODE_WEBRTC}:使用 WebRTC 默认媒体轨道(ScreenCapturerAndroid)。
|
||||||
* - {@link SelfCodecEncoder#STREAM_MODE_SELF_CODEC}:暂停 WebRTC 采集,改用自建 MediaCodec 编码,
|
* - {@link SelfCodecEncoder#STREAM_MODE_SELF_CODEC}:暂停 WebRTC 采集,改用自建 MediaCodec 编码,
|
||||||
* 经 DataChannel 的 video_channel 透传裸 H.264 码流,由控制端自建解码器渲染。
|
* 经 DataChannel 的 video_channel 透传裸 H.264 码流,由控制端自建解码器渲染。
|
||||||
*/
|
*/
|
||||||
public void setStreamMode(int mode) {
|
public void setStreamMode(int mode) {
|
||||||
if (mode == currentStreamMode) {
|
if (mode == currentStreamMode) {
|
||||||
@@ -429,12 +458,16 @@ public class ScreenCaptureService extends Service {
|
|||||||
reportStreamMode(mode);
|
reportStreamMode(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 返回当前串流模式。 */
|
/**
|
||||||
|
* 返回当前串流模式。
|
||||||
|
*/
|
||||||
public int getCurrentStreamMode() {
|
public int getCurrentStreamMode() {
|
||||||
return currentStreamMode;
|
return currentStreamMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 启动自编码通道。返回 false 表示启动失败(调用方应回退 WebRTC 模式)。 */
|
/**
|
||||||
|
* 启动自编码通道。返回 false 表示启动失败(调用方应回退 WebRTC 模式)。
|
||||||
|
*/
|
||||||
private boolean startSelfCodecEncoder() {
|
private boolean startSelfCodecEncoder() {
|
||||||
if (selfEncoder != null) {
|
if (selfEncoder != null) {
|
||||||
Log.w(TAG, "self codec encoder already running");
|
Log.w(TAG, "self codec encoder already running");
|
||||||
@@ -490,7 +523,9 @@ public class ScreenCaptureService extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 经 control DataChannel 向控制端回报当前生效的串流模式。 */
|
/**
|
||||||
|
* 经 control DataChannel 向控制端回报当前生效的串流模式。
|
||||||
|
*/
|
||||||
private void reportStreamMode(int mode) {
|
private void reportStreamMode(int mode) {
|
||||||
if (webRtcClient == null) return;
|
if (webRtcClient == null) return;
|
||||||
try {
|
try {
|
||||||
@@ -508,9 +543,14 @@ public class ScreenCaptureService extends Service {
|
|||||||
* 状态回调。由已 bind 的 MainActivity 注册,用于把连接状态同步到界面。
|
* 状态回调。由已 bind 的 MainActivity 注册,用于把连接状态同步到界面。
|
||||||
*/
|
*/
|
||||||
public interface ServiceStateListener {
|
public interface ServiceStateListener {
|
||||||
/** 信令服务器断开(WebSocket 断开)。 */
|
/**
|
||||||
|
* 信令服务器断开(WebSocket 断开)。
|
||||||
|
*/
|
||||||
void onSignalDisconnected();
|
void onSignalDisconnected();
|
||||||
/** 远程控制端断开,参数为控制端名称(用户名或设备 ID)。 */
|
|
||||||
|
/**
|
||||||
|
* 远程控制端断开,参数为控制端名称(用户名或设备 ID)。
|
||||||
|
*/
|
||||||
void onControllerDisconnected(String name);
|
void onControllerDisconnected(String name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -586,10 +626,8 @@ public class ScreenCaptureService extends Service {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(String error) {
|
public void onError(String error) {
|
||||||
Log.e(TAG, "WebSocket error: " + error);
|
Log.e(TAG, "WebSocket onError: " + error);
|
||||||
if(TextUtils.isEmpty(error)){
|
mainHandler.post(() -> onSignalServerDisconnected());
|
||||||
mainHandler.post(() -> onSignalServerDisconnected());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -730,7 +768,9 @@ public class ScreenCaptureService extends Service {
|
|||||||
return "未知的鉴权类型: " + authType;
|
return "未知的鉴权类型: " + authType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 鉴权失败时,向控制端回送带原因的拒绝消息并清理待处理状态。 */
|
/**
|
||||||
|
* 鉴权失败时,向控制端回送带原因的拒绝消息并清理待处理状态。
|
||||||
|
*/
|
||||||
private void sendAuthRejected(String reason) {
|
private void sendAuthRejected(String reason) {
|
||||||
if (pendingControllerId == null || wsClient == null) {
|
if (pendingControllerId == null || wsClient == null) {
|
||||||
return;
|
return;
|
||||||
@@ -860,6 +900,7 @@ public class ScreenCaptureService extends Service {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private int heartbeatCount = 0;
|
private int heartbeatCount = 0;
|
||||||
|
|
||||||
private void updateNotificationHeartbeat() {
|
private void updateNotificationHeartbeat() {
|
||||||
String dots = "";
|
String dots = "";
|
||||||
for (int i = 0; i < (heartbeatCount % 3) + 1; i++) dots += ".";
|
for (int i = 0; i < (heartbeatCount % 3) + 1; i++) dots += ".";
|
||||||
|
|||||||
Reference in New Issue
Block a user