Backup restore landing screen option.

pull/375/head
Anton Chekulaev 4 years ago
parent 7499334a6a
commit 81f34e93be

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

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

@ -5,91 +5,120 @@
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<data> <data>
<import type="org.thoughtcrime.securesms.loki.activities.BackupRestoreViewModel"/> <import type="org.thoughtcrime.securesms.loki.activities.BackupRestoreViewModel"/>
<import type="org.thoughtcrime.securesms.util.BackupUtil"/>
<import type="android.view.View"/> <import type="android.view.View"/>
<variable <variable
name="viewModel" name="viewModel"
type="org.thoughtcrime.securesms.loki.activities.BackupRestoreViewModel" /> type="org.thoughtcrime.securesms.loki.activities.BackupRestoreViewModel" />
</data> </data>
<LinearLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:orientation="vertical">
<View <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="match_parent"
android:layout_weight="1" /> android:orientation="vertical">
<TextView <View
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="0dp"
android:layout_marginLeft="@dimen/very_large_spacing" android:layout_weight="1" />
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" />
<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" />
<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`}"/>
<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 <TextView
android:id="@+id/restoreButton" android:layout_width="match_parent"
style="@style/Widget.Session.Button.Common.ProminentFilled" android:layout_height="wrap_content"
android:layout_width="match_parent" android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_height="@dimen/medium_button_height" android:layout_marginRight="@dimen/very_large_spacing"
android:layout_marginLeft="@dimen/massive_spacing" android:text="@string/activity_backup_restore_title"
android:layout_marginRight="@dimen/massive_spacing" android:textColor="@color/text"
android:text="@string/continue_2" android:textSize="@dimen/large_font_size"
android:visibility="@{BackupRestoreViewModel.validateData(viewModel.backupFile, viewModel.backupPassphrase) ? View.VISIBLE : View.INVISIBLE}"/> android:textStyle="bold" />
<TextView <TextView
android:id="@+id/termsTextView" 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_width="match_parent"
android:layout_height="@dimen/onboarding_button_bottom_offset" android:layout_height="match_parent"
android:layout_marginLeft="@dimen/massive_spacing" android:background="#A4000000"
android:layout_marginRight="@dimen/massive_spacing" android:visibility="@{viewModel.processingBackupFile == true ? View.VISIBLE : View.GONE}"
android:gravity="center" tools:visibility="visible">
android:text="By using this service, you agree to our Terms of Service and Privacy Policy"
android:textColor="@color/text" <ProgressBar
android:textColorLink="@color/text" android:layout_width="64dp"
android:textSize="@dimen/very_small_font_size" /> <!-- Intentionally not yet translated --> android:layout_height="64dp"
android:indeterminate="true"
</LinearLayout> android:layout_gravity="center"/>
</FrameLayout>
</FrameLayout>
</layout> </layout>

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

@ -1670,6 +1670,7 @@
<string name="activity_landing_title_2">Your Session begins here...</string> <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_register_button_title">Create Session ID</string>
<string name="activity_landing_restore_button_title">Continue Your Session</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_link_button_title">Link to an existing account</string>
<string name="activity_landing_device_unlinked_dialog_title">Your device was unlinked successfully</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="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> </resources>

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

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

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

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

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

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

@ -2,11 +2,9 @@ package org.thoughtcrime.securesms.loki.activities
import android.app.Activity import android.app.Activity
import android.app.Application import android.app.Application
import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Typeface import android.graphics.Typeface
import android.net.Uri import android.net.Uri
import android.os.AsyncTask
import android.os.Bundle import android.os.Bundle
import android.provider.OpenableColumns import android.provider.OpenableColumns
import android.text.Spannable import android.text.Spannable
@ -15,13 +13,20 @@ import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan import android.text.style.ClickableSpan
import android.text.style.StyleSpan import android.text.style.StyleSpan
import android.view.View import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.core.widget.addTextChangedListener import androidx.core.widget.addTextChangedListener
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.google.android.gms.common.util.Strings 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.R
import network.loki.messenger.databinding.ActivityBackupRestoreBinding import network.loki.messenger.databinding.ActivityBackupRestoreBinding
import org.thoughtcrime.securesms.ApplicationContext 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.crypto.AttachmentSecretProvider
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.logging.Log 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.setUpActionBarSessionLogo
import org.thoughtcrime.securesms.loki.utilities.show import org.thoughtcrime.securesms.loki.utilities.show
import org.thoughtcrime.securesms.notifications.NotificationChannels import org.thoughtcrime.securesms.notifications.NotificationChannels
import org.thoughtcrime.securesms.util.BackupUtil
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import java.io.IOException
class BackupRestoreActivity : BaseActionBarActivity() { class BackupRestoreActivity : BaseActionBarActivity() {
companion object { companion object {
private const val TAG = "BackupRestoreActivity" private const val TAG = "BackupRestoreActivity"
private const val REQUEST_CODE_BACKUP_FILE = 779955
} }
private val viewModel by viewModels<BackupRestoreViewModel>() 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?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setUpActionBarSessionLogo() 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 { viewBinding.restoreButton.setOnClickListener { viewModel.tryRestoreBackup() }
// Let user pick a file.
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { 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 = BackupUtil.BACKUP_FILE_MIME_TYPE
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 //region Legal info views
val termsExplanation = SpannableStringBuilder("By using this service, you agree to our Terms of Service and Privacy Policy") 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(StyleSpan(Typeface.BOLD), 40, 56, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
termsExplanation.setSpan(object : ClickableSpan() { termsExplanation.setSpan(object : ClickableSpan() {
override fun onClick(widget: View) { override fun onClick(widget: View) {
openURL("https://getsession.org/terms-of-service/") openURL("https://getsession.org/terms-of-service/")
} }
}, 40, 56, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) }, 40, 56, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
termsExplanation.setSpan(StyleSpan(Typeface.BOLD), 61, 75, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) termsExplanation.setSpan(StyleSpan(Typeface.BOLD), 61, 75, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
termsExplanation.setSpan(object : ClickableSpan() { termsExplanation.setSpan(object : ClickableSpan() {
override fun onClick(widget: View) { override fun onClick(widget: View) {
openURL("https://getsession.org/privacy-policy/") openURL("https://getsession.org/privacy-policy/")
} }
}, 61, 75, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) }, 61, 75, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
dataBinding.termsTextView.movementMethod = LinkMovementMethod.getInstance() viewBinding.termsTextView.movementMethod = LinkMovementMethod.getInstance()
dataBinding.termsTextView.text = termsExplanation viewBinding.termsTextView.text = termsExplanation
//endregion //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) { private fun openURL(url: String) {
try { try {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) 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() 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) { class BackupRestoreViewModel(application: Application): AndroidViewModel(application) {
companion object { companion object {
private const val TAG = "BackupRestoreViewModel"
@JvmStatic @JvmStatic
fun uriToFileName(view: View, fileUri: Uri?): String? { fun uriToFileName(view: View, fileUri: Uri?): String? {
fileUri ?: return null fileUri ?: return null
@ -201,15 +151,70 @@ class BackupRestoreViewModel(application: Application): AndroidViewModel(applica
@JvmStatic @JvmStatic
fun validateData(fileUri: Uri?, passphrase: String?): Boolean { 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 backupFile = MutableLiveData<Uri>(null)
val backupPassphrase = MutableLiveData<String>("000000000000000000000000000000") val backupPassphrase = MutableLiveData<String>(null)
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
fun onBackupFileSelected(backupFile: Uri) { backupImportResult.value = result
//TODO Check if backup file is correct. }
this.backupFile.value = backupFile
enum class BackupImportResult {
SUCCESS, FAILURE_VERSION_DOWNGRADE, FAILURE_UNKNOWN
} }
} }

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

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

@ -128,7 +128,8 @@ public class ChatsPreferenceFragment extends ListSummaryPreferenceFragment {
private void setBackupSummary() { private void setBackupSummary() {
findPreference(TextSecurePreferences.BACKUP_NOW) 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() { private void setMediaDownloadSummaries() {

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

Loading…
Cancel
Save