Improved & factorized call's media encryption icon & label info

This commit is contained in:
Sylvain Berfini 2024-06-20 10:17:07 +02:00
parent cad05a0c83
commit 4bd6dc4e0f
7 changed files with 208 additions and 111 deletions

View file

@ -47,7 +47,6 @@ import org.linphone.core.CoreListenerStub
import org.linphone.core.MediaDirection
import org.linphone.core.MediaEncryption
import org.linphone.core.SecurityLevel
import org.linphone.core.StreamType
import org.linphone.core.tools.Log
import org.linphone.ui.GenericViewModel
import org.linphone.ui.call.conference.viewmodel.ConferenceViewModel
@ -109,10 +108,12 @@ class CurrentCallViewModel @UiThread constructor() : GenericViewModel() {
val halfOpenedFolded = MutableLiveData<Boolean>()
val isZrtpPq = MutableLiveData<Boolean>()
val isZrtp = MutableLiveData<Boolean>()
val isZrtpSasValidationRequired = MutableLiveData<Boolean>()
val waitingForEncryptionInfo = MutableLiveData<Boolean>()
val isMediaEncrypted = MutableLiveData<Boolean>()
val hideVideo = MutableLiveData<Boolean>()
@ -238,6 +239,7 @@ class CurrentCallViewModel @UiThread constructor() : GenericViewModel() {
Log.w(
"$TAG Notified that authentication token is [${if (verified) "verified" else "not verified!"}]"
)
isZrtpSasValidationRequired.postValue(!verified)
zrtpAuthTokenVerifiedEvent.postValue(Event(verified))
}
@ -270,6 +272,7 @@ class CurrentCallViewModel @UiThread constructor() : GenericViewModel() {
"$TAG From now on current call will be [${newCurrentCall.remoteAddress.asStringUriOnly()}]"
)
configureCall(newCurrentCall)
updateEncryption()
} else {
Log.e(
"$TAG Failed to get a valid call to display, go to ended call fragment"
@ -390,6 +393,7 @@ class CurrentCallViewModel @UiThread constructor() : GenericViewModel() {
)
currentCall.removeListener(callListener)
configureCall(call)
updateEncryption()
} else if (LinphoneUtils.isCallIncoming(call.state)) {
Log.w(
"$TAG A call is being received [${call.remoteAddress.asStringUriOnly()}], using it as current call unless declined"
@ -965,7 +969,7 @@ class CurrentCallViewModel @UiThread constructor() : GenericViewModel() {
}
@WorkerThread
private fun updateEncryption(): Boolean {
private fun updateEncryption() {
when (val mediaEncryption = currentCall.currentParams.mediaEncryption) {
MediaEncryption.ZRTP -> {
val isDeviceTrusted = currentCall.authenticationTokenVerified
@ -992,12 +996,8 @@ class CurrentCallViewModel @UiThread constructor() : GenericViewModel() {
isMediaEncrypted.postValue(true)
isZrtp.postValue(true)
// When Post Quantum is available, ZRTP is Post Quantum if key exchange was made with Post Quantum algorithm
val stats = currentCall.getStats(StreamType.Audio)
isZrtpPq.postValue(
coreContext.core.postQuantumAvailable && stats?.isZrtpKeyAgreementAlgoPostQuantum == true
)
isZrtpSasValidationRequired.postValue(cacheMismatch || !isDeviceTrusted)
if (cacheMismatch || !isDeviceTrusted) {
Log.i("$TAG Showing ZRTP SAS confirmation dialog")
val tokenToRead = currentCall.localAuthenticationToken
@ -1010,23 +1010,19 @@ class CurrentCallViewModel @UiThread constructor() : GenericViewModel() {
)
}
}
return isDeviceTrusted
}
MediaEncryption.SRTP, MediaEncryption.DTLS -> {
Log.i("$TAG Current call media encryption is [$mediaEncryption]")
isMediaEncrypted.postValue(true)
isZrtp.postValue(false)
isZrtpPq.postValue(false)
}
else -> {
Log.w("$TAG Current call doesn't have any media encryption!")
isMediaEncrypted.postValue(false)
isZrtp.postValue(false)
isZrtpPq.postValue(false)
}
}
return false
waitingForEncryptionInfo.postValue(false)
}
@WorkerThread
@ -1035,6 +1031,8 @@ class CurrentCallViewModel @UiThread constructor() : GenericViewModel() {
"$TAG Configuring call with remote address [${call.remoteAddress.asStringUriOnly()}] as current"
)
contact.value?.destroy()
waitingForEncryptionInfo.postValue(true)
isMediaEncrypted.postValue(false)
terminatedByUsed = false
currentCall = call
@ -1126,8 +1124,6 @@ class CurrentCallViewModel @UiThread constructor() : GenericViewModel() {
}
}
updateEncryption()
contact.postValue(model)
displayedName.postValue(model.friend.name)

View file

@ -164,37 +164,16 @@
app:layout_constraintEnd_toEndOf="parent"
bind:ignore="UseAppTint" />
<ImageView
android:id="@+id/media_encryption_icon"
android:onClick="@{callMediaEncryptionStatisticsClickListener}"
android:layout_width="@dimen/small_icon_size"
android:layout_height="0dp"
android:layout_marginStart="10dp"
android:adjustViewBounds="true"
android:paddingTop="3dp"
android:contentDescription="@null"
android:src="@{viewModel.isZrtpPq ? @drawable/atom : @drawable/lock_simple, default=@drawable/atom}"
android:visibility="@{!viewModel.fullScreenMode &amp;&amp; !viewModel.pipMode &amp;&amp; viewModel.isMediaEncrypted ? View.VISIBLE : View.GONE}"
app:layout_constraintStart_toEndOf="@id/back"
app:layout_constraintTop_toTopOf="@id/media_encryption_label"
app:layout_constraintBottom_toBottomOf="@id/media_encryption_label"
app:tint="@color/blue_info_500" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/media_encryption_label"
android:onClick="@{callMediaEncryptionStatisticsClickListener}"
android:layout_width="wrap_content"
<include
android:id="@+id/call_media_encryption_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@{viewModel.isZrtpPq ? @string/call_post_quantum_zrtp_end_to_end_encrypted : @string/call_zrtp_end_to_end_encrypted, default=@string/call_post_quantum_zrtp_end_to_end_encrypted}"
android:textSize="12sp"
android:textColor="@color/blue_info_500"
android:maxLines="1"
android:ellipsize="end"
android:visibility="@{!viewModel.fullScreenMode &amp;&amp; !viewModel.pipMode &amp;&amp; viewModel.isMediaEncrypted ? View.VISIBLE : View.GONE}"
app:layout_constraintStart_toEndOf="@id/media_encryption_icon"
app:layout_constraintTop_toBottomOf="@id/conference_subject"/>
layout="@layout/call_media_encryption_info"
bind:viewModel="@{viewModel}"
bind:callMediaEncryptionStatisticsClickListener="@{callMediaEncryptionStatisticsClickListener}"
app:layout_constraintTop_toBottomOf="@id/conference_subject"
app:layout_constraintStart_toEndOf="@id/back"
app:layout_constraintEnd_toEndOf="parent"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_300"
@ -277,7 +256,7 @@
android:src="@drawable/record_fill"
android:contentDescription="@string/content_description_call_is_being_recorded"
android:visibility="@{viewModel.isRecording ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintTop_toBottomOf="@id/media_encryption_icon"
app:layout_constraintTop_toBottomOf="@id/call_media_encryption_info"
app:layout_constraintStart_toStartOf="parent"
app:tint="?attr/color_danger_500" />

View file

@ -67,7 +67,7 @@
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/media_encryption_icon"
app:layout_constraintTop_toBottomOf="@id/call_media_encryption_info"
app:layout_constraintBottom_toTopOf="@id/display_name" />
<androidx.appcompat.widget.AppCompatTextView
@ -229,37 +229,16 @@
app:layout_constraintEnd_toEndOf="parent"
bind:ignore="UseAppTint" />
<ImageView
android:id="@+id/media_encryption_icon"
android:onClick="@{callMediaEncryptionStatisticsClickListener}"
android:layout_width="@dimen/small_icon_size"
android:layout_height="0dp"
android:layout_marginStart="10dp"
android:adjustViewBounds="true"
android:paddingTop="3dp"
android:contentDescription="@null"
android:src="@{viewModel.isZrtpPq ? @drawable/atom : viewModel.isZrtp ? @drawable/lock_key : @drawable/lock_simple, default=@drawable/atom}"
android:visibility="@{!viewModel.fullScreenMode &amp;&amp; !viewModel.pipMode &amp;&amp; viewModel.isMediaEncrypted ? View.VISIBLE : View.GONE}"
app:layout_constraintStart_toEndOf="@id/back"
app:layout_constraintTop_toTopOf="@id/media_encryption_label"
app:layout_constraintBottom_toBottomOf="@id/media_encryption_label"
app:tint="@color/blue_info_500" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/media_encryption_label"
android:onClick="@{callMediaEncryptionStatisticsClickListener}"
android:layout_width="wrap_content"
<include
android:id="@+id/call_media_encryption_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@{viewModel.isZrtpPq ? @string/call_post_quantum_zrtp_end_to_end_encrypted : viewModel.isZrtp ? @string/call_zrtp_end_to_end_encrypted : @string/call_srtp_point_to_point_encrypted, default=@string/call_post_quantum_zrtp_end_to_end_encrypted}"
android:textSize="12sp"
android:textColor="@color/blue_info_500"
android:maxLines="1"
android:ellipsize="end"
android:visibility="@{!viewModel.fullScreenMode &amp;&amp; !viewModel.pipMode &amp;&amp; viewModel.isMediaEncrypted ? View.VISIBLE : View.GONE}"
app:layout_constraintStart_toEndOf="@id/media_encryption_icon"
app:layout_constraintTop_toBottomOf="@id/name"/>
layout="@layout/call_media_encryption_info"
bind:viewModel="@{viewModel}"
bind:callMediaEncryptionStatisticsClickListener="@{callMediaEncryptionStatisticsClickListener}"
app:layout_constraintTop_toBottomOf="@id/name"
app:layout_constraintStart_toEndOf="@id/back"
app:layout_constraintEnd_toEndOf="parent"/>
<org.linphone.ui.call.view.RoundCornersTextureView
android:id="@+id/local_preview_video_surface"
@ -285,7 +264,7 @@
android:src="@drawable/record_fill"
android:contentDescription="@string/content_description_call_is_being_recorded"
android:visibility="@{viewModel.isRecording ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintTop_toBottomOf="@id/media_encryption_icon"
app:layout_constraintTop_toBottomOf="@id/call_media_encryption_info"
app:layout_constraintStart_toStartOf="parent"
app:tint="?attr/color_danger_500" />

View file

@ -60,35 +60,15 @@
app:layout_constraintTop_toTopOf="@id/call_direction_label"
app:layout_constraintBottom_toBottomOf="@id/call_direction_label"/>
<ImageView
android:id="@+id/media_encryption_icon"
android:layout_width="@dimen/small_icon_size"
android:layout_height="0dp"
android:layout_marginStart="10dp"
android:adjustViewBounds="true"
android:paddingTop="3dp"
android:contentDescription="@null"
android:src="@{viewModel.isZrtpPq ? @drawable/atom : @drawable/lock_simple, default=@drawable/atom}"
android:visibility="@{!viewModel.fullScreenMode &amp;&amp; !viewModel.pipMode &amp;&amp; viewModel.isMediaEncrypted ? View.VISIBLE : View.GONE}"
app:layout_constraintStart_toEndOf="@id/back"
app:layout_constraintTop_toTopOf="@id/media_encryption_label"
app:layout_constraintBottom_toBottomOf="@id/media_encryption_label"
app:tint="@color/blue_info_500" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/media_encryption_label"
android:layout_width="wrap_content"
<include
android:id="@+id/call_media_encryption_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@{viewModel.isZrtpPq ? @string/call_post_quantum_zrtp_end_to_end_encrypted : @string/call_zrtp_end_to_end_encrypted, default=@string/call_post_quantum_zrtp_end_to_end_encrypted}"
android:textSize="12sp"
android:textColor="@color/blue_info_500"
android:maxLines="1"
android:ellipsize="end"
android:visibility="@{!viewModel.fullScreenMode &amp;&amp; !viewModel.pipMode &amp;&amp; viewModel.isMediaEncrypted ? View.VISIBLE : View.GONE}"
app:layout_constraintStart_toEndOf="@id/media_encryption_icon"
app:layout_constraintTop_toBottomOf="@id/call_direction_label"/>
layout="@layout/call_media_encryption_info"
bind:viewModel="@{viewModel}"
app:layout_constraintTop_toBottomOf="@id/name"
app:layout_constraintStart_toEndOf="@id/back"
app:layout_constraintEnd_toEndOf="parent"/>
<include
android:id="@+id/avatar"
@ -102,7 +82,7 @@
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/media_encryption_icon"
app:layout_constraintTop_toBottomOf="@id/call_media_encryption_info"
app:layout_constraintBottom_toTopOf="@id/name" />
<androidx.appcompat.widget.AppCompatTextView

View file

@ -0,0 +1,157 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View" />
<variable
name="callMediaEncryptionStatisticsClickListener"
type="View.OnClickListener" />
<variable
name="viewModel"
type="org.linphone.ui.call.viewmodel.CurrentCallViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="10dp">
<androidx.constraintlayout.widget.Group
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="wait_for_encryption_info_icon, wait_for_encryption_info_label"
android:visibility="@{!viewModel.fullScreenMode &amp;&amp; !viewModel.pipMode &amp;&amp; viewModel.waitingForEncryptionInfo ? View.VISIBLE : View.GONE}" />
<androidx.constraintlayout.widget.Group
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="no_media_encryption_icon, no_media_encryption_label"
android:visibility="@{!viewModel.fullScreenMode &amp;&amp; !viewModel.pipMode &amp;&amp; !viewModel.waitingForEncryptionInfo &amp;&amp; !viewModel.isMediaEncrypted ? View.VISIBLE : View.GONE, default=gone}" />
<androidx.constraintlayout.widget.Group
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="sas_validation_required_icon, sas_validation_required_label"
android:visibility="@{!viewModel.fullScreenMode &amp;&amp; !viewModel.pipMode &amp;&amp; !viewModel.waitingForEncryptionInfo &amp;&amp; viewModel.isZrtpSasValidationRequired ? View.VISIBLE : View.GONE, default=gone}" />
<androidx.constraintlayout.widget.Group
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="media_encryption_icon, media_encryption_label"
android:visibility="@{!viewModel.fullScreenMode &amp;&amp; !viewModel.pipMode &amp;&amp; !viewModel.waitingForEncryptionInfo &amp;&amp; viewModel.isMediaEncrypted &amp;&amp; !viewModel.isZrtpSasValidationRequired ? View.VISIBLE : View.GONE, default=gone}" />
<ImageView
android:id="@+id/wait_for_encryption_info_icon"
android:layout_width="@dimen/small_icon_size"
android:layout_height="0dp"
android:adjustViewBounds="true"
android:paddingTop="3dp"
android:contentDescription="@null"
android:src="@drawable/animated_in_progress"
animatedDrawable="@{true}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/wait_for_encryption_info_label"
app:layout_constraintBottom_toBottomOf="@id/wait_for_encryption_info_label"
app:tint="@color/white" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/wait_for_encryption_info_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@string/call_waiting_for_encryption_info"
android:textSize="12sp"
android:textColor="@color/white"
android:maxLines="1"
android:ellipsize="end"
app:layout_constraintStart_toEndOf="@id/wait_for_encryption_info_icon"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/no_media_encryption_icon"
android:layout_width="@dimen/small_icon_size"
android:layout_height="0dp"
android:adjustViewBounds="true"
android:paddingTop="3dp"
android:contentDescription="@null"
android:src="@drawable/lock_simple_open_bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/no_media_encryption_label"
app:layout_constraintBottom_toBottomOf="@id/no_media_encryption_label"
app:tint="@color/white" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/no_media_encryption_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@string/call_not_encrypted"
android:textSize="12sp"
android:textColor="@color/white"
android:maxLines="1"
android:ellipsize="end"
app:layout_constraintStart_toEndOf="@id/no_media_encryption_icon"
app:layout_constraintTop_toBottomOf="@id/wait_for_encryption_info_label"/>
<ImageView
android:id="@+id/sas_validation_required_icon"
android:onClick="@{callMediaEncryptionStatisticsClickListener}"
android:layout_width="@dimen/small_icon_size"
android:layout_height="0dp"
android:adjustViewBounds="true"
android:paddingTop="3dp"
android:contentDescription="@null"
android:src="@drawable/warning_circle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/sas_validation_required_label"
app:layout_constraintBottom_toBottomOf="@id/sas_validation_required_label"
app:tint="@color/orange_warning_600_night" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/sas_validation_required_label"
android:onClick="@{callMediaEncryptionStatisticsClickListener}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@string/call_zrtp_sas_validation_required"
android:textSize="12sp"
android:textColor="@color/orange_warning_600_night"
android:maxLines="1"
android:ellipsize="end"
app:layout_constraintStart_toEndOf="@id/sas_validation_required_icon"
app:layout_constraintTop_toBottomOf="@id/no_media_encryption_label"/>
<ImageView
android:id="@+id/media_encryption_icon"
android:onClick="@{callMediaEncryptionStatisticsClickListener}"
android:layout_width="@dimen/small_icon_size"
android:layout_height="0dp"
android:adjustViewBounds="true"
android:paddingTop="3dp"
android:contentDescription="@null"
android:src="@{viewModel.isZrtp ? @drawable/lock_key : @drawable/lock_simple, default=@drawable/lock_key}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/media_encryption_label"
app:layout_constraintBottom_toBottomOf="@id/media_encryption_label"
app:tint="@color/blue_info_500" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/media_encryption_label"
android:onClick="@{callMediaEncryptionStatisticsClickListener}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@{viewModel.isZrtp ? @string/call_zrtp_end_to_end_encrypted : @string/call_srtp_point_to_point_encrypted, default=@string/call_zrtp_end_to_end_encrypted}"
android:textSize="12sp"
android:textColor="@color/blue_info_500"
android:maxLines="1"
android:ellipsize="end"
app:layout_constraintStart_toEndOf="@id/media_encryption_icon"
app:layout_constraintTop_toBottomOf="@id/sas_validation_required_label"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -571,10 +571,13 @@
<string name="call_state_paused_by_remote">Mis en pause par le correpondant</string>
<string name="call_state_resuming">Reprise…</string>
<string name="call_state_ended">Terminé</string>
<string name="call_waiting_for_encryption_info">En attente du chiffrement…</string>
<string name="call_zrtp_end_to_end_encrypted">Appel chiffré de bout en bout</string>
<string name="call_post_quantum_zrtp_end_to_end_encrypted">Appel chiffré de bout en bout en post-quantique</string>
<string name="call_do_zrtp_sas_validation_again">Faire la validation à nouveau</string>
<string name="call_do_zrtp_sas_validation_again">Faire la vérification à nouveau</string>
<string name="call_zrtp_sas_validation_required">Vérification nécessaire</string>
<string name="call_srtp_point_to_point_encrypted">Appel chiffré de point à point</string>
<string name="call_not_encrypted">Appel non chiffré</string>
<string name="calls_list_title">Liste des appels</string>
<string name="call_transfer_title">Transférer l\'appel vers</string>
<string name="call_remote_is_recording">%s est en train d\'enregistrer</string>
@ -611,7 +614,7 @@
<string name="call_stats_zrtp_sas_algo">Algorithme SAS : %s</string>
<string name="call_history_deleted_toast">L\'historique des appels a été supprimé</string>
<string name="call_can_be_trusted_toast">Cet appel est complètement sécurisé</string>
<string name="call_can_be_trusted_toast">Appareil vérifié</string>
<string name="call_can_not_be_trusted_alert_toast">Problème de sécurité !</string>
<string name="call_transfer_in_progress_toast">Appel en cours de transfert</string>
<string name="call_transfer_successful_toast">L\'appel a été transferré</string>

View file

@ -608,10 +608,13 @@
<string name="call_state_paused_by_remote">Paused by remote</string>
<string name="call_state_resuming">Resuming…</string>
<string name="call_state_ended">Ended</string>
<string name="call_waiting_for_encryption_info">Waiting for encryption…</string>
<string name="call_zrtp_end_to_end_encrypted">End-to-end encrypted by ZRTP</string>
<string name="call_post_quantum_zrtp_end_to_end_encrypted">End-to-end encrypted by post-quantum ZRTP</string>
<string name="call_do_zrtp_sas_validation_again">Validate ZRTP SAS again</string>
<string name="call_zrtp_sas_validation_required">Validation required</string>
<string name="call_srtp_point_to_point_encrypted">Point-to-point encrypted by SRTP</string>
<string name="call_not_encrypted">Call is not encrypted</string>
<string name="calls_list_title">Calls list</string>
<string name="call_transfer_title">Transfer call to</string>
<string name="call_remote_is_recording">%s is recording</string>
@ -648,7 +651,7 @@
<string name="call_stats_zrtp_sas_algo">SAS algorithm: %s</string>
<string name="call_history_deleted_toast">History has been deleted</string>
<string name="call_can_be_trusted_toast">This call can be trusted</string>
<string name="call_can_be_trusted_toast">Device validated</string>
<string name="call_can_not_be_trusted_alert_toast">Security alert !</string>
<string name="call_transfer_in_progress_toast">Call is being transferred</string>
<string name="call_transfer_successful_toast">Call has been successfully transferred</string>