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