Fix / Verification Request Local Echo
This commit is contained in:
parent
a673bf092d
commit
82af848c33
@ -29,12 +29,11 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
internal interface RequestVerificationDMTask : Task<RequestVerificationDMTask.Params, SendResponse> {
|
internal interface RequestVerificationDMTask : Task<RequestVerificationDMTask.Params, SendResponse> {
|
||||||
data class Params(
|
data class Params(
|
||||||
val roomId: String,
|
val event: Event,
|
||||||
val from: String,
|
|
||||||
val methods: List<String>,
|
|
||||||
val to: String,
|
|
||||||
val cryptoService: CryptoService
|
val cryptoService: CryptoService
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun createParamsAndLocalEcho(roomId: String, from: String, methods: List<String>, to: String, cryptoService: CryptoService) : Params
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class DefaultRequestVerificationDMTask @Inject constructor(
|
internal class DefaultRequestVerificationDMTask @Inject constructor(
|
||||||
@ -45,8 +44,16 @@ internal class DefaultRequestVerificationDMTask @Inject constructor(
|
|||||||
private val roomAPI: RoomAPI)
|
private val roomAPI: RoomAPI)
|
||||||
: RequestVerificationDMTask {
|
: RequestVerificationDMTask {
|
||||||
|
|
||||||
|
override fun createParamsAndLocalEcho(roomId: String, from: String, methods: List<String>, to: String, cryptoService: CryptoService): RequestVerificationDMTask.Params {
|
||||||
|
val event = localEchoEventFactory.createVerificationRequest(roomId, from, to, methods)
|
||||||
|
.also { localEchoEventFactory.saveLocalEcho(monarchy, it) }
|
||||||
|
return RequestVerificationDMTask.Params(
|
||||||
|
event,
|
||||||
|
cryptoService
|
||||||
|
)
|
||||||
|
}
|
||||||
override suspend fun execute(params: RequestVerificationDMTask.Params): SendResponse {
|
override suspend fun execute(params: RequestVerificationDMTask.Params): SendResponse {
|
||||||
val event = createRequestEvent(params)
|
val event = handleEncryption(params)
|
||||||
val localID = event.eventId!!
|
val localID = event.eventId!!
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -54,7 +61,7 @@ internal class DefaultRequestVerificationDMTask @Inject constructor(
|
|||||||
val executeRequest = executeRequest<SendResponse> {
|
val executeRequest = executeRequest<SendResponse> {
|
||||||
apiCall = roomAPI.send(
|
apiCall = roomAPI.send(
|
||||||
localID,
|
localID,
|
||||||
roomId = params.roomId,
|
roomId = event.roomId ?: "",
|
||||||
content = event.content,
|
content = event.content,
|
||||||
eventType = event.type // message or room.encrypted
|
eventType = event.type // message or room.encrypted
|
||||||
)
|
)
|
||||||
@ -67,14 +74,13 @@ internal class DefaultRequestVerificationDMTask @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun createRequestEvent(params: RequestVerificationDMTask.Params): Event {
|
private suspend fun handleEncryption(params: RequestVerificationDMTask.Params): Event {
|
||||||
val event = localEchoEventFactory.createVerificationRequest(params.roomId, params.from, params.to, params.methods)
|
val roomId = params.event.roomId ?: ""
|
||||||
.also { localEchoEventFactory.saveLocalEcho(monarchy, it) }
|
if (params.cryptoService.isRoomEncrypted(roomId)) {
|
||||||
if (params.cryptoService.isRoomEncrypted(params.roomId)) {
|
|
||||||
try {
|
try {
|
||||||
return encryptEventTask.execute(EncryptEventTask.Params(
|
return encryptEventTask.execute(EncryptEventTask.Params(
|
||||||
params.roomId,
|
roomId,
|
||||||
event,
|
params.event,
|
||||||
listOf("m.relates_to"),
|
listOf("m.relates_to"),
|
||||||
params.cryptoService
|
params.cryptoService
|
||||||
))
|
))
|
||||||
@ -82,6 +88,6 @@ internal class DefaultRequestVerificationDMTask @Inject constructor(
|
|||||||
// We said it's ok to send verification request in clear
|
// We said it's ok to send verification request in clear
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return event
|
return params.event
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,10 +34,14 @@ import javax.inject.Inject
|
|||||||
internal interface SendVerificationMessageTask : Task<SendVerificationMessageTask.Params, SendResponse> {
|
internal interface SendVerificationMessageTask : Task<SendVerificationMessageTask.Params, SendResponse> {
|
||||||
data class Params(
|
data class Params(
|
||||||
val type: String,
|
val type: String,
|
||||||
val roomId: String,
|
val event: Event,
|
||||||
val content: Content,
|
|
||||||
val cryptoService: CryptoService?
|
val cryptoService: CryptoService?
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun createParamsAndLocalEcho(type: String,
|
||||||
|
roomId: String,
|
||||||
|
content: Content,
|
||||||
|
cryptoService: CryptoService?) : Params
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class DefaultSendVerificationMessageTask @Inject constructor(
|
internal class DefaultSendVerificationMessageTask @Inject constructor(
|
||||||
@ -48,8 +52,28 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
|
|||||||
@UserId private val userId: String,
|
@UserId private val userId: String,
|
||||||
private val roomAPI: RoomAPI) : SendVerificationMessageTask {
|
private val roomAPI: RoomAPI) : SendVerificationMessageTask {
|
||||||
|
|
||||||
|
override fun createParamsAndLocalEcho(type: String, roomId: String, content: Content, cryptoService: CryptoService?): SendVerificationMessageTask.Params {
|
||||||
|
val localID = LocalEcho.createLocalEchoId()
|
||||||
|
val event = Event(
|
||||||
|
roomId = roomId,
|
||||||
|
originServerTs = System.currentTimeMillis(),
|
||||||
|
senderId = userId,
|
||||||
|
eventId = localID,
|
||||||
|
type = type,
|
||||||
|
content = content,
|
||||||
|
unsignedData = UnsignedData(age = null, transactionId = localID)
|
||||||
|
).also {
|
||||||
|
localEchoEventFactory.saveLocalEcho(monarchy, it)
|
||||||
|
}
|
||||||
|
return SendVerificationMessageTask.Params(
|
||||||
|
type,
|
||||||
|
event,
|
||||||
|
cryptoService
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun execute(params: SendVerificationMessageTask.Params): SendResponse {
|
override suspend fun execute(params: SendVerificationMessageTask.Params): SendResponse {
|
||||||
val event = createRequestEvent(params)
|
val event = handleEncryption(params)
|
||||||
val localID = event.eventId!!
|
val localID = event.eventId!!
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -57,7 +81,7 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
|
|||||||
val executeRequest = executeRequest<SendResponse> {
|
val executeRequest = executeRequest<SendResponse> {
|
||||||
apiCall = roomAPI.send(
|
apiCall = roomAPI.send(
|
||||||
localID,
|
localID,
|
||||||
roomId = params.roomId,
|
roomId = event.roomId ?: "",
|
||||||
content = event.content,
|
content = event.content,
|
||||||
eventType = event.type
|
eventType = event.type
|
||||||
)
|
)
|
||||||
@ -70,25 +94,12 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun createRequestEvent(params: SendVerificationMessageTask.Params): Event {
|
private suspend fun handleEncryption(params: SendVerificationMessageTask.Params): Event {
|
||||||
val localID = LocalEcho.createLocalEchoId()
|
if (params.cryptoService?.isRoomEncrypted(params.event.roomId ?: "") == true) {
|
||||||
val event = Event(
|
|
||||||
roomId = params.roomId,
|
|
||||||
originServerTs = System.currentTimeMillis(),
|
|
||||||
senderId = userId,
|
|
||||||
eventId = localID,
|
|
||||||
type = params.type,
|
|
||||||
content = params.content,
|
|
||||||
unsignedData = UnsignedData(age = null, transactionId = localID)
|
|
||||||
).also {
|
|
||||||
localEchoEventFactory.saveLocalEcho(monarchy, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.cryptoService?.isRoomEncrypted(params.roomId) == true) {
|
|
||||||
try {
|
try {
|
||||||
return encryptEventTask.execute(EncryptEventTask.Params(
|
return encryptEventTask.execute(EncryptEventTask.Params(
|
||||||
params.roomId,
|
params.event.roomId ?: "",
|
||||||
event,
|
params.event,
|
||||||
listOf("m.relates_to"),
|
listOf("m.relates_to"),
|
||||||
params.cryptoService
|
params.cryptoService
|
||||||
))
|
))
|
||||||
@ -96,6 +107,6 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
|
|||||||
// We said it's ok to send verification request in clear
|
// We said it's ok to send verification request in clear
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return event
|
return params.event
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,6 @@ import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
|||||||
import im.vector.matrix.android.internal.crypto.model.rest.*
|
import im.vector.matrix.android.internal.crypto.model.rest.*
|
||||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.DefaultRequestVerificationDMTask
|
import im.vector.matrix.android.internal.crypto.tasks.DefaultRequestVerificationDMTask
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.RequestVerificationDMTask
|
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
import im.vector.matrix.android.internal.session.room.send.SendResponse
|
import im.vector.matrix.android.internal.session.room.send.SendResponse
|
||||||
import im.vector.matrix.android.internal.task.TaskConstraints
|
import im.vector.matrix.android.internal.task.TaskConstraints
|
||||||
@ -538,7 +537,7 @@ internal class DefaultSasVerificationService @Inject constructor(
|
|||||||
|
|
||||||
override fun requestKeyVerificationInDMs(userId: String, roomId: String, callback: MatrixCallback<String>?) {
|
override fun requestKeyVerificationInDMs(userId: String, roomId: String, callback: MatrixCallback<String>?) {
|
||||||
requestVerificationDMTask.configureWith(
|
requestVerificationDMTask.configureWith(
|
||||||
RequestVerificationDMTask.Params(
|
requestVerificationDMTask.createParamsAndLocalEcho(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
from = credentials.deviceId ?: "",
|
from = credentials.deviceId ?: "",
|
||||||
methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS),
|
methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS),
|
||||||
|
@ -50,7 +50,7 @@ internal class SasTransportRoomMessage(
|
|||||||
Timber.d("## SAS sending msg type $type")
|
Timber.d("## SAS sending msg type $type")
|
||||||
Timber.v("## SAS sending msg info $verificationInfo")
|
Timber.v("## SAS sending msg info $verificationInfo")
|
||||||
sendVerificationMessageTask.configureWith(
|
sendVerificationMessageTask.configureWith(
|
||||||
SendVerificationMessageTask.Params(
|
sendVerificationMessageTask.createParamsAndLocalEcho(
|
||||||
type,
|
type,
|
||||||
roomId,
|
roomId,
|
||||||
verificationInfo.toEventContent()!!,
|
verificationInfo.toEventContent()!!,
|
||||||
@ -82,7 +82,7 @@ internal class SasTransportRoomMessage(
|
|||||||
override fun cancelTransaction(transactionId: String, userId: String, userDevice: String, code: CancelCode) {
|
override fun cancelTransaction(transactionId: String, userId: String, userDevice: String, code: CancelCode) {
|
||||||
Timber.d("## SAS canceling transaction $transactionId for reason $code")
|
Timber.d("## SAS canceling transaction $transactionId for reason $code")
|
||||||
sendVerificationMessageTask.configureWith(
|
sendVerificationMessageTask.configureWith(
|
||||||
SendVerificationMessageTask.Params(
|
sendVerificationMessageTask.createParamsAndLocalEcho(
|
||||||
EventType.KEY_VERIFICATION_CANCEL,
|
EventType.KEY_VERIFICATION_CANCEL,
|
||||||
roomId,
|
roomId,
|
||||||
MessageVerificationCancelContent.create(transactionId, code).toContent(),
|
MessageVerificationCancelContent.create(transactionId, code).toContent(),
|
||||||
@ -108,7 +108,7 @@ internal class SasTransportRoomMessage(
|
|||||||
|
|
||||||
override fun done(transactionId: String) {
|
override fun done(transactionId: String) {
|
||||||
sendVerificationMessageTask.configureWith(
|
sendVerificationMessageTask.configureWith(
|
||||||
SendVerificationMessageTask.Params(
|
sendVerificationMessageTask.createParamsAndLocalEcho(
|
||||||
EventType.KEY_VERIFICATION_DONE,
|
EventType.KEY_VERIFICATION_DONE,
|
||||||
roomId,
|
roomId,
|
||||||
MessageVerificationDoneContent(
|
MessageVerificationDoneContent(
|
||||||
|
@ -18,7 +18,6 @@ package im.vector.matrix.android.internal.session.room
|
|||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
|
||||||
import im.vector.matrix.android.api.session.events.model.*
|
import im.vector.matrix.android.api.session.events.model.*
|
||||||
import im.vector.matrix.android.api.session.room.model.ReferencesAggregatedContent
|
import im.vector.matrix.android.api.session.room.model.ReferencesAggregatedContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||||
@ -481,14 +480,14 @@ internal class DefaultEventRelationsAggregationTask @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateVerificationState(oldState: VerificationState?, newState: VerificationState) : VerificationState{
|
private fun updateVerificationState(oldState: VerificationState?, newState: VerificationState) : VerificationState {
|
||||||
// Cancel is always prioritary ?
|
// Cancel is always prioritary ?
|
||||||
// Eg id i found that mac or keys mismatch and send a cancel and the other send a done, i have to
|
// Eg id i found that mac or keys mismatch and send a cancel and the other send a done, i have to
|
||||||
// consider as canceled
|
// consider as canceled
|
||||||
if (newState == VerificationState.CANCELED_BY_OTHER || newState == VerificationState.CANCELED_BY_ME) {
|
if (newState == VerificationState.CANCELED_BY_OTHER || newState == VerificationState.CANCELED_BY_ME) {
|
||||||
return newState
|
return newState
|
||||||
}
|
}
|
||||||
//never move out of cancel
|
// never move out of cancel
|
||||||
if (oldState == VerificationState.CANCELED_BY_OTHER || oldState == VerificationState.CANCELED_BY_ME) {
|
if (oldState == VerificationState.CANCELED_BY_OTHER || oldState == VerificationState.CANCELED_BY_ME) {
|
||||||
return oldState
|
return oldState
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@ import im.vector.matrix.android.api.session.room.model.message.MessageVerificati
|
|||||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.internal.session.room.VerificationState
|
import im.vector.matrix.android.internal.session.room.VerificationState
|
||||||
import im.vector.matrix.android.internal.session.room.isCanceled
|
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||||
import im.vector.riotx.core.resources.ColorProvider
|
import im.vector.riotx.core.resources.ColorProvider
|
||||||
import im.vector.riotx.core.resources.UserPreferencesProvider
|
import im.vector.riotx.core.resources.UserPreferencesProvider
|
||||||
@ -91,7 +90,7 @@ class VerificationItemFactory @Inject constructor(
|
|||||||
CancelCode.MismatchedCommitment,
|
CancelCode.MismatchedCommitment,
|
||||||
CancelCode.MismatchedKeys,
|
CancelCode.MismatchedKeys,
|
||||||
CancelCode.MismatchedSas -> {
|
CancelCode.MismatchedSas -> {
|
||||||
//We should display these bad conclusions
|
// We should display these bad conclusions
|
||||||
return VerificationRequestConclusionItem_()
|
return VerificationRequestConclusionItem_()
|
||||||
.attributes(
|
.attributes(
|
||||||
VerificationRequestConclusionItem.Attributes(
|
VerificationRequestConclusionItem.Attributes(
|
||||||
@ -113,10 +112,8 @@ class VerificationItemFactory @Inject constructor(
|
|||||||
}
|
}
|
||||||
else -> ignoredConclusion(event, highlight, callback)
|
else -> ignoredConclusion(event, highlight, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
EventType.KEY_VERIFICATION_DONE -> {
|
EventType.KEY_VERIFICATION_DONE -> {
|
||||||
|
|
||||||
// Is the request referenced is actually really completed?
|
// Is the request referenced is actually really completed?
|
||||||
if (referenceInformationData.referencesInfoData?.verificationStatus != VerificationState.DONE) return ignoredConclusion(event, highlight, callback)
|
if (referenceInformationData.referencesInfoData?.verificationStatus != VerificationState.DONE) return ignoredConclusion(event, highlight, callback)
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.detail.timeline.item
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.widget.ImageView
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
import androidx.appcompat.widget.AppCompatTextView
|
import androidx.appcompat.widget.AppCompatTextView
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
@ -55,12 +56,15 @@ abstract class VerificationRequestConclusionItem : AbsBaseMessageItem<Verificati
|
|||||||
ContextCompat.getDrawable(holder.view.context, startDrawable),
|
ContextCompat.getDrawable(holder.view.context, startDrawable),
|
||||||
null, null, null
|
null, null, null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
renderSendState(holder.view, null, holder.failedToSendIndicator)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Holder : AbsBaseMessageItem.Holder(STUB_ID) {
|
class Holder : AbsBaseMessageItem.Holder(STUB_ID) {
|
||||||
val titleView by bind<AppCompatTextView>(R.id.itemVerificationDoneTitleTextView)
|
val titleView by bind<AppCompatTextView>(R.id.itemVerificationDoneTitleTextView)
|
||||||
val descriptionView by bind<AppCompatTextView>(R.id.itemVerificationDoneDetailTextView)
|
val descriptionView by bind<AppCompatTextView>(R.id.itemVerificationDoneDetailTextView)
|
||||||
val endGuideline by bind<View>(R.id.messageEndGuideline)
|
val endGuideline by bind<View>(R.id.messageEndGuideline)
|
||||||
|
val failedToSendIndicator by bind<ImageView>(R.id.messageFailToSendIndicator)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -20,6 +20,7 @@ import android.graphics.Typeface
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
|
import android.widget.ImageView
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.appcompat.widget.AppCompatTextView
|
import androidx.appcompat.widget.AppCompatTextView
|
||||||
@ -110,6 +111,8 @@ abstract class VerificationRequestItem : AbsBaseMessageItem<VerificationRequestI
|
|||||||
|
|
||||||
holder.callback = callback
|
holder.callback = callback
|
||||||
holder.attributes = attributes
|
holder.attributes = attributes
|
||||||
|
|
||||||
|
renderSendState(holder.view, null, holder.failedToSendIndicator)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun unbind(holder: Holder) {
|
override fun unbind(holder: Holder) {
|
||||||
@ -142,6 +145,7 @@ abstract class VerificationRequestItem : AbsBaseMessageItem<VerificationRequestI
|
|||||||
val endGuideline by bind<View>(R.id.messageEndGuideline)
|
val endGuideline by bind<View>(R.id.messageEndGuideline)
|
||||||
private val declineButton by bind<Button>(R.id.sas_verification_verified_decline_button)
|
private val declineButton by bind<Button>(R.id.sas_verification_verified_decline_button)
|
||||||
private val acceptButton by bind<Button>(R.id.sas_verification_verified_accept_button)
|
private val acceptButton by bind<Button>(R.id.sas_verification_verified_accept_button)
|
||||||
|
val failedToSendIndicator by bind<ImageView>(R.id.messageFailToSendIndicator)
|
||||||
|
|
||||||
override fun bindView(itemView: View) {
|
override fun bindView(itemView: View) {
|
||||||
super.bindView(itemView)
|
super.bindView(itemView)
|
||||||
|
@ -54,6 +54,17 @@
|
|||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/messageFailToSendIndicator"
|
||||||
|
android:layout_width="14dp"
|
||||||
|
android:layout_height="14dp"
|
||||||
|
android:layout_marginStart="2dp"
|
||||||
|
android:src="@drawable/ic_warning_small"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:layout_toEndOf="@+id/viewStubContainer"
|
||||||
|
android:layout_alignTop="@+id/viewStubContainer"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/informationBottom"
|
android:id="@+id/informationBottom"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user