mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-21 23:15:23 +00:00
Backup restore landing screen option.
This commit is contained in:
parent
7499334a6a
commit
81f34e93be
@ -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'
|
||||
|
@ -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"
|
||||
|
@ -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>
|
@ -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"
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
});
|
||||
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user