Add local blacklist
This commit is contained in:
parent
7fac34efb1
commit
636d56bdc2
|
@ -1,4 +1,5 @@
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'org.greenrobot.greendao'
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
|
@ -31,6 +32,10 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
greendao {
|
||||
schemaVersion 1
|
||||
}
|
||||
|
||||
dependencies {
|
||||
def eventbus_version = '3.2.0'
|
||||
|
||||
|
@ -43,9 +48,11 @@ dependencies {
|
|||
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||
implementation 'androidx.recyclerview:recyclerview-selection:1.0.0'
|
||||
implementation 'com.google.android.material:material:1.1.0'
|
||||
implementation 'androidx.preference:preference:1.1.1'
|
||||
implementation 'androidx.work:work-runtime:2.4.0'
|
||||
implementation 'org.greenrobot:greendao:3.3.0'
|
||||
implementation "org.greenrobot:eventbus:$eventbus_version"
|
||||
annotationProcessor "org.greenrobot:eventbus-annotation-processor:$eventbus_version"
|
||||
}
|
||||
|
|
|
@ -33,16 +33,34 @@
|
|||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".BlacklistActivity"
|
||||
android:label="@string/title_blacklist_activity"
|
||||
android:parentActivityName=".MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".MainActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".EditBlacklistItemActivity"
|
||||
android:label="@string/title_add_blacklist_item_activity"
|
||||
android:parentActivityName=".BlacklistActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".BlacklistActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
android:label="@string/title_settings_activity">
|
||||
android:label="@string/title_settings_activity"
|
||||
android:parentActivityName=".MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".MainActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".DebugActivity"
|
||||
android:label="@string/debug_activity_label">
|
||||
android:label="@string/debug_activity_label"
|
||||
android:parentActivityName=".MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".MainActivity" />
|
||||
|
|
|
@ -0,0 +1,217 @@
|
|||
package dummydomain.yetanothercallblocker;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.view.ActionMode;
|
||||
import androidx.recyclerview.selection.SelectionTracker;
|
||||
import androidx.recyclerview.selection.StorageStrategy;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import dummydomain.yetanothercallblocker.data.BlacklistService;
|
||||
import dummydomain.yetanothercallblocker.data.DatabaseSingleton;
|
||||
import dummydomain.yetanothercallblocker.data.db.BlacklistDao;
|
||||
import dummydomain.yetanothercallblocker.data.db.BlacklistItem;
|
||||
import dummydomain.yetanothercallblocker.event.BlacklistChangedEvent;
|
||||
|
||||
public class BlacklistActivity extends AppCompatActivity {
|
||||
|
||||
private final Settings settings = App.getSettings();
|
||||
private final BlacklistDao blacklistDao = DatabaseSingleton.getBlacklistDao();
|
||||
private final BlacklistService blacklistService = DatabaseSingleton.getBlacklistService();
|
||||
|
||||
private BlacklistItemRecyclerViewAdapter blacklistAdapter;
|
||||
|
||||
private SelectionTracker<Long> selectionTracker;
|
||||
private ActionMode.Callback actionModeCallback;
|
||||
private ActionMode actionMode;
|
||||
|
||||
private AsyncTask<Void, Void, List<BlacklistItem>> loadBlacklistTask;
|
||||
|
||||
public static Intent getIntent(Context context) {
|
||||
return new Intent(context, BlacklistActivity.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_blacklist);
|
||||
|
||||
blacklistAdapter = new BlacklistItemRecyclerViewAdapter(this::onItemClicked);
|
||||
RecyclerView recyclerView = findViewById(R.id.blacklistItemsList);
|
||||
recyclerView.setAdapter(blacklistAdapter);
|
||||
recyclerView.addItemDecoration(new CustomVerticalDivider(this));
|
||||
|
||||
selectionTracker = new SelectionTracker.Builder<>(
|
||||
"blacklistSelection", recyclerView,
|
||||
blacklistAdapter.getItemKeyProvider(),
|
||||
blacklistAdapter.getItemDetailsLookup(recyclerView),
|
||||
StorageStrategy.createLongStorage())
|
||||
.build();
|
||||
|
||||
blacklistAdapter.setSelectionTracker(selectionTracker);
|
||||
|
||||
actionModeCallback = new ActionMode.Callback() {
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||
mode.getMenuInflater().inflate(R.menu.activity_blacklist_action_mode, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
||||
if (item.getItemId() == R.id.menu_delete) {
|
||||
new AlertDialog.Builder(BlacklistActivity.this)
|
||||
.setTitle(R.string.are_you_sure)
|
||||
.setMessage(R.string.blacklist_delete_confirmation)
|
||||
.setPositiveButton(R.string.yes, (dialog, which) -> {
|
||||
if (selectionTracker.hasSelection()) {
|
||||
blacklistService.delete(selectionTracker.getSelection());
|
||||
selectionTracker.clearSelection();
|
||||
loadItems();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.no, null)
|
||||
.show();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyActionMode(ActionMode mode) {
|
||||
selectionTracker.clearSelection();
|
||||
actionMode = null;
|
||||
}
|
||||
};
|
||||
|
||||
selectionTracker.addObserver(new SelectionTracker.SelectionObserver<Long>() {
|
||||
@Override
|
||||
public void onItemStateChanged(@NonNull Long key, boolean selected) {
|
||||
if (selectionTracker.hasSelection()) {
|
||||
if (actionMode == null) {
|
||||
actionMode = startSupportActionMode(actionModeCallback);
|
||||
}
|
||||
} else {
|
||||
if (actionMode != null) {
|
||||
actionMode.finish();
|
||||
actionMode = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (actionMode != null) {
|
||||
actionMode.setTitle(getString(R.string.selected_count,
|
||||
selectionTracker.getSelection().size()));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
selectionTracker.onRestoreInstanceState(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.activity_blacklist, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
menu.findItem(R.id.menu_block_blacklisted).setChecked(
|
||||
settings.getBlockBlacklisted());
|
||||
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
|
||||
EventUtils.register(this);
|
||||
|
||||
loadItems();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
selectionTracker.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
EventUtils.unregister(this);
|
||||
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
cancelLoadingBlacklistTask();
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN_ORDERED)
|
||||
public void onBlacklistChanged(BlacklistChangedEvent blacklistChangedEvent) {
|
||||
loadItems();
|
||||
}
|
||||
|
||||
private void loadItems() {
|
||||
cancelLoadingBlacklistTask();
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
AsyncTask<Void, Void, List<BlacklistItem>> loadBlacklistTask = this.loadBlacklistTask
|
||||
= new AsyncTask<Void, Void, List<BlacklistItem>>() {
|
||||
@Override
|
||||
protected List<BlacklistItem> doInBackground(Void... voids) {
|
||||
return blacklistDao.detach(blacklistDao.loadAll());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(List<BlacklistItem> items) {
|
||||
blacklistAdapter.setItems(items);
|
||||
}
|
||||
};
|
||||
loadBlacklistTask.execute();
|
||||
}
|
||||
|
||||
private void cancelLoadingBlacklistTask() {
|
||||
if (loadBlacklistTask != null) {
|
||||
loadBlacklistTask.cancel(true);
|
||||
loadBlacklistTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void onBlockBlacklistedChanged(MenuItem item) {
|
||||
settings.setBlockBlacklisted(!item.isChecked());
|
||||
}
|
||||
|
||||
public void onAddClicked(View view) {
|
||||
startActivity(EditBlacklistItemActivity.getIntent(this, null, null));
|
||||
}
|
||||
|
||||
private void onItemClicked(BlacklistItem blacklistItem) {
|
||||
startActivity(EditBlacklistItemActivity.getIntent(this, blacklistItem.getId()));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
package dummydomain.yetanothercallblocker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
import androidx.core.util.ObjectsCompat;
|
||||
import androidx.recyclerview.selection.ItemDetailsLookup;
|
||||
import androidx.recyclerview.selection.ItemKeyProvider;
|
||||
import androidx.recyclerview.selection.SelectionTracker;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.List;
|
||||
|
||||
import dummydomain.yetanothercallblocker.data.db.BlacklistItem;
|
||||
|
||||
public class BlacklistItemRecyclerViewAdapter extends GenericRecyclerViewAdapter
|
||||
<BlacklistItem, BlacklistItemRecyclerViewAdapter.ViewHolder> {
|
||||
|
||||
private SelectionTracker<Long> selectionTracker;
|
||||
|
||||
public BlacklistItemRecyclerViewAdapter(
|
||||
@Nullable ListInteractionListener<BlacklistItem> listener) {
|
||||
super(listener);
|
||||
}
|
||||
|
||||
public void setSelectionTracker(SelectionTracker<Long> selectionTracker) {
|
||||
this.selectionTracker = selectionTracker;
|
||||
}
|
||||
|
||||
public ItemKeyProvider<Long> getItemKeyProvider() {
|
||||
return new ItemKeyProvider<Long>(ItemKeyProvider.SCOPE_MAPPED) {
|
||||
@Nullable
|
||||
@Override
|
||||
public Long getKey(int position) {
|
||||
return items.get(position).getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPosition(@NonNull Long key) {
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
BlacklistItem item = items.get(i);
|
||||
if (key.equals(item.getId())) return i;
|
||||
}
|
||||
return RecyclerView.NO_POSITION;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public ItemDetailsLookup<Long> getItemDetailsLookup(RecyclerView recyclerView) {
|
||||
return new ItemDetailsLookup<Long>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public ItemDetails<Long> getItemDetails(@NonNull MotionEvent e) {
|
||||
View view = recyclerView.findChildViewUnder(e.getX(), e.getY());
|
||||
if (view != null) {
|
||||
RecyclerView.ViewHolder holder = recyclerView.getChildViewHolder(view);
|
||||
if (holder instanceof BlacklistItemRecyclerViewAdapter.ViewHolder) {
|
||||
return ((BlacklistItemRecyclerViewAdapter.ViewHolder) holder).getItemDetails();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.blacklist_item, parent, false);
|
||||
return new BlacklistItemRecyclerViewAdapter.ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DiffUtilCallback getDiffUtilCallback(
|
||||
List<BlacklistItem> oldList, List<BlacklistItem> newList) {
|
||||
return new DiffUtilCallback(oldList, newList);
|
||||
}
|
||||
|
||||
class ViewHolder extends GenericRecyclerViewAdapter
|
||||
<BlacklistItem, BlacklistItemRecyclerViewAdapter.ViewHolder>.GenericViewHolder {
|
||||
|
||||
final TextView name, pattern, stats;
|
||||
final AppCompatImageView errorIcon;
|
||||
final DateFormat dateFormat, timeFormat;
|
||||
|
||||
ItemDetailsLookup.ItemDetails<Long> itemDetails;
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
|
||||
name = itemView.findViewById(R.id.name);
|
||||
pattern = itemView.findViewById(R.id.pattern);
|
||||
stats = itemView.findViewById(R.id.stats);
|
||||
errorIcon = itemView.findViewById(R.id.errorIcon);
|
||||
|
||||
dateFormat = android.text.format.DateFormat.getMediumDateFormat(itemView.getContext());
|
||||
timeFormat = android.text.format.DateFormat.getTimeFormat(itemView.getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
void bind(BlacklistItem item) {
|
||||
name.setText(item.getName());
|
||||
name.setVisibility(TextUtils.isEmpty(item.getName()) ? View.GONE : View.VISIBLE);
|
||||
|
||||
pattern.setText(item.getHumanReadablePattern());
|
||||
|
||||
if (item.getNumberOfCalls() > 0) {
|
||||
stats.setVisibility(View.VISIBLE);
|
||||
|
||||
Context context = stats.getContext();
|
||||
String dateString = item.getLastCallDate() != null
|
||||
? dateFormat.format(item.getLastCallDate()) + ' '
|
||||
+ timeFormat.format(item.getLastCallDate())
|
||||
: context.getString(R.string.blacklist_item_date_no_info);
|
||||
|
||||
stats.setText(context.getResources().getQuantityString(
|
||||
R.plurals.blacklist_item_stats, item.getNumberOfCalls(),
|
||||
item.getNumberOfCalls(), dateString));
|
||||
} else {
|
||||
stats.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
errorIcon.setVisibility(item.getInvalid() ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (selectionTracker != null) {
|
||||
itemView.setActivated(selectionTracker.isSelected(item.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
ItemDetailsLookup.ItemDetails<Long> getItemDetails() {
|
||||
if (itemDetails == null) {
|
||||
itemDetails = new ItemDetailsLookup.ItemDetails<Long>() {
|
||||
@Override
|
||||
public int getPosition() {
|
||||
return getAdapterPosition();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Long getSelectionKey() {
|
||||
int position = getAdapterPosition();
|
||||
return position != RecyclerView.NO_POSITION
|
||||
? items.get(position).getId() : null;
|
||||
}
|
||||
};
|
||||
}
|
||||
return itemDetails;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " '" + pattern.getText() + "'";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class DiffUtilCallback
|
||||
extends GenericRecyclerViewAdapter.GenericDiffUtilCallback<BlacklistItem> {
|
||||
|
||||
DiffUtilCallback(List<BlacklistItem> oldList, List<BlacklistItem> newList) {
|
||||
super(oldList, newList);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean areItemsTheSame(BlacklistItem oldItem, BlacklistItem newItem) {
|
||||
if (oldItem.getId() != null || newItem.getId() != null) {
|
||||
return ObjectsCompat.equals(oldItem.getId(), newItem.getId());
|
||||
}
|
||||
|
||||
return ObjectsCompat.equals(oldItem.getPattern(), newItem.getPattern());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean areContentsTheSame(BlacklistItem oldItem, BlacklistItem newItem) {
|
||||
return ObjectsCompat.equals(oldItem.getPattern(), newItem.getPattern())
|
||||
&& ObjectsCompat.equals(oldItem.getName(), newItem.getName())
|
||||
&& oldItem.getNumberOfCalls() == newItem.getNumberOfCalls()
|
||||
&& ObjectsCompat.equals(oldItem.getLastCallDate(), newItem.getLastCallDate());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -10,63 +10,16 @@ import android.widget.TextView;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import dummydomain.yetanothercallblocker.data.CallLogItem;
|
||||
|
||||
public class CallLogItemRecyclerViewAdapter
|
||||
extends RecyclerView.Adapter<CallLogItemRecyclerViewAdapter.ViewHolder> {
|
||||
public class CallLogItemRecyclerViewAdapter extends GenericRecyclerViewAdapter
|
||||
<CallLogItem, CallLogItemRecyclerViewAdapter.ViewHolder> {
|
||||
|
||||
public interface OnListInteractionListener {
|
||||
void onListFragmentInteraction(CallLogItem item);
|
||||
}
|
||||
|
||||
private static class DiffUtilCallback extends DiffUtil.Callback {
|
||||
private List<CallLogItem> oldList;
|
||||
private List<CallLogItem> newList;
|
||||
|
||||
DiffUtilCallback(List<CallLogItem> oldList, List<CallLogItem> newList) {
|
||||
this.oldList = oldList;
|
||||
this.newList = newList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOldListSize() {
|
||||
return oldList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNewListSize() {
|
||||
return newList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
CallLogItem oldItem = oldList.get(oldItemPosition);
|
||||
CallLogItem newItem = newList.get(newItemPosition);
|
||||
|
||||
return newItem.type == oldItem.type
|
||||
&& TextUtils.equals(newItem.number, oldItem.number)
|
||||
&& newItem.timestamp == oldItem.timestamp
|
||||
&& newItem.duration == oldItem.duration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
return false; // time always updates
|
||||
}
|
||||
}
|
||||
|
||||
private final @Nullable OnListInteractionListener listener;
|
||||
|
||||
private List<CallLogItem> items = Collections.emptyList();
|
||||
|
||||
public CallLogItemRecyclerViewAdapter(@Nullable OnListInteractionListener listener) {
|
||||
this.listener = listener;
|
||||
public CallLogItemRecyclerViewAdapter(@Nullable ListInteractionListener<CallLogItem> listener) {
|
||||
super(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -78,32 +31,12 @@ public class CallLogItemRecyclerViewAdapter
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
|
||||
holder.bind(items.get(position));
|
||||
protected DiffUtilCallback getDiffUtilCallback(
|
||||
List<CallLogItem> oldList, List<CallLogItem> newList) {
|
||||
return new DiffUtilCallback(oldList, newList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return items.size();
|
||||
}
|
||||
|
||||
public void setItems(List<CallLogItem> items) {
|
||||
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(
|
||||
new DiffUtilCallback(this.items, items));
|
||||
|
||||
this.items = items;
|
||||
|
||||
diffResult.dispatchUpdatesTo(this);
|
||||
}
|
||||
|
||||
private void onClick(int index) {
|
||||
if (index != RecyclerView.NO_POSITION && listener != null) {
|
||||
listener.onListFragmentInteraction(items.get(index));
|
||||
}
|
||||
}
|
||||
|
||||
class ViewHolder extends RecyclerView.ViewHolder {
|
||||
final View view;
|
||||
class ViewHolder extends GenericRecyclerViewAdapter<CallLogItem, ViewHolder>.GenericViewHolder {
|
||||
|
||||
final AppCompatImageView callTypeIcon;
|
||||
final TextView label;
|
||||
|
@ -113,16 +46,13 @@ public class CallLogItemRecyclerViewAdapter
|
|||
ViewHolder(View view) {
|
||||
super(view);
|
||||
|
||||
this.view = view;
|
||||
|
||||
callTypeIcon = view.findViewById(R.id.callTypeIcon);
|
||||
label = view.findViewById(R.id.item_label);
|
||||
numberInfoIcon = view.findViewById(R.id.numberInfoIcon);
|
||||
time = view.findViewById(R.id.time);
|
||||
|
||||
view.setOnClickListener(v -> onClick(getAdapterPosition()));
|
||||
}
|
||||
|
||||
@Override
|
||||
void bind(CallLogItem item) {
|
||||
Integer icon;
|
||||
switch (item.type) {
|
||||
|
@ -180,4 +110,27 @@ public class CallLogItemRecyclerViewAdapter
|
|||
return super.toString() + " '" + label.getText() + "'";
|
||||
}
|
||||
}
|
||||
|
||||
static class DiffUtilCallback
|
||||
extends GenericRecyclerViewAdapter.GenericDiffUtilCallback<CallLogItem> {
|
||||
|
||||
DiffUtilCallback(List<CallLogItem> oldList, List<CallLogItem> newList) {
|
||||
super(oldList, newList);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean areItemsTheSame(CallLogItem oldItem, CallLogItem newItem) {
|
||||
return newItem.type == oldItem.type
|
||||
&& TextUtils.equals(newItem.number, oldItem.number)
|
||||
&& newItem.timestamp == oldItem.timestamp
|
||||
&& newItem.duration == oldItem.duration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
return false; // time always updates
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import java.lang.reflect.Method;
|
|||
|
||||
import dummydomain.yetanothercallblocker.data.DatabaseSingleton;
|
||||
import dummydomain.yetanothercallblocker.data.NumberInfo;
|
||||
import dummydomain.yetanothercallblocker.data.NumberInfoService;
|
||||
import dummydomain.yetanothercallblocker.event.CallEndedEvent;
|
||||
import dummydomain.yetanothercallblocker.event.CallOngoingEvent;
|
||||
|
||||
|
@ -66,14 +67,18 @@ public class CallReceiver extends BroadcastReceiver {
|
|||
boolean showNotifications = settings.getIncomingCallNotifications();
|
||||
|
||||
if (blockingEnabled || showNotifications) {
|
||||
NumberInfo numberInfo = DatabaseSingleton.getNumberInfo(incomingNumber);
|
||||
NumberInfoService numberInfoService = DatabaseSingleton.getNumberInfoService();
|
||||
NumberInfo numberInfo = numberInfoService.getNumberInfo(incomingNumber, false);
|
||||
|
||||
boolean blocked = false;
|
||||
if (blockingEnabled && !isOnCall && DatabaseSingleton.shouldBlock(numberInfo)) {
|
||||
if (blockingEnabled && !isOnCall && numberInfoService.shouldBlock(numberInfo)) {
|
||||
blocked = rejectCall(context);
|
||||
|
||||
if (blocked) {
|
||||
NotificationHelper.showBlockedCallNotification(context, numberInfo);
|
||||
|
||||
numberInfoService.blockedCall(numberInfo);
|
||||
|
||||
postEvent(new CallEndedEvent());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import dummydomain.yetanothercallblocker.data.DatabaseSingleton;
|
||||
import dummydomain.yetanothercallblocker.data.NumberInfo;
|
||||
import dummydomain.yetanothercallblocker.data.NumberInfoService;
|
||||
import dummydomain.yetanothercallblocker.event.CallEndedEvent;
|
||||
|
||||
import static dummydomain.yetanothercallblocker.EventUtils.postEvent;
|
||||
|
@ -28,6 +29,8 @@ public class CallScreeningServiceImpl extends CallScreeningService {
|
|||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CallScreeningServiceImpl.class);
|
||||
|
||||
private NumberInfoService numberInfoService = DatabaseSingleton.getNumberInfoService();
|
||||
|
||||
@Override
|
||||
public void onScreenCall(@NonNull Call.Details callDetails) {
|
||||
LOG.info("onScreenCall({})", callDetails);
|
||||
|
@ -95,9 +98,9 @@ public class CallScreeningServiceImpl extends CallScreeningService {
|
|||
}
|
||||
|
||||
if (!ignore) {
|
||||
numberInfo = DatabaseSingleton.getNumberInfo(number);
|
||||
numberInfo = numberInfoService.getNumberInfo(number, false);
|
||||
|
||||
shouldBlock = DatabaseSingleton.shouldBlock(numberInfo);
|
||||
shouldBlock = numberInfoService.shouldBlock(numberInfo);
|
||||
}
|
||||
} finally {
|
||||
LOG.debug("onScreenCall() blocking call: {}", shouldBlock);
|
||||
|
@ -124,9 +127,13 @@ public class CallScreeningServiceImpl extends CallScreeningService {
|
|||
|
||||
NotificationHelper.showBlockedCallNotification(this, numberInfo);
|
||||
|
||||
numberInfoService.blockedCall(numberInfo);
|
||||
|
||||
postEvent(new CallEndedEvent());
|
||||
}
|
||||
}
|
||||
|
||||
LOG.debug("onScreenCall() finished");
|
||||
}
|
||||
|
||||
private void extraLogging(Call.Details callDetails) {
|
||||
|
|
|
@ -0,0 +1,214 @@
|
|||
package dummydomain.yetanothercallblocker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
|
||||
import dummydomain.yetanothercallblocker.data.BlacklistService;
|
||||
import dummydomain.yetanothercallblocker.data.BlacklistUtils;
|
||||
import dummydomain.yetanothercallblocker.data.DatabaseSingleton;
|
||||
import dummydomain.yetanothercallblocker.data.db.BlacklistDao;
|
||||
import dummydomain.yetanothercallblocker.data.db.BlacklistItem;
|
||||
|
||||
import static dummydomain.yetanothercallblocker.data.BlacklistUtils.cleanPattern;
|
||||
import static dummydomain.yetanothercallblocker.data.BlacklistUtils.patternFromHumanReadable;
|
||||
|
||||
public class EditBlacklistItemActivity extends AppCompatActivity {
|
||||
|
||||
private static final String PARAM_ITEM_ID = "itemId";
|
||||
private static final String PARAM_NAME = "itemName";
|
||||
private static final String PARAM_NUMBER_PATTERN = "numberPattern";
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(EditBlacklistItemActivity.class);
|
||||
|
||||
private BlacklistDao blacklistDao = DatabaseSingleton.getBlacklistDao();
|
||||
private BlacklistService blacklistService = DatabaseSingleton.getBlacklistService();
|
||||
|
||||
private TextInputLayout nameTextField;
|
||||
private TextInputLayout patternTextField;
|
||||
|
||||
private BlacklistItem blacklistItem;
|
||||
|
||||
public static Intent getIntent(Context context, long itemId) {
|
||||
Intent intent = new Intent(context, EditBlacklistItemActivity.class);
|
||||
intent.putExtra(PARAM_ITEM_ID, itemId);
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent getIntent(Context context, String name, String numberPattern) {
|
||||
Intent intent = new Intent(context, EditBlacklistItemActivity.class);
|
||||
intent.putExtra(PARAM_NAME, name);
|
||||
intent.putExtra(PARAM_NUMBER_PATTERN, numberPattern);
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_edit_blacklist_item);
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
nameTextField = findViewById(R.id.nameTextField);
|
||||
patternTextField = findViewById(R.id.patternTextField);
|
||||
|
||||
EditText patternEditText = Objects.requireNonNull(patternTextField.getEditText());
|
||||
patternEditText.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
validate();
|
||||
}
|
||||
});
|
||||
patternEditText.setOnEditorActionListener((v, actionId, event) -> {
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
onSaveClicked(null);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
long itemIdFromParams = getIntent().getLongExtra(PARAM_ITEM_ID, -1);
|
||||
if (itemIdFromParams != -1) {
|
||||
blacklistItem = blacklistDao.findById(itemIdFromParams);
|
||||
if (blacklistItem == null) {
|
||||
LOG.warn("onCreate() no item with id={}", itemIdFromParams);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
setTitle(R.string.title_edit_blacklist_item_activity);
|
||||
}
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
String name;
|
||||
String pattern;
|
||||
|
||||
if (blacklistItem != null) {
|
||||
name = blacklistItem.getName();
|
||||
pattern = blacklistItem.getPattern();
|
||||
} else {
|
||||
name = getIntent().getStringExtra(PARAM_NAME);
|
||||
pattern = getIntent().getStringExtra(PARAM_NUMBER_PATTERN);
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(pattern)) {
|
||||
pattern = BlacklistUtils.patternToHumanReadable(pattern);
|
||||
}
|
||||
|
||||
setString(nameTextField, name);
|
||||
setString(patternTextField, pattern);
|
||||
}
|
||||
|
||||
patternTextField.requestFocus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.activity_edit_blacklist_item, menu);
|
||||
|
||||
if (blacklistItem == null) {
|
||||
menu.findItem(R.id.menu_delete).setVisible(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void onSaveClicked(MenuItem item) {
|
||||
if (validate()) {
|
||||
save();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
public void onDeleteClicked(MenuItem item) {
|
||||
blacklistService.delete(Collections.singletonList(blacklistItem.getId()));
|
||||
finish();
|
||||
}
|
||||
|
||||
private boolean validate() {
|
||||
String pattern = getString(patternTextField);
|
||||
boolean valid = true;
|
||||
boolean empty = TextUtils.isEmpty(pattern);
|
||||
|
||||
if (blacklistItem != null || !empty) {
|
||||
pattern = cleanPattern(patternFromHumanReadable(pattern));
|
||||
valid = BlacklistUtils.isValidPattern(pattern);
|
||||
}
|
||||
|
||||
patternTextField.setError(!valid ? getString(
|
||||
empty ? R.string.number_pattern_empty : R.string.number_pattern_incorrect)
|
||||
: null);
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
private void save() {
|
||||
String name = getString(nameTextField);
|
||||
String pattern = cleanPattern(patternFromHumanReadable(getString(patternTextField)));
|
||||
boolean invalid = !BlacklistUtils.isValidPattern(pattern);
|
||||
|
||||
if (blacklistItem != null) {
|
||||
boolean changed = false;
|
||||
if (!TextUtils.equals(name, blacklistItem.getName())) {
|
||||
blacklistItem.setName(name);
|
||||
changed = true;
|
||||
}
|
||||
if (!TextUtils.equals(pattern, blacklistItem.getPattern())) {
|
||||
blacklistItem.setPattern(pattern);
|
||||
changed = true;
|
||||
}
|
||||
if (invalid != blacklistItem.getInvalid()) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
blacklistService.save(blacklistItem);
|
||||
}
|
||||
} else {
|
||||
if (TextUtils.isEmpty(name) && TextUtils.isEmpty(pattern)) {
|
||||
LOG.warn("save() not creating a new item because fields are empty");
|
||||
return;
|
||||
}
|
||||
|
||||
BlacklistItem blacklistItem = new BlacklistItem(name, pattern);
|
||||
blacklistService.save(blacklistItem);
|
||||
}
|
||||
}
|
||||
|
||||
private String getString(TextInputLayout textInputLayout) {
|
||||
return Objects.requireNonNull(textInputLayout.getEditText()).getText().toString();
|
||||
}
|
||||
|
||||
private void setString(TextInputLayout textInputLayout, String s) {
|
||||
Objects.requireNonNull(textInputLayout.getEditText()).setText(s);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
package dummydomain.yetanothercallblocker;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class GenericRecyclerViewAdapter<T, V extends GenericRecyclerViewAdapter<T, V>.GenericViewHolder>
|
||||
extends RecyclerView.Adapter<V> {
|
||||
|
||||
public interface ListInteractionListener<T> {
|
||||
void onListItemClicked(T item);
|
||||
}
|
||||
|
||||
protected @Nullable
|
||||
ListInteractionListener<T> listener;
|
||||
|
||||
protected List<T> items = Collections.emptyList();
|
||||
|
||||
public GenericRecyclerViewAdapter(@Nullable ListInteractionListener<T> listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull V holder, int position) {
|
||||
onBindViewHolder(holder, items.get(position));
|
||||
}
|
||||
|
||||
protected void onBindViewHolder(@NonNull V holder, T item) {
|
||||
holder.bind(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return items.size();
|
||||
}
|
||||
|
||||
public void setItems(List<T> items) {
|
||||
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(
|
||||
getDiffUtilCallback(this.items, items));
|
||||
|
||||
this.items = items;
|
||||
|
||||
diffResult.dispatchUpdatesTo(this);
|
||||
}
|
||||
|
||||
protected abstract GenericDiffUtilCallback<T> getDiffUtilCallback(
|
||||
List<T> oldList, List<T> newList);
|
||||
|
||||
protected void onClick(int index) {
|
||||
if (index != RecyclerView.NO_POSITION && listener != null) {
|
||||
listener.onListItemClicked(items.get(index));
|
||||
}
|
||||
}
|
||||
|
||||
protected static class GenericDiffUtilCallback<T> extends DiffUtil.Callback {
|
||||
protected List<T> oldList;
|
||||
protected List<T> newList;
|
||||
|
||||
protected GenericDiffUtilCallback(List<T> oldList, List<T> newList) {
|
||||
this.oldList = oldList;
|
||||
this.newList = newList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOldListSize() {
|
||||
return oldList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNewListSize() {
|
||||
return newList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
return areItemsTheSame(oldList.get(oldItemPosition), newList.get(newItemPosition));
|
||||
}
|
||||
|
||||
protected boolean areItemsTheSame(T oldItem, T newItem) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
return areContentsTheSame(oldList.get(oldItemPosition), newList.get(newItemPosition));
|
||||
}
|
||||
|
||||
protected boolean areContentsTheSame(T oldItem, T newItem) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract class GenericViewHolder extends RecyclerView.ViewHolder {
|
||||
public GenericViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
|
||||
itemView.setOnClickListener(v -> onClick(getAdapterPosition()));
|
||||
}
|
||||
|
||||
abstract void bind(T item);
|
||||
}
|
||||
|
||||
}
|
|
@ -97,13 +97,13 @@ public class InfoDialogHelper {
|
|||
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
|
||||
if (numberInfo.contactItem != null) {
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.load_reviews_confirmation_title)
|
||||
.setTitle(R.string.are_you_sure)
|
||||
.setMessage(R.string.load_reviews_confirmation_message)
|
||||
.setPositiveButton(android.R.string.yes, (d1, w) -> {
|
||||
.setPositiveButton(R.string.yes, (d1, w) -> {
|
||||
reviewsAction.run();
|
||||
dialog.dismiss();
|
||||
})
|
||||
.setNegativeButton(android.R.string.no, null)
|
||||
.setNegativeButton(R.string.no, null)
|
||||
.show();
|
||||
} else {
|
||||
reviewsAction.run();
|
||||
|
@ -114,13 +114,13 @@ public class InfoDialogHelper {
|
|||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
|
||||
if (numberInfo.contactItem != null) {
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.load_reviews_confirmation_title)
|
||||
.setTitle(R.string.are_you_sure)
|
||||
.setMessage(R.string.load_reviews_confirmation_message)
|
||||
.setPositiveButton(android.R.string.yes, (d1, w) -> {
|
||||
.setPositiveButton(R.string.yes, (d1, w) -> {
|
||||
webReviewAction.run();
|
||||
dialog.dismiss();
|
||||
})
|
||||
.setNegativeButton(android.R.string.no, null)
|
||||
.setNegativeButton(R.string.no, null)
|
||||
.show();
|
||||
} else {
|
||||
webReviewAction.run();
|
||||
|
|
|
@ -200,6 +200,10 @@ public class MainActivity extends AppCompatActivity {
|
|||
loadCallLog();
|
||||
}
|
||||
|
||||
public void onOpenBlacklist(MenuItem item) {
|
||||
startActivity(BlacklistActivity.getIntent(this));
|
||||
}
|
||||
|
||||
public void onOpenSettings(MenuItem item) {
|
||||
startActivity(new Intent(this, SettingsActivity.class));
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@ public class Settings extends GenericSettings {
|
|||
public static final String PREF_INCOMING_CALL_NOTIFICATIONS = "incomingCallNotifications";
|
||||
public static final String PREF_BLOCK_NEGATIVE_SIA_NUMBERS = "blockNegativeSiaNumbers";
|
||||
public static final String PREF_BLOCK_HIDDEN_NUMBERS = "blockHiddenNumbers";
|
||||
public static final String PREF_BLOCK_BLACKLISTED = "blockBlacklisted";
|
||||
public static final String PREF_BLACKLIST_IS_NOT_EMPTY = "blacklistIsNotEmpty";
|
||||
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";
|
||||
|
@ -94,7 +96,7 @@ public class Settings extends GenericSettings {
|
|||
}
|
||||
|
||||
public boolean getCallBlockingEnabled() {
|
||||
return getBlockNegativeSiaNumbers() || getBlockHiddenNumbers();
|
||||
return getBlockNegativeSiaNumbers() || getBlockHiddenNumbers() || getBlacklistEnabled();
|
||||
}
|
||||
|
||||
public boolean getBlockNegativeSiaNumbers() {
|
||||
|
@ -113,6 +115,26 @@ public class Settings extends GenericSettings {
|
|||
setBoolean(PREF_BLOCK_HIDDEN_NUMBERS, block);
|
||||
}
|
||||
|
||||
public boolean getBlacklistEnabled() {
|
||||
return getBlockBlacklisted() && getBlacklistIsNotEmpty();
|
||||
}
|
||||
|
||||
public boolean getBlockBlacklisted() {
|
||||
return getBoolean(PREF_BLOCK_BLACKLISTED, true);
|
||||
}
|
||||
|
||||
public void setBlockBlacklisted(boolean block) {
|
||||
setBoolean(PREF_BLOCK_BLACKLISTED, block);
|
||||
}
|
||||
|
||||
public boolean getBlacklistIsNotEmpty() {
|
||||
return getBoolean(PREF_BLACKLIST_IS_NOT_EMPTY);
|
||||
}
|
||||
|
||||
public void setBlacklistIsNotEmpty(boolean flag) {
|
||||
setBoolean(PREF_BLACKLIST_IS_NOT_EMPTY, flag);
|
||||
}
|
||||
|
||||
public boolean getUseContacts() {
|
||||
return getBoolean(PREF_USE_CONTACTS);
|
||||
}
|
||||
|
|
|
@ -156,6 +156,8 @@ public class SettingsActivity extends AppCompatActivity
|
|||
.setOnPreferenceChangeListener(callBlockingListener);
|
||||
requireNonNull((SwitchPreferenceCompat) findPreference(Settings.PREF_BLOCK_HIDDEN_NUMBERS))
|
||||
.setOnPreferenceChangeListener(callBlockingListener);
|
||||
requireNonNull((SwitchPreferenceCompat) findPreference(Settings.PREF_BLOCK_BLACKLISTED))
|
||||
.setOnPreferenceChangeListener(callBlockingListener);
|
||||
|
||||
SwitchPreferenceCompat callScreeningPref =
|
||||
requireNonNull(findPreference(PREF_USE_CALL_SCREENING_SERVICE));
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
package dummydomain.yetanothercallblocker.data;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
|
||||
import dummydomain.yetanothercallblocker.data.db.BlacklistDao;
|
||||
import dummydomain.yetanothercallblocker.data.db.BlacklistItem;
|
||||
import dummydomain.yetanothercallblocker.event.BlacklistChangedEvent;
|
||||
import dummydomain.yetanothercallblocker.event.BlacklistItemChangedEvent;
|
||||
|
||||
import static dummydomain.yetanothercallblocker.EventUtils.postEvent;
|
||||
|
||||
public class BlacklistService {
|
||||
|
||||
public interface Callback {
|
||||
void changed(boolean notEmpty);
|
||||
}
|
||||
|
||||
private final Callback callback;
|
||||
private final BlacklistDao blacklistDao;
|
||||
|
||||
public BlacklistService(Callback callback, BlacklistDao blacklistDao) {
|
||||
this.callback = callback;
|
||||
this.blacklistDao = blacklistDao;
|
||||
}
|
||||
|
||||
public BlacklistItem getBlacklistItemForNumber(String number) {
|
||||
if (TextUtils.isEmpty(number)) return null;
|
||||
|
||||
number = BlacklistUtils.cleanNumber(number);
|
||||
|
||||
return blacklistDao.getFirstMatch(number);
|
||||
}
|
||||
|
||||
public void save(BlacklistItem blacklistItem) {
|
||||
boolean newItem = blacklistItem.getId() == null;
|
||||
|
||||
blacklistItem.setInvalid(!BlacklistUtils.isValidPattern(blacklistItem.getPattern()));
|
||||
blacklistDao.save(blacklistItem);
|
||||
|
||||
blacklistChanged();
|
||||
|
||||
postEvent(newItem ? new BlacklistChangedEvent() : new BlacklistItemChangedEvent());
|
||||
}
|
||||
|
||||
public void addCall(BlacklistItem blacklistItem, Date date) {
|
||||
blacklistItem.setLastCallDate(Objects.requireNonNull(date));
|
||||
blacklistItem.setNumberOfCalls(blacklistItem.getNumberOfCalls() + 1);
|
||||
blacklistDao.save(blacklistItem);
|
||||
|
||||
postEvent(new BlacklistItemChangedEvent());
|
||||
}
|
||||
|
||||
public void delete(Iterable<Long> keys) {
|
||||
blacklistDao.delete(keys);
|
||||
|
||||
blacklistChanged();
|
||||
}
|
||||
|
||||
private void blacklistChanged() {
|
||||
callback.changed(blacklistDao.countValid() != 0);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package dummydomain.yetanothercallblocker.data;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class BlacklistUtils {
|
||||
|
||||
private static final Pattern BLACKLIST_ITEM_VALID_PATTERN = Pattern.compile("\\+?[0-9%_]+");
|
||||
private static final Pattern PATTERN_CLEANING_PATTERN = Pattern.compile("[^+0-9%_*#]");
|
||||
private static final Pattern NUMBER_CLEANING_PATTERN = Pattern.compile("[^+0-9]");
|
||||
|
||||
public static String patternToHumanReadable(String pattern) {
|
||||
return pattern.replace('%', '*').replace('_', '#');
|
||||
}
|
||||
|
||||
public static String patternFromHumanReadable(String pattern) {
|
||||
return pattern.replace('*', '%').replace('#', '_');
|
||||
}
|
||||
|
||||
public static String cleanPattern(String pattern) {
|
||||
return PATTERN_CLEANING_PATTERN.matcher(pattern).replaceAll("");
|
||||
}
|
||||
|
||||
public static String cleanNumber(String number) {
|
||||
return NUMBER_CLEANING_PATTERN.matcher(number).replaceAll("");
|
||||
}
|
||||
|
||||
public static boolean isValidPattern(String pattern) {
|
||||
return BLACKLIST_ITEM_VALID_PATTERN.matcher(pattern).matches();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
package dummydomain.yetanothercallblocker.data;
|
||||
|
||||
import dummydomain.yetanothercallblocker.Settings;
|
||||
|
||||
public class BlockingDecisionMaker implements DatabaseSingleton.BlockingDecisionMaker {
|
||||
|
||||
private final Settings settings;
|
||||
|
||||
public BlockingDecisionMaker(Settings settings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldBlock(NumberInfo numberInfo) {
|
||||
if (numberInfo.contactItem != null) return false;
|
||||
|
||||
if (numberInfo.isHiddenNumber && settings.getBlockHiddenNumbers()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (numberInfo.rating == NumberInfo.Rating.NEGATIVE
|
||||
&& settings.getBlockNegativeSiaNumbers()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -6,6 +6,8 @@ import android.text.TextUtils;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import dummydomain.yetanothercallblocker.PermissionHelper;
|
||||
import dummydomain.yetanothercallblocker.data.db.BlacklistDao;
|
||||
import dummydomain.yetanothercallblocker.data.db.YacbDaoSessionFactory;
|
||||
import dummydomain.yetanothercallblocker.sia.Settings;
|
||||
import dummydomain.yetanothercallblocker.sia.SettingsImpl;
|
||||
import dummydomain.yetanothercallblocker.sia.Storage;
|
||||
|
@ -104,8 +106,6 @@ public class Config {
|
|||
DatabaseSingleton.setDbManager(new DbManager(storage, SIA_PATH_PREFIX,
|
||||
new DbDownloader(okHttpClientFactory)));
|
||||
|
||||
DatabaseSingleton.setHiddenNumberDetector(NumberUtils::isHiddenNumber);
|
||||
|
||||
CommunityDatabase communityDatabase = new CommunityDatabase(
|
||||
storage, AbstractDatabase.Source.ANY, SIA_PATH_PREFIX,
|
||||
SIA_SECONDARY_PATH_PREFIX, siaSettings, webService);
|
||||
|
@ -119,20 +119,33 @@ public class Config {
|
|||
wsParameterProvider.setSiaMetadata(siaMetadata);
|
||||
DatabaseSingleton.setSiaMetadata(siaMetadata);
|
||||
|
||||
DatabaseSingleton.setFeaturedDatabase(new FeaturedDatabase(
|
||||
storage, AbstractDatabase.Source.ANY, SIA_PATH_PREFIX));
|
||||
FeaturedDatabase featuredDatabase = new FeaturedDatabase(
|
||||
storage, AbstractDatabase.Source.ANY, SIA_PATH_PREFIX);
|
||||
DatabaseSingleton.setFeaturedDatabase(featuredDatabase);
|
||||
|
||||
DatabaseSingleton.setCommunityReviewsLoader(new CommunityReviewsLoader(webService));
|
||||
|
||||
DatabaseSingleton.setContactsProvider(number -> {
|
||||
YacbDaoSessionFactory daoSessionFactory = new YacbDaoSessionFactory(context, "YACB");
|
||||
|
||||
BlacklistDao blacklistDao = new BlacklistDao(daoSessionFactory::getDaoSession);
|
||||
DatabaseSingleton.setBlacklistDao(blacklistDao);
|
||||
|
||||
BlacklistService blacklistService = new BlacklistService(
|
||||
settings::setBlacklistIsNotEmpty, blacklistDao);
|
||||
DatabaseSingleton.setBlacklistService(blacklistService);
|
||||
|
||||
ContactsProvider contactsProvider = number -> {
|
||||
if (settings.getUseContacts() && PermissionHelper.hasContactsPermission(context)) {
|
||||
String contactName = ContactsHelper.getContactName(context, number);
|
||||
if (!TextUtils.isEmpty(contactName)) return new ContactItem(contactName);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
};
|
||||
|
||||
DatabaseSingleton.setBlockingDecisionMaker(new BlockingDecisionMaker(settings));
|
||||
NumberInfoService numberInfoService = new NumberInfoService(
|
||||
settings, NumberUtils::isHiddenNumber,
|
||||
communityDatabase, featuredDatabase, contactsProvider, blacklistService);
|
||||
DatabaseSingleton.setNumberInfoService(numberInfoService);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,40 +1,26 @@
|
|||
package dummydomain.yetanothercallblocker.data;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import dummydomain.yetanothercallblocker.data.db.BlacklistDao;
|
||||
import dummydomain.yetanothercallblocker.sia.model.CommunityReviewsLoader;
|
||||
import dummydomain.yetanothercallblocker.sia.model.SiaMetadata;
|
||||
import dummydomain.yetanothercallblocker.sia.model.database.CommunityDatabase;
|
||||
import dummydomain.yetanothercallblocker.sia.model.database.CommunityDatabaseItem;
|
||||
import dummydomain.yetanothercallblocker.sia.model.database.DbManager;
|
||||
import dummydomain.yetanothercallblocker.sia.model.database.FeaturedDatabase;
|
||||
import dummydomain.yetanothercallblocker.sia.model.database.FeaturedDatabaseItem;
|
||||
import dummydomain.yetanothercallblocker.sia.network.WebService;
|
||||
|
||||
public class DatabaseSingleton {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DatabaseSingleton.class);
|
||||
|
||||
private static WebService webService;
|
||||
|
||||
private static DbManager dbManager;
|
||||
|
||||
private static SiaMetadata siaMetadata;
|
||||
|
||||
private static HiddenNumberDetector hiddenNumberDetector;
|
||||
|
||||
private static CommunityDatabase communityDatabase;
|
||||
|
||||
private static FeaturedDatabase featuredDatabase;
|
||||
|
||||
private static CommunityReviewsLoader communityReviewsLoader;
|
||||
|
||||
private static ContactsProvider contactsProvider;
|
||||
private static BlacklistDao blacklistDao;
|
||||
private static BlacklistService blacklistService;
|
||||
|
||||
private static BlockingDecisionMaker blockingDecisionMaker;
|
||||
private static NumberInfoService numberInfoService;
|
||||
|
||||
static void setWebService(WebService webService) {
|
||||
DatabaseSingleton.webService = webService;
|
||||
|
@ -48,10 +34,6 @@ public class DatabaseSingleton {
|
|||
DatabaseSingleton.siaMetadata = siaMetadata;
|
||||
}
|
||||
|
||||
static void setHiddenNumberDetector(HiddenNumberDetector hiddenNumberDetector) {
|
||||
DatabaseSingleton.hiddenNumberDetector = hiddenNumberDetector;
|
||||
}
|
||||
|
||||
static void setCommunityDatabase(CommunityDatabase communityDatabase) {
|
||||
DatabaseSingleton.communityDatabase = communityDatabase;
|
||||
}
|
||||
|
@ -64,12 +46,16 @@ public class DatabaseSingleton {
|
|||
DatabaseSingleton.communityReviewsLoader = communityReviewsLoader;
|
||||
}
|
||||
|
||||
static void setContactsProvider(ContactsProvider contactsProvider) {
|
||||
DatabaseSingleton.contactsProvider = contactsProvider;
|
||||
static void setBlacklistDao(BlacklistDao blacklistDao) {
|
||||
DatabaseSingleton.blacklistDao = blacklistDao;
|
||||
}
|
||||
|
||||
static void setBlockingDecisionMaker(BlockingDecisionMaker blockingDecisionMaker) {
|
||||
DatabaseSingleton.blockingDecisionMaker = blockingDecisionMaker;
|
||||
static void setBlacklistService(BlacklistService blacklistService) {
|
||||
DatabaseSingleton.blacklistService = blacklistService;
|
||||
}
|
||||
|
||||
static void setNumberInfoService(NumberInfoService numberInfoService) {
|
||||
DatabaseSingleton.numberInfoService = numberInfoService;
|
||||
}
|
||||
|
||||
public static WebService getWebService() {
|
||||
|
@ -96,80 +82,20 @@ public class DatabaseSingleton {
|
|||
return communityReviewsLoader;
|
||||
}
|
||||
|
||||
public static BlacklistDao getBlacklistDao() {
|
||||
return blacklistDao;
|
||||
}
|
||||
|
||||
public static BlacklistService getBlacklistService() {
|
||||
return blacklistService;
|
||||
}
|
||||
|
||||
public static NumberInfoService getNumberInfoService() {
|
||||
return numberInfoService;
|
||||
}
|
||||
|
||||
public static NumberInfo getNumberInfo(String number) {
|
||||
LOG.debug("getNumberInfo({}) started", number);
|
||||
// TODO: check number format
|
||||
|
||||
NumberInfo numberInfo = new NumberInfo();
|
||||
numberInfo.number = number;
|
||||
|
||||
if (hiddenNumberDetector != null) {
|
||||
numberInfo.isHiddenNumber = hiddenNumberDetector.isHiddenNumber(number);
|
||||
}
|
||||
LOG.trace("getNumberInfo() isHiddenNumber={}", numberInfo.isHiddenNumber);
|
||||
|
||||
if (numberInfo.isHiddenNumber || TextUtils.isEmpty(numberInfo.number)) {
|
||||
numberInfo.noNumber = true;
|
||||
}
|
||||
LOG.trace("getNumberInfo() noNumber={}", numberInfo.noNumber);
|
||||
|
||||
if (numberInfo.noNumber) {
|
||||
LOG.debug("getNumberInfo() finished");
|
||||
return numberInfo;
|
||||
}
|
||||
|
||||
numberInfo.communityDatabaseItem = DatabaseSingleton.getCommunityDatabase()
|
||||
.getDbItemByNumber(number);
|
||||
LOG.trace("getNumberInfo() communityItem={}", numberInfo.communityDatabaseItem);
|
||||
|
||||
numberInfo.featuredDatabaseItem = DatabaseSingleton.getFeaturedDatabase()
|
||||
.getDbItemByNumber(number);
|
||||
LOG.trace("getNumberInfo() featuredItem={}", numberInfo.featuredDatabaseItem);
|
||||
|
||||
numberInfo.contactItem = DatabaseSingleton.contactsProvider.get(number);
|
||||
LOG.trace("getNumberInfo() contactItem={}", numberInfo.contactItem);
|
||||
|
||||
ContactItem contactItem = numberInfo.contactItem;
|
||||
FeaturedDatabaseItem featuredItem = numberInfo.featuredDatabaseItem;
|
||||
if (contactItem != null && !TextUtils.isEmpty(contactItem.displayName)) {
|
||||
numberInfo.name = contactItem.displayName;
|
||||
} else if (featuredItem != null && !TextUtils.isEmpty(featuredItem.getName())) {
|
||||
numberInfo.name = featuredItem.getName();
|
||||
}
|
||||
LOG.trace("getNumberInfo() name={}", numberInfo.name);
|
||||
|
||||
CommunityDatabaseItem communityItem = numberInfo.communityDatabaseItem;
|
||||
if (communityItem != null && communityItem.hasRatings()) {
|
||||
if (communityItem.getNegativeRatingsCount() > communityItem.getPositiveRatingsCount()
|
||||
+ communityItem.getNeutralRatingsCount()) {
|
||||
numberInfo.rating = NumberInfo.Rating.NEGATIVE;
|
||||
} else if (communityItem.getPositiveRatingsCount() > communityItem.getNeutralRatingsCount()
|
||||
+ communityItem.getNegativeRatingsCount()) {
|
||||
numberInfo.rating = NumberInfo.Rating.POSITIVE;
|
||||
} else if (communityItem.getNeutralRatingsCount() > communityItem.getPositiveRatingsCount()
|
||||
+ communityItem.getNegativeRatingsCount()) {
|
||||
numberInfo.rating = NumberInfo.Rating.NEUTRAL;
|
||||
}
|
||||
}
|
||||
LOG.trace("getNumberInfo() rating={}", numberInfo.rating);
|
||||
|
||||
LOG.debug("getNumberInfo() finished");
|
||||
return numberInfo;
|
||||
}
|
||||
|
||||
public static boolean shouldBlock(NumberInfo numberInfo) {
|
||||
if (blockingDecisionMaker != null) {
|
||||
return blockingDecisionMaker.shouldBlock(numberInfo);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public interface HiddenNumberDetector {
|
||||
boolean isHiddenNumber(String number);
|
||||
}
|
||||
|
||||
public interface BlockingDecisionMaker {
|
||||
boolean shouldBlock(NumberInfo numberInfo);
|
||||
return numberInfoService.getNumberInfo(number, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
package dummydomain.yetanothercallblocker.data;
|
||||
|
||||
import dummydomain.yetanothercallblocker.data.db.BlacklistItem;
|
||||
import dummydomain.yetanothercallblocker.sia.model.database.CommunityDatabaseItem;
|
||||
import dummydomain.yetanothercallblocker.sia.model.database.FeaturedDatabaseItem;
|
||||
|
||||
public class NumberInfo {
|
||||
|
||||
public enum BlockingReason {
|
||||
HIDDEN_NUMBER, SIA_RATING, BLACKLISTED
|
||||
}
|
||||
|
||||
public enum Rating {
|
||||
POSITIVE, NEUTRAL, NEGATIVE, UNKNOWN
|
||||
}
|
||||
|
@ -14,9 +19,10 @@ public class NumberInfo {
|
|||
|
||||
// info from various sources
|
||||
public boolean isHiddenNumber;
|
||||
public ContactItem contactItem;
|
||||
public CommunityDatabaseItem communityDatabaseItem;
|
||||
public FeaturedDatabaseItem featuredDatabaseItem;
|
||||
public ContactItem contactItem;
|
||||
public BlacklistItem blacklistItem;
|
||||
|
||||
// computed rating
|
||||
public Rating rating = Rating.UNKNOWN;
|
||||
|
@ -24,5 +30,6 @@ public class NumberInfo {
|
|||
// precomputed for convenience
|
||||
public boolean noNumber;
|
||||
public String name;
|
||||
public BlockingReason blockingReason;
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
package dummydomain.yetanothercallblocker.data;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import dummydomain.yetanothercallblocker.Settings;
|
||||
import dummydomain.yetanothercallblocker.sia.model.database.CommunityDatabase;
|
||||
import dummydomain.yetanothercallblocker.sia.model.database.CommunityDatabaseItem;
|
||||
import dummydomain.yetanothercallblocker.sia.model.database.FeaturedDatabase;
|
||||
import dummydomain.yetanothercallblocker.sia.model.database.FeaturedDatabaseItem;
|
||||
|
||||
public class NumberInfoService {
|
||||
|
||||
public interface HiddenNumberDetector {
|
||||
boolean isHiddenNumber(String number);
|
||||
}
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(NumberInfoService.class);
|
||||
|
||||
protected final Settings settings;
|
||||
|
||||
protected final HiddenNumberDetector hiddenNumberDetector;
|
||||
protected final CommunityDatabase communityDatabase;
|
||||
protected final FeaturedDatabase featuredDatabase;
|
||||
protected final ContactsProvider contactsProvider;
|
||||
protected final BlacklistService blacklistService;
|
||||
|
||||
public NumberInfoService(Settings settings, HiddenNumberDetector hiddenNumberDetector,
|
||||
CommunityDatabase communityDatabase, FeaturedDatabase featuredDatabase,
|
||||
ContactsProvider contactsProvider, BlacklistService blacklistService) {
|
||||
this.settings = settings;
|
||||
this.hiddenNumberDetector = hiddenNumberDetector;
|
||||
this.communityDatabase = communityDatabase;
|
||||
this.featuredDatabase = featuredDatabase;
|
||||
this.contactsProvider = contactsProvider;
|
||||
this.blacklistService = blacklistService;
|
||||
}
|
||||
|
||||
public NumberInfo getNumberInfo(String number, boolean full) {
|
||||
LOG.debug("getNumberInfo({}, {}) started", number, full);
|
||||
// TODO: check number format
|
||||
|
||||
NumberInfo numberInfo = new NumberInfo();
|
||||
numberInfo.number = number;
|
||||
|
||||
if (hiddenNumberDetector != null) {
|
||||
numberInfo.isHiddenNumber = hiddenNumberDetector.isHiddenNumber(number);
|
||||
}
|
||||
LOG.trace("getNumberInfo() isHiddenNumber={}", numberInfo.isHiddenNumber);
|
||||
|
||||
if (numberInfo.isHiddenNumber || TextUtils.isEmpty(number)
|
||||
|| TextUtils.getTrimmedLength(number) == 0) {
|
||||
numberInfo.noNumber = true;
|
||||
}
|
||||
LOG.trace("getNumberInfo() noNumber={}", numberInfo.noNumber);
|
||||
|
||||
if (numberInfo.noNumber) {
|
||||
numberInfo.blockingReason = getBlockingReason(numberInfo);
|
||||
LOG.trace("getNumberInfo() blockingReason={}", numberInfo.blockingReason);
|
||||
LOG.debug("getNumberInfo() finished early");
|
||||
return numberInfo;
|
||||
}
|
||||
|
||||
if (contactsProvider != null) {
|
||||
numberInfo.contactItem = contactsProvider.get(number);
|
||||
}
|
||||
LOG.trace("getNumberInfo() contactItem={}", numberInfo.contactItem);
|
||||
|
||||
if (communityDatabase != null) {
|
||||
numberInfo.communityDatabaseItem = communityDatabase.getDbItemByNumber(number);
|
||||
}
|
||||
LOG.trace("getNumberInfo() communityItem={}", numberInfo.communityDatabaseItem);
|
||||
|
||||
if (featuredDatabase != null) {
|
||||
numberInfo.featuredDatabaseItem = featuredDatabase.getDbItemByNumber(number);
|
||||
}
|
||||
LOG.trace("getNumberInfo() featuredItem={}", numberInfo.featuredDatabaseItem);
|
||||
|
||||
ContactItem contactItem = numberInfo.contactItem;
|
||||
FeaturedDatabaseItem featuredItem = numberInfo.featuredDatabaseItem;
|
||||
if (contactItem != null && !TextUtils.isEmpty(contactItem.displayName)) {
|
||||
numberInfo.name = contactItem.displayName;
|
||||
} else if (featuredItem != null && !TextUtils.isEmpty(featuredItem.getName())) {
|
||||
numberInfo.name = featuredItem.getName();
|
||||
}
|
||||
LOG.trace("getNumberInfo() name={}", numberInfo.name);
|
||||
|
||||
CommunityDatabaseItem communityItem = numberInfo.communityDatabaseItem;
|
||||
if (communityItem != null && communityItem.hasRatings()) {
|
||||
if (communityItem.getNegativeRatingsCount() > communityItem.getPositiveRatingsCount()
|
||||
+ communityItem.getNeutralRatingsCount()) {
|
||||
numberInfo.rating = NumberInfo.Rating.NEGATIVE;
|
||||
} else if (communityItem.getPositiveRatingsCount() > communityItem.getNeutralRatingsCount()
|
||||
+ communityItem.getNegativeRatingsCount()) {
|
||||
numberInfo.rating = NumberInfo.Rating.POSITIVE;
|
||||
} else if (communityItem.getNeutralRatingsCount() > communityItem.getPositiveRatingsCount()
|
||||
+ communityItem.getNegativeRatingsCount()) {
|
||||
numberInfo.rating = NumberInfo.Rating.NEUTRAL;
|
||||
}
|
||||
}
|
||||
LOG.trace("getNumberInfo() rating={}", numberInfo.rating);
|
||||
|
||||
if (blacklistService != null && settings.getBlacklistIsNotEmpty()) {
|
||||
// avoid loading blacklist if blocking for other reason
|
||||
if (full || getBlockingReason(numberInfo) == null) {
|
||||
numberInfo.blacklistItem = blacklistService.getBlacklistItemForNumber(number);
|
||||
}
|
||||
}
|
||||
LOG.trace("getNumberInfo() blacklistItem={}", numberInfo.blacklistItem);
|
||||
|
||||
numberInfo.blockingReason = getBlockingReason(numberInfo);
|
||||
LOG.trace("getNumberInfo() blockingReason={}", numberInfo.blockingReason);
|
||||
|
||||
LOG.debug("getNumberInfo() finished");
|
||||
return numberInfo;
|
||||
}
|
||||
|
||||
protected NumberInfo.BlockingReason getBlockingReason(NumberInfo numberInfo) {
|
||||
if (numberInfo.contactItem != null) return null;
|
||||
|
||||
if (numberInfo.isHiddenNumber && settings.getBlockHiddenNumbers()) {
|
||||
return NumberInfo.BlockingReason.HIDDEN_NUMBER;
|
||||
}
|
||||
|
||||
if (numberInfo.rating == NumberInfo.Rating.NEGATIVE
|
||||
&& settings.getBlockNegativeSiaNumbers()) {
|
||||
return NumberInfo.BlockingReason.SIA_RATING;
|
||||
}
|
||||
|
||||
if (numberInfo.blacklistItem != null && settings.getBlockBlacklisted()) {
|
||||
return NumberInfo.BlockingReason.BLACKLISTED;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean shouldBlock(NumberInfo numberInfo) {
|
||||
return numberInfo.blockingReason != null;
|
||||
}
|
||||
|
||||
public void blockedCall(NumberInfo numberInfo) {
|
||||
if (blacklistService != null && numberInfo.blacklistItem != null
|
||||
&& numberInfo.blockingReason == NumberInfo.BlockingReason.BLACKLISTED) {
|
||||
blacklistService.addCall(numberInfo.blacklistItem, new Date());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
package dummydomain.yetanothercallblocker.data.db;
|
||||
|
||||
import org.greenrobot.greendao.Property;
|
||||
import org.greenrobot.greendao.internal.SqlUtils;
|
||||
import org.greenrobot.greendao.query.CloseableListIterator;
|
||||
import org.greenrobot.greendao.query.QueryBuilder;
|
||||
import org.greenrobot.greendao.query.WhereCondition;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class BlacklistDao {
|
||||
|
||||
public interface DaoSessionProvider {
|
||||
DaoSession getDaoSession();
|
||||
}
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(BlacklistDao.class);
|
||||
|
||||
private final DaoSessionProvider daoSessionProvider;
|
||||
|
||||
public BlacklistDao(DaoSessionProvider daoSessionProvider) {
|
||||
this.daoSessionProvider = daoSessionProvider;
|
||||
}
|
||||
|
||||
public List<BlacklistItem> loadAll() {
|
||||
return getBlacklistItemDao().queryBuilder()
|
||||
.orderAsc(BlacklistItemDao.Properties.Pattern)
|
||||
.list();
|
||||
}
|
||||
|
||||
public <T extends Collection<BlacklistItem>> T detach(T items) {
|
||||
BlacklistItemDao dao = getBlacklistItemDao();
|
||||
for (BlacklistItem item : items) {
|
||||
dao.detach(item);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
public BlacklistItem findById(long id) {
|
||||
return getBlacklistItemDao().load(id);
|
||||
}
|
||||
|
||||
public void save(BlacklistItem blacklistItem) {
|
||||
getBlacklistItemDao().save(blacklistItem);
|
||||
}
|
||||
|
||||
public void delete(Iterable<Long> keys) {
|
||||
getBlacklistItemDao().deleteByKeyInTx(keys);
|
||||
}
|
||||
|
||||
public long countValid() {
|
||||
return getBlacklistItemDao().queryBuilder()
|
||||
.where(BlacklistItemDao.Properties.Invalid.notEq(true)).count();
|
||||
}
|
||||
|
||||
public BlacklistItem getFirstMatch(String number) {
|
||||
try (CloseableListIterator<BlacklistItem> it = getMatchesQueryBuilder(number).build()
|
||||
.listIterator()) {
|
||||
if (it.hasNext()) return it.next();
|
||||
} catch (IOException e) {
|
||||
LOG.debug("getFirstMatch()", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private QueryBuilder<BlacklistItem> getMatchesQueryBuilder(String number) {
|
||||
return getBlacklistItemDao().queryBuilder()
|
||||
.where(BlacklistItemDao.Properties.Invalid.notEq(true),
|
||||
new InverseLikeCondition(BlacklistItemDao.Properties.Pattern, number))
|
||||
.orderAsc(BlacklistItemDao.Properties.CreationDate);
|
||||
}
|
||||
|
||||
private BlacklistItemDao getBlacklistItemDao() {
|
||||
return daoSessionProvider.getDaoSession().getBlacklistItemDao();
|
||||
}
|
||||
|
||||
private static class InverseLikeCondition extends WhereCondition.PropertyCondition {
|
||||
InverseLikeCondition(Property property, String value) {
|
||||
super(property, " ? LIKE ", value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendTo(StringBuilder builder, String tableAlias) {
|
||||
builder.append(op);
|
||||
SqlUtils.appendProperty(builder, tableAlias, property);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
package dummydomain.yetanothercallblocker.data.db;
|
||||
|
||||
import org.greenrobot.greendao.annotation.Entity;
|
||||
import org.greenrobot.greendao.annotation.Generated;
|
||||
import org.greenrobot.greendao.annotation.Id;
|
||||
import org.greenrobot.greendao.annotation.Index;
|
||||
import org.greenrobot.greendao.annotation.NotNull;
|
||||
import org.greenrobot.greendao.annotation.Transient;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import static dummydomain.yetanothercallblocker.data.BlacklistUtils.patternFromHumanReadable;
|
||||
import static dummydomain.yetanothercallblocker.data.BlacklistUtils.patternToHumanReadable;
|
||||
|
||||
@Entity
|
||||
public class BlacklistItem {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
@Index
|
||||
@NotNull
|
||||
private String pattern;
|
||||
@Transient
|
||||
private String humanReadablePattern;
|
||||
|
||||
@NotNull
|
||||
private Date creationDate;
|
||||
|
||||
@NotNull
|
||||
private boolean invalid;
|
||||
|
||||
@NotNull
|
||||
private int numberOfCalls = 0;
|
||||
private Date lastCallDate;
|
||||
|
||||
public BlacklistItem() {}
|
||||
|
||||
public BlacklistItem(String name, String pattern) {
|
||||
this(null, name, patternFromHumanReadable(pattern), new Date(), false, 0, null);
|
||||
}
|
||||
|
||||
@Generated(hash = 1295831)
|
||||
public BlacklistItem(Long id, String name, @NotNull String pattern,
|
||||
@NotNull Date creationDate, boolean invalid, int numberOfCalls,
|
||||
Date lastCallDate) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.pattern = pattern;
|
||||
this.creationDate = creationDate;
|
||||
this.invalid = invalid;
|
||||
this.numberOfCalls = numberOfCalls;
|
||||
this.lastCallDate = lastCallDate;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getPattern() {
|
||||
return this.pattern;
|
||||
}
|
||||
|
||||
public void setPattern(String pattern) {
|
||||
this.pattern = pattern;
|
||||
this.humanReadablePattern = null;
|
||||
}
|
||||
|
||||
public String getHumanReadablePattern() {
|
||||
if (humanReadablePattern == null) {
|
||||
humanReadablePattern = patternToHumanReadable(pattern);
|
||||
}
|
||||
return humanReadablePattern;
|
||||
}
|
||||
|
||||
public Date getCreationDate() {
|
||||
return this.creationDate;
|
||||
}
|
||||
|
||||
public void setCreationDate(Date creationDate) {
|
||||
this.creationDate = creationDate;
|
||||
}
|
||||
|
||||
public boolean getInvalid() {
|
||||
return this.invalid;
|
||||
}
|
||||
|
||||
public void setInvalid(boolean invalid) {
|
||||
this.invalid = invalid;
|
||||
}
|
||||
|
||||
public Date getLastCallDate() {
|
||||
return this.lastCallDate;
|
||||
}
|
||||
|
||||
public void setLastCallDate(Date lastCallDate) {
|
||||
this.lastCallDate = lastCallDate;
|
||||
}
|
||||
|
||||
public int getNumberOfCalls() {
|
||||
return this.numberOfCalls;
|
||||
}
|
||||
|
||||
public void setNumberOfCalls(int numberOfCalls) {
|
||||
this.numberOfCalls = numberOfCalls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BlacklistItem{" +
|
||||
"id=" + id +
|
||||
", name='" + name + '\'' +
|
||||
", pattern='" + pattern + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package dummydomain.yetanothercallblocker.data.db;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class YacbDaoSessionFactory {
|
||||
|
||||
private final Context context;
|
||||
private final String dbName;
|
||||
|
||||
private final Object lock = new Object();
|
||||
private DaoSession daoSession;
|
||||
|
||||
public YacbDaoSessionFactory(Context context, String dbName) {
|
||||
this.context = context;
|
||||
this.dbName = dbName;
|
||||
}
|
||||
|
||||
public DaoSession getDaoSession() {
|
||||
DaoSession daoSession = this.daoSession;
|
||||
if (daoSession == null) {
|
||||
synchronized (lock) {
|
||||
daoSession = this.daoSession;
|
||||
if (daoSession == null) {
|
||||
this.daoSession = daoSession = initDaoSession();
|
||||
}
|
||||
}
|
||||
}
|
||||
return daoSession;
|
||||
}
|
||||
|
||||
private DaoSession initDaoSession() {
|
||||
YacbDbOpenHelper dbOpenHelper = new YacbDbOpenHelper(context, dbName);
|
||||
return new DaoMaster(dbOpenHelper.getWritableDb()).newSession();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package dummydomain.yetanothercallblocker.data.db;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.greenrobot.greendao.database.Database;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class YacbDbOpenHelper extends DaoMaster.OpenHelper {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(YacbDbOpenHelper.class);
|
||||
|
||||
public YacbDbOpenHelper(Context context, String name) {
|
||||
super(context, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(Database db, int oldVersion, int newVersion) {
|
||||
LOG.info("onUpgrade() oldVersion={}, newVersion={}", oldVersion, newVersion);
|
||||
|
||||
// upgrade
|
||||
|
||||
LOG.info("onUpgrade() finished");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package dummydomain.yetanothercallblocker.event;
|
||||
|
||||
public class BlacklistChangedEvent {}
|
|
@ -0,0 +1,3 @@
|
|||
package dummydomain.yetanothercallblocker.event;
|
||||
|
||||
public class BlacklistItemChangedEvent extends BlacklistChangedEvent {}
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- https://gist.github.com/mikovali/9e57c559bc459c932bc984b64f489090 -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<selector>
|
||||
<item android:state_activated="true">
|
||||
<color android:color="?attr/colorControlActivated" />
|
||||
</item>
|
||||
</selector>
|
||||
</item>
|
||||
|
||||
<item android:drawable="?attr/selectableItemBackground" />
|
||||
</layer-list>
|
|
@ -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="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" />
|
||||
</vector>
|
|
@ -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="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z" />
|
||||
</vector>
|
|
@ -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,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z" />
|
||||
</vector>
|
|
@ -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="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
||||
</vector>
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".BlacklistActivity">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/blacklistItemsList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layoutManager="LinearLayoutManager"
|
||||
tools:listitem="@layout/blacklist_item" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="@dimen/fab_margin"
|
||||
android:contentDescription="@string/blacklist_add"
|
||||
android:onClick="onAddClicked"
|
||||
android:src="@drawable/ic_plus_24dp" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -0,0 +1,51 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context=".EditBlacklistItemActivity">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/text_margin">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/nameTextField"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/edit_blacklist_item_name">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/nameEditText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textCapSentences|textAutoCorrect"
|
||||
android:maxLines="1" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/patternTextField"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/edit_blacklist_item_number_pattern">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/patternEditText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="phone" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/number_pattern_hint" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
|
@ -0,0 +1,51 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/activatedBackgroundIndicator"
|
||||
android:gravity="center_vertical|start"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="@dimen/item_padding"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingRight="@dimen/item_padding"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
tools:text="Annoying spammer" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pattern"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
tools:text="+123456789##" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="3 calls, last: Aug 01, 2020 10:10" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/errorIcon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/number_pattern_incorrect"
|
||||
android:src="@drawable/ic_error_24dp"
|
||||
app:tint="@color/design_default_color_error" />
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_block_blacklisted"
|
||||
android:checkable="true"
|
||||
android:onClick="onBlockBlacklistedChanged"
|
||||
android:title="@string/block_blacklisted_short" />
|
||||
|
||||
</menu>
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_delete"
|
||||
android:icon="@drawable/ic_delete_24dp"
|
||||
android:title="@string/blacklist_delete"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
</menu>
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:icon="@drawable/ic_check_24dp"
|
||||
android:onClick="onSaveClicked"
|
||||
android:title="@string/save"
|
||||
app:showAsAction="always" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_delete"
|
||||
android:onClick="onDeleteClicked"
|
||||
android:title="@string/blacklist_delete" />
|
||||
|
||||
</menu>
|
|
@ -25,6 +25,10 @@
|
|||
android:onClick="onUseContactsChanged"
|
||||
android:title="@string/use_contacts" />
|
||||
|
||||
<item
|
||||
android:onClick="onOpenBlacklist"
|
||||
android:title="@string/open_blacklist_activity" />
|
||||
|
||||
<item
|
||||
android:onClick="onOpenSettings"
|
||||
android:title="@string/open_settings_activity" />
|
||||
|
|
|
@ -70,7 +70,7 @@
|
|||
<string name="back">Atrás</string>
|
||||
<string name="no">No</string>
|
||||
|
||||
<string name="load_reviews_confirmation_title">¿Estás seguro?</string>
|
||||
<string name="are_you_sure">¿Estás seguro?</string>
|
||||
<string name="load_reviews_confirmation_message">Cargar las revisiones online filtrará el número a un servicio de terceros. ¿Estás seguro de que quieres hacerlo con un número presente en tus contactos?</string>
|
||||
|
||||
<string name="open_settings_activity">Ajustes</string>
|
||||
|
|
|
@ -74,7 +74,7 @@
|
|||
<string name="back">Retour</string>
|
||||
<string name="no">Non</string>
|
||||
|
||||
<string name="load_reviews_confirmation_title">Êtes-vous sûr ?</string>
|
||||
<string name="are_you_sure">Êtes-vous sûr ?</string>
|
||||
<string name="load_reviews_confirmation_message">La transmission d\'évaluations en ligne divulguera le numéro à un service tiers. Êtes-vous sûr de vouloir le faire avec un numéro présent dans vos contacts ?</string>
|
||||
|
||||
<string name="open_settings_activity">Paramètres</string>
|
||||
|
|
|
@ -70,7 +70,7 @@
|
|||
<string name="back">Terug</string>
|
||||
<string name="no">Nee</string>
|
||||
|
||||
<string name="load_reviews_confirmation_title">Weet je het zeker?</string>
|
||||
<string name="are_you_sure">Weet je het zeker?</string>
|
||||
<string name="load_reviews_confirmation_message">Door online-recensies te laden, lekt het nummer uit naar een externe dienst. Weet je zeker dat je dit wilt doen met een nummer uit je contactpersonenlijst?</string>
|
||||
|
||||
<string name="incoming_call_notifications">Inkomende oproep-meldingen</string>
|
||||
|
|
|
@ -25,6 +25,9 @@
|
|||
<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_blacklisted_short">Блокировать из ЧС</string>
|
||||
<string name="block_blacklisted">Блокировать из чёрного списка</string>
|
||||
<string name="block_blacklisted_summary">Блокировать звонки с номеров добавленных в чёрный список</string>
|
||||
<string name="use_call_screening_service">Продвинутый режим блокирования вызовов</string>
|
||||
<string name="use_call_screening_service_summary">Позволяет блокировать вызовы до того, как телефон зазвонит. Это требует назначить приложение \"приложением для звонков\" (на Android 7–9) или \"приложением для АОН и защиты от спама\" (на Android 10+)</string>
|
||||
<string name="use_call_screening_service_disable_message">Выберите другое приложение для звонков или защиты от спама в меню Настройки - Приложения - Приложения по умолчанию</string>
|
||||
|
@ -73,8 +76,9 @@
|
|||
<string name="online_reviews">Online-отзывы</string>
|
||||
<string name="add_web_review">Добавить отзыв (веб)</string>
|
||||
<string name="back">Назад</string>
|
||||
<string name="yes">Да</string>
|
||||
<string name="no">Нет</string>
|
||||
<string name="load_reviews_confirmation_title">Вы уверены?</string>
|
||||
<string name="are_you_sure">Вы уверены?</string>
|
||||
<string name="load_reviews_confirmation_message">Это номер из адресной книги! Для получения отзывов номер передается в сторонний сервис и может попасть к третьим лицам. Вы точно хотите посмотреть отзывы на этот номер?</string>
|
||||
<string name="call_log_permission_message">Разрешите доступ к журналу вызовов, чтобы здесь отображались недавние вызовы</string>
|
||||
<string name="notification_background_operation">Выполняется процесс в фоне…</string>
|
||||
|
@ -114,4 +118,25 @@
|
|||
<string name="export_logcat">Экспортировать logcat</string>
|
||||
<string name="export_logcat_summary">Экспортировать и поделиться содержимым logcat с приложением по вашему выбору (например, с email-клиентом). Отчёты могут содержать конфиденциальные данные (номера телефонов, имена контактов). Только выбранное приложение получит доступ к этим данным</string>
|
||||
<string name="no_number"><![CDATA[<нет номера>]]></string>
|
||||
<string name="save">Сохранить</string>
|
||||
<string name="selected_count">Выбрано: %1$d</string>
|
||||
|
||||
<string name="open_blacklist_activity">Чёрный список</string>
|
||||
<string name="title_blacklist_activity">Чёрный список</string>
|
||||
<plurals name="blacklist_item_stats">
|
||||
<item quantity="one">Звонил %2$s</item>
|
||||
<item quantity="few">%1$d звонка, последний: %2$s</item>
|
||||
<item quantity="many">%1$d звонков, последний: %2$s</item>
|
||||
</plurals>
|
||||
<string name="blacklist_item_date_no_info">нет информации</string>
|
||||
<string name="blacklist_add">Добавить</string>
|
||||
<string name="blacklist_delete">Удалить</string>
|
||||
<string name="blacklist_delete_confirmation">Удалить выбранные элементы?</string>
|
||||
<string name="title_add_blacklist_item_activity">Добавить</string>
|
||||
<string name="title_edit_blacklist_item_activity">Редактировать</string>
|
||||
<string name="edit_blacklist_item_name">Имя</string>
|
||||
<string name="edit_blacklist_item_number_pattern">Шаблон номера</string>
|
||||
<string name="number_pattern_incorrect">Некорректный шаблон</string>
|
||||
<string name="number_pattern_empty">Пустой шаблон</string>
|
||||
<string name="number_pattern_hint">Введите номер в формате +СТРАНА-НОМЕР (как Android показал бы в списке вызовов). Используйте \"*\" как подстановку для нуля и более цифр и \"#\" для одной цифры.</string>
|
||||
</resources>
|
|
@ -2,4 +2,5 @@
|
|||
<dimen name="app_bar_height">180dp</dimen>
|
||||
<dimen name="text_margin">16dp</dimen>
|
||||
<dimen name="item_padding">16dp</dimen>
|
||||
<dimen name="fab_margin">16dp</dimen>
|
||||
</resources>
|
||||
|
|
|
@ -72,9 +72,10 @@
|
|||
<string name="online_reviews">Online reviews</string>
|
||||
<string name="add_web_review">Add review (web)</string>
|
||||
<string name="back">Back</string>
|
||||
<string name="yes">Yes</string>
|
||||
<string name="no">No</string>
|
||||
|
||||
<string name="load_reviews_confirmation_title">Are you sure?</string>
|
||||
<string name="are_you_sure">Are you sure?</string>
|
||||
<string name="load_reviews_confirmation_message">Loading online reviews will leak the number to a 3rd party service. Are you sure you want to do it with a number present in your Contacts?</string>
|
||||
|
||||
<string name="open_settings_activity">Settings</string>
|
||||
|
@ -94,6 +95,9 @@
|
|||
<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">Block calls from hidden numbers. Experimental. Probably works better in \"advanced call blocking mode\". Please report your experience in the repo issues on gitlab</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>
|
||||
<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>
|
||||
|
@ -126,6 +130,26 @@
|
|||
<string name="export_logcat">Export logcat</string>
|
||||
<string name="export_logcat_summary">Export and share logcat contents with an app of your choosing (for example with an email client). The reports may contain sensitive data (phone numbers, contact names). Only the chosen app will have access to this data</string>
|
||||
<string name="no_number"><![CDATA[<no number>]]></string>
|
||||
<string name="save">Save</string>
|
||||
<string name="selected_count">%1$d selected</string>
|
||||
|
||||
<string name="open_blacklist_activity">Blacklist</string>
|
||||
<string name="title_blacklist_activity">Blacklist</string>
|
||||
<plurals name="blacklist_item_stats">
|
||||
<item quantity="one">Called at %2$s</item>
|
||||
<item quantity="other">%1$d calls, last: %2$s</item>
|
||||
</plurals>
|
||||
<string name="blacklist_item_date_no_info">no info</string>
|
||||
<string name="blacklist_add">Add</string>
|
||||
<string name="blacklist_delete">Delete</string>
|
||||
<string name="blacklist_delete_confirmation">Delete the selected items?</string>
|
||||
<string name="title_add_blacklist_item_activity">Add number</string>
|
||||
<string name="title_edit_blacklist_item_activity">Edit number</string>
|
||||
<string name="edit_blacklist_item_name">Name</string>
|
||||
<string name="edit_blacklist_item_number_pattern">Number pattern</string>
|
||||
<string name="number_pattern_incorrect">Incorrect pattern</string>
|
||||
<string name="number_pattern_empty">Empty pattern</string>
|
||||
<string name="number_pattern_hint">Enter the number in +COUNTRY-NUMBER format (as Android would show in your dialer). Use \"*\" as a wildcard for zero or more digits, and \"#\" for exactly one digit.</string>
|
||||
|
||||
<string name="open_debug_activity">Open debug screen</string>
|
||||
<string name="debug_activity_label">Debug</string>
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
|
||||
<item name="android:activatedBackgroundIndicator">@drawable/activated_background</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.NoActionBar">
|
||||
|
|
|
@ -38,6 +38,11 @@
|
|||
app:key="blockHiddenNumbers"
|
||||
app:summary="@string/block_hidden_number_summary"
|
||||
app:title="@string/block_hidden_number" />
|
||||
<SwitchPreferenceCompat
|
||||
app:defaultValue="true"
|
||||
app:key="blockBlacklisted"
|
||||
app:summary="@string/block_blacklisted_summary"
|
||||
app:title="@string/block_blacklisted" />
|
||||
<SwitchPreferenceCompat
|
||||
app:key="useCallScreeningService"
|
||||
app:persistent="false"
|
||||
|
|
|
@ -5,6 +5,7 @@ buildscript {
|
|||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.0.1'
|
||||
classpath 'org.greenrobot:greendao-gradle-plugin:3.3.0'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue