Use newly added APIs to get real information about a content being related to an ephemeral chat message or not, and use that information to hide save/export buttons & prevent screenshots
This commit is contained in:
parent
4e852601fc
commit
4cca59a39f
11 changed files with 62 additions and 33 deletions
|
@ -11,7 +11,7 @@ Group changes to describe their impact on the project, as follows:
|
|||
Security to invite users to upgrade in case of vulnerabilities.
|
||||
|
||||
|
||||
## [6.0.0] - 2025-02-??
|
||||
## [6.0.0] - 2025-03-??
|
||||
|
||||
6.0.0 release is a complete rework of Linphone Android, with a fully redesigned UI, so it is impossible to list everything here.
|
||||
|
||||
|
@ -21,6 +21,7 @@ Group changes to describe their impact on the project, as follows:
|
|||
- Improved multi account: you'll only see history, conversations, meetings etc... related to currently selected account, and you can switch the default account in two clicks.
|
||||
- Call transfer: Blind & Attended call transfer have been merged into one: during a call, if you initiate a transfer action, either pick another call to do the attended transfer or select a contact from the list (you can input a SIP URI not already in the suggestions list) to start a blind transfer.
|
||||
- User can only send up to 12 files in a single chat message.
|
||||
- IMDNs are now only sent to the message sender, preventing huge traffic in large groups, and thus the delivery status icon for received messages is now hidden in groups (as it was in 1-1 conversations).
|
||||
- Settings: a lot of them are gone, the one that are still there have been reworked to increase user friendliness.
|
||||
- Default screen (between contacts, call history, conversations & meetings list) will change depending on where you were when the app was paused or killed, and you will return to that last visited screen on the next startup.
|
||||
- Gradle files have been migrated from Groovy to Kotlin DSL, and dependencies are now in a separated file (libs.versions.toml).
|
||||
|
@ -38,6 +39,7 @@ Group changes to describe their impact on the project, as follows:
|
|||
- Chat while in call: a shortcut to a conversation screen with the remote.
|
||||
- Chat while in a conference: if the conference has a text stream enabled, you can chat with the other participants of the conference while it lasts. At the end, you'll find the messages history in the call history (and not in the list of conversations).
|
||||
- Auto export of media to native gallery even when auto download is enabled (but still not if VFS is enabled nor for ephemeral messages).
|
||||
- Save / export document & media from ephemeral messages will be disabled, and secure policy that prevents screenshots will be enforced in file viewer even if the setting is disabled.
|
||||
- Notification showing upload/download of files shared through chat will let user know the progress and keep the app alive during that process.
|
||||
- Screen sharing in conference: only desktop app starting with 6.0 version is able to start it, but on mobiles you'll be able to see it.
|
||||
- You can choose whatever ringtone you'd like for incoming calls (in Android notification channel settings).
|
||||
|
|
|
@ -134,6 +134,8 @@ adb logcat -d | ndk-stack -sym ./libs-debug/`adb shell getprop ro.product.cpu.ab
|
|||
```
|
||||
Warning: This command won't print anything until you reproduce the crash!
|
||||
|
||||
Starting [NDK r29](https://github.com/android/ndk/wiki/Changelog-r29) you will be able to directly use the ```libs-debug.zip``` file in ```ndk-stack -sym``` argument.
|
||||
|
||||
## Create an APK with a different package name
|
||||
|
||||
Simply edit the ```app/build.gradle.kts``` file and change the value of the ```packageName``` variable.
|
||||
|
|
|
@ -235,7 +235,7 @@ open class GenericActivity : AppCompatActivity() {
|
|||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun enableWindowSecureMode(enable: Boolean) {
|
||||
protected fun enableWindowSecureMode(enable: Boolean) {
|
||||
val flags: Int = window.attributes.flags
|
||||
if ((enable && flags and WindowManager.LayoutParams.FLAG_SECURE != 0) ||
|
||||
(!enable && flags and WindowManager.LayoutParams.FLAG_SECURE == 0)
|
||||
|
|
|
@ -41,8 +41,6 @@ import org.linphone.ui.call.model.CallModel
|
|||
import org.linphone.ui.call.viewmodel.CallsViewModel
|
||||
import org.linphone.ui.call.viewmodel.CurrentCallViewModel
|
||||
import org.linphone.ui.main.adapter.ConversationsContactsAndSuggestionsListAdapter
|
||||
import org.linphone.ui.main.contacts.model.ContactNumberOrAddressClickListener
|
||||
import org.linphone.ui.main.contacts.model.ContactNumberOrAddressModel
|
||||
import org.linphone.ui.main.history.viewmodel.StartCallViewModel
|
||||
import org.linphone.utils.ConfirmationDialogModel
|
||||
import org.linphone.ui.main.model.ConversationContactOrSuggestionModel
|
||||
|
@ -75,22 +73,6 @@ class TransferCallFragment : GenericCallFragment() {
|
|||
|
||||
private var numberOrAddressPickerDialog: Dialog? = null
|
||||
|
||||
private val listener = object : ContactNumberOrAddressClickListener {
|
||||
@UiThread
|
||||
override fun onClicked(model: ContactNumberOrAddressModel) {
|
||||
val address = model.address
|
||||
if (address != null) {
|
||||
coreContext.postOnCoreThread {
|
||||
// TODO FIXME: transfer call (blind)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
override fun onLongPress(model: ContactNumberOrAddressModel) {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
|
|
|
@ -73,11 +73,19 @@ class FileViewerActivity : GenericActivity() {
|
|||
return
|
||||
}
|
||||
|
||||
val isFromEphemeralMessage = args.getBoolean("isFromEphemeralMessage", false)
|
||||
if (isFromEphemeralMessage) {
|
||||
Log.i("$TAG Displayed content is from an ephemeral chat message, force secure mode to prevent screenshots")
|
||||
// Force preventing screenshots for ephemeral messages contents
|
||||
enableWindowSecureMode(true)
|
||||
}
|
||||
|
||||
val timestamp = args.getLong("timestamp", -1)
|
||||
val preLoadedContent = args.getString("content")
|
||||
Log.i(
|
||||
"$TAG Path argument is [$path], pre loaded text content is ${if (preLoadedContent.isNullOrEmpty()) "not available" else "available, using it"}"
|
||||
)
|
||||
viewModel.isFromEphemeralMessage.value = isFromEphemeralMessage
|
||||
viewModel.loadFile(path, timestamp, preLoadedContent)
|
||||
|
||||
binding.setBackClickListener {
|
||||
|
|
|
@ -17,6 +17,7 @@ import java.io.File
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.linphone.LinphoneApplication.Companion.corePreferences
|
||||
import org.linphone.R
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.databinding.FileMediaViewerActivityBinding
|
||||
|
@ -51,6 +52,13 @@ class MediaViewerActivity : GenericActivity() {
|
|||
val model = list[position]
|
||||
viewModel.currentlyDisplayedFileName.value = model.fileName
|
||||
viewModel.currentlyDisplayedFileDateTime.value = model.dateTime
|
||||
|
||||
val isFromEphemeral = model.isFromEphemeralMessage
|
||||
viewModel.isCurrentlyDisplayedFileFromEphemeralMessage.value = isFromEphemeral
|
||||
if (!corePreferences.enableSecureMode) {
|
||||
// Force preventing screenshots for ephemeral messages contents, but allow it for others
|
||||
enableWindowSecureMode(isFromEphemeral)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -96,10 +104,17 @@ class MediaViewerActivity : GenericActivity() {
|
|||
return
|
||||
}
|
||||
|
||||
val isFromEphemeralMessage = args.getBoolean("isFromEphemeralMessage", false)
|
||||
if (isFromEphemeralMessage) {
|
||||
Log.i("$TAG Displayed content is from an ephemeral chat message, force secure mode to prevent screenshots")
|
||||
// Force preventing screenshots for ephemeral messages contents
|
||||
enableWindowSecureMode(true)
|
||||
}
|
||||
|
||||
val timestamp = args.getLong("timestamp", -1)
|
||||
val isEncrypted = args.getBoolean("isEncrypted", false)
|
||||
val originalPath = args.getString("originalPath", "")
|
||||
val isFromEphemeralMessage = args.getBoolean("isFromEphemeralMessage", false)
|
||||
Log.i("$TAG Path argument is [$path], timestamp [$timestamp], encrypted [$isEncrypted] and original path [$originalPath]")
|
||||
viewModel.initTempModel(path, timestamp, isEncrypted, originalPath, isFromEphemeralMessage)
|
||||
|
||||
val conversationId = args.getString("conversationId").orEmpty()
|
||||
|
@ -184,12 +199,6 @@ class MediaViewerActivity : GenericActivity() {
|
|||
val currentItem = binding.mediaViewPager.currentItem
|
||||
val model = if (currentItem >= 0 && currentItem < list.size) list[currentItem] else null
|
||||
if (model != null) {
|
||||
// Never do auto media export for ephemeral messages!
|
||||
if (model.isFromEphemeralMessage) {
|
||||
Log.e("$TAG Do not export media from ephemeral message!")
|
||||
return
|
||||
}
|
||||
|
||||
val filePath = model.path
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
|
|
|
@ -69,6 +69,8 @@ class FileViewModel
|
|||
|
||||
val dateTime = MutableLiveData<String>()
|
||||
|
||||
val isFromEphemeralMessage = MutableLiveData<Boolean>()
|
||||
|
||||
val exportPlainTextFileEvent: MutableLiveData<Event<String>> by lazy {
|
||||
MutableLiveData<Event<String>>()
|
||||
}
|
||||
|
|
|
@ -41,6 +41,8 @@ class MediaListViewModel
|
|||
|
||||
val currentlyDisplayedFileDateTime = MutableLiveData<String>()
|
||||
|
||||
val isCurrentlyDisplayedFileFromEphemeralMessage = MutableLiveData<Boolean>()
|
||||
|
||||
private lateinit var temporaryModel: FileModel
|
||||
|
||||
override fun beforeNotifyingChatRoomFound(sameOne: Boolean) {
|
||||
|
@ -101,8 +103,17 @@ class MediaListViewModel
|
|||
val size = mediaContent.size.toLong()
|
||||
val timestamp = mediaContent.creationTimestamp
|
||||
if (path.isNotEmpty() && name.isNotEmpty()) {
|
||||
// TODO FIXME: we don't have the ephemeral info at Content level, using the chatRoom info even if content ephemeral status may or may not be different...
|
||||
val ephemeral = chatRoom.isEphemeralEnabled
|
||||
val messageId = mediaContent.relatedChatMessageId
|
||||
val ephemeral = if (messageId != null) {
|
||||
val chatMessage = chatRoom.findMessage(messageId)
|
||||
if (chatMessage == null) {
|
||||
Log.w("$TAG Failed to find message using ID [$messageId] related to this content, can't get real info about being related to ephemeral message")
|
||||
}
|
||||
chatMessage?.isEphemeral ?: chatRoom.isEphemeralEnabled
|
||||
} else {
|
||||
Log.e("$TAG No chat message ID related to this content, can't get real info about being related to ephemeral message")
|
||||
chatRoom.isEphemeralEnabled
|
||||
}
|
||||
|
||||
val model = FileModel(path, name, size, timestamp, isEncrypted, originalPath, ephemeral)
|
||||
list.add(model)
|
||||
|
|
|
@ -79,8 +79,17 @@ class ConversationDocumentsListViewModel
|
|||
val size = documentContent.size.toLong()
|
||||
val timestamp = documentContent.creationTimestamp
|
||||
if (path.isNotEmpty() && name.isNotEmpty()) {
|
||||
// TODO FIXME: we don't have the ephemeral info at Content level, using the chatRoom info even if content ephemeral status may or may not be different...
|
||||
val ephemeral = chatRoom.isEphemeralEnabled
|
||||
val messageId = documentContent.relatedChatMessageId
|
||||
val ephemeral = if (messageId != null) {
|
||||
val chatMessage = chatRoom.findMessage(messageId)
|
||||
if (chatMessage == null) {
|
||||
Log.w("$TAG Failed to find message using ID [$messageId] related to this content, can't get real info about being related to ephemeral message")
|
||||
}
|
||||
chatMessage?.isEphemeral ?: chatRoom.isEphemeralEnabled
|
||||
} else {
|
||||
Log.e("$TAG No chat message ID related to this content, can't get real info about being related to ephemeral message")
|
||||
chatRoom.isEphemeralEnabled
|
||||
}
|
||||
|
||||
val model =
|
||||
FileModel(path, name, size, timestamp, isEncrypted, originalPath, ephemeral) {
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="@{sharedViewModel.mediaViewerFullScreenMode ? View.GONE : View.VISIBLE}"
|
||||
app:constraint_referenced_ids="top_bar_background, back, file_name, share, save, date_time"/>
|
||||
app:constraint_referenced_ids="top_bar_background, back, file_name, date_time"/>
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/top_bar_barrier"
|
||||
|
@ -108,6 +108,7 @@
|
|||
android:padding="15dp"
|
||||
android:src="@drawable/share_network"
|
||||
android:contentDescription="@string/content_description_share_file"
|
||||
android:visibility="@{sharedViewModel.mediaViewerFullScreenMode || viewModel.isCurrentlyDisplayedFileFromEphemeralMessage ? View.GONE : View.VISIBLE}"
|
||||
app:tint="@color/gray_main2_500"
|
||||
app:layout_constraintEnd_toStartOf="@id/save"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
@ -121,6 +122,7 @@
|
|||
android:padding="15dp"
|
||||
android:src="@drawable/download_simple"
|
||||
android:contentDescription="@string/content_description_save_file"
|
||||
android:visibility="@{sharedViewModel.mediaViewerFullScreenMode || viewModel.isCurrentlyDisplayedFileFromEphemeralMessage ? View.GONE : View.VISIBLE}"
|
||||
app:tint="@color/gray_main2_500"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="@{viewModel.fullScreenMode ? View.GONE : View.VISIBLE}"
|
||||
app:constraint_referenced_ids="top_bar_background, back, file_name, share, save, date_time"/>
|
||||
app:constraint_referenced_ids="top_bar_background, back, file_name, date_time"/>
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/top_bar_barrier"
|
||||
|
@ -146,6 +146,7 @@
|
|||
android:padding="15dp"
|
||||
android:src="@drawable/share_network"
|
||||
android:contentDescription="@string/content_description_share_file"
|
||||
android:visibility="@{viewModel.fullScreenMode || viewModel.isFromEphemeralMessage ? View.GONE : View.VISIBLE}"
|
||||
app:tint="@color/gray_main2_500"
|
||||
app:layout_constraintEnd_toStartOf="@id/save"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
@ -159,6 +160,7 @@
|
|||
android:padding="15dp"
|
||||
android:src="@drawable/download_simple"
|
||||
android:contentDescription="@string/content_description_save_file"
|
||||
android:visibility="@{viewModel.fullScreenMode || viewModel.isFromEphemeralMessage ? View.GONE : View.VISIBLE}"
|
||||
app:tint="@color/gray_main2_500"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue