SearchActivityViewTwoPane.java revision 639c9f3e4e7900e3d4b18473e02909eece924f17
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
17package com.android.quicksearchbox.ui;
18
19import com.android.quicksearchbox.Corpus;
20import com.android.quicksearchbox.Promoter;
21import com.android.quicksearchbox.R;
22import com.android.quicksearchbox.Suggestions;
23
24import android.animation.Animator;
25import android.animation.ObjectAnimator;
26import android.animation.ValueAnimator;
27import android.animation.ValueAnimator.AnimatorUpdateListener;
28import android.content.ComponentName;
29import android.content.Context;
30import android.content.pm.PackageManager;
31import android.content.pm.PackageManager.NameNotFoundException;
32import android.content.res.Resources;
33import android.database.DataSetObserver;
34import android.graphics.drawable.Drawable;
35import android.os.Bundle;
36import android.util.AttributeSet;
37import android.util.Log;
38import android.view.Menu;
39import android.view.View;
40import android.view.ViewGroup;
41import android.widget.ExpandableListAdapter;
42import android.widget.ImageView;
43import android.widget.PopupMenu;
44
45/**
46 * Two-pane variant for the search activity view.
47 */
48public class SearchActivityViewTwoPane extends SearchActivityView {
49
50    private static final int ENTRY_ANIMATION_START_DELAY = 150; // in millis
51    private static final int ENTRY_ANIMATION_DURATION = 150; // in millis
52    private static final float ANIMATION_STARTING_WIDTH_FACTOR = 0.5f;
53    private static final String TOOLBAR_ICON_METADATA_NAME = "com.android.launcher.toolbar_icon";
54
55    private ImageView mMenuButton;
56
57    // View that shows the results other than the query completions
58    private ClusteredSuggestionsView mResultsView;
59    private SuggestionsAdapter<ExpandableListAdapter> mResultsAdapter;
60    private View mResultsHeader;
61    private View mSearchPlate;
62    private boolean mJustCreated;
63
64    public SearchActivityViewTwoPane(Context context) {
65        super(context);
66    }
67
68    public SearchActivityViewTwoPane(Context context, AttributeSet attrs) {
69        super(context, attrs);
70    }
71
72    public SearchActivityViewTwoPane(Context context, AttributeSet attrs, int defStyle) {
73        super(context, attrs, defStyle);
74    }
75
76    @Override
77    protected void onFinishInflate() {
78        super.onFinishInflate();
79
80        mMenuButton = (ImageView) findViewById(R.id.menu_button);
81        mMenuButton.setOnClickListener(new View.OnClickListener() {
82            public void onClick(View v) {
83                showPopupMenu();
84            }
85        });
86
87        mResultsView = (ClusteredSuggestionsView) findViewById(R.id.shortcuts);
88        mResultsAdapter = createClusteredSuggestionsAdapter();
89        mResultsAdapter.getListAdapter().registerDataSetObserver(new DataSetObserver(){
90            @Override
91            public void onChanged() {
92                mResultsView.expandAll();
93            }
94        });
95        mResultsView.setOnKeyListener(new SuggestionsViewKeyListener());
96        mResultsView.setFocusable(true);
97        mResultsHeader = findViewById(R.id.shortcut_title);
98        mSearchPlate = findViewById(R.id.left_pane);
99        mJustCreated = true;
100    }
101
102    private void showPopupMenu() {
103        PopupMenu popup = new PopupMenu(getContext(), mMenuButton);
104        Menu menu = popup.getMenu();
105        getActivity().createMenuItems(menu, false);
106        popup.show();
107    }
108
109    protected SuggestionsAdapter<ExpandableListAdapter> createClusteredSuggestionsAdapter() {
110        return new DelayingSuggestionsAdapter<ExpandableListAdapter>(
111                new ClusteredSuggestionsAdapter(
112                        getQsbApplication().getSuggestionViewFactory(),
113                        getContext()));
114    }
115
116    @Override
117    public void onResume() {
118        if (mJustCreated) {
119            setupEntryAnimations();
120            mJustCreated = false;
121        }
122    }
123
124    @Override
125    public void onPause() {
126        super.onPause();
127        getActivity().overridePendingTransition(R.anim.fade_in_fast, R.anim.fade_out_fast);
128    }
129
130    private void setupEntryAnimations() {
131        // TODO: Use the left/top of the source bounds to start the animation from
132        final int endingWidth = getResources().getDimensionPixelSize(R.dimen.suggestions_width);
133        final int startingWidth = (int) (endingWidth * ANIMATION_STARTING_WIDTH_FACTOR);
134
135        ViewGroup.LayoutParams params = mSearchPlate.getLayoutParams();
136        params.width = startingWidth;
137        mSearchPlate.setLayoutParams(params);
138
139        Animator animator = ObjectAnimator.ofInt(mSearchPlate, "alpha", 0, 255);
140        animator.setDuration(ENTRY_ANIMATION_DURATION);
141        ((ValueAnimator)animator).addUpdateListener(new AnimatorUpdateListener() {
142
143            public void onAnimationUpdate(ValueAnimator animator) {
144                ViewGroup.LayoutParams params = mSearchPlate.getLayoutParams();
145                params.width = startingWidth
146                        + (int) ((Integer) animator.getAnimatedValue() / 255f
147                                * (endingWidth - startingWidth));
148                mSearchPlate.setLayoutParams(params);
149            }
150        });
151        animator.setStartDelay(ENTRY_ANIMATION_START_DELAY);
152        animator.start();
153
154    }
155
156    @Override
157    public void onStop() {
158    }
159
160    @Override
161    public void start() {
162        super.start();
163        mResultsAdapter.getListAdapter().registerDataSetObserver(new ResultsObserver());
164        mResultsView.setSuggestionsAdapter(mResultsAdapter);
165    }
166
167    @Override
168    public void destroy() {
169        mResultsView.setSuggestionsAdapter(null);
170
171        super.destroy();
172    }
173
174    @Override
175    protected Drawable getVoiceSearchIcon() {
176        ComponentName voiceSearch = getVoiceSearch().getComponent();
177        if (voiceSearch != null) {
178            // this code copied from Launcher to get the same icon that's displayed on home screen
179            try {
180                PackageManager packageManager = getContext().getPackageManager();
181                // Look for the toolbar icon specified in the activity meta-data
182                Bundle metaData = packageManager.getActivityInfo(
183                        voiceSearch, PackageManager.GET_META_DATA).metaData;
184                if (metaData != null) {
185                    int iconResId = metaData.getInt(TOOLBAR_ICON_METADATA_NAME);
186                    if (iconResId != 0) {
187                        Resources res = packageManager.getResourcesForActivity(voiceSearch);
188                        if (DBG) Log.d(TAG, "Got toolbar icon from Voice Search");
189                        return res.getDrawable(iconResId);
190                    }
191                }
192            } catch (NameNotFoundException e) {
193                // Do nothing
194            }
195        }
196        if (DBG) Log.d(TAG, "Failed to get toolbar icon from Voice Search; using default.");
197        return super.getVoiceSearchIcon();
198    }
199
200    @Override
201    public void considerHidingInputMethod() {
202        // Don't hide keyboard when interacting with suggestions list
203    }
204
205    @Override
206    public void hideSuggestions() {
207        // Never hiding suggestions view in two-pane UI
208    }
209
210    @Override
211    public void showSuggestions() {
212        // Never hiding suggestions view in two-pane UI
213    }
214
215    @Override
216    public void showCorpusSelectionDialog() {
217        // not used
218    }
219
220    @Override
221    public void clearSuggestions() {
222        super.clearSuggestions();
223        mResultsAdapter.setSuggestions(null);
224    }
225
226    @Override
227    public void setMaxPromotedResults(int maxPromoted) {
228        mResultsView.setLimitSuggestionsToViewHeight(false);
229        mResultsAdapter.setMaxPromoted(maxPromoted);
230    }
231
232    @Override
233    public void limitResultsToViewHeight() {
234        mResultsView.setLimitSuggestionsToViewHeight(true);
235    }
236
237    @Override
238    public void setSuggestionClickListener(SuggestionClickListener listener) {
239        super.setSuggestionClickListener(listener);
240        mResultsAdapter.setSuggestionClickListener(listener);
241    }
242
243    @Override
244    public void setSuggestions(Suggestions suggestions) {
245        super.setSuggestions(suggestions);
246        suggestions.acquire();
247        mResultsAdapter.setSuggestions(suggestions);
248    }
249
250    @Override
251    protected void setCorpus(Corpus corpus) {
252        super.setCorpus(corpus);
253        mResultsAdapter.setPromoter(createResultsPromoter());
254    }
255
256    @Override
257    protected Promoter createSuggestionsPromoter() {
258        return getQsbApplication().createWebPromoter();
259    }
260
261    protected Promoter createResultsPromoter() {
262        Corpus corpus = getCorpus();
263        if (corpus == null) {
264            return getQsbApplication().createResultsPromoter();
265        } else {
266            return getQsbApplication().createSingleCorpusResultsPromoter(corpus);
267        }
268    }
269
270    protected void onResultsChanged() {
271        checkHideResultsHeader();
272    }
273
274    @Override
275    protected void updateQueryTextView(boolean queryEmpty) {
276        super.updateQueryTextView(queryEmpty);
277        if (mSearchCloseButton == null) return;
278
279        if (queryEmpty) {
280            mSearchCloseButton.setImageResource(R.drawable.ic_clear_disabled);
281        } else {
282            mSearchCloseButton.setImageResource(R.drawable.ic_clear);
283        }
284    }
285
286    private void checkHideResultsHeader() {
287        if (mResultsHeader != null) {
288            if (!mResultsAdapter.isEmpty()) {
289                if (DBG) Log.d(TAG, "Results non-empty");
290                mResultsHeader.setVisibility(VISIBLE);
291            } else {
292                if (DBG) Log.d(TAG, "Results empty");
293                mResultsHeader.setVisibility(INVISIBLE);
294            }
295        }
296    }
297
298    @Override
299    public Corpus getSearchCorpus() {
300        return getWebCorpus();
301    }
302
303    protected class ResultsObserver extends DataSetObserver {
304        @Override
305        public void onChanged() {
306            onResultsChanged();
307        }
308    }
309
310}
311