Skip to content

Commit

Permalink
feat: add backup/restore feature with auto backup
Browse files Browse the repository at this point in the history
  • Loading branch information
timschneeb committed Mar 10, 2023
1 parent c256340 commit 7babddf
Show file tree
Hide file tree
Showing 22 changed files with 896 additions and 26 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<a href="https://github.com/ThePBone/RootlessJamesDSP/actions/workflows/build.yml">
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/thepbone/rootlessjamesdsp/build.yml">
</a>

</p>
<p align="center">
<a href="#limitations">Limitations</a> •
Expand Down Expand Up @@ -117,7 +117,7 @@ All the limitations mentioned above are **not relevant** for the magisk/root ver
## Credits

* JamesDSP - [James Fung (@james34602)](https://github.com/james34602)
* Theming system based on Tachiyomi
* Theming system & backup system based on Tachiyomi

### Translators

Expand Down
2 changes: 1 addition & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ dependencies {
implementation("dev.rikka.shizuku:api:${AndroidConfig.shizukuVersion}")
implementation("dev.rikka.shizuku:provider:${AndroidConfig.shizukuVersion}")

// Used for backup feature; check if this can be removed later
// Used for backup file access
implementation("com.github.tachiyomiorg:unifile:17bec43")

// Root APIs
Expand Down
10 changes: 9 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

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

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

<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.DUMP" tools:ignore="ProtectedPermissions" />

Expand Down Expand Up @@ -236,6 +240,10 @@
android:value="true" />
</service>

<service
android:name=".backup.BackupRestoreService"
android:exported="false" />

<!-- Receivers -->
<receiver
android:name=".receiver.SessionReceiver"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import android.content.*
import android.media.projection.MediaProjectionManager
import android.net.Uri
import android.os.*
import android.provider.DocumentsContract
import android.view.HapticFeedbackConstants
import android.view.Menu
import androidx.activity.result.ActivityResultLauncher
Expand Down Expand Up @@ -215,6 +216,7 @@ class MainActivity : BaseActivity() {
.add(R.id.dsp_fragment_container, presetDialogHost!!)
.commitNow()
}
presetDialogHost?.pref?.refresh()

val dialogFragment = FileLibraryDialogFragment.newInstance("presets")
dialogFragment.setTargetFragment(presetDialogHost, 0)
Expand Down Expand Up @@ -323,6 +325,8 @@ class MainActivity : BaseActivity() {
runtimePermissionLauncher.launch(arrayOf(Manifest.permission.POST_NOTIFICATIONS))
}



// Load initial preference states
val initialPrefList = arrayOf(R.string.key_appearance_nav_hide, R.string.key_powered_on)
for (pref in initialPrefList)
Expand Down Expand Up @@ -692,7 +696,7 @@ class MainActivity : BaseActivity() {
}

class FakePresetFragment : Fragment(), TargetFragment {
private val pref by lazy {
val pref by lazy {
FileLibraryPreference(requireContext(), null).apply {
this.type = "Presets"
this.key = "presets"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
package me.timschneeberger.rootlessjamesdsp.activity

import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import me.timschneeberger.rootlessjamesdsp.R
import me.timschneeberger.rootlessjamesdsp.databinding.ActivitySettingsBinding
import me.timschneeberger.rootlessjamesdsp.fragment.SettingsAboutFragment
import me.timschneeberger.rootlessjamesdsp.fragment.SettingsBackupFragment
import me.timschneeberger.rootlessjamesdsp.fragment.SettingsFragment

class SettingsActivity : BaseActivity(),
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {

lateinit var backupLocationSelectLauncher: ActivityResultLauncher<Uri?>
lateinit var backupSaveFileSelectLauncher: ActivityResultLauncher<String>
lateinit var backupLoadFileSelectLauncher: ActivityResultLauncher<Array<String>>

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivitySettingsBinding.inflate(layoutInflater)
Expand All @@ -37,10 +46,48 @@ class SettingsActivity : BaseActivity(),
}
}

backupLocationSelectLauncher = registerForActivityResult(
ActivityResultContracts.OpenDocumentTree(),
::onBackupLocationSet
)

backupSaveFileSelectLauncher = registerForActivityResult(
ActivityResultContracts.CreateDocument("application/*")
) {
it ?: return@registerForActivityResult
val fragment = supportFragmentManager.findFragmentById(R.id.settings)
if(fragment is SettingsBackupFragment)
fragment.startManualBackup(it)
}

backupLoadFileSelectLauncher = registerForActivityResult(
ActivityResultContracts.OpenDocument()
) {
it ?: return@registerForActivityResult
val fragment = supportFragmentManager.findFragmentById(R.id.settings)
if(fragment is SettingsBackupFragment)
fragment.startManualRestore(it)
}

supportActionBar?.setDisplayHomeAsUpEnabled(true)
binding.settingsToolbar.setNavigationOnClickListener { onBackPressedDispatcher.onBackPressed() }
}

private fun onBackupLocationSet(uri: Uri?) {
val fragment = supportFragmentManager.findFragmentById(R.id.settings) as? SettingsBackupFragment

if (uri != null) {
contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
prefsApp.set(R.string.key_backup_location, uri.toString())
fragment?.updateSummaries()
} else {
fragment?.resetFrequencyIfLocationUnset()
}
}

override fun onSaveInstanceState(outState: Bundle) {
outState.putString(PERSIST_TITLE, supportActionBar?.title.toString())
super.onSaveInstanceState(outState)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package me.timschneeberger.rootlessjamesdsp.backup

import android.app.NotificationManager
import android.content.Context
import android.net.Uri
import androidx.core.net.toUri
import androidx.work.CoroutineWorker
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.hippo.unifile.UniFile
import me.timschneeberger.rootlessjamesdsp.Notifications
import me.timschneeberger.rootlessjamesdsp.R
import me.timschneeberger.rootlessjamesdsp.utils.Preferences
import me.timschneeberger.rootlessjamesdsp.utils.SystemServices
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import timber.log.Timber
import java.util.concurrent.TimeUnit

class BackupCreatorJob(private val context: Context, workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams) {

private val notificationManager = SystemServices.get<NotificationManager>(context)

override suspend fun doWork(): Result {
val notifier = BackupNotifier(context)
val uri = inputData.getString(LOCATION_URI_KEY)?.toUri()
?: preferences.get<String>(R.string.key_backup_location).toUri()
val isAutoBackup = inputData.getBoolean(IS_AUTO_BACKUP_KEY, true)

notificationManager.notify(Notifications.ID_BACKUP_PROGRESS, notifier.showBackupProgress().build())
return try {
val location = BackupManager(context).createBackup(uri, isAutoBackup)
if (!isAutoBackup) notifier.showBackupComplete(UniFile.fromUri(context, location.toUri()))
Result.success()
} catch (e: Exception) {
Timber.e(e)
if (!isAutoBackup) notifier.showBackupError(e.message)
Result.failure()
} finally {
notificationManager.cancel(Notifications.ID_BACKUP_PROGRESS)
}
}

companion object : KoinComponent {
private val preferences: Preferences.App by inject()

fun isManualJobRunning(context: Context): Boolean {
val list = WorkManager.getInstance(context).getWorkInfosByTag(TAG_MANUAL).get()
return list.find { it.state == WorkInfo.State.RUNNING } != null
}

fun setupTask(context: Context, prefInterval: Int? = null) {
val interval = prefInterval ?: preferences.get<String>(R.string.key_backup_frequency).toInt()
val workManager = WorkManager.getInstance(context)
if (interval > 0) {

val request = PeriodicWorkRequestBuilder<BackupCreatorJob>(
interval.toLong(),
TimeUnit.HOURS,
10,
TimeUnit.MINUTES,
)
.addTag(TAG_AUTO)
.setInputData(workDataOf(IS_AUTO_BACKUP_KEY to true))
.build()

workManager.enqueueUniquePeriodicWork(TAG_AUTO, ExistingPeriodicWorkPolicy.REPLACE, request)
} else {
workManager.cancelUniqueWork(TAG_AUTO)
}
}

fun startNow(context: Context, uri: Uri) {
val inputData = workDataOf(
IS_AUTO_BACKUP_KEY to false,
LOCATION_URI_KEY to uri.toString(),
)
val request = OneTimeWorkRequestBuilder<BackupCreatorJob>()
.addTag(TAG_MANUAL)
.setInputData(inputData)
.build()
WorkManager.getInstance(context).enqueueUniqueWork(TAG_MANUAL, ExistingWorkPolicy.KEEP, request)
}
}
}

private const val TAG_AUTO = "BackupCreator"
private const val TAG_MANUAL = "$TAG_AUTO:manual"

private const val IS_AUTO_BACKUP_KEY = "is_auto_backup" // Boolean
private const val LOCATION_URI_KEY = "location_uri" // String
Loading

0 comments on commit 7babddf

Please sign in to comment.