Add monitoring service

This commit is contained in:
xynngh 2020-09-08 19:09:41 +04:00
parent cf0ad91fb7
commit b06a3d47fb
26 changed files with 546 additions and 125 deletions

View File

@ -11,6 +11,7 @@
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.READ_LOG" android:maxSdkVersion="15" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- may be needed for call blocking on Android 9, also requires to be installed as a system app -->
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE"
@ -103,17 +104,31 @@
</activity>
<receiver
android:name=".CallReceiver"
android:name=".StartupReceiver"
android:directBootAware="true"
android:enabled="true"
android:exported="true">
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
</receiver>
<receiver
android:name=".CallReceiver"
android:directBootAware="true">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE" />
</intent-filter>
</receiver>
<service android:name=".work.TaskService" />
<service
android:name=".CallMonitoringService"
android:description="@string/monitoring_service_description"
android:directBootAware="true"
android:exported="false"
android:label="@string/monitoring_service_label" />
<service
android:name=".CallScreeningServiceImpl"
android:directBootAware="true"

View File

@ -45,6 +45,10 @@ public class App extends Application {
Config.init(getDeviceProtectedStorageContext(), settings);
setUiMode(settings.getUiMode());
if (settings.getUseMonitoringService()) {
CallMonitoringService.start(this);
}
}
private Context getDeviceProtectedStorageContext() {

View File

@ -0,0 +1,158 @@
package dummydomain.yetanothercallblocker;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import dummydomain.yetanothercallblocker.data.YacbHolder;
import static java.util.Objects.requireNonNull;
public class CallMonitoringService extends Service {
private static final String ACTION_START = "YACB_ACTION_START";
private static final String ACTION_STOP = "YACB_ACTION_STOP";
private static final Logger LOG = LoggerFactory.getLogger(CallMonitoringService.class);
private final MyPhoneStateListener phoneStateListener = new MyPhoneStateListener(this);
private final CallReceiver callReceiver = new CallReceiver(true);
private boolean monitoringStarted;
public static void start(Context context) {
ContextCompat.startForegroundService(context, getIntent(context, ACTION_START));
}
public static void stop(Context context) {
context.stopService(getIntent(context, ACTION_STOP));
}
private static Intent getIntent(Context context, String action) {
Intent intent = new Intent(context, CallMonitoringService.class);
intent.setAction(action);
return intent;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
LOG.debug("onStartCommand({})", intent);
if (intent != null && ACTION_STOP.equals(intent.getAction())) {
stopMonitoring();
stopForeground();
stopSelf();
} else {
startForeground();
startMonitoring();
}
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
LOG.debug("onBind({})", intent);
return null;
}
@Override
public void onDestroy() {
LOG.debug("onDestroy()");
stopMonitoring();
}
private void startForeground() {
startForeground(NotificationHelper.NOTIFICATION_ID_MONITORING_SERVICE,
NotificationHelper.createMonitoringServiceNotification(this));
}
private void stopForeground() {
stopForeground(true);
}
private void startMonitoring() {
if (monitoringStarted) return;
monitoringStarted = true;
try {
getTelephonyManager().listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(TelephonyManager.EXTRA_STATE_RINGING); // TODO: check
intentFilter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
registerReceiver(callReceiver, intentFilter);
} catch (Exception e) {
LOG.error("startMonitoring()", e);
}
}
private void stopMonitoring() {
if (!monitoringStarted) return;
try {
getTelephonyManager().listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
unregisterReceiver(callReceiver);
} catch (Exception e) {
LOG.error("stopMonitoring()", e);
}
monitoringStarted = false;
}
private TelephonyManager getTelephonyManager() {
return requireNonNull(
(TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE));
}
private static class MyPhoneStateListener extends PhoneStateListener {
private static final Logger LOG = LoggerFactory.getLogger(MyPhoneStateListener.class);
private final Context context;
public MyPhoneStateListener(Context context) {
this.context = context;
}
@Override
public void onCallStateChanged(int state, String phoneNumber) {
LOG.info("onCallStateChanged({}, \"{}\")", state, phoneNumber);
/*
* According to docs, an empty string may be passed if the app lacks permissions.
* The app deals with permissions in PhoneStateHandler.
*/
if (TextUtils.isEmpty(phoneNumber)) {
phoneNumber = null;
}
PhoneStateHandler phoneStateHandler = YacbHolder.getPhoneStateHandler();
switch (state) {
case TelephonyManager.CALL_STATE_IDLE:
phoneStateHandler.onIdle(context, phoneNumber);
break;
case TelephonyManager.CALL_STATE_RINGING:
phoneStateHandler.onRinging(context, phoneNumber);
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
phoneStateHandler.onOffHook(context, phoneNumber);
break;
}
}
}
}

View File

@ -1,97 +1,68 @@
package dummydomain.yetanothercallblocker;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.telecom.TelecomManager;
import android.telephony.TelephonyManager;
import androidx.annotation.NonNull;
import com.android.internal.telephony.ITelephony;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
import dummydomain.yetanothercallblocker.data.NumberInfo;
import dummydomain.yetanothercallblocker.data.NumberInfoService;
import dummydomain.yetanothercallblocker.data.YacbHolder;
import dummydomain.yetanothercallblocker.event.CallEndedEvent;
import dummydomain.yetanothercallblocker.event.CallOngoingEvent;
import static dummydomain.yetanothercallblocker.EventUtils.postEvent;
import static java.util.Objects.requireNonNull;
public class CallReceiver extends BroadcastReceiver {
private static final Logger LOG = LoggerFactory.getLogger(CallReceiver.class);
private static boolean isOnCall; // TODO: proper handling
private final boolean monitoringService;
@SuppressWarnings({"unused", "RedundantSuppression"}) // required for BroadcastReceivers
public CallReceiver() {
this(false);
}
public CallReceiver(boolean monitoringService) {
this.monitoringService = monitoringService;
}
@Override
public void onReceive(Context context, Intent intent) {
if (!TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(intent.getAction())) return;
LOG.debug("onReceive() invoked, monitoringService={}", monitoringService);
if (!TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(intent.getAction())
&& !TelephonyManager.EXTRA_STATE_RINGING.equals(intent.getAction())) { // TODO: check
LOG.warn("onReceive() unexpected action: {}", intent.getAction());
return;
}
@SuppressWarnings({"deprecation", "RedundantSuppression"}) // no choice
String extraIncomingNumber = TelephonyManager.EXTRA_INCOMING_NUMBER;
String telephonyExtraState = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
String incomingNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
boolean hasNumberExtra = intent.hasExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
LOG.info("Received intent: extraState={}, incomingNumber={}, hasNumberExtra={}",
String incomingNumber = intent.getStringExtra(extraIncomingNumber);
boolean hasNumberExtra = intent.hasExtra(extraIncomingNumber);
LOG.info("onReceive() extraState={}, incomingNumber={}, hasNumberExtra={}",
telephonyExtraState, incomingNumber, hasNumberExtra);
extraLogging(intent); // TODO: make optional or remove
if (TelephonyManager.EXTRA_STATE_OFFHOOK.equals(telephonyExtraState)) {
isOnCall = true;
postEvent(new CallOngoingEvent());
} else if (TelephonyManager.EXTRA_STATE_RINGING.equals(telephonyExtraState)) {
if (incomingNumber == null) {
if (hasNumberExtra) {
incomingNumber = "";
} else {
if (!PermissionHelper.hasNumberInfoPermissions(context)) {
LOG.warn("No info permissions");
return;
}
return; // TODO: check
}
}
if (TelephonyManager.EXTRA_STATE_RINGING.equals(intent.getAction())) {
LOG.warn("onReceive() ignoring untested action");
return;
}
Settings settings = App.getSettings();
if (incomingNumber == null && hasNumberExtra) {
incomingNumber = "";
}
boolean blockingEnabled = settings.getCallBlockingEnabled();
boolean showNotifications = settings.getIncomingCallNotifications();
if (blockingEnabled || showNotifications) {
NumberInfoService numberInfoService = YacbHolder.getNumberInfoService();
NumberInfo numberInfo = numberInfoService.getNumberInfo(incomingNumber,
settings.getCachedAutoDetectedCountryCode(), false);
boolean blocked = false;
if (blockingEnabled && !isOnCall && numberInfoService.shouldBlock(numberInfo)) {
blocked = rejectCall(context);
if (blocked) {
NotificationHelper.showBlockedCallNotification(context, numberInfo);
numberInfoService.blockedCall(numberInfo);
postEvent(new CallEndedEvent());
}
}
if (!blocked && showNotifications) {
NotificationHelper.showIncomingCallNotification(context, numberInfo);
}
}
} else if(TelephonyManager.EXTRA_STATE_IDLE.equals(telephonyExtraState)) {
isOnCall = false;
NotificationHelper.hideIncomingCallNotification(context);
postEvent(new CallEndedEvent());
PhoneStateHandler phoneStateHandler = YacbHolder.getPhoneStateHandler();
if (TelephonyManager.EXTRA_STATE_RINGING.equals(telephonyExtraState)) {
phoneStateHandler.onRinging(context, incomingNumber);
} else if (TelephonyManager.EXTRA_STATE_OFFHOOK.equals(telephonyExtraState)) {
phoneStateHandler.onOffHook(context, incomingNumber);
} else if (TelephonyManager.EXTRA_STATE_IDLE.equals(telephonyExtraState)) {
phoneStateHandler.onIdle(context, incomingNumber);
}
}
@ -115,41 +86,4 @@ public class CallReceiver extends BroadcastReceiver {
}
}
@SuppressLint("MissingPermission")
private boolean rejectCall(@NonNull Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
try {
TelecomManager telecomManager = requireNonNull(
(TelecomManager) context.getSystemService(Context.TELECOM_SERVICE));
//noinspection deprecation
telecomManager.endCall();
LOG.info("Rejected call using TelecomManager");
return true;
} catch (Exception e) {
LOG.warn("Error while rejecting call on API 28+", e);
}
}
try {
TelephonyManager tm = requireNonNull(
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE));
@SuppressLint("DiscouragedPrivateApi") // no choice
Method m = tm.getClass().getDeclaredMethod("getITelephony");
m.setAccessible(true);
ITelephony telephony = requireNonNull((ITelephony) m.invoke(tm));
telephony.endCall();
LOG.info("Rejected call using ITelephony");
return true;
} catch (Exception e) {
LOG.warn("Error while rejecting call", e);
}
return false;
}
}

