Suggestions.java revision ced9f76b761536341d739e9a243c98a4bf90638c
1/* 2 * Copyright (C) 2009 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.database.DataSetObservable; 22import android.database.DataSetObserver; 23import android.util.Log; 24 25import java.util.ArrayList; 26import java.util.Collections; 27import java.util.HashSet; 28import java.util.List; 29import java.util.Set; 30 31/** 32 * Contains all {@link SuggestionCursor} objects that have been reported. 33 */ 34public class Suggestions { 35 36 private static final boolean DBG = false; 37 private static final String TAG = "QSB.Suggestions"; 38 39 private final int mMaxPromoted; 40 41 private final String mQuery; 42 43 /** The sources that are expected to report. */ 44 private List<Corpus> mExpectedCorpora; 45 46 /** 47 * The observers that want notifications of changes to the published suggestions. 48 * This object may be accessed on any thread. 49 */ 50 private final DataSetObservable mDataSetObservable = new DataSetObservable(); 51 52 /** 53 * All {@link SuggestionCursor} objects that have been published so far, 54 * in the order that they were published. 55 * This object may only be accessed on the UI thread. 56 * */ 57 private ArrayList<CorpusResult> mCorpusResults; 58 59 private SuggestionCursor mShortcuts; 60 61 private final MyShortcutsObserver mShortcutsObserver = new MyShortcutsObserver(); 62 63 /** True if {@link Suggestions#close} has been called. */ 64 private boolean mClosed = false; 65 66 private final Promoter mPromoter; 67 68 private ListSuggestionCursor mPromoted; 69 70 /** 71 * Creates a new empty Suggestions. 72 * 73 * @param expectedCorpora The sources that are expected to report. 74 */ 75 public Suggestions(Promoter promoter, int maxPromoted, 76 String query, List<Corpus> expectedCorpora) { 77 mPromoter = promoter; 78 mMaxPromoted = maxPromoted; 79 mQuery = query; 80 mExpectedCorpora = expectedCorpora; 81 mCorpusResults = new ArrayList<CorpusResult>(mExpectedCorpora.size()); 82 mPromoted = null; // will be set by updatePromoted() 83 } 84 85 @VisibleForTesting 86 public String getQuery() { 87 return mQuery; 88 } 89 90 public List<Corpus> getExpectedCorpora() { 91 return mExpectedCorpora; 92 } 93 94 /** 95 * Gets the number of corpora that are expected to report. 96 */ 97 @VisibleForTesting 98 int getExpectedResultCount() { 99 return mExpectedCorpora.size(); 100 } 101 102 /** 103 * Registers an observer that will be notified when the reported results or 104 * the done status changes. 105 */ 106 public void registerDataSetObserver(DataSetObserver observer) { 107 if (mClosed) { 108 throw new IllegalStateException("registerDataSetObserver() when closed"); 109 } 110 mDataSetObservable.registerObserver(observer); 111 } 112 113 /** 114 * Unregisters an observer. 115 */ 116 public void unregisterDataSetObserver(DataSetObserver observer) { 117 mDataSetObservable.unregisterObserver(observer); 118 } 119 120 public SuggestionCursor getPromoted() { 121 if (mPromoted == null) { 122 updatePromoted(); 123 } 124 return mPromoted; 125 } 126 127 /** 128 * Gets the set of corpora that have reported results to this suggestions set. 129 * 130 * @return A collection of corpora. 131 */ 132 public Set<Corpus> getIncludedCorpora() { 133 HashSet<Corpus> corpora = new HashSet<Corpus>(); 134 for (CorpusResult result : mCorpusResults) { 135 corpora.add(result.getCorpus()); 136 } 137 return corpora; 138 } 139 140 /** 141 * Calls {@link DataSetObserver#onChanged()} on all observers. 142 */ 143 private void notifyDataSetChanged() { 144 if (DBG) Log.d(TAG, "notifyDataSetChanged()"); 145 mDataSetObservable.notifyChanged(); 146 } 147 148 /** 149 * Closes all the source results and unregisters all observers. 150 */ 151 public void close() { 152 if (DBG) Log.d(TAG, "close()"); 153 if (mClosed) { 154 throw new IllegalStateException("Double close()"); 155 } 156 mDataSetObservable.unregisterAll(); 157 mClosed = true; 158 if (mShortcuts != null) { 159 mShortcuts.close(); 160 mShortcuts = null; 161 } 162 for (CorpusResult result : mCorpusResults) { 163 result.close(); 164 } 165 mCorpusResults.clear(); 166 } 167 168 public boolean isClosed() { 169 return mClosed; 170 } 171 172 @Override 173 protected void finalize() { 174 if (!mClosed) { 175 Log.e(TAG, "LEAK! Finalized without being closed: Suggestions[" + mQuery + "]"); 176 } 177 } 178 179 /** 180 * Checks whether all sources have reported. 181 * Must be called on the UI thread, or before this object is seen by the UI thread. 182 */ 183 public boolean isDone() { 184 // TODO: Handle early completion because we have all the results we want. 185 return mCorpusResults.size() >= mExpectedCorpora.size(); 186 } 187 188 /** 189 * Sets the shortcut suggestions. 190 * Must be called on the UI thread, or before this object is seen by the UI thread. 191 * 192 * @param shortcuts The shortcuts. 193 */ 194 public void setShortcuts(SuggestionCursor shortcuts) { 195 if (DBG) Log.d(TAG, "setShortcuts(" + shortcuts + ")"); 196 mShortcuts = shortcuts; 197 if (shortcuts != null) { 198 mShortcuts.registerDataSetObserver(mShortcutsObserver); 199 } 200 } 201 202 /** 203 * Adds a list of corpus results. Must be called on the UI thread, or before this 204 * object is seen by the UI thread. 205 */ 206 public void addCorpusResults(List<CorpusResult> corpusResults) { 207 if (mClosed) { 208 for (CorpusResult corpusResult : corpusResults) { 209 corpusResult.close(); 210 } 211 return; 212 } 213 214 for (CorpusResult corpusResult : corpusResults) { 215 if (!mQuery.equals(corpusResult.getUserQuery())) { 216 throw new IllegalArgumentException("Got result for wrong query: " 217 + mQuery + " != " + corpusResult.getUserQuery()); 218 } 219 mCorpusResults.add(corpusResult); 220 } 221 mPromoted = null; 222 notifyDataSetChanged(); 223 } 224 225 private void updatePromoted() { 226 mPromoted = new ListSuggestionCursorNoDuplicates(mQuery); 227 if (mPromoter == null) { 228 return; 229 } 230 mPromoter.pickPromoted(mShortcuts, mCorpusResults, mMaxPromoted, mPromoted); 231 if (DBG) { 232 Log.d(TAG, "pickPromoted(" + mShortcuts + "," + mCorpusResults + "," 233 + mMaxPromoted + ") = " + mPromoted); 234 } 235 } 236 237 /** 238 * Gets the number of source results. 239 * Must be called on the UI thread, or before this object is seen by the UI thread. 240 */ 241 public int getResultCount() { 242 if (mClosed) { 243 throw new IllegalStateException("Called getSourceCount() when closed."); 244 } 245 return mCorpusResults == null ? 0 : mCorpusResults.size(); 246 } 247 248 public void filterByCorpus(Corpus singleCorpus) { 249 if ((mExpectedCorpora.size() == 1) && (mExpectedCorpora.get(0) == this)) { 250 return; 251 } 252 boolean haveCorpus = false; 253 for (Corpus corpus : mExpectedCorpora) { 254 if (corpus == singleCorpus) { 255 haveCorpus = true; 256 } 257 } 258 if (!haveCorpus) { 259 mExpectedCorpora = Collections.emptyList(); 260 mPromoted = null; 261 mCorpusResults.clear(); 262 notifyDataSetChanged(); 263 return; 264 } 265 mExpectedCorpora = Collections.singletonList(singleCorpus); 266 ArrayList<CorpusResult> filteredResults = new ArrayList<CorpusResult>(1); 267 for (CorpusResult result : mCorpusResults) { 268 if (result.getCorpus() == singleCorpus) { 269 filteredResults.add(result); 270 } 271 } 272 mCorpusResults = filteredResults; 273 mPromoted = null; 274 notifyDataSetChanged(); 275 } 276 277 @Override 278 public String toString() { 279 return "Suggestions{expectedCorpora=" + mExpectedCorpora 280 + ",mCorpusResults.size()=" + mCorpusResults.size() + "}"; 281 } 282 283 private class MyShortcutsObserver extends DataSetObserver { 284 @Override 285 public void onChanged() { 286 mPromoted = null; 287 notifyDataSetChanged(); 288 } 289 } 290 291} 292