/* -*- 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.gecko.home; import java.lang.ref.WeakReference; import java.util.concurrent.Future; import android.content.Context; import android.database.Cursor; import android.text.TextUtils; import android.util.AttributeSet; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import org.mozilla.gecko.AboutPages; import org.mozilla.gecko.R; import org.mozilla.gecko.Tab; import org.mozilla.gecko.Tabs; import org.mozilla.gecko.db.BrowserContract; import org.mozilla.gecko.db.BrowserContract.Combined; import org.mozilla.gecko.db.BrowserContract.URLColumns; import org.mozilla.gecko.distribution.PartnerBookmarksProviderProxy; import org.mozilla.gecko.icons.IconDescriptor; import org.mozilla.gecko.icons.IconResponse; import org.mozilla.gecko.icons.Icons; import org.mozilla.gecko.reader.ReaderModeUtils; import org.mozilla.gecko.reader.SavedReaderViewHelper; import org.mozilla.gecko.widget.FaviconView; public class TwoLinePageRow extends LinearLayout implements Tabs.OnTabsChangedListener { protected static final int NO_ICON = 0; private final TextView mTitle; private final TextView mUrl; private final ImageView mStatusIcon; private int mSwitchToTabIconId; private final FaviconView mFavicon; private Future mOngoingIconLoad; private boolean mShowIcons; // The URL for the page corresponding to this view. private String mPageUrl; private boolean mHasReaderCacheItem; 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); // Merge layouts lose their padding, so set it dynamically. setPadding(0, 0, (int) getResources().getDimension(R.dimen.page_row_edge_padding), 0); mTitle = (TextView) findViewById(R.id.title); mUrl = (TextView) findViewById(R.id.url); mStatusIcon = (ImageView) findViewById(R.id.status_icon_bookmark); mSwitchToTabIconId = NO_ICON; mShowIcons = true; mFavicon = (FaviconView) findViewById(R.id.icon); } @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 String 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. // data is an empty String for ADDED/CLOSED, and contains the previous/old URL during // LOCATION_CHANGE (the new URL is retrieved using tab.getURL()). // tabURL and data may be about:reader URLs if the current or old tab page was a reader view // page, however pageUrl will always be a plain URL (i.e. we only add about:reader when opening // a reader view bookmark, at all other times it's a normal bookmark with normal URL). final String tabUrl = tab.getURL(); if (!pageUrl.equals(ReaderModeUtils.stripAboutReaderUrl(tabUrl)) && !pageUrl.equals(ReaderModeUtils.stripAboutReaderUrl(data))) { return; } // Note: we *might* need to update the display status (i.e. switch-to-tab icon/label) if // a matching tab has been opened/closed/switched to a different page. updateDisplayedUrl() will // determine the changes (if any) that actually need to be made. A tab change with a matching URL // does not imply that any changes are needed - e.g. if a given URL is already open in one tab, and // is also opened in a second tab, the switch-to-tab status doesn't change, closing 1 of 2 tabs with a URL // similarly doesn't change the switch-to-tab display, etc. (However closing the last tab for // a given URL does require a status change, as does opening the first tab with that URL.) 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, 0, 0); } private void updateStatusIcon(boolean isBookmark, boolean isReaderItem) { if (isReaderItem) { mStatusIcon.setImageResource(R.drawable.status_icon_readercache); } else if (isBookmark) { mStatusIcon.setImageResource(R.drawable.star_blue); } if (mShowIcons && (isBookmark || isReaderItem)) { mStatusIcon.setVisibility(View.VISIBLE); } else if (mShowIcons) { // We use INVISIBLE to have consistent padding for our items. This means text/URLs // fade consistently in the same location, regardless of them being bookmarked. mStatusIcon.setVisibility(View.INVISIBLE); } else { mStatusIcon.setVisibility(View.GONE); } } /** * 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, boolean hasReaderCacheItem) { mPageUrl = url; mHasReaderCacheItem = hasReaderCacheItem; 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() { final Tab selectedTab = Tabs.getInstance().getSelectedTab(); final boolean isPrivate = (selectedTab != null) && (selectedTab.isPrivate()); // We always want to display the underlying page url, however for readermode pages // we navigate to the about:reader equivalent, hence we need to use that url when finding // existing tabs final String navigationUrl = mHasReaderCacheItem ? ReaderModeUtils.getAboutReaderForUrl(mPageUrl) : mPageUrl; Tab tab = Tabs.getInstance().getFirstTabForUrl(navigationUrl, 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, false); } protected void update(String title, String url, long bookmarkId, boolean hasReaderCacheItem) { if (mShowIcons) { // The bookmark id will be 0 (null in database) when the url // is not a bookmark and negative for 'fake' bookmarks. final boolean isBookmark = bookmarkId > 0; updateStatusIcon(isBookmark, hasReaderCacheItem); } else { updateStatusIcon(false, false); } // 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(); if (mOngoingIconLoad != null) { mOngoingIconLoad.cancel(true); } // 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 = ReaderModeUtils.stripAboutReaderUrl(url); if (TextUtils.isEmpty(pageURL)) { // If url is empty, display the item as-is but do not load an icon if we do not have a page URL (bug 1310622) } else if (bookmarkId < BrowserContract.Bookmarks.FAKE_PARTNER_BOOKMARKS_START) { mOngoingIconLoad = Icons.with(getContext()) .pageUrl(pageURL) .skipNetwork() .privileged(true) .icon(IconDescriptor.createGenericIcon( PartnerBookmarksProviderProxy.getUriForIcon(getContext(), bookmarkId).toString())) .build() .execute(mFavicon.createIconCallback()); } else { mOngoingIconLoad = Icons.with(getContext()) .pageUrl(pageURL) .skipNetwork() .build() .execute(mFavicon.createIconCallback()); } updateDisplayedUrl(url, hasReaderCacheItem); } /** * 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; } SavedReaderViewHelper rch = SavedReaderViewHelper.getSavedReaderViewHelper(getContext()); final boolean hasReaderCacheItem = rch.isURLCached(url); update(title, url, bookmarkId, hasReaderCacheItem); } }