Suggestions.java revision fde948e69f59589cf0d217ea414af7947de600bb
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 close(); 161 } 162 } 163 164 /** 165 * Checks whether all sources have reported. 166 * Must be called on the UI thread, or before this object is seen by the UI thread. 167 */ 168 public boolean isDone() { 169 // TODO: Handle early completion because we have all the results we want. 170 return mCorpusResults.size() >= mExpectedCorpusCount; 171 } 172 173 /** 174 * Sets the shortcut suggestions. 175 * Must be called on the UI thread, or before this object is seen by the UI thread. 176 * 177 * @param shortcuts The shortcuts. 178 */ 179 public void setShortcuts(SuggestionCursor shortcuts) { 180 if (DBG) Log.d(TAG, "setShortcuts(" + shortcuts + ")"); 181 mShortcuts = shortcuts; 182 if (shortcuts != null) { 183 mShortcuts.registerDataSetObserver(mShortcutsObserver); 184 } 185 } 186 187 /** 188 * Adds a corpus result. Must be called on the UI thread, or before this 189 * object is seen by the UI thread. 190 */ 191 public void addCorpusResult(CorpusResult corpusResult) { 192 if (mClosed) { 193 corpusResult.close(); 194 return; 195 } 196 if (!mQuery.equals(corpusResult.getUserQuery())) { 197 throw new IllegalArgumentException("Got result for wrong query: " 198 + mQuery + " != " + corpusResult.getUserQuery()); 199 } 200 mCorpusResults.add(corpusResult); 201 mResultsByCorpus.put(corpusResult.getCorpus(), corpusResult); 202 mPromoted = null; 203 notifyDataSetChanged(); 204 } 205 206 private void updatePromoted() { 207 mPromoted = new ListSuggestionCursorNoDuplicates(mQuery); 208 if (mPromoter == null) { 209 return; 210 } 211 mPromoter.pickPromoted(mShortcuts, mCorpusResults, mMaxPromoted, mPromoted); 212 } 213 214 /** 215 * Gets a given corpus result. 216 * Must be called on the UI thread, or before this object is seen by the UI thread. 217 * 218 * @param corpus corpus 219 * @return The source result for the given source. {@code null} if the source has not 220 * yet returned. 221 */ 222 public CorpusResult getCorpusResult(Corpus corpus) { 223 if (mClosed) { 224 throw new IllegalStateException("getCorpusResult(" + corpus + ") when closed."); 225 } 226 return mResultsByCorpus.get(corpus); 227 } 228 229 /** 230 * Gets the number of source results. 231 * Must be called on the UI thread, or before this object is seen by the UI thread. 232 */ 233 public int getSourceCount() { 234 if (mClosed) { 235 throw new IllegalStateException("Called getSourceCount() when closed."); 236 } 237 return mCorpusResults == null ? 0 : mCorpusResults.size(); 238 } 239 240 private class MyShortcutsObserver extends DataSetObserver { 241 @Override 242 public void onChanged() { 243 mPromoted = null; 244 notifyDataSetChanged(); 245 } 246 } 247 248} 249