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