ClusteredSuggestionsAdapter.java revision f32ccb142919068f22ac0a0c19459c9153f70eca
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 */
16package com.android.quicksearchbox.ui;
17
18import com.android.quicksearchbox.Corpora;
19import com.android.quicksearchbox.CorpusResult;
20import com.android.quicksearchbox.ListSuggestionCursor;
21import com.android.quicksearchbox.R;
22import com.android.quicksearchbox.Suggestion;
23import com.android.quicksearchbox.SuggestionCursor;
24import com.android.quicksearchbox.SuggestionPosition;
25import com.android.quicksearchbox.SuggestionUtils;
26import com.android.quicksearchbox.Suggestions;
27
28import android.content.Context;
29import android.view.LayoutInflater;
30import android.view.View;
31import android.view.ViewGroup;
32import android.widget.BaseExpandableListAdapter;
33import android.widget.ExpandableListAdapter;
34
35import java.util.ArrayList;
36import java.util.HashSet;
37
38/**
39 * Adapter for suggestions list where suggestions are clustered by corpus.
40 */
41public class ClusteredSuggestionsAdapter extends SuggestionsAdapterBase<ExpandableListAdapter> {
42
43    private final static int GROUP_SHIFT = 32;
44    private final static long CHILD_MASK = 0xffffffff;
45
46    private final Adapter mAdapter;
47    private final Context mContext;
48    private final LayoutInflater mInflater;
49
50    public ClusteredSuggestionsAdapter(SuggestionViewFactory fallbackFactory,
51            Corpora corpora, Context context) {
52        super(fallbackFactory, corpora);
53        mAdapter = new Adapter();
54        mContext = context;
55        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
56    }
57
58    @Override
59    public boolean isEmpty() {
60        return mAdapter.getGroupCount() == 0;
61    }
62
63    @Override
64    public SuggestionPosition getSuggestion(long suggestionId) {
65        return mAdapter.getChildById(suggestionId);
66    }
67
68    @Override
69    public ExpandableListAdapter getListAdapter() {
70        return mAdapter;
71    }
72
73    @Override
74    protected void notifyDataSetChanged() {
75        mAdapter.buildCorpusGroups();
76        mAdapter.notifyDataSetChanged();
77    }
78
79    @Override
80    protected void notifyDataSetInvalidated() {
81        mAdapter.buildCorpusGroups();
82        mAdapter.notifyDataSetInvalidated();
83    }
84
85    private class Adapter extends BaseExpandableListAdapter {
86
87        private ArrayList<SuggestionCursor> mCorpusGroups;
88        /**
89         * The number of corpora that have promoted suggestions displayed in the first cluster.
90         */
91        private int mPromotedCorpora;
92
93        public void buildCorpusGroups() {
94            Suggestions suggestions = getSuggestions();
95            SuggestionCursor promoted = getCurrentPromotedSuggestions();
96            HashSet<String> promotedSuggestions = new HashSet<String>();
97            if (promoted != null && promoted.getCount() > 0) {
98                promoted.moveTo(0);
99                do {
100                    promotedSuggestions.add(SuggestionUtils.getSuggestionKey(promoted));
101                } while (promoted.moveToNext());
102            }
103            if (suggestions == null) {
104                mCorpusGroups = null;
105                mPromotedCorpora = 0;
106            } else {
107                HashSet<String> promotedCorpora = new HashSet<String>();
108                if (mCorpusGroups == null) {
109                    mCorpusGroups = new ArrayList<SuggestionCursor>();
110                } else {
111                    mCorpusGroups.clear();
112                }
113                // TODO order corpora by usage
114                for (CorpusResult result : suggestions.getCorpusResults()) {
115                    ListSuggestionCursor corpusSuggestions = new ListSuggestionCursor(
116                            result.getUserQuery());
117                    for (int i = 0; i < result.getCount(); ++i) {
118                        result.moveTo(i);
119                        if (!result.isWebSearchSuggestion()) {
120                            if (promotedSuggestions.contains(
121                                    SuggestionUtils.getSuggestionKey(result))) {
122                                promotedCorpora.add(result.getCorpus().getName());
123                            } else {
124                                corpusSuggestions.add(new SuggestionPosition(result, i));
125                            }
126                        }
127                    }
128                    if (corpusSuggestions.getCount() > 0) {
129                        mCorpusGroups.add(corpusSuggestions);
130                    }
131                }
132                mPromotedCorpora = promotedCorpora.size();
133            }
134        }
135
136        @Override
137        public long getCombinedChildId(long groupId, long childId) {
138            return (groupId << GROUP_SHIFT) | (childId & CHILD_MASK);
139        }
140
141        @Override
142        public long getCombinedGroupId(long groupId) {
143            return groupId << GROUP_SHIFT;
144        }
145
146        public int getChildPosition(long childId) {
147            return (int) (childId & CHILD_MASK);
148        }
149
150        public int getGroupPosition(long childId) {
151            return (int) ((childId >> GROUP_SHIFT) & CHILD_MASK);
152        }
153
154        @Override
155        public Suggestion getChild(int groupPosition, int childPosition) {
156            SuggestionCursor c = getGroup(groupPosition);
157            c.moveTo(childPosition);
158                return new SuggestionPosition(c, childPosition);
159        }
160
161        public SuggestionPosition getChildById(long childId) {
162            return new SuggestionPosition(getGroup(getGroupPosition(childId)),
163                    getChildPosition(getChildPosition(childId)));
164        }
165
166        @Override
167        public long getChildId(int groupPosition, int childPosition) {
168            return childPosition;
169        }
170
171        @Override
172        public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
173                View convertView, ViewGroup parent) {
174            SuggestionCursor cursor = getGroup(groupPosition);
175            return getView(cursor, childPosition, getCombinedChildId(groupPosition, childPosition),
176                    convertView, parent);
177        }
178
179        @Override
180        public int getChildrenCount(int groupPosition) {
181            return getGroup(groupPosition).getCount();
182        }
183
184        @Override
185        public SuggestionCursor getGroup(int groupPosition) {
186            if (groupPosition < promotedGroupCount()) {
187                return getCurrentPromotedSuggestions();
188            } else {
189                return mCorpusGroups.get(groupPosition - promotedGroupCount());
190            }
191        }
192
193        private int promotedCount() {
194            SuggestionCursor promoted = getCurrentPromotedSuggestions();
195            return (promoted == null ? 0 : promoted.getCount());
196        }
197
198        private int promotedGroupCount() {
199            return (promotedCount() == 0 || mPromotedCorpora == 1) ? 0 : 1;
200        }
201
202        private int corpusGroupCount() {
203            return mCorpusGroups == null ? 0 : mCorpusGroups.size();
204        }
205
206        @Override
207        public int getGroupCount() {
208            return promotedGroupCount() + corpusGroupCount();
209        }
210
211        @Override
212        public long getGroupId(int groupPosition) {
213            return groupPosition;
214        }
215
216        @Override
217        public View getGroupView(
218                int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
219            if (convertView == null) {
220                convertView = mInflater.inflate(R.layout.suggestion_group, parent, false);
221            }
222            if (groupPosition == 0) {
223                // don't show the group separator for the first group, to avoid seeing an empty
224                // gap at the top of the list.
225                convertView.getLayoutParams().height = 0;
226            } else {
227                convertView.getLayoutParams().height = mContext.getResources().
228                        getDimensionPixelSize(R.dimen.suggestion_group_spacing);
229            }
230            // since we've fiddled with the layout params:
231            convertView.requestLayout();
232            return convertView;
233        }
234
235        @Override
236        public boolean hasStableIds() {
237            return false;
238        }
239
240        @Override
241        public boolean isChildSelectable(int groupPosition, int childPosition) {
242            return true;
243        }
244
245        @Override
246        public int getChildType(int groupPosition, int childPosition) {
247            return getSuggestionViewType(getGroup(groupPosition), childPosition);
248        }
249
250        @Override
251        public int getChildTypeCount() {
252            return getSuggestionViewTypeCount();
253        }
254    }
255
256}
257