├── .github
└── workflows
│ └── release_tag.yml
├── .gitignore
├── LICENSE.md
├── README.md
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── balti
│ │ └── migrate
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── balti
│ │ │ └── migrate
│ │ │ ├── BaseApplication.kt
│ │ │ ├── app
│ │ │ ├── MainActivity.kt
│ │ │ ├── MainActivityViewModel.kt
│ │ │ ├── di
│ │ │ │ └── AppDiModule.kt
│ │ │ └── ui
│ │ │ │ ├── components
│ │ │ │ └── MainScreenNavContainer.kt
│ │ │ │ ├── navigation
│ │ │ │ ├── AppNavBarItem.kt
│ │ │ │ ├── Graph.kt
│ │ │ │ ├── HomeGraphViewModel.kt
│ │ │ │ └── RestoreRouteChoicesViewModel.kt
│ │ │ │ ├── screens
│ │ │ │ ├── appSettings
│ │ │ │ │ ├── AppSettings.kt
│ │ │ │ │ ├── AppSettingsAction.kt
│ │ │ │ │ ├── AppSettingsState.kt
│ │ │ │ │ └── AppSettingsViewModel.kt
│ │ │ │ ├── home
│ │ │ │ │ ├── AboutDialog.kt
│ │ │ │ │ ├── ScreenHome.kt
│ │ │ │ │ ├── ScreenHomeAction.kt
│ │ │ │ │ ├── ScreenHomeState.kt
│ │ │ │ │ └── ScreenHomeViewModel.kt
│ │ │ │ └── setupPermission
│ │ │ │ │ ├── SetupPermissionScreen.kt
│ │ │ │ │ ├── SetupPermissionScreenAction.kt
│ │ │ │ │ ├── SetupPermissionScreenState.kt
│ │ │ │ │ └── SetupPermissionScreenViewModel.kt
│ │ │ │ └── theme
│ │ │ │ ├── Color.kt
│ │ │ │ ├── Theme.kt
│ │ │ │ └── Type.kt
│ │ │ ├── backup
│ │ │ ├── data
│ │ │ │ ├── service
│ │ │ │ │ └── BackupService.kt
│ │ │ │ └── sources
│ │ │ │ │ ├── BackupNotificationHandlerImpl.kt
│ │ │ │ │ ├── callLog
│ │ │ │ │ ├── CallLogDBWriter.kt
│ │ │ │ │ └── CallLogSource.kt
│ │ │ │ │ ├── contacts
│ │ │ │ │ ├── ContactsDBWriter.kt
│ │ │ │ │ └── ContactsSource.kt
│ │ │ │ │ └── sms
│ │ │ │ │ ├── SmsDBWriter.kt
│ │ │ │ │ └── SmsSource.kt
│ │ │ ├── di
│ │ │ │ └── BackupDiModule.kt
│ │ │ └── ui
│ │ │ │ └── screens
│ │ │ │ ├── backupName
│ │ │ │ ├── BackupName.kt
│ │ │ │ ├── BackupNameAction.kt
│ │ │ │ ├── BackupNameState.kt
│ │ │ │ └── BackupNameViewModel.kt
│ │ │ │ ├── listScreen
│ │ │ │ ├── callLogBackupSelection
│ │ │ │ │ ├── CallLogBackupSelection.kt
│ │ │ │ │ ├── CallLogBackupSelectionAction.kt
│ │ │ │ │ ├── CallLogBackupSelectionState.kt
│ │ │ │ │ └── CallLogBackupSelectionViewModel.kt
│ │ │ │ ├── contactBackupSelection
│ │ │ │ │ ├── ContactBackupSelection.kt
│ │ │ │ │ ├── ContactBackupSelectionAction.kt
│ │ │ │ │ ├── ContactBackupSelectionState.kt
│ │ │ │ │ ├── ContactBackupSelectionViewModel.kt
│ │ │ │ │ ├── ContactComponents.kt
│ │ │ │ │ ├── LocalAndSyncedContacts.kt
│ │ │ │ │ ├── OnlyLocalContacts.kt
│ │ │ │ │ ├── OnlySyncedContacts.kt
│ │ │ │ │ └── SyncedWarningDialog.kt
│ │ │ │ └── smsBackupSelection
│ │ │ │ │ ├── SmsBackupSelection.kt
│ │ │ │ │ ├── SmsBackupSelectionAction.kt
│ │ │ │ │ ├── SmsBackupSelectionState.kt
│ │ │ │ │ └── SmsBackupSelectionViewModel.kt
│ │ │ │ └── progressScreen
│ │ │ │ ├── BackupProgressScreen.kt
│ │ │ │ ├── BackupProgressScreenAction.kt
│ │ │ │ ├── BackupProgressScreenState.kt
│ │ │ │ └── BackupProgressScreenViewModel.kt
│ │ │ ├── common
│ │ │ ├── data
│ │ │ │ ├── model
│ │ │ │ │ ├── CallLogData.kt
│ │ │ │ │ ├── ContactData.kt
│ │ │ │ │ ├── JavaFile.kt
│ │ │ │ │ ├── MediaStoreDownloadFile.kt
│ │ │ │ │ ├── NotificationInfo.kt
│ │ │ │ │ ├── SafFile.kt
│ │ │ │ │ └── SmsData.kt
│ │ │ │ ├── repository
│ │ │ │ │ └── ProgressLogRepositoryImpl.kt
│ │ │ │ └── sources
│ │ │ │ │ ├── ContextSourceImpl.kt
│ │ │ │ │ ├── PreferencesImpl.kt
│ │ │ │ │ └── fileSystem
│ │ │ │ │ ├── ExportDirectoryBrowserMediaStore.kt
│ │ │ │ │ ├── ExportDirectoryBrowserSafFile.kt
│ │ │ │ │ ├── FileSystemSourceImpl.kt
│ │ │ │ │ ├── TextWriterImpl.kt
│ │ │ │ │ ├── TransferUtils.kt
│ │ │ │ │ ├── TransferUtilsJavaFile.kt
│ │ │ │ │ ├── TransferUtilsMediaStoreDownload.kt
│ │ │ │ │ └── TransferUtilsSafFile.kt
│ │ │ ├── di
│ │ │ │ └── CommonDiModule.kt
│ │ │ ├── ui
│ │ │ │ ├── components
│ │ │ │ │ ├── CountBar.kt
│ │ │ │ │ ├── EmptyImageVector.kt
│ │ │ │ │ ├── KeepScreenOn.kt
│ │ │ │ │ ├── LoadingDialog.kt
│ │ │ │ │ ├── LoadingProgressBar.kt
│ │ │ │ │ ├── LocationSelector.kt
│ │ │ │ │ ├── NextFab.kt
│ │ │ │ │ ├── RenderCallLogItem.kt
│ │ │ │ │ ├── RenderContactItem.kt
│ │ │ │ │ └── RenderSmsItem.kt
│ │ │ │ ├── listScreen
│ │ │ │ │ ├── ListLoadingLayout.kt
│ │ │ │ │ ├── ListNoDataLayout.kt
│ │ │ │ │ ├── ListPermissionRequestLayout.kt
│ │ │ │ │ └── ListScreenShell.kt
│ │ │ │ └── progressScreen
│ │ │ │ │ ├── ErrorLayoutToggle.kt
│ │ │ │ │ ├── ProgressLogLayout.kt
│ │ │ │ │ └── ProgressScreenBottomBar.kt
│ │ │ └── utils
│ │ │ │ ├── DBUtils.kt
│ │ │ │ ├── DateUtils.kt
│ │ │ │ ├── DeepLinkUtils.kt
│ │ │ │ ├── ListItemUtils.kt
│ │ │ │ ├── NotificationUtils.kt
│ │ │ │ ├── PermissionUtils.kt
│ │ │ │ └── ServiceUtils.kt
│ │ │ └── restore
│ │ │ ├── data
│ │ │ ├── service
│ │ │ │ └── RestoreService.kt
│ │ │ └── sources
│ │ │ │ ├── RestoreNotificationHandlerImpl.kt
│ │ │ │ ├── callLog
│ │ │ │ ├── CallLogDBReader.kt
│ │ │ │ └── CallLogRestore.kt
│ │ │ │ ├── contacts
│ │ │ │ └── ContactsDBReader.kt
│ │ │ │ └── sms
│ │ │ │ ├── SmsDBReader.kt
│ │ │ │ ├── SmsRestore.kt
│ │ │ │ └── dummies
│ │ │ │ ├── DummyComposeSmsActivity.kt
│ │ │ │ ├── DummyMmsBroadcastReceiver.kt
│ │ │ │ ├── DummySmsBroadcastReceiver.kt
│ │ │ │ └── DummySmsSendService.kt
│ │ │ ├── di
│ │ │ └── RestoreDiModule.kt
│ │ │ └── ui
│ │ │ └── screens
│ │ │ ├── browseRestoreDirectory
│ │ │ ├── BrowseRestoreDirectory.kt
│ │ │ ├── BrowseRestoreDirectoryActions.kt
│ │ │ ├── BrowseRestoreDirectoryState.kt
│ │ │ └── BrowseRestoreDirectoryViewModel.kt
│ │ │ ├── listScreen
│ │ │ ├── callLogRestoreSelection
│ │ │ │ ├── CallLogRestoreSelection.kt
│ │ │ │ ├── CallLogRestoreSelectionAction.kt
│ │ │ │ ├── CallLogRestoreSelectionState.kt
│ │ │ │ └── CallLogRestoreSelectionViewModel.kt
│ │ │ ├── contactRestoreSelection
│ │ │ │ ├── ContactRestoreSelection.kt
│ │ │ │ ├── ContactRestoreSelectionAction.kt
│ │ │ │ ├── ContactRestoreSelectionState.kt
│ │ │ │ └── ContactRestoreSelectionViewModel.kt
│ │ │ └── smsRestoreSelection
│ │ │ │ ├── SmsRestoreSelection.kt
│ │ │ │ ├── SmsRestoreSelectionAction.kt
│ │ │ │ ├── SmsRestoreSelectionState.kt
│ │ │ │ └── SmsRestoreSelectionViewModel.kt
│ │ │ ├── progressScreen
│ │ │ ├── RestoreProgressScreen.kt
│ │ │ ├── RestoreProgressScreenAction.kt
│ │ │ ├── RestoreProgressScreenState.kt
│ │ │ ├── RestoreProgressScreenViewModel.kt
│ │ │ └── SmsAppChangeDialog.kt
│ │ │ └── restoreSummary
│ │ │ ├── RestoreSummary.kt
│ │ │ ├── RestoreSummaryAction.kt
│ │ │ ├── RestoreSummaryItemState.kt
│ │ │ ├── RestoreSummaryState.kt
│ │ │ ├── RestoreSummaryViewModel.kt
│ │ │ └── components
│ │ │ ├── DelegateRestore.kt
│ │ │ ├── SimpleYesNoDialog.kt
│ │ │ ├── SpecialPermissions.kt
│ │ │ ├── StandardRestore.kt
│ │ │ └── SummaryItem.kt
│ └── res
│ │ ├── drawable
│ │ ├── app_icon_round.png
│ │ ├── baseline_voicemail_24.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── ic_launcher_foreground.xml
│ │ ├── notification_icon_00.xml
│ │ ├── notification_icon_05.xml
│ │ ├── notification_icon_10.xml
│ │ ├── notification_icon_15.xml
│ │ ├── notification_icon_20.xml
│ │ ├── notification_icon_25.xml
│ │ ├── notification_icon_30.xml
│ │ ├── notification_icon_35.xml
│ │ ├── notification_icon_40.xml
│ │ ├── notification_icon_45.xml
│ │ ├── notification_icon_50.xml
│ │ ├── notification_icon_55.xml
│ │ ├── notification_icon_60.xml
│ │ ├── notification_icon_65.xml
│ │ ├── notification_icon_70.xml
│ │ ├── notification_icon_75.xml
│ │ ├── notification_icon_80.xml
│ │ ├── notification_icon_85.xml
│ │ ├── notification_icon_90.xml
│ │ ├── notification_icon_95.xml
│ │ ├── notification_rotating_icon.xml
│ │ ├── outline_close_24.xml
│ │ └── outline_warning_24.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── values-night
│ │ └── themes.xml
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ │ └── xml
│ │ ├── backup_rules.xml
│ │ ├── data_extraction_rules.xml
│ │ └── file_paths.xml
│ └── test
│ └── java
│ └── balti
│ └── migrate
│ └── ExampleUnitTest.kt
├── build.gradle.kts
├── display_assets
├── Screenshot_01.png
├── Screenshot_02.png
├── Screenshot_03.png
├── Screenshot_04.png
└── feature_graphic.png
├── domain
├── .gitignore
├── build.gradle.kts
└── src
│ └── main
│ └── java
│ └── baltiapps
│ └── migrate
│ └── domain
│ ├── Constants.kt
│ ├── backup
│ ├── model
│ │ └── BackupLocation.kt
│ ├── repository
│ │ └── BackupDataRepository.kt
│ ├── sources
│ │ └── DataSource.kt
│ └── usecase
│ │ ├── BackupCallLogUseCase.kt
│ │ ├── BackupContactsUseCase.kt
│ │ ├── BackupSmsUseCase.kt
│ │ ├── ReadCallLogForBackupUseCase.kt
│ │ ├── ReadContactsForBackupUseCase.kt
│ │ └── ReadSmsForBackupUseCase.kt
│ ├── common
│ ├── Extensions.kt
│ ├── model
│ │ ├── CallLogListItem.kt
│ │ ├── ContactListItem.kt
│ │ ├── DataItem.kt
│ │ ├── GenericFile.kt
│ │ ├── ItemCreationDate.kt
│ │ ├── ListItem.kt
│ │ ├── Progress.kt
│ │ └── SmsListItem.kt
│ ├── repository
│ │ ├── DataRepository.kt
│ │ └── ProgressLogRepository.kt
│ ├── sources
│ │ ├── ContextSource.kt
│ │ ├── NotificationHandler.kt
│ │ ├── Preferences.kt
│ │ └── fileSystem
│ │ │ ├── ExportDirectoryBrowser.kt
│ │ │ ├── FileSystemSource.kt
│ │ │ └── FileSystemUtils.kt
│ ├── usecase
│ │ ├── StageSelectedCallLogs.kt
│ │ ├── StageSelectedContacts.kt
│ │ └── StageSelectedSms.kt
│ └── utils
│ │ └── BackupFilesUtils.kt
│ ├── exceptions
│ ├── ContentReadException.kt
│ ├── PermissionException.kt
│ ├── UnknownDataTypeException.kt
│ └── UnknownFileTypeException.kt
│ └── restore
│ ├── repository
│ └── RestoreDataRepository.kt
│ ├── sources
│ └── DataRestore.kt
│ └── usecase
│ ├── ExportContactsForRestoreUseCase.kt
│ ├── ReadCallLogForRestoreUseCase.kt
│ ├── ReadContactsForRestoreUseCase.kt
│ ├── ReadFilesFromBackupUseCase.kt
│ ├── ReadSmsForRestoreUseCase.kt
│ ├── RestoreCallLogUseCase.kt
│ └── RestoreSmsUseCase.kt
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── privacy_policy.md
├── settings.gradle.kts
└── stuff_to_do.txt
/.github/workflows/release_tag.yml:
--------------------------------------------------------------------------------
1 | name: Build APK on Tag
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*'
7 |
8 | jobs:
9 | build:
10 | name: Build and release
11 | runs-on: ubuntu-latest
12 |
13 | env:
14 | KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
15 | KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
16 | KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
17 | KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
18 |
19 | steps:
20 | - name: Checkout source code
21 | uses: actions/checkout@v4
22 |
23 | - name: Set up JDK
24 | uses: actions/setup-java@v4
25 | with:
26 | distribution: 'temurin'
27 | java-version: '21'
28 |
29 | - name: Set up Gradle
30 | uses: gradle/actions/setup-gradle@v4
31 |
32 | - name: Grant execute permission to gradlew
33 | run: chmod +x ./gradlew
34 |
35 | - name: Build APK
36 | run: ./gradlew assembleRelease
37 |
38 | - name: Build AAB
39 | run: ./gradlew bundleRelease
40 |
41 | - name: Create GitHub Release
42 | uses: softprops/action-gh-release@v2
43 | with:
44 | tag_name: ${{ github.ref_name }}
45 | name: ${{ github.ref_name }}
46 | files: |
47 | app/build/outputs/apk/release/*.apk
48 | app/build/outputs/bundle/release/*.aab
49 | env:
50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
51 |
52 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # License for "Migrate - Data Backup]"
2 |
3 | Copyright © BaltiApps
4 |
5 | ## 1. Definitions
6 | - **"Software"** refers to **Migrate - Data Backup** and any associated files distributed under this license.
7 | - **"You"** (or "Your") refers to any individual or entity using, modifying, or distributing the Software.
8 |
9 | ## 2. Grant of License
10 | Subject to the Conditions set forth below, BaltiApps hereby grants You a worldwide, royalty-free, non-exclusive license to:
11 |
12 | - Use, modify, and fork the Software for **personal, non-commercial purposes**;
13 | - Contribute modifications back to the original repository;
14 | - Distribute modified or unmodified copies of the Software, provided attribution is maintained;
15 | - Integrate the Software, with or without modifications, into custom ROMs or OEM ROMs.
16 |
17 | ## 3. Conditions
18 | Your rights under this License are subject to the following conditions:
19 |
20 | 1. **Attribution:** You must maintain clear and visible attribution to "BaltiApps" in any distributed version of the Software or its modifications.
21 |
22 | 2. **Non-Commercial Use Only:**
23 | - You may not sell, license, or otherwise monetize the Software or any derivative works without **prior written permission** from BaltiApps.
24 | - Permission may be granted or denied at BaltiApps' sole discretion.
25 |
26 | 3. **Commercial ROMs and OEM Use:**
27 | - If the Software is integrated into a commercial custom ROM or OEM ROM, **prior written permission** must be obtained from BaltiApps.
28 | - Permission may be granted or denied at BaltiApps' sole discretion.
29 |
30 | ## 4. Disclaimer
31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
32 |
33 | IN NO EVENT SHALL BALTIAPPS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Migrate - Data Backup
2 |
3 | 
4 |
5 | ## Branch version
6 | Version: 6.0.1 (Artemis)
7 |
8 | ## Screenshots
9 |
10 |
11 |
12 |
13 | ## Features
14 |
15 | - Backup and restore
16 | - Call log history
17 | - SMS
18 | - Contacts
19 |
20 | ## Planned features
21 |
22 | - Backup calender entries
23 | - Possible support for password protection
24 | - Root based features - using magisk or ADB root on custom ROMs
25 | - App backup
26 | - Some system features backup
27 |
28 | ## Compilation guide
29 | 1. Clone the three repositories.
30 | ```
31 | git clone https://github.com/BaltiApps/Migrate-OSS.git
32 | ```
33 | 2. Open `Migrate-OSS` project in Android Studio. Then compile and run (`Shift+F10` in most cases).
34 |
35 | ### Links
36 |
37 | [Privacy Policy](privacy_policy.md)
38 |
39 | [Telegram group link](https://t.me/migrateApp)
40 | [XDA thread Link](https://forum.xda-developers.com/t/app-root-5-0-1st-nov-2020-migrate-custom-rom-migration-tool.3862763/)
41 | [Official Google Play Store download](https://play.google.com/store/apps/details?id=balti.migrate)
42 | [Older versions on AndroidFileHost](https://www.androidfilehost.com/?w=files&flid=285270)
43 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/androidTest/java/balti/migrate/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package balti.migrate
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("balti.migrate", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/java/balti/migrate/BaseApplication.kt:
--------------------------------------------------------------------------------
1 | package balti.migrate
2 |
3 | import android.app.Application
4 | import balti.migrate.app.di.appDiModule
5 | import balti.migrate.backup.di.backupDiModule
6 | import balti.migrate.common.di.commonDiModule
7 | import balti.migrate.restore.di.restoreDiModule
8 | import org.koin.android.ext.koin.androidContext
9 | import org.koin.core.context.startKoin
10 | import timber.log.Timber
11 |
12 | class BaseApplication: Application() {
13 |
14 | override fun onCreate() {
15 | super.onCreate()
16 | if (BuildConfig.DEBUG) {
17 | Timber.plant(Timber.DebugTree())
18 | }
19 | startKoin {
20 | androidContext(this@BaseApplication)
21 | modules(commonDiModule, backupDiModule, restoreDiModule, appDiModule)
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/java/balti/migrate/app/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package balti.migrate.app
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import androidx.activity.ComponentActivity
6 | import androidx.activity.compose.setContent
7 | import androidx.activity.enableEdgeToEdge
8 | import androidx.compose.foundation.isSystemInDarkTheme
9 | import balti.migrate.app.ui.navigation.Graph
10 | import balti.migrate.app.ui.theme.MigrateTheme
11 | import balti.migrate.backup.data.service.BackupService
12 | import balti.migrate.restore.data.service.RestoreService
13 | import baltiapps.migrate.domain.ACTION_CANCEL_BACKUP
14 | import baltiapps.migrate.domain.ACTION_CANCEL_RESTORE
15 | import baltiapps.migrate.domain.ACTION_START_BACKUP
16 | import baltiapps.migrate.domain.ACTION_START_RESTORE
17 | import baltiapps.migrate.domain.EXTRA_BACKUP_NAME
18 | import baltiapps.migrate.domain.EXTRA_BACKUP_URI_STRING
19 | import baltiapps.migrate.domain.backup.model.BackupLocation
20 | import baltiapps.migrate.domain.common.sources.Preferences.DarkMode
21 | import org.koin.androidx.viewmodel.ext.android.viewModel
22 |
23 | class MainActivity : ComponentActivity() {
24 |
25 | private val viewModel: MainActivityViewModel by viewModel()
26 |
27 | override fun onCreate(savedInstanceState: Bundle?) {
28 | super.onCreate(savedInstanceState)
29 | enableEdgeToEdge()
30 | setContent {
31 | MigrateTheme(
32 | darkTheme = when(viewModel.darkMode.value) {
33 | DarkMode.LIGHT -> false
34 | DarkMode.DARK -> true
35 | DarkMode.SYSTEM -> isSystemInDarkTheme()
36 | },
37 | dynamicColor = viewModel.shouldFollowSystemColors.value,
38 | ) {
39 | Graph(
40 | startBackupService = ::startBackupService,
41 | cancelBackup = ::cancelBackup,
42 | startRestoreService = ::startRestoreService,
43 | cancelRestore = ::cancelRestore,
44 | shouldShowPermissionScreen = viewModel::shouldShowPermissionScreen,
45 | updateUiState = viewModel::updateUiState,
46 | )
47 | }
48 | }
49 | }
50 |
51 | private fun startBackupService(backupLocation: BackupLocation) {
52 | Intent(this, BackupService::class.java).apply {
53 | action = ACTION_START_BACKUP
54 | putExtra(EXTRA_BACKUP_URI_STRING, backupLocation.backupUriString)
55 | putExtra(EXTRA_BACKUP_NAME, backupLocation.backupName)
56 | }.run {
57 | startForegroundService(this)
58 | }
59 | }
60 |
61 | private fun cancelBackup() {
62 | Intent(this, BackupService::class.java).apply {
63 | action = ACTION_CANCEL_BACKUP
64 | }.run {
65 | startService(this)
66 | }
67 | }
68 |
69 | private fun startRestoreService() {
70 | Intent(this, RestoreService::class.java).apply {
71 | action = ACTION_START_RESTORE
72 | }.run {
73 | startForegroundService(this)
74 | }
75 | }
76 |
77 | private fun cancelRestore() {
78 | Intent(this, RestoreService::class.java).apply {
79 | action = ACTION_CANCEL_RESTORE
80 | }.run {
81 | startService(this)
82 | }
83 | }
84 | }
--------------------------------------------------------------------------------
/app/src/main/java/balti/migrate/app/MainActivityViewModel.kt:
--------------------------------------------------------------------------------
1 | package balti.migrate.app
2 |
3 | import androidx.compose.runtime.mutableStateOf
4 | import androidx.lifecycle.ViewModel
5 | import baltiapps.migrate.domain.common.sources.Preferences
6 | import baltiapps.migrate.domain.common.sources.Preferences.DarkMode
7 |
8 | class MainActivityViewModel(
9 | private val preferences: Preferences,
10 | ): ViewModel() {
11 |
12 | val darkMode = mutableStateOf(preferences.getDarkMode())
13 | val shouldFollowSystemColors = mutableStateOf(preferences.shouldFollowSystemColors())
14 |
15 | fun updateUiState(darkMode: DarkMode, followSystemColors: Boolean) {
16 | this.darkMode.value = darkMode
17 | shouldFollowSystemColors.value = followSystemColors
18 | }
19 |
20 | fun shouldShowPermissionScreen(): Boolean {
21 | return preferences.shouldShowPermissionScreen()
22 | }
23 | }
--------------------------------------------------------------------------------
/app/src/main/java/balti/migrate/app/di/AppDiModule.kt:
--------------------------------------------------------------------------------
1 | package balti.migrate.app.di
2 |
3 | import balti.migrate.app.MainActivityViewModel
4 | import balti.migrate.app.ui.navigation.HomeGraphViewModel
5 | import balti.migrate.app.ui.navigation.RestoreRouteChoicesViewModel
6 | import balti.migrate.app.ui.screens.appSettings.AppSettingsViewModel
7 | import balti.migrate.app.ui.screens.home.ScreenHomeViewModel
8 | import balti.migrate.app.ui.screens.setupPermission.SetupPermissionScreenViewModel
9 | import org.koin.core.module.dsl.viewModelOf
10 | import org.koin.dsl.module
11 |
12 | val appDiModule = module {
13 |
14 | /* ViewModel */
15 |
16 | viewModelOf(::MainActivityViewModel)
17 |
18 | viewModelOf(::HomeGraphViewModel)
19 | viewModelOf(::RestoreRouteChoicesViewModel)
20 | viewModelOf(::SetupPermissionScreenViewModel)
21 | viewModelOf(::ScreenHomeViewModel)
22 | viewModelOf(::AppSettingsViewModel)
23 | }
--------------------------------------------------------------------------------
/app/src/main/java/balti/migrate/app/ui/components/MainScreenNavContainer.kt:
--------------------------------------------------------------------------------
1 | package balti.migrate.app.ui.components
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.fillMaxSize
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.material3.Icon
7 | import androidx.compose.material3.NavigationBar
8 | import androidx.compose.material3.NavigationBarItem
9 | import androidx.compose.material3.Scaffold
10 | import androidx.compose.material3.Text
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.ui.Modifier
13 | import balti.migrate.app.ui.navigation.AppNavBarItem
14 |
15 | @Composable
16 | fun MainScreenNavContainer(
17 | appNavBarItems: List,
18 | currentNavRoute: AppNavBarItem,
19 | onBottomNavItemSelected: (AppNavBarItem) -> Unit,
20 | content: @Composable () -> Unit,
21 | ) {
22 | Scaffold(
23 | modifier = Modifier.fillMaxSize(),
24 | bottomBar = {
25 | NavigationBar {
26 | appNavBarItems.forEach { item ->
27 | NavigationBarItem(
28 | selected = currentNavRoute == item,
29 | onClick = {
30 | if (item != currentNavRoute) {
31 | onBottomNavItemSelected(item)
32 | }
33 | },
34 | icon = {
35 | Icon(
36 | imageVector = if (currentNavRoute == item) {
37 | item.filledIconVector
38 | } else item.unfilledIconVector,
39 | contentDescription = item.label,
40 | )
41 | },
42 | label = {
43 | Text(text = item.label)
44 | }
45 | )
46 | }
47 | }
48 | }
49 | ) { padding ->
50 | Box(
51 | modifier = Modifier.fillMaxSize().padding(padding)
52 | ) {
53 | content()
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/app/src/main/java/balti/migrate/app/ui/navigation/AppNavBarItem.kt:
--------------------------------------------------------------------------------
1 | package balti.migrate.app.ui.navigation
2 |
3 | import androidx.compose.ui.graphics.vector.ImageVector
4 |
5 | data class AppNavBarItem(
6 | val route: Any,
7 | val label: String,
8 | val filledIconVector: ImageVector,
9 | val unfilledIconVector: ImageVector,
10 | )
--------------------------------------------------------------------------------
/app/src/main/java/balti/migrate/app/ui/navigation/HomeGraphViewModel.kt:
--------------------------------------------------------------------------------
1 | package balti.migrate.app.ui.navigation
2 |
3 | import androidx.lifecycle.ViewModel
4 | import baltiapps.migrate.domain.backup.repository.BackupDataRepository
5 |
6 | class HomeGraphViewModel(
7 | private val backupRepository: BackupDataRepository,
8 | ) : ViewModel() {
9 | fun resetBackupRepository() {
10 | backupRepository.resetRepository()
11 | }
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/balti/migrate/app/ui/navigation/RestoreRouteChoicesViewModel.kt:
--------------------------------------------------------------------------------
1 | package balti.migrate.app.ui.navigation
2 |
3 | import androidx.lifecycle.ViewModel
4 | import baltiapps.migrate.domain.restore.repository.RestoreDataRepository
5 |
6 | class RestoreRouteChoicesViewModel(
7 | private val dataRepository: RestoreDataRepository
8 | ) : ViewModel() {
9 |
10 | private val routeMap = mapOf(
11 | RouteCallLogRestoreSelection to { dataRepository.getCallLogBackupFile() != null },
12 | RouteSmsRestoreSelection to { dataRepository.getSmsBackupFile() != null },
13 | RouteContactRestoreSelection to { dataRepository.getContactBackupFile() != null },
14 | RouteRestoreSummary to { true },
15 | )
16 |
17 | private var pointerIndex = -1
18 |
19 | fun findNextRoute(): Any {
20 | while (pointerIndex < routeMap.size) {
21 | pointerIndex++
22 | val route = routeMap.keys.elementAtOrNull(pointerIndex)
23 | if (route != null && routeMap[route]?.invoke() == true) {
24 | return route
25 | }
26 | }
27 | return routeMap.keys.last()
28 | }
29 |
30 | fun onBackFromRoute() {
31 | pointerIndex--
32 | }
33 |
34 | fun resetRestorePointer() {
35 | pointerIndex = -1
36 | }
37 | }
--------------------------------------------------------------------------------
/app/src/main/java/balti/migrate/app/ui/screens/appSettings/AppSettingsAction.kt:
--------------------------------------------------------------------------------
1 | package balti.migrate.app.ui.screens.appSettings
2 |
3 | import baltiapps.migrate.domain.common.sources.Preferences
4 |
5 | sealed class AppSettingsAction {
6 | data class ChangeDarkMode(
7 | val darkMode: Preferences.DarkMode,
8 | val updateUiState: (darkMode: Preferences.DarkMode, followSystemColors: Boolean) -> Unit,
9 | ) : AppSettingsAction()
10 | data class ChangeShouldFollowSystemColors(
11 | val shouldFollow: Boolean,
12 | val updateUiState: (darkMode: Preferences.DarkMode, followSystemColors: Boolean) -> Unit,
13 | ) : AppSettingsAction()
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/balti/migrate/app/ui/screens/appSettings/AppSettingsState.kt:
--------------------------------------------------------------------------------
1 | package balti.migrate.app.ui.screens.appSettings
2 |
3 | import baltiapps.migrate.domain.common.sources.Preferences
4 |
5 | data class AppSettingsState(
6 | val darkMode: Preferences.DarkMode,
7 | val shouldFollowSystemColors: Boolean,
8 | )
9 |
--------------------------------------------------------------------------------
/app/src/main/java/balti/migrate/app/ui/screens/appSettings/AppSettingsViewModel.kt:
--------------------------------------------------------------------------------
1 | package balti.migrate.app.ui.screens.appSettings
2 |
3 | import androidx.lifecycle.ViewModel
4 | import baltiapps.migrate.domain.common.sources.Preferences
5 | import kotlinx.coroutines.flow.MutableStateFlow
6 | import kotlinx.coroutines.flow.asStateFlow
7 | import kotlinx.coroutines.flow.update
8 |
9 | class AppSettingsViewModel(
10 | private val preferences: Preferences,
11 | ): ViewModel() {
12 |
13 | private val _state = MutableStateFlow(
14 | AppSettingsState(
15 | darkMode = preferences.getDarkMode(),
16 | shouldFollowSystemColors = preferences.shouldFollowSystemColors(),
17 | )
18 | )
19 | val state = _state.asStateFlow()
20 |
21 | fun onAction(action: AppSettingsAction) {
22 | when (action) {
23 | is AppSettingsAction.ChangeDarkMode -> {
24 | preferences.setDarkMode(action.darkMode)
25 | action.updateUiState(
26 | action.darkMode,
27 | _state.value.shouldFollowSystemColors,
28 | )
29 | _state.update {
30 | it.copy(darkMode = action.darkMode)
31 | }
32 | }
33 | is AppSettingsAction.ChangeShouldFollowSystemColors -> {
34 | preferences.setFollowSystemColors(action.shouldFollow)
35 | action.updateUiState(
36 | _state.value.darkMode,
37 | action.shouldFollow,
38 | )
39 | _state.update {
40 | it.copy(shouldFollowSystemColors = action.shouldFollow)
41 | }
42 | }
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/app/src/main/java/balti/migrate/app/ui/screens/home/ScreenHomeAction.kt:
--------------------------------------------------------------------------------
1 | package balti.migrate.app.ui.screens.home
2 |
3 | sealed class ScreenHomeAction {
4 | data object OnAboutButtonClicked: ScreenHomeAction()
5 | data object OnAboutDialogDismissed: ScreenHomeAction()
6 | data object OnAppBackupUnavailableDialogDismissed: ScreenHomeAction()
7 | }
--------------------------------------------------------------------------------
/app/src/main/java/balti/migrate/app/ui/screens/home/ScreenHomeState.kt:
--------------------------------------------------------------------------------
1 | package balti.migrate.app.ui.screens.home
2 |
3 | data class ScreenHomeState(
4 | val shouldShowAboutDialog: Boolean = false,
5 | val shouldShowAppBackupUnavailableDialog: Boolean = false,
6 | )
--------------------------------------------------------------------------------
/app/src/main/java/balti/migrate/app/ui/screens/home/ScreenHomeViewModel.kt:
--------------------------------------------------------------------------------
1 | package balti.migrate.app.ui.screens.home
2 |
3 | import androidx.lifecycle.ViewModel
4 | import baltiapps.migrate.domain.common.sources.Preferences
5 | import kotlinx.coroutines.flow.MutableStateFlow
6 | import kotlinx.coroutines.flow.asStateFlow
7 | import kotlinx.coroutines.flow.update
8 |
9 | class ScreenHomeViewModel(
10 | private val preferences: Preferences,
11 | ): ViewModel() {
12 |
13 | private val _state = MutableStateFlow(
14 | ScreenHomeState(
15 | shouldShowAppBackupUnavailableDialog = preferences.shouldShowAppBackupUnavailable(),
16 | )
17 | )
18 | val state = _state.asStateFlow()
19 |
20 | fun performAction(action: ScreenHomeAction) {
21 | when (action) {
22 | is ScreenHomeAction.OnAboutButtonClicked -> {
23 | _state.update { it.copy(shouldShowAboutDialog = true) }
24 | }
25 | is ScreenHomeAction.OnAboutDialogDismissed -> {
26 | _state.update { it.copy(shouldShowAboutDialog = false) }
27 | }
28 | is ScreenHomeAction.OnAppBackupUnavailableDialogDismissed -> {
29 | preferences.setShouldShowAppBackupUnavailable(false)
30 | _state.update { it.copy(shouldShowAppBackupUnavailableDialog = false) }
31 | }
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/app/src/main/java/balti/migrate/app/ui/screens/setupPermission/SetupPermissionScreenAction.kt:
--------------------------------------------------------------------------------
1 | package balti.migrate.app.ui.screens.setupPermission
2 |
3 | sealed class SetupPermissionScreenAction {
4 | data class OnCallLogPermissionsResult(val isGranted: Boolean): SetupPermissionScreenAction()
5 | data class OnSmsPermissionResult(val isGranted: Boolean): SetupPermissionScreenAction()
6 | data class OnContactsPermissionResult(val isGranted: Boolean): SetupPermissionScreenAction()
7 | data class OnNotificationPermissionResult(val isGranted: Boolean): SetupPermissionScreenAction()
8 | data object OnAllPermissionsGranted: SetupPermissionScreenAction()
9 | data class OnAllPermissionsResult(val isGranted: Boolean): SetupPermissionScreenAction()
10 | data class OnSkipClicked(val dontShowAgain: Boolean): SetupPermissionScreenAction()
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/balti/migrate/app/ui/screens/setupPermission/SetupPermissionScreenState.kt:
--------------------------------------------------------------------------------
1 | package balti.migrate.app.ui.screens.setupPermission
2 |
3 | data class SetupPermissionScreenState(
4 | val isCallLogPermissionsGranted: Boolean = false,
5 | val isSmsReadPermissionGranted: Boolean = false,
6 | val isContactsReadPermissionGranted: Boolean = false,
7 | val isNotificationPermissionGranted: Boolean = false,
8 | ) {
9 | val isAllPermissionsGranted: Boolean = isCallLogPermissionsGranted &&
10 | isSmsReadPermissionGranted &&
11 | isContactsReadPermissionGranted &&
12 | isNotificationPermissionGranted
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/balti/migrate/app/ui/screens/setupPermission/SetupPermissionScreenViewModel.kt:
--------------------------------------------------------------------------------
1 | package balti.migrate.app.ui.screens.setupPermission
2 |
3 | import androidx.lifecycle.ViewModel
4 | import balti.migrate.common.utils.PermissionUtils
5 | import baltiapps.migrate.domain.common.sources.ContextSource
6 | import baltiapps.migrate.domain.common.sources.Preferences
7 | import kotlinx.coroutines.flow.MutableStateFlow
8 | import kotlinx.coroutines.flow.asStateFlow
9 | import kotlinx.coroutines.flow.update
10 |
11 | class SetupPermissionScreenViewModel(
12 | private val contextSource: ContextSource,
13 | private val preferences: Preferences,
14 | ) : ViewModel() {
15 |
16 | private val _state = MutableStateFlow(SetupPermissionScreenState())
17 | val state = _state.asStateFlow()
18 |
19 | private fun updateState(isGranted: Boolean? = null) {
20 | _state.update {
21 | it.copy(
22 | isCallLogPermissionsGranted = isGranted ?: contextSource.checkPermissions(
23 | PermissionUtils.callLogPermissions
24 | ),
25 | isSmsReadPermissionGranted = isGranted ?: contextSource.checkPermission(
26 | PermissionUtils.smsReadPermission
27 | ),
28 | isContactsReadPermissionGranted = isGranted ?: contextSource.checkPermission(
29 | PermissionUtils.contactsReadPermission
30 | ),
31 | isNotificationPermissionGranted = isGranted
32 | ?: PermissionUtils.notificationsPermission?.run {
33 | contextSource.checkPermission(this)
34 | } ?: true,
35 | )
36 | }
37 | }
38 |
39 | init {
40 | updateState()
41 | }
42 |
43 | fun performAction(action: SetupPermissionScreenAction) {
44 | when (action) {
45 | is SetupPermissionScreenAction.OnCallLogPermissionsResult -> {
46 | _state.update { it.copy(isCallLogPermissionsGranted = action.isGranted) }
47 | }
48 | is SetupPermissionScreenAction.OnSmsPermissionResult -> {
49 | _state.update { it.copy(isSmsReadPermissionGranted = action.isGranted) }
50 | }
51 | is SetupPermissionScreenAction.OnContactsPermissionResult -> {
52 | _state.update { it.copy(isContactsReadPermissionGranted = action.isGranted) }
53 | }
54 | is SetupPermissionScreenAction.OnNotificationPermissionResult -> {
55 | _state.update { it.copy(isNotificationPermissionGranted = action.isGranted) }
56 | }
57 | is SetupPermissionScreenAction.OnAllPermissionsResult -> {
58 | if (action.isGranted) {
59 | updateState(isGranted = true)
60 | } else updateState()
61 | }
62 | is SetupPermissionScreenAction.OnAllPermissionsGranted -> {
63 | preferences.setShouldShowPermissionScreen(false)
64 | }
65 | is SetupPermissionScreenAction.OnSkipClicked -> {
66 | if (action.dontShowAgain) {
67 | preferences.setShouldShowPermissionScreen(false)
68 | }
69 | }
70 | }
71 | }
72 |
73 | }
--------------------------------------------------------------------------------
/app/src/main/java/balti/migrate/app/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package balti.migrate.app.ui.theme
2 |
3 | import androidx.compose.material3.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | val AppTypography = Typography()
10 |
--------------------------------------------------------------------------------
/app/src/main/java/balti/migrate/backup/data/sources/contacts/ContactsDBWriter.kt:
--------------------------------------------------------------------------------
1 | package balti.migrate.backup.data.sources.contacts
2 |
3 | import android.content.ContentValues
4 | import android.database.sqlite.SQLiteDatabase
5 | import balti.migrate.common.data.model.ContactData
6 | import balti.migrate.common.utils.DBUtils
7 | import baltiapps.migrate.domain.ContactsDBConstants.Companion.CONTACTS_TABLE_NAME
8 | import baltiapps.migrate.domain.ContactsDBConstants.Companion.DISPLAY_NAME
9 | import baltiapps.migrate.domain.ContactsDBConstants.Companion.VCF_CONTENT
10 | import baltiapps.migrate.domain.REDACTED
11 | import baltiapps.migrate.domain.common.getPercentage
12 | import baltiapps.migrate.domain.common.model.GenericFile
13 | import baltiapps.migrate.domain.common.model.Progress
14 | import baltiapps.migrate.domain.common.runCatchingWithProgress
15 | import baltiapps.migrate.domain.common.sources.fileSystem.DBWriter
16 | import kotlinx.coroutines.Dispatchers
17 | import kotlinx.coroutines.flow.Flow
18 | import kotlinx.coroutines.flow.flow
19 | import kotlinx.coroutines.flow.flowOn
20 | import java.io.File
21 |
22 | class ContactsDBWriter(
23 | private val dbUtils: DBUtils,
24 | ): DBWriter {
25 |
26 | private lateinit var sqLiteDatabase: SQLiteDatabase
27 |
28 | override fun setup(file: GenericFile) {
29 | val dbFile = File(file.path).apply {
30 | if (exists()) delete()
31 | }
32 |
33 | dbUtils.getDataBase(dbFile).apply {
34 | val sqlDropTable = "DROP TABLE IF EXISTS $CONTACTS_TABLE_NAME"
35 | val sqlCreateTable = "CREATE TABLE $CONTACTS_TABLE_NAME ( " +
36 | "id INTEGER PRIMARY KEY" +
37 | ", $DISPLAY_NAME TEXT" +
38 | ", $VCF_CONTENT TEXT" +
39 | ")"
40 | sqLiteDatabase = this
41 | execSQL(sqlDropTable)
42 | execSQL(sqlCreateTable)
43 | }
44 | }
45 |
46 | override fun writeRows(dataItems: List): Flow