Add monitoring service
This commit is contained in:
parent
cf0ad91fb7
commit
b06a3d47fb
|
@ -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"
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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 7–9) 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>
|
||||
|
|
|
@ -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">
|
||||
|
|
Loading…
Reference in New Issue