blob: 75b2e2ae874801b35a274bff8b39c5156e85d445 [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quicksearchbox;
import com.google.common.annotations.VisibleForTesting;
import android.util.Log;
import java.util.Iterator;
import java.util.LinkedList;
/**
* A promoter that gives preference to suggestions from higher ranking corpora.
*/
public class RankAwarePromoter extends AbstractPromoter {
private static final boolean DBG = false;
private static final String TAG = "QSB.RankAwarePromoter";
public RankAwarePromoter(Config config, SuggestionFilter filter, Promoter next) {
super(filter, next, config);
}
@Override
public void doPickPromoted(Suggestions suggestions,
int maxPromoted, ListSuggestionCursor promoted) {
promoteSuggestions(suggestions.getCorpusResults(), maxPromoted, promoted);
}
@VisibleForTesting
void promoteSuggestions(Iterable<CorpusResult> suggestions, int maxPromoted,
ListSuggestionCursor promoted) {
if (DBG) Log.d(TAG, "Available results: " + suggestions);
// Split non-empty results into important suggestions and not-so-important
// suggestions, each corpus's cursor positioned at the first suggestion.
LinkedList<CorpusResult> highRankingSuggestions = new LinkedList<CorpusResult>();
LinkedList<CorpusResult> lowRankingSuggestions = new LinkedList<CorpusResult>();
partitionSuggestionsByRank(suggestions, highRankingSuggestions, lowRankingSuggestions);
// Top results, evenly distributed between each high-ranking corpus.
promoteTopSuggestions(highRankingSuggestions, promoted, maxPromoted);
// Then try to fill promoted list with the remaining high-ranking suggestions,
// and then use the low-ranking suggestions if the list isn't full yet.
promoteEquallyFromEachCorpus(highRankingSuggestions, promoted, maxPromoted);
promoteEquallyFromEachCorpus(lowRankingSuggestions, promoted, maxPromoted);
if (DBG) Log.d(TAG, "Returning " + promoted.toString());
}
/**
* Shares the top slots evenly among each of the high-ranking (default) corpora.
*
* The corpora will appear in the promoted list in the order they are listed
* among the incoming suggestions (this method doesn't change their order).
*/
private void promoteTopSuggestions(LinkedList<CorpusResult> highRankingSuggestions,
ListSuggestionCursor promoted, int maxPromoted) {
int slotsLeft = getSlotsLeft(promoted, maxPromoted);
if (slotsLeft > 0 && !highRankingSuggestions.isEmpty()) {
int slotsToFill = Math.min(getSlotsAboveKeyboard() - promoted.getCount(), slotsLeft);
if (slotsToFill > 0) {
int stripeSize = Math.max(1, slotsToFill / highRankingSuggestions.size());
roundRobin(highRankingSuggestions, slotsToFill, stripeSize, promoted);
}
}
}
/**
* Tries to promote the same number of elements from each corpus.
*
* The corpora will appear in the promoted list in the order they are listed
* among the incoming suggestions (this method doesn't change their order).
*/
private void promoteEquallyFromEachCorpus(LinkedList<CorpusResult> suggestions,
ListSuggestionCursor promoted, int maxPromoted) {
int slotsLeft = getSlotsLeft(promoted, maxPromoted);
if (slotsLeft == 0) {
// No more items to add.
return;
}
if (suggestions.isEmpty()) {
return;
}
int stripeSize = Math.max(1, slotsLeft / suggestions.size());
roundRobin(suggestions, slotsLeft, stripeSize, promoted);
// We may still have a few slots left
slotsLeft = getSlotsLeft(promoted, maxPromoted);
roundRobin(suggestions, slotsLeft, slotsLeft, promoted);
}
/**
* Partitions the suggestions into "important" (high-ranking)
* and "not-so-important" (low-ranking) suggestions, dependent on the
* rank of the corpus the result is part of.
*
* @param suggestions
* @param highRankingSuggestions These should be displayed first to the
* user.
* @param lowRankingSuggestions These should be displayed if the
* high-ranking suggestions don't fill all the available space in the
* result view.
*/
private void partitionSuggestionsByRank(Iterable<CorpusResult> suggestions,
LinkedList<CorpusResult> highRankingSuggestions,
LinkedList<CorpusResult> lowRankingSuggestions) {
for (CorpusResult result : suggestions) {
if (result.getCount() > 0) {
result.moveTo(0);
Corpus corpus = result.getCorpus();
if (isCorpusHighlyRanked(corpus)) {
highRankingSuggestions.add(result);
} else {
lowRankingSuggestions.add(result);
}
}
}
}
private boolean isCorpusHighlyRanked(Corpus corpus) {
// The default corpora shipped with QSB (apps, etc.) are
// more important than ones that were registered later.
return corpus == null || corpus.isCorpusDefaultEnabled();
}
private int getSlotsLeft(ListSuggestionCursor promoted, int maxPromoted) {
// It's best to calculate this after each addition because duplicates
// may get filtered out automatically in the list of promoted items.
return Math.max(0, maxPromoted - promoted.getCount());
}
private int getSlotsAboveKeyboard() {
return getConfig().getNumSuggestionsAboveKeyboard();
}
/**
* Promotes "stripes" of suggestions from each corpus.
*
* @param results the list of CorpusResults from which to promote.
* Exhausted CorpusResults are removed from the list.
* @param maxPromoted maximum number of suggestions to promote.
* @param stripeSize number of suggestions to take from each corpus.
* @param promoted the list to which promoted suggestions are added.
* @return the number of suggestions actually promoted.
*/
private int roundRobin(LinkedList<CorpusResult> results, int maxPromoted, int stripeSize,
ListSuggestionCursor promoted) {
int count = 0;
if (maxPromoted > 0 && !results.isEmpty()) {
for (Iterator<CorpusResult> iter = results.iterator();
count < maxPromoted && iter.hasNext();) {
CorpusResult result = iter.next();
count += promote(result, stripeSize, promoted);
if (result.getPosition() == result.getCount()) {
iter.remove();
}
}
}
return count;
}
/**
* Copies suggestions from a SuggestionCursor to the list of promoted suggestions.
*
* @param cursor from which to copy the suggestions
* @param count maximum number of suggestions to copy
* @param promoted the list to which to add the suggestions
* @return the number of suggestions actually copied.
*/
private int promote(SuggestionCursor cursor, int count, ListSuggestionCursor promoted) {
if (count < 1 || cursor.getPosition() >= cursor.getCount()) {
return 0;
}
int addedCount = 0;
do {
if (accept(cursor)) {
if (promoted.add(new SuggestionPosition(cursor))) {
// Added successfully (wasn't already promoted).
addedCount++;
}
}
} while (cursor.moveToNext() && addedCount < count);
return addedCount;
}
}