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