Home | History | Annotate | Download | only in browser
      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