popup window preview
Some checks failed
Android CI / build (push) Has been cancelled
Validate Gradle Wrapper / Validation (push) Has been cancelled
Lint Code Base / run-lint (push) Has been cancelled

This commit is contained in:
lucky 2022-02-16 12:30:44 +03:00 committed by lucky
parent f7bed719e3
commit 2ff2448739
16 changed files with 111 additions and 59 deletions

View file

@ -0,0 +1,10 @@
name: "Validate Gradle Wrapper"
on: [push, pull_request]
jobs:
validation:
name: "Validation"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: gradle/wrapper-validation-action@v1

View file

@ -2,13 +2,10 @@
Redirect outgoing calls to Signal/Telegram/Threema.
[comment]: <> ([<img)
[comment]: <> ( src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png")
[comment]: <> ( alt="Get it on F-Droid")
[comment]: <> ( height="80">]&#40;https://f-droid.org/packages/me.lucky.red/&#41;)
[<img
src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Get it on F-Droid"
height="80">](https://f-droid.org/packages/me.lucky.red/)
[<img
src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png"
alt="Get it on Google Play"

View file

@ -10,8 +10,8 @@ android {
applicationId "me.lucky.red"
minSdk 29
targetSdk 32
versionCode 8
versionName "1.0.7"
versionCode 9
versionName "1.0.8"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@ -39,10 +39,10 @@ android {
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.appcompat:appcompat:1.4.2'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

View file

@ -8,6 +8,7 @@ import android.provider.ContactsContract
import android.telecom.CallRedirectionService
import android.telecom.PhoneAccountHandle
import androidx.annotation.RequiresPermission
import java.lang.ref.WeakReference
class CallRedirectionService : CallRedirectionService() {
companion object {
@ -16,7 +17,7 @@ class CallRedirectionService : CallRedirectionService() {
private const val TELEGRAM_MIMETYPE = "$PREFIX/vnd.org.telegram.messenger.android.call"
private const val THREEMA_MIMETYPE = "$PREFIX/vnd.ch.threema.app.call"
private const val WHATSAPP_MIMETYPE = "$PREFIX/vnd.com.whatsapp.voip.call"
private val MIMETYPES = mapOf(
private val MIMETYPE_TO_WEIGHT = mapOf(
SIGNAL_MIMETYPE to 0,
TELEGRAM_MIMETYPE to 1,
THREEMA_MIMETYPE to 2,
@ -25,9 +26,15 @@ class CallRedirectionService : CallRedirectionService() {
private val FALLBACK_MIMETYPES = arrayOf(
WHATSAPP_MIMETYPE,
)
private val MIMETYPE_TO_DST_NAME = mapOf(
SIGNAL_MIMETYPE to R.string.destination_signal,
TELEGRAM_MIMETYPE to R.string.destination_telegram,
THREEMA_MIMETYPE to R.string.destination_threema,
WHATSAPP_MIMETYPE to R.string.fallback_destination_whatsapp,
)
}
lateinit var prefs: Preferences
private lateinit var prefs: Preferences
private lateinit var window: PopupWindow
private var connectivityManager: ConnectivityManager? = null
@ -43,7 +50,7 @@ class CallRedirectionService : CallRedirectionService() {
private fun init() {
prefs = Preferences(this)
window = PopupWindow(this)
window = PopupWindow(this, WeakReference(this))
connectivityManager = getSystemService(ConnectivityManager::class.java)
}
@ -52,7 +59,7 @@ class CallRedirectionService : CallRedirectionService() {
initialPhoneAccount: PhoneAccountHandle,
allowInteractiveResponse: Boolean,
) {
if (!prefs.isServiceEnabled || !hasInternet() || !allowInteractiveResponse) {
if (!prefs.isEnabled || !hasInternet() || !allowInteractiveResponse) {
placeCallUnmodified()
return
}
@ -63,18 +70,12 @@ class CallRedirectionService : CallRedirectionService() {
placeCallUnmodified()
return
}
val record = records.minByOrNull { MIMETYPES[it.mimetype] ?: 0 }
val record = records.minByOrNull { MIMETYPE_TO_WEIGHT[it.mimetype] ?: 0 }
if (record == null || (record.mimetype in FALLBACK_MIMETYPES && !prefs.isFallbackChecked)) {
placeCallUnmodified()
return
}
window.show(record.uri, when (record.mimetype) {
SIGNAL_MIMETYPE -> R.string.destination_signal
TELEGRAM_MIMETYPE -> R.string.destination_telegram
THREEMA_MIMETYPE -> R.string.destination_threema
WHATSAPP_MIMETYPE -> R.string.fallback_destination_whatsapp
else -> return
})
window.show(record.uri, MIMETYPE_TO_DST_NAME[record.mimetype] ?: return)
}
@RequiresPermission(Manifest.permission.READ_CONTACTS)
@ -83,7 +84,7 @@ class CallRedirectionService : CallRedirectionService() {
val cursor = contentResolver.query(
Uri.withAppendedPath(
ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
Uri.encode(phoneNumber)
Uri.encode(phoneNumber),
),
arrayOf(ContactsContract.PhoneLookup._ID),
null,
@ -109,8 +110,8 @@ class CallRedirectionService : CallRedirectionService() {
arrayOf(ContactsContract.Data._ID, ContactsContract.Data.MIMETYPE),
"${ContactsContract.Data.CONTACT_ID} = ? AND " +
"${ContactsContract.Data.MIMETYPE} IN " +
"(${MIMETYPES.keys.joinToString(",") { "?" }})",
arrayOf(contactId, *MIMETYPES.keys.toTypedArray()),
"(${MIMETYPE_TO_WEIGHT.keys.joinToString(",") { "?" }})",
arrayOf(contactId, *MIMETYPE_TO_WEIGHT.keys.toTypedArray()),
null,
)
cursor?.apply {

View file

@ -23,6 +23,7 @@ class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var prefs: Preferences
private lateinit var window: PopupWindow
private var roleManager: RoleManager? = null
private val registerForCallRedirectionRole =
@ -42,14 +43,20 @@ class MainActivity : AppCompatActivity() {
setup()
}
override fun onDestroy() {
super.onDestroy()
window.cancel()
}
private fun init() {
prefs = Preferences(this)
window = PopupWindow(this, null)
roleManager = getSystemService(RoleManager::class.java)
binding.apply {
redirectionDelay.value = (prefs.redirectionDelay / 1000).toFloat()
popupPosition.editText?.setText(prefs.popupPosition.toString())
fallback.isChecked = prefs.isFallbackChecked
toggle.isChecked = prefs.isServiceEnabled
toggle.isChecked = prefs.isEnabled
}
}
@ -61,6 +68,9 @@ class MainActivity : AppCompatActivity() {
redirectionDelay.addOnChangeListener { _, value, _ ->
prefs.redirectionDelay = (value * 1000).toLong()
}
popupPosition.setEndIconOnClickListener {
window.preview()
}
popupPosition.editText?.doAfterTextChanged {
try {
prefs.popupPosition = it?.toString()?.toInt() ?: return@doAfterTextChanged
@ -75,7 +85,7 @@ class MainActivity : AppCompatActivity() {
requestPermissions()
return@setOnCheckedChangeListener
}
prefs.isServiceEnabled = isChecked
prefs.isEnabled = isChecked
}
}
}

View file

@ -1,6 +1,7 @@
package me.lucky.red
import android.Manifest
import android.content.Context
import android.content.Intent
import android.graphics.PixelFormat
import android.media.AudioManager
@ -10,20 +11,19 @@ import android.view.LayoutInflater
import android.view.WindowManager
import android.widget.TextView
import androidx.annotation.RequiresPermission
import java.lang.ref.WeakReference
import java.util.*
import kotlin.concurrent.timerTask
class PopupWindow(private val service: CallRedirectionService) {
private val windowManager = service
.applicationContext
.getSystemService(WindowManager::class.java)
private val audioManager = service
.applicationContext
.getSystemService(AudioManager::class.java)
class PopupWindow(
private val ctx: Context,
private val service: WeakReference<CallRedirectionService>?,
) {
private val prefs = Preferences(ctx)
private val windowManager = ctx.getSystemService(WindowManager::class.java)
private val audioManager = ctx.getSystemService(AudioManager::class.java)
@Suppress("InflateParams")
private val view = LayoutInflater
.from(service.applicationContext)
.inflate(R.layout.popup, null)
private val view = LayoutInflater.from(ctx).inflate(R.layout.popup, null)
private val layoutParams = WindowManager.LayoutParams().apply {
format = PixelFormat.TRANSLUCENT
flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
@ -31,18 +31,32 @@ class PopupWindow(private val service: CallRedirectionService) {
gravity = Gravity.BOTTOM
width = WindowManager.LayoutParams.WRAP_CONTENT
height = WindowManager.LayoutParams.WRAP_CONTENT
y = service.prefs.popupPosition
y = prefs.popupPosition
}
private var timer: Timer? = null
init {
view.setOnClickListener {
cancel()
service.placeCallUnmodified()
service?.get()?.placeCallUnmodified()
}
}
fun preview() {
remove()
layoutParams.y = prefs.popupPosition
val destinations = mutableListOf(
R.string.destination_signal,
R.string.destination_telegram,
R.string.destination_threema,
)
if (prefs.isFallbackChecked) destinations.add(R.string.fallback_destination_whatsapp)
setDescription(destinations.random())
add()
}
fun show(uri: Uri, destinationId: Int) {
val service = service?.get() ?: return
if (!remove()) {
service.placeCallUnmodified()
return
@ -65,23 +79,27 @@ class PopupWindow(private val service: CallRedirectionService) {
return@timerTask
}
service.cancelCall()
}, service.prefs.redirectionDelay)
view.findViewById<TextView>(R.id.description).text = String.format(
service.getString(R.string.popup),
service.getString(destinationId),
)
}, prefs.redirectionDelay)
setDescription(destinationId)
if (!add()) {
timer?.cancel()
service.placeCallUnmodified()
}
}
private fun setDescription(id: Int) {
view.findViewById<TextView>(R.id.description).text = ctx.getString(
R.string.popup,
ctx.getString(id),
)
}
@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)
ctx.startActivity(it)
}
}

View file

@ -6,20 +6,23 @@ import androidx.preference.PreferenceManager
class Preferences(ctx: Context) {
companion object {
private const val SERVICE_ENABLED = "service_enabled"
private const val ENABLED = "enabled"
private const val REDIRECTION_DELAY = "redirection_delay"
private const val POPUP_POSITION = "popup_position_y"
private const val FALLBACK_CHECKED = "fallback_checked"
private const val DEFAULT_REDIRECTION_DELAY = 2000L
private const val DEFAULT_POPUP_POSITION = 333
// migration
private const val SERVICE_ENABLED = "service_enabled"
}
private val prefs = PreferenceManager.getDefaultSharedPreferences(ctx)
var isServiceEnabled: Boolean
get() = prefs.getBoolean(SERVICE_ENABLED, false)
set(value) = prefs.edit { putBoolean(SERVICE_ENABLED, value) }
var isEnabled: Boolean
get() = prefs.getBoolean(ENABLED, prefs.getBoolean(SERVICE_ENABLED, false))
set(value) = prefs.edit { putBoolean(ENABLED, value) }
var redirectionDelay: Long
get() = prefs.getLong(REDIRECTION_DELAY, DEFAULT_REDIRECTION_DELAY)

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z"/>
</vector>

View file

@ -55,6 +55,8 @@
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/popupPosition"
app:endIconMode="custom"
app:endIconDrawable="@drawable/ic_baseline_check_circle_24"
android:hint="@string/popup_position"
android:layout_width="match_parent"
android:layout_height="wrap_content">

View file

@ -6,7 +6,7 @@
<string name="destination_signal">Signal</string>
<string name="destination_telegram">Telegram</string>
<string name="destination_threema">Threema</string>
<string name="redirection_delay_description">Delay before a call will be redirected.</string>
<string name="redirection_delay_description">The delay before a call will be redirected.</string>
<string name="popup_position">Popup position</string>
<string name="fallback">Fallback</string>
<string name="fallback_description">Redirect to WhatsApp if no other available.</string>

View file

@ -5,8 +5,8 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10"
classpath 'com.android.tools.build:gradle:7.2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.0"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View file

@ -0,0 +1 @@
popup window preview

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Before After
Before After

View file

@ -1,4 +1,4 @@
Минимальное приложение для перенаправления исходящих вызовов в Signal/Telegram/Threema.
Мини приложение для перенаправления исходящих вызовов в Signal/Telegram/Threema.
Вы можете отменить перенаправление, кликнув на всплывающее сообщение "Перенаправление в..".

View file

@ -1,6 +1,6 @@
#Tue Jun 14 23:11:06 MSK 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
distributionPath=wrapper/dists
distributionSha256Sum=f581709a9c35e9cb92e16f585d2c4bc99b2b1a5f85d2badbd3dc6bff59e1e6dd
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME