Backup restore landing screen option.

This commit is contained in:
Anton Chekulaev 2020-11-12 13:02:38 +11:00
parent 7499334a6a
commit 81f34e93be
16 changed files with 280 additions and 232 deletions

View File

@ -89,6 +89,7 @@ dependencies {
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-common-java8:2.2.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
implementation ("com.google.firebase:firebase-messaging:18.0.0") {
exclude group: 'com.google.firebase', module: 'firebase-core'

View File

@ -57,7 +57,7 @@
android:layout_marginLeft="@dimen/massive_spacing"
android:layout_marginTop="@dimen/medium_spacing"
android:layout_marginRight="@dimen/massive_spacing"
android:text="Backup" />
android:text="@string/activity_landing_restore_backup_button_title" />
<Button
android:id="@+id/linkButton"

View File

@ -5,91 +5,120 @@
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="org.thoughtcrime.securesms.loki.activities.BackupRestoreViewModel"/>
<import type="org.thoughtcrime.securesms.util.BackupUtil"/>
<import type="android.view.View"/>
<variable
name="viewModel"
type="org.thoughtcrime.securesms.loki.activities.BackupRestoreViewModel" />
</data>
<LinearLayout
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:layout_height="match_parent">
<View
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:text="@string/activity_backup_restore_title"
android:textColor="@color/text"
android:textSize="@dimen/large_font_size"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="4dp"
android:layout_marginRight="@dimen/very_large_spacing"
android:text="@string/activity_backup_restore_explanation_1"
android:textColor="@color/text"
android:textSize="@dimen/small_font_size" />
<Button
android:id="@+id/buttonSelectFile"
style="@style/Button.Primary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="4dp"
android:layout_marginRight="@dimen/very_large_spacing"
android:textColor="@color/black"
android:text="@{viewModel.backupFile != null ? BackupRestoreViewModel.uriToFileName(buttonSelectFile, viewModel.backupFile) : @string/activity_backup_restore_select_file}"
tools:text="Select a file"
/>
<EditText
android:id="@+id/backupCode"
style="@style/SmallSessionEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="10dp"
android:layout_marginRight="@dimen/very_large_spacing"
android:hint="@string/activity_backup_restore_passphrase"
android:inputType="numberDecimal|textNoSuggestions"
android:digits="0123456789"
android:maxLength="@{BackupUtil.BACKUP_PASSPHRASE_LENGTH}"
android:text="@={viewModel.backupPassphrase}"
android:visibility="@{viewModel.backupFile != null ? View.VISIBLE : View.INVISIBLE}"/>
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<Button
android:id="@+id/restoreButton"
style="@style/Widget.Session.Button.Common.ProminentFilled"
android:layout_width="match_parent"
android:layout_height="@dimen/medium_button_height"
android:layout_marginLeft="@dimen/massive_spacing"
android:layout_marginRight="@dimen/massive_spacing"
android:text="@string/continue_2"
android:visibility="@{BackupRestoreViewModel.validateData(viewModel.backupFile, viewModel.backupPassphrase) ? View.VISIBLE : View.INVISIBLE}"/>
<TextView
android:id="@+id/termsTextView"
android:layout_width="match_parent"
android:layout_height="@dimen/onboarding_button_bottom_offset"
android:layout_marginLeft="@dimen/massive_spacing"
android:layout_marginRight="@dimen/massive_spacing"
android:gravity="center"
android:text="By using this service, you agree to our Terms of Service and Privacy Policy"
android:textColor="@color/text"
android:textColorLink="@color/text"
android:textSize="@dimen/very_small_font_size" /> <!-- Intentionally not yet translated -->
</LinearLayout>
<FrameLayout
android:id="@+id/busyIndicator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:text="Restore from backup"
android:textColor="@color/text"
android:textSize="@dimen/large_font_size"
android:textStyle="bold" />
android:layout_height="match_parent"
android:background="#A4000000"
android:visibility="@{viewModel.processingBackupFile == true ? View.VISIBLE : View.GONE}"
tools:visibility="visible">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="4dp"
android:layout_marginRight="@dimen/very_large_spacing"
android:text="Go on and pick the backup file to restore from."
android:textColor="@color/text"
android:textSize="@dimen/small_font_size" />
<ProgressBar
android:layout_width="64dp"
android:layout_height="64dp"
android:indeterminate="true"
android:layout_gravity="center"/>
<Button
android:id="@+id/buttonSelectFile"
style="@style/Button.Primary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="4dp"
android:layout_marginRight="@dimen/very_large_spacing"
android:text="@{BackupRestoreViewModel.uriToFileName(buttonSelectFile, viewModel.backupFile), default=`Select a file`}"/>
</FrameLayout>
<EditText
android:id="@+id/backupCode"
style="@style/SmallSessionEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="10dp"
android:layout_marginRight="@dimen/very_large_spacing"
android:hint="Backup code"
android:inputType="numberDecimal"
android:text="@={viewModel.backupPassphrase}" />
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<Button
android:id="@+id/restoreButton"
style="@style/Widget.Session.Button.Common.ProminentFilled"
android:layout_width="match_parent"
android:layout_height="@dimen/medium_button_height"
android:layout_marginLeft="@dimen/massive_spacing"
android:layout_marginRight="@dimen/massive_spacing"
android:text="@string/continue_2"
android:visibility="@{BackupRestoreViewModel.validateData(viewModel.backupFile, viewModel.backupPassphrase) ? View.VISIBLE : View.INVISIBLE}"/>
<TextView
android:id="@+id/termsTextView"
android:layout_width="match_parent"
android:layout_height="@dimen/onboarding_button_bottom_offset"
android:layout_marginLeft="@dimen/massive_spacing"
android:layout_marginRight="@dimen/massive_spacing"
android:gravity="center"
android:text="By using this service, you agree to our Terms of Service and Privacy Policy"
android:textColor="@color/text"
android:textColorLink="@color/text"
android:textSize="@dimen/very_small_font_size" /> <!-- Intentionally not yet translated -->
</LinearLayout>
</FrameLayout>
</layout>

View File

@ -57,7 +57,7 @@
android:layout_marginLeft="@dimen/massive_spacing"
android:layout_marginTop="@dimen/small_spacing"
android:layout_marginRight="@dimen/massive_spacing"
android:text="Backup" />
android:text="@string/activity_landing_restore_backup_button_title" />
<Button
android:id="@+id/linkButton"

View File

@ -1670,6 +1670,7 @@
<string name="activity_landing_title_2">Your Session begins here...</string>
<string name="activity_landing_register_button_title">Create Session ID</string>
<string name="activity_landing_restore_button_title">Continue Your Session</string>
<string name="activity_landing_restore_backup_button_title">Restore Backup</string>
<string name="activity_landing_link_button_title">Link to an existing account</string>
<string name="activity_landing_device_unlinked_dialog_title">Your device was unlinked successfully</string>
@ -1869,4 +1870,9 @@
<string name="dialog_backup_activation_failed">Failed to activate backups. Please try again or contact support.</string>
<string name="activity_backup_restore_title">Restore backup</string>
<string name="activity_backup_restore_select_file">Select a file</string>
<string name="activity_backup_restore_explanation_1">Select a backup file and enter the passphrase it was created with.</string>
<string name="activity_backup_restore_passphrase">30-digit passphrase</string>
</resources>

View File

@ -29,9 +29,8 @@ public class BackupDialog {
@NonNull SwitchPreferenceCompat preference,
@NonNull BackupDirSelector backupDirSelector) {
// String[] password = BackupUtil.generateBackupPassphrase();
String[] password = new String[]{"00000", "00000", "00000", "00000", "00000", "00000"};
String passwordSt = Util.join(password, " ");
String[] password = BackupUtil.generateBackupPassphrase();
String passwordSt = Util.join(password, "");
AlertDialog dialog = new AlertDialog.Builder(context)
.setTitle(R.string.BackupDialog_enable_local_backups)
@ -83,7 +82,7 @@ public class BackupDialog {
dialog.findViewById(R.id.number_table).setOnClickListener(v -> {
((ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText("text", passwordSt));
Toast.makeText(context, R.string.BackupDialog_copied_to_clipboard, Toast.LENGTH_LONG).show();
Toast.makeText(context, R.string.BackupDialog_copied_to_clipboard, Toast.LENGTH_SHORT).show();
});

View File

@ -4,6 +4,7 @@ import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.text.TextUtils
import androidx.annotation.WorkerThread
import com.annimon.stream.function.Consumer
import com.annimon.stream.function.Predicate
import com.google.protobuf.ByteString
@ -40,6 +41,7 @@ object FullBackupExporter {
private val TAG = FullBackupExporter::class.java.simpleName
@JvmStatic
@WorkerThread
@Throws(IOException::class)
fun export(context: Context,
attachmentSecret: AttachmentSecret,

View File

@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.content.ContentValues
import android.content.Context
import android.net.Uri
import androidx.annotation.WorkerThread
import net.sqlcipher.database.SQLiteDatabase
import org.greenrobot.eventbus.EventBus
import org.thoughtcrime.securesms.attachments.AttachmentId
@ -38,8 +39,9 @@ object FullBackupImporter {
private val TAG = FullBackupImporter::class.java.simpleName
@Throws(IOException::class)
@JvmStatic
@WorkerThread
@Throws(IOException::class)
fun importFromUri(context: Context,
attachmentSecret: AttachmentSecret,
db: SQLiteDatabase,

View File

@ -466,13 +466,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
publicChatAPI.getChannelInfo(publicChat.getChannel(), publicChat.getServer()).success(info -> {
String groupId = GroupUtil.getEncodedOpenGroupId(publicChat.getId().getBytes());
//TODO Use same approach as in PublicChatManager#addChat()
// publicChatAPI.updateProfileIfNeeded(
// publicChat.getChannel(),
// publicChat.getServer(),
// groupId,
// info,
// false);
publicChatAPI.updateProfileIfNeeded(
publicChat.getChannel(),
publicChat.getServer(),
groupId,
info,
false);
runOnUiThread(ConversationActivity.this::updateSubtitleTextView);
return Unit.INSTANCE;

View File

@ -127,31 +127,35 @@ public class IdentityKeyUtil {
LinkedList<BackupProtos.SharedPreference> prefList = new LinkedList<>();
prefList.add(BackupProtos.SharedPreference.newBuilder()
.setFile(MasterSecretUtil.PREFERENCES_NAME)
.setKey(IDENTITY_PUBLIC_KEY_PREF)
.setValue(preferences.getString(IDENTITY_PUBLIC_KEY_PREF, null))
.build());
prefList.add(BackupProtos.SharedPreference.newBuilder()
.setFile(MasterSecretUtil.PREFERENCES_NAME)
.setKey(IDENTITY_PRIVATE_KEY_PREF)
.setValue(preferences.getString(IDENTITY_PRIVATE_KEY_PREF, null))
.build());
if (preferences.contains(ED25519_PUBLIC_KEY)) {
prefList.add(BackupProtos.SharedPreference.newBuilder()
.setFile(MasterSecretUtil.PREFERENCES_NAME)
.setKey(IDENTITY_PUBLIC_KEY_PREF)
.setValue(preferences.getString(IDENTITY_PUBLIC_KEY_PREF, null))
.build());
.setFile(MasterSecretUtil.PREFERENCES_NAME)
.setKey(ED25519_PUBLIC_KEY)
.setValue(preferences.getString(ED25519_PUBLIC_KEY, null))
.build());
}
if (preferences.contains(ED25519_SECRET_KEY)) {
prefList.add(BackupProtos.SharedPreference.newBuilder()
.setFile(MasterSecretUtil.PREFERENCES_NAME)
.setKey(IDENTITY_PRIVATE_KEY_PREF)
.setValue(preferences.getString(IDENTITY_PRIVATE_KEY_PREF, null))
.build());
prefList.add(BackupProtos.SharedPreference.newBuilder()
.setFile(MasterSecretUtil.PREFERENCES_NAME)
.setKey(ED25519_PUBLIC_KEY)
.setValue(preferences.getString(ED25519_PUBLIC_KEY, null))
.build());
prefList.add(BackupProtos.SharedPreference.newBuilder()
.setFile(MasterSecretUtil.PREFERENCES_NAME)
.setKey(ED25519_SECRET_KEY)
.setValue(preferences.getString(ED25519_SECRET_KEY, null))
.build());
prefList.add(BackupProtos.SharedPreference.newBuilder()
.setFile(MasterSecretUtil.PREFERENCES_NAME)
.setKey(LOKI_SEED)
.setValue(preferences.getString(LOKI_SEED, null))
.build());
.setFile(MasterSecretUtil.PREFERENCES_NAME)
.setKey(ED25519_SECRET_KEY)
.setValue(preferences.getString(ED25519_SECRET_KEY, null))
.build());
}
prefList.add(BackupProtos.SharedPreference.newBuilder()
.setFile(MasterSecretUtil.PREFERENCES_NAME)
.setKey(LOKI_SEED)
.setValue(preferences.getString(LOKI_SEED, null))
.build());
return prefList;
}

View File

@ -1,13 +1,12 @@
package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.database.NoExternalStorageException;
import org.thoughtcrime.securesms.loki.database.BackupFileRecord;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.database.BackupFileRecord;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.service.GenericForegroundService;
import org.thoughtcrime.securesms.util.BackupUtil;

View File

@ -2,11 +2,9 @@ package org.thoughtcrime.securesms.loki.activities
import android.app.Activity
import android.app.Application
import android.content.Context
import android.content.Intent
import android.graphics.Typeface
import android.net.Uri
import android.os.AsyncTask
import android.os.Bundle
import android.provider.OpenableColumns
import android.text.Spannable
@ -15,13 +13,20 @@ import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.text.style.StyleSpan
import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.core.widget.addTextChangedListener
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.google.android.gms.common.util.Strings
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import network.loki.messenger.R
import network.loki.messenger.databinding.ActivityBackupRestoreBinding
import org.thoughtcrime.securesms.ApplicationContext
@ -31,144 +36,93 @@ import org.thoughtcrime.securesms.backup.FullBackupImporter.DatabaseDowngradeExc
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.logging.Log
import org.thoughtcrime.securesms.loki.utilities.fadeIn
import org.thoughtcrime.securesms.loki.utilities.fadeOut
import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo
import org.thoughtcrime.securesms.loki.utilities.show
import org.thoughtcrime.securesms.notifications.NotificationChannels
import org.thoughtcrime.securesms.util.BackupUtil
import org.thoughtcrime.securesms.util.TextSecurePreferences
import java.io.IOException
class BackupRestoreActivity : BaseActionBarActivity() {
companion object {
private const val TAG = "BackupRestoreActivity"
private const val REQUEST_CODE_BACKUP_FILE = 779955
}
private val viewModel by viewModels<BackupRestoreViewModel>()
// region Lifecycle
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!!
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setUpActionBarSessionLogo()
val dataBinding = DataBindingUtil.setContentView<ActivityBackupRestoreBinding>(this, R.layout.activity_backup_restore)
dataBinding.lifecycleOwner = this
dataBinding.viewModel = viewModel
// setContentView(R.layout.activity_backup_restore)
dataBinding.restoreButton.setOnClickListener { restore() }
val viewBinding = DataBindingUtil.setContentView<ActivityBackupRestoreBinding>(this, R.layout.activity_backup_restore)
viewBinding.lifecycleOwner = this
viewBinding.viewModel = viewModel
dataBinding.buttonSelectFile.setOnClickListener {
// Let user pick a file.
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
viewBinding.restoreButton.setOnClickListener { viewModel.tryRestoreBackup() }
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 = "*/*"
}
startActivityForResult(intent, REQUEST_CODE_BACKUP_FILE)
})
}
dataBinding.backupCode.addTextChangedListener { text -> viewModel.backupPassphrase.value = text.toString() }
viewBinding.backupCode.addTextChangedListener { text -> viewModel.backupPassphrase.value = text.toString() }
// Focus passphrase text edit when backup file is selected.
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)
}
})
// React to backup import result.
viewModel.backupImportResult.observe(this) { result ->
if (result != null) when (result) {
BackupRestoreViewModel.BackupImportResult.SUCCESS -> {
val intent = Intent(this, HomeActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
this.show(intent)
}
BackupRestoreViewModel.BackupImportResult.FAILURE_VERSION_DOWNGRADE ->
Toast.makeText(this, R.string.RegistrationActivity_backup_failure_downgrade, Toast.LENGTH_LONG).show()
BackupRestoreViewModel.BackupImportResult.FAILURE_UNKNOWN ->
Toast.makeText(this, R.string.RegistrationActivity_incorrect_backup_passphrase, Toast.LENGTH_LONG).show()
}
}
//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)
dataBinding.termsTextView.movementMethod = LinkMovementMethod.getInstance()
dataBinding.termsTextView.text = termsExplanation
viewBinding.termsTextView.movementMethod = LinkMovementMethod.getInstance()
viewBinding.termsTextView.text = termsExplanation
//endregion
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQUEST_CODE_BACKUP_FILE -> {
if (resultCode == Activity.RESULT_OK && data != null && data.data != null) {
// // Acquire persistent access permissions for the file selected.
// val persistentFlags: Int = data.flags and
// (Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
// context.contentResolver.takePersistableUriPermission(data.data!!, persistentFlags)
viewModel.onBackupFileSelected(data.data!!)
}
}
}
}
// endregion
// region Interaction
private fun restore() {
if (viewModel.backupFile.value == null && Strings.isEmptyOrWhitespace(viewModel.backupPassphrase.value)) return
val backupFile = viewModel.backupFile.value!!
val passphrase = viewModel.backupPassphrase.value!!.trim()
object : AsyncTask<Void?, Void?, BackupImportResult>() {
override fun doInBackground(vararg params: Void?): BackupImportResult {
return try {
val context: Context = this@BackupRestoreActivity
val database = DatabaseFactory.getBackupDatabase(context)
FullBackupImporter.importFromUri(
context,
AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(),
DatabaseFactory.getBackupDatabase(context),
backupFile,
passphrase
)
DatabaseFactory.upgradeRestored(context, database)
NotificationChannels.restoreContactNotificationChannels(context)
TextSecurePreferences.setRestorationTime(context, System.currentTimeMillis())
BackupImportResult.SUCCESS
} catch (e: DatabaseDowngradeException) {
Log.w(TAG, "Failed due to the backup being from a newer version of Signal.", e)
BackupImportResult.FAILURE_VERSION_DOWNGRADE
} catch (e: IOException) {
Log.w(TAG, e)
BackupImportResult.FAILURE_UNKNOWN
}
}
override fun onPostExecute(result: BackupImportResult) {
val context = this@BackupRestoreActivity
when (result) {
BackupImportResult.SUCCESS -> {
TextSecurePreferences.setHasViewedSeed(context, true)
TextSecurePreferences.setHasSeenWelcomeScreen(context, true)
TextSecurePreferences.setPromptedPushRegistration(context, true)
TextSecurePreferences.setHasSeenMultiDeviceRemovalSheet(context)
TextSecurePreferences.setHasSeenLightThemeIntroSheet(context)
val application = ApplicationContext.getInstance(context)
application.setUpStorageAPIIfNeeded()
application.setUpP2PAPIIfNeeded()
HomeActivity.requestResetAllSessionsOnStartup(context)
val intent = Intent(context, HomeActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
show(intent)
}
BackupImportResult.FAILURE_VERSION_DOWNGRADE ->
Toast.makeText(context, R.string.RegistrationActivity_backup_failure_downgrade, Toast.LENGTH_LONG).show()
BackupImportResult.FAILURE_UNKNOWN ->
Toast.makeText(context, R.string.RegistrationActivity_incorrect_backup_passphrase, Toast.LENGTH_LONG).show()
}
}
}.execute()
}
private fun openURL(url: String) {
try {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
@ -177,17 +131,13 @@ class BackupRestoreActivity : BaseActionBarActivity() {
Toast.makeText(this, R.string.invalid_url, Toast.LENGTH_SHORT).show()
}
}
enum class BackupImportResult {
SUCCESS, FAILURE_VERSION_DOWNGRADE, FAILURE_UNKNOWN
}
// endregion
}
class BackupRestoreViewModel(application: Application): AndroidViewModel(application) {
companion object {
private const val TAG = "BackupRestoreViewModel"
@JvmStatic
fun uriToFileName(view: View, fileUri: Uri?): String? {
fileUri ?: return null
@ -201,15 +151,70 @@ class BackupRestoreViewModel(application: Application): AndroidViewModel(applica
@JvmStatic
fun validateData(fileUri: Uri?, passphrase: String?): Boolean {
return fileUri != null && !Strings.isEmptyOrWhitespace(passphrase)
return fileUri != null &&
!Strings.isEmptyOrWhitespace(passphrase) &&
passphrase!!.length == BackupUtil.BACKUP_PASSPHRASE_LENGTH
}
}
val backupFile = MutableLiveData<Uri>()
val backupPassphrase = MutableLiveData<String>("000000000000000000000000000000")
val backupFile = MutableLiveData<Uri>(null)
val backupPassphrase = MutableLiveData<String>(null)
fun onBackupFileSelected(backupFile: Uri) {
//TODO Check if backup file is correct.
this.backupFile.value = backupFile
val processingBackupFile = MutableLiveData<Boolean>(false)
val backupImportResult = MutableLiveData<BackupImportResult>(null)
fun tryRestoreBackup() = viewModelScope.launch {
if (backupImportResult.value == BackupImportResult.SUCCESS) return@launch
if (!validateData(backupFile.value, backupPassphrase.value)) return@launch
val context = getApplication<Application>()
val backupFile = backupFile.value!!
val passphrase = backupPassphrase.value!!
val result: BackupImportResult
processingBackupFile.value = true
withContext(Dispatchers.IO) {
result = try {
val database = DatabaseFactory.getBackupDatabase(context)
FullBackupImporter.importFromUri(
context,
AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(),
DatabaseFactory.getBackupDatabase(context),
backupFile,
passphrase
)
DatabaseFactory.upgradeRestored(context, database)
NotificationChannels.restoreContactNotificationChannels(context)
TextSecurePreferences.setRestorationTime(context, System.currentTimeMillis())
TextSecurePreferences.setHasViewedSeed(context, true)
TextSecurePreferences.setHasSeenWelcomeScreen(context, true)
TextSecurePreferences.setPromptedPushRegistration(context, true)
TextSecurePreferences.setHasSeenMultiDeviceRemovalSheet(context)
TextSecurePreferences.setHasSeenLightThemeIntroSheet(context)
val application = ApplicationContext.getInstance(context)
application.setUpStorageAPIIfNeeded()
application.setUpP2PAPIIfNeeded()
HomeActivity.requestResetAllSessionsOnStartup(context)
BackupImportResult.SUCCESS
} catch (e: DatabaseDowngradeException) {
Log.w(TAG, "Failed due to the backup being from a newer version of Signal.", e)
BackupImportResult.FAILURE_VERSION_DOWNGRADE
} catch (e: Exception) {
Log.w(TAG, e)
BackupImportResult.FAILURE_UNKNOWN
}
}
processingBackupFile.value = false
backupImportResult.value = result
}
enum class BackupImportResult {
SUCCESS, FAILURE_VERSION_DOWNGRADE, FAILURE_UNKNOWN
}
}

View File

@ -75,10 +75,9 @@ class PublicChatManager(private val context: Context) {
// Create the group if we don't have one
if (threadID < 0) {
if (info.profilePictureURL.isNotEmpty()) {
//TODO Use DownloadUtilities to pull the avatar from the server.
// val profilePictureAsByteArray = ApplicationContext.getInstance(context).publicChatAPI
// ?.downloadOpenGroupProfilePicture(server, info.profilePictureURL)
// profilePicture = BitmapUtil.fromByteArray(profilePictureAsByteArray)
val profilePictureAsByteArray = ApplicationContext.getInstance(context).publicChatAPI
?.downloadOpenGroupProfilePicture(server, info.profilePictureURL)
profilePicture = BitmapUtil.fromByteArray(profilePictureAsByteArray)
}
val result = GroupManager.createOpenGroup(chat.id, context, profilePicture, chat.displayName)
threadID = result.threadId

View File

@ -37,12 +37,13 @@ fun View.animateSizeChange(@DimenRes startSizeID: Int, @DimenRes endSizeID: Int,
fun View.fadeIn(duration: Long = 150) {
visibility = View.VISIBLE
animate().cancel()
animate().setDuration(duration).alpha(1.0f).start()
}
fun View.fadeOut(duration: Long = 150) {
animate().cancel()
animate().setDuration(duration).alpha(0.0f).setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
super.onAnimationEnd(animation)
visibility = View.GONE

View File

@ -128,7 +128,8 @@ public class ChatsPreferenceFragment extends ListSummaryPreferenceFragment {
private void setBackupSummary() {
findPreference(TextSecurePreferences.BACKUP_NOW)
.setSummary(String.format(getString(R.string.ChatsPreferenceFragment_last_backup_s), BackupUtil.getLastBackupTimeString(getContext(), Locale.getDefault())));
.setSummary(String.format(getString(R.string.ChatsPreferenceFragment_last_backup_s),
BackupUtil.getLastBackupTimeString(getContext(), Locale.getDefault())));
}
private void setMediaDownloadSummaries() {

View File

@ -32,7 +32,8 @@ import kotlin.jvm.Throws
object BackupUtil {
private const val TAG = "BackupUtil"
const val BACKUP_FILE_MIME_TYPE = "application/x-binary"
const val BACKUP_FILE_MIME_TYPE = "application/session-backup"
const val BACKUP_PASSPHRASE_LENGTH = 30
/**
* Set app-wide configuration to enable the backups and schedule them.
@ -88,7 +89,7 @@ object BackupUtil {
@JvmStatic
fun generateBackupPassphrase(): Array<String> {
val random = ByteArray(30).also { SecureRandom().nextBytes(it) }
val random = ByteArray(BACKUP_PASSPHRASE_LENGTH).also { SecureRandom().nextBytes(it) }
return Array(6) {i ->
String.format("%05d", ByteUtil.byteArray5ToLong(random, i * 5) % 100000)
}
@ -180,7 +181,7 @@ object BackupUtil {
val record = DatabaseFactory.getLokiBackupFilesDatabase(context)
.insertBackupFile(BackupFileRecord(fileUri, -1, date))
Log.v(TAG, "Backup file was created: $fileUri")
Log.v(TAG, "A backup file was created: $fileUri")
return record
}