| // Copyright 2015 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.ntp; |
| |
| import android.app.Activity; |
| import android.content.res.Resources; |
| import android.graphics.Canvas; |
| import android.graphics.Color; |
| import android.view.ContextMenu; |
| import android.view.ContextMenu.ContextMenuInfo; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.ExpandableListView; |
| |
| import org.chromium.chrome.R; |
| import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider; |
| import org.chromium.chrome.browser.compositor.layouts.content.InvalidationAwareThumbnailProvider; |
| import org.chromium.chrome.browser.ui.native_page.NativePage; |
| import org.chromium.chrome.browser.ui.native_page.NativePageHost; |
| import org.chromium.components.embedder_support.util.UrlConstants; |
| import org.chromium.ui.base.DeviceFormFactor; |
| import org.chromium.ui.base.ViewUtils; |
| |
| /** |
| * The native recent tabs page. Lists recently closed tabs, open windows and tabs from the user's |
| * synced devices, and snapshot documents sent from Chrome to Mobile in an expandable list view. |
| */ |
| public class RecentTabsPage |
| implements NativePage, ExpandableListView.OnChildClickListener, |
| ExpandableListView.OnGroupCollapseListener, |
| ExpandableListView.OnGroupExpandListener, RecentTabsManager.UpdatedCallback, |
| View.OnAttachStateChangeListener, View.OnCreateContextMenuListener, |
| InvalidationAwareThumbnailProvider, BrowserControlsStateProvider.Observer { |
| private final Activity mActivity; |
| private final BrowserControlsStateProvider mBrowserControlsStateProvider; |
| private final ExpandableListView mListView; |
| private final String mTitle; |
| private final ViewGroup mView; |
| |
| private RecentTabsManager mRecentTabsManager; |
| private RecentTabsRowAdapter mAdapter; |
| private NativePageHost mPageHost; |
| |
| private boolean mSnapshotContentChanged; |
| private int mSnapshotListPosition; |
| private int mSnapshotListTop; |
| private int mSnapshotWidth; |
| private int mSnapshotHeight; |
| |
| /** |
| * Whether {@link #mView} is attached to the application window. |
| */ |
| private boolean mIsAttachedToWindow; |
| |
| /** |
| * Constructor returns an instance of RecentTabsPage. |
| * |
| * @param activity The activity this view belongs to. |
| * @param recentTabsManager The RecentTabsManager which provides the model data. |
| * @param pageHost The NativePageHost used to provide a history navigation delegate object. |
| * @param browserControlsManager The BrowserControlsManager used to provide offset values. |
| */ |
| public RecentTabsPage(Activity activity, RecentTabsManager recentTabsManager, |
| NativePageHost pageHost, BrowserControlsStateProvider browserControlsStateProvider) { |
| mActivity = activity; |
| mRecentTabsManager = recentTabsManager; |
| mPageHost = pageHost; |
| Resources resources = activity.getResources(); |
| |
| mTitle = resources.getString(R.string.recent_tabs); |
| mRecentTabsManager.setUpdatedCallback(this); |
| LayoutInflater inflater = LayoutInflater.from(activity); |
| mView = (ViewGroup) inflater.inflate(R.layout.recent_tabs_page, null); |
| mListView = (ExpandableListView) mView.findViewById(R.id.odp_listview); |
| mAdapter = new RecentTabsRowAdapter(activity, recentTabsManager); |
| mListView.setAdapter(mAdapter); |
| mListView.setOnChildClickListener(this); |
| mListView.setGroupIndicator(null); |
| mListView.setOnGroupCollapseListener(this); |
| mListView.setOnGroupExpandListener(this); |
| mListView.setOnCreateContextMenuListener(this); |
| |
| mView.addOnAttachStateChangeListener(this); |
| |
| if (!DeviceFormFactor.isNonMultiDisplayContextOnTablet(mActivity)) { |
| mBrowserControlsStateProvider = browserControlsStateProvider; |
| mBrowserControlsStateProvider.addObserver(this); |
| onBottomControlsHeightChanged(mBrowserControlsStateProvider.getBottomControlsHeight(), |
| mBrowserControlsStateProvider.getBottomControlsMinHeight()); |
| } else { |
| mBrowserControlsStateProvider = null; |
| } |
| |
| onUpdated(); |
| } |
| |
| // NativePage overrides |
| |
| @Override |
| public String getUrl() { |
| return UrlConstants.RECENT_TABS_URL; |
| } |
| |
| @Override |
| public String getTitle() { |
| return mTitle; |
| } |
| |
| @Override |
| public int getBackgroundColor() { |
| return Color.WHITE; |
| } |
| |
| @Override |
| public boolean needsToolbarShadow() { |
| return true; |
| } |
| |
| @Override |
| public View getView() { |
| return mView; |
| } |
| |
| @Override |
| public String getHost() { |
| return UrlConstants.RECENT_TABS_HOST; |
| } |
| |
| @Override |
| public void destroy() { |
| assert !mIsAttachedToWindow : "Destroy called before removed from window"; |
| mRecentTabsManager.destroy(); |
| mRecentTabsManager = null; |
| mPageHost = null; |
| mAdapter.notifyDataSetInvalidated(); |
| mAdapter = null; |
| mListView.setAdapter((RecentTabsRowAdapter) null); |
| |
| mView.removeOnAttachStateChangeListener(this); |
| if (mBrowserControlsStateProvider != null) { |
| mBrowserControlsStateProvider.removeObserver(this); |
| } |
| } |
| |
| @Override |
| public void updateForUrl(String url) { |
| } |
| |
| // View.OnAttachStateChangeListener |
| @Override |
| public void onViewAttachedToWindow(View view) { |
| // Called when the user opens the RecentTabsPage or switches back to the RecentTabsPage from |
| // another tab. |
| mIsAttachedToWindow = true; |
| |
| // Work around a bug on Samsung devices where the recent tabs page does not appear after |
| // toggling the Sync quick setting. For some reason, the layout is being dropped on the |
| // flow and we need to force a root level layout to get the UI to appear. |
| view.getRootView().requestLayout(); |
| } |
| |
| @Override |
| public void onViewDetachedFromWindow(View view) { |
| // Called when the user navigates from the RecentTabsPage or switches to another tab. |
| mIsAttachedToWindow = false; |
| } |
| |
| // ExpandableListView.OnChildClickedListener |
| @Override |
| public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, |
| int childPosition, long id) { |
| return mAdapter.getGroup(groupPosition).onChildClick(childPosition); |
| } |
| |
| // ExpandableListView.OnGroupExpandedListener |
| @Override |
| public void onGroupExpand(int groupPosition) { |
| mAdapter.getGroup(groupPosition).setCollapsed(false); |
| mSnapshotContentChanged = true; |
| } |
| |
| // ExpandableListView.OnGroupCollapsedListener |
| @Override |
| public void onGroupCollapse(int groupPosition) { |
| mAdapter.getGroup(groupPosition).setCollapsed(true); |
| mSnapshotContentChanged = true; |
| } |
| |
| // RecentTabsManager.UpdatedCallback |
| @Override |
| public void onUpdated() { |
| mAdapter.notifyDataSetChanged(); |
| for (int i = 0; i < mAdapter.getGroupCount(); i++) { |
| if (mAdapter.getGroup(i).isCollapsed()) { |
| mListView.collapseGroup(i); |
| } else { |
| mListView.expandGroup(i); |
| } |
| } |
| mSnapshotContentChanged = true; |
| } |
| |
| @Override |
| public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { |
| // Would prefer to have this context menu view managed internal to RecentTabsGroupView |
| // Unfortunately, setting either onCreateContextMenuListener or onLongClickListener |
| // disables the native onClick (expand/collapse) behaviour of the group view. |
| ExpandableListView.ExpandableListContextMenuInfo info = |
| (ExpandableListView.ExpandableListContextMenuInfo) menuInfo; |
| |
| int type = ExpandableListView.getPackedPositionType(info.packedPosition); |
| int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition); |
| |
| if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) { |
| mAdapter.getGroup(groupPosition).onCreateContextMenuForGroup(menu, mActivity); |
| } else if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) { |
| int childPosition = ExpandableListView.getPackedPositionChild(info.packedPosition); |
| mAdapter.getGroup(groupPosition).onCreateContextMenuForChild(childPosition, menu, |
| mActivity); |
| } |
| } |
| |
| // InvalidationAwareThumbnailProvider |
| |
| @Override |
| public boolean shouldCaptureThumbnail() { |
| if (mView.getWidth() == 0 || mView.getHeight() == 0) return false; |
| |
| View topItem = mListView.getChildAt(0); |
| return mSnapshotContentChanged |
| || mSnapshotListPosition != mListView.getFirstVisiblePosition() |
| || mSnapshotListTop != (topItem == null ? 0 : topItem.getTop()) |
| || mView.getWidth() != mSnapshotWidth |
| || mView.getHeight() != mSnapshotHeight; |
| } |
| |
| @Override |
| public void captureThumbnail(Canvas canvas) { |
| ViewUtils.captureBitmap(mView, canvas); |
| mSnapshotContentChanged = false; |
| mSnapshotListPosition = mListView.getFirstVisiblePosition(); |
| View topItem = mListView.getChildAt(0); |
| mSnapshotListTop = topItem == null ? 0 : topItem.getTop(); |
| mSnapshotWidth = mView.getWidth(); |
| mSnapshotHeight = mView.getHeight(); |
| } |
| |
| @Override |
| public void onBottomControlsHeightChanged( |
| int bottomControlsHeight, int bottomControlsMinHeight) { |
| updateMargins(); |
| } |
| |
| @Override |
| public void onTopControlsHeightChanged(int topControlsHeight, int topControlsMinHeight) { |
| updateMargins(); |
| } |
| |
| @Override |
| public void onControlsOffsetChanged(int topOffset, int topControlsMinHeightOffset, |
| int bottomOffset, int bottomControlsMinHeightOffset, boolean needsAnimate) { |
| updateMargins(); |
| } |
| |
| private void updateMargins() { |
| final View recentTabsRoot = mView.findViewById(R.id.recent_tabs_root); |
| final int topControlsHeight = mBrowserControlsStateProvider.getTopControlsHeight(); |
| final int contentOffset = mBrowserControlsStateProvider.getContentOffset(); |
| ViewGroup.MarginLayoutParams layoutParams = |
| (ViewGroup.MarginLayoutParams) recentTabsRoot.getLayoutParams(); |
| int topMargin = layoutParams.topMargin; |
| |
| // If the top controls are at the resting position or their height is decreasing, we want to |
| // update the margin. We don't do this if the controls height is increasing because changing |
| // the margin shrinks the view height to its final value, leaving a gap at the bottom until |
| // the animation finishes. |
| if (contentOffset >= topControlsHeight) { |
| topMargin = topControlsHeight; |
| } |
| |
| // If the content offset is different from the margin, we use translationY to position the |
| // view in line with the content offset. |
| recentTabsRoot.setTranslationY(contentOffset - topMargin); |
| |
| final int bottomMargin = mBrowserControlsStateProvider.getBottomControlsHeight(); |
| if (topMargin != layoutParams.topMargin || bottomMargin != layoutParams.bottomMargin) { |
| layoutParams.topMargin = topMargin; |
| layoutParams.bottomMargin = bottomMargin; |
| recentTabsRoot.setLayoutParams(layoutParams); |
| } |
| } |
| } |