我想从设备中检索并显示SMS消息?


这是一个微不足道的过程。您可以在源代码SMSPopup中看到一个很好的例子

检查以下方法:

SmsMmsMessage getSmsDetails(Context context, long ignoreThreadId, boolean unreadOnly)
long findMessageId(Context context, long threadId, long _timestamp, int messageType
void setMessageRead(Context context, long messageId, int messageType)
void deleteMessage(Context context, long messageId, long threadId, int messageType)

下面是阅读的方法:

SmsMmsMessage getSmsDetails(Context context,
                            long ignoreThreadId, boolean unreadOnly)
{
   String SMS_READ_COLUMN = "read";
   String WHERE_CONDITION = unreadOnly ? SMS_READ_COLUMN + " = 0" : null;
   String SORT_ORDER = "date DESC";
   int count = 0;
   // Log.v(WHERE_CONDITION);
   if (ignoreThreadId > 0) {
      // Log.v("Ignoring sms threadId = " + ignoreThreadId);
      WHERE_CONDITION += " AND thread_id != " + ignoreThreadId;
   }
   Cursor cursor = context.getContentResolver().query(
                      SMS_INBOX_CONTENT_URI,
                      new String[] { "_id", "thread_id", "address", "person", "date", "body" },
                      WHERE_CONDITION,
                      null,
                      SORT_ORDER);
   if (cursor != null) {
      try {
         count = cursor.getCount();
         if (count > 0) {
            cursor.moveToFirst();
            // String[] columns = cursor.getColumnNames();
            // for (int i=0; i<columns.length; i++) {
            // Log.v("columns " + i + ": " + columns[i] + ": " + cursor.getString(i));
            // }                                         
            long messageId = cursor.getLong(0);
            long threadId = cursor.getLong(1);
            String address = cursor.getString(2);
            long contactId = cursor.getLong(3);
            String contactId_string = String.valueOf(contactId);
            long timestamp = cursor.getLong(4);

            String body = cursor.getString(5);                             
            if (!unreadOnly) {
                count = 0;
            }

            SmsMmsMessage smsMessage = new SmsMmsMessage(context, address,
                          contactId_string, body, timestamp,
                          threadId, count, messageId, SmsMmsMessage.MESSAGE_TYPE_SMS);
            return smsMessage;
         }
      } finally {
         cursor.close();
      }
   }               
   return null;
}

使用内容解析器(" Content://sms/inbox")来读取收件箱中的sms。

// public static final String INBOX = "content://sms/inbox";
// public static final String SENT = "content://sms/sent";
// public static final String DRAFT = "content://sms/draft";
Cursor cursor = getContentResolver().query(Uri.parse("content://sms/inbox"), null, null, null, null);

if (cursor.moveToFirst()) { // must check the result to prevent exception
    do {
       String msgData = "";
       for(int idx=0;idx<cursor.getColumnCount();idx++)
       {
           msgData += " " + cursor.getColumnName(idx) + ":" + cursor.getString(idx);
       }
       // use msgData
    } while (cursor.moveToNext());
} else {
   // empty box, no SMS
}

请添加READ_SMS权限。


if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        final String myPackageName = getPackageName();
        if (!Telephony.Sms.getDefaultSmsPackage(this).equals(myPackageName)) {

            Intent intent = new Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT);
            intent.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, myPackageName);
            startActivityForResult(intent, 1);
        }else {
            List<Sms> lst = getAllSms();
        }
    }else {
        List<Sms> lst = getAllSms();
    }

将app设置为默认短信app

    @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 1) {
    if (resultCode == RESULT_OK) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            final String myPackageName = getPackageName();
            if (Telephony.Sms.getDefaultSmsPackage(mActivity).equals(myPackageName)) {

                List<Sms> lst = getAllSms();
            }
        }
    }
}
}

获取短信的功能

public List<Sms> getAllSms() {
    List<Sms> lstSms = new ArrayList<Sms>();
    Sms objSms = new Sms();
    Uri message = Uri.parse("content://sms/");
    ContentResolver cr = mActivity.getContentResolver();

    Cursor c = cr.query(message, null, null, null, null);
    mActivity.startManagingCursor(c);
    int totalSMS = c.getCount();

    if (c.moveToFirst()) {
        for (int i = 0; i < totalSMS; i++) {

            objSms = new Sms();
            objSms.setId(c.getString(c.getColumnIndexOrThrow("_id")));
            objSms.setAddress(c.getString(c
                    .getColumnIndexOrThrow("address")));
            objSms.setMsg(c.getString(c.getColumnIndexOrThrow("body")));
            objSms.setReadState(c.getString(c.getColumnIndex("read")));
            objSms.setTime(c.getString(c.getColumnIndexOrThrow("date")));
            if (c.getString(c.getColumnIndexOrThrow("type")).contains("1")) {
                objSms.setFolderName("inbox");
            } else {
                objSms.setFolderName("sent");
            }

            lstSms.add(objSms);
            c.moveToNext();
        }
    }
    // else {
    // throw new RuntimeException("You have no SMS");
    // }
    c.close();

    return lstSms;
}

