I want to retrieve the SMS messages from the device and display them?
Use Content Resolver ("content://sms/inbox") to read SMS which are in inbox.
// 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
}
Please add READ_SMS permission.
moveToFirst
as I did. –
Thorpe 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();
}
Set app as default SMS 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();
}
}
}
}
}
Function to get SMS
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;
}
Sms class is below:
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;
}
}
Don't forget to define permission in your AndroidManifest.xml
<uses-permission android:name="android.permission.READ_SMS" />
String receiveDayTime = Functions.dateFromMilisec(Long.valueOf(c.getColumnIndexOrThrow("date")), "hh:mm a MMM dd, yyyy");
–
Gavial new SimpleDateFormat("hh:mm", Locale.US).format(new Date(Long.parseLong(_time)));
This will give you 24hr time. –
Tommietommy mActivity
is not defined. What is this? –
Bakke It is a trivial process. You can see a good example in the source code SMSPopup
Examine the following methods:
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)
this is the method for reading:
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;
}
From API 19 onwards you can make use of the Telephony Class for that; Since hardcored values won't retrieve messages in every devices because the content provider Uri changes from devices and manufacturers.
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();
}
}
This post is a little bit old, but here is another easy solution for getting data related to SMS
content provider in Android:
Use this 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();
It works with List
or Cursor
and there is a sample app to see how it looks and works.
In fact, there is a support for all Android content providers like: Contacts, Call logs, Calendar, ... Full doc with all options: https://github.com/EverythingMe/easy-content-providers/wiki/Android-providers
Hope it also helped :)
Multiple answers are already available but i think all of them are missing an important part of this question. Before reading data from an internal database or its table we have to understand how data is stored in it and only then we can find the solution to the above question that is :
How can I read SMS messages from the device programmatically in Android?
In android SMS table looks like this
Now you can select whatever you want from the database. In our case we only need
id,address and body
In case of reading SMS:
1.Ask for permissions
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.Now your code goes like this
// 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);
I hope this will be helpful. Thanks.
Step 1: first we have to add permissions in manifest file like
<uses-permission android:name="android.permission.RECEIVE_SMS" android:protectionLevel="signature" />
<uses-permission android:name="android.permission.READ_SMS" />
Step 2: then add service sms receiver class for receiving sms
<receiver android:name="com.aquadeals.seller.services.SmsReceiver">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
</intent-filter>
</receiver>
Step 3: Add run time permission
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;
}
Step 4: Add this classes in your app and test Interface class
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;
}
}
Google Play services has two APIs you can use to streamline the SMS-based verification process
Provides a fully automated user experience, without requiring the user to manually type verification codes and without requiring any extra app permissions and should be used when possible. It does, however, require you to place a custom hash code in the message body, so you must have control over server side as well.
- Message requirements - 11-digit hash code that uniquely identifies your app
- Sender requirements - None
- User interaction - None
Request SMS Verification in an Android App
Perform SMS Verification on a Server
Does not require the custom hash code, however require the user to approve your app's request to access the message containing the verification code. In order to minimize the chances of surfacing the wrong message to the user, SMS User Consent
will filter out messages from senders in the user's Contacts list.
- Message requirements - 4-10 digit alphanumeric code containing at least one number
- Sender requirements - Sender cannot be in the user's Contacts list
- User interaction - One tap to approve
The SMS User Consent API
is part of Google Play Services. To use it you’ll need at least version 17.0.0
of these libraries:
implementation "com.google.android.gms:play-services-auth:17.0.0"
implementation "com.google.android.gms:play-services-auth-api-phone:17.1.0"
Step 1: Start listening for SMS messages
SMS User Consent will listen for incoming SMS messages that contain a one-time-code for up to five minutes. It won’t look at any messages that are sent before it’s started. If you know the phone number that will send the one-time-code, you can specify the senderPhoneNumber
, or if you don’t null
will match any number.
smsRetriever.startSmsUserConsent(senderPhoneNumber /* or null */)
Step 2: Request consent to read a message
Once your app receives a message containing a one-time-code, it’ll be notified by a broadcast. At this point, you don’t have consent to read the message — instead you’re given an Intent
that you can start to prompt the user for consent. Inside your BroadcastReceiver
, you show the prompt using the Intent
in the extras
.
When you start that intent, it will prompt the user for permission to read a single message. They’ll be shown the entire text that they will share with your app.
val consentIntent = extras.getParcelable<Intent>(SmsRetriever.EXTRA_CONSENT_INTENT)
startActivityForResult(consentIntent, SMS_CONSENT_REQUEST)
Step 3: Parse the one-time-code and complete SMS Verification
When the user clicks “Allow”
— it’s time to actually read the message! Inside of onActivityResult
you can get the full text of the SMS Message from the data:
val message = data.getStringExtra(SmsRetriever.EXTRA_SMS_MESSAGE)
You then parse the SMS message and pass the one-time-code to your backend!
4-10 digit alphanumeric code containing at least one number
Can you explain what does that mean? Does it mean the length of entire message should be 4-10 characters of just the sms code? –
Pour The easiest function
To read the sms I wrote a function that returns a Conversation object:
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)
}
Using:
getSmsConversation(this){ conversations ->
conversations.forEach { conversation ->
println("Number: ${conversation.number}")
println("Message One: ${conversation.message[0].body}")
println("Message Two: ${conversation.message[1].body}")
}
}
Or get only conversation of specific number:
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}")
}
}
Kotlin Code to read SMS :
1- Add this permission to AndroidManifest.xml :
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
2-Create a BroadCastreceiver Class :
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-Get SMS Permission if Android 6 and above:
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- Add this request code to Activity or fragment :
companion object {
const val PERMISSIONS_REQUEST_READ_SMS = 100
}
5- Override Check permisstion Request result 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 ")
}
}
}
}
String WHERE_CONDITION = unreadOnly ? SMS_READ_COLUMN + " = 0" : null;
changed by:
String WHERE_CONDITION = unreadOnly ? SMS_READ_COLUMN + " = 0 " : SMS_READ_COLUMN + " = 1 ";
Get all SMS messages with 1 Python function:
I wrote a python function that installs SMS import/export from f-droid, and then gets the SMS files from the phone into the pc over USB using adb
.
"""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
It sends keystrokes to the app to do a manual export.
It is called with:
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)
Room For Improvement
I initially modified the config to set the schedule to export in the upcoming minute, and then waited for that minute, however nothing happend. So instead, I am sending manual keystrokes that export the sms messages.
Hier is an great Video Tutorial!!!! It works awsome!!!
It is a combination from a Google Sheet List with numbers and an Android App. (Very easy to follow Tutorial also for no coders!!!
Follow the Link for the Tutorial:
https://www.youtube.com/watch?v=PReU4ITp37I&list=PLuB9drjjGa0QvFzWq_bwO8bOTRaWpdP_d&index=2
Here is the Code for the Google App Script:
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);
}
And then you only have to follow the second part of the video where he building the Android APP in MIT App Inventer. I made a Screenshoot to see the project
© 2022 - 2024 — McMap. All rights reserved.