@ -3,66 +3,163 @@ package org.thoughtcrime.securesms.loki
import android.content.Context
import android.content.Context
import android.graphics.Color
import android.graphics.Color
import android.graphics.PorterDuff
import android.graphics.PorterDuff
import android.os.Handler
import android.support.v7.app.AlertDialog
import android.support.v7.app.AlertDialog
import android.util.AttributeSet
import android.util.AttributeSet
import android.util.Log
import android.view.View
import android.widget.LinearLayout
import android.widget.LinearLayout
import kotlinx.android.synthetic.main.view_device_linking.view.*
import kotlinx.android.synthetic.main.view_device_linking.view.*
import network.loki.messenger.R
import network.loki.messenger.R
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
import org.whispersystems.signalservice.loki.utilities.removing05PrefixIfNeeded
import java.io.File
import java.io.FileOutputStream
object DeviceLinkingDialog {
object DeviceLinkingDialog {
fun show ( context : Context , mode : DeviceLinkingView . Mode ) {
fun show ( context : Context , mode : DeviceLinkingView . Mode ) {
val view = DeviceLinkingView ( context , mode )
val view = DeviceLinkingView ( context , mode )
val dialog = AlertDialog . Builder ( context ) . setView ( view ) . show ( )
val dialog = AlertDialog . Builder ( context ) . setView ( view ) . show ( )
view . onCancel = { dialog . dismiss ( ) }
view . dismiss = { dialog . dismiss ( ) }
}
}
}
}
class DeviceLinkingView private constructor ( context : Context , attrs : AttributeSet ? , defStyleAttr : Int ) : LinearLayout ( context , attrs , defStyleAttr ) {
class DeviceLinkingView private constructor ( context : Context , attrs : AttributeSet ? , defStyleAttr : Int , private val mode : Mode ) : LinearLayout ( context , attrs , defStyleAttr ) {
private lateinit var mode : Mode
private var delegate : DeviceLinkingDialogDelegate ? = null
private var delegate : DeviceLinkingDialogDelegate ? = null
var onCancel : ( ( ) -> Unit ) ? = null
private lateinit var languageFileDirectory : File
var dismiss : ( ( ) -> Unit ) ? = null
// region Types
// region Types
enum class Mode { Master , Slave }
enum class Mode { Master , Slave }
// endregion
// endregion
// region Lifecycle
// region Lifecycle
constructor ( context : Context , mode : Mode ) : this ( context , null , 0 ) {
constructor ( context : Context , mode : Mode ) : this ( context , null , 0 , mode )
this . mode = mode
private constructor ( context : Context , attrs : AttributeSet ? ) : this ( context , attrs , 0 , Mode . Master ) // Just pass in a dummy mode
}
private constructor ( context : Context , attrs : AttributeSet ? ) : this ( context , attrs , 0 )
private constructor ( context : Context ) : this ( context , null )
private constructor ( context : Context ) : this ( context , null )
init {
init {
if ( mode == Mode . Slave ) {
if ( mode == Mode . Slave ) {
if ( delegate == null ) { throw IllegalStateException ( " Missing delegate for device linking dialog in slave mode. " ) }
if ( delegate == null ) { throw IllegalStateException ( " Missing delegate for device linking dialog in slave mode. " ) }
}
}
setUpLanguageFileDirectory ( )
setUpViewHierarchy ( )
setUpViewHierarchy ( )
when ( mode ) {
when ( mode ) {
Mode . Master -> throw IllegalStateException ( ) // DeviceLinkingSession.startListeningForLinkingRequests(this)
Mode . Master -> Log . d ( " Loki " , " TODO: DeviceLinkingSession.startListeningForLinkingRequests(this)" )
Mode . Slave -> throw IllegalStateException ( ) // DeviceLinkingSession.startListeningForAuthorization(this)
Mode . Slave -> Log . d ( " Loki " , " TODO: DeviceLinkingSession.startListeningForAuthorization(this)" )
}
}
}
}
private fun setUpLanguageFileDirectory ( ) {
val languages = listOf ( " english " , " japanese " , " portuguese " , " spanish " )
val directory = File ( context . applicationInfo . dataDir )
for ( language in languages ) {
val fileName = " $language .txt "
if ( directory . list ( ) . contains ( fileName ) ) { continue }
val inputStream = context . 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
}
private fun setUpViewHierarchy ( ) {
private fun setUpViewHierarchy ( ) {
inflate ( context , R . layout . view _device _linking , this )
inflate ( context , R . layout . view _device _linking , this )
spinner . indeterminateDrawable . setColorFilter ( Color . WHITE , PorterDuff . Mode . SRC _IN )
spinner . indeterminateDrawable . setColorFilter ( Color . WHITE , PorterDuff . Mode . SRC _IN )
cancelButton . setOnClickListener { onCancel ?. invoke ( ) }
val titleID = when ( mode ) {
Mode . Master -> R . string . view _device _linking _title _1
Mode . Slave -> R . string . view _device _linking _title _2
}
titleTextView . text = resources . getString ( titleID )
val explanationID = when ( mode ) {
Mode . Master -> R . string . view _device _linking _explanation _1
Mode . Slave -> R . string . view _device _linking _explanation _2
}
explanationTextView . text = resources . getString ( explanationID )
mnemonicTextView . visibility = if ( mode == Mode . Master ) View . GONE else View . VISIBLE
if ( mode == Mode . Slave ) {
val hexEncodedPublicKey = TextSecurePreferences . getLocalNumber ( context ) . removing05PrefixIfNeeded ( )
mnemonicTextView . text = MnemonicCodec ( languageFileDirectory ) . encode ( hexEncodedPublicKey ) . split ( " " ) . slice ( 0 until 3 ) . joinToString ( " " )
}
authorizeButton . visibility = View . GONE
cancelButton . setOnClickListener { cancel ( ) }
}
}
// endregion
// endregion
// region Device Linking
// region Device Linking
private fun requestUserAuthorization ( ) { // TODO: Device link
private fun requestUserAuthorization ( ) { // TODO: deviceLink parameter
// Called by DeviceLinkingSession when a linking request has been received
// To be called by DeviceLinkingSession when a linking request has been received
// TODO: this.deviceLink = deviceLink
spinner . visibility = View . GONE
val titleTextViewLayoutParams = titleTextView . layoutParams as LayoutParams
titleTextViewLayoutParams . topMargin = toPx ( 16 , resources )
titleTextView . layoutParams = titleTextViewLayoutParams
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 = TextSecurePreferences . getLocalNumber ( context ) . removing05PrefixIfNeeded ( ) // TODO: deviceLink.slave.hexEncodedPublicKey.removing05PrefixIfNeeded()
mnemonicTextView . text = MnemonicCodec ( languageFileDirectory ) . encode ( hexEncodedPublicKey ) . split ( " " ) . slice ( 0 until 3 ) . joinToString ( " " )
authorizeButton . visibility = View . VISIBLE
}
}
private fun authorizeDeviceLink ( ) {
private fun authorizeDeviceLink ( ) {
// TODO: val deviceLink = this.deviceLink!!
// TODO: val linkingAuthorizationMessage = DeviceLinkingUtilities.getLinkingAuthorizationMessage(deviceLink)
// TODO: Send the linking authorization message
// TODO: val session = DeviceLinkingSession.current!!
// TODO: session.stopListeningForLinkingRequests()
// TODO: session.markLinkingRequestAsProcessed()
dismiss ?. invoke ( )
// TODO: val master = DeviceLink.Device(deviceLink.master.hexEncodedPublicKey, linkingAuthorizationMessage.masterSignature)
// TODO: val signedDeviceLink = DeviceLink(master, deviceLink.slave)
// TODO: LokiStorageAPI.addDeviceLink(signedDeviceLink).fail { error ->
// TODO: Log.d("Loki", "Failed to add device link due to error: $error.")
// TODO: }
}
private fun handleDeviceLinkAuthorized ( ) { // TODO: deviceLink parameter
// To be called by DeviceLinkingSession when a device link has been authorized
// TODO: val session = DeviceLinkingSession.current!!
// TODO: session.stopListeningForLinkingAuthorization()
spinner . visibility = View . GONE
val titleTextViewLayoutParams = titleTextView . layoutParams as LayoutParams
titleTextViewLayoutParams . topMargin = toPx ( 8 , resources )
titleTextView . layoutParams = titleTextViewLayoutParams
titleTextView . text = resources . getString ( R . string . view _device _linking _title _4 )
val explanationTextViewLayoutParams = explanationTextView . layoutParams as LayoutParams
explanationTextViewLayoutParams . bottomMargin = toPx ( 12 , resources )
explanationTextView . layoutParams = explanationTextViewLayoutParams
explanationTextView . text = resources . getString ( R . string . view _device _linking _explanation _3 )
titleTextView . text = resources . getString ( R . string . view _device _linking _title _4 )
mnemonicTextView . visibility = View . GONE
buttonContainer . visibility = View . GONE
// TODO: LokiStorageAPI.addDeviceLink(signedDeviceLink).fail { error ->
// TODO: Log.d("Loki", "Failed to add device link due to error: $error.")
// TODO: }
Handler ( ) . postDelayed ( {
delegate ?. handleDeviceLinkAuthorized ( )
dismiss ?. invoke ( )
} , 4000 )
}
}
// endregion
private fun handleDeviceLinkAuthorized ( ) { // TODO: Device link
// region Interaction
// Called by DeviceLinkingSession when a device link has been authorized
private fun cancel ( ) {
// TODO: val session = DeviceLinkingSession.current!!
// TODO: session.stopListeningForLinkingRequests()
// TODO: session.markLinkingRequestAsProcessed() // Only relevant in master mode
delegate ?. handleDeviceLinkingDialogDismissed ( ) // Only relevant in slave mode
dismiss ?. invoke ( )
}
}
// endregion
// endregion
}
}