From 48a9998c26a4923179382dec9906a574e42cadf2 Mon Sep 17 00:00:00 2001 From: lucky <> Date: Fri, 4 Feb 2022 14:22:57 +0300 Subject: [PATCH] rename to Red show redirection destination fix dialer end call add priority Signal > Telegram --- README.md | 28 ++-- app/build.gradle | 6 +- .../{re => red}/ExampleInstrumentedTest.kt | 4 +- app/src/main/AndroidManifest.xml | 6 +- .../me/lucky/re/CallRedirectionService.kt | 100 -------------- app/src/main/java/me/lucky/re/DialogWindow.kt | 67 ---------- .../java/me/lucky/{re => red}/Application.kt | 2 +- .../me/lucky/red/CallRedirectionService.kt | 124 ++++++++++++++++++ .../java/me/lucky/{re => red}/MainActivity.kt | 4 +- app/src/main/java/me/lucky/red/PopupWindow.kt | 107 +++++++++++++++ .../java/me/lucky/{re => red}/Preferences.kt | 2 +- app/src/main/res/layout/popup.xml | 1 - app/src/main/res/values-night/themes.xml | 2 +- app/src/main/res/values-ru/strings.xml | 8 +- app/src/main/res/values/strings.xml | 8 +- app/src/main/res/values/themes.xml | 2 +- .../me/lucky/{re => red}/ExampleUnitTest.kt | 2 +- build.gradle | 2 +- data/{re.svg => red.svg} | 0 .../metadata/android/en-US/changelogs/2.txt | 4 + .../android/en-US/full_description.txt | 6 +- .../android/en-US/images/featureGraphic.png | Bin 14709 -> 16521 bytes .../en-US/images/phoneScreenshots/1.png | Bin 60719 -> 62368 bytes .../android/en-US/short_description.txt | 2 +- fastlane/metadata/android/en-US/title.txt | 2 +- .../android/ru-RU/full_description.txt | 15 ++- .../android/ru-RU/short_description.txt | 2 +- fastlane/metadata/android/ru-RU/title.txt | 2 +- settings.gradle | 2 +- 29 files changed, 291 insertions(+), 219 deletions(-) rename app/src/androidTest/java/me/lucky/{re => red}/ExampleInstrumentedTest.kt (87%) delete mode 100644 app/src/main/java/me/lucky/re/CallRedirectionService.kt delete mode 100644 app/src/main/java/me/lucky/re/DialogWindow.kt rename app/src/main/java/me/lucky/{re => red}/Application.kt (92%) create mode 100644 app/src/main/java/me/lucky/red/CallRedirectionService.kt rename app/src/main/java/me/lucky/{re => red}/MainActivity.kt (97%) create mode 100644 app/src/main/java/me/lucky/red/PopupWindow.kt rename app/src/main/java/me/lucky/{re => red}/Preferences.kt (95%) rename app/src/test/java/me/lucky/{re => red}/ExampleUnitTest.kt (93%) rename data/{re.svg => red.svg} (100%) create mode 100644 fastlane/metadata/android/en-US/changelogs/2.txt diff --git a/README.md b/README.md index 75ceb26..55a5c94 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,35 @@ -# Re +# Red -Redirect outgoing calls to Signal or Telegram. +Redirect outgoing calls to Signal/Telegram. [Get it on F-Droid](https://f-droid.org/packages/me.lucky.re/) - -[comment]: <> ([ ( src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png") - -[comment]: <> ( alt="Get it on Google Play") - -[comment]: <> ( height="80">](https://play.google.com/store/apps/details?id=me.lucky.re)) + height="80">](https://f-droid.org/packages/me.lucky.red/) +[Get it on Google Play](https://play.google.com/store/apps/details?id=me.lucky.red) -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 * ACCESS_NETWORK_STATE - check internet is available * CALL_PHONE - make a call via messenger * 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 +All permissions are mandatory. + ## License [![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](https://www.gnu.org/licenses/gpl-3.0.en.html) diff --git a/app/build.gradle b/app/build.gradle index dd2d459..325b6a5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,11 +7,11 @@ android { compileSdk 32 defaultConfig { - applicationId "me.lucky.re" + applicationId "me.lucky.red" minSdk 29 targetSdk 32 - versionCode 1 - versionName "1.0.0" + versionCode 2 + versionName "1.0.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/src/androidTest/java/me/lucky/re/ExampleInstrumentedTest.kt b/app/src/androidTest/java/me/lucky/red/ExampleInstrumentedTest.kt similarity index 87% rename from app/src/androidTest/java/me/lucky/re/ExampleInstrumentedTest.kt rename to app/src/androidTest/java/me/lucky/red/ExampleInstrumentedTest.kt index 9c1318c..2579b90 100644 --- a/app/src/androidTest/java/me/lucky/re/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/me/lucky/red/ExampleInstrumentedTest.kt @@ -1,4 +1,4 @@ -package me.lucky.re +package me.lucky.red import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -19,6 +19,6 @@ class ExampleInstrumentedTest { fun useAppContext() { // Context of the app under test. val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("me.lucky.re", appContext.packageName) + assertEquals("me.lucky.red", appContext.packageName) } } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ecbf4db..496a2a3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ + package="me.lucky.red"> @@ -9,14 +9,12 @@ + android:theme="@style/Theme.Red"> + 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 { + val results = mutableSetOf() + 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) + } +} diff --git a/app/src/main/java/me/lucky/re/MainActivity.kt b/app/src/main/java/me/lucky/red/MainActivity.kt similarity index 97% rename from app/src/main/java/me/lucky/re/MainActivity.kt rename to app/src/main/java/me/lucky/red/MainActivity.kt index bb5f569..8d571c9 100644 --- a/app/src/main/java/me/lucky/re/MainActivity.kt +++ b/app/src/main/java/me/lucky/red/MainActivity.kt @@ -1,4 +1,4 @@ -package me.lucky.re +package me.lucky.red import android.Manifest import android.app.role.RoleManager @@ -9,7 +9,7 @@ import android.provider.Settings import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity -import me.lucky.re.databinding.ActivityMainBinding +import me.lucky.red.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { companion object { diff --git a/app/src/main/java/me/lucky/red/PopupWindow.kt b/app/src/main/java/me/lucky/red/PopupWindow.kt new file mode 100644 index 0000000..6156826 --- /dev/null +++ b/app/src/main/java/me/lucky/red/PopupWindow.kt @@ -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(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 + } +} diff --git a/app/src/main/java/me/lucky/re/Preferences.kt b/app/src/main/java/me/lucky/red/Preferences.kt similarity index 95% rename from app/src/main/java/me/lucky/re/Preferences.kt rename to app/src/main/java/me/lucky/red/Preferences.kt index 14d679f..1c617e0 100644 --- a/app/src/main/java/me/lucky/re/Preferences.kt +++ b/app/src/main/java/me/lucky/red/Preferences.kt @@ -1,4 +1,4 @@ -package me.lucky.re +package me.lucky.red import android.content.Context import androidx.core.content.edit diff --git a/app/src/main/res/layout/popup.xml b/app/src/main/res/layout/popup.xml index 434a2f1..03b30b0 100644 --- a/app/src/main/res/layout/popup.xml +++ b/app/src/main/res/layout/popup.xml @@ -14,7 +14,6 @@ android:layout_height="wrap_content" android:background="@color/popup" android:padding="16dp" - android:text="@string/info" android:textColor="@color/black" android:textSize="16sp" /> diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index 684c95e..89f8cb1 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -1,6 +1,6 @@ -