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