// 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.BitmapFactory;
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.ThreadUtils;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.favicon.FaviconHelper;
import org.chromium.chrome.browser.favicon.FaviconHelper.FaviconImageCallback;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.content_public.browser.NavigationController;
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 MAXIMUM_HISTORY_ITEMS = 8;
private final Profile mProfile;
private final Context mContext;
private final NavigationController mNavigationController;
private final NavigationHistory mHistory;
private final NavigationAdapter mAdapter;
private final ListItemFactory mListItemFactory;
private final int mFaviconSize;
private Bitmap mDefaultFavicon;
/**
* Loads the favicons asynchronously.
*/
private FaviconHelper mFaviconHelper;
private boolean mInitialized;
/**
* Constructs a new popup with the given history information.
*
* @param profile The profile used for fetching favicons.
* @param context The context used for building the popup.
* @param navigationController The controller which takes care of page navigations.
* @param isForward Whether to request forward navigation entries.
*/
public NavigationPopup(Profile profile, Context context,
NavigationController navigationController, boolean isForward) {
super(context, null, android.R.attr.popupMenuStyle);
mProfile = profile;
mContext = context;
mNavigationController = navigationController;
mHistory = mNavigationController.getDirectedNavigationHistory(
isForward, MAXIMUM_HISTORY_ITEMS);
mAdapter = new NavigationAdapter();
mFaviconSize = mContext.getResources().getDimensionPixelSize(R.dimen.default_favicon_size);
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 (!mInitialized) initialize();
super.show();
}
@Override
public void dismiss() {
if (mInitialized) mFaviconHelper.destroy();
mInitialized = false;
super.dismiss();
}
private void initialize() {
ThreadUtils.assertOnUiThread();
mInitialized = true;
mFaviconHelper = new FaviconHelper();
Set<String> requestedUrls = new HashSet<String>();
for (int i = 0; i < mHistory.getEntryCount(); i++) {
NavigationEntry entry = mHistory.getEntryAtIndex(i);
if (entry.getFavicon() != null) continue;
final String pageUrl = entry.getUrl();
if (!requestedUrls.contains(pageUrl)) {
FaviconImageCallback imageCallback = new FaviconImageCallback() {
@Override
public void onFaviconAvailable(Bitmap bitmap, String iconUrl) {
NavigationPopup.this.onFaviconAvailable(pageUrl, bitmap);
}
};
mFaviconHelper.getLocalFaviconImageForURL(
mProfile, pageUrl, mFaviconSize, imageCallback);
requestedUrls.add(pageUrl);
}
}
FaviconImageCallback historyImageCallback = new FaviconImageCallback() {
@Override
public void onFaviconAvailable(Bitmap bitmap, String iconUrl) {
NavigationPopup.this.onFaviconAvailable(UrlConstants.HISTORY_URL, bitmap);
}
};
mFaviconHelper.getLocalFaviconImageForURL(
mProfile, UrlConstants.HISTORY_URL, mFaviconSize, historyImageCallback);
}
/**
* Called when favicon data requested by {@link #initialize()} is retrieved.
* @param pageUrl the page for which the favicon was retrieved.
* @param favicon the favicon data.
*/
private void onFaviconAvailable(String pageUrl, Object favicon) {
if (favicon == null) {
if (mDefaultFavicon == null) {
mDefaultFavicon = BitmapFactory.decodeResource(
mContext.getResources(), R.drawable.default_favicon);
}
favicon = mDefaultFavicon;
}
for (int i = 0; i < mHistory.getEntryCount(); i++) {
NavigationEntry entry = mHistory.getEntryAtIndex(i);
if (TextUtils.equals(pageUrl, 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);
mNavigationController.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 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;
}
}
}