Home | History | Annotate | Download | only in ui
      1 /*
      2  * Copyright (C) 2010 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 package com.android.quicksearchbox.ui;
     17 
     18 import com.android.quicksearchbox.Promoter;
     19 import com.android.quicksearchbox.Suggestion;
     20 import com.android.quicksearchbox.SuggestionCursor;
     21 import com.android.quicksearchbox.SuggestionPosition;
     22 import com.android.quicksearchbox.Suggestions;
     23 
     24 import android.database.DataSetObserver;
     25 import android.util.Log;
     26 import android.view.View;
     27 import android.view.View.OnFocusChangeListener;
     28 import android.view.ViewGroup;
     29 
     30 import java.util.HashMap;
     31 
     32 /**
     33  * Base class for suggestions adapters. The templated class A is the list adapter class.
     34  */
     35 public abstract class SuggestionsAdapterBase<A> implements SuggestionsAdapter<A> {
     36 
     37     private static final boolean DBG = false;
     38     private static final String TAG = "QSB.SuggestionsAdapter";
     39 
     40     private DataSetObserver mDataSetObserver;
     41 
     42     private Promoter mPromoter;
     43 
     44     private int mMaxPromoted;
     45 
     46     private SuggestionCursor mPromotedSuggestions;
     47     private final HashMap<String, Integer> mViewTypeMap;
     48     private final SuggestionViewFactory mViewFactory;
     49 
     50     private Suggestions mSuggestions;
     51 
     52     private SuggestionClickListener mSuggestionClickListener;
     53     private OnFocusChangeListener mOnFocusChangeListener;
     54 
     55     private boolean mClosed = false;
     56 
     57     protected SuggestionsAdapterBase(SuggestionViewFactory viewFactory) {
     58         mViewFactory = viewFactory;
     59         mViewTypeMap = new HashMap<String, Integer>();
     60         for (String viewType : mViewFactory.getSuggestionViewTypes()) {
     61             if (!mViewTypeMap.containsKey(viewType)) {
     62                 mViewTypeMap.put(viewType, mViewTypeMap.size());
     63             }
     64         }
     65     }
     66 
     67     public abstract boolean isEmpty();
     68 
     69     /**
     70      * Indicates if this adapter will publish suggestions other than those in the promoted list.
     71      */
     72     public abstract boolean willPublishNonPromotedSuggestions();
     73 
     74     public void setMaxPromoted(int maxPromoted) {
     75         if (DBG) Log.d(TAG, "setMaxPromoted " + maxPromoted);
     76         mMaxPromoted = maxPromoted;
     77         onSuggestionsChanged();
     78     }
     79 
     80     public boolean isClosed() {
     81         return mClosed;
     82     }
     83 
     84     public void close() {
     85         setSuggestions(null);
     86         mClosed = true;
     87     }
     88 
     89     public void setPromoter(Promoter promoter) {
     90         mPromoter = promoter;
     91         onSuggestionsChanged();
     92     }
     93 
     94     public void setSuggestionClickListener(SuggestionClickListener listener) {
     95         mSuggestionClickListener = listener;
     96     }
     97 
     98     public void setOnFocusChangeListener(OnFocusChangeListener l) {
     99         mOnFocusChangeListener = l;
    100     }
    101 
    102     public void setSuggestions(Suggestions suggestions) {
    103         if (mSuggestions == suggestions) {
    104             return;
    105         }
    106         if (mClosed) {
    107             if (suggestions != null) {
    108                 suggestions.release();
    109             }
    110             return;
    111         }
    112         if (mDataSetObserver == null) {
    113             mDataSetObserver = new MySuggestionsObserver();
    114         }
    115         // TODO: delay the change if there are no suggestions for the currently visible tab.
    116         if (mSuggestions != null) {
    117             mSuggestions.unregisterDataSetObserver(mDataSetObserver);
    118             mSuggestions.release();
    119         }
    120         mSuggestions = suggestions;
    121         if (mSuggestions != null) {
    122             mSuggestions.registerDataSetObserver(mDataSetObserver);
    123         }
    124         onSuggestionsChanged();
    125     }
    126 
    127     public Suggestions getSuggestions() {
    128         return mSuggestions;
    129     }
    130 
    131     public abstract SuggestionPosition getSuggestion(long suggestionId);
    132 
    133     protected int getPromotedCount() {
    134         return mPromotedSuggestions == null ? 0 : mPromotedSuggestions.getCount();
    135     }
    136 
    137     protected SuggestionPosition getPromotedSuggestion(int position) {
    138         if (mPromotedSuggestions == null) return null;
    139         return new SuggestionPosition(mPromotedSuggestions, position);
    140     }
    141 
    142     protected int getViewTypeCount() {
    143         return mViewTypeMap.size();
    144     }
    145 
    146     private String suggestionViewType(Suggestion suggestion) {
    147         String viewType = mViewFactory.getViewType(suggestion);
    148         if (!mViewTypeMap.containsKey(viewType)) {
    149             throw new IllegalStateException("Unknown viewType " + viewType);
    150         }
    151         return viewType;
    152     }
    153 
    154     protected int getSuggestionViewType(SuggestionCursor cursor, int position) {
    155         if (cursor == null) {
    156             return 0;
    157         }
    158         cursor.moveTo(position);
    159         return mViewTypeMap.get(suggestionViewType(cursor));
    160     }
    161 
    162     protected int getSuggestionViewTypeCount() {
    163         return mViewTypeMap.size();
    164     }
    165 
    166     protected View getView(SuggestionCursor suggestions, int position, long suggestionId,
    167             View convertView, ViewGroup parent) {
    168         suggestions.moveTo(position);
    169         View v = mViewFactory.getView(suggestions, suggestions.getUserQuery(), convertView, parent);
    170         if (v instanceof SuggestionView) {
    171             ((SuggestionView) v).bindAdapter(this, suggestionId);
    172         } else {
    173             SuggestionViewClickListener l = new SuggestionViewClickListener(suggestionId);
    174             v.setOnClickListener(l);
    175         }
    176 
    177         if (mOnFocusChangeListener != null) {
    178             v.setOnFocusChangeListener(mOnFocusChangeListener);
    179         }
    180         return v;
    181     }
    182 
    183     protected void onSuggestionsChanged() {
    184         if (DBG) Log.d(TAG, "onSuggestionsChanged(" + mSuggestions + ")");
    185         SuggestionCursor cursor = getPromoted(mSuggestions);
    186         changePromoted(cursor);
    187     }
    188 
    189     /**
    190      * Gets the cursor containing the currently shown suggestions. The caller should not hold
    191      * on to or modify the returned cursor.
    192      */
    193     public SuggestionCursor getCurrentPromotedSuggestions() {
    194         return mPromotedSuggestions;
    195     }
    196 
    197     /**
    198      * Gets the cursor for the given source.
    199      */
    200     protected SuggestionCursor getPromoted(Suggestions suggestions) {
    201         if (suggestions == null) return null;
    202         return suggestions.getPromoted(mPromoter, mMaxPromoted);
    203     }
    204 
    205     /**
    206      * Replace the cursor.
    207      *
    208      * This does not close the old cursor. Instead, all the cursors are closed in
    209      * {@link #setSuggestions(Suggestions)}.
    210      */
    211     private void changePromoted(SuggestionCursor newCursor) {
    212         if (DBG) {
    213             Log.d(TAG, "changeCursor(" + newCursor + ") count=" +
    214                     (newCursor == null ? 0 : newCursor.getCount()));
    215         }
    216         if (newCursor == mPromotedSuggestions) {
    217             if (newCursor != null) {
    218                 // Shortcuts may have changed without the cursor changing.
    219                 notifyDataSetChanged();
    220             }
    221             return;
    222         }
    223         mPromotedSuggestions = newCursor;
    224         if (mPromotedSuggestions != null) {
    225             notifyDataSetChanged();
    226         } else {
    227             notifyDataSetInvalidated();
    228         }
    229     }
    230 
    231     public void onSuggestionClicked(long suggestionId) {
    232         if (mClosed) {
    233             Log.w(TAG, "onSuggestionClicked after close");
    234         } else if (mSuggestionClickListener != null) {
    235             mSuggestionClickListener.onSuggestionClicked(this, suggestionId);
    236         }
    237     }
    238 
    239     public void onSuggestionQuickContactClicked(long suggestionId) {
    240         if (mClosed) {
    241             Log.w(TAG, "onSuggestionQuickContactClicked after close");
    242         } else if (mSuggestionClickListener != null) {
    243             mSuggestionClickListener.onSuggestionQuickContactClicked(this, suggestionId);
    244         }
    245     }
    246 
    247     public void onSuggestionRemoveFromHistoryClicked(long suggestionId) {
    248         if (mClosed) {
    249             Log.w(TAG, "onSuggestionRemoveFromHistoryClicked after close");
    250         } else if (mSuggestionClickListener != null) {
    251             mSuggestionClickListener.onSuggestionRemoveFromHistoryClicked(this, suggestionId);
    252         }
    253     }
    254 
    255     public void onSuggestionQueryRefineClicked(long suggestionId) {
    256         if (mClosed) {
    257             Log.w(TAG, "onSuggestionQueryRefineClicked after close");
    258         } else if (mSuggestionClickListener != null) {
    259             mSuggestionClickListener.onSuggestionQueryRefineClicked(this, suggestionId);
    260         }
    261     }
    262 
    263     public abstract A getListAdapter();
    264 
    265     protected abstract void notifyDataSetInvalidated();
    266 
    267     protected abstract void notifyDataSetChanged();
    268 
    269     private class MySuggestionsObserver extends DataSetObserver {
    270         @Override
    271         public void onChanged() {
    272             onSuggestionsChanged();
    273         }
    274     }
    275 
    276     private class SuggestionViewClickListener implements View.OnClickListener {
    277         private final long mSuggestionId;
    278         public SuggestionViewClickListener(long suggestionId) {
    279             mSuggestionId = suggestionId;
    280         }
    281         public void onClick(View v) {
    282             onSuggestionClicked(mSuggestionId);
    283         }
    284     }
    285 
    286 }
    287