Skip to content

Commit

Permalink
Export call logs using Companion App, not adb
Browse files Browse the repository at this point in the history
  • Loading branch information
mrrfv committed Jun 26, 2023
1 parent c357418 commit 7e722bf
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 112 deletions.
1 change: 1 addition & 0 deletions .github/test-script.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
create_dummy_files() {
adb shell mkdir -p /storage/emulated/0/open-android-backup-temp
adb shell touch /storage/emulated/0/open-android-backup-temp/SMS_Messages.csv
adb shell touch /storage/emulated/0/open-android-backup-temp/Call_Logs.csv
}

export unattended_mode="yes"
Expand Down
2 changes: 1 addition & 1 deletion companion_app/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ android {

defaultConfig {
applicationId "mrrfv.backup.companion"
minSdkVersion flutter.minSdkVersion
minSdkVersion 19
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
Expand Down
1 change: 1 addition & 0 deletions companion_app/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<uses-permission android:maxSdkVersion="29" android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />

<application
android:requestLegacyExternalStorage="true"
Expand Down
4 changes: 2 additions & 2 deletions companion_app/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ buildscript {
}

dependencies {
classpath 'com.android.tools.build:gradle:4.1.0'
classpath 'com.android.tools.build:gradle:7.0.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
Expand All @@ -26,6 +26,6 @@ subprojects {
project.evaluationDependsOn(':app')
}

task clean(type: Delete) {
tasks.register("clean", Delete) {
delete rootProject.buildDir
}
2 changes: 1 addition & 1 deletion companion_app/android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
org.gradle.jvmargs=-Xmx1536M --add-opens java.base/java.io=ALL-UNNAMED
android.useAndroidX=true
android.enableJetifier=true
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip
74 changes: 56 additions & 18 deletions companion_app/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:flutter_contacts/flutter_contacts.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:flutter_sms_inbox/flutter_sms_inbox.dart';
import 'package:csv/csv.dart';
import 'package:call_log/call_log.dart';
import "dart:io";

import 'package:device_info_plus/device_info_plus.dart';
Expand Down Expand Up @@ -44,6 +45,8 @@ class _HomeState extends State<Home> {
int contactsExported = 0;
int smsMessageCount = 0;
int smsMessagesExported = 0;
int callLogsAmount = -1; // -1 => not loaded yet, 0 => no call logs found
int callLogsExported = 0;
// for restores
bool showRestoreProgress = false;
int contactsAmountFilesystem = 0;
Expand All @@ -52,13 +55,21 @@ class _HomeState extends State<Home> {
Future<void> backup(BuildContext context) async {
// Requests contacts & internal storage permissions
if (await FlutterContacts.requestPermission() &&
await Permission.storage.request().isGranted &&
await Permission.sms.request().isGranted) {
// create an instance of the SmsQuery class
SmsQuery sms = SmsQuery();

// On Android 11 and later, request additional permissions.
if ((await DeviceInfoPlugin().androidInfo).version.sdkInt! > 29 &&
// Request storage permission on Android 10 and below.
if ((await DeviceInfoPlugin().androidInfo).version.sdkInt <= 29 &&
!await Permission.storage.request().isGranted) {
showInfoDialog(context, "Permissions Needed",
"You need to grant the storage permission to export your data. You can do this by going to Settings > Apps > Open Android Backup Companion > Permissions.");
// Open app settings if the permission wasn't granted
await openAppSettings();
}

// On Android 11 and later, request manage external storage permission.
if ((await DeviceInfoPlugin().androidInfo).version.sdkInt > 29 &&
!await Permission.manageExternalStorage.request().isGranted) {
// Open app settings if the permission wasn't granted
await openAppSettings();
Expand Down Expand Up @@ -88,9 +99,9 @@ class _HomeState extends State<Home> {
// Loop over the contacts and save them as a vCard.
for (var i = 0; i < contacts.length; i++) {
final String vCard = contacts[i].toVCard(withPhoto: true);
final File file = File(
final File contactsFile = File(
"/storage/emulated/0/open-android-backup-temp/open-android-backup-contact-$i.vcf");
file.writeAsString(vCard);
contactsFile.writeAsString(vCard);
setState(() {
contactsExported = i + 1;
});
Expand Down Expand Up @@ -118,18 +129,51 @@ class _HomeState extends State<Home> {
smsMessagesExported = i + 1;
});
}
String csv = const ListToCsvConverter().convert(processedMessages);
String csvProcessedMessages =
const ListToCsvConverter().convert(processedMessages);

final File smsFileExport =
File("/storage/emulated/0/open-android-backup-temp/SMS_Messages.csv");
smsFileExport.writeAsString(csvProcessedMessages);

// Export call logs.
Iterable<CallLogEntry> entries = await CallLog.get();

final File smsFileExport = File(
"/storage/emulated/0/open-android-backup-temp/SMS_Messages.csv");
smsFileExport.writeAsString(csv);
setState(() {
callLogsAmount = entries.length;
});

// Process call logs so they can be saved to a CSV file.
List<List<String>> processedCallLogs = [];
processedCallLogs.add(["Number", "Name", "Date", "Duration"]);
for (var i = 0; i < entries.length; i++) {
List<String> callLog = [
entries.elementAt(i).number.toString(),
entries.elementAt(i).name.toString(),
entries.elementAt(i).timestamp.toString(),
entries.elementAt(i).duration.toString(),
];
processedCallLogs.add(callLog);
setState(() {
callLogsExported = i + 1;
});
}
String csvCallLogs =
const ListToCsvConverter().convert(processedCallLogs);

final File callLogsFileExport =
File("/storage/emulated/0/open-android-backup-temp/Call_Logs.csv");
callLogsFileExport.writeAsString(csvCallLogs);

// Show a dialog if the export is complete
showInfoDialog(context, "Data Exported",
"Please continue the backup process on your computer.");
} else {
showInfoDialog(context, "Error",
"Storage, SMS or contacts permissions have not been granted.");
setState(() {
showBackupProgress = false;
});
}
}

Expand Down Expand Up @@ -242,15 +286,9 @@ class _HomeState extends State<Home> {
),
Visibility(
visible: showBackupProgress,
child: Text("Exported " +
contactsExported.toString() +
" contact(s) out of " +
contactsAmountDatabase.toString() +
". Found " +
smsMessageCount.toString() +
" SMS messages to process, of which " +
smsMessagesExported.toString() +
" have been exported.")),
// there must be a cleaner way to do this
child: Text(
"Exported ${contactsExported.toString()} contact(s) out of ${contactsAmountDatabase.toString()}. Found ${smsMessageCount.toString()} SMS messages to process, of which ${smsMessagesExported.toString()} have been exported. ${callLogsAmount == -1 ? '(loading...)' : '${callLogsAmount.toString()} call log(s) have been found, ${callLogsExported.toString()} of which have been exported.'}")),
const Divider(
color: Color.fromARGB(31, 44, 44, 44),
height: 25,
Expand Down
Loading

0 comments on commit 7e722bf

Please sign in to comment.