Home | History | Annotate | Download | only in browser
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 package org.chromium.chrome.browser;
      6 
      7 import android.content.Context;
      8 import android.graphics.Bitmap;
      9 import android.graphics.Color;
     10 import android.graphics.drawable.BitmapDrawable;
     11 import android.graphics.drawable.ColorDrawable;
     12 import android.graphics.drawable.Drawable;
     13 import android.text.TextUtils;
     14 import android.view.Gravity;
     15 import android.view.View;
     16 import android.view.ViewGroup;
     17 import android.widget.AdapterView;
     18 import android.widget.BaseAdapter;
     19 import android.widget.HeaderViewListAdapter;
     20 import android.widget.ListPopupWindow;
     21 import android.widget.PopupWindow;
     22 import android.widget.TextView;
     23 
     24 import org.chromium.base.CalledByNative;
     25 import org.chromium.base.ThreadUtils;
     26 import org.chromium.content.browser.NavigationClient;
     27 import org.chromium.content_public.browser.NavigationEntry;
     28 import org.chromium.content_public.browser.NavigationHistory;
     29 import org.chromium.ui.base.LocalizationUtils;
     30 
     31 import java.util.HashSet;
     32 import java.util.Set;
     33 
     34 /**
     35  * A popup that handles displaying the navigation history for a given tab.
     36  */
     37 public class NavigationPopup extends ListPopupWindow implements AdapterView.OnItemClickListener {
     38 
     39     private static final int FAVICON_SIZE_DP = 16;
     40 
     41     private static final int MAXIMUM_HISTORY_ITEMS = 8;
     42 
     43     private final Context mContext;
     44     private final NavigationClient mNavigationClient;
     45     private final NavigationHistory mHistory;
     46     private final NavigationAdapter mAdapter;
     47     private final ListItemFactory mListItemFactory;
     48 
     49     private final int mFaviconSize;
     50 
     51     private long mNativeNavigationPopup;
     52 
     53     /**
     54      * Constructs a new popup with the given history information.
     55      *
     56      * @param context The context used for building the popup.
     57      * @param navigationClient The owner of the history being displayed.
     58      * @param isForward Whether to request forward navigation entries.
     59      */
     60     public NavigationPopup(
     61             Context context, NavigationClient navigationClient, boolean isForward) {
     62         super(context, null, android.R.attr.popupMenuStyle);
     63         mContext = context;
     64         mNavigationClient = navigationClient;
     65         mHistory = mNavigationClient.getDirectedNavigationHistory(
     66                 isForward, MAXIMUM_HISTORY_ITEMS);
     67         mAdapter = new NavigationAdapter();
     68 
     69         float density = mContext.getResources().getDisplayMetrics().density;
     70         mFaviconSize = (int) (density * FAVICON_SIZE_DP);
     71 
     72         setModal(true);
     73         setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
     74         setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
     75         setOnItemClickListener(this);
     76 
     77         setAdapter(new HeaderViewListAdapter(null, null, mAdapter));
     78 
     79         mListItemFactory = new ListItemFactory(context);
     80     }
     81 
     82     /**
     83      * @return Whether a navigation popup is valid for the given page.
     84      */
     85     public boolean shouldBeShown() {
     86         return mHistory.getEntryCount() > 0;
     87     }
     88 
     89     @Override
     90     public void show() {
     91         if (mNativeNavigationPopup == 0) initializeNative();
     92         super.show();
     93     }
     94 
     95     @Override
     96     public void dismiss() {
     97         if (mNativeNavigationPopup != 0) {
     98             nativeDestroy(mNativeNavigationPopup);
     99             mNativeNavigationPopup = 0;
    100         }
    101         super.dismiss();
    102     }
    103 
    104     private void initializeNative() {
    105         ThreadUtils.assertOnUiThread();
    106         mNativeNavigationPopup = nativeInit();
    107 
    108         Set<String> requestedUrls = new HashSet<String>();
    109         for (int i = 0; i < mHistory.getEntryCount(); i++) {
    110             NavigationEntry entry = mHistory.getEntryAtIndex(i);
    111             if (entry.getFavicon() != null) continue;
    112             String url = entry.getUrl();
    113             if (!requestedUrls.contains(url)) {
    114                 nativeFetchFaviconForUrl(mNativeNavigationPopup, url);
    115                 requestedUrls.add(url);
    116             }
    117         }
    118         nativeFetchFaviconForUrl(mNativeNavigationPopup, nativeGetHistoryUrl());
    119     }
    120 
    121     @CalledByNative
    122     private void onFaviconUpdated(String url, Object favicon) {
    123         for (int i = 0; i < mHistory.getEntryCount(); i++) {
    124             NavigationEntry entry = mHistory.getEntryAtIndex(i);
    125             if (TextUtils.equals(url, entry.getUrl())) entry.updateFavicon((Bitmap) favicon);
    126         }
    127         mAdapter.notifyDataSetChanged();
    128     }
    129 
    130     @Override
    131     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    132         NavigationEntry entry = (NavigationEntry) parent.getItemAtPosition(position);
    133         mNavigationClient.goToNavigationIndex(entry.getIndex());
    134         dismiss();
    135     }
    136 
    137     private void updateBitmapForTextView(TextView view, Bitmap bitmap) {
    138         Drawable faviconDrawable = null;
    139         if (bitmap != null) {
    140             faviconDrawable = new BitmapDrawable(mContext.getResources(), bitmap);
    141             ((BitmapDrawable) faviconDrawable).setGravity(Gravity.FILL);
    142         } else {
    143             faviconDrawable = new ColorDrawable(Color.TRANSPARENT);
    144         }
    145         faviconDrawable.setBounds(0, 0, mFaviconSize, mFaviconSize);
    146         view.setCompoundDrawables(faviconDrawable, null, null, null);
    147     }
    148 
    149     private static class ListItemFactory {
    150         private static final int LIST_ITEM_HEIGHT_DP = 48;
    151         private static final int PADDING_DP = 8;
    152         private static final int TEXT_SIZE_SP = 18;
    153         private static final float FADE_LENGTH_DP = 25.0f;
    154         private static final float FADE_STOP = 0.75f;
    155 
    156         int mFadeEdgeLength;
    157         int mFadePadding;
    158         int mListItemHeight;
    159         int mPadding;
    160         boolean mIsLayoutDirectionRTL;
    161         Context mContext;
    162 
    163         public ListItemFactory(Context context) {
    164             mContext = context;
    165             computeFadeDimensions();
    166         }
    167 
    168         private void computeFadeDimensions() {
    169             // Fade with linear gradient starting 25dp from right margin.
    170             // Reaches 0% opacity at 75% length. (Simulated with extra padding)
    171             float density = mContext.getResources().getDisplayMetrics().density;
    172             float fadeLength = (FADE_LENGTH_DP * density);
    173             mFadeEdgeLength = (int) (fadeLength * FADE_STOP);
    174             mFadePadding = (int) (fadeLength * (1 - FADE_STOP));
    175             mListItemHeight = (int) (density * LIST_ITEM_HEIGHT_DP);
    176             mPadding = (int) (density * PADDING_DP);
    177             mIsLayoutDirectionRTL = LocalizationUtils.isLayoutRtl();
    178         }
    179 
    180         public TextView createListItem() {
    181             TextView view = new TextView(mContext);
    182             view.setFadingEdgeLength(mFadeEdgeLength);
    183             view.setHorizontalFadingEdgeEnabled(true);
    184             view.setSingleLine();
    185             view.setTextSize(TEXT_SIZE_SP);
    186             view.setMinimumHeight(mListItemHeight);
    187             view.setGravity(Gravity.CENTER_VERTICAL);
    188             view.setCompoundDrawablePadding(mPadding);
    189             if (!mIsLayoutDirectionRTL) {
    190                 view.setPadding(mPadding, 0, mPadding + mFadePadding , 0);
    191             } else {
    192                 view.setPadding(mPadding + mFadePadding, 0, mPadding, 0);
    193             }
    194             return view;
    195         }
    196     }
    197 
    198     private class NavigationAdapter extends BaseAdapter {
    199         @Override
    200         public int getCount() {
    201             return mHistory.getEntryCount();
    202         }
    203 
    204         @Override
    205         public Object getItem(int position) {
    206             return mHistory.getEntryAtIndex(position);
    207         }
    208 
    209         @Override
    210         public long getItemId(int position) {
    211             return ((NavigationEntry) getItem(position)).getIndex();
    212         }
    213 
    214         @Override
    215         public View getView(int position, View convertView, ViewGroup parent) {
    216             TextView view;
    217             if (convertView != null && convertView instanceof TextView) {
    218                 view = (TextView) convertView;
    219             } else {
    220                 view = mListItemFactory.createListItem();
    221             }
    222             NavigationEntry entry = (NavigationEntry) getItem(position);
    223 
    224             String entryText = entry.getTitle();
    225             if (TextUtils.isEmpty(entryText)) entryText = entry.getVirtualUrl();
    226             if (TextUtils.isEmpty(entryText)) entryText = entry.getUrl();
    227             view.setText(entryText);
    228             updateBitmapForTextView(view, entry.getFavicon());
    229 
    230             return view;
    231         }
    232     }
    233 
    234     private static native String nativeGetHistoryUrl();
    235 
    236     private native long nativeInit();
    237     private native void nativeDestroy(long nativeNavigationPopup);
    238     private native void nativeFetchFaviconForUrl(long nativeNavigationPopup, String url);
    239 }
    240