• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
TitleBar(BrowserActivity context)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 {
handleMessage(Message msg)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
createContextMenu(ContextMenu menu)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
onTouchEvent(MotionEvent event)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      */
setFavicon(Bitmap icon)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      */
setInVoiceMode(boolean inVoiceMode)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      */
setLock(Drawable d)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      */
setProgress(int newProgress)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      */
setDisplayTitle(String title)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 
setToTabPicker()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