Improve hidden number detection

This commit is contained in:
xynngh 2020-09-10 16:26:20 +04:00
parent 24f4dd5881
commit fa5b616573
5 changed files with 75 additions and 33 deletions

View File

@ -27,7 +27,8 @@ public class CallMonitoringService extends Service {
private static final Logger LOG = LoggerFactory.getLogger(CallMonitoringService.class);
private final MyPhoneStateListener phoneStateListener = new MyPhoneStateListener();
private final CallReceiver callReceiver = new CallReceiver(true);
private final CallReceiver callReceiver = new CallReceiver(
PhoneStateHandler.Source.PHONE_STATE_BROADCAST_RECEIVER_MONITORING);
private boolean monitoringStarted;
@ -135,15 +136,17 @@ public class CallMonitoringService extends Service {
}
PhoneStateHandler phoneStateHandler = YacbHolder.getPhoneStateHandler();
PhoneStateHandler.Source source = PhoneStateHandler.Source.PHONE_STATE_LISTENER;
switch (state) {
case TelephonyManager.CALL_STATE_IDLE:
phoneStateHandler.onIdle(phoneNumber);
phoneStateHandler.onIdle(source, phoneNumber);
break;
case TelephonyManager.CALL_STATE_RINGING:
phoneStateHandler.onRinging(phoneNumber);
phoneStateHandler.onRinging(source, phoneNumber);
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
phoneStateHandler.onOffHook(phoneNumber);
phoneStateHandler.onOffHook(source, phoneNumber);
break;
}
}

View File

@ -15,20 +15,20 @@ public class CallReceiver extends BroadcastReceiver {
private static final Logger LOG = LoggerFactory.getLogger(CallReceiver.class);
private final boolean monitoringService;
private final PhoneStateHandler.Source source;
@SuppressWarnings({"unused", "RedundantSuppression"}) // required for BroadcastReceivers
public CallReceiver() {
this(false);
this(PhoneStateHandler.Source.PHONE_STATE_BROADCAST_RECEIVER);
}
public CallReceiver(boolean monitoringService) {
this.monitoringService = monitoringService;
public CallReceiver(PhoneStateHandler.Source source) {
this.source = source;
}
@Override
public void onReceive(Context context, Intent intent) {
LOG.debug("onReceive() invoked, monitoringService={}", monitoringService);
LOG.debug("onReceive() invoked, source={}", source);
if (!TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(intent.getAction())
&& !TelephonyManager.EXTRA_STATE_RINGING.equals(intent.getAction())) { // TODO: check
@ -58,11 +58,11 @@ public class CallReceiver extends BroadcastReceiver {
PhoneStateHandler phoneStateHandler = YacbHolder.getPhoneStateHandler();
if (TelephonyManager.EXTRA_STATE_RINGING.equals(telephonyExtraState)) {
phoneStateHandler.onRinging(incomingNumber);
phoneStateHandler.onRinging(source, incomingNumber);
} else if (TelephonyManager.EXTRA_STATE_OFFHOOK.equals(telephonyExtraState)) {
phoneStateHandler.onOffHook(incomingNumber);
phoneStateHandler.onOffHook(source, incomingNumber);
} else if (TelephonyManager.EXTRA_STATE_IDLE.equals(telephonyExtraState)) {
phoneStateHandler.onIdle(incomingNumber);
phoneStateHandler.onIdle(source, incomingNumber);
}
}

View File

@ -3,6 +3,8 @@ package dummydomain.yetanothercallblocker;
import android.content.Context;
import android.text.TextUtils;
import androidx.core.util.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -21,6 +23,12 @@ import static dummydomain.yetanothercallblocker.EventUtils.postEvent;
public class PhoneStateHandler {
public enum Source {
PHONE_STATE_LISTENER,
PHONE_STATE_BROADCAST_RECEIVER_MONITORING,
PHONE_STATE_BROADCAST_RECEIVER
}
private static final Logger LOG = LoggerFactory.getLogger(PhoneStateHandler.class);
private final Context context;
@ -42,8 +50,8 @@ public class PhoneStateHandler {
this.notificationService = notificationService;
}
public void onRinging(String phoneNumber) {
LOG.debug("onRinging({})", phoneNumber);
public void onRinging(Source source, String phoneNumber) {
LOG.debug("onRinging({}, \"{}\")", source, phoneNumber);
boolean ignore = false;
@ -53,17 +61,30 @@ public class PhoneStateHandler {
return;
}
// TODO: check
LOG.debug("onRinging() ignoring null");
ignore = true;
if (source == Source.PHONE_STATE_LISTENER) {
LOG.info("onRinging() treating null from PhoneStateListener as a hidden number");
phoneNumber = "";
} else if ((source == Source.PHONE_STATE_BROADCAST_RECEIVER_MONITORING
|| source == Source.PHONE_STATE_BROADCAST_RECEIVER)
&& isEventPresent(sameSourceAndNumber(source, null))
&& !isEventPresent(nonEmptyNumber())) {
LOG.info("onRinging() treating repeated null from PhoneStateBroadcastReceiver" +
" as a hidden number");
phoneNumber = "";
}
if (phoneNumber == null) {
LOG.debug("onRinging() ignoring null");
ignore = true;
}
}
if (!ignore && !shouldProcess(phoneNumber)) {
if (!ignore && isEventPresent(sameNumber(phoneNumber))) {
LOG.debug("onRinging() ignoring repeated event");
ignore = true;
}
recordEvent(phoneNumber);
recordEvent(source, phoneNumber);
if (ignore) return;
@ -95,16 +116,16 @@ public class PhoneStateHandler {
}
}
public void onOffHook(String phoneNumber) {
LOG.debug("onOffHook({})", phoneNumber);
public void onOffHook(Source source, String phoneNumber) {
LOG.debug("onOffHook({}, \"{}\")", source, phoneNumber);
isOffHook = true;
postEvent(new CallOngoingEvent());
}
public void onIdle(String phoneNumber) {
LOG.debug("onIdle({})", phoneNumber);
public void onIdle(Source source, String phoneNumber) {
LOG.debug("onIdle({}, \"{}\")", source, phoneNumber);
isOffHook = false;
@ -113,38 +134,56 @@ public class PhoneStateHandler {
postEvent(new CallEndedEvent());
}
private boolean shouldProcess(String phoneNumber) {
private static Predicate<CallEvent> sameNumber(String number) {
return event -> TextUtils.equals(event.number, number);
}
private static Predicate<CallEvent> sameSourceAndNumber(Source source, String number) {
return event -> event.source == source && TextUtils.equals(event.number, number);
}
private static Predicate<CallEvent> nonEmptyNumber() {
return event -> !TextUtils.isEmpty(event.number);
}
private boolean isEventPresent(Predicate<CallEvent> predicate) {
return findEvent(predicate) != null;
}
private CallEvent findEvent(Predicate<CallEvent> predicate) {
// using 1 second ago as the cutoff point - consider everything older as unrelated events
long cutoff = System.nanoTime() - TimeUnit.SECONDS.toNanos(1);
if (lastEventTimestamp - cutoff < 0) { // no events in the last second
lastEvents.clear();
return true;
return null;
}
for (ListIterator<CallEvent> it = lastEvents.listIterator(); it.hasNext(); ) {
CallEvent event = it.next();
if (event.timestamp - cutoff < 0) { // event is older than the cutoff point
it.remove();
} else if (TextUtils.equals(event.number, phoneNumber)) {
return false; // don't process same event
} else if (predicate.test(event)) {
return event;
}
}
return true;
return null;
}
private void recordEvent(String phoneNumber) {
private void recordEvent(Source source, String phoneNumber) {
long currentTimestamp = System.nanoTime();
lastEvents.add(new CallEvent(phoneNumber, currentTimestamp));
lastEvents.add(new CallEvent(source, phoneNumber, currentTimestamp));
lastEventTimestamp = currentTimestamp;
}
private static class CallEvent {
final Source source;
final String number;
final long timestamp;
public CallEvent(String number, long timestamp) {
public CallEvent(Source source, String number, long timestamp) {
this.source = source;
this.number = number;
this.timestamp = timestamp;
}

View File

@ -24,7 +24,7 @@
<string name="block_negative_sia">Блокировать по отзывам</string>
<string name="block_negative_sia_numbers_summary">Блокировать звонки c номеров с отрицательным рейтингом</string>
<string name="block_hidden_number">Блокировать скрытые номера</string>
<string name="block_hidden_number_summary">(Экспериментальная функция.) Блокировать звонки со скрытых номеров. Вероятно, работает лучше в \"Продвинутом режиме блокирования вызовов\". Пожалуйста, сообщите о своём опыте использования в репозитории на GitLab</string>
<string name="block_hidden_number_summary">Блокировать звонки со скрытых номеров. Может работать по-другому (лучше или хуже) в \"Продвинутом режиме блокирования вызовов\"</string>
<string name="block_blacklisted_short">Блокировать из ЧС</string>
<string name="block_blacklisted">Блокировать из чёрного списка</string>
<string name="block_blacklisted_summary">Блокировать звонки с номеров добавленных в чёрный список</string>

View File

@ -97,7 +97,7 @@
<string name="block_negative_sia">Block based on rating</string>
<string name="block_negative_sia_numbers_summary">Block calls from numbers with negative rating (based on a community database)</string>
<string name="block_hidden_number">Block hidden numbers</string>
<string name="block_hidden_number_summary">(Experimental.) Block calls from hidden numbers. Probably works better in \"Advanced call blocking mode\". Please report your experience on GitLab</string>
<string name="block_hidden_number_summary">Block calls from hidden numbers. May work differently (better or worse) in \"Advanced call blocking mode\"</string>
<string name="block_blacklisted_short">Block blacklisted</string>
<string name="block_blacklisted">Block blacklisted numbers</string>
<string name="block_blacklisted_summary">Block calls from numbers added to the blacklist</string>