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