Skip to content

Commit

Permalink
Version 5
Browse files Browse the repository at this point in the history
Added a call screening service.
  • Loading branch information
StefanIlchev committed Oct 6, 2022
1 parent e121e79 commit d5f54e1
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 44 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ android {
defaultConfig {
minSdk 28
targetSdk compileSdk
versionCode 4
versionCode 5
versionName "$versionCode"
}

Expand Down
10 changes: 10 additions & 0 deletions src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@
<action android:name="android.intent.action.PHONE_STATE" />
</intent-filter>
</receiver>

<service
android:name=".CallService"
android:exported="true"
android:permission="android.permission.BIND_SCREENING_SERVICE">

<intent-filter>
<action android:name="android.telecom.CallScreeningService" />
</intent-filter>
</service>
</application>

<!-- receive phone state broadcasts -->
Expand Down
1 change: 1 addition & 0 deletions src/main/java/ilchev/stefan/callblocker/CallReceiver.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
37 changes: 37 additions & 0 deletions src/main/java/ilchev/stefan/callblocker/CallService.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
115 changes: 72 additions & 43 deletions src/main/java/ilchev/stefan/callblocker/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -11,14 +13,15 @@
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;

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 {

Expand All @@ -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();
Expand All @@ -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<CallBlocker, Boolean> 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<CallBlocker> 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();
Expand All @@ -79,45 +116,32 @@ private void updateContent(int sourceId, Function<CallBlocker, Boolean> 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);
}
}

Expand All @@ -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);
}
}
7 changes: 7 additions & 0 deletions src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@
android:layout_height="wrap_content"
android:paddingHorizontal="14dp">

<CheckBox
android:id="@+id/screener"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="14dp"
android:text="@string/call_screener" />

<EditText
android:id="@+id/regex"
android:layout_width="match_parent"
Expand Down
1 change: 1 addition & 0 deletions src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">Call Blocker</string>
<string name="call_screener" translatable="false">Call Screener</string>
<string name="regex" translatable="false">Regex</string>
<string name="block_none" translatable="false">Block None</string>
<string name="block_matches" translatable="false">Block Matches</string>
Expand Down

0 comments on commit d5f54e1

Please sign in to comment.