Added back take snapshot button in video call if enabled in CorePreferences + reworked/fixed PermissionHelper.hasWriteExternalStoragePermission() for Android >= 10
This commit is contained in:
parent
5bae5bc9de
commit
7b933ad76b
12 changed files with 86 additions and 73 deletions
|
@ -27,7 +27,6 @@ import kotlin.math.max
|
|||
import org.linphone.activities.main.chat.data.EventLogData
|
||||
import org.linphone.core.*
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.mediastream.Version
|
||||
import org.linphone.utils.Event
|
||||
import org.linphone.utils.LinphoneUtils
|
||||
import org.linphone.utils.PermissionHelper
|
||||
|
@ -71,7 +70,7 @@ class ChatMessagesListViewModel(private val chatRoom: ChatRoom) : ViewModel() {
|
|||
return
|
||||
}
|
||||
|
||||
if (Version.sdkStrictlyBelow(Version.API29_ANDROID_10) && !PermissionHelper.get().hasWriteExternalStoragePermission()) {
|
||||
if (!PermissionHelper.get().hasWriteExternalStoragePermission()) {
|
||||
for (content in chatMessage.contents) {
|
||||
if (content.isFileTransfer) {
|
||||
Log.i("[Chat Messages] Android < 10 detected and WRITE_EXTERNAL_STORAGE permission isn't granted yet")
|
||||
|
|
|
@ -33,7 +33,6 @@ import org.linphone.compatibility.Compatibility
|
|||
import org.linphone.core.Content
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.databinding.FileViewerTopBarFragmentBinding
|
||||
import org.linphone.mediastream.Version
|
||||
import org.linphone.utils.FileUtils
|
||||
import org.linphone.utils.PermissionHelper
|
||||
|
||||
|
@ -90,7 +89,7 @@ class TopBarFragment : GenericFragment<FileViewerTopBarFragmentBinding>() {
|
|||
private fun exportContent(content: Content) {
|
||||
lifecycleScope.launch {
|
||||
var mediaStoreFilePath = ""
|
||||
if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10) || PermissionHelper.get().hasWriteExternalStoragePermission()) {
|
||||
if (PermissionHelper.get().hasWriteExternalStoragePermission()) {
|
||||
Log.i("[File Viewer] Exporting image through Media Store API")
|
||||
when (content.type) {
|
||||
"image" -> {
|
||||
|
|
|
@ -94,6 +94,15 @@ class CallActivity : ProximitySensorActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
callsViewModel.askWriteExternalStoragePermissionEvent.observe(
|
||||
this
|
||||
) {
|
||||
it.consume {
|
||||
Log.i("[Call Activity] Asking for WRITE_EXTERNAL_STORAGE permission to take snapshot")
|
||||
requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 1)
|
||||
}
|
||||
}
|
||||
|
||||
controlsViewModel.askPermissionEvent.observe(
|
||||
this
|
||||
) {
|
||||
|
@ -283,6 +292,11 @@ class CallActivity : ProximitySensorActivity() {
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if (requestCode == 1) {
|
||||
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
Log.i("[Call Activity] WRITE_EXTERNAL_STORAGE permission has been granted, taking snapshot")
|
||||
callsViewModel.takeSnapshot()
|
||||
}
|
||||
}
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import java.util.*
|
|||
import kotlinx.coroutines.*
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.R
|
||||
import org.linphone.compatibility.Compatibility
|
||||
import org.linphone.contact.GenericContactData
|
||||
import org.linphone.core.*
|
||||
import org.linphone.core.tools.Log
|
||||
|
@ -82,6 +83,23 @@ open class CallData(val call: Call) : GenericContactData(call.remoteAddress) {
|
|||
Log.i("[Call] Remote recording changed: $recording")
|
||||
isRemotelyRecorded.value = recording
|
||||
}
|
||||
|
||||
override fun onSnapshotTaken(call: Call, filePath: String) {
|
||||
Log.i("[Call] Snapshot taken: $filePath")
|
||||
val content = Factory.instance().createContent()
|
||||
content.filePath = filePath
|
||||
content.type = "image"
|
||||
content.subtype = "jpeg"
|
||||
content.name = filePath.substring(filePath.indexOf("/") + 1)
|
||||
|
||||
scope.launch {
|
||||
if (Compatibility.addImageToMediaStore(coreContext.context, content)) {
|
||||
Log.i("[Call] Adding snapshot ${content.name} to Media Store terminated")
|
||||
} else {
|
||||
Log.e("[Call] Something went wrong while copying file to Media Store...")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||
|
@ -149,69 +167,6 @@ open class CallData(val call: Call) : GenericContactData(call.remoteAddress) {
|
|||
contextMenuClickListener?.onShowContextMenu(anchor, this)
|
||||
}
|
||||
|
||||
private fun initChatRoom() {
|
||||
val core = coreContext.core
|
||||
val localSipUri = core.defaultAccount?.params?.identityAddress?.asStringUriOnly()
|
||||
val remoteSipUri = call.remoteAddress.asStringUriOnly()
|
||||
val conference = call.conference
|
||||
|
||||
if (localSipUri != null) {
|
||||
val localAddress = Factory.instance().createAddress(localSipUri)
|
||||
val remoteSipAddress = Factory.instance().createAddress(remoteSipUri)
|
||||
chatRoom = core.searchChatRoom(null, localAddress, remoteSipAddress, arrayOfNulls(0))
|
||||
|
||||
if (chatRoom == null) {
|
||||
Log.w("[Call] Failed to find existing chat room for local address [$localSipUri] and remote address [$remoteSipUri]")
|
||||
var chatRoomParams: ChatRoomParams? = null
|
||||
if (conference != null) {
|
||||
val params = core.createDefaultChatRoomParams()
|
||||
params.subject = conference.subject
|
||||
params.backend = ChatRoomBackend.FlexisipChat
|
||||
params.isGroupEnabled = true
|
||||
chatRoomParams = params
|
||||
}
|
||||
|
||||
chatRoom = core.searchChatRoom(
|
||||
chatRoomParams,
|
||||
localAddress,
|
||||
null,
|
||||
arrayOf(remoteSipAddress)
|
||||
)
|
||||
}
|
||||
|
||||
if (chatRoom == null) {
|
||||
val chatRoomParams = core.createDefaultChatRoomParams()
|
||||
|
||||
if (conference != null) {
|
||||
Log.w("[Call] Failed to find existing chat room with same subject & participants, creating it")
|
||||
chatRoomParams.backend = ChatRoomBackend.FlexisipChat
|
||||
chatRoomParams.isGroupEnabled = true
|
||||
chatRoomParams.subject = conference.subject
|
||||
|
||||
val participants = arrayOfNulls<Address>(conference.participantCount)
|
||||
val addresses = arrayListOf<Address>()
|
||||
for (participant in conference.participantList) {
|
||||
addresses.add(participant.address)
|
||||
}
|
||||
addresses.toArray(participants)
|
||||
|
||||
Log.i("[Call] Creating chat room with same subject [${chatRoomParams.subject}] & participants as for conference")
|
||||
chatRoom = core.createChatRoom(chatRoomParams, localAddress, participants)
|
||||
} else {
|
||||
Log.w("[Call] Failed to find existing chat room with same participants, creating it")
|
||||
// TODO: configure chat room params
|
||||
chatRoom = core.createChatRoom(chatRoomParams, localAddress, arrayOf(remoteSipAddress))
|
||||
}
|
||||
}
|
||||
|
||||
if (chatRoom == null) {
|
||||
Log.e("[Call] Failed to create a chat room for local address [$localSipUri] and remote address [$remoteSipUri]!")
|
||||
}
|
||||
} else {
|
||||
Log.e("[Call] Failed to get either local [$localSipUri] or remote [$remoteSipUri] SIP address!")
|
||||
}
|
||||
}
|
||||
|
||||
private fun isCallPaused(): Boolean {
|
||||
return when (call.state) {
|
||||
Call.State.Paused, Call.State.Pausing -> true
|
||||
|
@ -298,8 +253,4 @@ open class CallData(val call: Call) : GenericContactData(call.remoteAddress) {
|
|||
)
|
||||
Log.i("[Call] Starting 30 seconds timer to automatically decline video request")
|
||||
}
|
||||
|
||||
fun isActiveAndNotInConference(): Boolean {
|
||||
return isPaused.value == false && isRemotelyPaused.value == false && isInRemoteConference.value == false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,8 @@ import org.linphone.core.*
|
|||
import org.linphone.core.tools.Log
|
||||
import org.linphone.utils.AppUtils
|
||||
import org.linphone.utils.Event
|
||||
import org.linphone.utils.FileUtils
|
||||
import org.linphone.utils.PermissionHelper
|
||||
|
||||
class CallsViewModel : ViewModel() {
|
||||
val currentCallData = MutableLiveData<CallData>()
|
||||
|
@ -41,6 +43,10 @@ class CallsViewModel : ViewModel() {
|
|||
|
||||
val chatAndCallsCount = MediatorLiveData<Int>()
|
||||
|
||||
val askWriteExternalStoragePermissionEvent: MutableLiveData<Event<Boolean>> by lazy {
|
||||
MutableLiveData<Event<Boolean>>()
|
||||
}
|
||||
|
||||
val callConnectedEvent: MutableLiveData<Event<Call>> by lazy {
|
||||
MutableLiveData<Event<Call>>()
|
||||
}
|
||||
|
@ -158,6 +164,20 @@ class CallsViewModel : ViewModel() {
|
|||
conference?.addParticipants(core.calls)
|
||||
}
|
||||
|
||||
fun takeSnapshot() {
|
||||
if (!PermissionHelper.get().hasWriteExternalStoragePermission()) {
|
||||
askWriteExternalStoragePermissionEvent.value = Event(true)
|
||||
} else {
|
||||
if (currentCallData.value?.call?.currentParams?.isVideoEnabled == true) {
|
||||
val fileName = System.currentTimeMillis().toString() + ".jpeg"
|
||||
Log.i("[Calls] Snapshot will be save under $fileName")
|
||||
currentCallData.value?.call?.takeVideoSnapshot(FileUtils.getFileStoragePath(fileName).absolutePath)
|
||||
} else {
|
||||
Log.e("[Calls] Current call doesn't have video, can't take snapshot")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initCallList() {
|
||||
val calls = arrayListOf<CallData>()
|
||||
|
||||
|
|
|
@ -77,6 +77,8 @@ class ControlsViewModel : ViewModel() {
|
|||
|
||||
val proximitySensorEnabled = MediatorLiveData<Boolean>()
|
||||
|
||||
val showTakeSnaptshotButton = MutableLiveData<Boolean>()
|
||||
|
||||
val goToConferenceParticipantsListEvent: MutableLiveData<Event<Boolean>> by lazy {
|
||||
MutableLiveData<Event<Boolean>>()
|
||||
}
|
||||
|
@ -453,6 +455,7 @@ class ControlsViewModel : ViewModel() {
|
|||
}
|
||||
|
||||
isVideoEnabled.value = enabled
|
||||
showTakeSnaptshotButton.value = enabled && corePreferences.showScreenshotButton
|
||||
isSwitchCameraAvailable.value = enabled && coreContext.showSwitchCameraButton()
|
||||
if (coreContext.core.currentCall?.conference != null) {
|
||||
isVideoSendReceive.value = coreContext.core.currentCall?.currentParams?.videoDirection == MediaDirection.SendRecv
|
||||
|
|
|
@ -743,7 +743,7 @@ class CoreContext(val context: Context, coreConfig: Config) {
|
|||
return
|
||||
}
|
||||
|
||||
if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10) || PermissionHelper.get().hasWriteExternalStoragePermission()) {
|
||||
if (PermissionHelper.get().hasWriteExternalStoragePermission()) {
|
||||
for (content in message.contents) {
|
||||
if (content.isFile && content.filePath != null && content.userData == null) {
|
||||
addContentToMediaStore(content)
|
||||
|
@ -764,7 +764,7 @@ class CoreContext(val context: Context, coreConfig: Config) {
|
|||
return
|
||||
}
|
||||
|
||||
if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10) || PermissionHelper.get().hasWriteExternalStoragePermission()) {
|
||||
if (PermissionHelper.get().hasWriteExternalStoragePermission()) {
|
||||
coroutineScope.launch {
|
||||
when (content.type) {
|
||||
"image" -> {
|
||||
|
|
|
@ -23,6 +23,7 @@ import android.Manifest
|
|||
import android.content.Context
|
||||
import org.linphone.compatibility.Compatibility
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.mediastream.Version
|
||||
|
||||
/**
|
||||
* Helper methods to check whether a permission has been granted and log the result
|
||||
|
@ -63,6 +64,7 @@ class PermissionHelper private constructor(private val context: Context) {
|
|||
}
|
||||
|
||||
fun hasWriteExternalStoragePermission(): Boolean {
|
||||
if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10)) return true
|
||||
return hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
}
|
||||
|
||||
|
|
BIN
app/src/main/res/drawable-xhdpi/voip_screenshot.png
Normal file
BIN
app/src/main/res/drawable-xhdpi/voip_screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
11
app/src/main/res/drawable/icon_call_screenshot.xml
Normal file
11
app/src/main/res/drawable/icon_call_screenshot.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true">
|
||||
<bitmap android:src="@drawable/voip_screenshot"
|
||||
android:tint="@color/primary_color"/>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:src="@drawable/voip_screenshot"
|
||||
android:tint="@color/voip_gray_blue_color"/>
|
||||
</item>
|
||||
</selector>
|
|
@ -142,6 +142,7 @@
|
|||
app:layout_constraintTop_toTopOf="@id/background" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/record_call"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_margin="10dp"
|
||||
|
@ -155,6 +156,18 @@
|
|||
app:layout_constraintStart_toStartOf="@id/background"
|
||||
app:layout_constraintTop_toTopOf="@id/background" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_margin="10dp"
|
||||
android:background="@drawable/button_call_recording_background"
|
||||
android:contentDescription="@string/content_description_take_screenshot"
|
||||
android:onClick="@{() -> callsViewModel.takeSnapshot()}"
|
||||
android:src="@drawable/icon_call_screenshot"
|
||||
android:visibility="@{!controlsViewModel.showTakeSnaptshotButton || controlsViewModel.fullScreenMode || controlsViewModel.pipMode ? View.GONE : View.VISIBLE}"
|
||||
app:layout_constraintStart_toStartOf="@id/background"
|
||||
app:layout_constraintTop_toBottomOf="@id/record_call" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
|
|
|
@ -821,6 +821,7 @@
|
|||
<string name="content_description_call_secured">Call is secured</string>
|
||||
<string name="content_description_call_security_pending">Call security is pending</string>
|
||||
<string name="content_description_chat_message_video_attachment">Video attachment</string>
|
||||
<string name="content_description_take_screenshot">Take a screenshot of received video</string>
|
||||
<string name="content_description_close_bubble">Close notification bubble</string>
|
||||
<string name="content_description_open_app">Open conversation in app instead of bubble</string>
|
||||
<string name="content_description_export">Open file in third-party app</string>
|
||||
|
|
Loading…
Reference in a new issue