Use in-app call overlay if system overlay permission wasn't granted

This commit is contained in:
Sylvain Berfini 2021-04-26 13:00:27 +02:00
parent 4e6eb852e7
commit f0d4b8c34e
9 changed files with 255 additions and 66 deletions

View file

@ -86,7 +86,10 @@ class CallActivity : ProximitySensorActivity() {
previewY = v.y - event.rawY
}
MotionEvent.ACTION_MOVE -> {
v.animate().x(event.rawX + previewX).y(event.rawY + previewY).setDuration(0)
v.animate()
.x(event.rawX + previewX)
.y(event.rawY + previewY)
.setDuration(0)
.start()
}
else -> {

View file

@ -25,6 +25,7 @@ import android.net.Uri
import android.os.Bundle
import android.os.Parcelable
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.view.inputmethod.InputMethodManager
import androidx.databinding.DataBindingUtil
@ -37,6 +38,7 @@ import androidx.navigation.findNavController
import com.google.android.material.snackbar.Snackbar
import java.io.UnsupportedEncodingException
import java.net.URLDecoder
import kotlin.math.abs
import kotlinx.coroutines.*
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences
@ -45,6 +47,7 @@ import org.linphone.activities.GenericActivity
import org.linphone.activities.SnackBarActivity
import org.linphone.activities.assistant.AssistantActivity
import org.linphone.activities.call.CallActivity
import org.linphone.activities.main.viewmodels.CallOverlayViewModel
import org.linphone.activities.main.viewmodels.SharedMainViewModel
import org.linphone.activities.navigateToDialer
import org.linphone.compatibility.Compatibility
@ -57,6 +60,7 @@ import org.linphone.utils.FileUtils
class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestinationChangedListener {
private lateinit var binding: MainActivityBinding
private lateinit var sharedViewModel: SharedMainViewModel
private lateinit var callOverlayViewModel: CallOverlayViewModel
private val listener = object : ContactsUpdatedListenerStub() {
override fun onContactsUpdated() {
@ -72,6 +76,12 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
private lateinit var tabsFragment: FragmentContainerView
private lateinit var statusFragment: FragmentContainerView
private var overlayX = 0f
private var overlayY = 0f
private var initPosX = 0f
private var initPosY = 0f
private var overlay: View? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -81,6 +91,9 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
sharedViewModel = ViewModelProvider(this).get(SharedMainViewModel::class.java)
binding.viewModel = sharedViewModel
callOverlayViewModel = ViewModelProvider(this).get(CallOverlayViewModel::class.java)
binding.callOverlayViewModel = callOverlayViewModel
sharedViewModel.toggleDrawerEvent.observe(this, {
it.consume {
if (binding.sideMenu.isDrawerOpen(Gravity.LEFT)) {
@ -112,6 +125,8 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
tabsFragment = findViewById(R.id.tabs_fragment)
statusFragment = findViewById(R.id.status_fragment)
initOverlay()
}
override fun onNewIntent(intent: Intent?) {
@ -403,4 +418,39 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
Log.e("[Main Activity] Failed to parse shortcut/locus id: $id")
}
}
private fun initOverlay() {
overlay = binding.root.findViewById(R.id.call_overlay)
val callOverlay = overlay
callOverlay ?: return
callOverlay.setOnTouchListener { view, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
overlayX = view.x - event.rawX
overlayY = view.y - event.rawY
initPosX = view.x
initPosY = view.y
}
MotionEvent.ACTION_MOVE -> {
view.animate()
.x(event.rawX + overlayX)
.y(event.rawY + overlayY)
.setDuration(0)
.start()
}
MotionEvent.ACTION_UP -> {
if (abs(initPosX - view.x) < 5 && abs(initPosY - view.y) < 5) {
view.performClick()
}
}
else -> return@setOnTouchListener false
}
true
}
callOverlay.setOnClickListener {
coreContext.onCallStarted()
}
}
}

View file

@ -85,7 +85,11 @@ class RecordingsFragment : MasterFragment<RecordingsFragmentBinding, RecordingsL
videoY = v.y - event.rawY
}
MotionEvent.ACTION_MOVE -> {
v.animate().x(event.rawX + videoX).y(event.rawY + videoY).setDuration(0).start()
v.animate()
.x(event.rawX + videoX)
.y(event.rawY + videoY)
.setDuration(0)
.start()
}
else -> {
v.performClick()

View file

@ -0,0 +1,74 @@
/*
* Copyright (c) 2010-2021 Belledonne Communications SARL.
*
* This file is part of linphone-android
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.linphone.activities.main.viewmodels
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.core.Call
import org.linphone.core.Core
import org.linphone.core.CoreListenerStub
class CallOverlayViewModel : ViewModel() {
val displayCallOverlay = MutableLiveData<Boolean>()
private val listener = object : CoreListenerStub() {
override fun onCallStateChanged(
core: Core,
call: Call,
state: Call.State,
message: String
) {
if (state == Call.State.IncomingReceived || state == Call.State.OutgoingInit) {
createCallOverlay()
} else if (state == Call.State.End || state == Call.State.Error || state == Call.State.Released) {
if (core.callsNb == 0) {
removeCallOverlay()
}
}
}
}
init {
displayCallOverlay.value = coreContext.core.callsNb > 0
coreContext.core.addListener(listener)
}
override fun onCleared() {
coreContext.core.removeListener(listener)
super.onCleared()
}
private fun createCallOverlay() {
// If system-wide call overlay is enabled or if already visible, abort
if (corePreferences.showCallOverlay) {
return
}
displayCallOverlay.value = true
}
private fun removeCallOverlay() {
displayCallOverlay.value = false
}
}

View file

@ -654,7 +654,7 @@ class CoreContext(val context: Context, coreConfig: Config) {
context.startActivity(intent)
}
private fun onCallStarted() {
fun onCallStarted() {
if (corePreferences.preventInterfaceFromShowingUp) {
Log.w("[Context] We were asked to not show the call screen")
return

View file

@ -1,36 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentBottom="true">
xmlns:tools="http://schemas.android.com/tools">
<!-- For proper snack bar placement -->
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinator"
<data>
<variable
name="callOverlayViewModel"
type="org.linphone.activities.main.viewmodels.CallOverlayViewModel" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toRightOf="@id/tabs_fragment">
android:layout_alignParentBottom="true">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
<!-- For proper snack bar placement -->
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/main_nav_graph"
tools:layout="@layout/dialer_fragment" />
android:layout_toRightOf="@id/tabs_fragment">
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/tabs_fragment"
android:name="org.linphone.activities.main.fragments.TabsFragment"
android:layout_width="@dimen/main_activity_tabs_fragment_size"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
tools:layout="@layout/tabs_fragment"/>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/main_nav_graph"
tools:layout="@layout/dialer_fragment" />
</RelativeLayout>
<include
android:id="@+id/call_overlay"
layout="@layout/call_overlay"
tools:visibility="@{callOverlayViewModel.displayCallOverlay}"/>
</RelativeLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/tabs_fragment"
android:name="org.linphone.activities.main.fragments.TabsFragment"
android:layout_width="@dimen/main_activity_tabs_fragment_size"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
tools:layout="@layout/tabs_fragment"/>
</RelativeLayout>
</layout>

View file

@ -1,15 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<ImageView
android:layout_width="60dp"
android:layout_height="60dp"
android:background="@drawable/background_round_primary_color"
android:contentDescription="@string/content_description_go_back_to_call"
android:adjustViewBounds="true"
android:padding="10dp"
android:src="@drawable/call_back" />
<data>
<import type="android.view.View" />
<variable
name="visibility"
type="Boolean" />
</data>
</RelativeLayout>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{visibility ? View.VISIBLE : View.GONE}">
<ImageView
android:layout_width="60dp"
android:layout_height="60dp"
android:background="@drawable/background_round_primary_color"
android:contentDescription="@string/content_description_go_back_to_call"
android:adjustViewBounds="true"
android:padding="10dp"
android:src="@drawable/call_back" />
</RelativeLayout>
</layout>

View file

@ -10,6 +10,9 @@
<variable
name="viewModel"
type="org.linphone.activities.main.viewmodels.SharedMainViewModel" />
<variable
name="callOverlayViewModel"
type="org.linphone.activities.main.viewmodels.CallOverlayViewModel" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
@ -35,7 +38,10 @@
android:layout_height="match_parent"
android:layout_below="@id/status_fragment">
<include android:id="@+id/content" layout="@layout/main_activity_content" />
<include
android:id="@+id/content"
layout="@layout/main_activity_content"
tools:callOverlayViewModel="@{callOverlayViewModel}"/>
<!-- Side Menu -->
<RelativeLayout

View file

@ -1,36 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentBottom="true">
xmlns:tools="http://schemas.android.com/tools">
<!-- For proper snack bar placement -->
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinator"
<data>
<variable
name="callOverlayViewModel"
type="org.linphone.activities.main.viewmodels.CallOverlayViewModel" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/tabs_fragment">
android:layout_alignParentBottom="true">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
<!-- For proper snack bar placement -->
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/main_nav_graph"
tools:layout="@layout/dialer_fragment" />
android:layout_above="@id/tabs_fragment">
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/tabs_fragment"
android:name="org.linphone.activities.main.fragments.TabsFragment"
android:layout_width="match_parent"
android:layout_height="@dimen/main_activity_tabs_fragment_size"
android:layout_alignParentBottom="true"
tools:layout="@layout/tabs_fragment"/>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/main_nav_graph"
tools:layout="@layout/dialer_fragment" />
</RelativeLayout>
<include
android:id="@+id/call_overlay"
layout="@layout/call_overlay"
tools:visibility="@{callOverlayViewModel.displayCallOverlay}" />
</RelativeLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/tabs_fragment"
android:name="org.linphone.activities.main.fragments.TabsFragment"
android:layout_width="match_parent"
android:layout_height="@dimen/main_activity_tabs_fragment_size"
android:layout_alignParentBottom="true"
tools:layout="@layout/tabs_fragment"/>
</RelativeLayout>
</layout>