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        View dismissBg = findViewById(R.id.dismiss_bg);
102        dismissBg.setOnClickListener(new View.OnClickListener() {
103            public void onClick(View v) {
104                if (isQueryEmpty() && mExitClickListener != null) {
105                    mExitClickListener.onClick(v);
106                }
107            }
108        });
109    }
110
111    private void showPopupMenu() {
112        PopupMenu popup = new PopupMenu(getContext(), mMenuButton);
113        Menu menu = popup.getMenu();
114        getActivity().createMenuItems(menu, false);
115        popup.show();
116    }
117
118    protected SuggestionsAdapter<ExpandableListAdapter> createClusteredSuggestionsAdapter() {
119        return new DelayingSuggestionsAdapter<ExpandableListAdapter>(
120                new ClusteredSuggestionsAdapter(
121                        getQsbApplication().getSuggestionViewFactory(),
122                        getContext()));
123    }
124
125    @Override
126    public void onResume() {
127        if (mJustCreated) {
128            setupEntryAnimations();
129            mJustCreated = false;
130        }
131    }
132
133    @Override
134    public void onPause() {
135        super.onPause();
136        getActivity().overridePendingTransition(R.anim.fade_in_fast, R.anim.fade_out_fast);
137    }
138
139    private void setupEntryAnimations() {
140        // TODO: Use the left/top of the source bounds to start the animation from
141        final int endingWidth = getResources().getDimensionPixelSize(R.dimen.suggestions_width);
142        final int startingWidth = (int) (endingWidth * ANIMATION_STARTING_WIDTH_FACTOR);
143
144        ViewGroup.LayoutParams params = mSearchPlate.getLayoutParams();
145        params.width = startingWidth;
146        mSearchPlate.setLayoutParams(params);
147
148        Animator animator = ObjectAnimator.ofInt(mSearchPlate, "alpha", 0, 255);
149        animator.setDuration(ENTRY_ANIMATION_DURATION);
150        ((ValueAnimator)animator).addUpdateListener(new AnimatorUpdateListener() {
151
152            public void onAnimationUpdate(ValueAnimator animator) {
153                ViewGroup.LayoutParams params = mSearchPlate.getLayoutParams();
154                params.width = startingWidth
155                        + (int) ((Integer) animator.getAnimatedValue() / 255f
156                                * (endingWidth - startingWidth));
157                mSearchPlate.setLayoutParams(params);
158            }
159        });
160        animator.setStartDelay(ENTRY_ANIMATION_START_DELAY);
161        animator.start();
162
163    }
164
165    @Override
166    public void onStop() {
167    }
168
169    @Override
170    public void start() {
171        super.start();
172        mResultsAdapter.getListAdapter().registerDataSetObserver(new ResultsObserver());
173        mResultsView.setSuggestionsAdapter(mResultsAdapter);
174    }
175
176    @Override
177    public void destroy() {
178        mResultsView.setSuggestionsAdapter(null);
179
180        super.destroy();
181    }
182
183    @Override
184    protected Drawable getVoiceSearchIcon() {
185        ComponentName voiceSearch = getVoiceSearch().getComponent();
186        if (voiceSearch != null) {
187            // this code copied from Launcher to get the same icon that's displayed on home screen
188            try {
189                PackageManager packageManager = getContext().getPackageManager();
190                // Look for the toolbar icon specified in the activity meta-data
191                Bundle metaData = packageManager.getActivityInfo(
192                        voiceSearch, PackageManager.GET_META_DATA).metaData;
193                if (metaData != null) {
194                    int iconResId = metaData.getInt(TOOLBAR_ICON_METADATA_NAME);
195                    if (iconResId != 0) {
196                        Resources res = packageManager.getResourcesForActivity(voiceSearch);
197                        if (DBG) Log.d(TAG, "Got toolbar icon from Voice Search");
198                        return res.getDrawable(iconResId);
199                    }
200                }
201            } catch (NameNotFoundException e) {
202                // Do nothing
203            }
204        }
205        if (DBG) Log.d(TAG, "Failed to get toolbar icon from Voice Search; using default.");
206        return super.getVoiceSearchIcon();
207    }
208
209    @Override
210    public void considerHidingInputMethod() {
211        // Don't hide keyboard when interacting with suggestions list
212    }
213
214    @Override
215    public void hideSuggestions() {
216        // Never hiding suggestions view in two-pane UI
217    }
218
219    @Override
220    public void showSuggestions() {
221        // Never hiding suggestions view in two-pane UI
222    }
223
224    @Override
225    public void showCorpusSelectionDialog() {
226        // not used
227    }
228
229    @Override
230    public void clearSuggestions() {
231        super.clearSuggestions();
232        mResultsAdapter.setSuggestions(null);
233    }
234
235    @Override
236    public void setMaxPromotedResults(int maxPromoted) {
237        mResultsView.setLimitSuggestionsToViewHeight(false);
238        mResultsAdapter.setMaxPromoted(maxPromoted);
239    }
240
241    @Override
242    public void limitResultsToViewHeight() {
243        mResultsView.setLimitSuggestionsToViewHeight(true);
244    }
245
246    @Override
247    public void setSuggestionClickListener(SuggestionClickListener listener) {
248        super.setSuggestionClickListener(listener);
249        mResultsAdapter.setSuggestionClickListener(listener);
250    }
251
252    @Override
253    public void setSuggestions(Suggestions suggestions) {
254        super.setSuggestions(suggestions);
255        suggestions.acquire();
256        mResultsAdapter.setSuggestions(suggestions);
257    }
258
259    @Override
260    protected void setCorpus(Corpus corpus) {
261        super.setCorpus(corpus);
262        mResultsAdapter.setPromoter(createResultsPromoter());
263    }
264
265    @Override
266    protected Promoter createSuggestionsPromoter() {
267        return getQsbApplication().createWebPromoter();
268    }
269
270    protected Promoter createResultsPromoter() {
271        Corpus corpus = getCorpus();
272        if (corpus == null) {
273            return getQsbApplication().createResultsPromoter();
274        } else {
275            return getQsbApplication().createSingleCorpusResultsPromoter(corpus);
276        }
277    }
278
279    protected void onResultsChanged() {
280        checkHideResultsHeader();
281    }
282
283    @Override
284    protected void updateQueryTextView(boolean queryEmpty) {
285        super.updateQueryTextView(queryEmpty);
286        if (mSearchCloseButton == null) return;
287
288        if (queryEmpty) {
289            mSearchCloseButton.setImageResource(R.drawable.ic_clear_disabled);
290        } else {
291            mSearchCloseButton.setImageResource(R.drawable.ic_clear);
292        }
293    }
294
295    private void checkHideResultsHeader() {
296        if (mResultsHeader != null) {
297            if (!mResultsAdapter.isEmpty()) {
298                if (DBG) Log.d(TAG, "Results non-empty");
299                mResultsHeader.setVisibility(VISIBLE);
300            } else {
301                if (DBG) Log.d(TAG, "Results empty");
302                mResultsHeader.setVisibility(INVISIBLE);
303            }
304        }
305    }
306
307    @Override
308    public Corpus getSearchCorpus() {
309        return getWebCorpus();
310    }
311
312    protected class ResultsObserver extends DataSetObserver {
313        @Override
314        public void onChanged() {
315            onResultsChanged();
316        }
317    }
318
319}
320