This commit is contained in:
jens 2021-09-18 02:04:29 +02:00
parent 57fe7ab08a
commit f983ba504b
7 changed files with 155 additions and 21 deletions

20
README.md Normal file
View File

@ -0,0 +1,20 @@
# RCS Exporter
I've been using the app SMS Backup & Restore for years. After getting a new phone and transferring the messages over I noticed some were missing.
I tried to find out why this is happening - because of a "new" message type called RCS: https://en.wikipedia.org/wiki/Rich_Communication_Services
Essentially those messages are stored in a separate database on the phone, hence not considered regular text messages.
The above mentioned program is not able to export those messages (as of September 2021).
I didn't want to reinvent the wheel - there are already enough backup/restore apps for SMS and MMS. Instead I just wanted to be able to export the RCS messages, too. Preferably it should be a format the SMS BR can then import.
So if you're interested:
1. Backup your SMS and MMS messages with another program.
2. Import them on the target phone.
3. Export the RCS using this app.
4. Import those on the new phone as well.
Remarks:
- This program has only been tested with text messages. I don't know if images, etc. can be exported as well.

View File

@ -11,6 +11,7 @@ android {
targetSdk 30 targetSdk 30
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
useLibrary 'org.apache.http.legacy'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }

View File

@ -27,6 +27,10 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<!-- https://developer.android.com/about/versions/pie/android-9.0-changes-28#apache-p-->
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
</application> </application>
</manifest> </manifest>

View File

