package com.smsreceive.app.sms; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.provider.Telephony; import android.text.TextUtils; import android.util.Log; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; 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() { } public static InboxResult readLatest(Context context) { String[] projection = { Telephony.Sms._ID, Telephony.Sms.ADDRESS, Telephony.Sms.BODY, Telephony.Sms.DATE }; try (Cursor cursor = context.getContentResolver().query( SMS_INBOX_URI, projection, null, null, Telephony.Sms.DATE + " DESC LIMIT 1")) { if (cursor == null) { Log.w(TAG, "SmsInboxReader.readLatest failed: cursor is null"); return InboxResult.failure("短信库查询 cursor 为空"); } if (!cursor.moveToFirst()) { Log.w(TAG, "SmsInboxReader.readLatest failed: inbox empty"); return InboxResult.failure("短信收件箱为空"); } long id = cursor.getLong(cursor.getColumnIndexOrThrow(Telephony.Sms._ID)); String sender = cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Sms.ADDRESS)); String body = cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Sms.BODY)); long date = cursor.getLong(cursor.getColumnIndexOrThrow(Telephony.Sms.DATE)); if (TextUtils.isEmpty(body)) { Log.w(TAG, "SmsInboxReader.readLatest failed: empty body id=" + id); return InboxResult.failure("最新短信正文为空"); } Log.d(TAG, "SmsInboxReader.readLatest success id=" + id + ", sender=" + maskSender(sender) + ", date=" + date + ", bodyLength=" + body.length()); return InboxResult.success(id, sender, body, date); } catch (SecurityException e) { Log.w(TAG, "SmsInboxReader.readLatest failed: READ_SMS denied", e); return InboxResult.failure("READ_SMS 未授权"); } catch (Exception e) { Log.w(TAG, "SmsInboxReader.readLatest failed", e); return InboxResult.failure("短信库查询失败:" + e.getClass().getSimpleName()); } } public static RecentSmsResult findLatestSms(Context context, int limit) { return findLatestSms(context, limit, 0L); } public static RecentSmsResult findLatestSms(Context context, int limit, long minDateMillis) { Uri uri = Telephony.Sms.CONTENT_URI; String[] projection = { Telephony.Sms._ID, Telephony.Sms.ADDRESS, Telephony.Sms.BODY, Telephony.Sms.DATE, Telephony.Sms.TYPE }; int safeLimit = Math.max(1, Math.min(limit, 100)); String selection = minDateMillis > 0L ? Telephony.Sms.DATE + ">=?" : null; String[] selectionArgs = minDateMillis > 0L ? new String[]{String.valueOf(minDateMillis)} : null; Log.d(TAG, "SmsInboxReader.findLatestSms start limit=" + safeLimit + ", minDate=" + (minDateMillis > 0L ? formatDate(minDateMillis) : "none")); try (Cursor cursor = context.getContentResolver().query( uri, projection, selection, selectionArgs, Telephony.Sms.DATE + " DESC LIMIT " + safeLimit)) { if (cursor == null) { Log.w(TAG, "SmsInboxReader.findLatestSms cursor is null"); return RecentSmsResult.failure("短信库查询 cursor 为空"); } int scanned = 0; while (cursor.moveToNext()) { long id = cursor.getLong(cursor.getColumnIndexOrThrow(Telephony.Sms._ID)); String sender = cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Sms.ADDRESS)); String body = cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Sms.BODY)); long date = cursor.getLong(cursor.getColumnIndexOrThrow(Telephony.Sms.DATE)); int type = cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Sms.TYPE)); scanned++; Log.d(TAG, "poll scan SMS[" + (scanned - 1) + "] id=" + id + ", type=" + smsTypeName(type) + ", date=" + formatDate(date) + ", sender=" + maskSender(sender) + ", bodyPreview=" + previewBody(body)); if (TextUtils.isEmpty(body)) { continue; } Log.d(TAG, "SmsInboxReader.findLatestSms hit latest id=" + id + ", date=" + formatDate(date) + ", scanned=" + scanned); return RecentSmsResult.success(id, sender, body, date, scanned); } Log.d(TAG, "SmsInboxReader.findLatestSms no sms scanned=" + scanned); return RecentSmsResult.noSms(scanned); } catch (SecurityException e) { Log.w(TAG, "SmsInboxReader.findLatestSms failed: READ_SMS denied", e); return RecentSmsResult.failure("READ_SMS 未授权"); } catch (Exception e) { Log.w(TAG, "SmsInboxReader.findLatestSms failed", e); return RecentSmsResult.failure("短信库查询失败:" + e.getClass().getSimpleName()); } } private static String maskSender(String sender) { if (sender == null || sender.length() <= 4) { return sender == null ? "" : sender; } return "***" + sender.substring(sender.length() - 4); } private static String previewBody(String body) { if (TextUtils.isEmpty(body)) { return ""; } String normalized = body.replace('\n', ' ').replace('\r', ' ').trim(); return normalized.length() <= 80 ? normalized : normalized.substring(0, 80) + "..."; } private static String formatDate(long dateMillis) { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA).format(new Date(dateMillis)); } private static String smsTypeName(int type) { switch (type) { case Telephony.Sms.MESSAGE_TYPE_INBOX: return "INBOX"; case Telephony.Sms.MESSAGE_TYPE_SENT: return "SENT"; case Telephony.Sms.MESSAGE_TYPE_DRAFT: return "DRAFT"; case Telephony.Sms.MESSAGE_TYPE_OUTBOX: return "OUTBOX"; case Telephony.Sms.MESSAGE_TYPE_FAILED: return "FAILED"; case Telephony.Sms.MESSAGE_TYPE_QUEUED: return "QUEUED"; default: return "UNKNOWN(" + type + ")"; } } 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; this.id = id; this.sender = sender == null ? "" : sender; this.body = body == null ? "" : body; this.dateMillis = dateMillis; this.failureReason = failureReason == null ? "" : failureReason; } static InboxResult success(long id, String sender, String body, long dateMillis) { return new InboxResult(true, id, sender, body, dateMillis, ""); } static InboxResult failure(String reason) { return new InboxResult(false, -1L, "", "", System.currentTimeMillis(), reason); } } public static final class RecentSmsResult { public final boolean success; public final long id; public final String sender; public final String body; public final long dateMillis; public final int scannedCount; public final String failureReason; private RecentSmsResult( boolean success, long id, String sender, String body, long dateMillis, int scannedCount, String failureReason) { this.success = success; this.id = id; this.sender = sender == null ? "" : sender; this.body = body == null ? "" : body; this.dateMillis = dateMillis; this.scannedCount = scannedCount; this.failureReason = failureReason == null ? "" : failureReason; } static RecentSmsResult success( long id, String sender, String body, long dateMillis, int scannedCount) { return new RecentSmsResult(true, id, sender, body, dateMillis, scannedCount, ""); } static RecentSmsResult noSms(int scannedCount) { return new RecentSmsResult(false, -1L, "", "", System.currentTimeMillis(), scannedCount, "最近短信未找到新短信"); } static RecentSmsResult failure(String reason) { return new RecentSmsResult(false, -1L, "", "", System.currentTimeMillis(), 0, reason); } } }