diff --git a/res/layout/view_device_linking.xml b/res/layout/view_device_linking.xml index b16429e9b1..e9ecbc4229 100644 --- a/res/layout/view_device_linking.xml +++ b/res/layout/view_device_linking.xml @@ -1,6 +1,7 @@ + + Your device has been linked successfully Authorize Cancel + Device Name (Optional) Scan QR Code Scan the QR code of the person you\'d like to securely message. They can find their QR code by going into Loki Messenger\'s in-app settings and clicking \"Show QR Code\". diff --git a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java index 0d2438d79b..0bed4c6adb 100644 --- a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java +++ b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java @@ -26,7 +26,6 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; -import android.os.AsyncTask; import android.os.Build; import android.os.Build.VERSION; import android.os.Bundle; @@ -40,14 +39,8 @@ import android.support.v7.app.AlertDialog; import android.support.v7.preference.Preference; import android.widget.Toast; -import org.jetbrains.annotations.NotNull; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.loki.DeviceLinkingDialog; -import org.thoughtcrime.securesms.loki.DeviceLinkingDialogDelegate; -import org.thoughtcrime.securesms.loki.DeviceLinkingView; import org.thoughtcrime.securesms.loki.LinkedDevicesActivity; -import org.thoughtcrime.securesms.loki.MultiDeviceUtilities; import org.thoughtcrime.securesms.loki.QRCodeDialog; import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment; import org.thoughtcrime.securesms.preferences.ChatsPreferenceFragment; @@ -58,7 +51,6 @@ import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.whispersystems.signalservice.loki.api.PairingAuthorisation; import org.whispersystems.signalservice.loki.crypto.MnemonicCodec; import org.whispersystems.signalservice.loki.utilities.Analytics; import org.whispersystems.signalservice.loki.utilities.SerializationKt; diff --git a/src/org/thoughtcrime/securesms/loki/DeviceLinkingDelegate.kt b/src/org/thoughtcrime/securesms/loki/DeviceLinkingDelegate.kt new file mode 100644 index 0000000000..d7509a5c42 --- /dev/null +++ b/src/org/thoughtcrime/securesms/loki/DeviceLinkingDelegate.kt @@ -0,0 +1,33 @@ +package org.thoughtcrime.securesms.loki + +import org.whispersystems.signalservice.loki.api.PairingAuthorisation + +interface DeviceLinkingDelegate { + companion object { + fun combine(vararg delegates: DeviceLinkingDelegate?): DeviceLinkingDelegate { + val validDelegates = delegates.filterNotNull() + return object : DeviceLinkingDelegate { + override fun handleDeviceLinkAuthorized(pairingAuthorisation: PairingAuthorisation) { + for (delegate in validDelegates) { delegate.handleDeviceLinkAuthorized(pairingAuthorisation) } + } + + override fun handleDeviceLinkingDialogDismissed() { + for (delegate in validDelegates) { delegate.handleDeviceLinkingDialogDismissed() } + } + + override fun sendPairingAuthorizedMessage(pairingAuthorisation: PairingAuthorisation) { + for (delegate in validDelegates) { delegate.sendPairingAuthorizedMessage(pairingAuthorisation) } + } + + override fun setDeviceDisplayName(hexEncodedPublicKey: String, displayName: String) { + for (delegate in validDelegates) { delegate.setDeviceDisplayName(hexEncodedPublicKey, displayName) } + } + } + } + } + + fun handleDeviceLinkAuthorized(pairingAuthorisation: PairingAuthorisation) {} + fun handleDeviceLinkingDialogDismissed() {} + fun sendPairingAuthorizedMessage(pairingAuthorisation: PairingAuthorisation) {} + fun setDeviceDisplayName(hexEncodedPublicKey: String, displayName: String) {} +} \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/loki/DeviceLinkingDialog.kt b/src/org/thoughtcrime/securesms/loki/DeviceLinkingDialog.kt index d5faaaca16..0b6bede2eb 100644 --- a/src/org/thoughtcrime/securesms/loki/DeviceLinkingDialog.kt +++ b/src/org/thoughtcrime/securesms/loki/DeviceLinkingDialog.kt @@ -8,13 +8,12 @@ import org.whispersystems.signalservice.loki.api.DeviceLinkingSession import org.whispersystems.signalservice.loki.api.DeviceLinkingSessionListener import org.whispersystems.signalservice.loki.api.PairingAuthorisation -class DeviceLinkingDialog private constructor(private val context: Context, private val mode: DeviceLinkingView.Mode, private val delegate: DeviceLinkingDialogDelegate?) : DeviceLinkingViewDelegate, DeviceLinkingSessionListener { +class DeviceLinkingDialog private constructor(private val context: Context, private val mode: DeviceLinkingView.Mode, private val delegate: DeviceLinkingDelegate?) : DeviceLinkingDelegate, DeviceLinkingSessionListener { private lateinit var view: DeviceLinkingView private lateinit var dialog: AlertDialog companion object { - - fun show(context: Context, mode: DeviceLinkingView.Mode, delegate: DeviceLinkingDialogDelegate?): DeviceLinkingDialog { + fun show(context: Context, mode: DeviceLinkingView.Mode, delegate: DeviceLinkingDelegate?): DeviceLinkingDialog { val dialog = DeviceLinkingDialog(context, mode, delegate) dialog.show() return dialog @@ -22,8 +21,10 @@ class DeviceLinkingDialog private constructor(private val context: Context, priv } private fun show() { - view = DeviceLinkingView(context, mode, this) + val delegate = DeviceLinkingDelegate.combine(this, this.delegate) + view = DeviceLinkingView(context, mode, delegate) dialog = AlertDialog.Builder(context).setView(view).show() + dialog.setCanceledOnTouchOutside(false) view.dismiss = { dismiss() } DeviceLinkingSession.shared.startListeningForLinkingRequests() DeviceLinkingSession.shared.addListener(this) @@ -35,20 +36,11 @@ class DeviceLinkingDialog private constructor(private val context: Context, priv dialog.dismiss() } - override fun handleDeviceLinkAuthorized(pairingAuthorisation: PairingAuthorisation) { - delegate?.handleDeviceLinkAuthorized(pairingAuthorisation) - } - override fun handleDeviceLinkingDialogDismissed() { if (mode == DeviceLinkingView.Mode.Master && view.pairingAuthorisation != null) { val authorisation = view.pairingAuthorisation!! DatabaseFactory.getLokiPreKeyBundleDatabase(context).removePreKeyBundle(authorisation.secondaryDevicePublicKey) } - delegate?.handleDeviceLinkingDialogDismissed() - } - - override fun sendPairingAuthorizedMessage(pairingAuthorisation: PairingAuthorisation) { - delegate?.sendPairingAuthorizedMessage(pairingAuthorisation) } override fun requestUserAuthorization(authorisation: PairingAuthorisation) { diff --git a/src/org/thoughtcrime/securesms/loki/DeviceLinkingDialogDelegate.kt b/src/org/thoughtcrime/securesms/loki/DeviceLinkingDialogDelegate.kt deleted file mode 100644 index 5083b013b3..0000000000 --- a/src/org/thoughtcrime/securesms/loki/DeviceLinkingDialogDelegate.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.thoughtcrime.securesms.loki - -import org.whispersystems.signalservice.loki.api.PairingAuthorisation - -interface DeviceLinkingDialogDelegate { - fun handleDeviceLinkAuthorized(pairingAuthorisation: PairingAuthorisation) { } - fun handleDeviceLinkingDialogDismissed() { } - fun sendPairingAuthorizedMessage(pairingAuthorisation: PairingAuthorisation) { } -} \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/loki/DeviceLinkingView.kt b/src/org/thoughtcrime/securesms/loki/DeviceLinkingView.kt index 35c5041d7e..e797a99e06 100644 --- a/src/org/thoughtcrime/securesms/loki/DeviceLinkingView.kt +++ b/src/org/thoughtcrime/securesms/loki/DeviceLinkingView.kt @@ -4,6 +4,8 @@ import android.content.Context import android.graphics.Color import android.graphics.PorterDuff import android.os.Handler +import android.text.Editable +import android.text.TextWatcher import android.util.AttributeSet import android.view.View import android.widget.LinearLayout @@ -12,12 +14,10 @@ import network.loki.messenger.R import org.thoughtcrime.securesms.util.TextSecurePreferences import org.whispersystems.signalservice.loki.api.PairingAuthorisation import org.whispersystems.signalservice.loki.crypto.MnemonicCodec -import org.whispersystems.signalservice.loki.utilities.removing05PrefixIfNeeded import java.io.File -import java.io.FileOutputStream -class DeviceLinkingView private constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, private val mode: Mode, private var delegate: DeviceLinkingViewDelegate) : LinearLayout(context, attrs, defStyleAttr) { - private lateinit var languageFileDirectory: File +class DeviceLinkingView private constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, private val mode: Mode, private var delegate: DeviceLinkingDelegate) : LinearLayout(context, attrs, defStyleAttr) { + private val languageFileDirectory: File = MnemonicUtilities.getLanguageFileDirectory(context) var dismiss: (() -> Unit)? = null var pairingAuthorisation: PairingAuthorisation? = null private set @@ -27,12 +27,11 @@ class DeviceLinkingView private constructor(context: Context, attrs: AttributeSe // endregion // region Lifecycle - constructor(context: Context, mode: Mode, delegate: DeviceLinkingViewDelegate) : this(context, null, 0, mode, delegate) - private constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0, Mode.Master, object : DeviceLinkingViewDelegate { }) // Just pass in a dummy mode + constructor(context: Context, mode: Mode, delegate: DeviceLinkingDelegate) : this(context, null, 0, mode, delegate) + private constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0, Mode.Master, object : DeviceLinkingDelegate { }) // Just pass in a dummy mode private constructor(context: Context) : this(context, null) init { - languageFileDirectory = MnemonicUtilities.getLanguageFileDirectory(context) setUpViewHierarchy() } @@ -57,6 +56,30 @@ class DeviceLinkingView private constructor(context: Context, attrs: AttributeSe authorizeButton.visibility = View.GONE authorizeButton.setOnClickListener { authorizePairing() } cancelButton.setOnClickListener { cancel() } + + deviceNameText.visibility = View.GONE + deviceNameText.input.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + override fun afterTextChanged(s: Editable?) { + val string = s?.toString() ?: "" + when { + string.trim().length > 30 -> { + deviceNameText.input.error = "Too Long" + enableAuthorizeButton(false) + } + else -> { + deviceNameText.input.error = null + enableAuthorizeButton(true) + } + } + } + }) + } + + private fun enableAuthorizeButton(enabled: Boolean) { + authorizeButton.isEnabled = enabled + authorizeButton.alpha = if (enabled) 1f else 0.5f } // endregion @@ -71,9 +94,10 @@ class DeviceLinkingView private constructor(context: Context, attrs: AttributeSe titleTextView.text = resources.getString(R.string.view_device_linking_title_3) explanationTextView.text = resources.getString(R.string.view_device_linking_explanation_2) mnemonicTextView.visibility = View.VISIBLE - val hexEncodedPublicKey = pairingAuthorisation.secondaryDevicePublicKey.removing05PrefixIfNeeded() - mnemonicTextView.text = MnemonicCodec(languageFileDirectory).encode(hexEncodedPublicKey).split(" ").slice(0 until 3).joinToString(" ") + mnemonicTextView.text = MnemonicUtilities.getFirst3Words(MnemonicCodec(languageFileDirectory), pairingAuthorisation.secondaryDevicePublicKey) authorizeButton.visibility = View.VISIBLE + deviceNameText.visibility = View.VISIBLE + enableAuthorizeButton(true) } fun onDeviceLinkAuthorized(pairingAuthorisation: PairingAuthorisation) { @@ -105,6 +129,7 @@ class DeviceLinkingView private constructor(context: Context, attrs: AttributeSe if (mode != Mode.Master || pairingAuthorisation == null) { return; } delegate.sendPairingAuthorizedMessage(pairingAuthorisation) delegate.handleDeviceLinkAuthorized(pairingAuthorisation) + delegate.setDeviceDisplayName(pairingAuthorisation.secondaryDevicePublicKey, deviceNameText.text.toString().trim()) dismiss?.invoke() } diff --git a/src/org/thoughtcrime/securesms/loki/DeviceLinkingViewDelegate.kt b/src/org/thoughtcrime/securesms/loki/DeviceLinkingViewDelegate.kt deleted file mode 100644 index 56c77bd9c7..0000000000 --- a/src/org/thoughtcrime/securesms/loki/DeviceLinkingViewDelegate.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.thoughtcrime.securesms.loki - -import org.whispersystems.signalservice.loki.api.PairingAuthorisation - -interface DeviceLinkingViewDelegate { - - fun handleDeviceLinkAuthorized(pairingAuthorisation: PairingAuthorisation) { } - fun handleDeviceLinkingDialogDismissed() { } - fun sendPairingAuthorizedMessage(pairingAuthorisation: PairingAuthorisation) { } -} \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt b/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt index a0fd9ae183..4414101900 100644 --- a/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.loki +import android.os.AsyncTask import android.os.Bundle import android.view.MenuItem import android.widget.Toast @@ -10,10 +11,11 @@ import network.loki.messenger.R import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.sms.MessageSender import org.thoughtcrime.securesms.util.TextSecurePreferences +import org.thoughtcrime.securesms.util.Util import org.whispersystems.signalservice.loki.api.LokiStorageAPI import org.whispersystems.signalservice.loki.api.PairingAuthorisation -class LinkedDevicesActivity : PassphraseRequiredActionBarActivity(), DeviceLinkingDialogDelegate { +class LinkedDevicesActivity : PassphraseRequiredActionBarActivity(), DeviceLinkingDelegate { companion object { private val TAG = DeviceActivity::class.java.simpleName @@ -68,7 +70,13 @@ class LinkedDevicesActivity : PassphraseRequiredActionBarActivity(), DeviceLinki } override fun sendPairingAuthorizedMessage(pairingAuthorisation: PairingAuthorisation) { - signAndSendPairingAuthorisationMessage(this, pairingAuthorisation) - this.deviceListFragment.refresh() + AsyncTask.execute { + signAndSendPairingAuthorisationMessage(this, pairingAuthorisation) + Util.runOnMain { this.deviceListFragment.refresh() } + } + } + + override fun setDeviceDisplayName(hexEncodedPublicKey: String, displayName: String) { + DatabaseFactory.getLokiUserDatabase(this).setDisplayName(hexEncodedPublicKey, displayName) } } diff --git a/src/org/thoughtcrime/securesms/loki/SeedActivity.kt b/src/org/thoughtcrime/securesms/loki/SeedActivity.kt index 87954908a6..04c788bf5e 100644 --- a/src/org/thoughtcrime/securesms/loki/SeedActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/SeedActivity.kt @@ -32,7 +32,7 @@ import org.whispersystems.signalservice.loki.utilities.retryIfNeeded import java.io.File import java.io.FileOutputStream -class SeedActivity : BaseActionBarActivity(), DeviceLinkingDialogDelegate { +class SeedActivity : BaseActionBarActivity(), DeviceLinkingDelegate { private lateinit var languageFileDirectory: File private var mode = Mode.Register set(newValue) { field = newValue; updateUI() }