Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/src/main/java/eu/faircode/netguard/ActivityMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -911,6 +911,9 @@ public boolean onMenuItemActionCollapse(MenuItem item) {
});

final SearchView searchView = (SearchView) menuSearch.getActionView();
// Hint at the "tracker:<company>" search prefix (#447), which filters apps
// by the tracker companies they have been observed contacting.
searchView.setQueryHint(getString(R.string.menu_search_hint));
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
Expand Down
51 changes: 40 additions & 11 deletions app/src/main/java/eu/faircode/netguard/AdapterRule.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,13 @@
import net.kollnig.missioncontrol.R;
import net.kollnig.missioncontrol.data.BlockingMode;
import net.kollnig.missioncontrol.data.InternetBlocklist;
import net.kollnig.missioncontrol.data.TrackerList;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

public class AdapterRule extends RecyclerView.Adapter<AdapterRule.ViewHolder> implements Filterable {
private static final String TAG = "TrackerControl.Adapter";
Expand All @@ -78,6 +82,7 @@ public class AdapterRule extends RecyclerView.Adapter<AdapterRule.ViewHolder> im
private boolean wifiActive = true;
private boolean otherActive = true;
private boolean live = true;
private final Context appContext;
private List<Rule> listAll = new ArrayList<>();
private List<Rule> listFiltered = new ArrayList<>();
private int iconSize;
Expand Down Expand Up @@ -106,6 +111,7 @@ public ViewHolder(View itemView) {

public AdapterRule(Context context, View anchor) {
this.anchor = anchor;
this.appContext = context.getApplicationContext();
this.inflater = LayoutInflater.from(context);
this.glideOptions = new RequestOptions().format(DecodeFormat.PREFER_RGB_565);

Expand Down Expand Up @@ -402,6 +408,11 @@ private void updateRule(Context context, Rule rule, boolean root, List<Rule> lis
}
}

// Prefix that switches the search box from matching app name/package/uid to
// matching the tracker companies an app has been observed contacting, e.g.
// "tracker:gravy analytics" (#447).
private static final String TRACKER_SEARCH_PREFIX = "tracker:";

@Override
public Filter getFilter() {
return new Filter() {
Expand All @@ -411,18 +422,36 @@ protected FilterResults performFiltering(CharSequence query) {
if (query == null)
listResult.addAll(listAll);
else {
String queryStr = query.toString().toLowerCase().trim();
int uid;
try {
uid = Integer.parseInt(queryStr);
} catch (NumberFormatException ignore) {
uid = -1;
String queryStr = query.toString().toLowerCase(Locale.ROOT).trim();
if (queryStr.startsWith(TRACKER_SEARCH_PREFIX)) {
String company = queryStr.substring(TRACKER_SEARCH_PREFIX.length()).trim();
// Built once per filter invocation (single DB scan), not per
// row, and runs on this Filter's background thread.
Map<Integer, Set<String>> trackersByUid =
TrackerList.getInstance(appContext).getTrackerNamesByUid();
for (Rule rule : listAll) {
Set<String> companies = trackersByUid.get(rule.uid);
if (companies == null)
continue;
for (String name : companies)
if (name != null && name.toLowerCase(Locale.ROOT).contains(company)) {
listResult.add(rule);
break;
}
}
} else {
int uid;
try {
uid = Integer.parseInt(queryStr);
} catch (NumberFormatException ignore) {
uid = -1;
}
for (Rule rule : listAll)
if (rule.uid == uid ||
rule.packageName.toLowerCase().contains(queryStr) ||
(rule.name != null && rule.name.toLowerCase().contains(queryStr)))
listResult.add(rule);
}
for (Rule rule : listAll)
if (rule.uid == uid ||
rule.packageName.toLowerCase().contains(queryStr) ||
(rule.name != null && rule.name.toLowerCase().contains(queryStr)))
listResult.add(rule);
}

FilterResults result = new FilterResults();
Expand Down
26 changes: 26 additions & 0 deletions app/src/main/java/net/kollnig/missioncontrol/data/TrackerList.java
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,32 @@ public synchronized void invalidateTrackerCountCache() {
cachedTrackerCounts = null;
}

/**
* Retrieves the set of contacted tracker company names, per app UID (#447), so
* the app-list search can filter apps by "tracker:<company>". A single DB scan
* builds a lookup for every app at once, instead of querying per row; the
* caller (AdapterRule's search Filter) already runs on a background thread, so
* no caching is needed here.
*
* @return Map of app UID to the set of tracker company names observed for it
*/
public synchronized Map<Integer, Set<String>> getTrackerNamesByUid() {
Map<Integer, Set<String>> trackers = new ArrayMap<>();

try (Cursor cursor = databaseHelper.getHosts()) {
if (cursor.moveToFirst()) {
do {
int appUid = cursor.getInt(cursor.getColumnIndexOrThrow("uid"));
String hostname = cursor.getString(cursor.getColumnIndexOrThrow("daddr"));
Tracker tracker = findTracker(hostname);
checkTracker(trackers, appUid, tracker);
} while (cursor.moveToNext());
}
}

return trackers;
}

/**
* Helper method to check tracker
*
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<string name="channel_access">Access notifications</string>

<string name="menu_search">Search for app</string>
<string name="menu_search_hint">Search app, or tracker:company</string>
<string name="menu_filter">Filter apps</string>
<string name="menu_app_user">Show user apps</string>
<string name="menu_app_system">Show system apps</string>
Expand Down