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

View File

@ -15,20 +15,20 @@ public class CallReceiver extends BroadcastReceiver {
private static final Logger LOG = LoggerFactory.getLogger(CallReceiver.class); private static final Logger LOG = LoggerFactory.getLogger(CallReceiver.class);
private final boolean monitoringService; private final PhoneStateHandler.Source source;
@SuppressWarnings({"unused", "RedundantSuppression"}) // required for BroadcastReceivers @SuppressWarnings({"unused", "RedundantSuppression"}) // required for BroadcastReceivers
public CallReceiver() { public CallReceiver() {
this(false); this(PhoneStateHandler.Source.PHONE_STATE_BROADCAST_RECEIVER);
} }
public CallReceiver(boolean monitoringService) { public CallReceiver(PhoneStateHandler.Source source) {
this.monitoringService = monitoringService; this.source = source;
} }
@Override @Override
public void onReceive(Context context, Intent intent) { 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()) if (!TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(intent.getAction())
&& !TelephonyManager.EXTRA_STATE_RINGING.equals(intent.getAction())) { // TODO: check && !TelephonyManager.EXTRA_STATE_RINGING.equals(intent.getAction())) { // TODO: check
@ -58,11 +58,11 @@ public class CallReceiver extends BroadcastReceiver {
PhoneStateHandler phoneStateHandler = YacbHolder.getPhoneStateHandler(); PhoneStateHandler phoneStateHandler = YacbHolder.getPhoneStateHandler();
if (TelephonyManager.EXTRA_STATE_RINGING.equals(telephonyExtraState)) { if (TelephonyManager.EXTRA_STATE_RINGING.equals(telephonyExtraState)) {
phoneStateHandler.onRinging(incomingNumber); phoneStateHandler.onRinging(source, incomingNumber);
} else if (TelephonyManager.EXTRA_STATE_OFFHOOK.equals(telephonyExtraState)) { } else if (TelephonyManager.EXTRA_STATE_OFFHOOK.equals(telephonyExtraState)) {
phoneStateHandler.onOffHook(incomingNumber); phoneStateHandler.onOffHook(source, incomingNumber);
} else if (TelephonyManager.EXTRA_STATE_IDLE.equals(telephonyExtraState)) { } 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.content.Context;
import android.text.TextUtils; import android.text.TextUtils;
import androidx.core.util.Predicate;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -21,6 +23,12 @@ import static dummydomain.yetanothercallblocker.EventUtils.postEvent;
public class PhoneStateHandler { 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 static final Logger LOG = LoggerFactory.getLogger(PhoneStateHandler.class);
private final Context context; private final Context context;
@ -42,8 +50,8 @@ public class PhoneStateHandler {
this.notificationService = notificationService; this.notificationService = notificationService;
} }
public void onRinging(String phoneNumber) { public void onRinging(Source source, String phoneNumber) {
LOG.debug("onRinging({})", phoneNumber); LOG.debug("onRinging({}, \"{}\")", source, phoneNumber);
boolean ignore = false; boolean ignore = false;
@ -53,17 +61,30 @@ public class PhoneStateHandler {
return; return;
} }
// TODO: check 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"); LOG.debug("onRinging() ignoring null");
ignore = true; ignore = true;
} }
}
if (!ignore && !shouldProcess(phoneNumber)) { if (!ignore && isEventPresent(sameNumber(phoneNumber))) {
LOG.debug("onRinging() ignoring repeated event"); LOG.debug("onRinging() ignoring repeated event");
ignore = true; ignore = true;
} }
recordEvent(phoneNumber); recordEvent(source, phoneNumber);
if (ignore) return; if (ignore) return;
@ -95,16 +116,16 @@ public class PhoneStateHandler {
} }
} }
public void onOffHook(String phoneNumber) { public void onOffHook(Source source, String phoneNumber) {
LOG.debug("onOffHook({})", phoneNumber); LOG.debug("onOffHook({}, \"{}\")", source, phoneNumber);
isOffHook = true; isOffHook = true;
postEvent(new CallOngoingEvent()); postEvent(new CallOngoingEvent());
} }
public void onIdle(String phoneNumber) { public void onIdle(Source source, String phoneNumber) {
LOG.debug("onIdle({})", phoneNumber); LOG.debug("onIdle({}, \"{}\")", source, phoneNumber);
isOffHook = false; isOffHook = false;
@ -113,38 +134,56 @@ public class PhoneStateHandler {
postEvent(new CallEndedEvent()); 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 // using 1 second ago as the cutoff point - consider everything older as unrelated events
long cutoff = System.nanoTime() - TimeUnit.SECONDS.toNanos(1); long cutoff = System.nanoTime() - TimeUnit.SECONDS.toNanos(1);
if (lastEventTimestamp - cutoff < 0) { // no events in the last second if (lastEventTimestamp - cutoff < 0) { // no events in the last second
lastEvents.clear(); lastEvents.clear();
return true; return null;
} }
for (ListIterator<CallEvent> it = lastEvents.listIterator(); it.hasNext(); ) { for (ListIterator<CallEvent> it = lastEvents.listIterator(); it.hasNext(); ) {
CallEvent event = it.next(); CallEvent event = it.next();
if (event.timestamp - cutoff < 0) { // event is older than the cutoff point if (event.timestamp - cutoff < 0) { // event is older than the cutoff point
it.remove(); it.remove();
} else if (TextUtils.equals(event.number, phoneNumber)) { } else if (predicate.test(event)) {
return false; // don't process same event return event;
} }
} }
return true; return null;
} }
private void recordEvent(String phoneNumber) { private void recordEvent(Source source, String phoneNumber) {
long currentTimestamp = System.nanoTime(); long currentTimestamp = System.nanoTime();
lastEvents.add(new CallEvent(phoneNumber, currentTimestamp)); lastEvents.add(new CallEvent(source, phoneNumber, currentTimestamp));
lastEventTimestamp = currentTimestamp; lastEventTimestamp = currentTimestamp;
} }
private static class CallEvent { private static class CallEvent {
final Source source;
final String number; final String number;
final long timestamp; final long timestamp;
public CallEvent(String number, long timestamp) { public CallEvent(Source source, String number, long timestamp) {
this.source = source;
this.number = number; this.number = number;
this.timestamp = timestamp; this.timestamp = timestamp;
} }

View File

@ -24,7 +24,7 @@
<string name="block_negative_sia">Блокировать по отзывам</string> <string name="block_negative_sia">Блокировать по отзывам</string>
<string name="block_negative_sia_numbers_summary">Блокировать звонки c номеров с отрицательным рейтингом</string> <string name="block_negative_sia_numbers_summary">Блокировать звонки c номеров с отрицательным рейтингом</string>
<string name="block_hidden_number">Блокировать скрытые номера</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_short">Блокировать из ЧС</string>
<string name="block_blacklisted">Блокировать из чёрного списка</string> <string name="block_blacklisted">Блокировать из чёрного списка</string>
<string name="block_blacklisted_summary">Блокировать звонки с номеров добавленных в чёрный список</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">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_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">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_short">Block blacklisted</string>
<string name="block_blacklisted">Block blacklisted numbers</string> <string name="block_blacklisted">Block blacklisted numbers</string>
<string name="block_blacklisted_summary">Block calls from numbers added to the blacklist</string> <string name="block_blacklisted_summary">Block calls from numbers added to the blacklist</string>