main #1

Closed
sookie wants to merge 5 commits from sookie/SMS-Receive:main into main
23 changed files with 241 additions and 214 deletions
Showing only changes of commit 95a3c6d8c4 - Show all commits

View File

@ -1,4 +1,4 @@
package com.smsreceive.app;
package com.smsreceive.app.sms;
import android.content.Context;
import android.util.Log;

View File

@ -15,7 +15,7 @@
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:name=".ui.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -25,7 +25,7 @@
</activity>
<receiver
android:name=".SmsReceiver"
android:name=".sms.SmsReceiver"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BROADCAST_SMS">
@ -35,7 +35,7 @@
</receiver>
<receiver
android:name=".BootReceiver"
android:name=".keepalive.BootReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
@ -46,12 +46,12 @@
</receiver>
<service
android:name=".SmsKeepAliveService"
android:name=".keepalive.SmsKeepAliveService"
android:enabled="true"
android:exported="false" />
<service
android:name=".SmsPollingService"
android:name=".keepalive.SmsPollingService"
android:enabled="true"
android:exported="false" />
</application>

View File

@ -1,47 +0,0 @@
package com.smsreceive.app;
final class FeishuWebhookPushResult {
static final String STATUS_SUCCESS = "success";
static final String STATUS_DISABLED = "disabled";
static final String STATUS_MISSING_CONFIG = "missing_config";
static final String STATUS_SIGN_ERROR = "sign_error";
static final String STATUS_NETWORK_ERROR = "network_error";
static final String STATUS_TIMEOUT = "timeout";
static final String STATUS_HTTP_ERROR = "http_error";
static final String STATUS_INVALID_JSON = "invalid_json";
static final String STATUS_API_ERROR = "api_error";
final boolean success;
final String status;
final String message;
final int httpStatus;
final int apiCode;
final long timeMillis;
private FeishuWebhookPushResult(
boolean success,
String status,
String message,
int httpStatus,
int apiCode,
long timeMillis) {
this.success = success;
this.status = status == null ? "" : status;
this.message = message == null ? "" : message;
this.httpStatus = httpStatus;
this.apiCode = apiCode;
this.timeMillis = timeMillis;
}
static FeishuWebhookPushResult success(String message) {
return new FeishuWebhookPushResult(true, STATUS_SUCCESS, message, 200, 0, System.currentTimeMillis());
}
static FeishuWebhookPushResult failure(String status, String message) {
return failure(status, message, 0, 0);
}
static FeishuWebhookPushResult failure(String status, String message, int httpStatus, int apiCode) {
return new FeishuWebhookPushResult(false, status, message, httpStatus, apiCode, System.currentTimeMillis());
}
}

View File

@ -1,4 +1,4 @@
package com.smsreceive.app;
package com.smsreceive.app.feishu;
import android.content.Context;
import android.content.Intent;
@ -11,6 +11,8 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.smsreceive.app.sms.CaptureResult;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.nio.charset.StandardCharsets;
@ -29,7 +31,7 @@ import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
final class FeishuWebhookClient {
public final class FeishuWebhookClient {
private static final String TAG = "[SMS]SmsReceive";
private static final String WEBHOOK_URL_PREFIX = "https://open.feishu.cn/open-apis/bot/v2/hook/";
private static final char[] BASE64_TABLE =
@ -47,7 +49,7 @@ final class FeishuWebhookClient {
private FeishuWebhookClient() {
}
static void pushCaptureResultAsync(Context context, CaptureResult result) {
public static void pushCaptureResultAsync(Context context, CaptureResult result) {
if (context == null || result == null) {
return;
}
@ -76,7 +78,7 @@ final class FeishuWebhookClient {
pushMarkdownAsync(appContext, markdown, result);
}
static void pushMarkdownAsync(Context context, String markdownContent) {
public static void pushMarkdownAsync(Context context, String markdownContent) {
pushMarkdownAsync(context, markdownContent, null);
}
@ -98,7 +100,7 @@ final class FeishuWebhookClient {
});
}
static FeishuWebhookPushResult pushMarkdown(
public static FeishuWebhookPushResult pushMarkdown(
FeishuWebhookConfigStore.Config config,
String markdownContent,
long timestampSeconds) {
@ -157,14 +159,14 @@ final class FeishuWebhookClient {
}
}
static String generateSign(String secret, long timestampSeconds) throws GeneralSecurityException {
public static String generateSign(String secret, long timestampSeconds) throws GeneralSecurityException {
String stringToSign = timestampSeconds + "\n" + (secret == null ? "" : secret);
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(stringToSign.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
return base64EncodeNoWrap(mac.doFinal(new byte[0]));
}
static String buildRequestJson(String markdownContent, long timestampSeconds, String sign) throws JSONException {
public static String buildRequestJson(String markdownContent, long timestampSeconds, String sign) throws JSONException {
JSONObject markdown = new JSONObject()
.put("tag", "markdown")
.put("content", markdownContent == null ? "" : markdownContent);
@ -178,7 +180,7 @@ final class FeishuWebhookClient {
.toString();
}
static FeishuWebhookPushResult parseResponse(String responseBody) {
public static FeishuWebhookPushResult parseResponse(String responseBody) {
try {
JSONObject json = new JSONObject(responseBody == null ? "" : responseBody);
int code = json.optInt("code", Integer.MIN_VALUE);
@ -198,15 +200,15 @@ final class FeishuWebhookClient {
}
}
static String buildWebhookUrl(String webhookId) {
public static String buildWebhookUrl(String webhookId) {
return WEBHOOK_URL_PREFIX + (webhookId == null ? "" : webhookId.trim());
}
static String buildMarkdownFromCapture(CaptureResult result) {
public static String buildMarkdownFromCapture(CaptureResult result) {
return buildMarkdownFromCapture(result, new FeishuWebhookConfigStore.Config(false, "", "", false, false));
}
static String buildMarkdownFromCapture(CaptureResult result, FeishuWebhookConfigStore.Config config) {
public static String buildMarkdownFromCapture(CaptureResult result, FeishuWebhookConfigStore.Config config) {
boolean filterCode = config != null && config.filterVerificationCode;
boolean includeFullBody = config == null || !filterCode || config.sendFullBodyDebug;
StringBuilder builder = new StringBuilder();

View File

@ -1,9 +1,11 @@
package com.smsreceive.app;
package com.smsreceive.app.feishu;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import com.smsreceive.app.sms.CaptureResult;
import org.json.JSONException;
import org.json.JSONObject;
@ -16,8 +18,8 @@ import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
final class FeishuWebhookConfigStore {
static final String ACTION_PUSH_UPDATED = "com.smsreceive.app.ACTION_FEISHU_PUSH_UPDATED";
public final class FeishuWebhookConfigStore {
public static final String ACTION_PUSH_UPDATED = "com.smsreceive.app.ACTION_FEISHU_PUSH_UPDATED";
private static final String TAG = "[SMS]SmsReceive";
private static final String PREFS = "feishu_webhook";
@ -43,7 +45,7 @@ final class FeishuWebhookConfigStore {
private FeishuWebhookConfigStore() {
}
static Config loadConfig(Context context) {
public static Config loadConfig(Context context) {
ensureDefaultConfigFile(context);
File file = configFile(context);
if (!file.exists()) {
@ -59,7 +61,7 @@ final class FeishuWebhookConfigStore {
}
}
static void saveConfig(
public static void saveConfig(
Context context,
boolean enabled,
String webhookId,
@ -82,7 +84,7 @@ final class FeishuWebhookConfigStore {
}
}
static void saveLastResult(Context context, FeishuWebhookPushResult result) {
public static void saveLastResult(Context context, FeishuWebhookPushResult result) {
preferences(context).edit()
.putLong(KEY_LAST_TIME, result.timeMillis)
.putBoolean(KEY_LAST_SUCCESS, result.success)
@ -136,7 +138,7 @@ final class FeishuWebhookConfigStore {
+ ", smsKey=" + smsKey);
}
static LastResult loadLastResult(Context context) {
public static LastResult loadLastResult(Context context) {
SharedPreferences prefs = preferences(context);
return new LastResult(
prefs.getLong(KEY_LAST_TIME, 0L),
@ -147,14 +149,14 @@ final class FeishuWebhookConfigStore {
prefs.getInt(KEY_LAST_API_CODE, 0));
}
static LastPushedSms loadLastPushedSms(Context context) {
public static LastPushedSms loadLastPushedSms(Context context) {
SharedPreferences prefs = preferences(context);
return new LastPushedSms(
prefs.getLong(KEY_LAST_PUSHED_SMS_RECEIVED_SECOND, 0L),
prefs.getString(KEY_LAST_PUSHED_SMS_KEY, ""));
}
static String maskSecret(String secret) {
public static String maskSecret(String secret) {
if (isEmpty(secret)) {
return "";
}
@ -164,15 +166,15 @@ final class FeishuWebhookConfigStore {
return secret.substring(0, 3) + "***" + secret.substring(secret.length() - 3);
}
static String configPath(Context context) {
public static String configPath(Context context) {
return configFile(context).getAbsolutePath();
}
static String defaultConfigPath(Context context) {
public static String defaultConfigPath(Context context) {
return defaultConfigFile(context).getAbsolutePath();
}
static String configTemplate() {
public static String configTemplate() {
try {
return configToJson(defaultConfig()).toString(2);
} catch (JSONException e) {
@ -289,14 +291,14 @@ final class FeishuWebhookConfigStore {
+ Integer.toHexString((result.body == null ? "" : result.body).hashCode());
}
static final class Config {
final boolean enabled;
final String webhookId;
final String secret;
final boolean sendFullBodyDebug;
final boolean filterVerificationCode;
public static final class Config {
public final boolean enabled;
public final String webhookId;
public final String secret;
public final boolean sendFullBodyDebug;
public final boolean filterVerificationCode;
Config(
public Config(
boolean enabled,
String webhookId,
String secret,
@ -309,22 +311,22 @@ final class FeishuWebhookConfigStore {
this.filterVerificationCode = filterVerificationCode;
}
boolean hasWebhookId() {
public boolean hasWebhookId() {
return !isEmpty(webhookId);
}
boolean hasSecret() {
public boolean hasSecret() {
return !isEmpty(secret);
}
}
static final class LastResult {
final long timeMillis;
final boolean success;
final String status;
final String message;
final int httpStatus;
final int apiCode;
public static final class LastResult {
public final long timeMillis;
public final boolean success;
public final String status;
public final String message;
public final int httpStatus;
public final int apiCode;
LastResult(long timeMillis, boolean success, String status, String message, int httpStatus, int apiCode) {
this.timeMillis = timeMillis;
@ -336,9 +338,9 @@ final class FeishuWebhookConfigStore {
}
}
static final class LastPushedSms {
final long receivedSecond;
final String smsKey;
public static final class LastPushedSms {
public final long receivedSecond;
public final String smsKey;
LastPushedSms(long receivedSecond, String smsKey) {
this.receivedSecond = receivedSecond;

View File

@ -0,0 +1,47 @@
package com.smsreceive.app.feishu;
public final class FeishuWebhookPushResult {
public static final String STATUS_SUCCESS = "success";
public static final String STATUS_DISABLED = "disabled";
public static final String STATUS_MISSING_CONFIG = "missing_config";
public static final String STATUS_SIGN_ERROR = "sign_error";
public static final String STATUS_NETWORK_ERROR = "network_error";
public static final String STATUS_TIMEOUT = "timeout";
public static final String STATUS_HTTP_ERROR = "http_error";
public static final String STATUS_INVALID_JSON = "invalid_json";
public static final String STATUS_API_ERROR = "api_error";
public final boolean success;
public final String status;
public final String message;
public final int httpStatus;
public final int apiCode;
public final long timeMillis;
private FeishuWebhookPushResult(
boolean success,
String status,
String message,
int httpStatus,
int apiCode,
long timeMillis) {
this.success = success;
this.status = status == null ? "" : status;
this.message = message == null ? "" : message;
this.httpStatus = httpStatus;
this.apiCode = apiCode;
this.timeMillis = timeMillis;
}
public static FeishuWebhookPushResult success(String message) {
return new FeishuWebhookPushResult(true, STATUS_SUCCESS, message, 200, 0, System.currentTimeMillis());
}
public static FeishuWebhookPushResult failure(String status, String message) {
return failure(status, message, 0, 0);
}
public static FeishuWebhookPushResult failure(String status, String message, int httpStatus, int apiCode) {
return new FeishuWebhookPushResult(false, status, message, httpStatus, apiCode, System.currentTimeMillis());
}
}

View File

@ -1,4 +1,4 @@
package com.smsreceive.app;
package com.smsreceive.app.keepalive;
import android.content.BroadcastReceiver;
import android.content.Context;

View File

@ -1,4 +1,4 @@
package com.smsreceive.app;
package com.smsreceive.app.keepalive;
import android.content.ContentValues;
import android.content.Context;
@ -11,7 +11,7 @@ import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
final class KeepAliveDatabase {
public final class KeepAliveDatabase {
private static final String TAG = "[SMS]SmsReceive";
private static final String DATABASE_NAME = "sms_keep_alive.db";
private static final int DATABASE_VERSION = 1;
@ -23,7 +23,7 @@ final class KeepAliveDatabase {
private KeepAliveDatabase() {
}
static long writeLastActiveTime(Context context) {
public static long writeLastActiveTime(Context context) {
long now = System.currentTimeMillis();
SQLiteDatabase database = helper(context).getWritableDatabase();
ContentValues values = new ContentValues();
@ -35,7 +35,7 @@ final class KeepAliveDatabase {
return now;
}
static long readLastActiveTime(Context context) {
public static long readLastActiveTime(Context context) {
SQLiteDatabase database = helper(context).getReadableDatabase();
try (Cursor cursor = database.query(
TABLE_META,

View File

@ -1,4 +1,4 @@
package com.smsreceive.app;
package com.smsreceive.app.keepalive;
import android.app.Notification;
import android.app.NotificationChannel;
@ -9,6 +9,8 @@ import android.content.Intent;
import android.os.Build;
import android.util.Log;
import com.smsreceive.app.ui.MainActivity;
final class KeepAliveNotification {
static final int NOTIFICATION_ID = 2101;
private static final String TAG = "[SMS]SmsReceive";

View File

@ -1,11 +1,11 @@
package com.smsreceive.app;
package com.smsreceive.app.keepalive;
import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;
import android.util.Log;
final class KeepAliveStateStore {
public final class KeepAliveStateStore {
private static final String TAG = "[SMS]SmsReceive";
private static final String PREFS = "sms_keep_alive";
@ -23,14 +23,14 @@ final class KeepAliveStateStore {
private KeepAliveStateStore() {
}
static void setEnabledByUser(Context context, boolean enabled) {
public static void setEnabledByUser(Context context, boolean enabled) {
Log.d(TAG, "KeepAliveStateStore.setEnabledByUser enabled=" + enabled);
preferences(context).edit()
.putBoolean(KEY_ENABLED_BY_USER, enabled)
.apply();
}
static void recordServiceStarted(Context context) {
public static void recordServiceStarted(Context context) {
long now = System.currentTimeMillis();
Log.d(TAG, "KeepAliveStateStore.recordServiceStarted time=" + now);
preferences(context).edit()
@ -40,7 +40,7 @@ final class KeepAliveStateStore {
.apply();
}
static void recordServiceStopped(Context context, String reason) {
public static void recordServiceStopped(Context context, String reason) {
Log.d(TAG, "KeepAliveStateStore.recordServiceStopped reason=" + reason);
preferences(context).edit()
.putBoolean(KEY_SERVICE_RUNNING, false)
@ -48,7 +48,7 @@ final class KeepAliveStateStore {
.apply();
}
static void recordHeartbeat(Context context) {
public static void recordHeartbeat(Context context) {
Log.d(TAG, "KeepAliveStateStore.recordHeartbeat");
preferences(context).edit()
.putBoolean(KEY_SERVICE_RUNNING, true)
@ -56,7 +56,7 @@ final class KeepAliveStateStore {
.apply();
}
static void recordBootEvent(Context context, String action) {
public static void recordBootEvent(Context context, String action) {
long now = System.currentTimeMillis();
Log.d(TAG, "KeepAliveStateStore.recordBootEvent action=" + action + ", time=" + now);
preferences(context).edit()
@ -65,7 +65,7 @@ final class KeepAliveStateStore {
.apply();
}
static void recordServiceStartFailure(Context context, String reason) {
public static void recordServiceStartFailure(Context context, String reason) {
Log.w(TAG, "KeepAliveStateStore.recordServiceStartFailure reason=" + reason);
preferences(context).edit()
.putBoolean(KEY_SERVICE_RUNNING, false)
@ -73,39 +73,39 @@ final class KeepAliveStateStore {
.apply();
}
static void setManualAutostartConfirmed(Context context, boolean confirmed) {
public static void setManualAutostartConfirmed(Context context, boolean confirmed) {
Log.d(TAG, "KeepAliveStateStore.setManualAutostartConfirmed confirmed=" + confirmed);
preferences(context).edit()
.putBoolean(KEY_MANUAL_AUTOSTART_CONFIRMED, confirmed)
.apply();
}
static void setManualBatteryUnrestrictedConfirmed(Context context, boolean confirmed) {
public static void setManualBatteryUnrestrictedConfirmed(Context context, boolean confirmed) {
Log.d(TAG, "KeepAliveStateStore.setManualBatteryUnrestrictedConfirmed confirmed=" + confirmed);
preferences(context).edit()
.putBoolean(KEY_MANUAL_BATTERY_UNRESTRICTED_CONFIRMED, confirmed)
.apply();
}
static void setBatteryOptimizationIgnored(Context context, boolean ignored) {
public static void setBatteryOptimizationIgnored(Context context, boolean ignored) {
Log.d(TAG, "KeepAliveStateStore.setBatteryOptimizationIgnored ignored=" + ignored);
preferences(context).edit()
.putBoolean(KEY_BATTERY_OPTIMIZATION_IGNORED, ignored)
.apply();
}
static void setToastOnDatabaseWrite(Context context, boolean enabled) {
public static void setToastOnDatabaseWrite(Context context, boolean enabled) {
Log.d(TAG, "KeepAliveStateStore.setToastOnDatabaseWrite enabled=" + enabled);
preferences(context).edit()
.putBoolean(KEY_TOAST_ON_DATABASE_WRITE, enabled)
.apply();
}
static boolean isToastOnDatabaseWriteEnabled(Context context) {
public static boolean isToastOnDatabaseWriteEnabled(Context context) {
return preferences(context).getBoolean(KEY_TOAST_ON_DATABASE_WRITE, false);
}
static State load(Context context) {
public static State load(Context context) {
SharedPreferences prefs = preferences(context);
return new State(
prefs.getBoolean(KEY_ENABLED_BY_USER, false),
@ -128,17 +128,17 @@ final class KeepAliveStateStore {
return TextUtils.isEmpty(value) ? "" : value;
}
static final class State {
final boolean enabledByUser;
final boolean serviceRunning;
final long lastHeartbeatMillis;
final String lastBootEvent;
final long lastBootTimeMillis;
final String lastServiceStartFailure;
final boolean manualAutostartConfirmed;
final boolean manualBatteryUnrestrictedConfirmed;
final boolean batteryOptimizationIgnored;
final boolean toastOnDatabaseWrite;
public static final class State {
public final boolean enabledByUser;
public final boolean serviceRunning;
public final long lastHeartbeatMillis;
public final String lastBootEvent;
public final long lastBootTimeMillis;
public final String lastServiceStartFailure;
public final boolean manualAutostartConfirmed;
public final boolean manualBatteryUnrestrictedConfirmed;
public final boolean batteryOptimizationIgnored;
public final boolean toastOnDatabaseWrite;
State(
boolean enabledByUser,

View File

@ -1,4 +1,4 @@
package com.smsreceive.app;
package com.smsreceive.app.keepalive;
import android.app.Service;
import android.content.Context;
@ -10,6 +10,8 @@ import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
import com.smsreceive.app.sms.SmsCaptureStore;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
@ -41,7 +43,7 @@ public final class SmsKeepAliveService extends Service {
}
};
static void start(Context context) {
public static void start(Context context) {
Log.d(TAG, "SmsKeepAliveService.start requested sdk=" + Build.VERSION.SDK_INT);
Intent intent = new Intent(context, SmsKeepAliveService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@ -51,7 +53,7 @@ public final class SmsKeepAliveService extends Service {
}
}
static void stop(Context context) {
public static void stop(Context context) {
Log.d(TAG, "SmsKeepAliveService.stop requested");
context.stopService(new Intent(context, SmsKeepAliveService.class));
}

View File

@ -1,4 +1,4 @@
package com.smsreceive.app;
package com.smsreceive.app.keepalive;
import android.Manifest;
import android.app.Service;
@ -12,6 +12,11 @@ import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
import com.smsreceive.app.feishu.FeishuWebhookClient;
import com.smsreceive.app.sms.CaptureResult;
import com.smsreceive.app.sms.SmsCaptureStore;
import com.smsreceive.app.sms.SmsInboxReader;
public final class SmsPollingService extends Service {
private static final String TAG = "[SMS]SmsReceive";
private static final String SOURCE_INBOX_POLLING = "sms_inbox_polling";
@ -29,7 +34,7 @@ public final class SmsPollingService extends Service {
}
};
static void start(Context context) {
public static void start(Context context) {
Log.d(TAG, "SmsPollingService.start requested sdk=" + Build.VERSION.SDK_INT);
long startTimeMillis = System.currentTimeMillis() - 2_000L;
SmsPollingStateStore.recordStarted(context, startTimeMillis);
@ -42,7 +47,7 @@ public final class SmsPollingService extends Service {
}
}
static void stop(Context context) {
public static void stop(Context context) {
Log.d(TAG, "SmsPollingService.stop requested");
SmsPollingStateStore.recordStopped(context, "用户停止轮询");
context.stopService(new Intent(context, SmsPollingService.class));

View File

@ -1,11 +1,11 @@
package com.smsreceive.app;
package com.smsreceive.app.keepalive;
import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;
import android.util.Log;
final class SmsPollingStateStore {
public final class SmsPollingStateStore {
private static final String TAG = "[SMS]SmsReceive";
private static final String PREFS = "sms_polling";
private static final String KEY_ENABLED_BY_USER = "enabled_by_user";
@ -22,7 +22,7 @@ final class SmsPollingStateStore {
private SmsPollingStateStore() {
}
static void recordStarted(Context context, long startTimeMillis) {
public static void recordStarted(Context context, long startTimeMillis) {
Log.d(TAG, "SmsPollingStateStore.recordStarted startTime=" + startTimeMillis);
preferences(context).edit()
.putBoolean(KEY_ENABLED_BY_USER, true)
@ -32,7 +32,7 @@ final class SmsPollingStateStore {
.apply();
}
static void recordStopped(Context context, String reason) {
public static void recordStopped(Context context, String reason) {
Log.d(TAG, "SmsPollingStateStore.recordStopped reason=" + reason);
preferences(context).edit()
.putBoolean(KEY_ENABLED_BY_USER, false)
@ -41,7 +41,7 @@ final class SmsPollingStateStore {
.apply();
}
static void recordServiceStopped(Context context, String reason) {
public static void recordServiceStopped(Context context, String reason) {
Log.d(TAG, "SmsPollingStateStore.recordServiceStopped reason=" + reason);
preferences(context).edit()
.putBoolean(KEY_RUNNING, false)
@ -49,13 +49,13 @@ final class SmsPollingStateStore {
.apply();
}
static void recordServiceRunning(Context context) {
public static void recordServiceRunning(Context context) {
preferences(context).edit()
.putBoolean(KEY_RUNNING, true)
.apply();
}
static void recordHit(Context context, long smsId, long hitTimeMillis) {
public static void recordHit(Context context, long smsId, long hitTimeMillis) {
Log.d(TAG, "SmsPollingStateStore.recordHit id=" + smsId + ", time=" + hitTimeMillis);
preferences(context).edit()
.putLong(KEY_LAST_HIT_ID, smsId)
@ -64,7 +64,7 @@ final class SmsPollingStateStore {
.apply();
}
static void setIntervalSeconds(Context context, int seconds) {
public static void setIntervalSeconds(Context context, int seconds) {
int safeSeconds = clampIntervalSeconds(seconds);
Log.d(TAG, "SmsPollingStateStore.setIntervalSeconds seconds=" + safeSeconds);
preferences(context).edit()
@ -72,11 +72,11 @@ final class SmsPollingStateStore {
.apply();
}
static int getIntervalSeconds(Context context) {
public static int getIntervalSeconds(Context context) {
return clampIntervalSeconds(preferences(context).getInt(KEY_INTERVAL_SECONDS, DEFAULT_INTERVAL_SECONDS));
}
static State load(Context context) {
public static State load(Context context) {
SharedPreferences prefs = preferences(context);
return new State(
prefs.getBoolean(KEY_ENABLED_BY_USER, false),
@ -100,14 +100,14 @@ final class SmsPollingStateStore {
return Math.max(MIN_INTERVAL_SECONDS, Math.min(seconds, MAX_INTERVAL_SECONDS));
}
static final class State {
final boolean enabledByUser;
final boolean running;
final long startTimeMillis;
final long lastHitId;
final long lastHitTimeMillis;
final String lastFailure;
final int intervalSeconds;
public static final class State {
public final boolean enabledByUser;
public final boolean running;
public final long startTimeMillis;
public final long lastHitId;
public final long lastHitTimeMillis;
public final String lastFailure;
public final int intervalSeconds;
State(
boolean enabledByUser,

View File

@ -1,15 +1,15 @@
package com.smsreceive.app;
package com.smsreceive.app.sms;
final class CaptureResult {
static final long UNKNOWN_SMS_PROVIDER_ID = -1L;
public final class CaptureResult {
public static final long UNKNOWN_SMS_PROVIDER_ID = -1L;
final long receivedAtMillis;
final long smsProviderId;
final String sender;
final String body;
final VerificationCodeParser.ParseResult parseResult;
final String source;
final String failureReason;
public final long receivedAtMillis;
public final long smsProviderId;
public final String sender;
public final String body;
public final VerificationCodeParser.ParseResult parseResult;
public final String source;
public final String failureReason;
private CaptureResult(
long receivedAtMillis,
@ -28,7 +28,7 @@ final class CaptureResult {
this.failureReason = failureReason == null ? "" : failureReason;
}
static CaptureResult success(
public static CaptureResult success(
long receivedAtMillis,
String sender,
String body,
@ -37,7 +37,7 @@ final class CaptureResult {
return success(receivedAtMillis, UNKNOWN_SMS_PROVIDER_ID, sender, body, parseResult, source);
}
static CaptureResult success(
public static CaptureResult success(
long receivedAtMillis,
long smsProviderId,
String sender,
@ -47,7 +47,7 @@ final class CaptureResult {
return new CaptureResult(receivedAtMillis, smsProviderId, sender, body, parseResult, source, "");
}
static CaptureResult failure(
public static CaptureResult failure(
long receivedAtMillis,
String sender,
String body,
@ -56,7 +56,7 @@ final class CaptureResult {
return failure(receivedAtMillis, UNKNOWN_SMS_PROVIDER_ID, sender, body, source, failureReason);
}
static CaptureResult failure(
public static CaptureResult failure(
long receivedAtMillis,
long smsProviderId,
String sender,

View File

@ -1,12 +1,12 @@
package com.smsreceive.app;
package com.smsreceive.app.sms;
import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;
import android.util.Log;
final class SmsCaptureStore {
static final String ACTION_CAPTURE_UPDATED = "com.smsreceive.app.ACTION_CAPTURE_UPDATED";
public final class SmsCaptureStore {
public static final String ACTION_CAPTURE_UPDATED = "com.smsreceive.app.ACTION_CAPTURE_UPDATED";
private static final String TAG = "[SMS]SmsReceive";
private static final String PREFS = "sms_capture";
@ -25,7 +25,7 @@ final class SmsCaptureStore {
private SmsCaptureStore() {
}
static void save(Context context, CaptureResult result) {
public static void save(Context context, CaptureResult result) {
VerificationCodeParser.ParseResult parse = result.parseResult;
Log.d(TAG, "SmsCaptureStore.save source=" + result.source
+ ", success=" + parse.success
@ -53,7 +53,7 @@ final class SmsCaptureStore {
editor.apply();
}
static StoredCapture load(Context context) {
public static StoredCapture load(Context context) {
SharedPreferences prefs = preferences(context);
return new StoredCapture(
prefs.getLong(KEY_TIME, 0L),
@ -66,12 +66,12 @@ final class SmsCaptureStore {
prefs.getString(KEY_BODY_PREVIEW, ""));
}
static void clear(Context context) {
public static void clear(Context context) {
Log.d(TAG, "SmsCaptureStore.clear");
preferences(context).edit().clear().apply();
}
static DeliveryDiagnostics loadDeliveryDiagnostics(Context context) {
public static DeliveryDiagnostics loadDeliveryDiagnostics(Context context) {
SharedPreferences prefs = preferences(context);
return new DeliveryDiagnostics(
prefs.getLong(KEY_LAST_BROADCAST_TIME, 0L),
@ -101,15 +101,15 @@ final class SmsCaptureStore {
return normalized.length() <= 48 ? normalized : normalized.substring(0, 48) + "...";
}
static final class StoredCapture {
final long timeMillis;
final String sender;
final String code;
final String strategy;
final int confidence;
final String source;
final String failure;
final String bodyPreview;
public static final class StoredCapture {
public final long timeMillis;
public final String sender;
public final String code;
public final String strategy;
public final int confidence;
public final String source;
public final String failure;
public final String bodyPreview;
StoredCapture(
long timeMillis,
@ -131,10 +131,10 @@ final class SmsCaptureStore {
}
}
static final class DeliveryDiagnostics {
final long lastBroadcastTimeMillis;
final long lastInboxTimeMillis;
final String lastInboxSource;
public static final class DeliveryDiagnostics {
public final long lastBroadcastTimeMillis;
public final long lastInboxTimeMillis;
public final String lastInboxSource;
DeliveryDiagnostics(long lastBroadcastTimeMillis, long lastInboxTimeMillis, String lastInboxSource) {
this.lastBroadcastTimeMillis = lastBroadcastTimeMillis;
@ -142,7 +142,7 @@ final class SmsCaptureStore {
this.lastInboxSource = lastInboxSource == null ? "" : lastInboxSource;
}
boolean inboxNewerThanBroadcast() {
public boolean inboxNewerThanBroadcast() {
return lastInboxTimeMillis > 0L && lastInboxTimeMillis > lastBroadcastTimeMillis;
}
}

View File

@ -1,4 +1,4 @@
package com.smsreceive.app;
package com.smsreceive.app.sms;
import android.content.Context;
import android.database.Cursor;
@ -11,14 +11,14 @@ import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
final class SmsInboxReader {
public final class SmsInboxReader {
private static final String TAG = "[SMS]SmsReceive";
private static final Uri SMS_INBOX_URI = Uri.parse("content://sms/inbox");
private SmsInboxReader() {
}
static InboxResult readLatest(Context context) {
public static InboxResult readLatest(Context context) {
String[] projection = {
Telephony.Sms._ID,
Telephony.Sms.ADDRESS,
@ -64,7 +64,7 @@ final class SmsInboxReader {
}
}
static int logRecentMessages(Context context, int limit) {
public static int logRecentMessages(Context context, int limit) {
Uri uri = Telephony.Sms.CONTENT_URI;
String[] projection = {
Telephony.Sms._ID,
@ -116,11 +116,11 @@ final class SmsInboxReader {
}
}
static RecentCodeResult findLatestVerificationCode(Context context, int limit) {
public static RecentCodeResult findLatestVerificationCode(Context context, int limit) {
return findLatestVerificationCode(context, limit, 0L);
}
static RecentCodeResult findLatestVerificationCode(Context context, int limit, long minDateMillis) {
public static RecentCodeResult findLatestVerificationCode(Context context, int limit, long minDateMillis) {
Uri uri = Telephony.Sms.CONTENT_URI;
String[] projection = {
Telephony.Sms._ID,
@ -231,13 +231,13 @@ final class SmsInboxReader {
}
}
static final class InboxResult {
final boolean success;
final long id;
final String sender;
final String body;
final long dateMillis;
final String failureReason;
public static final class InboxResult {
public final boolean success;
public final long id;
public final String sender;
public final String body;
public final long dateMillis;
public final String failureReason;
private InboxResult(boolean success, long id, String sender, String body, long dateMillis, String failureReason) {
this.success = success;
@ -257,15 +257,15 @@ final class SmsInboxReader {
}
}
static final class RecentCodeResult {
final boolean success;
final long id;
final String sender;
final String body;
final long dateMillis;
final VerificationCodeParser.ParseResult parseResult;
final int scannedCount;
final String failureReason;
public static final class RecentCodeResult {
public final boolean success;
public final long id;
public final String sender;
public final String body;
public final long dateMillis;
public final VerificationCodeParser.ParseResult parseResult;
public final int scannedCount;
public final String failureReason;
private RecentCodeResult(
boolean success,

View File

@ -1,4 +1,4 @@
package com.smsreceive.app;
package com.smsreceive.app.sms;
import android.content.Intent;
import android.provider.Telephony;

View File

@ -1,4 +1,4 @@
package com.smsreceive.app;
package com.smsreceive.app.sms;
import android.content.BroadcastReceiver;
import android.content.Context;
@ -7,6 +7,8 @@ import android.provider.Telephony;
import android.util.Log;
import android.widget.Toast;
import com.smsreceive.app.feishu.FeishuWebhookClient;
public final class SmsReceiver extends BroadcastReceiver {
private static final String TAG = "[SMS]SmsReceive";
private static final String SOURCE_SYSTEM_BROADCAST = "system_sms_broadcast";

View File

@ -1,4 +1,4 @@
package com.smsreceive.app;
package com.smsreceive.app.sms;
import java.util.ArrayList;
import java.util.List;

View File

@ -1,4 +1,4 @@
package com.smsreceive.app;
package com.smsreceive.app.ui;
import android.Manifest;
import android.app.Activity;
@ -35,6 +35,19 @@ import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
import com.smsreceive.app.feishu.FeishuWebhookClient;
import com.smsreceive.app.feishu.FeishuWebhookConfigStore;
import com.smsreceive.app.feishu.FeishuWebhookPushResult;
import com.smsreceive.app.keepalive.KeepAliveDatabase;
import com.smsreceive.app.keepalive.KeepAliveStateStore;
import com.smsreceive.app.keepalive.SmsKeepAliveService;
import com.smsreceive.app.keepalive.SmsPollingService;
import com.smsreceive.app.keepalive.SmsPollingStateStore;
import com.smsreceive.app.sms.CaptureResult;
import com.smsreceive.app.sms.SmsCaptureStore;
import com.smsreceive.app.sms.SmsInboxReader;
import com.smsreceive.app.sms.VerificationCodeParser;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

View File

@ -1,4 +1,4 @@
package com.smsreceive.app;
package com.smsreceive.app.feishu;
import org.json.JSONObject;
import org.junit.Test;

View File

@ -1,4 +1,4 @@
package com.smsreceive.app;
package com.smsreceive.app.sms;
import org.junit.Test;

View File

@ -2,4 +2,3 @@ org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8
android.useAndroidX=true
android.enableJetifier=false
android.injected.testOnly=false
android.aapt2FromMavenOverride=/Users/zouchao/Library/Android/sdk/build-tools/30.0.3/aapt2