rename to Red

show redirection destination
fix dialer end call
add priority Signal > Telegram
This commit is contained in:
lucky 2022-02-04 14:22:57 +03:00
parent 9b3c582337
commit 48a9998c26
29 changed files with 291 additions and 219 deletions

View file

@ -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">]&#40;https://play.google.com/store/apps/details?id=me.lucky.re&#41;)
<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
[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](https://www.gnu.org/licenses/gpl-3.0.en.html) [![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](https://www.gnu.org/licenses/gpl-3.0.en.html)

View file

@ -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"
} }

View file

@ -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)
} }
} }

View file

@ -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"

View file

@ -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
}
}

View file

@ -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) {}
}
}

View file

@ -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

View 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)
}
}

View file

@ -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 {

View 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
}
}

View file

@ -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

View file

@ -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" />

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -1,4 +1,4 @@
package me.lucky.re package me.lucky.red
import org.junit.Test import org.junit.Test

View file

@ -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

View file

Before

Width:  |  Height:  |  Size: 876 B

After

Width:  |  Height:  |  Size: 876 B

Before After
Before After

View file

@ -0,0 +1,4 @@
rename to Red
show redirection destination
fix dialer end call
add priority Signal > Telegram

View file

@ -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

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Before After
Before After

View file

@ -1 +1 @@
Redirect outgoing calls to Signal or Telegram Redirect outgoing calls to Signal/Telegram

View file

@ -1 +1 @@
Re Red

View file

@ -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

View file

@ -1 +1 @@
Перенаправить исходящие звонки в Сигнал или Телеграм Перенаправить исходящие вызовы в Signal/Telegram

View file

@ -1 +1 @@
Re Red

View file

@ -5,5 +5,5 @@ dependencyResolutionManagement {
mavenCentral() mavenCentral()
} }
} }
rootProject.name = "Re" rootProject.name = "Red"
include ':app' include ':app'