main #1

Closed
sookie wants to merge 5 commits from sookie/SMS-Receive:main into main
3 changed files with 502 additions and 206 deletions
Showing only changes of commit 300632a1e0 - Show all commits

View File

@ -2,6 +2,7 @@ package com.smsreceive.app.ui;
import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.NotificationManager;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
@ -19,21 +20,18 @@ import android.os.Looper;
import android.os.PowerManager;
import android.provider.Settings;
import android.provider.Telephony;
import android.text.InputType;
import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import com.smsreceive.app.R;
import com.smsreceive.app.feishu.FeishuWebhookClient;
import com.smsreceive.app.feishu.FeishuWebhookConfigStore;
import com.smsreceive.app.feishu.FeishuWebhookPushResult;
@ -59,22 +57,31 @@ public final class MainActivity extends Activity {
private static final long DATABASE_HEARTBEAT_STALE_MILLIS = 30_000L;
private TextView permissionText;
private TextView latestText;
private TextView googlePlayText;
private TextView keepAliveText;
private TextView databaseHeartbeatText;
private TextView deliveryDiagnosticsText;
private TextView feishuPushText;
private TextView latestText;
private Button keepAliveButton;
private Button autostartConfirmButton;
private Button batteryConfirmButton;
private Button pollingButton;
private RadioButton toastOnDatabaseWriteRadio;
private CheckBox feishuPushEnabledCheckBox;
private CheckBox autostartConfirmCheckBox;
private CheckBox batteryConfirmCheckBox;
private EditText feishuWebhookIdEdit;
private EditText feishuSecretEdit;
private EditText pollingIntervalEdit;
private AlertDialog debugInfoDialog;
private View debugInfoView;
private long lastInboxSmsId = -1L;
private String googlePlayTextValue = "";
private String keepAliveTextValue = "";
private String databaseHeartbeatTextValue = "";
private String deliveryDiagnosticsTextValue = "";
private String feishuPushTextValue = "";
private int databaseHeartbeatBackgroundColor = 0xFFFFFFFF;
private int feishuPushBackgroundColor = 0xFFFFFFFF;
private final Handler mainHandler = new Handler(Looper.getMainLooper());
private final BroadcastReceiver updateReceiver = new BroadcastReceiver() {
@ -91,11 +98,15 @@ public final class MainActivity extends Activity {
readLatestInboxSms(SOURCE_INBOX_OBSERVER, true);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "MainActivity.onCreate");
setContentView(createContentView());
setContentView(R.layout.activity_main);
bindMainViews();
bindMainActions();
refreshUi();
}
@Override
@ -139,189 +150,95 @@ public final class MainActivity extends Activity {
}
}
private View createContentView() {
ScrollView scrollView = new ScrollView(this);
scrollView.setFillViewport(true);
private void bindMainViews() {
permissionText = findViewById(R.id.permission_text);
latestText = findViewById(R.id.latest_text);
keepAliveButton = findViewById(R.id.keep_alive_button);
toastOnDatabaseWriteRadio = findViewById(R.id.toast_database_write_radio);
pollingButton = findViewById(R.id.polling_button);
feishuPushEnabledCheckBox = findViewById(R.id.feishu_push_enabled_checkbox);
autostartConfirmCheckBox = findViewById(R.id.autostart_confirm_checkbox);
batteryConfirmCheckBox = findViewById(R.id.battery_confirm_checkbox);
feishuWebhookIdEdit = findViewById(R.id.feishu_webhook_id_edit);
feishuSecretEdit = findViewById(R.id.feishu_secret_edit);
pollingIntervalEdit = findViewById(R.id.polling_interval_edit);
}
LinearLayout root = new LinearLayout(this);
root.setOrientation(LinearLayout.VERTICAL);
root.setPadding(dp(20), dp(24), dp(20), dp(24));
scrollView.addView(root, new ScrollView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
TextView title = new TextView(this);
title.setText("短信接收");
title.setTextSize(24);
title.setTextColor(0xFF17202A);
title.setGravity(Gravity.START);
root.addView(title, matchWrap());
TextView subtitle = new TextView(this);
subtitle.setText("主路径RECEIVE_SMS + SMS_RECEIVED_ACTION。收到短信后保存短信原文和诊断摘要。");
subtitle.setTextSize(14);
subtitle.setTextColor(0xFF5F6B7A);
subtitle.setPadding(0, dp(6), 0, dp(16));
root.addView(subtitle, matchWrap());
permissionText = section(root, "权限状态");
googlePlayText = section(root, "Google API 诊断");
keepAliveText = section(root, "后台保活状态");
databaseHeartbeatText = section(root, "数据库心跳诊断");
deliveryDiagnosticsText = section(root, "短信广播诊断");
feishuPushText = section(root, "飞书推送状态");
latestText = section(root, "最近结果");
LinearLayout actions = new LinearLayout(this);
actions.setOrientation(LinearLayout.VERTICAL);
actions.setPadding(0, dp(12), 0, 0);
root.addView(actions, matchWrap());
Button requestPermissionButton = button("申请短信权限");
requestPermissionButton.setOnClickListener(v -> requestSmsPermission());
actions.addView(requestPermissionButton, matchWrap());
keepAliveButton = button("开启常驻保活");
private void bindMainActions() {
findViewById(R.id.request_permission_button).setOnClickListener(v -> requestSmsPermission());
findViewById(R.id.debug_info_button).setOnClickListener(v -> showDebugInfoDialog());
keepAliveButton.setOnClickListener(v -> toggleKeepAlive());
actions.addView(keepAliveButton, matchWrap());
toastOnDatabaseWriteRadio = new RadioButton(this);
toastOnDatabaseWriteRadio.setText("每次写入数据库时弹 Toast");
toastOnDatabaseWriteRadio.setTextSize(14);
toastOnDatabaseWriteRadio.setTextColor(0xFF27313F);
toastOnDatabaseWriteRadio.setOnClickListener(v -> toggleToastOnDatabaseWrite());
actions.addView(toastOnDatabaseWriteRadio, matchWrap());
Button readInboxButton = button("读取最新短信");
readInboxButton.setOnClickListener(v -> readLatestInboxSms(SOURCE_INBOX_MANUAL, true));
actions.addView(readInboxButton, matchWrap());
pollingButton = button("开始短信轮询");
findViewById(R.id.read_inbox_button).setOnClickListener(v -> readLatestInboxSms(SOURCE_INBOX_MANUAL, true));
pollingButton.setOnClickListener(v -> togglePolling());
actions.addView(pollingButton, matchWrap());
pollingIntervalEdit = new EditText(this);
pollingIntervalEdit.setHint("轮询间隔秒数,默认 1");
pollingIntervalEdit.setSingleLine(true);
pollingIntervalEdit.setInputType(InputType.TYPE_CLASS_NUMBER);
actions.addView(pollingIntervalEdit, matchWrap());
feishuPushEnabledCheckBox = new CheckBox(this);
feishuPushEnabledCheckBox.setText("开启飞书远端推送");
feishuPushEnabledCheckBox.setTextSize(14);
feishuPushEnabledCheckBox.setTextColor(0xFF27313F);
actions.addView(feishuPushEnabledCheckBox, matchWrap());
feishuWebhookIdEdit = new EditText(this);
feishuWebhookIdEdit.setHint("飞书 webhook id");
feishuWebhookIdEdit.setSingleLine(true);
actions.addView(feishuWebhookIdEdit, matchWrap());
feishuSecretEdit = new EditText(this);
feishuSecretEdit.setHint("飞书 webhook secret");
feishuSecretEdit.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
feishuSecretEdit.setSingleLine(true);
actions.addView(feishuSecretEdit, matchWrap());
Button saveFeishuButton = button("保存飞书配置");
saveFeishuButton.setOnClickListener(v -> saveFeishuConfigFromUi());
actions.addView(saveFeishuButton, matchWrap());
Button testFeishuButton = button("测试飞书推送");
testFeishuButton.setOnClickListener(v -> testFeishuPush());
actions.addView(testFeishuButton, matchWrap());
Button settingsButton = button("打开应用权限设置");
settingsButton.setOnClickListener(v -> openAppSettings());
actions.addView(settingsButton, matchWrap());
Button batterySettingsButton = button("打开电池优化设置");
batterySettingsButton.setOnClickListener(v -> openBatteryOptimizationSettings());
actions.addView(batterySettingsButton, matchWrap());
Button requestBatteryButton = button("请求忽略电池优化");
requestBatteryButton.setOnClickListener(v -> requestIgnoreBatteryOptimizations());
actions.addView(requestBatteryButton, matchWrap());
Button xiaomiAutostartButton = button("打开小米自启动设置");
xiaomiAutostartButton.setOnClickListener(v -> openXiaomiAutostartSettings());
actions.addView(xiaomiAutostartButton, matchWrap());
autostartConfirmButton = button("确认已开启小米自启动");
autostartConfirmButton.setOnClickListener(v -> toggleManualAutostartConfirmed());
actions.addView(autostartConfirmButton, matchWrap());
batteryConfirmButton = button("确认省电策略已设为无限制");
batteryConfirmButton.setOnClickListener(v -> toggleManualBatteryConfirmed());
actions.addView(batteryConfirmButton, matchWrap());
Button clearButton = button("清空最近结果");
clearButton.setOnClickListener(v -> {
findViewById(R.id.save_feishu_button).setOnClickListener(v -> saveFeishuConfigFromUi());
findViewById(R.id.test_feishu_button).setOnClickListener(v -> testFeishuPush());
findViewById(R.id.settings_button).setOnClickListener(v -> openAppSettings());
findViewById(R.id.battery_settings_button).setOnClickListener(v -> openBatteryOptimizationSettings());
findViewById(R.id.request_battery_button).setOnClickListener(v -> requestIgnoreBatteryOptimizations());
findViewById(R.id.xiaomi_autostart_button).setOnClickListener(v -> openXiaomiAutostartSettings());
autostartConfirmCheckBox.setOnClickListener(v -> toggleManualAutostartConfirmed());
batteryConfirmCheckBox.setOnClickListener(v -> toggleManualBatteryConfirmed());
findViewById(R.id.clear_button).setOnClickListener(v -> {
SmsCaptureStore.clear(this);
Toast.makeText(this, "已清空最近结果", Toast.LENGTH_SHORT).show();
refreshUi();
});
actions.addView(clearButton, matchWrap());
Button refreshButton = button("刷新状态");
refreshButton.setOnClickListener(v -> refreshUi());
actions.addView(refreshButton, matchWrap());
return scrollView;
findViewById(R.id.refresh_button).setOnClickListener(v -> refreshUi());
}
private TextView section(LinearLayout root, String label) {
TextView title = new TextView(this);
title.setText(label);
title.setTextSize(16);
title.setTextColor(0xFF17202A);
title.setPadding(0, dp(12), 0, dp(4));
root.addView(title, matchWrap());
TextView value = new TextView(this);
value.setTextSize(14);
value.setTextColor(0xFF27313F);
value.setLineSpacing(dp(2), 1.0f);
value.setPadding(dp(12), dp(10), dp(12), dp(10));
value.setBackgroundColor(0xFFFFFFFF);
root.addView(value, matchWrap());
return value;
private void showDebugInfoDialog() {
if (debugInfoDialog == null) {
debugInfoView = LayoutInflater.from(this).inflate(R.layout.dialog_debug_info, null);
googlePlayText = debugInfoView.findViewById(R.id.google_play_text);
keepAliveText = debugInfoView.findViewById(R.id.keep_alive_text);
databaseHeartbeatText = debugInfoView.findViewById(R.id.database_heartbeat_text);
deliveryDiagnosticsText = debugInfoView.findViewById(R.id.delivery_diagnostics_text);
feishuPushText = debugInfoView.findViewById(R.id.feishu_push_text);
debugInfoDialog = new AlertDialog.Builder(this)
.setTitle("调试信息")
.setView(debugInfoView)
.setPositiveButton("关闭", null)
.create();
}
applyDebugInfoState();
debugInfoDialog.show();
}
private Button button(String text) {
Button button = new Button(this);
button.setText(text);
button.setAllCaps(false);
return button;
private void applyDebugInfoState() {
if (googlePlayText == null) {
return;
}
private LinearLayout.LayoutParams matchWrap() {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
params.setMargins(0, dp(4), 0, dp(4));
return params;
googlePlayText.setText(googlePlayTextValue);
keepAliveText.setText(keepAliveTextValue);
databaseHeartbeatText.setText(databaseHeartbeatTextValue);
databaseHeartbeatText.setBackgroundColor(databaseHeartbeatBackgroundColor);
deliveryDiagnosticsText.setText(deliveryDiagnosticsTextValue);
feishuPushText.setText(feishuPushTextValue);
feishuPushText.setBackgroundColor(feishuPushBackgroundColor);
}
private void refreshUi() {
boolean receiveGranted = hasReceiveSmsPermission();
boolean readGranted = hasReadSmsPermission();
boolean googlePlayInstalled = isGooglePlayServicesInstalled();
Log.d(TAG, "refreshUi receiveSmsPermissionGranted=" + receiveGranted
+ ", readSmsPermissionGranted=" + readGranted
+ ", googlePlayInstalled=" + isGooglePlayServicesInstalled());
+ ", googlePlayInstalled=" + googlePlayInstalled);
permissionText.setText("RECEIVE_SMS" + (receiveGranted ? "已授权" : "未授权")
+ "\nREAD_SMS" + (readGranted ? "已授权" : "未授权")
+ "\n说明如果 receiver 收不到广播,前台会用 READ_SMS 读取最新收件箱作为兜底。");
googlePlayText.setText(isGooglePlayServicesInstalled()
googlePlayTextValue = googlePlayInstalled
? "已检测到 com.google.android.gms。SMS User Consent / Retriever 可作为后续备选路径验证。"
: "未检测到 com.google.android.gms。当前实现不依赖 Google API主路径仍是系统短信广播。");
: "未检测到 com.google.android.gms。当前实现不依赖 Google API主路径仍是系统短信广播。";
refreshKeepAliveUi();
refreshDatabaseHeartbeatUi();
refreshDeliveryDiagnosticsUi();
refreshPollingUi();
refreshFeishuPushUi();
applyDebugInfoState();
SmsCaptureStore.StoredCapture capture = SmsCaptureStore.load(this);
if (capture.timeMillis <= 0L) {
@ -352,15 +269,9 @@ public final class MainActivity extends Activity {
+ ", notificationsEnabled=" + areNotificationsEnabled()
+ ", manualAutostart=" + state.manualAutostartConfirmed
+ ", manualBatteryUnrestricted=" + state.manualBatteryUnrestrictedConfirmed);
if (keepAliveButton != null) {
keepAliveButton.setText(state.enabledByUser ? "关闭常驻保活" : "开启常驻保活");
}
if (autostartConfirmButton != null) {
autostartConfirmButton.setText(state.manualAutostartConfirmed ? "取消自启动确认" : "确认已开启小米自启动");
}
if (batteryConfirmButton != null) {
batteryConfirmButton.setText(state.manualBatteryUnrestrictedConfirmed ? "取消省电无限制确认" : "确认省电策略已设为无限制");
}
autostartConfirmCheckBox.setChecked(state.manualAutostartConfirmed);
batteryConfirmCheckBox.setChecked(state.manualBatteryUnrestrictedConfirmed);
StringBuilder builder = new StringBuilder();
builder.append("用户开关:").append(state.enabledByUser ? "已开启" : "未开启").append('\n');
@ -376,14 +287,12 @@ public final class MainActivity extends Activity {
builder.append("通知可见性:").append(areNotificationsEnabled() ? "系统允许通知" : "通知可能被关闭").append('\n');
builder.append("小米自启动:").append(state.manualAutostartConfirmed ? "已人工确认" : "未确认").append('\n');
builder.append("省电无限制:").append(state.manualBatteryUnrestrictedConfirmed ? "已人工确认" : "未确认");
keepAliveText.setText(builder.toString());
keepAliveTextValue = builder.toString();
}
private void refreshDatabaseHeartbeatUi() {
KeepAliveStateStore.State state = KeepAliveStateStore.load(this);
if (toastOnDatabaseWriteRadio != null) {
toastOnDatabaseWriteRadio.setChecked(state.toastOnDatabaseWrite);
}
long now = System.currentTimeMillis();
long lastActiveTime = KeepAliveDatabase.readLastActiveTime(this);
@ -402,7 +311,7 @@ public final class MainActivity extends Activity {
if (lastActiveTime <= 0L) {
builder.append("最后写入:-").append('\n');
builder.append("判断:数据库还没有 lastActiveTime。开启常驻保活后会开始写入。");
databaseHeartbeatText.setBackgroundColor(0xFFFFFFFF);
databaseHeartbeatBackgroundColor = 0xFFFFFFFF;
} else {
builder.append("最后写入:").append(formatTimeWithMillis(lastActiveTime)).append('\n');
builder.append("距离现在:").append(gapMillis).append(" ms").append('\n');
@ -412,13 +321,13 @@ public final class MainActivity extends Activity {
.append(",大约在此后 ")
.append(DATABASE_HEARTBEAT_INTERVAL_MILLIS / 1000L)
.append(" 秒内停止写入。");
databaseHeartbeatText.setBackgroundColor(0xFFFFE0E0);
databaseHeartbeatBackgroundColor = 0xFFFFE0E0;
} else {
builder.append("判断:数据库心跳仍在正常窗口内。");
databaseHeartbeatText.setBackgroundColor(0xFFE8F5E9);
databaseHeartbeatBackgroundColor = 0xFFE8F5E9;
}
}
databaseHeartbeatText.setText(builder.toString());
databaseHeartbeatTextValue = builder.toString();
}
private void refreshDeliveryDiagnosticsUi() {
@ -436,15 +345,13 @@ public final class MainActivity extends Activity {
} else {
builder.append("判断:暂无收件箱新于广播的异常记录。");
}
deliveryDiagnosticsText.setText(builder.toString());
deliveryDiagnosticsTextValue = builder.toString();
}
private void refreshPollingUi() {
SmsPollingStateStore.State state = SmsPollingStateStore.load(this);
if (pollingButton != null) {
pollingButton.setText(state.enabledByUser ? "停止短信轮询" : "开始短信轮询");
}
if (pollingIntervalEdit != null && !pollingIntervalEdit.hasFocus()) {
if (!pollingIntervalEdit.hasFocus()) {
pollingIntervalEdit.setText(String.valueOf(state.intervalSeconds));
}
Log.d(TAG, "refreshPollingUi enabled=" + state.enabledByUser
@ -460,13 +367,11 @@ public final class MainActivity extends Activity {
FeishuWebhookConfigStore.Config config = FeishuWebhookConfigStore.loadConfig(this);
FeishuWebhookConfigStore.LastResult lastResult = FeishuWebhookConfigStore.loadLastResult(this);
FeishuWebhookConfigStore.LastPushedSms lastPushedSms = FeishuWebhookConfigStore.loadLastPushedSms(this);
if (feishuPushEnabledCheckBox != null) {
feishuPushEnabledCheckBox.setChecked(config.enabled);
}
if (feishuWebhookIdEdit != null && !feishuWebhookIdEdit.hasFocus()) {
if (!feishuWebhookIdEdit.hasFocus()) {
feishuWebhookIdEdit.setText(config.webhookId);
}
if (feishuSecretEdit != null && !feishuSecretEdit.hasFocus()) {
if (!feishuSecretEdit.hasFocus()) {
feishuSecretEdit.setText(config.secret);
}
@ -499,8 +404,8 @@ public final class MainActivity extends Activity {
boolean configIssue = !config.enabled || !config.hasWebhookId() || !config.hasSecret()
|| FeishuWebhookPushResult.STATUS_DISABLED.equals(lastResult.status)
|| FeishuWebhookPushResult.STATUS_MISSING_CONFIG.equals(lastResult.status);
feishuPushText.setBackgroundColor(configIssue ? 0xFFFFE0E0 : 0xFFFFFFFF);
feishuPushText.setText(builder.toString());
feishuPushBackgroundColor = configIssue ? 0xFFFFE0E0 : 0xFFFFFFFF;
feishuPushTextValue = builder.toString();
}
private void requestSmsPermission() {
@ -562,16 +467,14 @@ public final class MainActivity extends Activity {
boolean enabled = !state.toastOnDatabaseWrite;
Log.d(TAG, "toggleToastOnDatabaseWrite enabled=" + enabled);
KeepAliveStateStore.setToastOnDatabaseWrite(this, enabled);
if (toastOnDatabaseWriteRadio != null) {
toastOnDatabaseWriteRadio.setChecked(enabled);
}
refreshUi();
}
private void saveFeishuConfigFromUi() {
boolean enabled = feishuPushEnabledCheckBox != null && feishuPushEnabledCheckBox.isChecked();
String webhookId = feishuWebhookIdEdit == null ? "" : feishuWebhookIdEdit.getText().toString();
String secret = feishuSecretEdit == null ? "" : feishuSecretEdit.getText().toString();
boolean enabled = feishuPushEnabledCheckBox.isChecked();
String webhookId = feishuWebhookIdEdit.getText().toString();
String secret = feishuSecretEdit.getText().toString();
Log.d(TAG, "saveFeishuConfigFromUi enabled=" + enabled
+ ", webhookConfigured=" + !TextUtils.isEmpty(webhookId)
+ ", secretConfigured=" + !TextUtils.isEmpty(secret));
@ -664,14 +567,12 @@ public final class MainActivity extends Activity {
private int savePollingIntervalFromUi() {
int intervalSeconds = parsePollingIntervalSeconds();
SmsPollingStateStore.setIntervalSeconds(this, intervalSeconds);
if (pollingIntervalEdit != null && !pollingIntervalEdit.hasFocus()) {
pollingIntervalEdit.setText(String.valueOf(intervalSeconds));
}
return intervalSeconds;
}
private int parsePollingIntervalSeconds() {
String raw = pollingIntervalEdit == null ? "" : pollingIntervalEdit.getText().toString().trim();
String raw = pollingIntervalEdit.getText().toString().trim();
if (TextUtils.isEmpty(raw)) {
return 1;
}
@ -832,8 +733,4 @@ public final class MainActivity extends Activity {
}
return "***" + sender.substring(sender.length() - 4);
}
private int dp(int value) {
return (int) (value * getResources().getDisplayMetrics().density + 0.5f);
}
}

View File

@ -0,0 +1,289 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/screen_bg"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="20dp"
android:paddingTop="24dp"
android:paddingRight="20dp"
android:paddingBottom="24dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="短信接收"
android:textColor="@color/text_primary"
android:textSize="24sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:layout_marginBottom="16dp"
android:text="主路径RECEIVE_SMS + SMS_RECEIVED_ACTION。收到短信后保存短信原文和诊断摘要。"
android:textColor="@color/text_secondary"
android:textSize="14sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="权限状态"
android:textColor="@color/text_primary"
android:textSize="16sp" />
<TextView
android:id="@+id/permission_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:background="@android:color/white"
android:lineSpacingExtra="2dp"
android:padding="12dp"
android:textColor="@color/text_primary"
android:textSize="14sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="最近结果"
android:textColor="@color/text_primary"
android:textSize="16sp" />
<TextView
android:id="@+id/latest_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:background="@android:color/white"
android:lineSpacingExtra="2dp"
android:padding="12dp"
android:textColor="@color/text_primary"
android:textSize="14sp" />
<Button
android:id="@+id/debug_info_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="调试信息"
android:textAllCaps="false" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:orientation="vertical">
<Button
android:id="@+id/request_permission_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="申请短信权限"
android:textAllCaps="false" />
<Button
android:id="@+id/keep_alive_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="开启常驻保活"
android:textAllCaps="false" />
<RadioButton
android:id="@+id/toast_database_write_radio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="每次写入数据库时弹 Toast"
android:textColor="@color/text_primary"
android:textSize="14sp" />
<Button
android:id="@+id/read_inbox_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="读取最新短信"
android:textAllCaps="false" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:baselineAligned="false"
android:gravity="center_vertical"
android:orientation="horizontal">
<Button
android:id="@+id/polling_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="开始短信轮询"
android:textAllCaps="false" />
<EditText
android:id="@+id/polling_interval_edit"
android:layout_width="96dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:hint="默认1"
android:inputType="number"
android:maxLines="1" />
</LinearLayout>
<CheckBox
android:id="@+id/feishu_push_enabled_checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="开启飞书远端推送"
android:textColor="@color/text_primary"
android:textSize="14sp" />
<EditText
android:id="@+id/feishu_webhook_id_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:hint="飞书 webhook id"
android:maxLines="1" />
<EditText
android:id="@+id/feishu_secret_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:hint="飞书 webhook secret"
android:inputType="textPassword"
android:maxLines="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:baselineAligned="false"
android:orientation="horizontal">
<Button
android:id="@+id/save_feishu_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="保存飞书配置"
android:textAllCaps="false" />
<Button
android:id="@+id/test_feishu_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_weight="1"
android:text="测试飞书推送"
android:textAllCaps="false" />
</LinearLayout>
<Button
android:id="@+id/settings_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="打开应用权限设置"
android:textAllCaps="false" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:baselineAligned="false"
android:gravity="center_vertical"
android:orientation="horizontal">
<Button
android:id="@+id/battery_settings_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="打开电池优化设置"
android:textAllCaps="false" />
<CheckBox
android:id="@+id/battery_confirm_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:text="确认省电策略无限制"
android:textColor="@color/text_primary"
android:textSize="14sp" />
</LinearLayout>
<Button
android:id="@+id/request_battery_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="请求忽略电池优化"
android:textAllCaps="false" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:baselineAligned="false"
android:gravity="center_vertical"
android:orientation="horizontal">
<Button
android:id="@+id/xiaomi_autostart_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="打开小米自启动设置"
android:textAllCaps="false" />
<CheckBox
android:id="@+id/autostart_confirm_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:text="确认自启动开启"
android:textColor="@color/text_primary"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:baselineAligned="false"
android:orientation="horizontal">
<Button
android:id="@+id/clear_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="清空最近结果"
android:textAllCaps="false" />
<Button
android:id="@+id/refresh_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_weight="1"
android:text="刷新状态"
android:textAllCaps="false" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@ -0,0 +1,110 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="20dp"
android:paddingTop="20dp"
android:paddingRight="20dp"
android:paddingBottom="8dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Google API 诊断"
android:textColor="@color/text_primary"
android:textSize="16sp" />
<TextView
android:id="@+id/google_play_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:background="@android:color/white"
android:lineSpacingExtra="2dp"
android:padding="12dp"
android:textColor="@color/text_primary"
android:textSize="14sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="后台保活状态"
android:textColor="@color/text_primary"
android:textSize="16sp" />
<TextView
android:id="@+id/keep_alive_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:background="@android:color/white"
android:lineSpacingExtra="2dp"
android:padding="12dp"
android:textColor="@color/text_primary"
android:textSize="14sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="数据库心跳诊断"
android:textColor="@color/text_primary"
android:textSize="16sp" />
<TextView
android:id="@+id/database_heartbeat_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:background="@android:color/white"
android:lineSpacingExtra="2dp"
android:padding="12dp"
android:textColor="@color/text_primary"
android:textSize="14sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="短信广播诊断"
android:textColor="@color/text_primary"
android:textSize="16sp" />
<TextView
android:id="@+id/delivery_diagnostics_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:background="@android:color/white"
android:lineSpacingExtra="2dp"
android:padding="12dp"
android:textColor="@color/text_primary"
android:textSize="14sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="飞书推送状态"
android:textColor="@color/text_primary"
android:textSize="16sp" />
<TextView
android:id="@+id/feishu_push_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:background="@android:color/white"
android:lineSpacingExtra="2dp"
android:padding="12dp"
android:textColor="@color/text_primary"
android:textSize="14sp" />
</LinearLayout>
</ScrollView>