RankAwarePromoter.java revision b83882b9efa37ec0f20a0f1c85cf5ccc93194aee
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.quicksearchbox; 18 19import com.google.common.annotations.VisibleForTesting; 20 21import android.util.Log; 22 23import java.util.Iterator; 24import java.util.LinkedList; 25 26/** 27 * A promoter that gives preference to suggestions from higher ranking corpora. 28 */ 29public class RankAwarePromoter implements Promoter { 30 31 private static final boolean DBG = false; 32 private static final String TAG = "QSB.RankAwarePromoter"; 33 34 private final Config mConfig; 35 36 public RankAwarePromoter(Config config) { 37 mConfig = config; 38 } 39 40 protected Config getConfig() { 41 return mConfig; 42 } 43 44 public void pickPromoted(Suggestions suggestions, 45 int maxPromoted, ListSuggestionCursor promoted) { 46 promoteSuggestions(suggestions.getCorpusResults(), maxPromoted, promoted); 47 } 48 49 @VisibleForTesting 50 void promoteSuggestions(Iterable<CorpusResult> suggestions, int maxPromoted, 51 ListSuggestionCursor promoted) { 52 if (DBG) Log.d(TAG, "Available results: " + suggestions); 53 54 // Split non-empty results into default sources and other, positioned at first suggestion 55 LinkedList<CorpusResult> defaultResults = new LinkedList<CorpusResult>(); 56 LinkedList<CorpusResult> otherResults = new LinkedList<CorpusResult>(); 57 for (CorpusResult result : suggestions) { 58 if (result.getCount() > 0) { 59 result.moveTo(0); 60 Corpus corpus = result.getCorpus(); 61 if (corpus == null || corpus.isCorpusDefaultEnabled()) { 62 defaultResults.add(result); 63 } else { 64 otherResults.add(result); 65 } 66 } 67 } 68 69 // Share the top slots equally among each of the default corpora 70 if (maxPromoted > 0 && !defaultResults.isEmpty()) { 71 int slotsToFill = Math.min(getSlotsAboveKeyboard() - promoted.getCount(), maxPromoted); 72 if (slotsToFill > 0) { 73 int stripeSize = Math.max(1, slotsToFill / defaultResults.size()); 74 maxPromoted -= roundRobin(defaultResults, slotsToFill, stripeSize, promoted); 75 } 76 } 77 78 // Then try to fill with the remaining promoted results 79 if (maxPromoted > 0 && !defaultResults.isEmpty()) { 80 int stripeSize = Math.max(1, maxPromoted / defaultResults.size()); 81 maxPromoted -= roundRobin(defaultResults, maxPromoted, stripeSize, promoted); 82 // We may still have a few slots left 83 maxPromoted -= roundRobin(defaultResults, maxPromoted, maxPromoted, promoted); 84 } 85 86 // Then try to fill with the rest 87 if (maxPromoted > 0 && !otherResults.isEmpty()) { 88 int stripeSize = Math.max(1, maxPromoted / otherResults.size()); 89 maxPromoted -= roundRobin(otherResults, maxPromoted, stripeSize, promoted); 90 // We may still have a few slots left 91 maxPromoted -= roundRobin(otherResults, maxPromoted, maxPromoted, promoted); 92 } 93 94 if (DBG) Log.d(TAG, "Returning " + promoted.toString()); 95 } 96 97 private int getSlotsAboveKeyboard() { 98 return mConfig.getNumSuggestionsAboveKeyboard(); 99 } 100 101 /** 102 * Promotes "stripes" of suggestions from each corpus. 103 * 104 * @param results the list of CorpusResults from which to promote. 105 * Exhausted CorpusResults are removed from the list. 106 * @param maxPromoted maximum number of suggestions to promote. 107 * @param stripeSize number of suggestions to take from each corpus. 108 * @param promoted the list to which promoted suggestions are added. 109 * @return the number of suggestions actually promoted. 110 */ 111 private int roundRobin(LinkedList<CorpusResult> results, int maxPromoted, int stripeSize, 112 ListSuggestionCursor promoted) { 113 int count = 0; 114 if (maxPromoted > 0 && !results.isEmpty()) { 115 for (Iterator<CorpusResult> iter = results.iterator(); 116 count < maxPromoted && iter.hasNext();) { 117 CorpusResult result = iter.next(); 118 count += promote(result, stripeSize, promoted); 119 if (result.getPosition() == result.getCount()) { 120 iter.remove(); 121 } 122 } 123 } 124 return count; 125 } 126 127 /** 128 * Copies suggestions from a SuggestionCursor to the list of promoted suggestions. 129 * 130 * @param cursor from which to copy the suggestions 131 * @param count maximum number of suggestions to copy 132 * @param promoted the list to which to add the suggestions 133 * @return the number of suggestions actually copied. 134 */ 135 private int promote(SuggestionCursor cursor, int count, ListSuggestionCursor promoted) { 136 if (count < 1 || cursor.getPosition() >= cursor.getCount()) { 137 return 0; 138 } 139 int addedCount = 0; 140 do { 141 if (accept(cursor)) { 142 promoted.add(new SuggestionPosition(cursor)); 143 addedCount++; 144 } 145 } while (cursor.moveToNext() && addedCount < count); 146 return addedCount; 147 } 148 149 /** 150 * Determines if a suggestion should be added to the promoted suggestion list. 151 * 152 * @param s The suggestion in question 153 * @return true to include it in the results 154 */ 155 protected boolean accept(Suggestion s) { 156 return true; 157 } 158 159} 160