Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

predictable order of results #2202

Merged
merged 3 commits into from
Nov 15, 2023
Merged
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
53 changes: 49 additions & 4 deletions app/src/main/java/fr/neamar/kiss/DataHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -360,16 +360,19 @@ public void requestResults(String query, Searcher searcher) {
* @param searcher the searcher currently running
*/
public void requestAllRecords(Searcher searcher) {
List<Pojo> collectedPojos = new ArrayList<>();
for (ProviderEntry entry : this.providers.values()) {
if (searcher.isCancelled())
break;
if (entry.provider == null)
continue;

List<? extends Pojo> pojos = entry.provider.getPojos();
if (pojos != null)
searcher.addResults(pojos);
if (pojos != null) {
collectedPojos.addAll(pojos);
}
}
searcher.addResults(collectedPojos);
}

/**
Expand All @@ -380,11 +383,10 @@ public void requestAllRecords(Searcher searcher) {
*
* @param context android context
* @param itemCount max number of items to retrieve, total number may be less (search or calls are not returned for instance)
* @param historyMode Recency vs Frecency vs Frequency vs Adaptive vs Alphabetically
* @param itemsToExcludeById Items to exclude from history by their id
* @return pojos in recent history
*/
public List<Pojo> getHistory(Context context, int itemCount, String historyMode, Set<String> itemsToExcludeById) {
public List<Pojo> getHistory(Context context, int itemCount, Set<String> itemsToExcludeById) {
// Pre-allocate array slots that are likely to be used based on the current maximum item
// count
List<Pojo> history = new ArrayList<>(Math.min(itemCount, 256));
Expand All @@ -393,9 +395,11 @@ public List<Pojo> getHistory(Context context, int itemCount, String historyMode,
int extendedItemCount = itemCount + itemsToExcludeById.size();

// Read history
String historyMode = getHistoryMode();
List<ValuedHistoryRecord> ids = DBHelper.getHistory(context, extendedItemCount, historyMode);

// Find associated items
int size = ids.size();
for (int i = 0; i < ids.size(); i++) {
// Ask all providers if they know this id
Pojo pojo = getPojo(ids.get(i).record);
Expand All @@ -408,6 +412,7 @@ public List<Pojo> getHistory(Context context, int itemCount, String historyMode,
continue;
}

pojo.relevance = size - i;
history.add(pojo);

// Break if maximum number of items have been retrieved
Expand All @@ -419,6 +424,46 @@ public List<Pojo> getHistory(Context context, int itemCount, String historyMode,
return history;
}

/**
* Apply relevance from history to given pojos.
*
* @param pojos which needs to have relevance set
* @param historyMode
*/
public void applyRelevanceFromHistory(List<? extends Pojo> pojos, String historyMode) {
if ("alphabetically".equals(historyMode)) {
// "alphabetically" is special case because relevance needs to be set for all pojos instead of these from history.
// This is done by setting all relevance to zero which results in order by name from used comparator.
for (Pojo pojo : pojos) {
pojo.relevance = 0;
}
} else {
// Get length of history, this is needed so there are no entries missed.
// If only number of displayed elements is used, this will result in more entries to be sorted by name.
int historyLength = getHistoryLength();

Map<String, Integer> relevance = new HashMap<>();
List<ValuedHistoryRecord> ids = DBHelper.getHistory(context, historyLength, historyMode);
int size = ids.size();
for (int i = 0; i < size; i++) {
relevance.put(ids.get(i).record, size - i);
}

for (Pojo pojo : pojos) {
Integer calculated = relevance.get(pojo.id);
pojo.relevance = calculated != null ? calculated : 0;
}
}
}

/**
* @return history mode from settings: Recency vs Frecency vs Frequency vs Adaptive vs Alphabetically
*/
public String getHistoryMode() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
return prefs.getString("history-mode", "recency");
}

public int getHistoryLength() {
return DBHelper.getHistoryLength(this.context);
}
Expand Down
12 changes: 5 additions & 7 deletions app/src/main/java/fr/neamar/kiss/db/DBHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -195,13 +195,11 @@ public static int getHistoryLength(Context context) {
// Cursor query (boolean distinct, String table, String[] columns,
// String selection, String[] selectionArgs, String groupBy, String
// having, String orderBy, String limit)
Cursor cursor = db.query(false, "history", new String[]{"COUNT(*)"}, null, null,
null, null, null, null);

cursor.moveToFirst();
int historyLength = cursor.getInt(0);
cursor.close();
return historyLength;
try (Cursor cursor = db.query(false, "history", new String[]{"COUNT(*)"}, null, null,
null, null, null, null)) {
cursor.moveToFirst();
return cursor.getInt(0);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
import fr.neamar.kiss.KissApplication;
import fr.neamar.kiss.MainActivity;
import fr.neamar.kiss.pojo.AppPojo;
import fr.neamar.kiss.pojo.ReversedNameComparator;
import fr.neamar.kiss.pojo.Pojo;
import fr.neamar.kiss.pojo.ReversedNameComparator;
import fr.neamar.kiss.pojo.ShortcutPojo;

/**
Expand Down Expand Up @@ -59,8 +59,13 @@ protected Void doInBackground(Void... voids) {
@Override
protected void onPostExecute(Void param) {
super.onPostExecute(param);

MainActivity activity = activityWeakReference.get();
if (activity == null)
return;

// Build sections for fast scrolling
activityWeakReference.get().adapter.buildSections();
activity.adapter.buildSections();
}

/**
Expand Down
20 changes: 8 additions & 12 deletions app/src/main/java/fr/neamar/kiss/searcher/HistorySearcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.util.List;
import java.util.Set;

import fr.neamar.kiss.DataHandler;
import fr.neamar.kiss.KissApplication;
import fr.neamar.kiss.MainActivity;
import fr.neamar.kiss.db.ShortcutRecord;
Expand All @@ -27,7 +28,7 @@ public HistorySearcher(MainActivity activity) {
}

@Override
int getMaxResultCount() {
protected int getMaxResultCount() {
// Convert `"number-of-display-elements"` to double first before truncating to int to avoid
// `java.lang.NumberFormatException` crashes for values larger than `Integer.MAX_VALUE`
try {
Expand All @@ -40,21 +41,22 @@ int getMaxResultCount() {
@Override
protected Void doInBackground(Void... voids) {
// Ask for records
String historyMode = prefs.getString("history-mode", "recency");
boolean excludeFavorites = prefs.getBoolean("exclude-favorites-history", false);

MainActivity activity = activityWeakReference.get();
if (activity == null)
return null;

DataHandler dataHandler = KissApplication.getApplication(activity).getDataHandler();

//Gather excluded
Set<String> excludedFromHistory = KissApplication.getApplication(activity).getDataHandler().getExcludedFromHistory();
Set<String> excludedFromHistory = dataHandler.getExcludedFromHistory();
Set<String> excludedPojoById = new HashSet<>(excludedFromHistory);

// add ids of shortcuts for excluded apps
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
for (String id : excludedFromHistory) {
Pojo pojo = KissApplication.getApplication(activity).getDataHandler().getItemById(id);
Pojo pojo = dataHandler.getItemById(id);
if (pojo instanceof AppPojo) {
List<ShortcutInfo> shortcutInfos = ShortcutUtil.getShortcuts(activity, ((AppPojo) pojo).packageName);
for (ShortcutInfo shortcutInfo : shortcutInfos) {
Expand All @@ -69,18 +71,12 @@ protected Void doInBackground(Void... voids) {

if (excludeFavorites) {
// Gather favorites
for (Pojo favoritePojo : KissApplication.getApplication(activity).getDataHandler().getFavorites()) {
for (Pojo favoritePojo : dataHandler.getFavorites()) {
excludedPojoById.add(favoritePojo.id);
}
}

List<Pojo> pojos = KissApplication.getApplication(activity).getDataHandler()
.getHistory(activity, getMaxResultCount(), historyMode, excludedPojoById);

int size = pojos.size();
for(int i = 0; i < size; i += 1) {
pojos.get(i).relevance = size - i;
}
List<Pojo> pojos = dataHandler.getHistory(activity, getMaxResultCount(), excludedPojoById);

this.addResults(pojos);
return null;
Expand Down
77 changes: 77 additions & 0 deletions app/src/main/java/fr/neamar/kiss/searcher/PojoWithTagSearcher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package fr.neamar.kiss.searcher;

import android.content.SharedPreferences;
import android.preference.PreferenceManager;

import androidx.annotation.NonNull;

import java.util.ArrayList;
import java.util.List;

import fr.neamar.kiss.KissApplication;
import fr.neamar.kiss.MainActivity;
import fr.neamar.kiss.pojo.Pojo;
import fr.neamar.kiss.pojo.PojoWithTags;

/**
* Returns a list of all results that match the specified pojo with tags.
*/
public abstract class PojoWithTagSearcher extends Searcher {

private final SharedPreferences prefs;

public PojoWithTagSearcher(MainActivity activity, String query) {
super(activity, query);
prefs = PreferenceManager.getDefaultSharedPreferences(activity);
}

@Override
protected Void doInBackground(Void... voids) {
MainActivity activity = activityWeakReference.get();
if (activity == null)
return null;

KissApplication.getApplication(activity).getDataHandler().requestAllRecords(this);

return null;
}

@Override
public boolean addResults(List<? extends Pojo> pojos) {
List<Pojo> filteredPojos = new ArrayList<>();
for (Pojo pojo : pojos) {
if (!(pojo instanceof PojoWithTags)) {
continue;
}
PojoWithTags pojoWithTags = (PojoWithTags) pojo;
if (acceptPojo(pojoWithTags)) {
filteredPojos.add(pojoWithTags);
}
}

MainActivity activity = activityWeakReference.get();
if (activity == null) {
return false;
}

KissApplication.getApplication(activity).getDataHandler().applyRelevanceFromHistory(filteredPojos, getTaggedResultSortMode());

return super.addResults(filteredPojos);
}

@NonNull
private String getTaggedResultSortMode() {
String sortMode = prefs.getString("tagged-result-sort-mode", "default");
if ("default".equals(sortMode)) {
sortMode = KissApplication.getApplication(activityWeakReference.get()).getDataHandler().getHistoryMode();
}
return sortMode;

}

protected int getMaxResultCount() {
return Integer.MAX_VALUE;
}

abstract protected boolean acceptPojo(PojoWithTags pojoWithTags);
}
6 changes: 1 addition & 5 deletions app/src/main/java/fr/neamar/kiss/searcher/Searcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ PriorityQueue<Pojo> getPojoProcessor(Context context) {
return new PriorityQueue<>(DEFAULT_MAX_RESULTS, new RelevanceComparator());
}

int getMaxResultCount() {
protected int getMaxResultCount() {
return DEFAULT_MAX_RESULTS;
}

Expand All @@ -69,10 +69,6 @@ public boolean addResults(List<? extends Pojo> pojos) {
if (isCancelled())
return false;

MainActivity activity = activityWeakReference.get();
if (activity == null)
return false;

return this.processedPojos.addAll(pojos);
}

Expand Down
45 changes: 4 additions & 41 deletions app/src/main/java/fr/neamar/kiss/searcher/TagsSearcher.java
Original file line number Diff line number Diff line change
@@ -1,56 +1,19 @@
package fr.neamar.kiss.searcher;

import java.util.ArrayList;
import java.util.List;

import fr.neamar.kiss.KissApplication;
import fr.neamar.kiss.MainActivity;
import fr.neamar.kiss.pojo.Pojo;
import fr.neamar.kiss.pojo.PojoWithTags;

/**
* Returns a list of all applications that match the specified tag
* Returns a list of all results that match the specified tag
*/

public class TagsSearcher extends Searcher {
public class TagsSearcher extends PojoWithTagSearcher {
public TagsSearcher(MainActivity activity, String query) {
super(activity, query == null ? "<tags>" : query);
}

@Override
public boolean addResults(List<? extends Pojo> pojos) {
List<Pojo> filteredPojos = new ArrayList<>();
for (Pojo pojo : pojos) {
if (!(pojo instanceof PojoWithTags)) {
continue;
}
PojoWithTags pojoWithTags = (PojoWithTags) pojo;
if (pojoWithTags.getTags() == null || pojoWithTags.getTags().isEmpty()) {
continue;
}

if (!pojoWithTags.getTags().contains(query)) {
continue;
}

filteredPojos.add(pojoWithTags);
}
return super.addResults(filteredPojos);
protected boolean acceptPojo(PojoWithTags pojoWithTags) {
return pojoWithTags.getTags() != null && pojoWithTags.getTags().contains(query);
}

@Override
protected Void doInBackground(Void... voids) {
MainActivity activity = activityWeakReference.get();
if (activity == null)
return null;

KissApplication.getApplication(activity).getDataHandler().requestAllRecords(this);

return null;
}

@Override
int getMaxResultCount() {
return 250;
}
}
Loading