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.content.Context;
import android.util.Log; import android.util.Log;

View File

@ -15,7 +15,7 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<activity <activity
android:name=".MainActivity" android:name=".ui.MainActivity"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -25,7 +25,7 @@
</activity> </activity>
<receiver <receiver
android:name=".SmsReceiver" android:name=".sms.SmsReceiver"
android:enabled="true" android:enabled="true"
android:exported="true" android:exported="true"
android:permission="android.permission.BROADCAST_SMS"> android:permission="android.permission.BROADCAST_SMS">
@ -35,7 +35,7 @@
</receiver> </receiver>
<receiver <receiver
android:name=".BootReceiver" android:name=".keepalive.BootReceiver"
android:enabled="true" android:enabled="true"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
@ -46,12 +46,12 @@
</receiver> </receiver>
<service <service
android:name=".SmsKeepAliveService" android:name=".keepalive.SmsKeepAliveService"
android:enabled="true" android:enabled="true"
android:exported="false" /> android:exported="false" />
<service <service
android:name=".SmsPollingService" android:name=".keepalive.SmsPollingService"
android:enabled="true" android:enabled="true"
android:exported="false" /> android:exported="false" />
</application> </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.Context;
import android.content.Intent; import android.content.Intent;
@ -11,6 +11,8 @@ import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import com.smsreceive.app.sms.CaptureResult;
import java.io.IOException; import java.io.IOException;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -29,7 +31,7 @@ import okhttp3.Request;
import okhttp3.RequestBody; import okhttp3.RequestBody;
import okhttp3.Response; import okhttp3.Response;
final class FeishuWebhookClient { public final class FeishuWebhookClient {
private static final String TAG = "[SMS]SmsReceive"; 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 String WEBHOOK_URL_PREFIX = "https://open.feishu.cn/open-apis/bot/v2/hook/";
private static final char[] BASE64_TABLE = private static final char[] BASE64_TABLE =
@ -47,7 +49,7 @@ final class FeishuWebhookClient {
private FeishuWebhookClient() { private FeishuWebhookClient() {
} }
static void pushCaptureResultAsync(Context context, CaptureResult result) { public static void pushCaptureResultAsync(Context context, CaptureResult result) {
if (context == null || result == null) { if (context == null || result == null) {
return; return;
} }
@ -76,7 +78,7 @@ final class FeishuWebhookClient {
pushMarkdownAsync(appContext, markdown, result); pushMarkdownAsync(appContext, markdown, result);
} }
static void pushMarkdownAsync(Context context, String markdownContent) { public static void pushMarkdownAsync(Context context, String markdownContent) {
pushMarkdownAsync(context, markdownContent, null); pushMarkdownAsync(context, markdownContent, null);
} }
@ -98,7 +100,7 @@ final class FeishuWebhookClient {
}); });
} }
static FeishuWebhookPushResult pushMarkdown( public static FeishuWebhookPushResult pushMarkdown(
FeishuWebhookConfigStore.Config config, FeishuWebhookConfigStore.Config config,
String markdownContent, String markdownContent,
long timestampSeconds) { 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); String stringToSign = timestampSeconds + "\n" + (secret == null ? "" : secret);
Mac mac = Mac.getInstance("HmacSHA256"); Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(stringToSign.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); mac.init(new SecretKeySpec(stringToSign.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
return base64EncodeNoWrap(mac.doFinal(new byte[0])); 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() JSONObject markdown = new JSONObject()
.put("tag", "markdown") .put("tag", "markdown")
.put("content", markdownContent == null ? "" : markdownContent); .put("content", markdownContent == null ? "" : markdownContent);
@ -178,7 +180,7 @@ final class FeishuWebhookClient {
.toString(); .toString();
} }
static FeishuWebhookPushResult parseResponse(String responseBody) { public static FeishuWebhookPushResult parseResponse(String responseBody) {
try { try {
JSONObject json = new JSONObject(responseBody == null ? "" : responseBody); JSONObject json = new JSONObject(responseBody == null ? "" : responseBody);
int code = json.optInt("code", Integer.MIN_VALUE); 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()); 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)); 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 filterCode = config != null && config.filterVerificationCode;
boolean includeFullBody = config == null || !filterCode || config.sendFullBodyDebug; boolean includeFullBody = config == null || !filterCode || config.sendFullBodyDebug;
StringBuilder builder = new StringBuilder(); 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.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.util.Log; import android.util.Log;
import com.smsreceive.app.sms.CaptureResult;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -16,8 +18,8 @@ import java.io.InputStreamReader;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
final class FeishuWebhookConfigStore { public final class FeishuWebhookConfigStore {
static final String ACTION_PUSH_UPDATED = "com.smsreceive.app.ACTION_FEISHU_PUSH_UPDATED"; 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 TAG = "[SMS]SmsReceive";
private static final String PREFS = "feishu_webhook"; private static final String PREFS = "feishu_webhook";
@ -43,7 +45,7 @@ final class FeishuWebhookConfigStore {
private FeishuWebhookConfigStore() { private FeishuWebhookConfigStore() {
} }
static Config loadConfig(Context context) { public static Config loadConfig(Context context) {
ensureDefaultConfigFile(context); ensureDefaultConfigFile(context);
File file = configFile(context); File file = configFile(context);
if (!file.exists()) { if (!file.exists()) {
@ -59,7 +61,7 @@ final class FeishuWebhookConfigStore {
} }
} }
static void saveConfig( public static void saveConfig(
Context context, Context context,
boolean enabled, boolean enabled,
String webhookId, 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() preferences(context).edit()
.putLong(KEY_LAST_TIME, result.timeMillis) .putLong(KEY_LAST_TIME, result.timeMillis)
.putBoolean(KEY_LAST_SUCCESS, result.success) .putBoolean(KEY_LAST_SUCCESS, result.success)
@ -136,7 +138,7 @@ final class FeishuWebhookConfigStore {
+ ", smsKey=" + smsKey); + ", smsKey=" + smsKey);
} }
static LastResult loadLastResult(Context context) { public static LastResult loadLastResult(Context context) {
SharedPreferences prefs = preferences(context); SharedPreferences prefs = preferences(context);
return new LastResult( return new LastResult(
prefs.getLong(KEY_LAST_TIME, 0L), prefs.getLong(KEY_LAST_TIME, 0L),
@ -147,14 +149,14 @@ final class FeishuWebhookConfigStore {
prefs.getInt(KEY_LAST_API_CODE, 0)); prefs.getInt(KEY_LAST_API_CODE, 0));
} }
static LastPushedSms loadLastPushedSms(Context context) { public static LastPushedSms loadLastPushedSms(Context context) {
SharedPreferences prefs = preferences(context); SharedPreferences prefs = preferences(context);
return new LastPushedSms( return new LastPushedSms(
prefs.getLong(KEY_LAST_PUSHED_SMS_RECEIVED_SECOND, 0L), prefs.getLong(KEY_LAST_PUSHED_SMS_RECEIVED_SECOND, 0L),
prefs.getString(KEY_LAST_PUSHED_SMS_KEY, "")); prefs.getString(KEY_LAST_PUSHED_SMS_KEY, ""));
} }
static String maskSecret(String secret) { public static String maskSecret(String secret) {
if (isEmpty(secret)) { if (isEmpty(secret)) {
return ""; return "";
} }
@ -164,15 +166,15 @@ final class FeishuWebhookConfigStore {
return secret.substring(0, 3) + "***" + secret.substring(secret.length() - 3); 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(); return configFile(context).getAbsolutePath();
} }
static String defaultConfigPath(Context context) { public static String defaultConfigPath(Context context) {
return defaultConfigFile(context).getAbsolutePath(); return defaultConfigFile(context).getAbsolutePath();
} }
static String configTemplate() { public static String configTemplate() {
try { try {
return configToJson(defaultConfig()).toString(2); return configToJson(defaultConfig()).toString(2);
} catch (JSONException e) { } catch (JSONException e) {
@ -289,14 +291,14 @@ final class FeishuWebhookConfigStore {
+ Integer.toHexString((result.body == null ? "" : result.body).hashCode()); + Integer.toHexString((result.body == null ? "" : result.body).hashCode());
} }
static final class Config { public static final class Config {
final boolean enabled; public final boolean enabled;
final String webhookId; public final String webhookId;
final String secret; public final String secret;
final boolean sendFullBodyDebug; public final boolean sendFullBodyDebug;
final boolean filterVerificationCode; public final boolean filterVerificationCode;
Config( public Config(
boolean enabled, boolean enabled,
String webhookId, String webhookId,
String secret, String secret,
@ -309,22 +311,22 @@ final class FeishuWebhookConfigStore {
this.filterVerificationCode = filterVerificationCode; this.filterVerificationCode = filterVerificationCode;
} }
boolean hasWebhookId() { public boolean hasWebhookId() {
return !isEmpty(webhookId); return !isEmpty(webhookId);
} }
boolean hasSecret() { public boolean hasSecret() {
return !isEmpty(secret); return !isEmpty(secret);
} }
} }
static final class LastResult { public static final class LastResult {
final long timeMillis; public final long timeMillis;
final boolean success; public final boolean success;
final String status; public final String status;
final String message; public final String message;
final int httpStatus; public final int httpStatus;
final int apiCode; public final int apiCode;
LastResult(long timeMillis, boolean success, String status, String message, int httpStatus, int apiCode) { LastResult(long timeMillis, boolean success, String status, String message, int httpStatus, int apiCode) {
this.timeMillis = timeMillis; this.timeMillis = timeMillis;
@ -336,9 +338,9 @@ final class FeishuWebhookConfigStore {
} }
} }
static final class LastPushedSms { public static final class LastPushedSms {
final long receivedSecond; public final long receivedSecond;
final String smsKey; public final String smsKey;
LastPushedSms(long receivedSecond, String smsKey) { LastPushedSms(long receivedSecond, String smsKey) {
this.receivedSecond = receivedSecond; 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.BroadcastReceiver;
import android.content.Context; 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.ContentValues;
import android.content.Context; import android.content.Context;
@ -11,7 +11,7 @@ import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.Locale; import java.util.Locale;
final class KeepAliveDatabase { public final class KeepAliveDatabase {
private static final String TAG = "[SMS]SmsReceive"; private static final String TAG = "[SMS]SmsReceive";
private static final String DATABASE_NAME = "sms_keep_alive.db"; private static final String DATABASE_NAME = "sms_keep_alive.db";
private static final int DATABASE_VERSION = 1; private static final int DATABASE_VERSION = 1;
@ -23,7 +23,7 @@ final class KeepAliveDatabase {
private KeepAliveDatabase() { private KeepAliveDatabase() {
} }
static long writeLastActiveTime(Context context) { public static long writeLastActiveTime(Context context) {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
SQLiteDatabase database = helper(context).getWritableDatabase(); SQLiteDatabase database = helper(context).getWritableDatabase();
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
@ -35,7 +35,7 @@ final class KeepAliveDatabase {
return now; return now;
} }
static long readLastActiveTime(Context context) { public static long readLastActiveTime(Context context) {
SQLiteDatabase database = helper(context).getReadableDatabase(); SQLiteDatabase database = helper(context).getReadableDatabase();
try (Cursor cursor = database.query( try (Cursor cursor = database.query(
TABLE_META, TABLE_META,

View File

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

View File

@ -1,4 +1,4 @@
package com.smsreceive.app; package com.smsreceive.app.keepalive;
import android.app.Service; import android.app.Service;
import android.content.Context; import android.content.Context;
@ -10,6 +10,8 @@ import android.os.Looper;
import android.util.Log; import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import com.smsreceive.app.sms.SmsCaptureStore;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.Locale; 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); Log.d(TAG, "SmsKeepAliveService.start requested sdk=" + Build.VERSION.SDK_INT);
Intent intent = new Intent(context, SmsKeepAliveService.class); Intent intent = new Intent(context, SmsKeepAliveService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 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"); Log.d(TAG, "SmsKeepAliveService.stop requested");
context.stopService(new Intent(context, SmsKeepAliveService.class)); 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.Manifest;
import android.app.Service; import android.app.Service;
@ -12,6 +12,11 @@ import android.os.Looper;
import android.util.Log; import android.util.Log;
import android.widget.Toast; 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 { public final class SmsPollingService extends Service {
private static final String TAG = "[SMS]SmsReceive"; private static final String TAG = "[SMS]SmsReceive";
private static final String SOURCE_INBOX_POLLING = "sms_inbox_polling"; 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); Log.d(TAG, "SmsPollingService.start requested sdk=" + Build.VERSION.SDK_INT);
long startTimeMillis = System.currentTimeMillis() - 2_000L; long startTimeMillis = System.currentTimeMillis() - 2_000L;
SmsPollingStateStore.recordStarted(context, startTimeMillis); 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"); Log.d(TAG, "SmsPollingService.stop requested");
SmsPollingStateStore.recordStopped(context, "用户停止轮询"); SmsPollingStateStore.recordStopped(context, "用户停止轮询");
context.stopService(new Intent(context, SmsPollingService.class)); 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.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
final class SmsPollingStateStore { public final class SmsPollingStateStore {
private static final String TAG = "[SMS]SmsReceive"; private static final String TAG = "[SMS]SmsReceive";
private static final String PREFS = "sms_polling"; private static final String PREFS = "sms_polling";
private static final String KEY_ENABLED_BY_USER = "enabled_by_user"; private static final String KEY_ENABLED_BY_USER = "enabled_by_user";
@ -22,7 +22,7 @@ final class SmsPollingStateStore {
private 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); Log.d(TAG, "SmsPollingStateStore.recordStarted startTime=" + startTimeMillis);
preferences(context).edit() preferences(context).edit()
.putBoolean(KEY_ENABLED_BY_USER, true) .putBoolean(KEY_ENABLED_BY_USER, true)
@ -32,7 +32,7 @@ final class SmsPollingStateStore {
.apply(); .apply();
} }
static void recordStopped(Context context, String reason) { public static void recordStopped(Context context, String reason) {
Log.d(TAG, "SmsPollingStateStore.recordStopped reason=" + reason); Log.d(TAG, "SmsPollingStateStore.recordStopped reason=" + reason);
preferences(context).edit() preferences(context).edit()
.putBoolean(KEY_ENABLED_BY_USER, false) .putBoolean(KEY_ENABLED_BY_USER, false)
@ -41,7 +41,7 @@ final class SmsPollingStateStore {
.apply(); .apply();
} }
static void recordServiceStopped(Context context, String reason) { public static void recordServiceStopped(Context context, String reason) {
Log.d(TAG, "SmsPollingStateStore.recordServiceStopped reason=" + reason); Log.d(TAG, "SmsPollingStateStore.recordServiceStopped reason=" + reason);
preferences(context).edit() preferences(context).edit()
.putBoolean(KEY_RUNNING, false) .putBoolean(KEY_RUNNING, false)
@ -49,13 +49,13 @@ final class SmsPollingStateStore {
.apply(); .apply();
} }
static void recordServiceRunning(Context context) { public static void recordServiceRunning(Context context) {
preferences(context).edit() preferences(context).edit()
.putBoolean(KEY_RUNNING, true) .putBoolean(KEY_RUNNING, true)
.apply(); .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); Log.d(TAG, "SmsPollingStateStore.recordHit id=" + smsId + ", time=" + hitTimeMillis);
preferences(context).edit() preferences(context).edit()
.putLong(KEY_LAST_HIT_ID, smsId) .putLong(KEY_LAST_HIT_ID, smsId)
@ -64,7 +64,7 @@ final class SmsPollingStateStore {
.apply(); .apply();
} }
static void setIntervalSeconds(Context context, int seconds) { public static void setIntervalSeconds(Context context, int seconds) {
int safeSeconds = clampIntervalSeconds(seconds); int safeSeconds = clampIntervalSeconds(seconds);
Log.d(TAG, "SmsPollingStateStore.setIntervalSeconds seconds=" + safeSeconds); Log.d(TAG, "SmsPollingStateStore.setIntervalSeconds seconds=" + safeSeconds);
preferences(context).edit() preferences(context).edit()
@ -72,11 +72,11 @@ final class SmsPollingStateStore {
.apply(); .apply();
} }
static int getIntervalSeconds(Context context) { public static int getIntervalSeconds(Context context) {
return clampIntervalSeconds(preferences(context).getInt(KEY_INTERVAL_SECONDS, DEFAULT_INTERVAL_SECONDS)); 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); SharedPreferences prefs = preferences(context);
return new State( return new State(
prefs.getBoolean(KEY_ENABLED_BY_USER, false), 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)); return Math.max(MIN_INTERVAL_SECONDS, Math.min(seconds, MAX_INTERVAL_SECONDS));
} }
static final class State { public static final class State {
final boolean enabledByUser; public final boolean enabledByUser;
final boolean running; public final boolean running;
final long startTimeMillis; public final long startTimeMillis;
final long lastHitId; public final long lastHitId;
final long lastHitTimeMillis; public final long lastHitTimeMillis;
final String lastFailure; public final String lastFailure;
final int intervalSeconds; public final int intervalSeconds;
State( State(
boolean enabledByUser, boolean enabledByUser,

View File

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

View File

@ -1,12 +1,12 @@
package com.smsreceive.app; package com.smsreceive.app.sms;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
final class SmsCaptureStore { public final class SmsCaptureStore {
static final String ACTION_CAPTURE_UPDATED = "com.smsreceive.app.ACTION_CAPTURE_UPDATED"; public static final String ACTION_CAPTURE_UPDATED = "com.smsreceive.app.ACTION_CAPTURE_UPDATED";
private static final String TAG = "[SMS]SmsReceive"; private static final String TAG = "[SMS]SmsReceive";
private static final String PREFS = "sms_capture"; private static final String PREFS = "sms_capture";
@ -25,7 +25,7 @@ final class SmsCaptureStore {
private SmsCaptureStore() { private SmsCaptureStore() {
} }
static void save(Context context, CaptureResult result) { public static void save(Context context, CaptureResult result) {
VerificationCodeParser.ParseResult parse = result.parseResult; VerificationCodeParser.ParseResult parse = result.parseResult;
Log.d(TAG, "SmsCaptureStore.save source=" + result.source Log.d(TAG, "SmsCaptureStore.save source=" + result.source
+ ", success=" + parse.success + ", success=" + parse.success
@ -53,7 +53,7 @@ final class SmsCaptureStore {
editor.apply(); editor.apply();
} }
static StoredCapture load(Context context) { public static StoredCapture load(Context context) {
SharedPreferences prefs = preferences(context); SharedPreferences prefs = preferences(context);
return new StoredCapture( return new StoredCapture(
prefs.getLong(KEY_TIME, 0L), prefs.getLong(KEY_TIME, 0L),
@ -66,12 +66,12 @@ final class SmsCaptureStore {
prefs.getString(KEY_BODY_PREVIEW, "")); prefs.getString(KEY_BODY_PREVIEW, ""));
} }
static void clear(Context context) { public static void clear(Context context) {
Log.d(TAG, "SmsCaptureStore.clear"); Log.d(TAG, "SmsCaptureStore.clear");
preferences(context).edit().clear().apply(); preferences(context).edit().clear().apply();
} }
static DeliveryDiagnostics loadDeliveryDiagnostics(Context context) { public static DeliveryDiagnostics loadDeliveryDiagnostics(Context context) {
SharedPreferences prefs = preferences(context); SharedPreferences prefs = preferences(context);
return new DeliveryDiagnostics( return new DeliveryDiagnostics(
prefs.getLong(KEY_LAST_BROADCAST_TIME, 0L), prefs.getLong(KEY_LAST_BROADCAST_TIME, 0L),
@ -101,15 +101,15 @@ final class SmsCaptureStore {
return normalized.length() <= 48 ? normalized : normalized.substring(0, 48) + "..."; return normalized.length() <= 48 ? normalized : normalized.substring(0, 48) + "...";
} }
static final class StoredCapture { public static final class StoredCapture {
final long timeMillis; public final long timeMillis;
final String sender; public final String sender;
final String code; public final String code;
final String strategy; public final String strategy;
final int confidence; public final int confidence;
final String source; public final String source;
final String failure; public final String failure;
final String bodyPreview; public final String bodyPreview;
StoredCapture( StoredCapture(
long timeMillis, long timeMillis,
@ -131,10 +131,10 @@ final class SmsCaptureStore {
} }
} }
static final class DeliveryDiagnostics { public static final class DeliveryDiagnostics {
final long lastBroadcastTimeMillis; public final long lastBroadcastTimeMillis;
final long lastInboxTimeMillis; public final long lastInboxTimeMillis;
final String lastInboxSource; public final String lastInboxSource;
DeliveryDiagnostics(long lastBroadcastTimeMillis, long lastInboxTimeMillis, String lastInboxSource) { DeliveryDiagnostics(long lastBroadcastTimeMillis, long lastInboxTimeMillis, String lastInboxSource) {
this.lastBroadcastTimeMillis = lastBroadcastTimeMillis; this.lastBroadcastTimeMillis = lastBroadcastTimeMillis;
@ -142,7 +142,7 @@ final class SmsCaptureStore {
this.lastInboxSource = lastInboxSource == null ? "" : lastInboxSource; this.lastInboxSource = lastInboxSource == null ? "" : lastInboxSource;
} }
boolean inboxNewerThanBroadcast() { public boolean inboxNewerThanBroadcast() {
return lastInboxTimeMillis > 0L && lastInboxTimeMillis > lastBroadcastTimeMillis; 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.content.Context;
import android.database.Cursor; import android.database.Cursor;
@ -11,14 +11,14 @@ import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.Locale; import java.util.Locale;
final class SmsInboxReader { public final class SmsInboxReader {
private static final String TAG = "[SMS]SmsReceive"; private static final String TAG = "[SMS]SmsReceive";
private static final Uri SMS_INBOX_URI = Uri.parse("content://sms/inbox"); private static final Uri SMS_INBOX_URI = Uri.parse("content://sms/inbox");
private SmsInboxReader() { private SmsInboxReader() {
} }
static InboxResult readLatest(Context context) { public static InboxResult readLatest(Context context) {
String[] projection = { String[] projection = {
Telephony.Sms._ID, Telephony.Sms._ID,
Telephony.Sms.ADDRESS, 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; Uri uri = Telephony.Sms.CONTENT_URI;
String[] projection = { String[] projection = {
Telephony.Sms._ID, 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); 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; Uri uri = Telephony.Sms.CONTENT_URI;
String[] projection = { String[] projection = {
Telephony.Sms._ID, Telephony.Sms._ID,
@ -231,13 +231,13 @@ final class SmsInboxReader {
} }
} }
static final class InboxResult { public static final class InboxResult {
final boolean success; public final boolean success;
final long id; public final long id;
final String sender; public final String sender;
final String body; public final String body;
final long dateMillis; public final long dateMillis;
final String failureReason; public final String failureReason;
private InboxResult(boolean success, long id, String sender, String body, long dateMillis, String failureReason) { private InboxResult(boolean success, long id, String sender, String body, long dateMillis, String failureReason) {
this.success = success; this.success = success;
@ -257,15 +257,15 @@ final class SmsInboxReader {
} }
} }
static final class RecentCodeResult { public static final class RecentCodeResult {
final boolean success; public final boolean success;
final long id; public final long id;
final String sender; public final String sender;
final String body; public final String body;
final long dateMillis; public final long dateMillis;
final VerificationCodeParser.ParseResult parseResult; public final VerificationCodeParser.ParseResult parseResult;
final int scannedCount; public final int scannedCount;
final String failureReason; public final String failureReason;
private RecentCodeResult( private RecentCodeResult(
boolean success, boolean success,

View File

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

View File

@ -1,4 +1,4 @@
package com.smsreceive.app; package com.smsreceive.app.ui;
import android.Manifest; import android.Manifest;
import android.app.Activity; import android.app.Activity;
@ -35,6 +35,19 @@ import android.widget.Switch;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; 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.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.Locale; 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.json.JSONObject;
import org.junit.Test; import org.junit.Test;

View File

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

View File

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