session-android/app/src/main/java/org/thoughtcrime/securesms/backup/BackupRestoreActivity.kt

206 lines
8.7 KiB
Kotlin
Raw Normal View History

package org.thoughtcrime.securesms.backup
2020-10-20 06:39:01 +02:00
import android.app.Activity
import android.app.Application
import android.content.Intent
import android.graphics.Typeface
import android.net.Uri
import android.os.Bundle
import android.provider.OpenableColumns
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.style.ClickableSpan
import android.text.style.StyleSpan
import android.view.View
import android.widget.Toast
2020-11-12 03:02:38 +01:00
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
2020-10-20 06:39:01 +02:00
import androidx.activity.viewModels
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
2020-11-12 03:02:38 +01:00
import androidx.lifecycle.viewModelScope
2020-10-20 06:39:01 +02:00
import com.google.android.gms.common.util.Strings
2020-11-12 03:02:38 +01:00
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
2020-10-20 06:39:01 +02:00
import network.loki.messenger.R
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.utilities.Log
2020-10-20 06:39:01 +02:00
import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.backup.FullBackupImporter.DatabaseDowngradeException
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.home.HomeActivity
2020-10-20 06:39:01 +02:00
import org.thoughtcrime.securesms.notifications.NotificationChannels
2020-11-12 03:02:38 +01:00
import org.thoughtcrime.securesms.util.BackupUtil
import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo
import org.thoughtcrime.securesms.util.show
2020-10-20 06:39:01 +02:00
class BackupRestoreActivity : BaseActionBarActivity() {
companion object {
private const val TAG = "BackupRestoreActivity"
}
2020-11-10 07:25:26 +01:00
private val viewModel by viewModels<BackupRestoreViewModel>()
2020-10-20 06:39:01 +02:00
2020-11-12 03:02:38 +01:00
private val fileSelectionResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()
) { result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK && result.data != null && result.data!!.data != null) {
viewModel.backupFile.value = result.data!!.data!!
}
}
2020-10-20 06:39:01 +02:00
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setUpActionBarSessionLogo()
2021-05-21 07:02:34 +02:00
// val viewBinding = DataBindingUtil.setContentView<ActivityBackupRestoreBinding>(this, R.layout.activity_backup_restore)
// viewBinding.lifecycleOwner = this
// viewBinding.viewModel = viewModel
2020-10-20 06:39:01 +02:00
2021-05-21 07:02:34 +02:00
// viewBinding.restoreButton.setOnClickListener { viewModel.tryRestoreBackup() }
2020-11-12 03:02:38 +01:00
2021-05-21 07:02:34 +02:00
// viewBinding.buttonSelectFile.setOnClickListener {
// fileSelectionResultLauncher.launch(Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
// //FIXME On some old APIs (tested on 21 & 23) the mime type doesn't filter properly
// // and the backup files are unavailable for selection.
//// type = BackupUtil.BACKUP_FILE_MIME_TYPE
// type = "*/*"
// })
// }
2020-10-20 06:39:01 +02:00
2021-05-21 07:02:34 +02:00
// viewBinding.backupCode.addTextChangedListener { text -> viewModel.backupPassphrase.value = text.toString() }
2020-11-12 03:02:38 +01:00
// Focus passphrase text edit when backup file is selected.
2021-05-21 07:02:34 +02:00
// viewModel.backupFile.observe(this, { backupFile ->
// if (backupFile != null) viewBinding.backupCode.post {
// viewBinding.backupCode.requestFocus()
// (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager)
// .showSoftInput(viewBinding.backupCode, InputMethodManager.SHOW_IMPLICIT)
// }
// })
2020-11-12 03:02:38 +01:00
// React to backup import result.
viewModel.backupImportResult.observe(this) { result ->
if (result != null) when (result) {
BackupRestoreViewModel.BackupRestoreResult.SUCCESS -> {
2020-11-12 03:02:38 +01:00
val intent = Intent(this, HomeActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
this.show(intent)
}
BackupRestoreViewModel.BackupRestoreResult.FAILURE_VERSION_DOWNGRADE ->
2020-11-12 03:02:38 +01:00
Toast.makeText(this, R.string.RegistrationActivity_backup_failure_downgrade, Toast.LENGTH_LONG).show()
BackupRestoreViewModel.BackupRestoreResult.FAILURE_UNKNOWN ->
2020-11-12 03:02:38 +01:00
Toast.makeText(this, R.string.RegistrationActivity_incorrect_backup_passphrase, Toast.LENGTH_LONG).show()
}
}
2020-10-20 06:39:01 +02:00
//region Legal info views
val termsExplanation = SpannableStringBuilder("By using this service, you agree to our Terms of Service and Privacy Policy")
termsExplanation.setSpan(StyleSpan(Typeface.BOLD), 40, 56, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
termsExplanation.setSpan(object : ClickableSpan() {
override fun onClick(widget: View) {
openURL("https://getsession.org/terms-of-service/")
}
}, 40, 56, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
termsExplanation.setSpan(StyleSpan(Typeface.BOLD), 61, 75, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
termsExplanation.setSpan(object : ClickableSpan() {
override fun onClick(widget: View) {
openURL("https://getsession.org/privacy-policy/")
}
}, 61, 75, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
2021-05-21 07:02:34 +02:00
// viewBinding.termsTextView.movementMethod = LinkMovementMethod.getInstance()
// viewBinding.termsTextView.text = termsExplanation
2020-10-20 06:39:01 +02:00
//endregion
}
private fun openURL(url: String) {
try {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
startActivity(intent)
} catch (e: Exception) {
Toast.makeText(this, R.string.invalid_url, Toast.LENGTH_SHORT).show()
}
}
}
2020-11-10 07:25:26 +01:00
class BackupRestoreViewModel(application: Application): AndroidViewModel(application) {
2020-10-20 06:39:01 +02:00
companion object {
2020-11-12 03:02:38 +01:00
private const val TAG = "BackupRestoreViewModel"
2020-10-20 06:39:01 +02:00
@JvmStatic
fun uriToFileName(view: View, fileUri: Uri?): String? {
fileUri ?: return null
view.context.contentResolver.query(fileUri, null, null, null, null).use {
val nameIndex = it!!.getColumnIndex(OpenableColumns.DISPLAY_NAME)
it.moveToFirst()
return it.getString(nameIndex)
}
}
@JvmStatic
fun validateData(fileUri: Uri?, passphrase: String?): Boolean {
2020-11-12 03:02:38 +01:00
return fileUri != null &&
!Strings.isEmptyOrWhitespace(passphrase) &&
passphrase!!.length == BackupUtil.BACKUP_PASSPHRASE_LENGTH
2020-10-20 06:39:01 +02:00
}
}
2020-11-12 03:02:38 +01:00
val backupFile = MutableLiveData<Uri>(null)
val backupPassphrase = MutableLiveData<String>(null)
val processingBackupFile = MutableLiveData<Boolean>(false)
val backupImportResult = MutableLiveData<BackupRestoreResult>(null)
2020-11-12 03:02:38 +01:00
fun tryRestoreBackup() = viewModelScope.launch {
if (processingBackupFile.value == true) return@launch
if (backupImportResult.value == BackupRestoreResult.SUCCESS) return@launch
2020-11-12 03:02:38 +01:00
if (!validateData(backupFile.value, backupPassphrase.value)) return@launch
val context = getApplication<Application>()
val backupFile = backupFile.value!!
val passphrase = backupPassphrase.value!!
val result: BackupRestoreResult
2020-11-12 03:02:38 +01:00
processingBackupFile.value = true
withContext(Dispatchers.IO) {
result = try {
val database = DatabaseComponent.get(context).openHelper().readableDatabase
2020-11-12 03:02:38 +01:00
FullBackupImporter.importFromUri(
context,
AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(),
database,
2020-11-12 03:02:38 +01:00
backupFile,
passphrase
)
DatabaseFactory.upgradeRestored(context, database)
NotificationChannels.restoreContactNotificationChannels(context)
TextSecurePreferences.setRestorationTime(context, System.currentTimeMillis())
TextSecurePreferences.setHasViewedSeed(context, true)
TextSecurePreferences.setHasSeenWelcomeScreen(context, true)
BackupRestoreResult.SUCCESS
2020-11-12 03:02:38 +01:00
} catch (e: DatabaseDowngradeException) {
Log.w(TAG, "Failed due to the backup being from a newer version of Signal.", e)
BackupRestoreResult.FAILURE_VERSION_DOWNGRADE
2020-11-12 03:02:38 +01:00
} catch (e: Exception) {
Log.w(TAG, e)
BackupRestoreResult.FAILURE_UNKNOWN
2020-11-12 03:02:38 +01:00
}
}
processingBackupFile.value = false
2020-10-20 06:39:01 +02:00
2020-11-12 03:02:38 +01:00
backupImportResult.value = result
}
enum class BackupRestoreResult {
2020-11-12 03:02:38 +01:00
SUCCESS, FAILURE_VERSION_DOWNGRADE, FAILURE_UNKNOWN
2020-10-20 06:39:01 +02:00
}
}