feat: 支持免密连接模式及允许免密连接开关
This commit is contained in:
@@ -24,6 +24,9 @@ public class ConnectionRequestActivity extends AppCompatActivity {
|
||||
/** 启动本 Activity 时携带的发起方(控制端)设备 ID。 */
|
||||
public static final String EXTRA_CONTROLLER_ID = "controller_id";
|
||||
|
||||
/** 启动本 Activity 时携带的鉴权类型(NONE=免密连接 / CODE / PASSWORD)。 */
|
||||
public static final String EXTRA_AUTH_TYPE = "auth_type";
|
||||
|
||||
/** 无响应自动拒绝的超时时间(毫秒)。 */
|
||||
private static final long AUTO_REJECT_TIMEOUT = 60_000L;
|
||||
|
||||
@@ -38,6 +41,8 @@ public class ConnectionRequestActivity extends AppCompatActivity {
|
||||
String controllerId = getIntent() != null
|
||||
? getIntent().getStringExtra(EXTRA_CONTROLLER_ID)
|
||||
: null;
|
||||
String authType = getIntent() != null
|
||||
? getIntent().getStringExtra(EXTRA_AUTH_TYPE) : null;
|
||||
|
||||
// 服务实例不存在或缺少必要参数时,直接结束,不做任何连接处理。
|
||||
if (ScreenCaptureService.getInstance() == null || controllerId == null) {
|
||||
@@ -48,7 +53,13 @@ public class ConnectionRequestActivity extends AppCompatActivity {
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.connection_request_title);
|
||||
builder.setMessage(getString(R.string.connection_request_message, controllerId));
|
||||
|
||||
// 免密连接时在提示中标注,便于用户知晓无需验证码/密码
|
||||
String message = getString(R.string.connection_request_message, controllerId);
|
||||
if ("NONE".equalsIgnoreCase(authType)) {
|
||||
message += "\n\n" + getString(R.string.connection_request_mode_none);
|
||||
}
|
||||
builder.setMessage(message);
|
||||
builder.setCancelable(false);
|
||||
|
||||
builder.setPositiveButton(R.string.connection_accept, (d, which) -> onAccept());
|
||||
|
||||
@@ -114,6 +114,13 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
|
||||
binding.tvAuthHint.setText("固定密码已保存");
|
||||
});
|
||||
|
||||
// 允许免密连接开关:默认开启;关闭后控制端必须使用验证码或密码
|
||||
binding.switchAllowNoAuth.setChecked(AuthSettings.isNoAuthAllowed(this));
|
||||
binding.switchAllowNoAuth.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
AuthSettings.setNoAuthAllowed(this, isChecked);
|
||||
binding.tvAuthHint.setText(isChecked ? R.string.allow_no_auth_on : R.string.allow_no_auth_off);
|
||||
});
|
||||
|
||||
updateUI(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -464,28 +464,40 @@ public class ScreenCaptureService extends Service {
|
||||
? controllerName : controllerId;
|
||||
pendingOfferSdp = offerSdp;
|
||||
|
||||
// 鉴权:动态验证码(CODE) / 固定密码(PASSWORD)。
|
||||
// 鉴权模式:免密(NONE) / 动态验证码(CODE) / 固定密码(PASSWORD)。
|
||||
// 服务器仅做转发,真实校验在此完成(密钥不离开被控端)。
|
||||
String authType = message.getAuthType();
|
||||
String authValue = message.getAuthValue();
|
||||
if (authType != null && !authType.trim().isEmpty()) {
|
||||
String failReason = verifyAuth(authType.trim(), authValue);
|
||||
if (failReason == null) {
|
||||
// 校验通过:自动接受,直接建立连接(无需手动确认)
|
||||
Log.i(TAG, "Auth passed (" + authType + "), auto-accepting " + controllerId);
|
||||
mainHandler.post(this::acceptConnection);
|
||||
} else {
|
||||
Log.w(TAG, "Auth failed (" + authType + "): " + failReason + " from " + controllerId);
|
||||
sendAuthRejected(failReason);
|
||||
String rawAuthType = message.getAuthType();
|
||||
String authType = (rawAuthType == null || rawAuthType.trim().isEmpty())
|
||||
? "NONE" : rawAuthType.trim();
|
||||
boolean isNoAuth = "NONE".equalsIgnoreCase(authType);
|
||||
if (isNoAuth) {
|
||||
// 免密连接:若被控端已关闭免密,则直接拒绝,要求使用验证码/密码
|
||||
if (!AuthSettings.isNoAuthAllowed(this)) {
|
||||
Log.w(TAG, "Reject 免密连接 from " + controllerId + ": 被控端已关闭免密连接");
|
||||
sendAuthRejected("被控端已关闭免密连接,请使用动态验证码或固定密码");
|
||||
return;
|
||||
}
|
||||
// 免密连接:弹出手动确认弹窗,由用户决定是否接受
|
||||
Log.i(TAG, "免密连接,转交用户手动确认: " + controllerId);
|
||||
Intent dialogIntent = new Intent(this, ConnectionRequestActivity.class);
|
||||
dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
dialogIntent.putExtra(ConnectionRequestActivity.EXTRA_CONTROLLER_ID, controllerId);
|
||||
dialogIntent.putExtra(ConnectionRequestActivity.EXTRA_AUTH_TYPE, "NONE");
|
||||
startActivity(dialogIntent);
|
||||
return;
|
||||
}
|
||||
|
||||
// 未携带鉴权信息:沿用原有手动确认弹窗
|
||||
Intent dialogIntent = new Intent(this, ConnectionRequestActivity.class);
|
||||
dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
dialogIntent.putExtra(ConnectionRequestActivity.EXTRA_CONTROLLER_ID, controllerId);
|
||||
startActivity(dialogIntent);
|
||||
// 携带鉴权信息:被控端本地校验,通过则自动接受
|
||||
String authValue = message.getAuthValue();
|
||||
String failReason = verifyAuth(authType, authValue);
|
||||
if (failReason == null) {
|
||||
// 校验通过:自动接受,直接建立连接(无需手动确认)
|
||||
Log.i(TAG, "Auth passed (" + authType + "), auto-accepting " + controllerId);
|
||||
mainHandler.post(this::acceptConnection);
|
||||
} else {
|
||||
Log.w(TAG, "Auth failed (" + authType + "): " + failReason + " from " + controllerId);
|
||||
sendAuthRejected(failReason);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,6 +14,8 @@ public class AuthSettings {
|
||||
private static final String PREF_NAME = "controlled_auth_prefs";
|
||||
private static final String KEY_DYNAMIC_CODE = "dynamic_code";
|
||||
private static final String KEY_FIXED_PASSWORD = "fixed_password";
|
||||
/** 是否允许“免密连接”(被控端手动确认)。默认开启。 */
|
||||
private static final String KEY_ALLOW_NO_AUTH = "allow_no_auth";
|
||||
|
||||
private static final String CODE_CHARS =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
@@ -57,4 +59,16 @@ public class AuthSettings {
|
||||
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||
.edit().putString(KEY_FIXED_PASSWORD, password == null ? "" : password).apply();
|
||||
}
|
||||
|
||||
/** 是否允许免密连接(被控端手动确认)。默认开启。 */
|
||||
public static boolean isNoAuthAllowed(Context context) {
|
||||
return context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||
.getBoolean(KEY_ALLOW_NO_AUTH, true);
|
||||
}
|
||||
|
||||
/** 设置是否允许免密连接。 */
|
||||
public static void setNoAuthAllowed(Context context, boolean allowed) {
|
||||
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||
.edit().putBoolean(KEY_ALLOW_NO_AUTH, allowed).apply();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +140,39 @@
|
||||
android:textSize="13sp"
|
||||
android:textColor="#888888" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/allow_no_auth_label"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/allow_no_auth_desc"
|
||||
android:textSize="12sp"
|
||||
android:textColor="#888888" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/switch_allow_no_auth"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_start"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -15,4 +15,11 @@
|
||||
<string name="accessibility_dialog_cancel">取消</string>
|
||||
<string name="resolution_label">采集分辨率</string>
|
||||
<string name="resolution_changed_message">采集分辨率已切换为 %1$d×%2$d</string>
|
||||
|
||||
<!-- 免密连接 -->
|
||||
<string name="connection_request_mode_none">连接方式:免密连接(被控端手动确认)</string>
|
||||
<string name="allow_no_auth_label">允许免密连接</string>
|
||||
<string name="allow_no_auth_desc">关闭后,控制端必须使用动态验证码或固定密码才能连接</string>
|
||||
<string name="allow_no_auth_on">已允许免密连接</string>
|
||||
<string name="allow_no_auth_off">已关闭免密连接</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user