diff --git a/WebRTCControlled/app/src/main/AndroidManifest.xml b/WebRTCControlled/app/src/main/AndroidManifest.xml
index 66bb3a1..0e99fd3 100644
--- a/WebRTCControlled/app/src/main/AndroidManifest.xml
+++ b/WebRTCControlled/app/src/main/AndroidManifest.xml
@@ -25,6 +25,11 @@
+
+
startScreenSharing());
binding.btnStop.setOnClickListener(v -> stopScreenSharing());
- // 安全验证:显示动态验证码,提供重新生成与固定密码设置
- binding.tvDynamicCode.setText(AuthSettings.getDynamicCode(this));
- binding.btnRegenerateCode.setOnClickListener(v -> {
- String newCode = AuthSettings.generateDynamicCode();
- AuthSettings.setDynamicCode(this, newCode);
- binding.tvDynamicCode.setText(newCode);
- binding.tvAuthHint.setText("动态验证码已重新生成");
- });
- binding.btnSavePassword.setOnClickListener(v -> {
- String p1 = binding.etFixedPassword.getText().toString();
- String p2 = binding.etConfirmPassword.getText().toString();
- if (p1.isEmpty()) {
- binding.tvAuthHint.setText("固定密码不能为空");
- return;
- }
- if (!p1.equals(p2)) {
- binding.tvAuthHint.setText("两次输入的密码不一致");
- return;
- }
- AuthSettings.setFixedPassword(this, p1);
- binding.etFixedPassword.setText("");
- binding.etConfirmPassword.setText("");
- 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);
- });
+ // 打开安全与输入设置页面(动态验证码 / 固定密码 / 免密连接 / 模拟点击方式)
+ binding.btnOpenSettings.setOnClickListener(v ->
+ startActivity(new Intent(this, SettingsActivity.class)));
updateUI(false);
}
@@ -195,6 +169,10 @@ public class MainActivity extends BaseMvvmActivity {
+
+ /** Spinner 选项与 InputSettings 常量的一一对应(顺序须与 string-array 一致)。 */
+ private static final String[] METHOD_BY_POSITION = {
+ InputSettings.METHOD_AUTO,
+ InputSettings.METHOD_SYSTEM,
+ InputSettings.METHOD_ROOT,
+ InputSettings.METHOD_ACCESSIBILITY,
+ InputSettings.METHOD_SHELL
+ };
+
+ @Override
+ protected int getLayoutId() {
+ return R.layout.activity_settings;
+ }
+
+ @Override
+ protected void initView() {
+ setupInputMethodSpinner();
+ setupAuth();
+ }
+
+ /** 构建并初始化「模拟点击方式」下拉框,选中当前已保存的方式。 */
+ private void setupInputMethodSpinner() {
+ Spinner spinner = binding.spinnerInputMethod;
+ ArrayAdapter adapter = ArrayAdapter.createFromResource(
+ this, R.array.input_method_entries, android.R.layout.simple_spinner_item);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spinner.setAdapter(adapter);
+
+ String current = InputSettings.getInputMethod(this);
+ int selectIdx = 0;
+ for (int i = 0; i < METHOD_BY_POSITION.length; i++) {
+ if (METHOD_BY_POSITION[i].equals(current)) {
+ selectIdx = i;
+ break;
+ }
+ }
+ spinner.setSelection(selectIdx);
+ updateInputMethodHint(METHOD_BY_POSITION[selectIdx]);
+
+ spinner.setOnItemSelectedListener(new android.widget.AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(android.widget.AdapterView> parent, android.view.View view,
+ int position, long id) {
+ String method = METHOD_BY_POSITION[position];
+ InputSettings.setInputMethod(SettingsActivity.this, method);
+ updateInputMethodHint(method);
+ binding.tvInputMethodHint.setText(R.string.input_method_saved);
+ }
+
+ @Override
+ public void onNothingSelected(android.widget.AdapterView> parent) {
+ }
+ });
+ }
+
+ /** 根据选中的方式展示说明(仅展示,不覆盖已保存状态)。 */
+ private void updateInputMethodHint(String method) {
+ int resId;
+ switch (method) {
+ case InputSettings.METHOD_SYSTEM:
+ resId = R.string.input_method_system;
+ break;
+ case InputSettings.METHOD_ROOT:
+ resId = R.string.input_method_root;
+ break;
+ case InputSettings.METHOD_ACCESSIBILITY:
+ resId = R.string.input_method_accessibility;
+ break;
+ case InputSettings.METHOD_SHELL:
+ resId = R.string.input_method_shell;
+ break;
+ case InputSettings.METHOD_AUTO:
+ default:
+ resId = R.string.input_method_auto;
+ break;
+ }
+ binding.tvInputMethodHint.setText(getString(R.string.input_method_desc) + "\n" + getString(resId));
+ }
+
+ /** 动态验证码、固定密码与免密连接开关。 */
+ private void setupAuth() {
+ binding.tvDynamicCode.setText(AuthSettings.getDynamicCode(this));
+ binding.btnRegenerateCode.setOnClickListener(v -> {
+ String newCode = AuthSettings.generateDynamicCode();
+ AuthSettings.setDynamicCode(this, newCode);
+ binding.tvDynamicCode.setText(newCode);
+ binding.tvAuthHint.setText("动态验证码已重新生成");
+ });
+ binding.btnSavePassword.setOnClickListener(v -> {
+ String p1 = binding.etFixedPassword.getText().toString();
+ String p2 = binding.etConfirmPassword.getText().toString();
+ if (p1.isEmpty()) {
+ binding.tvAuthHint.setText("固定密码不能为空");
+ return;
+ }
+ if (!p1.equals(p2)) {
+ binding.tvAuthHint.setText("两次输入的密码不一致");
+ return;
+ }
+ AuthSettings.setFixedPassword(this, p1);
+ binding.etFixedPassword.setText("");
+ binding.etConfirmPassword.setText("");
+ 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);
+ });
+ }
+
+ @Override
+ protected void initData() {
+ // 无需额外数据初始化
+ }
+
+ @Override
+ public void onBackPressed() {
+ // 设置即时生效(已写入 SharedPreferences),直接返回主页面。
+ Toast.makeText(this, "设置已保存", Toast.LENGTH_SHORT).show();
+ super.onBackPressed();
+ }
+}
diff --git a/WebRTCControlled/app/src/main/java/com/ttstd/controlled/activity/settings/SettingsViewModel.java b/WebRTCControlled/app/src/main/java/com/ttstd/controlled/activity/settings/SettingsViewModel.java
new file mode 100644
index 0000000..3129bbe
--- /dev/null
+++ b/WebRTCControlled/app/src/main/java/com/ttstd/controlled/activity/settings/SettingsViewModel.java
@@ -0,0 +1,6 @@
+package com.ttstd.controlled.activity.settings;
+
+import com.ttstd.controlled.base.BaseViewModel;
+
+public class SettingsViewModel extends BaseViewModel {
+}
diff --git a/WebRTCControlled/app/src/main/java/com/ttstd/controlled/service/ScreenCaptureService.java b/WebRTCControlled/app/src/main/java/com/ttstd/controlled/service/ScreenCaptureService.java
index 091d060..fea2d90 100644
--- a/WebRTCControlled/app/src/main/java/com/ttstd/controlled/service/ScreenCaptureService.java
+++ b/WebRTCControlled/app/src/main/java/com/ttstd/controlled/service/ScreenCaptureService.java
@@ -36,6 +36,7 @@ import com.ttstd.controlled.input.RootShellInputUtils;
import com.ttstd.controlled.input.ShellInputUtils;
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.utils.AuthSettings;
import com.ttstd.controlled.signaling.WebSocketClient;
@@ -62,6 +63,10 @@ public class ScreenCaptureService extends Service {
public static final String EXTRA_SERVER_URL = "server_url";
public static final String EXTRA_DEVICE_ID = "device_id";
+ /** 静态授权结果,解决部分 Android 10 设备跨进程/Intent 传递 Intent 时 Token 失效的问题 */
+ public static int sResultCode = Activity.RESULT_CANCELED;
+ public static Intent sResultData = null;
+
private ScreenCapturerAndroid screenCapturer;
private WebSocketClient wsClient;
private WebRtcClient webRtcClient;
@@ -128,17 +133,21 @@ public class ScreenCaptureService extends Service {
startForeground(NOTIFICATION_ID, notification);
}
- int resultCode = intent.getIntExtra(EXTRA_RESULT_CODE, Activity.RESULT_CANCELED);
- Intent resultData = intent.getParcelableExtra(EXTRA_RESULT_DATA);
+ // 优先从静态变量获取授权结果(解决 Android 10+ Intent 传递 Token 失效),若无则 fallback 到 Intent extras。
+ int resultCode = (sResultCode != Activity.RESULT_CANCELED)
+ ? sResultCode : intent.getIntExtra(EXTRA_RESULT_CODE, Activity.RESULT_CANCELED);
+ Intent resultData = (sResultData != null)
+ ? sResultData : intent.getParcelableExtra(EXTRA_RESULT_DATA);
+
String serverUrl = intent.getStringExtra(EXTRA_SERVER_URL);
deviceId = intent.getStringExtra(EXTRA_DEVICE_ID);
- // 保存屏幕捕获授权结果,供"自编码"模式重新获取 MediaProjection 使用。
+ // 保存授权结果,供后续切换模式(如自编码模式)重新申请 MediaProjection 时使用。
this.resultCode = resultCode;
this.resultDataIntent = resultData;
if (resultCode != Activity.RESULT_OK || resultData == null || serverUrl == null || deviceId == null) {
- Log.e(TAG, "Invalid service parameters");
+ Log.e(TAG, "Invalid service parameters: resultCode=" + resultCode + ", hasData=" + (resultData != null));
stopSelf();
return START_NOT_STICKY;
}
@@ -149,6 +158,7 @@ public class ScreenCaptureService extends Service {
}
isInitialized = true;
+ // ... 获取分辨率逻辑保持不变 ...
// 获取屏幕真实尺寸和刷新率
WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
@@ -165,6 +175,7 @@ public class ScreenCaptureService extends Service {
// 分辨率缩放逻辑:如果设备分辨率大于 1080p,则等比例缩小到 1080p 以内
int captureWidth = realWidth;
int captureHeight = realHeight;
+ // 模拟器或低配设备建议使用 720p 甚至 480p,否则软件编码 1080p 会导致卡死
int maxResolution = 1920; // 1080p 的长边
if (Math.max(realWidth, realHeight) > maxResolution) {
@@ -202,8 +213,21 @@ public class ScreenCaptureService extends Service {
inputHandler.setResolutionRequestListener(this::onRemoteResolutionRequested);
inputHandler.setStreamModeListener(this::onStreamModeRequested);
- // 开启屏幕捕获,使用计算出的 capture 分辨率
- startScreenCapture(resultCode, resultData, serverUrl, deviceId, captureWidth, captureHeight, fps);
+ // 启动心脏起搏器,解决画面静止或模拟器不推帧的问题
+ mainHandler.removeCallbacks(heartbeatRunnable);
+ mainHandler.post(heartbeatRunnable);
+
+ // Android 10+ 强烈建议在 startForeground 之后延时一点点再初始化 MediaProjection,
+ // 确保系统已经感知到服务已切换为 FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION。
+ final int finalCaptureWidth = captureWidth;
+ final int finalCaptureHeight = captureHeight;
+ final int finalFps = fps;
+ mainHandler.postDelayed(() -> {
+ if (!isShuttingDown) {
+ startScreenCapture(resultCode, resultData, serverUrl, deviceId, finalCaptureWidth, finalCaptureHeight, finalFps);
+ }
+ }, 200);
+
return START_NOT_STICKY;
}
@@ -214,6 +238,34 @@ public class ScreenCaptureService extends Service {
* 3. 兜底使用 root shell 方式。
*/
private InputExecutor createInputExecutor() {
+ String method = InputSettings.getInputMethod(this);
+ switch (method) {
+ case InputSettings.METHOD_SYSTEM:
+ Log.i(TAG, "Input executor: SystemInputUtils (user selected)");
+ return new SystemInputUtils();
+ case InputSettings.METHOD_ROOT:
+ Log.i(TAG, "Input executor: RootShellInputUtils (user selected)");
+ return new RootShellInputUtils();
+ case InputSettings.METHOD_ACCESSIBILITY:
+ Log.i(TAG, "Input executor: AccessibilityInputUtils (user selected)");
+ return new AccessibilityInputUtils();
+ case InputSettings.METHOD_SHELL:
+ Log.i(TAG, "Input executor: ShellInputUtils (user selected)");
+ return new ShellInputUtils();
+ case InputSettings.METHOD_AUTO:
+ default:
+ return autoSelectInputExecutor();
+ }
+ }
+
+ /**
+ * 根据当前应用权限/环境自动选择最合适的输入执行器:
+ * 1. 系统签名应用优先使用隐藏 API 注入(支持精确触摸与任意按键);
+ * 2. 已获取 root 时使用 root shell;
+ * 3. 已开启无障碍服务时使用 AccessibilityInputUtils(无需 root/系统签名);
+ * 4. 兜底使用普通 shell 方式。
+ */
+ private InputExecutor autoSelectInputExecutor() {
if (SignatureUtils.isSystemSignature(this) || SignatureUtils.isSharedSystemUid(this)) {
Log.i(TAG, "Input executor: SystemInputUtils (system signature)");
return new SystemInputUtils();
@@ -488,6 +540,8 @@ public class ScreenCaptureService extends Service {
public void onDestroy() {
super.onDestroy();
isShuttingDown = true;
+ sResultCode = Activity.RESULT_CANCELED;
+ sResultData = null;
if (instance == this) {
instance = null;
}
@@ -572,7 +626,11 @@ public class ScreenCaptureService extends Service {
// 初始化屏幕捕获
screenCapturer = new ScreenCapturerAndroid(resultData, new MediaProjection.Callback() {
-
+ @Override
+ public void onStop() {
+ super.onStop();
+ Log.e(TAG, "MediaProjection stopped (ScreenCapturerAndroid)");
+ }
});
webRtcClient.setVideoCapturer(screenCapturer, width, height, fps);
@@ -662,19 +720,19 @@ public class ScreenCaptureService extends Service {
* PASSWORD: 与被控端固定密码比对。
*/
private String verifyAuth(String authType, String authValue) {
- if (authValue == null) authValue = "";
+ String valueToVerify = (authValue == null) ? "" : authValue;
if ("CODE".equalsIgnoreCase(authType)) {
String expected = AuthSettings.getDynamicCode(this);
if (expected == null || expected.isEmpty()) {
return "被控端未生成动态验证码";
}
- return expected.equals(authValue) ? null : "动态验证码错误";
+ return expected.equals(valueToVerify) ? null : "动态验证码错误";
} else if ("PASSWORD".equalsIgnoreCase(authType)) {
String expected = AuthSettings.getFixedPassword(this);
if (expected == null || expected.isEmpty()) {
return "被控端未设置固定密码";
}
- return expected.equals(authValue) ? null : "固定密码错误";
+ return expected.equals(valueToVerify) ? null : "固定密码错误";
}
return "未知的鉴权类型: " + authType;
}
@@ -798,6 +856,24 @@ public class ScreenCaptureService extends Service {
Log.i(TAG, "Sent CONNECTION_REJECTED to " + controllerId);
}
+ private final Runnable heartbeatRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (!isShuttingDown) {
+ updateNotificationHeartbeat();
+ mainHandler.postDelayed(this, 1000);
+ }
+ }
+ };
+
+ private int heartbeatCount = 0;
+ private void updateNotificationHeartbeat() {
+ String dots = "";
+ for (int i = 0; i < (heartbeatCount % 3) + 1; i++) dots += ".";
+ heartbeatCount++;
+ updateNotification("远程控制服务运行中" + dots);
+ }
+
private void handleIceCandidate(SignalMessage message) {
try {
JsonObject payload = gson.fromJson(message.getPayload(), JsonObject.class);
@@ -826,7 +902,7 @@ public class ScreenCaptureService extends Service {
.setContentText(contentText)
.setSmallIcon(android.R.drawable.ic_menu_camera)
.setContentIntent(pendingIntent)
- .setPriority(NotificationCompat.PRIORITY_LOW)
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
.setOngoing(true)
.build();
}
@@ -836,7 +912,7 @@ public class ScreenCaptureService extends Service {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID,
"屏幕共享服务",
- NotificationManager.IMPORTANCE_LOW);
+ NotificationManager.IMPORTANCE_DEFAULT);
channel.setDescription("远程控制屏幕采集前台服务");
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(channel);
diff --git a/WebRTCControlled/app/src/main/java/com/ttstd/controlled/utils/InputSettings.java b/WebRTCControlled/app/src/main/java/com/ttstd/controlled/utils/InputSettings.java
new file mode 100644
index 0000000..3558786
--- /dev/null
+++ b/WebRTCControlled/app/src/main/java/com/ttstd/controlled/utils/InputSettings.java
@@ -0,0 +1,38 @@
+package com.ttstd.controlled.utils;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+/**
+ * 模拟点击方式(输入执行器)的可选配置。
+ * 默认「自动选择」,由 {@link com.ttstd.controlled.service.ScreenCaptureService}
+ * 根据当前权限/环境挑选最合适的执行器;用户也可在此手动指定。
+ */
+public class InputSettings {
+
+ private static final String PREF_NAME = "controlled_input_prefs";
+ private static final String KEY_INPUT_METHOD = "input_method";
+
+ /** 自动选择(按 系统签名 -> Root -> 无障碍 -> 普通 Shell 顺序兜底)。 */
+ public static final String METHOD_AUTO = "auto";
+ /** 系统隐藏 API 注入(需系统签名 / 共享系统 UID)。 */
+ public static final String METHOD_SYSTEM = "system";
+ /** Root Shell(input 命令,需 Root 权限)。 */
+ public static final String METHOD_ROOT = "root";
+ /** 无障碍服务手势(无需 Root / 系统签名)。 */
+ public static final String METHOD_ACCESSIBILITY = "accessibility";
+ /** 普通 Shell(input 命令,兼容性最差)。 */
+ public static final String METHOD_SHELL = "shell";
+
+ /** 获取当前选中的模拟点击方式,默认 {@link #METHOD_AUTO}。 */
+ public static String getInputMethod(Context context) {
+ return context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
+ .getString(KEY_INPUT_METHOD, METHOD_AUTO);
+ }
+
+ /** 设置模拟点击方式。 */
+ public static void setInputMethod(Context context, String method) {
+ context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
+ .edit().putString(KEY_INPUT_METHOD, method == null ? METHOD_AUTO : method).apply();
+ }
+}
diff --git a/WebRTCControlled/app/src/main/java/com/ttstd/controlled/webrtc/WebRtcClient.java b/WebRTCControlled/app/src/main/java/com/ttstd/controlled/webrtc/WebRtcClient.java
index d4c7b04..fc1b74c 100644
--- a/WebRTCControlled/app/src/main/java/com/ttstd/controlled/webrtc/WebRtcClient.java
+++ b/WebRTCControlled/app/src/main/java/com/ttstd/controlled/webrtc/WebRtcClient.java
@@ -127,7 +127,10 @@ public class WebRtcClient {
.createInitializationOptions();
PeerConnectionFactory.initialize(initOptions);
- DefaultVideoEncoderFactory encoderFactory = new DefaultVideoEncoderFactory(eglBase.getEglBaseContext(), true, true);
+ // 第三个参数 enableH264HighProfile 设为 false:H264 仅作兜底,
+ // 使用 Constrained Baseline(profile-level-id 42e01f),各浏览器/桌面端均可稳定解码;
+ // 主用 VP8/VP9,由 optimizeSdp 在 m=video 行优先排列。
+ DefaultVideoEncoderFactory encoderFactory = new DefaultVideoEncoderFactory(eglBase.getEglBaseContext(), true, false);
DefaultVideoDecoderFactory decoderFactory = new DefaultVideoDecoderFactory(eglBase.getEglBaseContext());
// 打印所有支持的编码格式
@@ -541,16 +544,31 @@ public class WebRtcClient {
String[] lines = sdp.split("\n");
StringBuilder newSdp = new StringBuilder();
- List videoPayloads = new ArrayList<>();
- // 识别所有视频负载类型
+ // 按编解码器类型分组收集 payload type。
+ // 重排时优先 VP8/VP9(控制端/浏览器原生解码兼容性最好,桌面端与 Chromium/Linux 均可稳定硬/软解),
+ // H264 放最后作为兜底,避免部分环境无法解码 H264 High Profile 而黑屏。
+ List vp8 = new ArrayList<>();
+ List vp9 = new ArrayList<>();
+ List h264 = new ArrayList<>();
for (String line : lines) {
String trimmedLine = line.trim();
- if (trimmedLine.startsWith("a=rtpmap:") && (trimmedLine.contains("H264/90000") || trimmedLine.contains("VP8/90000") || trimmedLine.contains("VP9/90000"))) {
+ if (trimmedLine.startsWith("a=rtpmap:")) {
String payload = trimmedLine.split(":")[1].split(" ")[0];
- videoPayloads.add(payload);
+ if (trimmedLine.contains("VP8/90000")) {
+ vp8.add(payload);
+ } else if (trimmedLine.contains("VP9/90000")) {
+ vp9.add(payload);
+ } else if (trimmedLine.contains("H264/90000")) {
+ h264.add(payload);
+ }
}
}
+ List videoPayloads = new ArrayList<>();
+ videoPayloads.addAll(vp8);
+ videoPayloads.addAll(vp9);
+ videoPayloads.addAll(h264);
+
if (videoPayloads.isEmpty()) {
return sdp;
}
@@ -563,7 +581,6 @@ public class WebRtcClient {
String[] parts = trimmedLine.split(" ");
if (parts.length > 3) {
StringBuilder mLine = new StringBuilder(parts[0] + " " + parts[1] + " " + parts[2]);
- // 将 H264 放在最前面(如果存在)
for (String payload : videoPayloads) {
mLine.append(" ").append(payload);
}
@@ -577,14 +594,7 @@ public class WebRtcClient {
newSdp.append(trimmedLine).append("\r\n");
}
} else if (trimmedLine.startsWith("a=fmtp:")) {
- boolean isVideoPayload = false;
- for (String payload : videoPayloads) {
- if (trimmedLine.startsWith("a=fmtp:" + payload)) {
- isVideoPayload = true;
- break;
- }
- }
- if (isVideoPayload) {
+ if (videoPayloads.contains(trimmedLine.split(":")[1].split(" ")[0])) {
String bonus = ";x-google-start-bitrate=" + START_BITRATE_KBPS
+ ";x-google-max-bitrate=" + MAX_BITRATE_KBPS
+ ";x-google-min-bitrate=" + MIN_BITRATE_KBPS;
diff --git a/WebRTCControlled/app/src/main/res/layout/activity_main.xml b/WebRTCControlled/app/src/main/res/layout/activity_main.xml
index 833cdd7..cc83c34 100644
--- a/WebRTCControlled/app/src/main/res/layout/activity_main.xml
+++ b/WebRTCControlled/app/src/main/res/layout/activity_main.xml
@@ -61,117 +61,14 @@
android:textSize="16sp"
android:textStyle="bold" />
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:text="@string/btn_open_settings" />