Hook up onboarding logic

This commit is contained in:
Niels Andriesse 2019-12-17 10:04:08 +01:00
parent f42e69388e
commit d4db46aeca
9 changed files with 233 additions and 9 deletions

View File

@ -48,6 +48,7 @@
<Button <Button
style="@style/MediumProminentFilledButton" style="@style/MediumProminentFilledButton"
android:id="@+id/registerButton"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/medium_button_height" android:layout_height="@dimen/medium_button_height"
android:layout_marginLeft="@dimen/massive_spacing" android:layout_marginLeft="@dimen/massive_spacing"

View File

@ -33,6 +33,7 @@
<TextView <TextView
style="@style/SessionIDTextView" style="@style/SessionIDTextView"
android:id="@+id/publicKeyTextView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing" android:layout_marginLeft="@dimen/very_large_spacing"
@ -46,8 +47,8 @@
android:layout_weight="1"/> android:layout_weight="1"/>
<Button <Button
android:id="@+id/registerButton"
style="@style/MediumProminentFilledButton" style="@style/MediumProminentFilledButton"
android:id="@+id/registerButton"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/medium_button_height" android:layout_height="@dimen/medium_button_height"
android:layout_marginLeft="@dimen/massive_spacing" android:layout_marginLeft="@dimen/massive_spacing"
@ -56,6 +57,7 @@
<Button <Button
style="@style/MediumProminentOutlineButton" style="@style/MediumProminentOutlineButton"
android:id="@+id/copyButton"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/medium_button_height" android:layout_height="@dimen/medium_button_height"
android:layout_marginLeft="@dimen/massive_spacing" android:layout_marginLeft="@dimen/massive_spacing"
@ -64,7 +66,7 @@
android:text="Copy" /> android:text="Copy" />
<Button <Button
android:id="@+id/legalButton" android:id="@+id/termsButton"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/onboarding_button_bottom_offset" android:layout_height="@dimen/onboarding_button_bottom_offset"
android:layout_marginLeft="@dimen/massive_spacing" android:layout_marginLeft="@dimen/massive_spacing"

View File

@ -49,6 +49,7 @@
<Button <Button
style="@style/MediumProminentFilledButton" style="@style/MediumProminentFilledButton"
android:id="@+id/restoreButton"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/medium_button_height" android:layout_height="@dimen/medium_button_height"
android:layout_marginLeft="@dimen/massive_spacing" android:layout_marginLeft="@dimen/massive_spacing"
@ -57,7 +58,7 @@
android:text="Continue" /> android:text="Continue" />
<Button <Button
android:id="@+id/legalButton" android:id="@+id/termsButton"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/onboarding_button_bottom_offset" android:layout_height="@dimen/onboarding_button_bottom_offset"
android:layout_marginLeft="@dimen/massive_spacing" android:layout_marginLeft="@dimen/massive_spacing"

View File

@ -1652,4 +1652,10 @@
<string name="dialog_device_unlink_title">Device unlinked</string> <string name="dialog_device_unlink_title">Device unlinked</string>
<string name="dialog_device_unlink_message">This device has been successfully unlinked</string> <string name="dialog_device_unlink_message">This device has been successfully unlinked</string>
<!-- Loki -->
<!-- Session -->
<string name="activity_register_public_key_copied_message">Copied to clipboard</string>
<!-- Session -->
</resources> </resources>

View File

@ -37,6 +37,7 @@ import android.support.v4.content.ContextCompat;
import android.support.v4.graphics.drawable.DrawableCompat; import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.support.v7.preference.Preference; import android.support.v7.preference.Preference;
import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
@ -365,7 +366,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
.setNeutralButton(R.string.activity_settings_seed_dialog_ok_button_title, null) .setNeutralButton(R.string.activity_settings_seed_dialog_ok_button_title, null)
.show(); .show();
} catch (Exception e) { } catch (Exception e) {
// Do nothing Log.d("Loki", e.getMessage());
} }
break; break;
default: default:

View File