@ -1,29 +1,40 @@
package de.server47.smsexport; package de.server47.smsexport;
import android.app.Activity; import android.app.Activity;
import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.Toast;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.documentfile.provider.DocumentFile;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
public class MainActivity extends Activity public class MainActivity extends Activity
{ {
Button bRead; Button bRead, bWrite;
final static int typeSMS = 0; final static int typeSMS = 0;
final static int typeMMS = 1; final static int typeMMS = 1;
final static int typeICS = 2; final static int typeICS = 2;
final static int requestCodeExport = 815;
String contentToWrite = "bla";
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState) protected void onCreate(@Nullable Bundle savedInstanceState)
{ {
@ -31,6 +42,7 @@ public class MainActivity extends Activity
setContentView(R.layout.main_activity); setContentView(R.layout.main_activity);
bRead = (Button)findViewById(R.id.bRead); bRead = (Button)findViewById(R.id.bRead);
bWrite = (Button)findViewById(R.id.bWrite);
bRead.setOnClickListener(new View.OnClickListener() bRead.setOnClickListener(new View.OnClickListener()
{ {
@ -48,16 +60,24 @@ public class MainActivity extends Activity
export.append("backup_date=\"1631273111057\""); export.append("backup_date=\"1631273111057\"");
export.append("type=\"full\">"); export.append("type=\"full\">");
// 05.05.2001 19:15:00 // 05.05.2001 19:15:00
String pattern = "dd-MM-yyyy H:m:s"; String pattern = "dd.MM.yyyy H:m:s";
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern); SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern);
for(int i = 0; i < response.size(); i++) for(int i = 0; i < response.size(); i++)
{ {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(Long.parseLong(response.get(i).get("date")));
/*
type: 1 = inbound
type: 2 = outbound
*/
export.append( export.append(
"<sms protocol=\"0\"" + "<sms protocol=\"0\"" +
"address=\"" + response.get(i).get("remote_uri").replace("tel:", "") + "\"" + "address=\"" + response.get(i).get("remote_uri").replace("tel:", "") + "\"" +
"date=\"" + response.get(i).get("date") + "\"" + "date=\"" + response.get(i).get("date") + "\"" +
"type=\"1\"" + "type=\"" + response.get(i).get("type") + "\"" +
"subject=\"null\"" + "subject=\"null\"" +
"body=\"" + response.get(i).get("body") + "\"" + "body=\"" + response.get(i).get("body") + "\"" +
"toa=\"null\"" + "toa=\"null\"" +
@ -66,19 +86,92 @@ public class MainActivity extends Activity
"read=\"1\"" + "read=\"1\"" +
"status=\"-1\"" + "status=\"-1\"" +
"locked=\"0\"" + "locked=\"0\"" +
"date_sent=\"0\"" + "date_sent=\"" + response.get(i).get("date_sent") + "\"" +
"sub_id=\"-1\"" + "sub_id=\"-1\"" +
"readable_date=\"" + 05.05.2001 19:15:00 + "\"" + "readable_date=\"" + simpleDateFormat.format(cal.getTime()) + "\"" +
"contact_name=\"(Unknown)\"/>"; "contact_name=\"(Unknown)\"/>"
response.get(i).get()
); );
} }
export.append("</smses>"); export.append("</smses>");
contentToWrite = export.toString();
} }
} }
}); });
bWrite.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, requestCodeExport);
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
if(resultCode == RESULT_OK)
{
switch (requestCode)
{
case requestCodeExport:
if (resultCode == RESULT_OK)
{
Uri uriTree = data.getData();
exportFiles(uriTree, contentToWrite);
}
break;
}
}
}
void exportFiles(Uri uriTree, String content)
{
String fileName = "Message_export_" + String.valueOf(Calendar.getInstance().getTimeInMillis()) + ".xml";
DocumentFile directory = DocumentFile.fromTreeUri(this, uriTree);
// Clean up
// for(DocumentFile file : directory.listFiles())
// {
// /*
// On some few users' devices it seems this caused a crash because file.getName() was null.
// The reason for that remains unknown, but we don't want the export to crash because of it.
// */
// if(!StringUtils.isEmpty(file.getName()))
// {
// if (file.getName().equals(XmlFileInterface.settingsFileName) && file.canWrite())
// file.delete();
// else if (file.getName().equals(prefsFileName) && file.canWrite())
// file.delete();
// }
// }
DocumentFile exportFile = directory.createFile("text/xml", fileName);
if(exportFile.canWrite())
{
try
{
OutputStream out = getApplicationContext().getContentResolver().openOutputStream(exportFile.getUri());
out.write(content.getBytes());
out.close();
Toast.makeText(MainActivity.this, "Export complete.", Toast.LENGTH_LONG).show();
}
catch (IOException e)
{
e.printStackTrace();
Toast.makeText(MainActivity.this, "Error while writing file.", Toast.LENGTH_LONG).show();
}
}
// else
// Toast.makeText(MainActivity.this, getResources().getString(R.string.ConfigurationExportError), Toast.LENGTH_LONG).show();
} }
List<Map<String,String>> readMessages(int messageType) List<Map<String,String>> readMessages(int messageType)

View File

@ -2,8 +2,16 @@
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android" <androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"
android:layout_margin="@dimen/defaultMargin"
android:gravity="center_horizontal"> android:gravity="center_horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline2" />
<Button <Button
android:id="@+id/bRead" android:id="@+id/bRead"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -11,4 +19,11 @@
android:layout_gravity="center" android:layout_gravity="center"
android:text="Read" /> android:text="Read" />
<Button
android:id="@+id/bWrite"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Write" />
</androidx.appcompat.widget.LinearLayoutCompat> </androidx.appcompat.widget.LinearLayoutCompat>

View File

@ -1,3 +1,4 @@
<resources> <resources>
<dimen name="fab_margin">16dp</dimen> <dimen name="fab_margin">16dp</dimen>
<dimen name="defaultMargin">25px</dimen>
</resources> </resources>

View File

@ -1,5 +1,5 @@
<resources> <resources>
<string name="app_name">SmsExport</string> <string name="app_name">RcsExport</string>
<string name="action_settings">Settings</string> <string name="action_settings">Settings</string>
<!-- Strings used for fragments for navigation --> <!-- Strings used for fragments for navigation -->
<string name="first_fragment_label">First Fragment</string> <string name="first_fragment_label">First Fragment</string>