Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@ class BlockingManager @Inject constructor(
private val callBlockerBlockingClient: CallBlockerBlockingClient,
private val callControlBlockingClient: CallControlBlockingClient,
private val qksmsBlockingClient: QksmsBlockingClient,
private val shouldIAnswerBlockingClient: ShouldIAnswerBlockingClient
private val shouldIAnswerBlockingClient: ShouldIAnswerBlockingClient,
private val spamBlockerBlockingClient: SpamBlockerBlockingClient
) : BlockingClient {

private val client: BlockingClient
get() = when (prefs.blockingManager.get()) {
Preferences.BLOCKING_MANAGER_CB -> callBlockerBlockingClient
Preferences.BLOCKING_MANAGER_SIA -> shouldIAnswerBlockingClient
Preferences.BLOCKING_MANAGER_CC -> callControlBlockingClient
Preferences.BLOCKING_MANAGER_SB -> spamBlockerBlockingClient
else -> qksmsBlockingClient
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*
* Copyright (C) 2017 Moez Bhatti <moez.bhatti@gmail.com>
*
* This file is part of QKSMS.
*
* QKSMS 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.
*
* QKSMS 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 QKSMS. If not, see <http://www.gnu.org/licenses/>.
*/
package dev.octoshrimpy.quik.blocking

import android.annotation.SuppressLint
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.IBinder
import android.os.Message
import android.os.Messenger
import android.util.Log
import androidx.core.os.bundleOf
import dev.octoshrimpy.quik.util.tryOrNull
import io.reactivex.Completable
import io.reactivex.Single
import io.reactivex.subjects.SingleSubject
import javax.inject.Inject

object Protocol {
const val action = "sms.screening.provider.PublicSMSScreeningService"

const val smsScreening = 1
const val smsScreeningResult = 2

// request
const val keyNumber = "number"
const val keySmsContent = "smsContent"
const val keySimSlot = "simSlot"

// response
const val keyShouldBlock = "shouldBlock"
const val keyReason = "reason" // Why the message is blocked or allowed
}
Comment on lines +43 to +56

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this should be moved to a specific file or module where all of the protocol related things should live? Rather than redefining it for every new app.



const val SpamBlockerPackageName = "spam.blocker"
Comment on lines +43 to +59

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of the const vals need to be upper case with underscores.


@SuppressLint("QueryPermissionsNeeded")
private fun PackageManager.queryPublicScreeningProviders(): List<ResolveInfo> {
val intent = Intent(Protocol.action)
val services = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
queryIntentServices(intent, PackageManager.ResolveInfoFlags.of(0))
} else {
@Suppress("DEPRECATION")
queryIntentServices(intent, 0)
}

return services.filter { it.serviceInfo?.exported == true }
}

fun listAvailableProviders(context: Context): List<ComponentName> {
return context.packageManager
.queryPublicScreeningProviders()
.map {
ComponentName(it.serviceInfo.packageName, it.serviceInfo.name)
}
}


class SpamBlockerBlockingClient @Inject constructor(
private val context: Context
) : BlockingClient {

override fun isAvailable(): Boolean {
return listAvailableProviders(context).any {
it.packageName == SpamBlockerPackageName
}
}

override fun getClientCapability() = BlockingClient.Capability.CANT_BLOCK

override fun shouldBlock(address: String): Single<BlockingClient.Action> = isBlacklisted(address)

override fun isBlacklisted(address: String): Single<BlockingClient.Action> {
return Binder(context, address).isBlocked()
.map { blocked ->
when (blocked) {
true -> BlockingClient.Action.Block()
false -> BlockingClient.Action.DoNothing
}
}
}

override fun block(addresses: List<String>): Completable = Completable.fromCallable { openSettings() }

override fun unblock(addresses: List<String>): Completable = Completable.fromCallable { openSettings() }

override fun openSettings() {
val pm = context.packageManager
val intent = pm.getLaunchIntentForPackage(SpamBlockerPackageName)
intent?.let { context.startActivity(it) }
}

private class Binder(
private val context: Context,
private val address: String
) : ServiceConnection {

private val subject: SingleSubject<Boolean> = SingleSubject.create()
private var serviceMessenger: Messenger? = null
private var isBound: Boolean = false

fun isBlocked(): Single<Boolean> {
// If either version of Should I Answer? is installed and SIA is enabled, build the
// intent to request a rating
Comment on lines +127 to +128

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not understanding this comment. Is it necessary?

val intent: Intent? = tryOrNull(false) {
context.packageManager.getApplicationInfo(SpamBlockerPackageName, 0).enabled
Intent(Protocol.action).setPackage(SpamBlockerPackageName)
}

// If the intent isn't null, bind the service and wait for a result. Otherwise, don't block
if (intent != null) {
val r = context.bindService(intent, this, Context.BIND_AUTO_CREATE)
Log.i("quik spamblocker", "bind service: $r")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use Timber here.

} else {
subject.onSuccess(false)
}

return subject
}

override fun onServiceConnected(className: ComponentName, service: IBinder) {
serviceMessenger = Messenger(service)
isBound = true

val message = Message().apply {
what = Protocol.smsScreening
data = bundleOf(Protocol.keyNumber to address)
replyTo = Messenger(IncomingHandler { response ->
Log.i("quik spamblocker", "shouldBlock: ${response.shouldBlock}")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Timber here as well.

subject.onSuccess(response.shouldBlock)

// We're done, so unbind the service
if (isBound && serviceMessenger != null) {
context.unbindService(this@Binder)
}
})
}

serviceMessenger?.send(message)
}

override fun onServiceDisconnected(className: ComponentName) {
serviceMessenger = null
isBound = false
}
}

private class IncomingHandler(private val callback: (response: Response) -> Unit) : Handler() {
class Response(bundle: Bundle) {
val shouldBlock = bundle.getBoolean(Protocol.keyShouldBlock)

// optional
// val blockReason = bundle.getString(Protocol.keyBlockReason)
}

override fun handleMessage(msg: Message) {
when (msg.what) {
Protocol.smsScreeningResult -> callback(Response(msg.data))
else -> super.handleMessage(msg)
}
}
}
}
2 changes: 2 additions & 0 deletions domain/src/main/java/com/moez/QKSMS/util/Preferences.kt
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ class Preferences @Inject constructor(
const val BLOCKING_MANAGER_CC = 1
const val BLOCKING_MANAGER_SIA = 2
const val BLOCKING_MANAGER_CB = 3
const val BLOCKING_MANAGER_SB = 4


const val MESSAGE_LINK_HANDLING_BLOCK = 0
const val MESSAGE_LINK_HANDLING_ALLOW = 1
Expand Down
7 changes: 7 additions & 0 deletions presentation/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,13 @@
android:resource="@array/preloaded_fonts" />
</application>

<!-- For dynamically querying SMS screening providers -->
<queries>
<intent>
<action android:name="sms.screening.provider.PublicSMSScreeningService" />
</intent>
</queries>

<queries>
<package android:name="com.cuiet.blockCalls" />
<package android:name="com.flexaspect.android.everycallcontrol" />
Expand Down
9 changes: 9 additions & 0 deletions presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,15 @@ class Navigator @Inject constructor(
startActivityExternal(intent)
}

/**
* Launch F-Droid and display the SpamBlocker listing
*/
fun installSB() {
val url = "https://f-droid.org/packages/spam.blocker/"
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
startActivityExternal(intent)
}

fun showSupport() {
val intent = Intent(Intent.ACTION_SENDTO)
intent.data = Uri.parse("mailto:")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class BlockingPresenter @Inject constructor(
Preferences.BLOCKING_MANAGER_CB -> R.string.blocking_manager_call_blocker_title
Preferences.BLOCKING_MANAGER_CC -> R.string.blocking_manager_call_control_title
Preferences.BLOCKING_MANAGER_SIA -> R.string.blocking_manager_sia_title
Preferences.BLOCKING_MANAGER_SB -> R.string.blocking_manager_spam_blocker_title
else -> R.string.app_name
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class BlockingManagerController : QkController<BlockingManagerControllerBinding,
binding.callBlocker.actionView.imageTintList = imageTintList
binding.callControl.actionView.imageTintList = imageTintList
binding.shouldIAnswer.actionView.imageTintList = imageTintList
binding.spamBlocker.actionView.imageTintList = imageTintList
}

override fun onActivityResumed(activity: Activity) {
Expand All @@ -78,6 +79,11 @@ class BlockingManagerController : QkController<BlockingManagerControllerBinding,
binding.shouldIAnswer.actionView.isActivated = state.siaInstalled
binding.shouldIAnswer.actionView.isInvisible = state.blockingManager != Preferences.BLOCKING_MANAGER_SIA
&& state.siaInstalled

binding.spamBlocker.actionView.setImageResource(getActionIcon(state.spamBlockerInstalled))
binding.spamBlocker.actionView.isActivated = state.spamBlockerInstalled
binding.spamBlocker.actionView.isInvisible = state.blockingManager != Preferences.BLOCKING_MANAGER_SB
&& state.spamBlockerInstalled
}

private fun getActionIcon(installed: Boolean): Int = when {
Expand All @@ -90,6 +96,7 @@ class BlockingManagerController : QkController<BlockingManagerControllerBinding,
override fun callBlockerClicked(): Observable<*> = binding.callBlocker.clicks()
override fun callControlClicked(): Observable<*> = binding.callControl.clicks()
override fun siaClicked(): Observable<*> = binding.shouldIAnswer.clicks()
override fun spamBlockerClicked(): Observable<*> = binding.spamBlocker.clicks()

override fun showCopyDialog(manager: String): Single<Boolean> = Single.create { emitter ->
AlertDialog.Builder(activity)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import dev.octoshrimpy.quik.blocking.CallBlockerBlockingClient
import dev.octoshrimpy.quik.blocking.CallControlBlockingClient
import dev.octoshrimpy.quik.blocking.QksmsBlockingClient
import dev.octoshrimpy.quik.blocking.ShouldIAnswerBlockingClient
import dev.octoshrimpy.quik.blocking.SpamBlockerBlockingClient
import dev.octoshrimpy.quik.common.Navigator
import dev.octoshrimpy.quik.common.base.QkPresenter
import dev.octoshrimpy.quik.repository.ConversationRepository
Expand All @@ -27,12 +28,14 @@ class BlockingManagerPresenter @Inject constructor(
private val navigator: Navigator,
private val prefs: Preferences,
private val qksms: QksmsBlockingClient,
private val shouldIAnswer: ShouldIAnswerBlockingClient
private val shouldIAnswer: ShouldIAnswerBlockingClient,
private val spamBlocker: SpamBlockerBlockingClient,
) : QkPresenter<BlockingManagerView, BlockingManagerState>(BlockingManagerState(
blockingManager = prefs.blockingManager.get(),
callBlockerInstalled = callBlocker.isAvailable(),
callControlInstalled = callControl.isAvailable(),
siaInstalled = shouldIAnswer.isAvailable()
siaInstalled = shouldIAnswer.isAvailable(),
spamBlockerInstalled = spamBlocker.isAvailable(),
)) {

init {
Expand Down Expand Up @@ -61,6 +64,12 @@ class BlockingManagerPresenter @Inject constructor(
.autoDisposable(view.scope())
.subscribe { available -> newState { copy(siaInstalled = available) } }

view.activityResumed()
.map { spamBlocker.isAvailable() }
.distinctUntilChanged()
.autoDisposable(view.scope())
.subscribe { available -> newState { copy(siaInstalled = available) } }

view.qksmsClicked()
.observeOn(Schedulers.io())
.map { getAddressesToBlock(qksms) }
Expand Down Expand Up @@ -130,6 +139,21 @@ class BlockingManagerPresenter @Inject constructor(
.subscribe {
prefs.blockingManager.set(Preferences.BLOCKING_MANAGER_SIA)
}

view.spamBlockerClicked()
.filter {
val installed = spamBlocker.isAvailable()
if (!installed) {
navigator.installSB()
}

val enabled = prefs.blockingManager.get() == Preferences.BLOCKING_MANAGER_SB
installed && !enabled
}
.autoDisposable(view.scope())
.subscribe {
prefs.blockingManager.set(Preferences.BLOCKING_MANAGER_SB)
}
}

private fun getAddressesToBlock(client: BlockingClient) = conversationRepo.getBlockedConversations()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ data class BlockingManagerState(
val blockingManager: Int = 0,
val callBlockerInstalled: Boolean = false,
val callControlInstalled: Boolean = false,
val siaInstalled: Boolean = false
val siaInstalled: Boolean = false,
val spamBlockerInstalled: Boolean = false
)
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ interface BlockingManagerView : QkViewContract<BlockingManagerState> {
fun callBlockerClicked(): Observable<*>
fun callControlClicked(): Observable<*>
fun siaClicked(): Observable<*>
fun spamBlockerClicked(): Observable<*>


fun showCopyDialog(manager: String): Single<Boolean>

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@
app:title="@string/blocking_manager_sia_title"
app:widget="@layout/blocking_manager_list_option" />

<dev.octoshrimpy.quik.feature.blocking.manager.BlockingManagerPreferenceView
android:id="@+id/spamBlocker"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:icon="@drawable/ic_blocking_manager_spam_blocker"
app:summary="@string/blocking_manager_spam_blocker_summary"
app:title="@string/blocking_manager_spam_blocker_title"
app:widget="@layout/blocking_manager_list_option" />

</LinearLayout>

</ScrollView>
2 changes: 2 additions & 0 deletions presentation/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,8 @@
<string name="blocking_manager_call_control_summary">Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers</string>
<string name="blocking_manager_sia_title" translatable="false">Should I Answer?</string>
<string name="blocking_manager_sia_summary">Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app</string>
<string name="blocking_manager_spam_blocker_title" translatable="false">SpamBlocker</string>
<string name="blocking_manager_spam_blocker_summary">Filter messages with advanced rules by using the \"Spam Blocker\" app</string>
<string name="blocking_manager_copy_title">Copy blocked numbers</string>
<string name="blocking_manager_copy_summary">Continue to %s and copy over your existing blocked numbers</string>

Expand Down