@ -49,8 +49,8 @@ public class IdentityKeyUtil {
private static final String IDENTITY_PUBLIC_KEY_CIPHERTEXT_LEGACY_PREF = "pref_identity_public_curve25519"; private static final String IDENTITY_PUBLIC_KEY_CIPHERTEXT_LEGACY_PREF = "pref_identity_public_curve25519";
private static final String IDENTITY_PRIVATE_KEY_CIPHERTEXT_LEGACY_PREF = "pref_identity_private_curve25519"; private static final String IDENTITY_PRIVATE_KEY_CIPHERTEXT_LEGACY_PREF = "pref_identity_private_curve25519";
private static final String IDENTITY_PUBLIC_KEY_PREF = "pref_identity_public_v3"; public static final String IDENTITY_PUBLIC_KEY_PREF = "pref_identity_public_v3";
private static final String IDENTITY_PRIVATE_KEY_PREF = "pref_identity_private_v3"; public static final String IDENTITY_PRIVATE_KEY_PREF = "pref_identity_private_v3";
public static final String lokiSeedKey = "loki_seed"; public static final String lokiSeedKey = "loki_seed";

View File

@ -1,10 +1,18 @@
package org.thoughtcrime.securesms.loki.redesign package org.thoughtcrime.securesms.loki.redesign
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_display_name_v2.* import kotlinx.android.synthetic.main.activity_display_name_v2.*
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.ConversationListActivity
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.loki.redesign.utilities.setUpActionBarSessionLogo import org.thoughtcrime.securesms.loki.redesign.utilities.setUpActionBarSessionLogo
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.api.crypto.ProfileCipher
class DisplayNameActivity : BaseActionBarActivity() { class DisplayNameActivity : BaseActionBarActivity() {
@ -12,10 +20,40 @@ class DisplayNameActivity : BaseActionBarActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setUpActionBarSessionLogo() setUpActionBarSessionLogo()
setContentView(R.layout.activity_display_name_v2) setContentView(R.layout.activity_display_name_v2)
registerButton.setOnClickListener { register() }
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
displayNameEditText.requestFocus() displayNameEditText.requestFocus()
} }
private fun register() {
val displayName = displayNameEditText.text.toString().trim()
if (displayName.isEmpty()) {
return Toast.makeText(this, "Please pick a display name", Toast.LENGTH_SHORT).show()
}
if (!displayName.matches(Regex("[a-zA-Z0-9_]+"))) {
return Toast.makeText(this, "Please pick a display name that consists of only a-z, A-Z, 0-9 and _ characters", Toast.LENGTH_SHORT).show()
}
if (displayName.toByteArray().size > ProfileCipher.NAME_PADDED_LENGTH) {
return Toast.makeText(this, "Please pick a shorter display name", Toast.LENGTH_SHORT).show()
}
val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(displayNameEditText.windowToken, 0)
TextSecurePreferences.setProfileName(this, displayName)
TextSecurePreferences.setHasSeenWelcomeScreen(this, true)
TextSecurePreferences.setPromptedPushRegistration(this, true)
val publicChatAPI = ApplicationContext.getInstance(this).lokiPublicChatAPI
if (publicChatAPI != null) {
// TODO: This won't be necessary anymore when we don't auto-join the Loki Public Chat anymore
val application = ApplicationContext.getInstance(this)
application.setUpStorageAPIIfNeeded()
application.createDefaultPublicChatsIfNeeded()
val servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers()
servers.forEach { publicChatAPI.setDisplayName(displayName, it) }
}
startActivity(Intent(this, ConversationListActivity::class.java))
finish()
}
} }

View File

@ -1,32 +1,125 @@
package org.thoughtcrime.securesms.loki.redesign package org.thoughtcrime.securesms.loki.redesign
import android.content.ClipData
import android.content.ClipboardManager
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.os.Bundle import android.os.Bundle
import android.text.Spannable import android.text.Spannable
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import android.text.style.StyleSpan import android.text.style.StyleSpan
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_register.* import kotlinx.android.synthetic.main.activity_register.*
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.IdentityDatabase
import org.thoughtcrime.securesms.loki.redesign.utilities.setUpActionBarSessionLogo import org.thoughtcrime.securesms.loki.redesign.utilities.setUpActionBarSessionLogo
import org.thoughtcrime.securesms.util.Base64
import org.thoughtcrime.securesms.util.Hex
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.curve25519.Curve25519
import org.whispersystems.libsignal.ecc.Curve
import org.whispersystems.libsignal.ecc.ECKeyPair
import org.whispersystems.libsignal.util.KeyHelper
import org.whispersystems.signalservice.loki.utilities.hexEncodedPublicKey
import java.io.File
import java.io.FileOutputStream
class RegisterActivity : BaseActionBarActivity() { class RegisterActivity : BaseActionBarActivity() {
private var seed: ByteArray? = null
private var keyPair: ECKeyPair? = null
set(value) { field = value; updatePublicKeyTextView() }
// region Lifecycle
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_register) setContentView(R.layout.activity_register)
setUpLanguageFileDirectory()
setUpActionBarSessionLogo() setUpActionBarSessionLogo()
registerButton.setOnClickListener { register() } registerButton.setOnClickListener { register() }
copyButton.setOnClickListener { copyPublicKey() }
val termsExplanation = SpannableStringBuilder("By using this service, you agree to our Terms and Conditions and Privacy Statement") val termsExplanation = SpannableStringBuilder("By using this service, you agree to our Terms and Conditions and Privacy Statement")
termsExplanation.setSpan(StyleSpan(Typeface.BOLD), 40, 60, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) termsExplanation.setSpan(StyleSpan(Typeface.BOLD), 40, 60, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
termsExplanation.setSpan(StyleSpan(Typeface.BOLD), 65, 82, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) termsExplanation.setSpan(StyleSpan(Typeface.BOLD), 65, 82, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
legalButton.text = termsExplanation termsButton.text = termsExplanation
termsButton.setOnClickListener { showTerms() }
updateKeyPair()
}
// endregion
// region General
private fun setUpLanguageFileDirectory() {
val languages = listOf( "english", "japanese", "portuguese", "spanish" )
val directory = File(applicationInfo.dataDir)
for (language in languages) {
val fileName = "$language.txt"
if (directory.list().contains(fileName)) { continue }
val inputStream = assets.open("mnemonic/$fileName")
val file = File(directory, fileName)
val outputStream = FileOutputStream(file)
val buffer = ByteArray(1024)
while (true) {
val count = inputStream.read(buffer)
if (count < 0) { break }
outputStream.write(buffer, 0, count)
}
inputStream.close()
outputStream.close()
}
}
// endregion
// region Updating
private fun updateKeyPair() {
val seedCandidate = Curve25519.getInstance(Curve25519.BEST).generateSeed(16)
try {
this.keyPair = Curve.generateKeyPair(seedCandidate + seedCandidate) // Validate the seed
} catch (exception: Exception) {
return updateKeyPair()
}
seed = seedCandidate
} }
private fun updatePublicKeyTextView() {
publicKeyTextView.text = keyPair!!.hexEncodedPublicKey
}
// endregion
// region Interaction
private fun register() { private fun register() {
IdentityKeyUtil.save(this, IdentityKeyUtil.lokiSeedKey, Hex.toStringCondensed(seed))
IdentityKeyUtil.save(this, IdentityKeyUtil.IDENTITY_PUBLIC_KEY_PREF, Base64.encodeBytes(keyPair!!.publicKey.serialize()))
IdentityKeyUtil.save(this, IdentityKeyUtil.IDENTITY_PRIVATE_KEY_PREF, Base64.encodeBytes(keyPair!!.privateKey.serialize()))
val userHexEncodedPublicKey = keyPair!!.hexEncodedPublicKey
val registrationID = KeyHelper.generateRegistrationId(false)
TextSecurePreferences.setLocalRegistrationId(this, registrationID)
DatabaseFactory.getIdentityDatabase(this).saveIdentity(Address.fromSerialized(userHexEncodedPublicKey),
IdentityKeyUtil.getIdentityKeyPair(this).publicKey, IdentityDatabase.VerifiedStatus.VERIFIED,
true, System.currentTimeMillis(), true)
TextSecurePreferences.setLocalNumber(this, userHexEncodedPublicKey)
val intent = Intent(this, DisplayNameActivity::class.java) val intent = Intent(this, DisplayNameActivity::class.java)
startActivity(intent) startActivity(intent)
} }
private fun copyPublicKey() {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Session ID", keyPair!!.hexEncodedPublicKey)
clipboard.primaryClip = clip
Toast.makeText(this, R.string.activity_register_public_key_copied_message, Toast.LENGTH_SHORT).show()
}
private fun showTerms() {
try {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/loki-project/loki-messenger-android/blob/master/privacy-policy.md"))
startActivity(intent)
} catch (e: Exception) {
Toast.makeText(this, "Couldn't open link", Toast.LENGTH_SHORT).show()
}
}
// endregion
} }

View File

