1 /* 2 * Copyright (C) 2009 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.browser; 18 19 import android.content.Context; 20 import android.content.Intent; 21 import android.content.pm.PackageManager; 22 import android.content.pm.ResolveInfo; 23 import android.content.res.Resources; 24 import android.graphics.Bitmap; 25 import android.graphics.Color; 26 import android.graphics.Rect; 27 import android.graphics.drawable.Animatable; 28 import android.graphics.drawable.BitmapDrawable; 29 import android.graphics.drawable.Drawable; 30 import android.graphics.drawable.LayerDrawable; 31 import android.graphics.drawable.PaintDrawable; 32 import android.os.Handler; 33 import android.os.Message; 34 import android.speech.RecognizerIntent; 35 import android.text.SpannableString; 36 import android.text.Spanned; 37 import android.text.TextUtils; 38 import android.text.style.ImageSpan; 39 import android.util.DisplayMetrics; 40 import android.util.TypedValue; 41 import android.view.ContextMenu; 42 import android.view.LayoutInflater; 43 import android.view.MenuInflater; 44 import android.view.MotionEvent; 45 import android.view.View; 46 import android.view.ViewConfiguration; 47 import android.widget.ImageView; 48 import android.widget.LinearLayout; 49 import android.widget.ProgressBar; 50 import android.widget.TextView; 51 52 import com.android.common.speech.LoggingEvents; 53 54 /** 55 * This class represents a title bar for a particular "tab" or "window" in the 56 * browser. 57 */ 58 public class TitleBar extends LinearLayout { 59 private TextView mTitle; 60 private Drawable mCloseDrawable; 61 private ImageView mRtButton; 62 private Drawable mCircularProgress; 63 private ProgressBar mHorizontalProgress; 64 private ImageView mFavicon; 65 private ImageView mLockIcon; 66 private ImageView mStopButton; 67 private Drawable mBookmarkDrawable; 68 private Drawable mVoiceDrawable; 69 private boolean mInLoad; 70 private BrowserActivity mBrowserActivity; 71 private Drawable mGenericFavicon; 72 private int mIconDimension; 73 private View mTitleBg; 74 private MyHandler mHandler; 75 private Intent mVoiceSearchIntent; 76 private boolean mInVoiceMode; 77 private Drawable mVoiceModeBackground; 78 private Drawable mNormalBackground; 79 private Drawable mLoadingBackground; 80 private ImageSpan mArcsSpan; 81 private int mLeftMargin; 82 private int mRightMargin; 83 84 private static int LONG_PRESS = 1; 85 86 public TitleBar(BrowserActivity context) { 87 super(context, null); 88 mHandler = new MyHandler(); 89 LayoutInflater factory = LayoutInflater.from(context); 90 factory.inflate(R.layout.title_bar, this); 91 mBrowserActivity = context; 92 93 mTitle = (TextView) findViewById(R.id.title); 94 mTitle.setCompoundDrawablePadding(5); 95 96 mTitleBg = findViewById(R.id.title_bg); 97 mLockIcon = (ImageView) findViewById(R.id.lock); 98 mFavicon = (ImageView) findViewById(R.id.favicon); 99 mStopButton = (ImageView) findViewById(R.id.stop); 100 101 mRtButton = (ImageView) findViewById(R.id.rt_btn); 102 Resources resources = context.getResources(); 103 mCircularProgress = (Drawable) resources.getDrawable( 104 com.android.internal.R.drawable.search_spinner); 105 DisplayMetrics metrics = resources.getDisplayMetrics(); 106 mLeftMargin = (int) TypedValue.applyDimension( 107 TypedValue.COMPLEX_UNIT_DIP, 8f, metrics); 108 mRightMargin = (int) TypedValue.applyDimension( 109 TypedValue.COMPLEX_UNIT_DIP, 6f, metrics); 110 mIconDimension = (int) TypedValue.applyDimension( 111 TypedValue.COMPLEX_UNIT_DIP, 20f, metrics); 112 mCircularProgress.setBounds(0, 0, mIconDimension, mIconDimension); 113 mHorizontalProgress = (ProgressBar) findViewById( 114 R.id.progress_horizontal); 115 mGenericFavicon = context.getResources().getDrawable( 116 R.drawable.app_web_browser_sm); 117 mVoiceSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); 118 mVoiceSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, 119 RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH); 120 // This extra tells voice search not to send the application id in its 121 // results intent - http://b/2546173 122 // 123 // TODO: Make a constant for this extra. 124 mVoiceSearchIntent.putExtra("android.speech.extras.SEND_APPLICATION_ID_EXTRA", false); 125 PackageManager pm = context.getPackageManager(); 126 ResolveInfo ri = pm.resolveActivity(mVoiceSearchIntent, 127 PackageManager.MATCH_DEFAULT_ONLY); 128 if (ri == null) { 129 mVoiceSearchIntent = null; 130 } else { 131 mVoiceDrawable = resources.getDrawable( 132 android.R.drawable.ic_btn_speak_now); 133 } 134 mBookmarkDrawable = mRtButton.getDrawable(); 135 mVoiceModeBackground = resources.getDrawable( 136 R.drawable.title_voice); 137 mNormalBackground = mTitleBg.getBackground(); 138 mLoadingBackground = resources.getDrawable(R.drawable.title_loading); 139 mArcsSpan = new ImageSpan(context, R.drawable.arcs, 140 ImageSpan.ALIGN_BASELINE); 141 } 142 143 private class MyHandler extends Handler { 144 public void handleMessage(Message msg) { 145 if (msg.what == LONG_PRESS) { 146 // Prevent the normal action from happening by setting the title 147 // bar's state to false. 148 mTitleBg.setPressed(false); 149 // Need to call a special method on BrowserActivity for when the 150 // fake title bar is up, because its ViewGroup does not show a 151 // context menu. 152 mBrowserActivity.showTitleBarContextMenu(); 153 } 154 } 155 }; 156 157 @Override 158 public void createContextMenu(ContextMenu menu) { 159 MenuInflater inflater = mBrowserActivity.getMenuInflater(); 160 inflater.inflate(R.menu.title_context, menu); 161 mBrowserActivity.onCreateContextMenu(menu, this, null); 162 } 163 164 @Override 165 public boolean onTouchEvent(MotionEvent event) { 166 ImageView button = mInLoad ? mStopButton : mRtButton; 167 switch (event.getAction()) { 168 case MotionEvent.ACTION_DOWN: 169 // Make all touches hit either the textfield or the button, 170 // depending on which side of the right edge of the textfield 171 // they hit. 172 if ((int) event.getX() > mTitleBg.getRight()) { 173 button.setPressed(true); 174 } else { 175 mTitleBg.setPressed(true); 176 mHandler.sendMessageDelayed(mHandler.obtainMessage( 177 LONG_PRESS), 178 ViewConfiguration.getLongPressTimeout()); 179 } 180 break; 181 case MotionEvent.ACTION_MOVE: 182 int slop = ViewConfiguration.get(mBrowserActivity) 183 .getScaledTouchSlop(); 184 if ((int) event.getY() > getHeight() + slop) { 185 // We only trigger the actions in ACTION_UP if one or the 186 // other is pressed. Since the user moved off the title 187 // bar, mark both as not pressed. 188 mTitleBg.setPressed(false); 189 button.setPressed(false); 190 mHandler.removeMessages(LONG_PRESS); 191 break; 192 } 193 int x = (int) event.getX(); 194 int titleRight = mTitleBg.getRight(); 195 if (mTitleBg.isPressed() && x > titleRight + slop) { 196 mTitleBg.setPressed(false); 197 mHandler.removeMessages(LONG_PRESS); 198 } else if (button.isPressed() && x < titleRight - slop) { 199 button.setPressed(false); 200 } 201 break; 202 case MotionEvent.ACTION_CANCEL: 203 button.setPressed(false); 204 mTitleBg.setPressed(false); 205 mHandler.removeMessages(LONG_PRESS); 206 break; 207 case MotionEvent.ACTION_UP: 208 if (button.isPressed()) { 209 if (mInVoiceMode) { 210 if (mBrowserActivity.getTabControl().getCurrentTab() 211 .voiceSearchSourceIsGoogle()) { 212 Intent i = new Intent( 213 LoggingEvents.ACTION_LOG_EVENT); 214 i.putExtra(LoggingEvents.EXTRA_EVENT, 215 LoggingEvents.VoiceSearch.RETRY); 216 mBrowserActivity.sendBroadcast(i); 217 } 218 mBrowserActivity.startActivity(mVoiceSearchIntent); 219 } else if (mInLoad) { 220 mBrowserActivity.stopLoading(); 221 } else { 222 mBrowserActivity.bookmarksOrHistoryPicker(false); 223 } 224 button.setPressed(false); 225 } else if (mTitleBg.isPressed()) { 226 mHandler.removeMessages(LONG_PRESS); 227 if (mInVoiceMode) { 228 if (mBrowserActivity.getTabControl().getCurrentTab() 229 .voiceSearchSourceIsGoogle()) { 230 Intent i = new Intent( 231 LoggingEvents.ACTION_LOG_EVENT); 232 i.putExtra(LoggingEvents.EXTRA_EVENT, 233 LoggingEvents.VoiceSearch.N_BEST_REVEAL); 234 mBrowserActivity.sendBroadcast(i); 235 } 236 mBrowserActivity.showVoiceSearchResults( 237 mTitle.getText().toString().trim()); 238 } else { 239 mBrowserActivity.editUrl(); 240 } 241 mTitleBg.setPressed(false); 242 } 243 break; 244 default: 245 break; 246 } 247 return true; 248 } 249 250 /** 251 * Set a new Bitmap for the Favicon. 252 */ 253 /* package */ void setFavicon(Bitmap icon) { 254 Drawable[] array = new Drawable[3]; 255 array[0] = new PaintDrawable(Color.BLACK); 256 PaintDrawable p = new PaintDrawable(Color.WHITE); 257 array[1] = p; 258 if (icon == null) { 259 array[2] = mGenericFavicon; 260 } else { 261 array[2] = new BitmapDrawable(icon); 262 } 263 LayerDrawable d = new LayerDrawable(array); 264 d.setLayerInset(1, 1, 1, 1, 1); 265 d.setLayerInset(2, 2, 2, 2, 2); 266 mFavicon.setImageDrawable(d); 267 } 268 269 /** 270 * Change the TitleBar to or from voice mode. If there is no package to 271 * handle voice search, the TitleBar cannot be set to voice mode. 272 */ 273 /* package */ void setInVoiceMode(boolean inVoiceMode) { 274 if (mInVoiceMode == inVoiceMode) return; 275 mInVoiceMode = inVoiceMode && mVoiceSearchIntent != null; 276 Drawable titleDrawable; 277 if (mInVoiceMode) { 278 mRtButton.setImageDrawable(mVoiceDrawable); 279 titleDrawable = mVoiceModeBackground; 280 mTitle.setEllipsize(null); 281 mRtButton.setVisibility(View.VISIBLE); 282 mStopButton.setVisibility(View.GONE); 283 mTitleBg.setBackgroundDrawable(titleDrawable); 284 mTitleBg.setPadding(mLeftMargin, mTitleBg.getPaddingTop(), 285 mRightMargin, mTitleBg.getPaddingBottom()); 286 } else { 287 if (mInLoad) { 288 titleDrawable = mLoadingBackground; 289 mRtButton.setVisibility(View.GONE); 290 mStopButton.setVisibility(View.VISIBLE); 291 } else { 292 titleDrawable = mNormalBackground; 293 mRtButton.setVisibility(View.VISIBLE); 294 mStopButton.setVisibility(View.GONE); 295 mRtButton.setImageDrawable(mBookmarkDrawable); 296 } 297 mTitle.setEllipsize(TextUtils.TruncateAt.END); 298 mTitleBg.setBackgroundDrawable(titleDrawable); 299 mTitleBg.setPadding(mLeftMargin, 0, mRightMargin, 0); 300 } 301 mTitle.setSingleLine(!mInVoiceMode); 302 } 303 304 /** 305 * Set the Drawable for the lock icon, or null to hide it. 306 */ 307 /* package */ void setLock(Drawable d) { 308 if (null == d) { 309 mLockIcon.setVisibility(View.GONE); 310 } else { 311 mLockIcon.setImageDrawable(d); 312 mLockIcon.setVisibility(View.VISIBLE); 313 } 314 } 315 316 /** 317 * Update the progress, from 0 to 100. 318 */ 319 /* package */ void setProgress(int newProgress) { 320 if (newProgress >= mHorizontalProgress.getMax()) { 321 mTitle.setCompoundDrawables(null, null, null, null); 322 ((Animatable) mCircularProgress).stop(); 323 mHorizontalProgress.setVisibility(View.INVISIBLE); 324 if (!mInVoiceMode) { 325 mRtButton.setImageDrawable(mBookmarkDrawable); 326 mRtButton.setVisibility(View.VISIBLE); 327 mStopButton.setVisibility(View.GONE); 328 mTitleBg.setBackgroundDrawable(mNormalBackground); 329 mTitleBg.setPadding(mLeftMargin, 0, mRightMargin, 0); 330 } 331 mInLoad = false; 332 } else { 333 mHorizontalProgress.setProgress(newProgress); 334 if (!mInLoad && getWindowToken() != null) { 335 // checking the window token lets us be sure that we 336 // are attached to a window before starting the animation, 337 // preventing a potential race condition 338 // (fix for bug http://b/2115736) 339 mTitle.setCompoundDrawables(null, null, mCircularProgress, 340 null); 341 ((Animatable) mCircularProgress).start(); 342 mHorizontalProgress.setVisibility(View.VISIBLE); 343 if (!mInVoiceMode) { 344 mTitleBg.setBackgroundDrawable(mLoadingBackground); 345 mTitleBg.setPadding(mLeftMargin, 0, mRightMargin, 0); 346 mRtButton.setVisibility(View.GONE); 347 mStopButton.setVisibility(View.VISIBLE); 348 } 349 mInLoad = true; 350 } 351 } 352 } 353 354 /** 355 * Update the text displayed in the title bar. 356 * @param title String to display. If null, the loading string will be 357 * shown. 358 */ 359 /* package */ void setDisplayTitle(String title) { 360 if (title == null) { 361 mTitle.setText(R.string.title_bar_loading); 362 } else { 363 if (mInVoiceMode) { 364 // Add two spaces. The second one will be replaced with an 365 // image, and the first one will put space between it and the 366 // text 367 SpannableString spannable = new SpannableString(title + " "); 368 int end = spannable.length(); 369 spannable.setSpan(mArcsSpan, end - 1, end, 370 Spanned.SPAN_MARK_POINT); 371 mTitle.setText(spannable); 372 } else { 373 mTitle.setText(title); 374 } 375 } 376 } 377 378 /* package */ void setToTabPicker() { 379 mTitle.setText(R.string.tab_picker_title); 380 setFavicon(null); 381 setLock(null); 382 mHorizontalProgress.setVisibility(View.GONE); 383 } 384 } 385