diff --git a/build.gradle b/build.gradle
index fd7dfb6..e91f650 100644
--- a/build.gradle
+++ b/build.gradle
@@ -42,7 +42,7 @@ android {
defaultConfig {
minSdk 28
targetSdk compileSdk
- versionCode 4
+ versionCode 5
versionName "$versionCode"
}
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index 5a75bb4..8b477bf 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -25,6 +25,16 @@
+
+
+
+
+
+
+
diff --git a/src/main/java/ilchev/stefan/callblocker/CallReceiver.java b/src/main/java/ilchev/stefan/callblocker/CallReceiver.java
index decdf58..a9941d8 100644
--- a/src/main/java/ilchev/stefan/callblocker/CallReceiver.java
+++ b/src/main/java/ilchev/stefan/callblocker/CallReceiver.java
@@ -29,6 +29,7 @@ private static String getIncomingNumber(Intent intent) {
private static boolean endCall(Context context) {
if (context.checkSelfPermission(Manifest.permission.ANSWER_PHONE_CALLS) != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(context, Manifest.permission.ANSWER_PHONE_CALLS, Toast.LENGTH_LONG).show();
+ return false;
}
var telecomManager = context.getSystemService(TelecomManager.class);
return telecomManager != null && telecomManager.endCall();
diff --git a/src/main/java/ilchev/stefan/callblocker/CallService.java b/src/main/java/ilchev/stefan/callblocker/CallService.java
new file mode 100644
index 0000000..806946b
--- /dev/null
+++ b/src/main/java/ilchev/stefan/callblocker/CallService.java
@@ -0,0 +1,37 @@
+package ilchev.stefan.callblocker;
+
+import android.os.Build;
+import android.telecom.Call;
+import android.telecom.CallScreeningService;
+import android.util.Log;
+
+public class CallService extends CallScreeningService {
+
+ private static final String TAG = "CallService";
+
+ private static void endCall(CallResponse.Builder builder) {
+ builder.setDisallowCall(true)
+ .setRejectCall(true)
+ .setSkipNotification(true);
+ }
+
+ @Override
+ public void onScreenCall(Call.Details callDetails) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
+ callDetails.getCallDirection() != Call.Details.DIRECTION_INCOMING) {
+ return;
+ }
+ var builder = new CallResponse.Builder();
+ try (AutoCloseable ignored = () -> respondToCall(callDetails, builder.build())) {
+ var phoneNumber = callDetails.getHandle().getSchemeSpecificPart();
+ var sharedPreferences = getSharedPreferences(BuildConfig.APPLICATION_ID, MODE_PRIVATE);
+ var callBlocker = new CallBlocker(sharedPreferences);
+ if (callBlocker.isBlocked(phoneNumber)) {
+ endCall(builder);
+ CallReceiver.notifyBlockedCall(this, phoneNumber);
+ }
+ } catch (Throwable t) {
+ Log.w(TAG, t);
+ }
+ }
+}
diff --git a/src/main/java/ilchev/stefan/callblocker/MainActivity.java b/src/main/java/ilchev/stefan/callblocker/MainActivity.java
index 6c7c5e6..fbd8f64 100644
--- a/src/main/java/ilchev/stefan/callblocker/MainActivity.java
+++ b/src/main/java/ilchev/stefan/callblocker/MainActivity.java
@@ -3,6 +3,8 @@
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
+import android.app.role.RoleManager;
+import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
@@ -11,6 +13,7 @@
import android.text.TextWatcher;
import android.util.Log;
import android.view.View;
+import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.RadioGroup;
import android.widget.TextView;
@@ -18,7 +21,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
-import java.util.function.Function;
+import java.util.function.Consumer;
public class MainActivity extends Activity {
@@ -40,6 +43,31 @@ private static Boolean toBoolean(int value) {
: null;
}
+ private final TextWatcher regexListener = new TextWatcher() {
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ var regex = s + "";
+ updateContent(R.id.regex, callBlocker -> callBlocker.setRegex(regex));
+ }
+ };
+
+ private final RadioGroup.OnCheckedChangeListener blockListener = (group, checkedId) -> {
+ var isMatches = toBoolean(checkedId);
+ updateContent(R.id.block, callBlocker -> callBlocker.setMatches(isMatches));
+ };
+
+ private final CompoundButton.OnCheckedChangeListener screenerListener = (buttonView, isChecked) ->
+ updateScreener(buttonView);
+
@SuppressWarnings({"deprecation", "RedundantSuppression"})
private PackageInfo getPackageInfo(int flags) throws PackageManager.NameNotFoundException {
var packageManager = getPackageManager();
@@ -49,25 +77,34 @@ private PackageInfo getPackageInfo(int flags) throws PackageManager.NameNotFound
: packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(flags));
}
- private void updateContent(int sourceId, Function function) {
+ private void tryStartActivityForResult(Intent intent, int requestCode, Bundle options) {
+ try {
+ startActivityForResult(intent, requestCode, options);
+ } catch (Throwable t) {
+ Log.w(TAG, t);
+ }
+ }
+
+ private void updateContent(int sourceId, Consumer consumer) {
var sharedPreferences = getSharedPreferences(BuildConfig.APPLICATION_ID, MODE_PRIVATE);
String error = null;
try {
var callBlocker = new CallBlocker(sharedPreferences);
- var isLoop = function != null ? function.apply(callBlocker) : null;
- if (isLoop != null) {
- if (isLoop) {
- return;
- }
+ if (consumer != null) {
+ consumer.accept(callBlocker);
callBlocker.put(sharedPreferences.edit()).apply();
}
EditText regexView = sourceId != R.id.regex ? findViewById(R.id.regex) : null;
if (regexView != null) {
+ regexView.removeTextChangedListener(regexListener);
regexView.setText(callBlocker.getRegex());
+ regexView.addTextChangedListener(regexListener);
}
RadioGroup blockView = sourceId != R.id.block ? findViewById(R.id.block) : null;
if (blockView != null) {
+ blockView.setOnCheckedChangeListener(null);
blockView.check(toBlockId(callBlocker.isMatches()));
+ blockView.setOnCheckedChangeListener(blockListener);
}
} catch (Throwable t) {
error = t.getLocalizedMessage();
@@ -79,45 +116,32 @@ private void updateContent(int sourceId, Function function
}
}
- private final TextWatcher regexListener = new TextWatcher() {
-
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- }
+ private void updateScreener(CompoundButton screener, int visibility, boolean isChecked) {
+ screener.setVisibility(visibility);
+ screener.setOnCheckedChangeListener(null);
+ screener.setChecked(isChecked);
+ screener.setOnCheckedChangeListener(screenerListener);
+ }
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
+ private void updateScreener(CompoundButton buttonView) {
+ CompoundButton screener = buttonView != null ? buttonView : findViewById(R.id.screener);
+ if (screener == null) {
+ return;
}
-
- @Override
- public void afterTextChanged(Editable s) {
- var regex = s + "";
- updateContent(R.id.regex, callBlocker -> {
- var result = regex.equals(callBlocker.getRegex());
- callBlocker.setRegex(regex);
- return result;
- });
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
+ updateScreener(screener, View.GONE, false);
+ return;
}
- };
-
- private final RadioGroup.OnCheckedChangeListener blockListener = (group, checkedId) -> {
- var isMatches = toBoolean(checkedId);
- updateContent(R.id.block, callBlocker -> {
- var result = isMatches == callBlocker.isMatches();
- callBlocker.setMatches(isMatches);
- return result;
- });
- };
-
- private void initContent() {
- EditText regexView = findViewById(R.id.regex);
- if (regexView != null) {
- regexView.removeTextChangedListener(regexListener);
- regexView.addTextChangedListener(regexListener);
+ var roleManager = getSystemService(RoleManager.class);
+ if (roleManager == null || !roleManager.isRoleAvailable(RoleManager.ROLE_CALL_SCREENING)) {
+ updateScreener(screener, View.GONE, false);
+ return;
}
- RadioGroup blockView = findViewById(R.id.block);
- if (blockView != null) {
- blockView.setOnCheckedChangeListener(blockListener);
+ var isRoleHeld = roleManager.isRoleHeld(RoleManager.ROLE_CALL_SCREENING);
+ updateScreener(screener, View.VISIBLE, isRoleHeld);
+ if (buttonView != null && !isRoleHeld) {
+ var intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_CALL_SCREENING);
+ tryStartActivityForResult(intent, 0, null);
}
}
@@ -144,8 +168,13 @@ protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
updateContent(View.NO_ID, null);
- initContent();
requestRequestedPermissions();
CallReceiver.notifyBlockedCall(this, null);
}
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ updateScreener(null);
+ }
}
diff --git a/src/main/res/layout/activity_main.xml b/src/main/res/layout/activity_main.xml
index a1fbd03..ed1a7d6 100644
--- a/src/main/res/layout/activity_main.xml
+++ b/src/main/res/layout/activity_main.xml
@@ -9,6 +9,13 @@
android:layout_height="wrap_content"
android:paddingHorizontal="14dp">
+
+
Call Blocker
+ Call Screener
Regex
Block None
Block Matches