/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.goanna.home; import java.lang.ref.WeakReference; import org.mozilla.goanna.AboutPages; import org.mozilla.goanna.R; import org.mozilla.goanna.ReaderModeUtils; import org.mozilla.goanna.Tab; import org.mozilla.goanna.Tabs; import org.mozilla.goanna.db.BrowserContract.Combined; import org.mozilla.goanna.db.BrowserContract.URLColumns; import org.mozilla.goanna.favicons.Favicons; import org.mozilla.goanna.favicons.OnFaviconLoadedListener; import org.mozilla.goanna.widget.FaviconView; import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; import android.text.TextUtils; import android.util.AttributeSet; import android.view.Gravity; import android.view.LayoutInflater; import android.widget.LinearLayout; import android.widget.TextView; public class TwoLinePageRow extends LinearLayout implements Tabs.OnTabsChangedListener { protected static final int NO_ICON = 0; private final TextView mTitle; private final TextView mUrl; private int mSwitchToTabIconId; private int mPageTypeIconId; private final FaviconView mFavicon; private boolean mShowIcons; private int mLoadFaviconJobId = Favicons.NOT_LOADING; // Only holds a reference to the FaviconView itself, so if the row gets // discarded while a task is outstanding, we'll leak less memory. private static class UpdateViewFaviconLoadedListener implements OnFaviconLoadedListener { private final WeakReference view; public UpdateViewFaviconLoadedListener(FaviconView view) { this.view = new WeakReference(view); } /** * Update this row's favicon. *

* This method is always invoked on the UI thread. */ @Override public void onFaviconLoaded(String url, String faviconURL, Bitmap favicon) { FaviconView v = view.get(); if (v == null) { // Guess we stuck around after the TwoLinePageRow went away. return; } if (favicon == null) { v.showDefaultFavicon(); return; } v.updateImage(favicon, faviconURL); } } // Listener for handling Favicon loads. private final OnFaviconLoadedListener mFaviconListener; // The URL for the page corresponding to this view. private String mPageUrl; public TwoLinePageRow(Context context) { this(context, null); } public TwoLinePageRow(Context context, AttributeSet attrs) { super(context, attrs); setGravity(Gravity.CENTER_VERTICAL); LayoutInflater.from(context).inflate(R.layout.two_line_page_row, this); mTitle = (TextView) findViewById(R.id.title); mUrl = (TextView) findViewById(R.id.url); mSwitchToTabIconId = NO_ICON; mPageTypeIconId = NO_ICON; mShowIcons = true; mFavicon = (FaviconView) findViewById(R.id.icon); mFaviconListener = new UpdateViewFaviconLoadedListener(mFavicon); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); Tabs.registerOnTabsChangedListener(this); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); // Tabs' listener array is safe to modify during use: its // iteration pattern is based on snapshots. Tabs.unregisterOnTabsChangedListener(this); } /** * Update the row in response to a tab change event. *

* This method is always invoked on the UI thread. */ @Override public void onTabChanged(final Tab tab, final Tabs.TabEvents msg, final Object data) { // Carefully check if this tab event is relevant to this row. final String pageUrl = mPageUrl; if (pageUrl == null) { return; } if (tab == null) { return; } // Return early if the page URL doesn't match the current tab URL, // or the old tab URL. final String tabUrl = tab.getURL(); if (!pageUrl.equals(tabUrl) && !pageUrl.equals(data)) { return; } switch (msg) { case ADDED: case CLOSED: case LOCATION_CHANGE: updateDisplayedUrl(); break; default: break; } } private void setTitle(String text) { mTitle.setText(text); } protected void setUrl(String text) { mUrl.setText(text); } protected void setUrl(int stringId) { mUrl.setText(stringId); } protected String getUrl() { return mPageUrl; } protected void setSwitchToTabIcon(int iconId) { if (mSwitchToTabIconId == iconId) { return; } mSwitchToTabIconId = iconId; mUrl.setCompoundDrawablesWithIntrinsicBounds(mSwitchToTabIconId, 0, mPageTypeIconId, 0); } private void setPageTypeIcon(int iconId) { if (mPageTypeIconId == iconId) { return; } mPageTypeIconId = iconId; mUrl.setCompoundDrawablesWithIntrinsicBounds(mSwitchToTabIconId, 0, mPageTypeIconId, 0); } /** * Stores the page URL, so that we can use it to replace "Switch to tab" if the open * tab changes or is closed. */ private void updateDisplayedUrl(String url) { mPageUrl = url; updateDisplayedUrl(); } /** * Replaces the page URL with "Switch to tab" if there is already a tab open with that URL. * Only looks for tabs that are either private or non-private, depending on the current * selected tab. */ protected void updateDisplayedUrl() { boolean isPrivate = Tabs.getInstance().getSelectedTab().isPrivate(); Tab tab = Tabs.getInstance().getFirstTabForUrl(mPageUrl, isPrivate); if (!mShowIcons || tab == null) { setUrl(mPageUrl); setSwitchToTabIcon(NO_ICON); } else { setUrl(R.string.switch_to_tab); setSwitchToTabIcon(R.drawable.ic_url_bar_tab); } } public void setShowIcons(boolean showIcons) { mShowIcons = showIcons; } /** * Update the data displayed by this row. *

* This method must be invoked on the UI thread. * * @param title to display. * @param url to display. */ public void update(String title, String url) { update(title, url, 0); } protected void update(String title, String url, long bookmarkId) { if (mShowIcons) { // The bookmark id will be 0 (null in database) when the url // is not a bookmark. if (bookmarkId == 0) { setPageTypeIcon(NO_ICON); } else { setPageTypeIcon(R.drawable.ic_url_bar_star); } } else { setPageTypeIcon(NO_ICON); } // Use the URL instead of an empty title for consistency with the normal URL // bar view - this is the equivalent of getDisplayTitle() in Tab.java setTitle(TextUtils.isEmpty(title) ? url : title); // No point updating the below things if URL has not changed. Prevents evil Favicon flicker. if (url.equals(mPageUrl)) { return; } // Blank the Favicon, so we don't show the wrong Favicon if we scroll and miss DB. mFavicon.clearImage(); Favicons.cancelFaviconLoad(mLoadFaviconJobId); // Displayed RecentTabsPanel URLs may refer to pages opened in reader mode, so we // remove the about:reader prefix to ensure the Favicon loads properly. final String pageURL = AboutPages.isAboutReader(url) ? ReaderModeUtils.getUrlFromAboutReader(url) : url; mLoadFaviconJobId = Favicons.getSizedFaviconForPageFromLocal(getContext(), pageURL, mFaviconListener); updateDisplayedUrl(url); } /** * Update the data displayed by this row. *

* This method must be invoked on the UI thread. * * @param cursor to extract data from. */ public void updateFromCursor(Cursor cursor) { if (cursor == null) { return; } int titleIndex = cursor.getColumnIndexOrThrow(URLColumns.TITLE); final String title = cursor.getString(titleIndex); int urlIndex = cursor.getColumnIndexOrThrow(URLColumns.URL); final String url = cursor.getString(urlIndex); final long bookmarkId; final int bookmarkIdIndex = cursor.getColumnIndex(Combined.BOOKMARK_ID); if (bookmarkIdIndex != -1) { bookmarkId = cursor.getLong(bookmarkIdIndex); } else { bookmarkId = 0; } update(title, url, bookmarkId); } }