package com.stevesoltys.backup.ui import android.app.Application import android.app.backup.BackupProgress import android.app.backup.IBackupObserver import android.content.Intent import android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION import android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION import android.net.Uri import android.util.Log import androidx.documentfile.provider.DocumentFile import androidx.lifecycle.AndroidViewModel import com.stevesoltys.backup.Backup import com.stevesoltys.backup.isOnExternalStorage import com.stevesoltys.backup.settings.getBackupFolderUri import com.stevesoltys.backup.settings.setBackupFolderUri import com.stevesoltys.backup.transport.ConfigurableBackupTransportService import com.stevesoltys.backup.transport.TRANSPORT_ID private val TAG = BackupViewModel::class.java.simpleName abstract class BackupViewModel(protected val app: Application) : AndroidViewModel(app) { private val locationWasSet = MutableLiveEvent<LocationResult>() /** * Will be set to true if this is the initial location. * It will be false if an existing location was changed. */ internal val onLocationSet: LiveEvent<LocationResult> get() = locationWasSet private val mChooseBackupLocation = MutableLiveEvent<Boolean>() internal val chooseBackupLocation: LiveEvent<Boolean> get() = mChooseBackupLocation internal fun chooseBackupLocation() = mChooseBackupLocation.setEvent(true) internal fun recoveryCodeIsSet() = Backup.keyManager.hasBackupKey() internal fun validLocationIsSet(): Boolean { val uri = getBackupFolderUri(app) ?: return false if (uri.isOnExternalStorage()) return true // might be a temporary failure val file = DocumentFile.fromTreeUri(app, uri) ?: return false return file.isDirectory } internal fun handleChooseFolderResult(result: Intent?) { val folderUri = result?.data ?: return // persist permission to access backup folder across reboots val takeFlags = result.flags and (FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION) app.contentResolver.takePersistableUriPermission(folderUri, takeFlags) // check if this is initial set-up or a later change val initialSetUp = !validLocationIsSet() if (acceptBackupLocation(folderUri)) { // store backup folder location in settings setBackupFolderUri(app, folderUri) // stop backup service to be sure the old location will get updated app.stopService(Intent(app, ConfigurableBackupTransportService::class.java)) Log.d(TAG, "New storage location chosen: $folderUri") // initialize the new location // TODO don't do this when restoring Backup.backupManager.initializeTransports(arrayOf(TRANSPORT_ID), InitializationObserver(initialSetUp)) } else { Log.w(TAG, "Location was rejected: $folderUri") // notify the UI that the location was invalid locationWasSet.setEvent(LocationResult(false, initialSetUp)) } } protected open fun acceptBackupLocation(folderUri: Uri): Boolean { return true } private inner class InitializationObserver(private val initialSetUp: Boolean) : IBackupObserver.Stub() { override fun onUpdate(currentBackupPackage: String, backupProgress: BackupProgress) { // noop } override fun onResult(target: String, status: Int) { // noop } override fun backupFinished(status: Int) { if (Log.isLoggable(TAG, Log.INFO)) { Log.i(TAG, "Initialization finished. Status: $status") } if (status == 0) { // notify the UI that the location has been set locationWasSet.postEvent(LocationResult(true, initialSetUp)) } else { // notify the UI that the location was invalid locationWasSet.postEvent(LocationResult(false, initialSetUp)) } } } } class LocationResult(val validLocation: Boolean, val initialSetup: Boolean)