// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.chrome.browser; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.text.TextUtils; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.HeaderViewListAdapter; import android.widget.ListPopupWindow; import android.widget.PopupWindow; import android.widget.TextView; import org.chromium.base.CalledByNative; import org.chromium.base.ThreadUtils; import org.chromium.content.browser.NavigationClient; import org.chromium.content_public.browser.NavigationEntry; import org.chromium.content_public.browser.NavigationHistory; import org.chromium.ui.base.LocalizationUtils; import java.util.HashSet; import java.util.Set; /** * A popup that handles displaying the navigation history for a given tab. */ public class NavigationPopup extends ListPopupWindow implements AdapterView.OnItemClickListener { private static final int FAVICON_SIZE_DP = 16; private static final int MAXIMUM_HISTORY_ITEMS = 8; private final Context mContext; private final NavigationClient mNavigationClient; private final NavigationHistory mHistory; private final NavigationAdapter mAdapter; private final ListItemFactory mListItemFactory; private final int mFaviconSize; private long mNativeNavigationPopup; /** * Constructs a new popup with the given history information. * * @param context The context used for building the popup. * @param navigationClient The owner of the history being displayed. * @param isForward Whether to request forward navigation entries. */ public NavigationPopup( Context context, NavigationClient navigationClient, boolean isForward) { super(context, null, android.R.attr.popupMenuStyle); mContext = context; mNavigationClient = navigationClient; mHistory = mNavigationClient.getDirectedNavigationHistory( isForward, MAXIMUM_HISTORY_ITEMS); mAdapter = new NavigationAdapter(); float density = mContext.getResources().getDisplayMetrics().density; mFaviconSize = (int) (density * FAVICON_SIZE_DP); setModal(true); setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); setHeight(ViewGroup.LayoutParams.WRAP_CONTENT); setOnItemClickListener(this); setAdapter(new HeaderViewListAdapter(null, null, mAdapter)); mListItemFactory = new ListItemFactory(context); } /** * @return Whether a navigation popup is valid for the given page. */ public boolean shouldBeShown() { return mHistory.getEntryCount() > 0; } @Override public void show() { if (mNativeNavigationPopup == 0) initializeNative(); super.show(); } @Override public void dismiss() { if (mNativeNavigationPopup != 0) { nativeDestroy(mNativeNavigationPopup); mNativeNavigationPopup = 0; } super.dismiss(); } private void initializeNative() { ThreadUtils.assertOnUiThread(); mNativeNavigationPopup = nativeInit(); Set requestedUrls = new HashSet(); for (int i = 0; i < mHistory.getEntryCount(); i++) { NavigationEntry entry = mHistory.getEntryAtIndex(i); if (entry.getFavicon() != null) continue; String url = entry.getUrl(); if (!requestedUrls.contains(url)) { nativeFetchFaviconForUrl(mNativeNavigationPopup, url); requestedUrls.add(url); } } nativeFetchFaviconForUrl(mNativeNavigationPopup, nativeGetHistoryUrl()); } @CalledByNative private void onFaviconUpdated(String url, Object favicon) { for (int i = 0; i < mHistory.getEntryCount(); i++) { NavigationEntry entry = mHistory.getEntryAtIndex(i); if (TextUtils.equals(url, entry.getUrl())) entry.updateFavicon((Bitmap) favicon); } mAdapter.notifyDataSetChanged(); } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { NavigationEntry entry = (NavigationEntry) parent.getItemAtPosition(position); mNavigationClient.goToNavigationIndex(entry.getIndex()); dismiss(); } private void updateBitmapForTextView(TextView view, Bitmap bitmap) { Drawable faviconDrawable = null; if (bitmap != null) { faviconDrawable = new BitmapDrawable(mContext.getResources(), bitmap); ((BitmapDrawable) faviconDrawable).setGravity(Gravity.FILL); } else { faviconDrawable = new ColorDrawable(Color.TRANSPARENT); } faviconDrawable.setBounds(0, 0, mFaviconSize, mFaviconSize); view.setCompoundDrawables(faviconDrawable, null, null, null); } private static class ListItemFactory { private static final int LIST_ITEM_HEIGHT_DP = 48; private static final int PADDING_DP = 8; private static final int TEXT_SIZE_SP = 18; private static final float FADE_LENGTH_DP = 25.0f; private static final float FADE_STOP = 0.75f; int mFadeEdgeLength; int mFadePadding; int mListItemHeight; int mPadding; boolean mIsLayoutDirectionRTL; Context mContext; public ListItemFactory(Context context) { mContext = context; computeFadeDimensions(); } private void computeFadeDimensions() { // Fade with linear gradient starting 25dp from right margin. // Reaches 0% opacity at 75% length. (Simulated with extra padding) float density = mContext.getResources().getDisplayMetrics().density; float fadeLength = (FADE_LENGTH_DP * density); mFadeEdgeLength = (int) (fadeLength * FADE_STOP); mFadePadding = (int) (fadeLength * (1 - FADE_STOP)); mListItemHeight = (int) (density * LIST_ITEM_HEIGHT_DP); mPadding = (int) (density * PADDING_DP); mIsLayoutDirectionRTL = LocalizationUtils.isLayoutRtl(); } public TextView createListItem() { TextView view = new TextView(mContext); view.setFadingEdgeLength(mFadeEdgeLength); view.setHorizontalFadingEdgeEnabled(true); view.setSingleLine(); view.setTextSize(TEXT_SIZE_SP); view.setMinimumHeight(mListItemHeight); view.setGravity(Gravity.CENTER_VERTICAL); view.setCompoundDrawablePadding(mPadding); if (!mIsLayoutDirectionRTL) { view.setPadding(mPadding, 0, mPadding + mFadePadding , 0); } else { view.setPadding(mPadding + mFadePadding, 0, mPadding, 0); } return view; } } private class NavigationAdapter extends BaseAdapter { @Override public int getCount() { return mHistory.getEntryCount(); } @Override public Object getItem(int position) { return mHistory.getEntryAtIndex(position); } @Override public long getItemId(int position) { return ((NavigationEntry) getItem(position)).getIndex(); } @Override public View getView(int position, View convertView, ViewGroup parent) { TextView view; if (convertView != null && convertView instanceof TextView) { view = (TextView) convertView; } else { view = mListItemFactory.createListItem(); } NavigationEntry entry = (NavigationEntry) getItem(position); String entryText = entry.getTitle(); if (TextUtils.isEmpty(entryText)) entryText = entry.getVirtualUrl(); if (TextUtils.isEmpty(entryText)) entryText = entry.getUrl(); view.setText(entryText); updateBitmapForTextView(view, entry.getFavicon()); return view; } } private static native String nativeGetHistoryUrl(); private native long nativeInit(); private native void nativeDestroy(long nativeNavigationPopup); private native void nativeFetchFaviconForUrl(long nativeNavigationPopup, String url); }