OLD | NEW |
(Empty) | |
| 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 |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.chrome.browser.ntp; |
| 6 |
| 7 import android.app.Activity; |
| 8 import android.content.Context; |
| 9 import android.graphics.Canvas; |
| 10 import android.graphics.Rect; |
| 11 import android.view.ContextMenu; |
| 12 import android.view.LayoutInflater; |
| 13 import android.view.Menu; |
| 14 import android.view.MenuItem.OnMenuItemClickListener; |
| 15 import android.view.View; |
| 16 |
| 17 import com.google.android.apps.chrome.R; |
| 18 |
| 19 import org.chromium.base.VisibleForTesting; |
| 20 import org.chromium.base.metrics.RecordHistogram; |
| 21 import org.chromium.base.metrics.RecordUserAction; |
| 22 import org.chromium.chrome.browser.LogoBridge; |
| 23 import org.chromium.chrome.browser.LogoBridge.Logo; |
| 24 import org.chromium.chrome.browser.LogoBridge.LogoObserver; |
| 25 import org.chromium.chrome.browser.NativePage; |
| 26 import org.chromium.chrome.browser.Tab; |
| 27 import org.chromium.chrome.browser.UrlConstants; |
| 28 import org.chromium.chrome.browser.compositor.layouts.content.InvalidationAwareT
humbnailProvider; |
| 29 import org.chromium.chrome.browser.enhancedbookmarks.EnhancedBookmarkUtils; |
| 30 import org.chromium.chrome.browser.favicon.FaviconHelper; |
| 31 import org.chromium.chrome.browser.favicon.FaviconHelper.FaviconImageCallback; |
| 32 import org.chromium.chrome.browser.favicon.LargeIconBridge; |
| 33 import org.chromium.chrome.browser.favicon.LargeIconBridge.LargeIconCallback; |
| 34 import org.chromium.chrome.browser.ntp.NewTabPageView.NewTabPageManager; |
| 35 import org.chromium.chrome.browser.preferences.PrefServiceBridge; |
| 36 import org.chromium.chrome.browser.profiles.MostVisitedSites; |
| 37 import org.chromium.chrome.browser.profiles.MostVisitedSites.MostVisitedURLsObse
rver; |
| 38 import org.chromium.chrome.browser.profiles.MostVisitedSites.ThumbnailCallback; |
| 39 import org.chromium.chrome.browser.profiles.Profile; |
| 40 import org.chromium.chrome.browser.search_engines.TemplateUrlService; |
| 41 import org.chromium.chrome.browser.search_engines.TemplateUrlService.TemplateUrl
ServiceObserver; |
| 42 import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType; |
| 43 import org.chromium.chrome.browser.tabmodel.TabModelSelector; |
| 44 import org.chromium.content_public.browser.LoadUrlParams; |
| 45 import org.chromium.ui.base.DeviceFormFactor; |
| 46 import org.chromium.ui.base.PageTransition; |
| 47 |
| 48 import java.util.concurrent.TimeUnit; |
| 49 |
| 50 /** |
| 51 * Provides functionality when the user interacts with the NTP. |
| 52 */ |
| 53 public class NewTabPage |
| 54 implements NativePage, InvalidationAwareThumbnailProvider, TemplateUrlSe
rviceObserver { |
| 55 |
| 56 // MostVisitedItem Context menu item IDs. |
| 57 static final int ID_OPEN_IN_NEW_TAB = 0; |
| 58 static final int ID_OPEN_IN_INCOGNITO_TAB = 1; |
| 59 static final int ID_REMOVE = 2; |
| 60 |
| 61 private static MostVisitedSites sMostVisitedSitesForTests; |
| 62 |
| 63 private final Tab mTab; |
| 64 private final TabModelSelector mTabModelSelector; |
| 65 private final Activity mActivity; |
| 66 |
| 67 private final Profile mProfile; |
| 68 private final String mTitle; |
| 69 private final int mBackgroundColor; |
| 70 private final NewTabPageView mNewTabPageView; |
| 71 |
| 72 private MostVisitedSites mMostVisitedSites; |
| 73 private FaviconHelper mFaviconHelper; |
| 74 private LargeIconBridge mLargeIconBridge; |
| 75 private LogoBridge mLogoBridge; |
| 76 private boolean mSearchProviderHasLogo; |
| 77 private String mOnLogoClickUrl; |
| 78 private FakeboxDelegate mFakeboxDelegate; |
| 79 |
| 80 // The timestamp at which the constructor was called. |
| 81 private final long mConstructedTimeNs; |
| 82 |
| 83 private boolean mIsLoaded; |
| 84 |
| 85 // Whether destroy() has been called. |
| 86 private boolean mIsDestroyed; |
| 87 |
| 88 /** |
| 89 * Allows clients to listen for updates to the scroll changes of the search
box on the |
| 90 * NTP. |
| 91 */ |
| 92 public interface OnSearchBoxScrollListener { |
| 93 /** |
| 94 * Callback to be notified when the scroll position of the search box on
the NTP has |
| 95 * changed. A scroll percentage of 0, means the search box has no scrol
l applied and |
| 96 * is in it's natural resting position. A value of 1 means the search b
ox is scrolled |
| 97 * entirely to the top of the screen viewport. |
| 98 * |
| 99 * @param scrollPercentage The percentage the search box has been scroll
ed off the page. |
| 100 */ |
| 101 void onScrollChanged(float scrollPercentage); |
| 102 } |
| 103 |
| 104 /** |
| 105 * Handles user interaction with the fakebox (the URL bar in the NTP). |
| 106 */ |
| 107 public interface FakeboxDelegate { |
| 108 /** |
| 109 * Shows the voice recognition dialog. Called when the user taps the mic
rophone icon. |
| 110 */ |
| 111 void startVoiceRecognition(); |
| 112 |
| 113 /** |
| 114 * Focuses the URL bar when the user taps the fakebox, types in the fake
box, or pastes text |
| 115 * into the fakebox. |
| 116 * |
| 117 * @param pastedText The text that was pasted or typed into the fakebox,
or null if the user |
| 118 * just tapped the fakebox. |
| 119 */ |
| 120 void requestUrlFocusFromFakebox(String pastedText); |
| 121 } |
| 122 |
| 123 /** |
| 124 * @param url The URL to check whether it is for the NTP. |
| 125 * @return Whether the passed in URL is used to render the NTP. |
| 126 */ |
| 127 public static boolean isNTPUrl(String url) { |
| 128 return url != null && url.startsWith(UrlConstants.NTP_URL); |
| 129 } |
| 130 |
| 131 @VisibleForTesting |
| 132 static void setMostVisitedSitesForTests(MostVisitedSites mostVisitedSitesFor
Tests) { |
| 133 sMostVisitedSitesForTests = mostVisitedSitesForTests; |
| 134 } |
| 135 |
| 136 private final NewTabPageManager mNewTabPageManager = new NewTabPageManager()
{ |
| 137 @Override |
| 138 public boolean isLocationBarShownInNTP() { |
| 139 if (mIsDestroyed) return false; |
| 140 Context context = mNewTabPageView.getContext(); |
| 141 return isInSingleUrlBarMode(context) |
| 142 && !mNewTabPageView.urlFocusAnimationsDisabled(); |
| 143 } |
| 144 |
| 145 private void recordOpenedMostVisitedItem(MostVisitedItem item) { |
| 146 if (mIsDestroyed) return; |
| 147 NewTabPageUma.recordAction(NewTabPageUma.ACTION_OPENED_MOST_VISITED_
ENTRY); |
| 148 RecordHistogram.recordEnumeratedHistogram("NewTabPage.MostVisited",
item.getIndex(), |
| 149 NewTabPageView.MAX_MOST_VISITED_SITES); |
| 150 mMostVisitedSites.recordOpenedMostVisitedItem(item.getIndex()); |
| 151 } |
| 152 |
| 153 @Override |
| 154 public boolean shouldShowOptOutPromo() { |
| 155 return false; |
| 156 } |
| 157 |
| 158 @Override |
| 159 public void optOutPromoShown() {} |
| 160 |
| 161 @Override |
| 162 public void optOutPromoClicked(boolean settingsClicked) { |
| 163 assert false : "Should never be called for this page"; |
| 164 } |
| 165 |
| 166 @Override |
| 167 public void open(MostVisitedItem item) { |
| 168 if (mIsDestroyed) return; |
| 169 recordOpenedMostVisitedItem(item); |
| 170 mTab.loadUrl(new LoadUrlParams(item.getUrl())); |
| 171 } |
| 172 |
| 173 @Override |
| 174 public void onCreateContextMenu(ContextMenu menu, OnMenuItemClickListene
r listener) { |
| 175 if (mIsDestroyed) return; |
| 176 menu.add(Menu.NONE, ID_OPEN_IN_NEW_TAB, Menu.NONE, R.string.contextm
enu_open_in_new_tab) |
| 177 .setOnMenuItemClickListener(listener); |
| 178 if (PrefServiceBridge.getInstance().isIncognitoModeEnabled()) { |
| 179 menu.add(Menu.NONE, ID_OPEN_IN_INCOGNITO_TAB, Menu.NONE, |
| 180 R.string.contextmenu_open_in_incognito_tab).setOnMenuIte
mClickListener( |
| 181 listener); |
| 182 } |
| 183 menu.add(Menu.NONE, ID_REMOVE, Menu.NONE, R.string.remove) |
| 184 .setOnMenuItemClickListener(listener); |
| 185 } |
| 186 |
| 187 @Override |
| 188 public boolean onMenuItemClick(int menuId, MostVisitedItem item) { |
| 189 if (mIsDestroyed) return false; |
| 190 switch (menuId) { |
| 191 case ID_OPEN_IN_NEW_TAB: |
| 192 recordOpenedMostVisitedItem(item); |
| 193 mTabModelSelector.openNewTab(new LoadUrlParams(item.getUrl()
), |
| 194 TabLaunchType.FROM_LONGPRESS_BACKGROUND, mTab, false
); |
| 195 return true; |
| 196 case ID_OPEN_IN_INCOGNITO_TAB: |
| 197 recordOpenedMostVisitedItem(item); |
| 198 mTabModelSelector.openNewTab(new LoadUrlParams(item.getUrl()
), |
| 199 TabLaunchType.FROM_LONGPRESS_FOREGROUND, mTab, true)
; |
| 200 return true; |
| 201 case ID_REMOVE: |
| 202 mMostVisitedSites.blacklistUrl(item.getUrl()); |
| 203 return true; |
| 204 default: |
| 205 return false; |
| 206 } |
| 207 } |
| 208 |
| 209 @Override |
| 210 public void navigateToBookmarks() { |
| 211 if (mIsDestroyed) return; |
| 212 RecordUserAction.record("MobileNTPSwitchToBookmarks"); |
| 213 if (!EnhancedBookmarkUtils.showEnhancedBookmarkIfEnabled(mActivity))
{ |
| 214 mTab.loadUrl(new LoadUrlParams(UrlConstants.BOOKMARKS_URL)); |
| 215 } |
| 216 } |
| 217 |
| 218 @Override |
| 219 public void navigateToRecentTabs() { |
| 220 if (mIsDestroyed) return; |
| 221 RecordUserAction.record("MobileNTPSwitchToOpenTabs"); |
| 222 mTab.loadUrl(new LoadUrlParams(UrlConstants.RECENT_TABS_URL)); |
| 223 } |
| 224 |
| 225 @Override |
| 226 public void focusSearchBox(boolean beginVoiceSearch, String pastedText)
{ |
| 227 if (mIsDestroyed) return; |
| 228 if (mFakeboxDelegate != null) { |
| 229 if (beginVoiceSearch) { |
| 230 mFakeboxDelegate.startVoiceRecognition(); |
| 231 } else { |
| 232 mFakeboxDelegate.requestUrlFocusFromFakebox(pastedText); |
| 233 } |
| 234 } |
| 235 } |
| 236 |
| 237 @Override |
| 238 public void setMostVisitedURLsObserver(MostVisitedURLsObserver observer,
int numResults) { |
| 239 if (mIsDestroyed) return; |
| 240 mMostVisitedSites.setMostVisitedURLsObserver(observer, numResults); |
| 241 } |
| 242 |
| 243 @Override |
| 244 public void getURLThumbnail(String url, ThumbnailCallback thumbnailCallb
ack) { |
| 245 if (mIsDestroyed) return; |
| 246 mMostVisitedSites.getURLThumbnail(url, thumbnailCallback); |
| 247 } |
| 248 |
| 249 @Override |
| 250 public void getLocalFaviconImageForURL( |
| 251 String url, int size, FaviconImageCallback faviconCallback) { |
| 252 if (mIsDestroyed) return; |
| 253 if (mFaviconHelper == null) mFaviconHelper = new FaviconHelper(); |
| 254 mFaviconHelper.getLocalFaviconImageForURL(mProfile, url, FaviconHelp
er.FAVICON |
| 255 | FaviconHelper.TOUCH_ICON | FaviconHelper.TOUCH_PRECOMPOSED
_ICON, size, |
| 256 faviconCallback); |
| 257 } |
| 258 |
| 259 @Override |
| 260 public void getLargeIconForUrl(String url, int size, LargeIconCallback c
allback) { |
| 261 if (mIsDestroyed) return; |
| 262 if (mLargeIconBridge == null) mLargeIconBridge = new LargeIconBridge
(); |
| 263 mLargeIconBridge.getLargeIconForUrl(mProfile, url, size, callback); |
| 264 } |
| 265 |
| 266 @Override |
| 267 public void openLogoLink() { |
| 268 if (mIsDestroyed) return; |
| 269 if (mOnLogoClickUrl == null) return; |
| 270 mTab.loadUrl( |
| 271 new LoadUrlParams(mOnLogoClickUrl, PageTransition.LINK)); |
| 272 } |
| 273 |
| 274 @Override |
| 275 public void getSearchProviderLogo(final LogoObserver logoObserver) { |
| 276 if (mIsDestroyed) return; |
| 277 LogoObserver wrapperCallback = new LogoObserver() { |
| 278 @Override |
| 279 public void onLogoAvailable(Logo logo, boolean fromCache) { |
| 280 if (mIsDestroyed) return; |
| 281 mOnLogoClickUrl = logo != null ? logo.onClickUrl : null; |
| 282 logoObserver.onLogoAvailable(logo, fromCache); |
| 283 } |
| 284 }; |
| 285 mLogoBridge.getCurrentLogo(wrapperCallback); |
| 286 } |
| 287 |
| 288 @Override |
| 289 public void onLoadingComplete() { |
| 290 long loadTimeMs = (System.nanoTime() - mConstructedTimeNs) / 1000000
; |
| 291 RecordHistogram.recordTimesHistogram( |
| 292 "Tab.NewTabOnload", loadTimeMs, TimeUnit.MILLISECONDS); |
| 293 mIsLoaded = true; |
| 294 |
| 295 if (mIsDestroyed) return; |
| 296 mMostVisitedSites.onLoadingComplete(); |
| 297 } |
| 298 }; |
| 299 |
| 300 /** |
| 301 * Constructs a NewTabPage. |
| 302 * @param activity The activity used for context to create the new tab page'
s View. |
| 303 * @param tab The Tab that is showing this new tab page. |
| 304 * @param tabModelSelector The TabModelSelector used to open tabs. |
| 305 */ |
| 306 public NewTabPage(Activity activity, Tab tab, TabModelSelector tabModelSelec
tor) { |
| 307 mConstructedTimeNs = System.nanoTime(); |
| 308 |
| 309 mTab = tab; |
| 310 mActivity = activity; |
| 311 mTabModelSelector = tabModelSelector; |
| 312 mProfile = tab.getProfile(); |
| 313 |
| 314 mTitle = activity.getResources().getString(R.string.button_new_tab); |
| 315 mBackgroundColor = activity.getResources().getColor(R.color.ntp_bg); |
| 316 TemplateUrlService.getInstance().addObserver(this); |
| 317 |
| 318 mMostVisitedSites = buildMostVisitedSites(mProfile); |
| 319 mLogoBridge = new LogoBridge(mProfile); |
| 320 mSearchProviderHasLogo = TemplateUrlService.getInstance().isDefaultSearc
hEngineGoogle(); |
| 321 |
| 322 LayoutInflater inflater = LayoutInflater.from(activity); |
| 323 mNewTabPageView = (NewTabPageView) inflater.inflate(R.layout.new_tab_pag
e, null); |
| 324 mNewTabPageView.initialize(mNewTabPageManager, isInSingleUrlBarMode(acti
vity), |
| 325 mSearchProviderHasLogo); |
| 326 } |
| 327 |
| 328 private static MostVisitedSites buildMostVisitedSites(Profile profile) { |
| 329 if (sMostVisitedSitesForTests != null) { |
| 330 return sMostVisitedSitesForTests; |
| 331 } else { |
| 332 return new MostVisitedSites(profile); |
| 333 } |
| 334 } |
| 335 |
| 336 /** @return The view container for the new tab page. */ |
| 337 @VisibleForTesting |
| 338 NewTabPageView getNewTabPageView() { |
| 339 return mNewTabPageView; |
| 340 } |
| 341 |
| 342 /** |
| 343 * Updates whether the NewTabPage should animate on URL focus changes. |
| 344 * @param disable Whether to disable the animations. |
| 345 */ |
| 346 public void setUrlFocusAnimationsDisabled(boolean disable) { |
| 347 mNewTabPageView.setUrlFocusAnimationsDisabled(disable); |
| 348 } |
| 349 |
| 350 private boolean isInSingleUrlBarMode(Context context) { |
| 351 return mSearchProviderHasLogo && !DeviceFormFactor.isTablet(context); |
| 352 } |
| 353 |
| 354 private void onSearchEngineUpdated() { |
| 355 // TODO(newt): update this if other search providers provide logos. |
| 356 mSearchProviderHasLogo = TemplateUrlService.getInstance().isDefaultSearc
hEngineGoogle(); |
| 357 mNewTabPageView.setSearchProviderHasLogo(mSearchProviderHasLogo); |
| 358 } |
| 359 |
| 360 /** |
| 361 * Specifies the percentage the URL is focused during an animation. 1.0 spe
cifies that the URL |
| 362 * bar has focus and has completed the focus animation. 0 is when the URL b
ar is does not have |
| 363 * any focus. |
| 364 * |
| 365 * @param percent The percentage of the URL bar focus animation. |
| 366 */ |
| 367 public void setUrlFocusChangeAnimationPercent(float percent) { |
| 368 mNewTabPageView.setUrlFocusChangeAnimationPercent(percent); |
| 369 } |
| 370 |
| 371 /** |
| 372 * Get the bounds of the search box in relation to the top level NewTabPage
view. |
| 373 * |
| 374 * @param originalBounds The bounding region of the search box without exter
nal transforms |
| 375 * applied. The delta between this and the transforme
d bounds determines |
| 376 * the amount of scroll applied to this view. |
| 377 * @param transformedBounds The bounding region of the search box including
any transforms |
| 378 * applied by the parent view hierarchy up to the N
ewTabPage view. |
| 379 * This more accurately reflects the current drawin
g location of the |
| 380 * search box. |
| 381 */ |
| 382 public void getSearchBoxBounds(Rect originalBounds, Rect transformedBounds)
{ |
| 383 mNewTabPageView.getSearchBoxBounds(originalBounds, transformedBounds); |
| 384 } |
| 385 |
| 386 /** |
| 387 * @return Whether the location bar is shown in the NTP. |
| 388 */ |
| 389 public boolean isLocationBarShownInNTP() { |
| 390 return mNewTabPageManager.isLocationBarShownInNTP(); |
| 391 } |
| 392 |
| 393 /** |
| 394 * Sets the listener for search box scroll changes. |
| 395 * @param listener The listener to be notified on changes. |
| 396 */ |
| 397 public void setSearchBoxScrollListener(OnSearchBoxScrollListener listener) { |
| 398 mNewTabPageView.setSearchBoxScrollListener(listener); |
| 399 } |
| 400 |
| 401 /** |
| 402 * Sets the FakeboxDelegate that this pages interacts with. |
| 403 */ |
| 404 public void setFakeboxDelegate(FakeboxDelegate fakeboxDelegate) { |
| 405 mFakeboxDelegate = fakeboxDelegate; |
| 406 } |
| 407 |
| 408 /** |
| 409 * @return Whether the NTP has finished loaded. |
| 410 */ |
| 411 @VisibleForTesting |
| 412 public boolean isLoadedForTests() { |
| 413 return mIsLoaded; |
| 414 } |
| 415 |
| 416 // TemplateUrlServiceObserver overrides |
| 417 |
| 418 @Override |
| 419 public void onTemplateURLServiceChanged() { |
| 420 onSearchEngineUpdated(); |
| 421 } |
| 422 |
| 423 // NativePage overrides |
| 424 |
| 425 @Override |
| 426 public void destroy() { |
| 427 assert getView().getParent() == null : "Destroy called before removed fr
om window"; |
| 428 if (mFaviconHelper != null) { |
| 429 mFaviconHelper.destroy(); |
| 430 mFaviconHelper = null; |
| 431 } |
| 432 if (mLargeIconBridge != null) { |
| 433 mLargeIconBridge.destroy(); |
| 434 mLargeIconBridge = null; |
| 435 } |
| 436 if (mMostVisitedSites != null) { |
| 437 mMostVisitedSites.destroy(); |
| 438 mMostVisitedSites = null; |
| 439 } |
| 440 if (mLogoBridge != null) { |
| 441 mLogoBridge.destroy(); |
| 442 mLogoBridge = null; |
| 443 } |
| 444 TemplateUrlService.getInstance().removeObserver(this); |
| 445 mIsDestroyed = true; |
| 446 } |
| 447 |
| 448 @Override |
| 449 public String getUrl() { |
| 450 return UrlConstants.NTP_URL; |
| 451 } |
| 452 |
| 453 @Override |
| 454 public String getTitle() { |
| 455 return mTitle; |
| 456 } |
| 457 |
| 458 @Override |
| 459 public int getBackgroundColor() { |
| 460 return mBackgroundColor; |
| 461 } |
| 462 |
| 463 @Override |
| 464 public View getView() { |
| 465 return mNewTabPageView; |
| 466 } |
| 467 |
| 468 @Override |
| 469 public String getHost() { |
| 470 return UrlConstants.NTP_HOST; |
| 471 } |
| 472 |
| 473 @Override |
| 474 public void updateForUrl(String url) { |
| 475 } |
| 476 |
| 477 // InvalidationAwareThumbnailProvider |
| 478 |
| 479 @Override |
| 480 public boolean shouldCaptureThumbnail() { |
| 481 return mNewTabPageView.shouldCaptureThumbnail(); |
| 482 } |
| 483 |
| 484 @Override |
| 485 public void captureThumbnail(Canvas canvas) { |
| 486 mNewTabPageView.captureThumbnail(canvas); |
| 487 } |
| 488 } |
OLD | NEW |