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