短信类如下:

public class Sms{
private String _id;
private String _address;
private String _msg;
private String _readState; //"0" for have not read sms and "1" for have read sms
private String _time;
private String _folderName;

public String getId(){
return _id;
}
public String getAddress(){
return _address;
}
public String getMsg(){
return _msg;
}
public String getReadState(){
return _readState;
}
public String getTime(){
return _time;
}
public String getFolderName(){
return _folderName;
}


public void setId(String id){
_id = id;
}
public void setAddress(String address){
_address = address;
}
public void setMsg(String msg){
_msg = msg;
}
public void setReadState(String readState){
_readState = readState;
}
public void setTime(String time){
_time = time;
}
public void setFolderName(String folderName){
_folderName = folderName;
}

}

不要忘记在AndroidManifest.xml中定义权限

<uses-permission android:name="android.permission.READ_SMS" />

String WHERE_CONDITION = unreadOnly ? SMS_READ_COLUMN + " = 0" : null;

改变了:

String WHERE_CONDITION = unreadOnly ? SMS_READ_COLUMN + " = 0 " : SMS_READ_COLUMN + " = 1 ";

这篇文章有点老了,但这里有另一个简单的解决方案来获得Android中与SMS内容提供商相关的数据:

使用这个lib: https://github.com/EverythingMe/easy-content-providers

Get all SMS: TelephonyProvider telephonyProvider = new TelephonyProvider(context); List<Sms> smses = telephonyProvider.getSms(Filter.ALL).getList(); Each Sms has all fields, so you can get any info you need: address, body, receivedDate, type(INBOX, SENT, DRAFT, ..), threadId, ... Gel all MMS: List<Mms> mmses = telephonyProvider.getMms(Filter.ALL).getList(); Gel all Thread: List<Thread> threads = telephonyProvider.getThreads().getList(); Gel all Conversation: List<Conversation> conversations = telephonyProvider.getConversations().getList();

它与列表或光标一起工作,有一个示例应用程序来查看它的外观和工作方式。

事实上,有一个支持所有Android内容提供者,如:联系人,呼叫日志,日历,… 完整的文档,所有选项:https://github.com/EverythingMe/easy-content-providers/wiki/Android-providers

希望它也有帮助:)


第一步:首先我们必须在清单文件中添加权限 就像

<uses-permission android:name="android.permission.RECEIVE_SMS" android:protectionLevel="signature" />
<uses-permission android:name="android.permission.READ_SMS" />

步骤2:添加服务短信接收类,用于接收短信

<receiver android:name="com.aquadeals.seller.services.SmsReceiver">
    <intent-filter>
        <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
    </intent-filter>
</receiver>

步骤3:添加运行时权限

private boolean checkAndRequestPermissions()
{
    int sms = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_SMS);

    if (sms != PackageManager.PERMISSION_GRANTED)
    {
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_SMS}, REQUEST_ID_MULTIPLE_PERMISSIONS);
        return false;
    }
    return true;
}

第四步:在应用程序中添加这些类并进行测试 接口类

public interface SmsListener {
   public void messageReceived(String messageText);
}

SmsReceiver.java

public class SmsReceiver extends BroadcastReceiver {
    private static SmsListener mListener;
    public Pattern p = Pattern.compile("(|^)\\d{6}");
    @Override
    public void onReceive(Context context, Intent intent) {
        Bundle data  = intent.getExtras();
        Object[] pdus = (Object[]) data.get("pdus");
        for(int i=0;i<pdus.length;i++)
        {
            SmsMessage smsMessage = SmsMessage.createFromPdu((byte[]) pdus[i]);
            String sender = smsMessage.getDisplayOriginatingAddress();
            String phoneNumber = smsMessage.getDisplayOriginatingAddress();
            String senderNum = phoneNumber ;
            String messageBody = smsMessage.getMessageBody();
            try{
                if(messageBody!=null){
                    Matcher m = p.matcher(messageBody);
                    if(m.find()) {
                        mListener.messageReceived(m.group(0));
                    }
                }
            }
            catch(Exception e){}
        }
    }
    public static void bindListener(SmsListener listener) {
        mListener = listener; 
    }
}

