session-android/app/src/main/java/org/thoughtcrime/securesms/onboarding/LinkDeviceActivity.kt

213 lines
8.8 KiB
Kotlin
Raw Normal View History

2021-07-09 03:14:21 +02:00
package org.thoughtcrime.securesms.onboarding
2021-02-26 01:10:45 +01:00
import android.content.Context
import android.content.Intent
2021-02-26 01:10:45 +01:00
import android.os.Bundle
import android.text.InputType
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
2021-02-26 01:10:45 +01:00
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
2021-02-26 01:40:48 +01:00
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentPagerAdapter
import androidx.lifecycle.lifecycleScope
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
2021-02-26 01:10:45 +01:00
import network.loki.messenger.R
import network.loki.messenger.databinding.ActivityLinkDeviceBinding
import network.loki.messenger.databinding.FragmentRecoveryPhraseBinding
import org.session.libsession.utilities.TextSecurePreferences
2021-05-18 01:17:22 +02:00
import org.session.libsignal.crypto.MnemonicCodec
2021-02-26 01:10:45 +01:00
import org.session.libsignal.utilities.Hex
import org.session.libsignal.utilities.KeyHelper
2021-05-18 01:12:33 +02:00
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.hexEncodedPublicKey
import org.thoughtcrime.securesms.ApplicationContext
2021-02-26 01:10:45 +01:00
import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.crypto.KeyPairUtilities
import org.thoughtcrime.securesms.crypto.MnemonicUtilities
2021-07-09 05:56:38 +02:00
import org.thoughtcrime.securesms.util.ScanQRCodeWrapperFragment
import org.thoughtcrime.securesms.util.ScanQRCodeWrapperFragmentDelegate
2021-07-09 05:18:48 +02:00
import org.thoughtcrime.securesms.util.push
import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo
2021-02-26 01:10:45 +01:00
class LinkDeviceActivity : BaseActionBarActivity(), ScanQRCodeWrapperFragmentDelegate {
private lateinit var binding: ActivityLinkDeviceBinding
2021-02-26 01:10:45 +01:00
private val adapter = LinkDeviceActivityAdapter(this)
private var restoreJob: Job? = null
2021-02-26 01:10:45 +01:00
override fun onBackPressed() {
2021-04-29 05:41:43 +02:00
if (restoreJob?.isActive == true) return // Don't allow going back with a pending job
super.onBackPressed()
}
2021-02-26 01:10:45 +01:00
// region Lifecycle
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setUpActionBarSessionLogo()
TextSecurePreferences.apply {
setHasViewedSeed(this@LinkDeviceActivity, true)
setConfigurationMessageSynced(this@LinkDeviceActivity, false)
setRestorationTime(this@LinkDeviceActivity, System.currentTimeMillis())
setLastProfileUpdateTime(this@LinkDeviceActivity, 0)
}
binding = ActivityLinkDeviceBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.viewPager.adapter = adapter
binding.tabLayout.setupWithViewPager(binding.viewPager)
2021-02-26 01:10:45 +01:00
}
// endregion
// region Interaction
override fun handleQRCodeScanned(mnemonic: String) {
try {
val seed = Hex.fromStringCondensed(mnemonic)
continueWithSeed(seed)
} catch (e: Exception) {
Log.e("Loki","Error getting seed from QR code", e)
Toast.makeText(this, "An error occurred.", Toast.LENGTH_LONG).show()
}
2021-02-26 01:10:45 +01:00
}
fun continueWithMnemonic(mnemonic: String) {
val loadFileContents: (String) -> String = { fileName ->
MnemonicUtilities.loadFileContents(this, fileName)
}
try {
val hexEncodedSeed = MnemonicCodec(loadFileContents).decode(mnemonic)
val seed = Hex.fromStringCondensed(hexEncodedSeed)
continueWithSeed(seed)
} catch (error: Exception) {
val message = if (error is MnemonicCodec.DecodingError) {
error.description
} else {
"An error occurred."
}
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
}
}
private fun continueWithSeed(seed: ByteArray) {
// only have one sync job running at a time (prevent QR from trying to spawn a new job)
if (restoreJob?.isActive == true) return
restoreJob = lifecycleScope.launch {
// RestoreActivity handles seed this way
val keyPairGenerationResult = KeyPairUtilities.generate(seed)
val x25519KeyPair = keyPairGenerationResult.x25519KeyPair
KeyPairUtilities.store(this@LinkDeviceActivity, seed, keyPairGenerationResult.ed25519KeyPair, x25519KeyPair)
val userHexEncodedPublicKey = x25519KeyPair.hexEncodedPublicKey
val registrationID = KeyHelper.generateRegistrationId(false)
TextSecurePreferences.setLocalRegistrationId(this@LinkDeviceActivity, registrationID)
TextSecurePreferences.setLocalNumber(this@LinkDeviceActivity, userHexEncodedPublicKey)
TextSecurePreferences.setRestorationTime(this@LinkDeviceActivity, System.currentTimeMillis())
TextSecurePreferences.setHasViewedSeed(this@LinkDeviceActivity, true)
binding.loader.isVisible = true
val snackBar = Snackbar.make(binding.containerLayout, R.string.activity_link_device_skip_prompt,Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.registration_activity__skip) { register(true) }
val skipJob = launch {
delay(30_000L)
snackBar.show()
// show a dialog or something saying do you want to skip this bit?
}
// start polling and wait for updated message
ApplicationContext.getInstance(this@LinkDeviceActivity).apply {
startPollingIfNeeded()
}
TextSecurePreferences.events.filter { it == TextSecurePreferences.CONFIGURATION_SYNCED }.collect {
// handle we've synced
snackBar.dismiss()
skipJob.cancel()
register(false)
}
binding.loader.isVisible = false
}
}
private fun register(skipped: Boolean) {
restoreJob?.cancel()
binding.loader.isVisible = false
TextSecurePreferences.setLastConfigurationSyncTime(this, System.currentTimeMillis())
val intent = Intent(this@LinkDeviceActivity, if (skipped) DisplayNameActivity::class.java else PNModeActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
push(intent)
2021-02-26 01:10:45 +01:00
}
// endregion
}
// region Adapter
private class LinkDeviceActivityAdapter(private val activity: LinkDeviceActivity) : FragmentPagerAdapter(activity.supportFragmentManager) {
val recoveryPhraseFragment = RecoveryPhraseFragment()
override fun getCount(): Int {
return 2
}
override fun getItem(index: Int): Fragment {
return when (index) {
0 -> recoveryPhraseFragment
1 -> {
val result = ScanQRCodeWrapperFragment()
result.delegate = activity
result.message = activity.getString(R.string.activity_link_device_qr_message)
2021-02-26 01:10:45 +01:00
result
}
else -> throw IllegalStateException()
}
}
override fun getPageTitle(index: Int): CharSequence {
2021-02-26 01:10:45 +01:00
return when (index) {
0 -> activity.getString(R.string.activity_link_device_recovery_phrase)
1 -> activity.getString(R.string.activity_link_device_scan_qr_code)
2021-02-26 01:10:45 +01:00
else -> throw IllegalStateException()
}
}
}
// endregion
// region Recovery Phrase Fragment
class RecoveryPhraseFragment : Fragment() {
private lateinit var binding: FragmentRecoveryPhraseBinding
2021-02-26 01:10:45 +01:00
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentRecoveryPhraseBinding.inflate(inflater, container, false)
return binding.root
2021-02-26 01:10:45 +01:00
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
with(binding) {
mnemonicEditText.imeOptions = EditorInfo.IME_ACTION_DONE or 16777216 // Always use incognito keyboard
mnemonicEditText.setRawInputType(InputType.TYPE_CLASS_TEXT)
mnemonicEditText.setOnEditorActionListener { v, actionID, _ ->
if (actionID == EditorInfo.IME_ACTION_DONE) {
val imm = v.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(v.windowToken, 0)
handleContinueButtonTapped()
true
} else {
false
}
2021-02-26 01:10:45 +01:00
}
continueButton.setOnClickListener { handleContinueButtonTapped() }
2021-02-26 01:10:45 +01:00
}
}
private fun handleContinueButtonTapped() {
val mnemonic = binding.mnemonicEditText.text?.trim().toString()
2021-02-26 01:10:45 +01:00
(requireActivity() as LinkDeviceActivity).continueWithMnemonic(mnemonic)
}
}
// endregion