diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessagesListViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessagesListViewModel.kt index 8821cd8f1..978000344 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessagesListViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessagesListViewModel.kt @@ -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") diff --git a/app/src/main/java/org/linphone/activities/main/files/fragments/TopBarFragment.kt b/app/src/main/java/org/linphone/activities/main/files/fragments/TopBarFragment.kt index f8987756d..c74b5a844 100644 --- a/app/src/main/java/org/linphone/activities/main/files/fragments/TopBarFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/files/fragments/TopBarFragment.kt @@ -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() { 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" -> { diff --git a/app/src/main/java/org/linphone/activities/voip/CallActivity.kt b/app/src/main/java/org/linphone/activities/voip/CallActivity.kt index 3cb076124..8108d3b61 100644 --- a/app/src/main/java/org/linphone/activities/voip/CallActivity.kt +++ b/app/src/main/java/org/linphone/activities/voip/CallActivity.kt @@ -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) } diff --git a/app/src/main/java/org/linphone/activities/voip/data/CallData.kt b/app/src/main/java/org/linphone/activities/voip/data/CallData.kt index 8e9d5779a..dbeaaa29b 100644 --- a/app/src/main/java/org/linphone/activities/voip/data/CallData.kt +++ b/app/src/main/java/org/linphone/activities/voip/data/CallData.kt @@ -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
(conference.participantCount) - val addresses = arrayListOf
() - 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 - } } diff --git a/app/src/main/java/org/linphone/activities/voip/viewmodels/CallsViewModel.kt b/app/src/main/java/org/linphone/activities/voip/viewmodels/CallsViewModel.kt index ec565c8c8..bfdf40f48 100644 --- a/app/src/main/java/org/linphone/activities/voip/viewmodels/CallsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/voip/viewmodels/CallsViewModel.kt @@ -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() @@ -41,6 +43,10 @@ class CallsViewModel : ViewModel() { val chatAndCallsCount = MediatorLiveData() + val askWriteExternalStoragePermissionEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + val callConnectedEvent: MutableLiveData> by lazy { MutableLiveData>() } @@ -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() diff --git a/app/src/main/java/org/linphone/activities/voip/viewmodels/ControlsViewModel.kt b/app/src/main/java/org/linphone/activities/voip/viewmodels/ControlsViewModel.kt index 7218cb3e7..2afa1cdde 100644 --- a/app/src/main/java/org/linphone/activities/voip/viewmodels/ControlsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/voip/viewmodels/ControlsViewModel.kt @@ -77,6 +77,8 @@ class ControlsViewModel : ViewModel() { val proximitySensorEnabled = MediatorLiveData() + val showTakeSnaptshotButton = MutableLiveData() + val goToConferenceParticipantsListEvent: MutableLiveData> by lazy { MutableLiveData>() } @@ -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 diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index 31e7d1b56..5ad8df3a3 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -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" -> { diff --git a/app/src/main/java/org/linphone/utils/PermissionHelper.kt b/app/src/main/java/org/linphone/utils/PermissionHelper.kt index 679a3b76a..3d3069dce 100644 --- a/app/src/main/java/org/linphone/utils/PermissionHelper.kt +++ b/app/src/main/java/org/linphone/utils/PermissionHelper.kt @@ -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) } diff --git a/app/src/main/res/drawable-xhdpi/voip_screenshot.png b/app/src/main/res/drawable-xhdpi/voip_screenshot.png new file mode 100644 index 000000000..e1880264a Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/voip_screenshot.png differ diff --git a/app/src/main/res/drawable/icon_call_screenshot.xml b/app/src/main/res/drawable/icon_call_screenshot.xml new file mode 100644 index 000000000..5aa57811f --- /dev/null +++ b/app/src/main/res/drawable/icon_call_screenshot.xml @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/voip_single_call_fragment.xml b/app/src/main/res/layout/voip_single_call_fragment.xml index 3b05f6e71..6b952af0f 100644 --- a/app/src/main/res/layout/voip_single_call_fragment.xml +++ b/app/src/main/res/layout/voip_single_call_fragment.xml @@ -142,6 +142,7 @@ app:layout_constraintTop_toTopOf="@id/background" /> + + Call is secured Call security is pending Video attachment + Take a screenshot of received video Close notification bubble Open conversation in app instead of bubble Open file in third-party app