从API 19开始,你可以使用Telephony类;因为硬核值不会在每个设备中检索消息,因为内容提供程序Uri会因设备和制造商而变化。

public void getAllSms(Context context) {

    ContentResolver cr = context.getContentResolver();
    Cursor c = cr.query(Telephony.Sms.CONTENT_URI, null, null, null, null);
    int totalSMS = 0;
    if (c != null) {
        totalSMS = c.getCount();
        if (c.moveToFirst()) {
            for (int j = 0; j < totalSMS; j++) {
                String smsDate = c.getString(c.getColumnIndexOrThrow(Telephony.Sms.DATE));
                String number = c.getString(c.getColumnIndexOrThrow(Telephony.Sms.ADDRESS));
                String body = c.getString(c.getColumnIndexOrThrow(Telephony.Sms.BODY));
                Date dateFormat= new Date(Long.valueOf(smsDate));
                String type;
                switch (Integer.parseInt(c.getString(c.getColumnIndexOrThrow(Telephony.Sms.TYPE)))) {
                    case Telephony.Sms.MESSAGE_TYPE_INBOX:
                        type = "inbox";
                        break;
                    case Telephony.Sms.MESSAGE_TYPE_SENT:
                        type = "sent";
                        break;
                    case Telephony.Sms.MESSAGE_TYPE_OUTBOX:
                        type = "outbox";
                        break;
                    default:
                        break;
                }


                c.moveToNext();
            }
        }

        c.close();

    } else {
        Toast.makeText(this, "No message to show!", Toast.LENGTH_SHORT).show();
    }
}

目前已有多种答案,但我认为所有答案都忽略了这个问题的一个重要部分。 在从内部数据库或其表中读取数据之前,我们必须了解数据是如何存储在其中的,只有这样我们才能找到上述问题的解决方案:

如何在Android系统下以编程方式从设备上读取短信?

在android中,短信表是这样的

现在您可以从数据库中选择您想要的任何内容。在我们的例子中,我们只需要

身份,地址和正文

阅读短信时:

1.请求许可

int REQUEST_PHONE_CALL = 1;

   if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_SMS) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_SMS}, REQUEST_PHONE_CALL);
        }

or

 <uses-permission android:name="android.permission.READ_SMS" />

2.现在你的代码是这样的

// Create Inbox box URI
Uri inboxURI = Uri.parse("content://sms/inbox");

// List required columns
String[] reqCols = new String[]{"_id", "address", "body"};

// Get Content Resolver object, which will deal with Content Provider
ContentResolver cr = getContentResolver();

// Fetch Inbox SMS Message from Built-in Content Provider
Cursor c = cr.query(inboxURI, reqCols, null, null, null);

// Attached Cursor with adapter and display in listview
adapter = new SimpleCursorAdapter(this, R.layout.a1_row, c,
        new String[]{"body", "address"}, new int[]{
        R.id.A1_txt_Msg, R.id.A1_txt_Number});
lst.setAdapter(adapter);

我希望这对你有所帮助。 谢谢。


读取短信的Kotlin代码:

1-将此权限添加到AndroidManifest.xml:

    <uses-permission android:name="android.permission.RECEIVE_SMS"/>

创建BroadCastreceiver类:

package utils.broadcastreceivers

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.telephony.SmsMessage
import android.util.Log

class MySMSBroadCastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
    var body = ""
    val bundle = intent?.extras
    val pdusArr = bundle!!.get("pdus") as Array<Any>
    var messages: Array<SmsMessage?>  = arrayOfNulls(pdusArr.size)

 // if SMSis Long and contain more than 1 Message we'll read all of them
    for (i in pdusArr.indices) {
        messages[i] = SmsMessage.createFromPdu(pdusArr[i] as ByteArray)
    }
      var MobileNumber: String? = messages[0]?.originatingAddress
       Log.i(TAG, "MobileNumber =$MobileNumber")         
       val bodyText = StringBuilder()
        for (i in messages.indices) {
            bodyText.append(messages[i]?.messageBody)
        }
        body = bodyText.toString()
        if (body.isNotEmpty()){
       // Do something, save SMS in DB or variable , static object or .... 
                       Log.i("Inside Receiver :" , "body =$body")
        }
    }
 }

3-获得短信权限如果Android 6及以上:

   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && 
    ActivityCompat.checkSelfPermission(context!!,
            Manifest.permission.RECEIVE_SMS
        ) != PackageManager.PERMISSION_GRANTED
    ) { // Needs permission

            requestPermissions(arrayOf(Manifest.permission.RECEIVE_SMS),
            PERMISSIONS_REQUEST_READ_SMS
        )

    } else { // Permission has already been granted

    }

4-将此请求代码添加到Activity或fragment:

 companion object {
    const val PERMISSIONS_REQUEST_READ_SMS = 100
   }

5- Override检查权限请求结果fun:

 override fun onRequestPermissionsResult(
    requestCode: Int, permissions: Array<out String>,
    grantResults: IntArray
) {
    when (requestCode) {

        PERMISSIONS_REQUEST_READ_SMS -> {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Log.i("BroadCastReceiver", "PERMISSIONS_REQUEST_READ_SMS Granted")
            } else {
                //  toast("Permission must be granted  ")
            }
        }
    }
}

最简单的函数

为了读取短信,我写了一个返回Conversation对象的函数:

class Conversation(val number: String, val message: List<Message>)
class Message(val number: String, val body: String, val date: Date)

fun getSmsConversation(context: Context, number: String? = null, completion: (conversations: List<Conversation>?) -> Unit) {
        val cursor = context.contentResolver.query(Telephony.Sms.CONTENT_URI, null, null, null, null)

        val numbers = ArrayList<String>()
        val messages = ArrayList<Message>()
        var results = ArrayList<Conversation>()

        while (cursor != null && cursor.moveToNext()) {
            val smsDate = cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Sms.DATE))
            val number = cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Sms.ADDRESS))
            val body = cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Sms.BODY))

            numbers.add(number)
            messages.add(Message(number, body, Date(smsDate.toLong())))
        }

        cursor?.close()

        numbers.forEach { number ->
            if (results.find { it.number == number } == null) {
                val msg = messages.filter { it.number == number }
                results.add(Conversation(number = number, message = msg))
            }
        }

        if (number != null) {
            results = results.filter { it.number == number } as ArrayList<Conversation>
        }

        completion(results)
    }

使用:

getSmsConversation(this){ conversations ->
    conversations.forEach { conversation ->
        println("Number: ${conversation.number}")
        println("Message One: ${conversation.message[0].body}")
        println("Message Two: ${conversation.message[1].body}")
    }
}

或只得到具体数字的对话:

getSmsConversation(this, "+33666494128"){ conversations ->
    conversations.forEach { conversation ->
        println("Number: ${conversation.number}")
        println("Message One: ${conversation.message[0].body}")
        println("Message Two: ${conversation.message[1].body}")
    }
}

谷歌Play服务有两个api,可用于简化基于短信的验证过程

短信检索器接口

提供完全自动化的用户体验,不需要用户手动输入验证码,也不需要任何额外的应用权限,应该在可能的情况下使用。但是,它要求您在消息体中放置自定义哈希代码,因此您还必须控制服务器端。

消息要求-唯一标识应用程序的11位哈希码 发件人要求—无 用户交互—无

在Android应用程序中请求短信验证

在服务器上进行短信验证

短信用户同意API

不需要自定义哈希码,但需要用户批准你的应用程序的请求,以访问包含验证码的消息。为了最大限度地减少向用户显示错误消息的机会,SMS用户同意将从用户的联系人列表中过滤出发件人的消息。

消息要求-至少包含一个数字的4-10位字母数字代码 发件人要求-发件人不能在用户的联系人列表中 用户交互-一键批准

短信用户同意API是谷歌播放服务的一部分。要使用它,你至少需要这些库的17.0.0版本:

implementation "com.google.android.gms:play-services-auth:17.0.0"
implementation "com.google.android.gms:play-services-auth-api-phone:17.1.0"

第一步:开始监听短信

SMS用户同意将监听包含一次性代码的传入SMS消息长达五分钟。它不会查看在启动之前发送的任何消息。如果知道发送一次性代码的电话号码,则可以指定senderPhoneNumber,如果不指定,则null将匹配任何号码。

 smsRetriever.startSmsUserConsent(senderPhoneNumber /* or null */)

步骤2:请求同意阅读消息

一旦你的应用程序接收到包含一次性代码的消息,它就会被广播通知。在这一点上,你没有同意阅读消息-相反,你给了一个意图,你可以开始提示用户同意。在你的BroadcastReceiver内部,你使用extras中的Intent来显示提示。 当您启动该意图时,它将提示用户允许读取单个消息。他们将会看到与你的应用共享的全部文本。

