| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package org.chromium.chrome.browser.ntp; | 5 package org.chromium.chrome.browser.ntp; |
| 6 | 6 |
| 7 import android.annotation.SuppressLint; | 7 import android.annotation.SuppressLint; |
| 8 import android.content.Context; | 8 import android.content.Context; |
| 9 import android.content.res.Configuration; | 9 import android.content.res.Configuration; |
| 10 import android.content.res.Resources; | |
| 11 import android.graphics.Bitmap; | |
| 12 import android.graphics.BitmapFactory; | |
| 13 import android.graphics.Canvas; | 10 import android.graphics.Canvas; |
| 14 import android.graphics.Color; | |
| 15 import android.graphics.Point; | 11 import android.graphics.Point; |
| 16 import android.graphics.Rect; | 12 import android.graphics.Rect; |
| 17 import android.graphics.drawable.BitmapDrawable; | |
| 18 import android.support.annotation.Nullable; | 13 import android.support.annotation.Nullable; |
| 19 import android.support.v4.graphics.drawable.RoundedBitmapDrawable; | |
| 20 import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory; | |
| 21 import android.support.v7.widget.DefaultItemAnimator; | 14 import android.support.v7.widget.DefaultItemAnimator; |
| 22 import android.support.v7.widget.RecyclerView; | 15 import android.support.v7.widget.RecyclerView; |
| 23 import android.support.v7.widget.RecyclerView.AdapterDataObserver; | 16 import android.support.v7.widget.RecyclerView.AdapterDataObserver; |
| 24 import android.support.v7.widget.RecyclerView.ViewHolder; | 17 import android.support.v7.widget.RecyclerView.ViewHolder; |
| 25 import android.text.Editable; | 18 import android.text.Editable; |
| 26 import android.text.TextUtils; | |
| 27 import android.text.TextWatcher; | 19 import android.text.TextWatcher; |
| 28 import android.util.AttributeSet; | 20 import android.util.AttributeSet; |
| 29 import android.view.LayoutInflater; | 21 import android.view.LayoutInflater; |
| 30 import android.view.MotionEvent; | 22 import android.view.MotionEvent; |
| 31 import android.view.View; | 23 import android.view.View; |
| 32 import android.view.View.OnLayoutChangeListener; | 24 import android.view.View.OnLayoutChangeListener; |
| 33 import android.view.ViewGroup; | 25 import android.view.ViewGroup; |
| 34 import android.view.ViewStub; | 26 import android.view.ViewStub; |
| 35 import android.widget.FrameLayout; | 27 import android.widget.FrameLayout; |
| 36 import android.widget.ImageView; | 28 import android.widget.ImageView; |
| 37 import android.widget.TextView; | 29 import android.widget.TextView; |
| 38 | 30 |
| 39 import org.chromium.base.ApiCompatibilityUtils; | |
| 40 import org.chromium.base.Callback; | |
| 41 import org.chromium.base.Log; | |
| 42 import org.chromium.base.TraceEvent; | 31 import org.chromium.base.TraceEvent; |
| 43 import org.chromium.base.VisibleForTesting; | 32 import org.chromium.base.VisibleForTesting; |
| 44 import org.chromium.chrome.R; | 33 import org.chromium.chrome.R; |
| 45 import org.chromium.chrome.browser.ChromeActivity; | 34 import org.chromium.chrome.browser.ChromeActivity; |
| 46 import org.chromium.chrome.browser.favicon.LargeIconBridge.LargeIconCallback; | |
| 47 import org.chromium.chrome.browser.ntp.LogoBridge.Logo; | 35 import org.chromium.chrome.browser.ntp.LogoBridge.Logo; |
| 48 import org.chromium.chrome.browser.ntp.LogoBridge.LogoObserver; | 36 import org.chromium.chrome.browser.ntp.LogoBridge.LogoObserver; |
| 49 import org.chromium.chrome.browser.ntp.MostVisitedItem.MostVisitedItemManager; | |
| 50 import org.chromium.chrome.browser.ntp.NewTabPage.DestructionObserver; | 37 import org.chromium.chrome.browser.ntp.NewTabPage.DestructionObserver; |
| 51 import org.chromium.chrome.browser.ntp.NewTabPage.OnSearchBoxScrollListener; | 38 import org.chromium.chrome.browser.ntp.NewTabPage.OnSearchBoxScrollListener; |
| 52 import org.chromium.chrome.browser.ntp.cards.CardsVariationParameters; | 39 import org.chromium.chrome.browser.ntp.cards.CardsVariationParameters; |
| 53 import org.chromium.chrome.browser.ntp.cards.NewTabPageAdapter; | 40 import org.chromium.chrome.browser.ntp.cards.NewTabPageAdapter; |
| 54 import org.chromium.chrome.browser.ntp.cards.NewTabPageRecyclerView; | 41 import org.chromium.chrome.browser.ntp.cards.NewTabPageRecyclerView; |
| 55 import org.chromium.chrome.browser.offlinepages.OfflinePageBridge; | 42 import org.chromium.chrome.browser.offlinepages.OfflinePageBridge; |
| 56 import org.chromium.chrome.browser.profiles.MostVisitedSites.MostVisitedURLsObse
rver; | |
| 57 import org.chromium.chrome.browser.profiles.Profile; | 43 import org.chromium.chrome.browser.profiles.Profile; |
| 58 import org.chromium.chrome.browser.suggestions.SuggestionsUiDelegate; | 44 import org.chromium.chrome.browser.suggestions.SuggestionsUiDelegate; |
| 45 import org.chromium.chrome.browser.suggestions.Tile; |
| 46 import org.chromium.chrome.browser.suggestions.TileGroup; |
| 59 import org.chromium.chrome.browser.tab.Tab; | 47 import org.chromium.chrome.browser.tab.Tab; |
| 60 import org.chromium.chrome.browser.util.MathUtils; | 48 import org.chromium.chrome.browser.util.MathUtils; |
| 61 import org.chromium.chrome.browser.util.ViewUtils; | 49 import org.chromium.chrome.browser.util.ViewUtils; |
| 62 import org.chromium.chrome.browser.widget.RoundedIconGenerator; | |
| 63 import org.chromium.chrome.browser.widget.displaystyle.UiConfig; | 50 import org.chromium.chrome.browser.widget.displaystyle.UiConfig; |
| 64 import org.chromium.ui.base.DeviceFormFactor; | 51 import org.chromium.ui.base.DeviceFormFactor; |
| 65 | 52 |
| 66 import java.util.Arrays; | |
| 67 import java.util.HashSet; | |
| 68 import java.util.Set; | |
| 69 | |
| 70 import jp.tomorrowkey.android.gifplayer.BaseGifImage; | 53 import jp.tomorrowkey.android.gifplayer.BaseGifImage; |
| 71 | 54 |
| 72 /** | 55 /** |
| 73 * The native new tab page, represented by some basic data such as title and url
, and an Android | 56 * The native new tab page, represented by some basic data such as title and url
, and an Android |
| 74 * View that displays the page. | 57 * View that displays the page. |
| 75 */ | 58 */ |
| 76 public class NewTabPageView extends FrameLayout | 59 public class NewTabPageView |
| 77 implements MostVisitedURLsObserver, OnLayoutChangeListener { | 60 extends FrameLayout implements OnLayoutChangeListener, TileGroup.Observe
r { |
| 61 private static final String TAG = "NewTabPageView"; |
| 78 | 62 |
| 79 private static final long SNAP_SCROLL_DELAY_MS = 30; | 63 private static final long SNAP_SCROLL_DELAY_MS = 30; |
| 80 private static final String TAG = "NewTabPageView"; | 64 |
| 65 private static final int MAX_TILES = 12; |
| 66 private static final int MAX_TILE_ROWS_WITH_LOGO = 2; |
| 67 private static final int MAX_TILE_ROWS_WITHOUT_LOGO = 3; |
| 81 | 68 |
| 82 private NewTabPageRecyclerView mRecyclerView; | 69 private NewTabPageRecyclerView mRecyclerView; |
| 83 | 70 |
| 84 private NewTabPageLayout mNewTabPageLayout; | 71 private NewTabPageLayout mNewTabPageLayout; |
| 85 private LogoView mSearchProviderLogoView; | 72 private LogoView mSearchProviderLogoView; |
| 86 private View mSearchBoxView; | 73 private View mSearchBoxView; |
| 87 private ImageView mVoiceSearchButton; | 74 private ImageView mVoiceSearchButton; |
| 88 private MostVisitedLayout mMostVisitedLayout; | 75 private MostVisitedLayout mMostVisitedLayout; |
| 89 private View mMostVisitedPlaceholder; | 76 private View mMostVisitedPlaceholder; |
| 90 private View mNoSearchLogoSpacer; | 77 private View mNoSearchLogoSpacer; |
| 91 | 78 |
| 92 private OnSearchBoxScrollListener mSearchBoxScrollListener; | 79 private OnSearchBoxScrollListener mSearchBoxScrollListener; |
| 93 | 80 |
| 94 private ChromeActivity mActivity; | 81 private ChromeActivity mActivity; |
| 95 private NewTabPageManager mManager; | 82 private NewTabPageManager mManager; |
| 83 private TileGroup.Delegate mTileGroupDelegate; |
| 84 private TileGroup mTileGroup; |
| 96 private UiConfig mUiConfig; | 85 private UiConfig mUiConfig; |
| 97 private MostVisitedDesign mMostVisitedDesign; | |
| 98 private MostVisitedItem[] mMostVisitedItems; | |
| 99 private boolean mFirstShow = true; | 86 private boolean mFirstShow = true; |
| 100 private boolean mSearchProviderHasLogo = true; | 87 private boolean mSearchProviderHasLogo = true; |
| 101 private boolean mHasReceivedMostVisitedSites; | |
| 102 private boolean mPendingSnapScroll; | 88 private boolean mPendingSnapScroll; |
| 103 | 89 |
| 104 /** | 90 /** |
| 105 * The number of asynchronous tasks that need to complete before the page is
done loading. | 91 * The number of asynchronous tasks that need to complete before the page is
done loading. |
| 106 * This starts at one to track when the view is finished attaching to the wi
ndow. | 92 * This starts at one to track when the view is finished attaching to the wi
ndow. |
| 107 */ | 93 */ |
| 108 private int mPendingLoadTasks = 1; | 94 private int mPendingLoadTasks = 1; |
| 109 private boolean mLoadHasCompleted; | 95 private boolean mLoadHasCompleted; |
| 110 | 96 |
| 111 private float mUrlFocusChangePercent; | 97 private float mUrlFocusChangePercent; |
| 112 private boolean mDisableUrlFocusChangeAnimations; | 98 private boolean mDisableUrlFocusChangeAnimations; |
| 113 | 99 |
| 114 /** Flag used to request some layout changes after the next layout pass is c
ompleted. */ | 100 /** Flag used to request some layout changes after the next layout pass is c
ompleted. */ |
| 115 private boolean mTileCountChanged; | 101 private boolean mTileCountChanged; |
| 116 private boolean mSnapshotMostVisitedChanged; | 102 private boolean mSnapshotMostVisitedChanged; |
| 117 private boolean mNewTabPageRecyclerViewChanged; | 103 private boolean mNewTabPageRecyclerViewChanged; |
| 118 private int mSnapshotWidth; | 104 private int mSnapshotWidth; |
| 119 private int mSnapshotHeight; | 105 private int mSnapshotHeight; |
| 120 private int mSnapshotScrollY; | 106 private int mSnapshotScrollY; |
| 121 private ContextMenuManager mContextMenuManager; | 107 private ContextMenuManager mContextMenuManager; |
| 122 | 108 |
| 123 /** | 109 /** |
| 124 * Manages the view interaction with the rest of the system. | 110 * Manages the view interaction with the rest of the system. |
| 125 */ | 111 */ |
| 126 public interface NewTabPageManager extends MostVisitedItemManager, Suggestio
nsUiDelegate { | 112 public interface NewTabPageManager extends SuggestionsUiDelegate { |
| 127 /** @return Whether the location bar is shown in the NTP. */ | 113 /** @return Whether the location bar is shown in the NTP. */ |
| 128 boolean isLocationBarShownInNTP(); | 114 boolean isLocationBarShownInNTP(); |
| 129 | 115 |
| 130 /** @return Whether voice search is enabled and the microphone should be
shown. */ | 116 /** @return Whether voice search is enabled and the microphone should be
shown. */ |
| 131 boolean isVoiceSearchEnabled(); | 117 boolean isVoiceSearchEnabled(); |
| 132 | 118 |
| 133 /** @return Whether the omnibox 'Search or type URL' text should be show
n. */ | 119 /** @return Whether the omnibox 'Search or type URL' text should be show
n. */ |
| 134 boolean isFakeOmniboxTextEnabledTablet(); | 120 boolean isFakeOmniboxTextEnabledTablet(); |
| 135 | 121 |
| 136 /** | 122 /** |
| 137 * Animates the search box up into the omnibox and bring up the keyboard
. | 123 * Animates the search box up into the omnibox and bring up the keyboard
. |
| 138 * @param beginVoiceSearch Whether to begin a voice search. | 124 * @param beginVoiceSearch Whether to begin a voice search. |
| 139 * @param pastedText Text to paste in the omnibox after it's been focuse
d. May be null. | 125 * @param pastedText Text to paste in the omnibox after it's been focuse
d. May be null. |
| 140 */ | 126 */ |
| 141 void focusSearchBox(boolean beginVoiceSearch, String pastedText); | 127 void focusSearchBox(boolean beginVoiceSearch, String pastedText); |
| 142 | 128 |
| 143 /** | 129 /** |
| 144 * Gets the list of most visited sites. | |
| 145 * @param observer The observer to be notified with the list of sites. | |
| 146 * @param numResults The maximum number of sites to retrieve. | |
| 147 */ | |
| 148 void setMostVisitedURLsObserver(MostVisitedURLsObserver observer, int nu
mResults); | |
| 149 | |
| 150 /** | |
| 151 * Called when the user clicks on the logo. | 130 * Called when the user clicks on the logo. |
| 152 * @param isAnimatedLogoShowing Whether the animated GIF logo is playing
. | 131 * @param isAnimatedLogoShowing Whether the animated GIF logo is playing
. |
| 153 */ | 132 */ |
| 154 void onLogoClicked(boolean isAnimatedLogoShowing); | 133 void onLogoClicked(boolean isAnimatedLogoShowing); |
| 155 | 134 |
| 156 /** | 135 /** |
| 157 * Gets the default search provider's logo and calls logoObserver with t
he result. | 136 * Gets the default search provider's logo and calls logoObserver with t
he result. |
| 158 * @param logoObserver The callback to notify when the logo is available
. | 137 * @param logoObserver The callback to notify when the logo is available
. |
| 159 */ | 138 */ |
| 160 void getSearchProviderLogo(LogoObserver logoObserver); | 139 void getSearchProviderLogo(LogoObserver logoObserver); |
| 161 | 140 |
| 162 /** | 141 /** |
| 163 * Called when the NTP has completely finished loading (all views will b
e inflated | |
| 164 * and any dependent resources will have been loaded). | |
| 165 * @param mostVisitedItems The MostVisitedItem shown on the NTP. Used to
record metrics. | |
| 166 */ | |
| 167 void onLoadingComplete(MostVisitedItem[] mostVisitedItems); | |
| 168 | |
| 169 /** | |
| 170 * @return whether the {@link NewTabPage} associated with this manager i
s the current page | 142 * @return whether the {@link NewTabPage} associated with this manager i
s the current page |
| 171 * displayed to the user. | 143 * displayed to the user. |
| 172 */ | 144 */ |
| 173 boolean isCurrentPage(); | 145 boolean isCurrentPage(); |
| 174 | 146 |
| 175 /** | 147 /** |
| 176 * @return The context menu manager. Will be {@code null} if the {@link
NewTabPageView} is | 148 * @return The context menu manager. Will be {@code null} if the {@link
NewTabPageView} is |
| 177 * not done initialising. | 149 * not done initialising. |
| 178 */ | 150 */ |
| 179 @Nullable | 151 @Nullable |
| (...skipping 10 matching lines...) Expand all Loading... |
| 190 /** | 162 /** |
| 191 * Initializes the NTP. This must be called immediately after inflation, bef
ore this object is | 163 * Initializes the NTP. This must be called immediately after inflation, bef
ore this object is |
| 192 * used in any other way. | 164 * used in any other way. |
| 193 * | 165 * |
| 194 * @param manager NewTabPageManager used to perform various actions when the
user interacts | 166 * @param manager NewTabPageManager used to perform various actions when the
user interacts |
| 195 * with the page. | 167 * with the page. |
| 196 * @param tab The Tab that is showing this new tab page. | 168 * @param tab The Tab that is showing this new tab page. |
| 197 * @param searchProviderHasLogo Whether the search provider has a logo. | 169 * @param searchProviderHasLogo Whether the search provider has a logo. |
| 198 * @param scrollPosition The adapter scroll position to initialize to. | 170 * @param scrollPosition The adapter scroll position to initialize to. |
| 199 */ | 171 */ |
| 200 public void initialize( | 172 public void initialize(NewTabPageManager manager, Tab tab, TileGroup.Delegat
e tileGroupDelegate, |
| 201 NewTabPageManager manager, Tab tab, boolean searchProviderHasLogo, i
nt scrollPosition) { | 173 boolean searchProviderHasLogo, int scrollPosition) { |
| 202 TraceEvent.begin(TAG + ".initialize()"); | 174 TraceEvent.begin(TAG + ".initialize()"); |
| 203 mActivity = tab.getActivity(); | 175 mActivity = tab.getActivity(); |
| 204 mManager = manager; | 176 mManager = manager; |
| 177 mTileGroupDelegate = tileGroupDelegate; |
| 205 mUiConfig = new UiConfig(this); | 178 mUiConfig = new UiConfig(this); |
| 206 | 179 |
| 207 assert manager.getSuggestionsSource() != null; | 180 assert manager.getSuggestionsSource() != null; |
| 208 | 181 |
| 209 mRecyclerView = (NewTabPageRecyclerView) findViewById(R.id.new_tab_page_
recycler_view); | 182 mRecyclerView = (NewTabPageRecyclerView) findViewById(R.id.new_tab_page_
recycler_view); |
| 210 // Don't attach now, the recyclerView itself will determine when to do i
t. | 183 // Don't attach now, the recyclerView itself will determine when to do i
t. |
| 211 mNewTabPageLayout = | 184 mNewTabPageLayout = |
| 212 (NewTabPageLayout) LayoutInflater.from(getContext()) | 185 (NewTabPageLayout) LayoutInflater.from(getContext()) |
| 213 .inflate(R.layout.new_tab_page_layout, mRecyclerView, fa
lse); | 186 .inflate(R.layout.new_tab_page_layout, mRecyclerView, fa
lse); |
| 214 mRecyclerView.setAboveTheFoldView(mNewTabPageLayout); | 187 mRecyclerView.setAboveTheFoldView(mNewTabPageLayout); |
| (...skipping 17 matching lines...) Expand all Loading... |
| 232 mContextMenuManager = | 205 mContextMenuManager = |
| 233 new ContextMenuManager(mActivity, mManager.getNavigationDelegate
(), mRecyclerView); | 206 new ContextMenuManager(mActivity, mManager.getNavigationDelegate
(), mRecyclerView); |
| 234 mActivity.getWindowAndroid().addContextMenuCloseListener(mContextMenuMan
ager); | 207 mActivity.getWindowAndroid().addContextMenuCloseListener(mContextMenuMan
ager); |
| 235 manager.addDestructionObserver(new DestructionObserver() { | 208 manager.addDestructionObserver(new DestructionObserver() { |
| 236 @Override | 209 @Override |
| 237 public void onDestroy() { | 210 public void onDestroy() { |
| 238 mActivity.getWindowAndroid().removeContextMenuCloseListener(mCon
textMenuManager); | 211 mActivity.getWindowAndroid().removeContextMenuCloseListener(mCon
textMenuManager); |
| 239 } | 212 } |
| 240 }); | 213 }); |
| 241 | 214 |
| 242 mMostVisitedDesign = new MostVisitedDesign(getContext()); | |
| 243 mMostVisitedLayout = | 215 mMostVisitedLayout = |
| 244 (MostVisitedLayout) mNewTabPageLayout.findViewById(R.id.most_vis
ited_layout); | 216 (MostVisitedLayout) mNewTabPageLayout.findViewById(R.id.most_vis
ited_layout); |
| 245 mMostVisitedDesign.initMostVisitedLayout(searchProviderHasLogo); | 217 mMostVisitedLayout.setMaxRows( |
| 218 searchProviderHasLogo ? MAX_TILE_ROWS_WITH_LOGO : MAX_TILE_ROWS_
WITHOUT_LOGO); |
| 219 mTileGroup = new TileGroup( |
| 220 mManager, mContextMenuManager, mTileGroupDelegate, /* observer =
*/ this); |
| 246 | 221 |
| 247 mSearchProviderLogoView = | 222 mSearchProviderLogoView = |
| 248 (LogoView) mNewTabPageLayout.findViewById(R.id.search_provider_l
ogo); | 223 (LogoView) mNewTabPageLayout.findViewById(R.id.search_provider_l
ogo); |
| 249 mSearchBoxView = mNewTabPageLayout.findViewById(R.id.search_box); | 224 mSearchBoxView = mNewTabPageLayout.findViewById(R.id.search_box); |
| 250 mNoSearchLogoSpacer = mNewTabPageLayout.findViewById(R.id.no_search_logo
_spacer); | 225 mNoSearchLogoSpacer = mNewTabPageLayout.findViewById(R.id.no_search_logo
_spacer); |
| 251 | 226 |
| 252 initializeSearchBoxTextView(); | 227 initializeSearchBoxTextView(); |
| 253 initializeVoiceSearchButton(); | 228 initializeVoiceSearchButton(); |
| 254 | 229 |
| 255 mNewTabPageLayout.addOnLayoutChangeListener(this); | 230 mNewTabPageLayout.addOnLayoutChangeListener(this); |
| 256 setSearchProviderHasLogo(searchProviderHasLogo); | 231 setSearchProviderHasLogo(searchProviderHasLogo); |
| 257 | 232 |
| 258 mPendingLoadTasks++; | 233 mPendingLoadTasks++; |
| 259 mManager.setMostVisitedURLsObserver( | 234 mTileGroup.startObserving(MAX_TILES); |
| 260 this, mMostVisitedDesign.getNumberOfTiles(searchProviderHasLogo)
); | |
| 261 | 235 |
| 262 // Set up snippets | 236 // Set up snippets |
| 263 NewTabPageAdapter newTabPageAdapter = new NewTabPageAdapter(mManager, mN
ewTabPageLayout, | 237 NewTabPageAdapter newTabPageAdapter = new NewTabPageAdapter(mManager, mN
ewTabPageLayout, |
| 264 mUiConfig, OfflinePageBridge.getForProfile(Profile.getLastUsedPr
ofile()), | 238 mUiConfig, OfflinePageBridge.getForProfile(Profile.getLastUsedPr
ofile()), |
| 265 mContextMenuManager); | 239 mContextMenuManager, /* tileGroupDelegate = */ null); |
| 266 mRecyclerView.setAdapter(newTabPageAdapter); | 240 mRecyclerView.setAdapter(newTabPageAdapter); |
| 267 | 241 |
| 268 int scrollOffset; | 242 int scrollOffset; |
| 269 if (CardsVariationParameters.isScrollBelowTheFoldEnabled()) { | 243 if (CardsVariationParameters.isScrollBelowTheFoldEnabled()) { |
| 270 scrollPosition = newTabPageAdapter.getFirstHeaderPosition(); | 244 scrollPosition = newTabPageAdapter.getFirstHeaderPosition(); |
| 271 scrollOffset = getResources().getDimensionPixelSize(R.dimen.ntp_sear
ch_box_height); | 245 scrollOffset = getResources().getDimensionPixelSize(R.dimen.ntp_sear
ch_box_height); |
| 272 } else { | 246 } else { |
| 273 scrollOffset = 0; | 247 scrollOffset = 0; |
| 274 } | 248 } |
| 275 mRecyclerView.getLinearLayoutManager().scrollToPositionWithOffset( | 249 mRecyclerView.getLinearLayoutManager().scrollToPositionWithOffset( |
| (...skipping 198 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 474 * is complete. | 448 * is complete. |
| 475 */ | 449 */ |
| 476 private void loadTaskCompleted() { | 450 private void loadTaskCompleted() { |
| 477 assert mPendingLoadTasks > 0; | 451 assert mPendingLoadTasks > 0; |
| 478 mPendingLoadTasks--; | 452 mPendingLoadTasks--; |
| 479 if (mPendingLoadTasks == 0) { | 453 if (mPendingLoadTasks == 0) { |
| 480 if (mLoadHasCompleted) { | 454 if (mLoadHasCompleted) { |
| 481 assert false; | 455 assert false; |
| 482 } else { | 456 } else { |
| 483 mLoadHasCompleted = true; | 457 mLoadHasCompleted = true; |
| 484 mManager.onLoadingComplete(mMostVisitedItems); | 458 mTileGroupDelegate.onLoadingComplete(mTileGroup.getTiles()); |
| 485 // Load the logo after everything else is finished, since it's l
ower priority. | 459 // Load the logo after everything else is finished, since it's l
ower priority. |
| 486 loadSearchProviderLogo(); | 460 loadSearchProviderLogo(); |
| 487 } | 461 } |
| 488 } | 462 } |
| 489 } | 463 } |
| 490 | 464 |
| 491 /** | 465 /** |
| 492 * Loads the search provider logo (e.g. Google doodle), if any. | 466 * Loads the search provider logo (e.g. Google doodle), if any. |
| 493 */ | 467 */ |
| 494 private void loadSearchProviderLogo() { | 468 private void loadSearchProviderLogo() { |
| (...skipping 10 matching lines...) Expand all Loading... |
| 505 | 479 |
| 506 /** | 480 /** |
| 507 * Changes the layout depending on whether the selected search provider (e.g
. Google, Bing) | 481 * Changes the layout depending on whether the selected search provider (e.g
. Google, Bing) |
| 508 * has a logo. | 482 * has a logo. |
| 509 * @param hasLogo Whether the search provider has a logo. | 483 * @param hasLogo Whether the search provider has a logo. |
| 510 */ | 484 */ |
| 511 public void setSearchProviderHasLogo(boolean hasLogo) { | 485 public void setSearchProviderHasLogo(boolean hasLogo) { |
| 512 if (hasLogo == mSearchProviderHasLogo) return; | 486 if (hasLogo == mSearchProviderHasLogo) return; |
| 513 mSearchProviderHasLogo = hasLogo; | 487 mSearchProviderHasLogo = hasLogo; |
| 514 | 488 |
| 515 mMostVisitedDesign.setSearchProviderHasLogo(mMostVisitedLayout, hasLogo)
; | 489 // Set a bit more top padding if there is no logo. |
| 490 int paddingTop = getResources().getDimensionPixelSize(hasLogo |
| 491 ? R.dimen.most_visited_layout_padding_top |
| 492 : R.dimen.most_visited_layout_no_logo_padding_top); |
| 493 mMostVisitedLayout.setPadding(0, paddingTop, 0, mMostVisitedLayout.getPa
ddingBottom()); |
| 516 | 494 |
| 517 // Hide or show all the views above the Most Visited items. | 495 // Hide or show all the views above the Most Visited items. |
| 518 int visibility = hasLogo ? View.VISIBLE : View.GONE; | 496 int visibility = hasLogo ? View.VISIBLE : View.GONE; |
| 519 int childCount = mNewTabPageLayout.getChildCount(); | 497 int childCount = mNewTabPageLayout.getChildCount(); |
| 520 for (int i = 0; i < childCount; i++) { | 498 for (int i = 0; i < childCount; i++) { |
| 521 View child = mNewTabPageLayout.getChildAt(i); | 499 View child = mNewTabPageLayout.getChildAt(i); |
| 522 if (child == mMostVisitedLayout) break; | 500 if (child == mMostVisitedLayout) break; |
| 523 // Don't change the visibility of a ViewStub as that will automagica
lly inflate it. | 501 // Don't change the visibility of a ViewStub as that will automagica
lly inflate it. |
| 524 if (child instanceof ViewStub) continue; | 502 if (child instanceof ViewStub) continue; |
| 525 child.setVisibility(visibility); | 503 child.setVisibility(visibility); |
| (...skipping 218 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 744 onUrlFocusAnimationChanged(); | 722 onUrlFocusAnimationChanged(); |
| 745 updateSearchBoxOnScroll(); | 723 updateSearchBoxOnScroll(); |
| 746 | 724 |
| 747 mRecyclerView.updatePeekingCardAndHeader(); | 725 mRecyclerView.updatePeekingCardAndHeader(); |
| 748 // The positioning of elements may have been changed (since the elements
expand to fill | 726 // The positioning of elements may have been changed (since the elements
expand to fill |
| 749 // the available vertical space), so adjust the scroll. | 727 // the available vertical space), so adjust the scroll. |
| 750 mRecyclerView.snapScroll(mSearchBoxView, | 728 mRecyclerView.snapScroll(mSearchBoxView, |
| 751 mRecyclerView.computeVerticalScrollOffset(), getHeight()); | 729 mRecyclerView.computeVerticalScrollOffset(), getHeight()); |
| 752 } | 730 } |
| 753 | 731 |
| 754 // MostVisitedURLsObserver implementation | |
| 755 | |
| 756 @Override | |
| 757 public void onMostVisitedURLsAvailable(final String[] titles, final String[]
urls, | |
| 758 final String[] whitelistIconPaths, final int[] sources) { | |
| 759 Set<String> urlSet = new HashSet<>(Arrays.asList(urls)); | |
| 760 | |
| 761 // If no Most Visited items have been built yet, this is the initial loa
d. Build the Most | |
| 762 // Visited items immediately so the layout is stable during initial rend
ering. They can be | |
| 763 // replaced later if there are offline urls, but that will not affect th
e layout widths and | |
| 764 // heights. A stable layout enables reliable scroll position initializat
ion. | |
| 765 if (!mHasReceivedMostVisitedSites) { | |
| 766 buildMostVisitedItems(titles, urls, whitelistIconPaths, null, source
s); | |
| 767 } | |
| 768 | |
| 769 // TODO(https://crbug.com/607573): We should show offline-available cont
ent in a nonblocking | |
| 770 // way so that responsiveness of the NTP does not depend on ready availa
bility of offline | |
| 771 // pages. | |
| 772 mManager.getUrlsAvailableOffline(urlSet, new Callback<Set<String>>() { | |
| 773 @Override | |
| 774 public void onResult(Set<String> offlineUrls) { | |
| 775 buildMostVisitedItems(titles, urls, whitelistIconPaths, offlineU
rls, sources); | |
| 776 } | |
| 777 }); | |
| 778 } | |
| 779 | |
| 780 private void buildMostVisitedItems(final String[] titles, final String[] url
s, | |
| 781 final String[] whitelistIconPaths, @Nullable final Set<String> offli
neUrls, | |
| 782 final int[] sources) { | |
| 783 mMostVisitedLayout.removeAllViews(); | |
| 784 | |
| 785 MostVisitedItem[] oldItems = mMostVisitedItems; | |
| 786 int oldItemCount = oldItems == null ? 0 : oldItems.length; | |
| 787 mMostVisitedItems = new MostVisitedItem[titles.length]; | |
| 788 | |
| 789 final boolean isInitialLoad = !mHasReceivedMostVisitedSites; | |
| 790 LayoutInflater inflater = LayoutInflater.from(getContext()); | |
| 791 | |
| 792 // Add the most visited items to the page. | |
| 793 for (int i = 0; i < titles.length; i++) { | |
| 794 final String url = urls[i]; | |
| 795 final String title = titles[i]; | |
| 796 final String whitelistIconPath = whitelistIconPaths[i]; | |
| 797 final int source = sources[i]; | |
| 798 | |
| 799 boolean offlineAvailable = offlineUrls != null && offlineUrls.contai
ns(url); | |
| 800 | |
| 801 // Look for an existing item to reuse. | |
| 802 MostVisitedItem item = null; | |
| 803 for (int j = 0; j < oldItemCount; j++) { | |
| 804 MostVisitedItem oldItem = oldItems[j]; | |
| 805 if (oldItem != null && TextUtils.equals(url, oldItem.getUrl()) | |
| 806 && TextUtils.equals(title, oldItem.getTitle()) | |
| 807 && offlineAvailable == oldItem.isOfflineAvailable() | |
| 808 && whitelistIconPath.equals(oldItem.getWhitelistIconPath
())) { | |
| 809 item = oldItem; | |
| 810 item.setIndex(i); | |
| 811 oldItems[j] = null; | |
| 812 break; | |
| 813 } | |
| 814 } | |
| 815 | |
| 816 // If nothing can be reused, create a new item. | |
| 817 if (item == null) { | |
| 818 item = new MostVisitedItem(mManager, title, url, whitelistIconPa
th, | |
| 819 offlineAvailable, i, source); | |
| 820 View view = | |
| 821 mMostVisitedDesign.createMostVisitedItemView(inflater, i
tem, isInitialLoad); | |
| 822 item.initView(view); | |
| 823 } | |
| 824 | |
| 825 mMostVisitedItems[i] = item; | |
| 826 mMostVisitedLayout.addView(item.getView()); | |
| 827 } | |
| 828 | |
| 829 mHasReceivedMostVisitedSites = true; | |
| 830 updateMostVisitedPlaceholderVisibility(); | |
| 831 | |
| 832 if (mUrlFocusChangePercent == 1f && oldItemCount != mMostVisitedItems.le
ngth) { | |
| 833 // If the number of NTP Tile rows change while the URL bar is focuse
d, the icons' | |
| 834 // position will be wrong. Schedule the translation to be updated. | |
| 835 mTileCountChanged = true; | |
| 836 } | |
| 837 | |
| 838 if (isInitialLoad) { | |
| 839 loadTaskCompleted(); | |
| 840 // The page contents are initially hidden; otherwise they'll be draw
n centered on the | |
| 841 // page before the most visited sites are available and then jump up
wards to make space | |
| 842 // once the most visited sites are available. | |
| 843 mNewTabPageLayout.setVisibility(View.VISIBLE); | |
| 844 } | |
| 845 mSnapshotMostVisitedChanged = true; | |
| 846 } | |
| 847 | |
| 848 @Override | |
| 849 public void onIconMadeAvailable(String siteUrl) { | |
| 850 mMostVisitedDesign.onIconUpdated(siteUrl); | |
| 851 } | |
| 852 | |
| 853 /** | 732 /** |
| 854 * Shows the most visited placeholder ("Nothing to see here") if there are n
o most visited | 733 * Shows the most visited placeholder ("Nothing to see here") if there are n
o most visited |
| 855 * items and there is no search provider logo. | 734 * items and there is no search provider logo. |
| 856 */ | 735 */ |
| 857 private void updateMostVisitedPlaceholderVisibility() { | 736 private void updateMostVisitedPlaceholderVisibility() { |
| 858 boolean showPlaceholder = mHasReceivedMostVisitedSites | 737 boolean showPlaceholder = mTileGroup.hasReceivedData() |
| 859 && mMostVisitedLayout.getChildCount() == 0 | 738 && mMostVisitedLayout.getChildCount() == 0 && !mSearchProviderHa
sLogo; |
| 860 && !mSearchProviderHasLogo; | |
| 861 | 739 |
| 862 mNoSearchLogoSpacer.setVisibility( | 740 mNoSearchLogoSpacer.setVisibility( |
| 863 (mSearchProviderHasLogo || showPlaceholder) ? View.GONE : View.I
NVISIBLE); | 741 (mSearchProviderHasLogo || showPlaceholder) ? View.GONE : View.I
NVISIBLE); |
| 864 | 742 |
| 865 if (showPlaceholder) { | 743 if (showPlaceholder) { |
| 866 if (mMostVisitedPlaceholder == null) { | 744 if (mMostVisitedPlaceholder == null) { |
| 867 ViewStub mostVisitedPlaceholderStub = (ViewStub) mNewTabPageLayo
ut | 745 ViewStub mostVisitedPlaceholderStub = (ViewStub) mNewTabPageLayo
ut |
| 868 .findViewById(R.id.most_visited_placeholder_stub); | 746 .findViewById(R.id.most_visited_placeholder_stub); |
| 869 | 747 |
| 870 mMostVisitedPlaceholder = mostVisitedPlaceholderStub.inflate(); | 748 mMostVisitedPlaceholder = mostVisitedPlaceholderStub.inflate(); |
| 871 } | 749 } |
| 872 mMostVisitedLayout.setVisibility(GONE); | 750 mMostVisitedLayout.setVisibility(GONE); |
| 873 mMostVisitedPlaceholder.setVisibility(VISIBLE); | 751 mMostVisitedPlaceholder.setVisibility(VISIBLE); |
| 874 } else if (mMostVisitedPlaceholder != null) { | 752 } else if (mMostVisitedPlaceholder != null) { |
| 875 mMostVisitedLayout.setVisibility(VISIBLE); | 753 mMostVisitedLayout.setVisibility(VISIBLE); |
| 876 mMostVisitedPlaceholder.setVisibility(GONE); | 754 mMostVisitedPlaceholder.setVisibility(GONE); |
| 877 } | 755 } |
| 878 } | 756 } |
| 879 | 757 |
| 880 /** | |
| 881 * The design for most visited tiles: each tile shows a large icon and the s
ite's title. | |
| 882 */ | |
| 883 private class MostVisitedDesign { | |
| 884 | |
| 885 private static final int NUM_TILES = 8; | |
| 886 private static final int NUM_TILES_NO_LOGO = 12; | |
| 887 private static final int MAX_ROWS = 2; | |
| 888 private static final int MAX_ROWS_NO_LOGO = 3; | |
| 889 | |
| 890 private static final int ICON_CORNER_RADIUS_DP = 4; | |
| 891 private static final int ICON_TEXT_SIZE_DP = 20; | |
| 892 private static final int ICON_MIN_SIZE_PX = 48; | |
| 893 | |
| 894 private int mMinIconSize; | |
| 895 private int mDesiredIconSize; | |
| 896 private RoundedIconGenerator mIconGenerator; | |
| 897 | |
| 898 MostVisitedDesign(Context context) { | |
| 899 Resources res = context.getResources(); | |
| 900 mDesiredIconSize = res.getDimensionPixelSize(R.dimen.most_visited_ic
on_size); | |
| 901 // On ldpi devices, mDesiredIconSize could be even smaller than ICON
_MIN_SIZE_PX. | |
| 902 mMinIconSize = Math.min(mDesiredIconSize, ICON_MIN_SIZE_PX); | |
| 903 int desiredIconSizeDp = Math.round( | |
| 904 mDesiredIconSize / res.getDisplayMetrics().density); | |
| 905 int iconColor = ApiCompatibilityUtils.getColor( | |
| 906 getResources(), R.color.default_favicon_background_color); | |
| 907 mIconGenerator = new RoundedIconGenerator(context, desiredIconSizeDp
, desiredIconSizeDp, | |
| 908 ICON_CORNER_RADIUS_DP, iconColor, ICON_TEXT_SIZE_DP); | |
| 909 } | |
| 910 | |
| 911 public int getNumberOfTiles(boolean searchProviderHasLogo) { | |
| 912 return searchProviderHasLogo ? NUM_TILES : NUM_TILES_NO_LOGO; | |
| 913 } | |
| 914 | |
| 915 public void initMostVisitedLayout(boolean searchProviderHasLogo) { | |
| 916 mMostVisitedLayout.setMaxRows(searchProviderHasLogo ? MAX_ROWS : MAX
_ROWS_NO_LOGO); | |
| 917 } | |
| 918 | |
| 919 public void setSearchProviderHasLogo(View mostVisitedLayout, boolean has
Logo) { | |
| 920 int paddingTop = getResources().getDimensionPixelSize(hasLogo | |
| 921 ? R.dimen.most_visited_layout_padding_top | |
| 922 : R.dimen.most_visited_layout_no_logo_padding_top); | |
| 923 mostVisitedLayout.setPadding(0, paddingTop, 0, mMostVisitedLayout.ge
tPaddingBottom()); | |
| 924 } | |
| 925 | |
| 926 class LargeIconCallbackImpl implements LargeIconCallback { | |
| 927 private MostVisitedItem mItem; | |
| 928 private MostVisitedItemView mItemView; | |
| 929 private boolean mIsInitialLoad; | |
| 930 | |
| 931 public LargeIconCallbackImpl( | |
| 932 MostVisitedItem item, MostVisitedItemView itemView, boolean
isInitialLoad) { | |
| 933 mItem = item; | |
| 934 mItemView = itemView; | |
| 935 mIsInitialLoad = isInitialLoad; | |
| 936 } | |
| 937 | |
| 938 @Override | |
| 939 public void onLargeIconAvailable( | |
| 940 Bitmap icon, int fallbackColor, boolean isFallbackColorDefau
lt) { | |
| 941 if (icon == null) { | |
| 942 mIconGenerator.setBackgroundColor(fallbackColor); | |
| 943 icon = mIconGenerator.generateIconForUrl(mItem.getUrl()); | |
| 944 mItemView.setIcon(new BitmapDrawable(getResources(), icon)); | |
| 945 mItem.setTileType(isFallbackColorDefault ? MostVisitedTileTy
pe.ICON_DEFAULT | |
| 946 : MostVisitedTileTy
pe.ICON_COLOR); | |
| 947 } else { | |
| 948 RoundedBitmapDrawable roundedIcon = RoundedBitmapDrawableFac
tory.create( | |
| 949 getResources(), icon); | |
| 950 int cornerRadius = Math.round(ICON_CORNER_RADIUS_DP | |
| 951 * getResources().getDisplayMetrics().density * icon.
getWidth() | |
| 952 / mDesiredIconSize); | |
| 953 roundedIcon.setCornerRadius(cornerRadius); | |
| 954 roundedIcon.setAntiAlias(true); | |
| 955 roundedIcon.setFilterBitmap(true); | |
| 956 mItemView.setIcon(roundedIcon); | |
| 957 mItem.setTileType(MostVisitedTileType.ICON_REAL); | |
| 958 } | |
| 959 mSnapshotMostVisitedChanged = true; | |
| 960 if (mIsInitialLoad) loadTaskCompleted(); | |
| 961 } | |
| 962 } | |
| 963 | |
| 964 public View createMostVisitedItemView( | |
| 965 LayoutInflater inflater, MostVisitedItem item, boolean isInitial
Load) { | |
| 966 final MostVisitedItemView view = (MostVisitedItemView) inflater.infl
ate( | |
| 967 R.layout.most_visited_item, mMostVisitedLayout, false); | |
| 968 view.setTitle(TitleUtil.getTitleForDisplay(item.getTitle(), item.get
Url())); | |
| 969 view.setOfflineAvailable(item.isOfflineAvailable()); | |
| 970 | |
| 971 LargeIconCallback iconCallback = new LargeIconCallbackImpl(item, vie
w, isInitialLoad); | |
| 972 if (isInitialLoad) mPendingLoadTasks++; | |
| 973 if (!loadWhitelistIcon(item, iconCallback)) { | |
| 974 mManager.getLargeIconForUrl(item.getUrl(), mMinIconSize, iconCal
lback); | |
| 975 } | |
| 976 | |
| 977 return view; | |
| 978 } | |
| 979 | |
| 980 private boolean loadWhitelistIcon(MostVisitedItem item, LargeIconCallbac
k iconCallback) { | |
| 981 if (item.getWhitelistIconPath().isEmpty()) return false; | |
| 982 | |
| 983 Bitmap bitmap = BitmapFactory.decodeFile(item.getWhitelistIconPath()
); | |
| 984 if (bitmap == null) { | |
| 985 Log.d(TAG, "Image decoding failed: %s", item.getWhitelistIconPat
h()); | |
| 986 return false; | |
| 987 } | |
| 988 iconCallback.onLargeIconAvailable(bitmap, Color.BLACK, false); | |
| 989 return true; | |
| 990 } | |
| 991 | |
| 992 public void onIconUpdated(final String url) { | |
| 993 if (mMostVisitedItems == null) return; | |
| 994 // Find a matching most visited item. | |
| 995 for (MostVisitedItem item : mMostVisitedItems) { | |
| 996 if (item.getUrl().equals(url)) { | |
| 997 LargeIconCallback iconCallback = new LargeIconCallbackImpl( | |
| 998 item, (MostVisitedItemView) item.getView(), false); | |
| 999 mManager.getLargeIconForUrl(url, mMinIconSize, iconCallback)
; | |
| 1000 break; | |
| 1001 } | |
| 1002 } | |
| 1003 } | |
| 1004 } | |
| 1005 | |
| 1006 @Override | 758 @Override |
| 1007 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | 759 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
| 1008 if (mNewTabPageLayout != null) { | 760 if (mNewTabPageLayout != null) { |
| 1009 mNewTabPageLayout.setParentViewportHeight(MeasureSpec.getSize(height
MeasureSpec)); | 761 mNewTabPageLayout.setParentViewportHeight(MeasureSpec.getSize(height
MeasureSpec)); |
| 1010 } | 762 } |
| 1011 super.onMeasure(widthMeasureSpec, heightMeasureSpec); | 763 super.onMeasure(widthMeasureSpec, heightMeasureSpec); |
| 1012 | 764 |
| 1013 mRecyclerView.updatePeekingCardAndHeader(); | 765 mRecyclerView.updatePeekingCardAndHeader(); |
| 1014 } | 766 } |
| 1015 | 767 |
| (...skipping 16 matching lines...) Expand all Loading... |
| 1032 * @return The adapter position the user has scrolled to. | 784 * @return The adapter position the user has scrolled to. |
| 1033 */ | 785 */ |
| 1034 public int getScrollPosition() { | 786 public int getScrollPosition() { |
| 1035 return mRecyclerView.getScrollPosition(); | 787 return mRecyclerView.getScrollPosition(); |
| 1036 } | 788 } |
| 1037 | 789 |
| 1038 /** @return the context menu manager. */ | 790 /** @return the context menu manager. */ |
| 1039 public ContextMenuManager getContextMenuManager() { | 791 public ContextMenuManager getContextMenuManager() { |
| 1040 return mContextMenuManager; | 792 return mContextMenuManager; |
| 1041 } | 793 } |
| 794 |
| 795 // TileGroup.Observer interface. |
| 796 |
| 797 @Override |
| 798 public void onInitialTileDataLoaded() { |
| 799 // The page contents are initially hidden; otherwise they'll be drawn ce
ntered on the |
| 800 // page before the most visited sites are available and then jump upward
s to make space |
| 801 // once the most visited sites are available. |
| 802 onTileDataChanged(); |
| 803 mNewTabPageLayout.setVisibility(View.VISIBLE); |
| 804 } |
| 805 |
| 806 @Override |
| 807 public void onTileDataChanged() { |
| 808 mTileGroup.renderTileViews(mMostVisitedLayout, !mLoadHasCompleted); |
| 809 mSnapshotMostVisitedChanged = true; |
| 810 } |
| 811 |
| 812 @Override |
| 813 public void onTileCountChanged() { |
| 814 // If the number of tile rows change while the URL bar is focused, the i
cons' |
| 815 // position will be wrong. Schedule the translation to be updated. |
| 816 if (mUrlFocusChangePercent == 1f) mTileCountChanged = true; |
| 817 updateMostVisitedPlaceholderVisibility(); |
| 818 } |
| 819 |
| 820 @Override |
| 821 public void onTileIconChanged(Tile tile) { |
| 822 mMostVisitedLayout.updateIconView(tile.getUrl(), tile.getIcon()); |
| 823 mSnapshotMostVisitedChanged = true; |
| 824 } |
| 825 |
| 826 @Override |
| 827 public void onLoadTaskAdded() { |
| 828 mPendingLoadTasks++; |
| 829 } |
| 830 |
| 831 @Override |
| 832 public void onLoadTaskCompleted() { |
| 833 loadTaskCompleted(); |
| 834 } |
| 1042 } | 835 } |
| OLD | NEW |