diff options
Diffstat (limited to 'mobile/android/base/java/org/mozilla/gecko/home/TwoLinePageRow.java')
-rw-r--r-- | mobile/android/base/java/org/mozilla/gecko/home/TwoLinePageRow.java | 324 |
1 files changed, 324 insertions, 0 deletions
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/TwoLinePageRow.java b/mobile/android/base/java/org/mozilla/gecko/home/TwoLinePageRow.java new file mode 100644 index 000000000..68eb8daa5 --- /dev/null +++ b/mobile/android/base/java/org/mozilla/gecko/home/TwoLinePageRow.java @@ -0,0 +1,324 @@ +/* -*- 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<IconResponse> 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. + * <p> + * 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. + * <p> + * 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. + * <p> + * 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); + } +} |