val consentIntent = extras.getParcelable<Intent>(SmsRetriever.EXTRA_CONSENT_INTENT)
startActivityForResult(consentIntent, SMS_CONSENT_REQUEST)

第三步:解析一次性代码,完成短信验证

当用户点击“允许”-是时候真正阅读消息了!在onActivityResult内部,你可以从数据中获得短信的全文:

val message = data.getStringExtra(SmsRetriever.EXTRA_SMS_MESSAGE)

然后解析SMS消息并将一次性代码传递到后端!


Hier是一个很棒的视频教程!!!!效果非常好!!

这是一个组合从谷歌表列表与数字和一个Android应用程序。(非常容易遵循教程也没有编码员!!

点击链接进入教程:

https://www.youtube.com/watch?v=PReU4ITp37I&list=PLuB9drjjGa0QvFzWq_bwO8bOTRaWpdP_d&index=2

下面是谷歌应用程序脚本的代码:

const SHEET_URL = "https://docs.google.com/spreadsheets/d/16_fp7lQsnaMLaDYMVsE5YxsohQBANllEVcZeMP5ZpiU/edit#gid=0";
const SHEET_NAME = "SMS";

const doGet = () => {
  const sheet = SpreadsheetApp.openByUrl(SHEET_URL).getSheetByName(SHEET_NAME);
  const [header, ...data] = sheet.getDataRange().getDisplayValues();
  

  const PHONE = header.indexOf("Phone");
  const TEXT = header.indexOf("Text");
  const STATUS = header.indexOf("Status");

  const output = [];

 data.forEach((row, index) => {
  if (row[STATUS] === "") {
    output.push([index+1, row[PHONE], row[TEXT]]);
  }
});

const json = JSON.stringify(output);

return ContentService.createTextOutput(json).setMimeType(ContentService.MimeType.TEXT);
}

const doPost = (e) => {
  const sheet = SpreadsheetApp.openByUrl(SHEET_URL).getSheetByName(SHEET_NAME);
  const [header] = sheet.getRange("A1:1").getValues();
  const STATUS = header.indexOf("Status");
  var rowId = Number(e.parameter.row);
  sheet.getRange(rowId + 1, STATUS +1).setValue("SMS Sent");
  return ContentService.createTextOutput("").setMimeType(ContentService.MimeType.TEXT);
}

然后你只需要跟随视频的第二部分他在MIT APP Inventer中构建Android应用程序。我做了一个截图来看看这个项目


用1个Python函数获取所有短信:

我写了一个python函数,从f-droid安装SMS导入/导出,然后使用adb通过USB将手机中的SMS文件导入到pc中。

"""Ensures sms messages can be read through adb."""
import hashlib
import os
import time
from typing import List

import requests  # type: ignore[import]

from src.helper import create_and_write_file, load_dict_from_file
from src.sender.helper_sms import cmd_res, device_ready


def sms_ie_config_content(int_time: int, output_dir: str) -> List[str]:
    """Creates the sms_ie .xml config file content to schedule an export at the
    time: int time.

    int_time contains the number of minutes after midnight on which an
    sms export is scheduled.

    :param int_time: int:
    :param output_dir: str:
    :param int_time: int:
    :param output_dir: str:

    """

    content = [
        "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>",
        "<map>",
        '    <boolean name="include_binary_data" value="true" />',
        '    <boolean name="mms" value="true" />',
        '    <string name="max_messages"></string>',
        '    <boolean name="schedule_export" value="true" />',
        '    <boolean name="sms" value="true" />',
        '    <boolean name="debugging" value="false" />',
        '    <boolean name="export_call_logs" value="true" />',
        f'    <int name="export_time" value="{int_time}" />',
        '    <boolean name="export_messages" value="true" />',
        '    <string name="export_dir">content://com.android.providers.'
        + "downloads.documents/tree/raw%3A%2Fstorage%2Femulated%2F0%2FDo"
        + f"wnload%2F{output_dir}</string>",
        "</map>",
    ]
    return content


def sha256(fname: str) -> str:
    """Computes the sha256 sum of a file.

    :param fname:
    """
    hash_sha256 = hashlib.sha256()
    with open(fname, "rb") as f:
        for chunk in iter(lambda: f.read(4096), b""):
            hash_sha256.update(chunk)
    return hash_sha256.hexdigest()


def get_file(url: str, filename: str, expected_hash: str) -> None:
    """Downloads a file and verifies its filecontent is as expected.

    :param url: param filename:
    :param expected_hash:
    :param filename:
    """
    if not sms_ie_apk_file_exists(filename, expected_hash):
        response = requests.get(url)
        with open(filename, "wb") as some_file:
            some_file.write(response.content)
    assert_sms_ie_apk_file_exists(filename, expected_hash)


def assert_sms_ie_apk_file_exists(filename: str, expected_hash: str) -> None:
    """Verifies a file exists and that its content is as expected. Throws error
    if file does not exist or if the content is not expected.

    :param filename: param expected_hash:
    :param expected_hash:
    """
    if not os.path.exists(filename):
        raise Exception(f"Error, filename={filename} did not exist.")
    if expected_hash is not None:
        if sha256(filename) != expected_hash:
            raise Exception(f"Download is corrupted, {sha256(filename)}.")


def sms_ie_apk_file_exists(filename: str, expected_hash: str) -> bool:
    """Returns True a file exists and that its content is as expected. Returns
    False otherwise.

    :param filename: param expected_hash:
    :param expected_hash:
    """
    if not os.path.exists(filename):
        return False
    if sha256(filename) != expected_hash:
        return False
    return True


def app_is_installed(package_name: str) -> bool:
    """Verifies an application is installed on the phone.

    :param package_name:
    """

    installation_response = cmd_res(
        f"adb shell pm list packages {package_name}"
    )
    return installation_response == f"package:{package_name}\n"


def adb_install_apk(filename: str, package_name: str) -> None:
    """Installs an application and verifies it is installed.

    :param filename: param package_name:
    :param package_name:
    """
    if not app_is_installed(package_name):
        installation_response = cmd_res(f"adb install {filename}")
        print(f"installation_response={installation_response}")
    if not app_is_installed(package_name):
        raise Exception(
            f"Error, after installation, package:{package_name} "
            + "was not found."
        )


def ensure_sms_ie_config_file_exists(xml_path: str) -> None:
    """Ensures the configuration for the SMS Import/Export applictation exists.

    :param xml_path:
    """
    # Ensure the app directory exists.
    assert_dir_exists_on_phone("/data/data/com.github.tmo1.sms_ie/")
    ensure_dir_exists_on_phone(
        "/data/data/com.github.tmo1.sms_ie/shared_prefs/"
    )

    ensure_file_exists_on_phone(xml_path)


def create_sms_ie_output_folder(output_path: str) -> None:
    """Creates the output directory on the phone to which the sms messages will
    be exported.

    :param output_path: str:
    :param output_path: str:
    """
    ensure_dir_exists_on_phone(output_path)


def ensure_dir_exists_on_phone(path: str) -> None:
    """Creates a directory if it does not yet exist on the phone.

    :param path:
    """
    command = f"adb shell mkdir -p {path}"
    cmd_res(command)

    assert_dir_exists_on_phone(path)


def assert_dir_exists_on_phone(path: str) -> None:
    """Throws error if a directory does not yet exist on the phone.

    :param path:
    """
    command = f'adb shell [ -d {path} ] && echo "exists."'
    output = cmd_res(command)
    if output != "exists.\n":
        raise Exception(f"Error, app dir:{path} is not found.")


def file_exists_on_phone(filepath: str) -> bool:
    """Returns True if a file exists on the phone. Returns False otherwise.

    :param filepath:
    """
    command = f'adb shell [ -f {filepath} ] && echo "exists."'
    output = cmd_res(command)
    if output != "exists.\n":
        return False
    return True


def remove_file_if_exists_on_phone(filepath: str) -> None:
    """Removes file from phone if it exists.

    :param filepath:
    """
    if file_exists_on_phone(filepath):
        command = f"adb shell rm {filepath}"
        cmd_res(command)
    assert_file_does_not_exists_on_phone(filepath)


def assert_file_does_not_exists_on_phone(filepath: str) -> None:
    """Throws error if file exists on phone.

    :param filepath:
    """
    if file_exists_on_phone(filepath):
        raise Exception("Error file:{filepath} still exists on phone.")


def assert_file_exists_on_phone(filepath: str) -> None:
    """Throws error if a file does not exist on the phone.

    # TODO: verify this is not a duplicate function.

    :param filepath:
    """
    if not file_exists_on_phone(filepath):
        raise Exception("Error file:{filepath} still exists on phone.")


def ensure_file_exists_on_phone(path: str) -> None:
    """Creates a file if it does not yet exist on the phone.

    # TODO: verify this is not a duplicate function.

    :param path:
    """
    command = f"adb shell touch {path}"
    cmd_res(command)
    assert_file_exists_on_phone(path)


def copy_file_from_pc_to_phone(
    local_filepath: str, phone_filepath: str
) -> None:
    """Copies a file from the pc to the phone.

    :param local_filepath: param phone_filepath:
    :param phone_filepath:
    """
    # Overwrite file content

    command = f"adb push {local_filepath} {phone_filepath}"
    print(f"command={command}")
    # TODO: verify mdf5 values are identical
    cmd_res(command)


def copy_file_from_phone_to_pc(
    phone_filepath: str, local_filepath: str
) -> None:
    """Copies a file from the phone to the pc.

    :param phone_filepath: param local_filepath:
    :param local_filepath:
    """
    # Overwrite file content

    command = f"adb pull {phone_filepath} {local_filepath}"
    print(f"command={command}")
    # TODO: verify mdf5 values are identical
    cmd_res(command)


def verify_app_is_in_foreground(package_name: str) -> None:
    """Verify an app is opened and into the foreground.

    :param package_name:
    """

    command = (
        "adb shell dumpsys activity recents | grep 'Recent #0' | cut -d="
        + " -f2 | sed 's| .*||' | cut -d '/' -f1"
    )
    output = cmd_res(command)
    if output != f"{package_name}\n":
        raise Exception(
            "Error, app is not running in the foreground. Please try again."
        )


def restart_application(package_name: str) -> None:
    """Restarts an application.

    :param package_name: str:
    :param package_name: str:
    """

    command = f"adb shell am force-stop {package_name}"
    cmd_res(command)
    # command=f"adb shell monkey -p '{package_name}' 1"
    # Works but does not export according to schedule.
    command = (
        f"adb shell am start -n {package_name}/{package_name}.MainActivity"
    )
    print(f"command={command}")
    cmd_res(command)
    time.sleep(2)


def screen_is_locked() -> bool:
    """Returns True if the screen is locked.

    Returns False if the screen is unlocked.
    """
    command = "adb shell dumpsys power | grep 'mHolding'"
    output = cmd_res(command)
    lines = output.split()
    if (
        lines[0] == "mHoldingWakeLockSuspendBlocker=true"
        and lines[1] == "mHoldingDisplaySuspendBlocker=true"
    ):
        return False
    if (
        lines[0] == "mHoldingWakeLockSuspendBlocker=true"
        and lines[1] == "mHoldingDisplaySuspendBlocker=false"
    ):
        return True
    raise Exception(f"Unexpected output in check if screen is locked:{output}")


def clear_app_data(package_name) -> None:
    """Deletes application data.

    :param package_name:
    """
    command = f"adb shell pm clear {package_name}"
    cmd_res(command)


def send_export_sms_inputs(
    package_name, keystrokes: List[int], pause_time: int
) -> None:
    """Sends keystrokes to phone.

    :param package_name: param keystrokes: List[int]:
    :param pause_time: int:
    :param keystrokes: List[int]:
    :param pause_time: int:
    """
    time.sleep(pause_time)
    for keystroke in keystrokes:
        verify_app_is_in_foreground(package_name)
        command = f"adb shell input keyevent {keystroke}"
        cmd_res(command)
        time.sleep(pause_time)


def get_phone_date():
    """Gets current date from phone in format yyyy-mm-dd."""
    command = "adb shell date +%F"
    date = cmd_res(command)
    return date.strip()  # removes newlines


def get_phone_time(buffer_sec: int) -> tuple[int, int]:
    """Gets the time from the phone, and computes whether the time up to the
    next whole minute is enough or whether the code should wait an additional
    minute.

    :param buffer_sec: int:
    :param buffer_sec: int:
    """
    # TODO: change to: date +%T for HH:MM:SS format without parsing
    command = "adb shell date"
    date_and_time = cmd_res(command)
    time_elements = date_and_time.split(" ")

    # Extract the 18:19:20 time from the date and time string.
    for element in time_elements:
        if ":" in element:
            time_str = element

    # Split 18:19:20 into hrs, mins and seconds
    if time_str is None:
        raise Exception("Phone time not found")
    [hrs, mins, secs] = list(map(int, time_str.split(":")))
    print(f"{hrs}:{mins}:{secs}")
    wait_time = 60 - secs

    # Ensure a buffer in when to export, is taken into account.
    if secs + buffer_sec > 60:
        mins = mins + 1
        wait_time = wait_time + 60

    # Expected time:
    return (
        hrs * 60 + mins + 1,
        wait_time,
    )  # Plus 1 because the export should happen at the next minute.


def get_sms_messages_from_phone(sms_ie_dict) -> dict:
    """Gets sms messages from phone and stores them locally in a .json file.

    :param sms_ie_dict:
    """
    # Get the sms_ie .apk file from the release page.
    url = (
        "https://github.com/tmo1/sms-ie/releases/download/v1.4.1/com.github"
        + ".tmo1.sms_ie-v1.4.1.apk"
    )
    filename = "sms_ie.apk"
    expected_hash = (
        "185afc567ea5fce2df4045925687a79a717bd31185cd944a4c80c51e64ce77ec"
    )

    get_file(url, filename, expected_hash=expected_hash)

    # Connect to phone through adb
    # check if device connected
    if device_ready():

        # Install sms_ie.apk file.
        adb_install_apk(filename, sms_ie_dict["package_name"])

        clear_app_data(sms_ie_dict["package_name"])

        # Verify sms_ie config file is found and exists.
        ensure_sms_ie_config_file_exists(sms_ie_dict["phone_xml_path"])

        # Specify output directory on android device.
        # Verify output directory exists on android device.
        create_sms_ie_output_folder(sms_ie_dict["output_dirpath"])

        # Delete sms_ie output file if it exists on phone.
        output_filepath = (
            f'{sms_ie_dict["output_dirpath"]}messages-{get_phone_date()}.json'
        )
        print(f"output_filepath={output_filepath}")
        remove_file_if_exists_on_phone(output_filepath)

        # Get the time on the phone and add buffer of 10 seconds.
        export_time, wait_time = get_phone_time(5)
        print(f"export_time={export_time}")
        print(f"wait_time={wait_time}")

        # Verify config file contains schedule, and if not overwrite config.
        config_content = sms_ie_config_content(
            export_time, sms_ie_dict["output_dir"]
        )

        # Create local copy of file content:
        create_and_write_file(sms_ie_dict["local_xml_path"], config_content)

        # Push config file to device.
        copy_file_from_pc_to_phone(
            sms_ie_dict["local_xml_path"], sms_ie_dict["phone_xml_path"]
        )

        if not screen_is_locked():
            clear_app_data(sms_ie_dict["package_name"])

            # Restart sms_ie application
            restart_application(sms_ie_dict["package_name"])

            # enter, enter
            time.sleep(2)
            print("pressing enter 4 times, to grant permissions")
            send_export_sms_inputs(
                sms_ie_dict["package_name"], [23, 23, 23, 23], 1
            )

            print("pressing enter,tab, enter to export sms output")
            # enter, tab, enter
            send_export_sms_inputs(
                sms_ie_dict["package_name"], [23, 61, 23], 1
            )
            print("Waiting 15 seconds for the export of data to be completed.")
            time.sleep(15)

            # Wait for wait time+file creation duration buffer
            print(
                f"Waiting for:{wait_time} seconds until sms data export file"
                + " is created."
            )
            time.sleep(wait_time)

            # Verify output file exists
            assert_file_exists_on_phone(output_filepath)

            # Copy file from phone to local storage
            copy_file_from_phone_to_pc(
                output_filepath, sms_ie_dict["local_sms_filepath"]
            )

            # Verify the sms messages file exists locally.
            if not os.path.exists(sms_ie_dict["local_sms_filepath"]):
                raise Exception(
                    f"Error, filename={sms_ie_dict['local_sms_filepath']} did"
                    + " not exist."
                )

        else:
            raise Exception("Error, please unlock screen and try again.")
    else:
        raise Exception("Please connect phone and enable adb, and try again.")

    # Load sms_ie output file content.
    sms_messages = load_dict_from_file(sms_ie_dict["local_sms_filepath"])
    return sms_messages

它向应用程序发送击键来进行手动导出。

它被称为:

output_dir = "sms_ie_output"
sms_ie_dict = {
    "private_dir": "private_data",
    # ensure_private_data_templates_exist(private_dir)
    "output_dir": output_dir,
    "output_dirpath": f"/sdcard/Download/{output_dir}/",
    "phone_xml_path": "/data/data/com.github.tmo1.sms_ie/shared_prefs/com."
    + "github.tmo1.sms_ie_preferences.xml",
    "local_xml_path": "installation/com.github.tmo1.sms_ie_preferences.xml",
    "package_name": "com.github.tmo1.sms_ie",
    "local_sms_messages_dir": "private_data/",
    "local_sms_filepath": "private_data/messages.json",
}


sms_messages = get_sms_messages_from_phone(sms_ie_dict)

改进空间

我最初修改了配置,将计划设置为在下一分钟导出,然后等待那一分钟,然而什么都没有发生。因此,我发送手动击键来导出sms消息。