@ -1,29 +1,111 @@
package org.thoughtcrime.securesms.loki.redesign package org.thoughtcrime.securesms.loki.redesign
import android.content.Intent
import android.graphics.Typeface import android.graphics.Typeface
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.text.Spannable import android.text.Spannable
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import android.text.style.StyleSpan import android.text.style.StyleSpan
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_restore.* import kotlinx.android.synthetic.main.activity_restore.*
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.IdentityDatabase
import org.thoughtcrime.securesms.loki.redesign.utilities.setUpActionBarSessionLogo import org.thoughtcrime.securesms.loki.redesign.utilities.setUpActionBarSessionLogo
import org.thoughtcrime.securesms.util.Base64
import org.thoughtcrime.securesms.util.Hex
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.libsignal.ecc.Curve
import org.whispersystems.libsignal.util.KeyHelper
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
import org.whispersystems.signalservice.loki.utilities.hexEncodedPublicKey
import java.io.File
import java.io.FileOutputStream
class RestoreActivity : BaseActionBarActivity() { class RestoreActivity : BaseActionBarActivity() {
private lateinit var languageFileDirectory: File
// region Lifecycle
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setUpLanguageFileDirectory()
setUpActionBarSessionLogo() setUpActionBarSessionLogo()
setContentView(R.layout.activity_restore) setContentView(R.layout.activity_restore)
mnemonicEditText.imeOptions = mnemonicEditText.imeOptions or 16777216 // Always use incognito keyboard
restoreButton.setOnClickListener { restore() }
val termsExplanation = SpannableStringBuilder("By using this service, you agree to our Terms and Conditions and Privacy Statement") val termsExplanation = SpannableStringBuilder("By using this service, you agree to our Terms and Conditions and Privacy Statement")
termsExplanation.setSpan(StyleSpan(Typeface.BOLD), 40, 60, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) termsExplanation.setSpan(StyleSpan(Typeface.BOLD), 40, 60, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
termsExplanation.setSpan(StyleSpan(Typeface.BOLD), 65, 82, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) termsExplanation.setSpan(StyleSpan(Typeface.BOLD), 65, 82, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
legalButton.text = termsExplanation termsButton.text = termsExplanation
termsButton.setOnClickListener { showTerms() }
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
mnemonicEditText.requestFocus() mnemonicEditText.requestFocus()
} }
// endregion
// region General
private fun setUpLanguageFileDirectory() {
val languages = listOf( "english", "japanese", "portuguese", "spanish" )
val directory = File(applicationInfo.dataDir)
for (language in languages) {
val fileName = "$language.txt"
if (directory.list().contains(fileName)) { continue }
val inputStream = assets.open("mnemonic/$fileName")
val file = File(directory, fileName)
val outputStream = FileOutputStream(file)
val buffer = ByteArray(1024)
while (true) {
val count = inputStream.read(buffer)
if (count < 0) { break }
outputStream.write(buffer, 0, count)
}
inputStream.close()
outputStream.close()
}
languageFileDirectory = directory
}
// endregion
// region Interaction
private fun restore() {
val mnemonic = mnemonicEditText.text.toString()
try {
val hexEncodedSeed = MnemonicCodec(languageFileDirectory).decode(mnemonic)
var seed = Hex.fromStringCondensed(hexEncodedSeed)
IdentityKeyUtil.save(this, IdentityKeyUtil.lokiSeedKey, Hex.toStringCondensed(seed))
if (seed.size == 16) { seed = seed + seed }
val keyPair = Curve.generateKeyPair(seed)
IdentityKeyUtil.save(this, IdentityKeyUtil.IDENTITY_PUBLIC_KEY_PREF, Base64.encodeBytes(keyPair.publicKey.serialize()))
IdentityKeyUtil.save(this, IdentityKeyUtil.IDENTITY_PRIVATE_KEY_PREF, Base64.encodeBytes(keyPair.privateKey.serialize()))
val userHexEncodedPublicKey = keyPair.hexEncodedPublicKey
val registrationID = KeyHelper.generateRegistrationId(false)
TextSecurePreferences.setLocalRegistrationId(this, registrationID)
DatabaseFactory.getIdentityDatabase(this).saveIdentity(Address.fromSerialized(userHexEncodedPublicKey),
IdentityKeyUtil.getIdentityKeyPair(this).publicKey, IdentityDatabase.VerifiedStatus.VERIFIED,
true, System.currentTimeMillis(), true)
TextSecurePreferences.setLocalNumber(this, userHexEncodedPublicKey)
val intent = Intent(this, DisplayNameActivity::class.java)
startActivity(intent)
} catch (e: Exception) {
val message = if (e is MnemonicCodec.DecodingError) e.description else MnemonicCodec.DecodingError.Generic.description
return Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
}
private fun showTerms() {
try {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/loki-project/loki-messenger-android/blob/master/privacy-policy.md"))
startActivity(intent)
} catch (e: Exception) {
Toast.makeText(this, "Couldn't open link", Toast.LENGTH_SHORT).show()
}
}
// endregion
} }