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 17 package com.android.quicksearchbox.ui; 18 19 import com.android.quicksearchbox.Corpus; 20 import com.android.quicksearchbox.Promoter; 21 import com.android.quicksearchbox.R; 22 import com.android.quicksearchbox.Suggestions; 23 24 import android.animation.Animator; 25 import android.animation.ObjectAnimator; 26 import android.animation.ValueAnimator; 27 import android.animation.ValueAnimator.AnimatorUpdateListener; 28 import android.content.ComponentName; 29 import android.content.Context; 30 import android.content.pm.PackageManager; 31 import android.content.pm.PackageManager.NameNotFoundException; 32 import android.content.res.Resources; 33 import android.database.DataSetObserver; 34 import android.graphics.drawable.Drawable; 35 import android.os.Bundle; 36 import android.util.AttributeSet; 37 import android.util.Log; 38 import android.view.Menu; 39 import android.view.View; 40 import android.view.ViewGroup; 41 import android.widget.ExpandableListAdapter; 42 import android.widget.ImageView; 43 import android.widget.PopupMenu; 44 45 /** 46 * Two-pane variant for the search activity view. 47 */ 48 public 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