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