View File

@ -31,11 +31,12 @@ public class NotificationHelper {
private static final int NOTIFICATION_ID_INCOMING_CALL = 1;
private static final int NOTIFICATION_ID_BLOCKED_CALL = 2;
public static final int NOTIFICATION_ID_TASKS = 3;
public static final int NOTIFICATION_ID_MONITORING_SERVICE = 3;
public static final int NOTIFICATION_ID_TASKS = 4;
private static final String CHANNEL_GROUP_ID_INCOMING_CALLS = "incoming_calls";
private static final String CHANNEL_GROUP_ID_BLOCKED_CALLS = "blocked_calls";
private static final String CHANNEL_GROUP_ID_TASKS = "tasks";
private static final String CHANNEL_GROUP_ID_SERVICES = "services";
private static final String CHANNEL_ID_KNOWN = "known_calls";
private static final String CHANNEL_ID_POSITIVE = "positive_calls";
@ -43,6 +44,7 @@ public class NotificationHelper {
private static final String CHANNEL_ID_UNKNOWN = "unknown_calls";
private static final String CHANNEL_ID_NEGATIVE = "negative_calls";
private static final String CHANNEL_ID_BLOCKED_INFO = "blocked_info";
private static final String CHANNEL_ID_MONITORING_SERVICE = "monitoring_service";
private static final String CHANNEL_ID_TASKS = "tasks";
private static boolean notificationChannelsInitialized;
@ -91,6 +93,22 @@ public class NotificationHelper {
notify(context, tag, NOTIFICATION_ID_BLOCKED_CALL, notification);
}
public static Notification createMonitoringServiceNotification(Context context) {
initNotificationChannels(context);
PendingIntent contentIntent = pendingActivity(context,
new Intent(context, MainActivity.class));
return new NotificationCompat.Builder(context, CHANNEL_ID_MONITORING_SERVICE)
.setSmallIcon(R.drawable.ic_security_24dp)
.setContentTitle(context.getString(R.string.monitoring_service_notification_title))
.setContentIntent(contentIntent)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.setPriority(NotificationCompat.PRIORITY_MIN)
.setShowWhen(false)
.build();
}
public static Notification createServiceNotification(Context context, String title) {
initNotificationChannels(context);
@ -253,10 +271,10 @@ public class NotificationHelper {
context.getString(R.string.notification_channel_group_name_blocked_calls));
notificationManager.createNotificationChannelGroup(channelGroupBlocked);
NotificationChannelGroup channelGroupTasks = new NotificationChannelGroup(
CHANNEL_GROUP_ID_TASKS,
context.getString(R.string.notification_channel_group_name_tasks));
notificationManager.createNotificationChannelGroup(channelGroupTasks);
NotificationChannelGroup channelGroupServices = new NotificationChannelGroup(
CHANNEL_GROUP_ID_SERVICES,
context.getString(R.string.notification_channel_group_name_services));
notificationManager.createNotificationChannelGroup(channelGroupServices);
List<NotificationChannel> channels = new ArrayList<>();
@ -304,11 +322,19 @@ public class NotificationHelper {
channel.setGroup(channelGroupBlocked.getId());
channels.add(channel);
channel = new NotificationChannel(
CHANNEL_ID_MONITORING_SERVICE,
context.getString(R.string.notification_channel_name_monitoring_service),
NotificationManager.IMPORTANCE_LOW
);
channel.setGroup(channelGroupServices.getId());
channels.add(channel);
channel = new NotificationChannel(
CHANNEL_ID_TASKS, context.getString(R.string.notification_channel_name_tasks),
NotificationManager.IMPORTANCE_LOW
);
channel.setGroup(channelGroupTasks.getId());
channel.setGroup(channelGroupServices.getId());
channels.add(channel);
notificationManager.createNotificationChannels(channels);

View File

@ -0,0 +1,99 @@
package dummydomain.yetanothercallblocker;
import android.content.Context;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import dummydomain.yetanothercallblocker.data.NumberInfo;
import dummydomain.yetanothercallblocker.data.NumberInfoService;
import dummydomain.yetanothercallblocker.event.CallEndedEvent;
import dummydomain.yetanothercallblocker.event.CallOngoingEvent;
import dummydomain.yetanothercallblocker.utils.PhoneUtils;
import static dummydomain.yetanothercallblocker.EventUtils.postEvent;
public class PhoneStateHandler {
private static final Logger LOG = LoggerFactory.getLogger(PhoneStateHandler.class);
private final Settings settings;
private final NumberInfoService numberInfoService;
private boolean isOffHook;
public PhoneStateHandler(Settings settings, NumberInfoService numberInfoService) {
this.settings = settings;
this.numberInfoService = numberInfoService;
}
public void onRinging(Context context, String phoneNumber) {
LOG.debug("onRinging({})", phoneNumber);
if (phoneNumber == null) {
if (!PermissionHelper.hasNumberInfoPermissions(context)) {
LOG.warn("No info permissions");
return;
}
return; // TODO: check
}
boolean blockingEnabled = settings.getCallBlockingEnabled();
boolean showNotifications = settings.getIncomingCallNotifications();
if (!blockingEnabled && !showNotifications) {
return;
}
NumberInfo numberInfo = numberInfoService.getNumberInfo(phoneNumber,
settings.getCachedAutoDetectedCountryCode(), false);
boolean blocked = false;
if (blockingEnabled && !isOffHook && numberInfoService.shouldBlock(numberInfo)) {
blocked = PhoneUtils.rejectCall(context);
if (blocked) {
notifyBlocked(context, numberInfo);
numberInfoService.blockedCall(numberInfo);
postEvent(new CallEndedEvent());
}
}
if (!blocked && showNotifications) {
startIndication(context, numberInfo);
}
}
public void onOffHook(Context context, String phoneNumber) {
LOG.debug("onOffHook({})", phoneNumber);
isOffHook = true;
postEvent(new CallOngoingEvent());
}
public void onIdle(Context context, String phoneNumber) {
LOG.debug("onIdle({})", phoneNumber);
isOffHook = false;
stopAllIndication(context);
postEvent(new CallEndedEvent());
}
private void startIndication(Context context, NumberInfo numberInfo) {
NotificationHelper.showIncomingCallNotification(context, numberInfo);
}
private void stopAllIndication(Context context) {
NotificationHelper.hideIncomingCallNotification(context);
}
private void notifyBlocked(Context context, NumberInfo numberInfo) {
NotificationHelper.showBlockedCallNotification(context, numberInfo);
}
}

View File

@ -22,6 +22,7 @@ public class Settings extends GenericSettings {
public static final String PREF_USE_CONTACTS = "useContacts";
public static final String PREF_UI_MODE = "uiMode";
public static final String PREF_NUMBER_OF_RECENT_CALLS = "numberOfRecentCalls";
public static final String PREF_USE_MONITORING_SERVICE = "useMonitoringService";
public static final String PREF_NOTIFICATIONS_KNOWN = "showNotificationsForKnownCallers";
public static final String PREF_NOTIFICATIONS_UNKNOWN = "showNotificationsForUnknownCallers";
public static final String PREF_LAST_UPDATE_TIME = "lastUpdateTime";
@ -161,6 +162,14 @@ public class Settings extends GenericSettings {
setInt(PREF_NUMBER_OF_RECENT_CALLS, number);
}
public boolean getUseMonitoringService() {
return getBoolean(PREF_USE_MONITORING_SERVICE);
}
public void setUseMonitoringService(boolean use) {
setBoolean(PREF_USE_MONITORING_SERVICE, use);
}
public boolean getNotificationsForKnownCallers() {
return getBoolean(PREF_NOTIFICATIONS_KNOWN);
}

View File

@ -1,6 +1,7 @@
package dummydomain.yetanothercallblocker;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
@ -31,6 +32,7 @@ import java.util.regex.Pattern;
import dummydomain.yetanothercallblocker.preference.IntListPreference;
import dummydomain.yetanothercallblocker.utils.DebuggingUtils;
import dummydomain.yetanothercallblocker.utils.FileUtils;
import dummydomain.yetanothercallblocker.utils.PackageManagerUtils;
import dummydomain.yetanothercallblocker.work.UpdateScheduler;
import static java.util.Objects.requireNonNull;
@ -180,6 +182,23 @@ public class SettingsActivity extends AppCompatActivity
callScreeningPref.setVisible(false);
}
SwitchPreferenceCompat monitoringPref =
requireNonNull(findPreference(Settings.PREF_USE_MONITORING_SERVICE));
monitoringPref.setOnPreferenceChangeListener((preference, newValue) -> {
boolean enabled = Boolean.TRUE.equals(newValue);
Context context = requireContext();
PackageManagerUtils.setComponentEnabledOrDefault(
context, StartupReceiver.class, enabled);
if (enabled) {
CallMonitoringService.start(context);
} else {
CallMonitoringService.stop(context);
}
return true;
});
SwitchPreferenceCompat nonPersistentAutoUpdatePref =
requireNonNull(findPreference(PREF_AUTO_UPDATE_ENABLED));
nonPersistentAutoUpdatePref.setChecked(updateScheduler.isAutoUpdateScheduled());

View File

@ -0,0 +1,24 @@
package dummydomain.yetanothercallblocker;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class StartupReceiver extends BroadcastReceiver {
private static final Logger LOG = LoggerFactory.getLogger(StartupReceiver.class);
@SuppressLint("UnsafeProtectedBroadcastReceiver")
@Override
public void onReceive(Context context, Intent intent) {
LOG.debug("onReceive({})", intent);
// this broadcast receiver is used to start CallMonitoringService at boot or after app update
// the actual starting happens in App
}
}

View File

@ -6,6 +6,7 @@ import android.text.TextUtils;
import java.util.concurrent.TimeUnit;
import dummydomain.yetanothercallblocker.PermissionHelper;
import dummydomain.yetanothercallblocker.PhoneStateHandler;
import dummydomain.yetanothercallblocker.data.db.BlacklistDao;
import dummydomain.yetanothercallblocker.data.db.YacbDaoSessionFactory;
import dummydomain.yetanothercallblocker.sia.Settings;
@ -146,6 +147,8 @@ public class Config {
settings, NumberUtils::isHiddenNumber, NumberUtils::normalizeNumber,
communityDatabase, featuredDatabase, contactsProvider, blacklistService);
YacbHolder.setNumberInfoService(numberInfoService);
YacbHolder.setPhoneStateHandler(new PhoneStateHandler(settings, numberInfoService));
}
}

View File

@ -1,5 +1,6 @@
package dummydomain.yetanothercallblocker.data;
import dummydomain.yetanothercallblocker.PhoneStateHandler;
import dummydomain.yetanothercallblocker.data.db.BlacklistDao;
import dummydomain.yetanothercallblocker.sia.model.CommunityReviewsLoader;
import dummydomain.yetanothercallblocker.sia.model.SiaMetadata;
@ -22,6 +23,8 @@ public class YacbHolder {
private static NumberInfoService numberInfoService;
private static PhoneStateHandler phoneStateHandler;
static void setWebService(WebService webService) {
YacbHolder.webService = webService;
}
@ -58,6 +61,10 @@ public class YacbHolder {
YacbHolder.numberInfoService = numberInfoService;
}
static void setPhoneStateHandler(PhoneStateHandler phoneStateHandler) {
YacbHolder.phoneStateHandler = phoneStateHandler;
}
public static WebService getWebService() {
return webService;
}
@ -94,6 +101,10 @@ public class YacbHolder {
return numberInfoService;
}
public static PhoneStateHandler getPhoneStateHandler() {
return phoneStateHandler;
}
public static NumberInfo getNumberInfo(String number, String countryCode) {
return numberInfoService.getNumberInfo(number, countryCode, true);
}

View File

@ -0,0 +1,34 @@
package dummydomain.yetanothercallblocker.utils;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
public class PackageManagerUtils {
public static boolean isComponentEnabled(Context ctx, Class<?> cls) {
return isComponentEnabled(ctx, new ComponentName(ctx, cls));
}
public static void setComponentEnabledOrDefault(Context ctx, Class<?> cls, boolean enabled) {
setComponentEnabledOrDefault(ctx, new ComponentName(ctx, cls), enabled);
}
public static boolean isComponentEnabled(Context ctx, ComponentName componentName) {
return getPackageManager(ctx).getComponentEnabledSetting(componentName)
== PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
}
public static void setComponentEnabledOrDefault(Context ctx, ComponentName componentName,
boolean enabled) {
getPackageManager(ctx).setComponentEnabledSetting(componentName, enabled
? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
PackageManager.DONT_KILL_APP);
}
private static PackageManager getPackageManager(Context ctx) {
return ctx.getPackageManager();
}
}

View File

@ -0,0 +1,67 @@
package dummydomain.yetanothercallblocker.utils;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
import android.telecom.TelecomManager;
import android.telephony.TelephonyManager;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import com.android.internal.telephony.ITelephony;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
import static java.util.Objects.requireNonNull;
public class PhoneUtils {
private static final Logger LOG = LoggerFactory.getLogger(PhoneUtils.class);
public static boolean rejectCall(@NonNull Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
try {
TelecomManager telecomManager = requireNonNull(
(TelecomManager) context.getSystemService(Context.TELECOM_SERVICE));
telecomManagerEndCall(telecomManager);
LOG.info("Rejected call using TelecomManager");
return true;
} catch (Exception e) {
LOG.warn("Error while rejecting call on API 28+", e);
}
}
try {
TelephonyManager tm = requireNonNull(
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE));
@SuppressLint("DiscouragedPrivateApi") // no choice
Method m = tm.getClass().getDeclaredMethod("getITelephony");
m.setAccessible(true);
ITelephony telephony = requireNonNull((ITelephony) m.invoke(tm));
telephony.endCall();
LOG.info("Rejected call using ITelephony");
return true;
} catch (Exception e) {
LOG.warn("Error while rejecting call", e);
}
return false;
}
@SuppressWarnings({"deprecation", "RedundantSuppression"}) // no choice
@SuppressLint("MissingPermission") // maybe shouldn't
@RequiresApi(Build.VERSION_CODES.P)
private static void telecomManagerEndCall(TelecomManager telecomManager) {
telecomManager.endCall();
}
}

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M12,1L3,5v6c0,5.55 3.84,10.74 9,12 5.16,-1.26 9,-6.45 9,-12L21,5l-9,-4zM12,11.99h7c-0.53,4.12 -3.28,7.79 -7,8.94L12,12L5,12L5,6.3l7,-3.11v8.8z" />
</vector>

View File

@ -159,7 +159,6 @@
<string name="notification_channel_name_neutral">Ουδέτερες κλήσεις</string>
<string name="notification_channel_name_positive">Θετικές κλήσεις</string>
<string name="notification_channel_name_known">Γνωστές κλήσεις</string>
<string name="notification_channel_group_name_tasks">Καθήκοντα</string>
<string name="notification_channel_group_name_blocked_calls">Αποκλεισμένες κλήσεις</string>
<string name="notification_channel_group_name_incoming_calls">Εισερχόμενες κλήσεις</string>
</resources>

View File

@ -3,7 +3,6 @@
<string name="notification_channel_group_name_incoming_calls">Llamadas entrantes</string>
<string name="notification_channel_group_name_blocked_calls">Llamadas bloqueadas</string>
<string name="notification_channel_group_name_tasks">Tareas</string>
<string name="notification_channel_name_known">Llamadas conocidas</string>
<string name="notification_channel_name_positive">Llamadas positivas</string>

View File

@ -3,7 +3,6 @@
<string name="app_name">Yet Another Call Blocker</string>
<string name="notification_channel_group_name_incoming_calls">Appels entrants</string>
<string name="notification_channel_group_name_blocked_calls">Appels bloqués</string>
<string name="notification_channel_group_name_tasks">Tâches</string>
<string name="notification_channel_name_known">Appels identifiés</string>
<string name="notification_channel_name_positive">Appels positifs</string>
<string name="notification_channel_name_neutral">Appels neutres</string>

View File

@ -122,7 +122,6 @@
<string name="title_activity_reviews">Recenzije</string>
<string name="blacklist_item_no_calls">Nikada nazvan</string>
<string name="block_hidden_number">Blokiraj skrivene brojeve</string>
<string name="notification_channel_group_name_tasks">Zadaci</string>
<string name="sia_category_telemarketer">Telemarketing</string>
<string name="call_log_permission_message">Odobri dozvolu „Telefon” za prikaz nedavnih poziva</string>
<string name="edit_blacklist_item_number_pattern">Format broja</string>

View File

@ -14,7 +14,6 @@
<string name="notification_channel_name_neutral">Numeri neutro</string>
<string name="notification_channel_name_positive">Numeri buoni</string>
<string name="notification_channel_name_known">Chiamate conosciute</string>
<string name="notification_channel_group_name_tasks">Azioni</string>
<string name="notification_channel_group_name_blocked_calls">Chiamate bloccate</string>
<string name="notification_channel_group_name_incoming_calls">Chiamate in entrata</string>
<string name="app_name">Yet Another Call Blocker</string>

View File

@ -149,7 +149,6 @@
<string name="notification_channel_name_neutral">Nøytrale anrop</string>
<string name="notification_channel_name_positive">Positive anrop</string>
<string name="notification_channel_name_known">Kjente anrop</string>
<string name="notification_channel_group_name_tasks">Gjøremål</string>
<string name="notification_channel_group_name_blocked_calls">Blokkerte anrop</string>
<string name="notification_channel_group_name_incoming_calls">Innkommende anrop</string>
</resources>

View File

@ -3,7 +3,6 @@
<string name="notification_channel_group_name_incoming_calls">Inkomende oproepen</string>
<string name="notification_channel_group_name_blocked_calls">Geblokkeerde oproepen</string>
<string name="notification_channel_group_name_tasks">Taken</string>
<string name="notification_channel_name_known">Bekende oproepen</string>
<string name="notification_channel_name_positive">Gewenste oproepen</string>

View File

@ -126,6 +126,5 @@
<string name="notification_channel_name_tasks">Zadania</string>
<string name="notification_channel_group_name_blocked_calls">Zablokowane połączenia</string>
<string name="notification_channel_group_name_incoming_calls">Połączenia przychodzące</string>
<string name="notification_channel_group_name_tasks">Zadania</string>
<string name="app_name">Yet Another Call Blocker</string>
</resources>

View File

@ -162,5 +162,11 @@
<string name="sia_category_safe_nonprofit">Безопасная некоммерческая организация</string>
<string name="sia_category_safe_company">Безопасная компания</string>
<string name="notification_channel_name_tasks">Задачи</string>
<string name="notification_channel_group_name_tasks">Задачи</string>
<string name="notification_channel_group_name_services">Службы</string>
<string name="notification_channel_name_monitoring_service">Служба мониторинга</string>
<string name="use_monitoring_service">Использовать службу мониторинга</string>
<string name="use_monitoring_service_summary">Включает постоянно запущенную службу мониторинга, которая помогает получать события телефонии на некоторых телефонах. Включайте эту функцию, только если блокирование вызовов и информационные уведомления не работают. Эта функция никак не влияет на расход энергии</string>
<string name="monitoring_service_label">Служба мониторинга вызовов</string>
<string name="monitoring_service_description">Служба для мониторинга состояния телефона в фоне</string>
<string name="monitoring_service_notification_title">Мониторинг вызовов</string>
</resources>

View File

@ -157,5 +157,4 @@
<string name="lookup_hint_input_number">Номер телефону</string>
<string name="db_management_activity_label">Керування базою</string>
<string name="notification_channel_name_tasks">Завдання</string>
<string name="notification_channel_group_name_tasks">Завдання</string>
</resources>

View File

@ -3,7 +3,7 @@
<string name="notification_channel_group_name_incoming_calls">Incoming calls</string>
<string name="notification_channel_group_name_blocked_calls">Blocked calls</string>
<string name="notification_channel_group_name_tasks">Tasks</string>
<string name="notification_channel_group_name_services">Services</string>
<string name="notification_channel_name_known">Known calls</string>
<string name="notification_channel_name_positive">Positive calls</string>
@ -11,6 +11,7 @@
<string name="notification_channel_name_unknown">Unknown calls</string>
<string name="notification_channel_name_negative">Negative calls</string>
<string name="notification_channel_name_blocked_info">Blocked info</string>
<string name="notification_channel_name_monitoring_service">Monitoring service</string>
<string name="notification_channel_name_tasks">Tasks</string>
<string name="notification_incoming_call_contact">Contact</string>
@ -103,6 +104,8 @@
<string name="use_call_screening_service">Advanced call blocking mode</string>
<string name="use_call_screening_service_summary">Allows to block calls before the phone starts to ring. Requires the app to be set as the \"Phone app\" (Android 79) or as the \"Caller ID app\" (Android 10+)</string>
<string name="use_call_screening_service_disable_message">Select different \"Phone app\" or \"Caller ID app\" in Settings - Apps - Default apps</string>
<string name="use_monitoring_service">Use a monitoring service</string>
<string name="use_monitoring_service_summary">Enables an always-running monitoring service that helps with receiving telephony events on some phones. Enable this feature only if call blocking and informational notifications don\'t work. This feature has no effect on battery life</string>
<string name="auto_updates">Auto-update database</string>
<string name="auto_updates_summary">Automatically receive daily DB updates (these are incremental/delta updates, so they consume very little traffic)</string>
<string name="use_contacts">Use contacts</string>
@ -182,4 +185,8 @@
<string name="lookup_number_not_found">Not found</string>
<string name="lookup_res_category">Category: %d</string>
<string name="lookup_res_featured_name">Featured name: %s</string>
<string name="monitoring_service_label">Call monitoring service</string>
<string name="monitoring_service_description">A service for monitoring phone state in background</string>
<string name="monitoring_service_notification_title">Monitoring calls</string>
</resources>

View File

@ -50,6 +50,11 @@
app:title="@string/use_call_screening_service" />
</PreferenceCategory>
<SwitchPreferenceCompat
app:key="useMonitoringService"
app:summary="@string/use_monitoring_service_summary"
app:title="@string/use_monitoring_service" />
<PreferenceCategory
app:key="categoryNotifications"
app:title="@string/settings_category_notifications_incoming_calls">