blob: 78e8ac5a2195b285ebdb1ac3241569c79cc9bee0 [file] [log] [blame]
// Copyright (c) 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.browser.NavigationEntry;
import org.chromium.content.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<String> requestedUrls = new HashSet<String>();
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.isSystemLayoutDirectionRtl();
}
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);
}