• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.quicksearchbox;
18 
19 import com.google.common.annotations.VisibleForTesting;
20 
21 import android.util.Log;
22 
23 import java.util.Iterator;
24 import java.util.LinkedList;
25 
26 /**
27  * A promoter that gives preference to suggestions from higher ranking corpora.
28  */
29 public class RankAwarePromoter extends AbstractPromoter {
30 
31     private static final boolean DBG = false;
32     private static final String TAG = "QSB.RankAwarePromoter";
33 
RankAwarePromoter(Config config, SuggestionFilter filter, Promoter next)34     public RankAwarePromoter(Config config, SuggestionFilter filter, Promoter next) {
35         super(filter, next, config);
36     }
37 
38     @Override
doPickPromoted(Suggestions suggestions, int maxPromoted, ListSuggestionCursor promoted)39     public void doPickPromoted(Suggestions suggestions,
40             int maxPromoted, ListSuggestionCursor promoted) {
41         promoteSuggestions(suggestions.getCorpusResults(), maxPromoted, promoted);
42     }
43 
44     @VisibleForTesting
promoteSuggestions(Iterable<CorpusResult> suggestions, int maxPromoted, ListSuggestionCursor promoted)45     void promoteSuggestions(Iterable<CorpusResult> suggestions, int maxPromoted,
46             ListSuggestionCursor promoted) {
47         if (DBG) Log.d(TAG, "Available results: " + suggestions);
48 
49         // Split non-empty results into important suggestions and not-so-important
50         // suggestions, each corpus's cursor positioned at the first suggestion.
51         LinkedList<CorpusResult> highRankingSuggestions = new LinkedList<CorpusResult>();
52         LinkedList<CorpusResult> lowRankingSuggestions = new LinkedList<CorpusResult>();
53         partitionSuggestionsByRank(suggestions, highRankingSuggestions, lowRankingSuggestions);
54 
55         // Top results, evenly distributed between each high-ranking corpus.
56         promoteTopSuggestions(highRankingSuggestions, promoted, maxPromoted);
57 
58         // Then try to fill promoted list with the remaining high-ranking suggestions,
59         // and then use the low-ranking suggestions if the list isn't full yet.
60         promoteEquallyFromEachCorpus(highRankingSuggestions, promoted, maxPromoted);
61         promoteEquallyFromEachCorpus(lowRankingSuggestions, promoted, maxPromoted);
62 
63         if (DBG) Log.d(TAG, "Returning " + promoted.toString());
64     }
65 
66     /**
67      * Shares the top slots evenly among each of the high-ranking (default) corpora.
68      *
69      * The corpora will appear in the promoted list in the order they are listed
70      * among the incoming suggestions (this method doesn't change their order).
71      */
promoteTopSuggestions(LinkedList<CorpusResult> highRankingSuggestions, ListSuggestionCursor promoted, int maxPromoted)72     private void promoteTopSuggestions(LinkedList<CorpusResult> highRankingSuggestions,
73             ListSuggestionCursor promoted, int maxPromoted) {
74 
75         int slotsLeft = getSlotsLeft(promoted, maxPromoted);
76         if (slotsLeft > 0 && !highRankingSuggestions.isEmpty()) {
77             int slotsToFill = Math.min(getSlotsAboveKeyboard() - promoted.getCount(), slotsLeft);
78 
79             if (slotsToFill > 0) {
80                 int stripeSize = Math.max(1, slotsToFill / highRankingSuggestions.size());
81                 roundRobin(highRankingSuggestions, slotsToFill, stripeSize, promoted);
82             }
83         }
84     }
85 
86     /**
87      * Tries to promote the same number of elements from each corpus.
88      *
89      * The corpora will appear in the promoted list in the order they are listed
90      * among the incoming suggestions (this method doesn't change their order).
91      */
promoteEquallyFromEachCorpus(LinkedList<CorpusResult> suggestions, ListSuggestionCursor promoted, int maxPromoted)92     private void promoteEquallyFromEachCorpus(LinkedList<CorpusResult> suggestions,
93             ListSuggestionCursor promoted, int maxPromoted) {
94 
95         int slotsLeft = getSlotsLeft(promoted, maxPromoted);
96         if (slotsLeft == 0) {
97             // No more items to add.
98             return;
99         }
100 
101         if (suggestions.isEmpty()) {
102             return;
103         }
104 
105         int stripeSize = Math.max(1, slotsLeft / suggestions.size());
106         roundRobin(suggestions, slotsLeft, stripeSize, promoted);
107 
108         // We may still have a few slots left
109         slotsLeft = getSlotsLeft(promoted, maxPromoted);
110         roundRobin(suggestions, slotsLeft, slotsLeft, promoted);
111     }
112 
113     /**
114      * Partitions the suggestions into "important" (high-ranking)
115      * and "not-so-important" (low-ranking) suggestions, dependent on the
116      * rank of the corpus the result is part of.
117      *
118      * @param suggestions
119      * @param highRankingSuggestions These should be displayed first to the
120      *     user.
121      * @param lowRankingSuggestions These should be displayed if the
122      *     high-ranking suggestions don't fill all the available space in the
123      *     result view.
124      */
partitionSuggestionsByRank(Iterable<CorpusResult> suggestions, LinkedList<CorpusResult> highRankingSuggestions, LinkedList<CorpusResult> lowRankingSuggestions)125     private void partitionSuggestionsByRank(Iterable<CorpusResult> suggestions,
126             LinkedList<CorpusResult> highRankingSuggestions,
127             LinkedList<CorpusResult> lowRankingSuggestions) {
128 
129         for (CorpusResult result : suggestions) {
130             if (result.getCount() > 0) {
131                 result.moveTo(0);
132                 Corpus corpus = result.getCorpus();
133                 if (isCorpusHighlyRanked(corpus)) {
134                     highRankingSuggestions.add(result);
135                 } else {
136                     lowRankingSuggestions.add(result);
137                 }
138             }
139         }
140     }
141 
isCorpusHighlyRanked(Corpus corpus)142     private boolean isCorpusHighlyRanked(Corpus corpus) {
143         // The default corpora shipped with QSB (apps, etc.) are
144         // more important than ones that were registered later.
145         return corpus == null || corpus.isCorpusDefaultEnabled();
146     }
147 
getSlotsLeft(ListSuggestionCursor promoted, int maxPromoted)148     private int getSlotsLeft(ListSuggestionCursor promoted, int maxPromoted) {
149         // It's best to calculate this after each addition because duplicates
150         // may get filtered out automatically in the list of promoted items.
151         return Math.max(0, maxPromoted - promoted.getCount());
152     }
153 
getSlotsAboveKeyboard()154     private int getSlotsAboveKeyboard() {
155         return getConfig().getNumSuggestionsAboveKeyboard();
156     }
157 
158     /**
159      * Promotes "stripes" of suggestions from each corpus.
160      *
161      * @param results     the list of CorpusResults from which to promote.
162      *                    Exhausted CorpusResults are removed from the list.
163      * @param maxPromoted maximum number of suggestions to promote.
164      * @param stripeSize  number of suggestions to take from each corpus.
165      * @param promoted    the list to which promoted suggestions are added.
166      * @return the number of suggestions actually promoted.
167      */
roundRobin(LinkedList<CorpusResult> results, int maxPromoted, int stripeSize, ListSuggestionCursor promoted)168     private int roundRobin(LinkedList<CorpusResult> results, int maxPromoted, int stripeSize,
169             ListSuggestionCursor promoted) {
170         int count = 0;
171         if (maxPromoted > 0 && !results.isEmpty()) {
172             for (Iterator<CorpusResult> iter = results.iterator();
173                  count < maxPromoted && iter.hasNext();) {
174                 CorpusResult result = iter.next();
175                 count += promote(result, stripeSize, promoted);
176                 if (result.getPosition() == result.getCount()) {
177                     iter.remove();
178                 }
179             }
180         }
181         return count;
182     }
183 
184     /**
185      * Copies suggestions from a SuggestionCursor to the list of promoted suggestions.
186      *
187      * @param cursor from which to copy the suggestions
188      * @param count maximum number of suggestions to copy
189      * @param promoted the list to which to add the suggestions
190      * @return the number of suggestions actually copied.
191      */
promote(SuggestionCursor cursor, int count, ListSuggestionCursor promoted)192     private int promote(SuggestionCursor cursor, int count, ListSuggestionCursor promoted) {
193         if (count < 1 || cursor.getPosition() >= cursor.getCount()) {
194             return 0;
195         }
196         int addedCount = 0;
197         do {
198             if (accept(cursor)) {
199                 if (promoted.add(new SuggestionPosition(cursor))) {
200                     // Added successfully (wasn't already promoted).
201                     addedCount++;
202                 }
203             }
204         } while (cursor.moveToNext() && addedCount < count);
205         return addedCount;
206     }
207 
208 }
209