RankAwarePromoter.java revision 4572856ac85bb53ea06e65d00beebdf336af9b27
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 android.util.Log;
20
21import java.util.ArrayList;
22import java.util.Iterator;
23import java.util.LinkedList;
24
25/**
26 * A promoter that gives preference to suggestions from higher ranking corpora.
27 */
28public class RankAwarePromoter implements Promoter<CorpusResult> {
29
30    private static final boolean DBG = false;
31    private static final String TAG = "QSB.RankAwarePromoter";
32
33    private final Config mConfig;
34
35    public RankAwarePromoter(Config config) {
36        mConfig = config;
37    }
38
39    public void pickPromoted(SuggestionCursor shortcuts, ArrayList<CorpusResult> suggestions,
40            int maxPromoted, ListSuggestionCursor promoted) {
41
42        if (DBG) Log.d(TAG, "Available results: " + suggestions);
43
44        // Split non-empty results into default sources and other, positioned at first suggestion
45        LinkedList<CorpusResult> defaultResults = new LinkedList<CorpusResult>();
46        LinkedList<CorpusResult> otherResults = new LinkedList<CorpusResult>();
47        for (CorpusResult result : suggestions) {
48            if (result.getCount() > 0) {
49                result.moveTo(0);
50                Corpus corpus = result.getCorpus();
51                if (corpus == null || corpus.isCorpusDefaultEnabled()) {
52                    defaultResults.add(result);
53                } else {
54                    otherResults.add(result);
55                }
56            }
57        }
58
59        // Share the top slots equally among each of the default corpora
60        if (maxPromoted > 0 && !defaultResults.isEmpty()) {
61            int slotsToFill = Math.min(getSlotsAboveKeyboard() - promoted.getCount(), maxPromoted);
62            if (slotsToFill > 0) {
63                int stripeSize = Math.max(1, slotsToFill / defaultResults.size());
64                maxPromoted -= roundRobin(defaultResults, slotsToFill, stripeSize, promoted);
65            }
66        }
67
68        // Then try to fill with the remaining promoted results
69        if (maxPromoted > 0 && !defaultResults.isEmpty()) {
70            int stripeSize = Math.max(1, maxPromoted / defaultResults.size());
71            maxPromoted -= roundRobin(defaultResults, maxPromoted, stripeSize, promoted);
72            // We may still have a few slots left
73            maxPromoted -= roundRobin(defaultResults, maxPromoted, maxPromoted, promoted);
74        }
75
76        // Then try to fill with the rest
77        if (maxPromoted > 0 && !otherResults.isEmpty()) {
78            int stripeSize = Math.max(1, maxPromoted / otherResults.size());
79            maxPromoted -= roundRobin(otherResults, maxPromoted, stripeSize, promoted);
80            // We may still have a few slots left
81            maxPromoted -= roundRobin(otherResults, maxPromoted, maxPromoted, promoted);
82        }
83
84        if (DBG) Log.d(TAG, "Returning " + promoted.toString());
85    }
86
87    private int getSlotsAboveKeyboard() {
88        return mConfig.getNumSuggestionsAboveKeyboard();
89    }
90
91    /**
92     * Promotes "stripes" of suggestions from each corpus.
93     *
94     * @param results     the list of CorpusResults from which to promote.
95     *                    Exhausted CorpusResults are removed from the list.
96     * @param maxPromoted maximum number of suggestions to promote.
97     * @param stripeSize  number of suggestions to take from each corpus.
98     * @param promoted    the list to which promoted suggestions are added.
99     * @return the number of suggestions actually promoted.
100     */
101    private int roundRobin(LinkedList<CorpusResult> results, int maxPromoted, int stripeSize,
102            ListSuggestionCursor promoted) {
103        int count = 0;
104        if (maxPromoted > 0 && !results.isEmpty()) {
105            for (Iterator<CorpusResult> iter = results.iterator();
106                 count < maxPromoted && iter.hasNext();) {
107                CorpusResult result = iter.next();
108                count += promote(result, stripeSize, promoted);
109                if (result.getPosition() == result.getCount()) {
110                    iter.remove();
111                }
112            }
113        }
114        return count;
115    }
116
117    /**
118     * Copies suggestions from a SuggestionCursor to the list of promoted suggestions.
119     *
120     * @param cursor from which to copy the suggestions
121     * @param count maximum number of suggestions to copy
122     * @param promoted the list to which to add the suggestions
123     * @return the number of suggestions actually copied.
124     */
125    private int promote(SuggestionCursor cursor, int count, ListSuggestionCursor promoted) {
126        if (count < 1 || cursor.getPosition() >= cursor.getCount()) {
127            return 0;
128        }
129        int i = 0;
130        do {
131            promoted.add(new SuggestionPosition(cursor));
132            i++;
133        } while (cursor.moveToNext() && i < count);
134        return i;
135    }
136}
137