rename to Red
show redirection destination fix dialer end call add priority Signal > Telegram
This commit is contained in:
parent
9b3c582337
commit
48a9998c26
29 changed files with 291 additions and 219 deletions
28
README.md
28
README.md
|
@ -1,37 +1,35 @@
|
||||||
# Re
|
# Red
|
||||||
|
|
||||||
Redirect outgoing calls to Signal or Telegram.
|
Redirect outgoing calls to Signal/Telegram.
|
||||||
|
|
||||||
[<img
|
[<img
|
||||||
src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||||
alt="Get it on F-Droid"
|
alt="Get it on F-Droid"
|
||||||
height="80">](https://f-droid.org/packages/me.lucky.re/)
|
height="80">](https://f-droid.org/packages/me.lucky.red/)
|
||||||
|
[<img
|
||||||
[comment]: <> ([<img)
|
src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png"
|
||||||
|
alt="Get it on Google Play"
|
||||||
[comment]: <> ( src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png")
|
height="80">](https://play.google.com/store/apps/details?id=me.lucky.red)
|
||||||
|
|
||||||
[comment]: <> ( alt="Get it on Google Play")
|
|
||||||
|
|
||||||
[comment]: <> ( height="80">](https://play.google.com/store/apps/details?id=me.lucky.re))
|
|
||||||
|
|
||||||
<img
|
<img
|
||||||
src="https://raw.githubusercontent.com/x13a/Re/main/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png"
|
src="https://raw.githubusercontent.com/x13a/Red/main/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png"
|
||||||
width="30%"
|
width="30%"
|
||||||
height="30%">
|
height="30%">
|
||||||
|
|
||||||
Tiny app to redirect outgoing calls to Signal or Telegram if available.
|
Tiny app to redirect outgoing calls to Signal/Telegram if available.
|
||||||
|
|
||||||
You can cancel redirection by clicking on `Redirecting` popup.
|
You can cancel redirection by clicking on `Redirecting to..` popup.
|
||||||
|
|
||||||
## Permissions
|
## Permissions
|
||||||
|
|
||||||
* ACCESS_NETWORK_STATE - check internet is available
|
* ACCESS_NETWORK_STATE - check internet is available
|
||||||
* CALL_PHONE - make a call via messenger
|
* CALL_PHONE - make a call via messenger
|
||||||
* READ_CONTACTS - check contact has a messenger record
|
* READ_CONTACTS - check contact has a messenger record
|
||||||
* SYSTEM_ALERT_WINDOW - show redirecting popup and launch activity from background
|
* SYSTEM_ALERT_WINDOW - show redirecting popup and launch an activity from background
|
||||||
* CALL_REDIRECTION - process outgoing call
|
* CALL_REDIRECTION - process outgoing call
|
||||||
|
|
||||||
|
All permissions are mandatory.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,11 @@ android {
|
||||||
compileSdk 32
|
compileSdk 32
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "me.lucky.re"
|
applicationId "me.lucky.red"
|
||||||
minSdk 29
|
minSdk 29
|
||||||
targetSdk 32
|
targetSdk 32
|
||||||
versionCode 1
|
versionCode 2
|
||||||
versionName "1.0.0"
|
versionName "1.0.1"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package me.lucky.re
|
package me.lucky.red
|
||||||
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
@ -19,6 +19,6 @@ class ExampleInstrumentedTest {
|
||||||
fun useAppContext() {
|
fun useAppContext() {
|
||||||
// Context of the app under test.
|
// Context of the app under test.
|
||||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
assertEquals("me.lucky.re", appContext.packageName)
|
assertEquals("me.lucky.red", appContext.packageName)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="me.lucky.re">
|
package="me.lucky.red">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.CALL_PHONE" />
|
<uses-permission android:name="android.permission.CALL_PHONE" />
|
||||||
|
@ -9,14 +9,12 @@
|
||||||
<uses-feature android:name="android.hardware.telephony" android:required="true" />
|
<uses-feature android:name="android.hardware.telephony" android:required="true" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
|
||||||
android:fullBackupContent="true"
|
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:name=".Application"
|
android:name=".Application"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.Re">
|
android:theme="@style/Theme.Red">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
package me.lucky.re
|
|
||||||
|
|
||||||
import android.database.Cursor
|
|
||||||
import android.net.ConnectivityManager
|
|
||||||
import android.net.NetworkCapabilities
|
|
||||||
import android.net.Uri
|
|
||||||
import android.provider.ContactsContract
|
|
||||||
import android.telecom.CallRedirectionService
|
|
||||||
import android.telecom.PhoneAccountHandle
|
|
||||||
|
|
||||||
class CallRedirectionService : CallRedirectionService() {
|
|
||||||
companion object {
|
|
||||||
private const val SIGNAL_MIMETYPE = "vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.call"
|
|
||||||
private const val TELEGRAM_MIMETYPE = "vnd.android.cursor.item/vnd.org.telegram.messenger.android.call"
|
|
||||||
}
|
|
||||||
|
|
||||||
private lateinit var prefs: Preferences
|
|
||||||
private lateinit var dialog: DialogWindow
|
|
||||||
private var connectivityManager: ConnectivityManager? = null
|
|
||||||
|
|
||||||
override fun onCreate() {
|
|
||||||
super.onCreate()
|
|
||||||
prefs = Preferences(this)
|
|
||||||
dialog = DialogWindow(this)
|
|
||||||
connectivityManager = getSystemService(ConnectivityManager::class.java)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPlaceCall(
|
|
||||||
handle: Uri,
|
|
||||||
initialPhoneAccount: PhoneAccountHandle,
|
|
||||||
allowInteractiveResponse: Boolean,
|
|
||||||
) {
|
|
||||||
if (!prefs.isServiceEnabled || !hasInternet()) {
|
|
||||||
placeCallUnmodified()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val uri = getUriFromPhoneNumber(handle.schemeSpecificPart)
|
|
||||||
if (uri != null) {
|
|
||||||
dialog.show(uri)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
placeCallUnmodified()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getContactIdByPhoneNumber(phoneNumber: String): String? {
|
|
||||||
var result: String? = null
|
|
||||||
val cursor: Cursor?
|
|
||||||
try {
|
|
||||||
cursor = contentResolver.query(
|
|
||||||
Uri.withAppendedPath(
|
|
||||||
ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
|
|
||||||
Uri.encode(phoneNumber)
|
|
||||||
),
|
|
||||||
arrayOf(ContactsContract.PhoneLookup._ID),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
)
|
|
||||||
} catch (exc: SecurityException) { return null }
|
|
||||||
cursor?.apply {
|
|
||||||
if (moveToFirst()) {
|
|
||||||
result = getString(getColumnIndexOrThrow(ContactsContract.PhoneLookup._ID))
|
|
||||||
}
|
|
||||||
close()
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getUriFromPhoneNumber(phoneNumber: String): Uri? {
|
|
||||||
val contactId = getContactIdByPhoneNumber(phoneNumber) ?: return null
|
|
||||||
var result: Uri? = null
|
|
||||||
val cursor: Cursor?
|
|
||||||
try {
|
|
||||||
cursor = contentResolver.query(
|
|
||||||
ContactsContract.Data.CONTENT_URI,
|
|
||||||
arrayOf(ContactsContract.Data._ID),
|
|
||||||
"${ContactsContract.Data.CONTACT_ID} = ? AND " +
|
|
||||||
"${ContactsContract.Data.MIMETYPE} IN (?, ?)",
|
|
||||||
arrayOf(contactId, SIGNAL_MIMETYPE, TELEGRAM_MIMETYPE),
|
|
||||||
null,
|
|
||||||
)
|
|
||||||
} catch (exc: SecurityException) { return null }
|
|
||||||
cursor?.apply {
|
|
||||||
if (moveToFirst()) {
|
|
||||||
result = Uri.withAppendedPath(
|
|
||||||
ContactsContract.Data.CONTENT_URI,
|
|
||||||
Uri.encode(getString(getColumnIndexOrThrow(ContactsContract.Data._ID))),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
close()
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun hasInternet(): Boolean {
|
|
||||||
return connectivityManager
|
|
||||||
?.getNetworkCapabilities(connectivityManager?.activeNetwork)
|
|
||||||
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) ?: false
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
package me.lucky.re
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.graphics.PixelFormat
|
|
||||||
import android.net.Uri
|
|
||||||
import android.view.Gravity
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.WindowManager
|
|
||||||
import java.util.*
|
|
||||||
import kotlin.concurrent.timerTask
|
|
||||||
|
|
||||||
class DialogWindow(private val service: CallRedirectionService) {
|
|
||||||
companion object {
|
|
||||||
private const val CANCEL_DELAY = 3000L
|
|
||||||
}
|
|
||||||
|
|
||||||
private val windowManager = service
|
|
||||||
.applicationContext
|
|
||||||
.getSystemService(WindowManager::class.java)
|
|
||||||
@Suppress("InflateParams")
|
|
||||||
private val floatView = LayoutInflater
|
|
||||||
.from(service.applicationContext)
|
|
||||||
.inflate(R.layout.popup, null)
|
|
||||||
private val layoutParams: WindowManager.LayoutParams = WindowManager.LayoutParams().apply {
|
|
||||||
format = PixelFormat.TRANSLUCENT
|
|
||||||
flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|
|
||||||
type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
|
|
||||||
gravity = Gravity.BOTTOM
|
|
||||||
width = WindowManager.LayoutParams.WRAP_CONTENT
|
|
||||||
height = WindowManager.LayoutParams.WRAP_CONTENT
|
|
||||||
y = 384
|
|
||||||
}
|
|
||||||
private var data: Uri? = null
|
|
||||||
private var timer: Timer? = null
|
|
||||||
|
|
||||||
init {
|
|
||||||
floatView.setOnClickListener {
|
|
||||||
timer?.cancel()
|
|
||||||
service.placeCallUnmodified()
|
|
||||||
remove()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun show(uri: Uri?) {
|
|
||||||
remove()
|
|
||||||
timer?.cancel()
|
|
||||||
timer = Timer()
|
|
||||||
timer?.schedule(timerTask {
|
|
||||||
service.cancelCall()
|
|
||||||
remove()
|
|
||||||
Intent(Intent.ACTION_VIEW).also {
|
|
||||||
it.data = data
|
|
||||||
it.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
|
||||||
service.startActivity(it)
|
|
||||||
}
|
|
||||||
data = null
|
|
||||||
}, CANCEL_DELAY)
|
|
||||||
data = uri
|
|
||||||
windowManager?.addView(floatView, layoutParams)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun remove() {
|
|
||||||
try {
|
|
||||||
windowManager?.removeView(floatView)
|
|
||||||
} catch (exc: IllegalArgumentException) {}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
package me.lucky.re
|
package me.lucky.red
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import com.google.android.material.color.DynamicColors
|
import com.google.android.material.color.DynamicColors
|
124
app/src/main/java/me/lucky/red/CallRedirectionService.kt
Normal file
124
app/src/main/java/me/lucky/red/CallRedirectionService.kt
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
package me.lucky.red
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.net.ConnectivityManager
|
||||||
|
import android.net.NetworkCapabilities
|
||||||
|
import android.net.Uri
|
||||||
|
import android.provider.ContactsContract
|
||||||
|
import android.telecom.CallRedirectionService
|
||||||
|
import android.telecom.PhoneAccountHandle
|
||||||
|
import androidx.annotation.RequiresPermission
|
||||||
|
|
||||||
|
class CallRedirectionService : CallRedirectionService() {
|
||||||
|
companion object {
|
||||||
|
private const val SIGNAL_MIMETYPE =
|
||||||
|
"vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.call"
|
||||||
|
private const val TELEGRAM_MIMETYPE =
|
||||||
|
"vnd.android.cursor.item/vnd.org.telegram.messenger.android.call"
|
||||||
|
private val MIMETYPES = mapOf(
|
||||||
|
SIGNAL_MIMETYPE to 0,
|
||||||
|
TELEGRAM_MIMETYPE to 1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var prefs: Preferences
|
||||||
|
private lateinit var window: PopupWindow
|
||||||
|
private var connectivityManager: ConnectivityManager? = null
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun init() {
|
||||||
|
prefs = Preferences(this)
|
||||||
|
window = PopupWindow(this)
|
||||||
|
connectivityManager = getSystemService(ConnectivityManager::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPlaceCall(
|
||||||
|
handle: Uri,
|
||||||
|
initialPhoneAccount: PhoneAccountHandle,
|
||||||
|
allowInteractiveResponse: Boolean,
|
||||||
|
) {
|
||||||
|
if (!prefs.isServiceEnabled || !hasInternet() || !allowInteractiveResponse) {
|
||||||
|
placeCallUnmodified()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val records: Array<Record>
|
||||||
|
try {
|
||||||
|
records = getRecordsFromPhoneNumber(handle.schemeSpecificPart)
|
||||||
|
} catch (exc: SecurityException) {
|
||||||
|
placeCallUnmodified()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val record = records.minByOrNull { MIMETYPES[it.mimetype] ?: 0 }
|
||||||
|
if (record == null) {
|
||||||
|
placeCallUnmodified()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
window.show(record.uri, when (record.mimetype) {
|
||||||
|
SIGNAL_MIMETYPE -> R.string.signal
|
||||||
|
TELEGRAM_MIMETYPE -> R.string.telegram
|
||||||
|
else -> return
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresPermission(Manifest.permission.READ_CONTACTS)
|
||||||
|
private fun getContactIdByPhoneNumber(phoneNumber: String): String? {
|
||||||
|
var result: String? = null
|
||||||
|
val cursor = contentResolver.query(
|
||||||
|
Uri.withAppendedPath(
|
||||||
|
ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
|
||||||
|
Uri.encode(phoneNumber)
|
||||||
|
),
|
||||||
|
arrayOf(ContactsContract.PhoneLookup._ID),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
cursor?.apply {
|
||||||
|
if (moveToFirst())
|
||||||
|
result = getString(getColumnIndexOrThrow(ContactsContract.PhoneLookup._ID))
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class Record(val uri: Uri, val mimetype: String)
|
||||||
|
|
||||||
|
@RequiresPermission(Manifest.permission.READ_CONTACTS)
|
||||||
|
private fun getRecordsFromPhoneNumber(phoneNumber: String): Array<Record> {
|
||||||
|
val results = mutableSetOf<Record>()
|
||||||
|
val contactId = getContactIdByPhoneNumber(phoneNumber) ?: return results.toTypedArray()
|
||||||
|
val cursor = contentResolver.query(
|
||||||
|
ContactsContract.Data.CONTENT_URI,
|
||||||
|
arrayOf(ContactsContract.Data._ID, ContactsContract.Data.MIMETYPE),
|
||||||
|
"${ContactsContract.Data.CONTACT_ID} = ? AND " +
|
||||||
|
"${ContactsContract.Data.MIMETYPE} IN " +
|
||||||
|
"(${MIMETYPES.keys.joinToString(",") { "?" }})",
|
||||||
|
arrayOf(contactId, *MIMETYPES.keys.toTypedArray()),
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
cursor?.apply {
|
||||||
|
while (moveToNext())
|
||||||
|
results.add(Record(
|
||||||
|
Uri.withAppendedPath(
|
||||||
|
ContactsContract.Data.CONTENT_URI,
|
||||||
|
Uri.encode(getString(getColumnIndexOrThrow(ContactsContract.Data._ID))),
|
||||||
|
),
|
||||||
|
getString(getColumnIndexOrThrow(ContactsContract.Data.MIMETYPE)),
|
||||||
|
))
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
return results.toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresPermission(Manifest.permission.ACCESS_NETWORK_STATE)
|
||||||
|
private fun hasInternet(): Boolean {
|
||||||
|
val capabilities = connectivityManager
|
||||||
|
?.getNetworkCapabilities(connectivityManager?.activeNetwork) ?: return false
|
||||||
|
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
|
||||||
|
capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package me.lucky.re
|
package me.lucky.red
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.app.role.RoleManager
|
import android.app.role.RoleManager
|
||||||
|
@ -9,7 +9,7 @@ import android.provider.Settings
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
|
||||||
import me.lucky.re.databinding.ActivityMainBinding
|
import me.lucky.red.databinding.ActivityMainBinding
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
companion object {
|
companion object {
|
107
app/src/main/java/me/lucky/red/PopupWindow.kt
Normal file
107
app/src/main/java/me/lucky/red/PopupWindow.kt
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
package me.lucky.red
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.PixelFormat
|
||||||
|
import android.media.AudioManager
|
||||||
|
import android.net.Uri
|
||||||
|
import android.view.Gravity
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.WindowManager
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.RequiresPermission
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.concurrent.timerTask
|
||||||
|
|
||||||
|
class PopupWindow(private val service: CallRedirectionService) {
|
||||||
|
companion object {
|
||||||
|
private const val CANCEL_DELAY = 2000L
|
||||||
|
}
|
||||||
|
|
||||||
|
private val windowManager = service
|
||||||
|
.applicationContext
|
||||||
|
.getSystemService(WindowManager::class.java)
|
||||||
|
private val audioManager = service
|
||||||
|
.applicationContext
|
||||||
|
.getSystemService(AudioManager::class.java)
|
||||||
|
@Suppress("InflateParams")
|
||||||
|
private val view = LayoutInflater
|
||||||
|
.from(service.applicationContext)
|
||||||
|
.inflate(R.layout.popup, null)
|
||||||
|
private val layoutParams = WindowManager.LayoutParams().apply {
|
||||||
|
format = PixelFormat.TRANSLUCENT
|
||||||
|
flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|
||||||
|
type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
|
||||||
|
gravity = Gravity.BOTTOM
|
||||||
|
width = WindowManager.LayoutParams.WRAP_CONTENT
|
||||||
|
height = WindowManager.LayoutParams.WRAP_CONTENT
|
||||||
|
y = 333
|
||||||
|
}
|
||||||
|
private var timer: Timer? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
view.setOnClickListener {
|
||||||
|
timer?.cancel()
|
||||||
|
service.placeCallUnmodified()
|
||||||
|
remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun show(uri: Uri, destinationId: Int) {
|
||||||
|
if (!remove()) {
|
||||||
|
service.placeCallUnmodified()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
timer?.cancel()
|
||||||
|
timer = Timer()
|
||||||
|
timer?.schedule(timerTask {
|
||||||
|
if (!remove()) {
|
||||||
|
service.placeCallUnmodified()
|
||||||
|
return@timerTask
|
||||||
|
}
|
||||||
|
if (audioManager?.mode != AudioManager.MODE_IN_CALL) {
|
||||||
|
service.placeCallUnmodified()
|
||||||
|
return@timerTask
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
call(uri)
|
||||||
|
} catch (exc: SecurityException) {
|
||||||
|
service.placeCallUnmodified()
|
||||||
|
return@timerTask
|
||||||
|
}
|
||||||
|
service.cancelCall()
|
||||||
|
}, CANCEL_DELAY)
|
||||||
|
view.findViewById<TextView>(R.id.description).text = String.format(
|
||||||
|
service.getString(R.string.popup),
|
||||||
|
service.getString(destinationId),
|
||||||
|
)
|
||||||
|
if (!add()) {
|
||||||
|
timer?.cancel()
|
||||||
|
service.placeCallUnmodified()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresPermission(Manifest.permission.CALL_PHONE)
|
||||||
|
private fun call(data: Uri) {
|
||||||
|
Intent(Intent.ACTION_VIEW).let {
|
||||||
|
it.data = data
|
||||||
|
it.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
service.startActivity(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun add(): Boolean {
|
||||||
|
try {
|
||||||
|
windowManager?.addView(view, layoutParams)
|
||||||
|
} catch (exc: WindowManager.BadTokenException) { return false }
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun remove(): Boolean {
|
||||||
|
try {
|
||||||
|
windowManager?.removeView(view)
|
||||||
|
} catch (exc: IllegalArgumentException) {
|
||||||
|
} catch (exc: WindowManager.BadTokenException) { return false }
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package me.lucky.re
|
package me.lucky.red
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
|
@ -14,7 +14,6 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@color/popup"
|
android:background="@color/popup"
|
||||||
android:padding="16dp"
|
android:padding="16dp"
|
||||||
android:text="@string/info"
|
|
||||||
android:textColor="@color/black"
|
android:textColor="@color/black"
|
||||||
android:textSize="16sp" />
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Base application theme. -->
|
<!-- Base application theme. -->
|
||||||
<style name="Theme.Re" parent="Theme.Material3.DayNight.NoActionBar">
|
<style name="Theme.Red" parent="Theme.Material3.DayNight.NoActionBar">
|
||||||
<!-- Primary brand color. -->
|
<!-- Primary brand color. -->
|
||||||
<item name="colorPrimary">@color/purple_200</item>
|
<item name="colorPrimary">@color/purple_200</item>
|
||||||
<item name="colorOnPrimary">@color/black</item>
|
<item name="colorOnPrimary">@color/black</item>
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Re</string>
|
<string name="app_name">Red</string>
|
||||||
<string name="description">Приложение будет от всей души пытаться перенаправить исходящие вызовы в Сигнал или Телеграм, но иногда у него может не получаться. Для работы ему нужны тонны разрешений. Кликайте на переключатель и выдавайте разрешения пока он не включится.</string>
|
<string name="description">Приложение будет пытаться перенаправить исходящие вызовы в Signal/Telegram. Для работы ему нужно много разрешений. Кликайте на переключатель и выдавайте разрешения пока он не включится.</string>
|
||||||
<string name="info">Перенаправление</string>
|
<string name="popup">Перенаправление в %1$s</string>
|
||||||
|
<string name="signal">Signal</string>
|
||||||
|
<string name="telegram">Telegram</string>
|
||||||
</resources>
|
</resources>
|
|
@ -1,6 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Re</string>
|
<string name="app_name">Red</string>
|
||||||
<string name="description">The app will try to redirect outgoing calls to Signal or Telegram if available. For work it will require many permissions. Click on toggle and grant permissions until it turns ON.</string>
|
<string name="description">The app will try to redirect outgoing calls to Signal/Telegram if available. For work it requires many permissions. Click on the toggle and grant permissions until it turns ON.</string>
|
||||||
<string name="info">Redirecting</string>
|
<string name="popup">Redirecting to %1$s</string>
|
||||||
|
<string name="signal">Signal</string>
|
||||||
|
<string name="telegram">Telegram</string>
|
||||||
</resources>
|
</resources>
|
|
@ -1,6 +1,6 @@
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Base application theme. -->
|
<!-- Base application theme. -->
|
||||||
<style name="Theme.Re" parent="Theme.Material3.DayNight.NoActionBar">
|
<style name="Theme.Red" parent="Theme.Material3.DayNight.NoActionBar">
|
||||||
<!-- Primary brand color. -->
|
<!-- Primary brand color. -->
|
||||||
<item name="colorPrimary">@color/purple_500</item>
|
<item name="colorPrimary">@color/purple_500</item>
|
||||||
<item name="colorOnPrimary">@color/white</item>
|
<item name="colorOnPrimary">@color/white</item>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package me.lucky.re
|
package me.lucky.red
|
||||||
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
|
@ -5,7 +5,7 @@ buildscript {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath "com.android.tools.build:gradle:7.0.4"
|
classpath 'com.android.tools.build:gradle:7.1.0'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10"
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
|
Before Width: | Height: | Size: 876 B After Width: | Height: | Size: 876 B |
4
fastlane/metadata/android/en-US/changelogs/2.txt
Normal file
4
fastlane/metadata/android/en-US/changelogs/2.txt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
rename to Red
|
||||||
|
show redirection destination
|
||||||
|
fix dialer end call
|
||||||
|
add priority Signal > Telegram
|
|
@ -1,6 +1,6 @@
|
||||||
Tiny app to redirect outgoing calls to Signal or Telegram if available.
|
Tiny app to redirect outgoing calls to Signal/Telegram if available.
|
||||||
|
|
||||||
You can cancel redirection by clicking on "Redirecting" popup.
|
You can cancel redirection by clicking on "Redirecting to.." popup.
|
||||||
|
|
||||||
Permissions:
|
Permissions:
|
||||||
* ACCESS_NETWORK_STATE - check internet is available
|
* ACCESS_NETWORK_STATE - check internet is available
|
||||||
|
@ -9,5 +9,7 @@ Permissions:
|
||||||
* SYSTEM_ALERT_WINDOW - show redirecting popup and launch an activity from background
|
* SYSTEM_ALERT_WINDOW - show redirecting popup and launch an activity from background
|
||||||
* CALL_REDIRECTION - process outgoing call
|
* CALL_REDIRECTION - process outgoing call
|
||||||
|
|
||||||
|
All permissions are mandatory.
|
||||||
|
|
||||||
It is Free Open Source Software.
|
It is Free Open Source Software.
|
||||||
License: GPL-3
|
License: GPL-3
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 16 KiB |
Binary file not shown.
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 61 KiB |
|
@ -1 +1 @@
|
||||||
Redirect outgoing calls to Signal or Telegram
|
Redirect outgoing calls to Signal/Telegram
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Re
|
Red
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
Минимальное приложение для перенаправления исходящих звонков в Сигнал или Телеграм, если возможно.
|
Минимальное приложение для перенаправления исходящих вызовов в Signal/Telegram.
|
||||||
|
|
||||||
Вы можете отменить перенаправление, кликнув на всплывающий блок "Перенаправление".
|
Вы можете отменить перенаправление, кликнув на всплывающее сообщение "Перенаправление в..".
|
||||||
|
|
||||||
Разрешения:
|
Разрешения:
|
||||||
* ACCESS_NETWORK_STATE - проверить доступность интернета
|
* ACCESS_NETWORK_STATE - проверить наличие интернета
|
||||||
* CALL_PHONE - сделать звонок через мессенджер
|
* CALL_PHONE - позвонить через мессенджер
|
||||||
* READ_CONTACTS - проверить контакт на наличие записи из мессенджера
|
* READ_CONTACTS - проверить контакт на наличие записи из мессенджера
|
||||||
* SYSTEM_ALERT_WINDOW - показать всплывающий блок о перенаправлении и запустить активити из фона
|
* SYSTEM_ALERT_WINDOW - показать всплывающее сообщение о перенаправлении и запустить активити из
|
||||||
* CALL_REDIRECTION - обработать исходящий звонок
|
фона
|
||||||
|
* CALL_REDIRECTION - обработать исходящий вызов
|
||||||
|
|
||||||
|
Все разрешения обязательны для работы приложения.
|
||||||
|
|
||||||
Это свободное программное обеспечение с открытым исходным кодом.
|
Это свободное программное обеспечение с открытым исходным кодом.
|
||||||
Лицензия: GPL-3
|
Лицензия: GPL-3
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Перенаправить исходящие звонки в Сигнал или Телеграм
|
Перенаправить исходящие вызовы в Signal/Telegram
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Re
|
Red
|
||||||
|
|
|
@ -5,5 +5,5 @@ dependencyResolutionManagement {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rootProject.name = "Re"
|
rootProject.name = "Red"
|
||||||
include ':app'
|
include ':app'
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue