Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(4)

Unified Diff: chrome/android/java_staging/src/org/chromium/chrome/browser/ntp/NewTabPageView.java

Issue 1141283003: Upstream oodles of Chrome for Android code into Chromium. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: final patch? Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/android/java_staging/src/org/chromium/chrome/browser/ntp/NewTabPageView.java
diff --git a/chrome/android/java_staging/src/org/chromium/chrome/browser/ntp/NewTabPageView.java b/chrome/android/java_staging/src/org/chromium/chrome/browser/ntp/NewTabPageView.java
new file mode 100644
index 0000000000000000000000000000000000000000..97c663e07986719dd71cb0b0c4e8ee5553b75a0e
--- /dev/null
+++ b/chrome/android/java_staging/src/org/chromium/chrome/browser/ntp/NewTabPageView.java
@@ -0,0 +1,961 @@
+// 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.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.net.Uri;
+import android.os.Build;
+import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
+import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.text.method.LinkMovementMethod;
+import android.text.style.ClickableSpan;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnLayoutChangeListener;
+import android.view.ViewGroup;
+import android.view.ViewStub;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.google.android.apps.chrome.R;
+
+import org.chromium.base.CommandLine;
+import org.chromium.base.FieldTrialList;
+import org.chromium.base.VisibleForTesting;
+import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.chrome.browser.ApplicationSwitches;
+import org.chromium.chrome.browser.LogoBridge.Logo;
+import org.chromium.chrome.browser.LogoBridge.LogoObserver;
+import org.chromium.chrome.browser.favicon.FaviconHelper.FaviconImageCallback;
+import org.chromium.chrome.browser.favicon.LargeIconBridge.LargeIconCallback;
+import org.chromium.chrome.browser.ntp.MostVisitedItem.MostVisitedItemManager;
+import org.chromium.chrome.browser.ntp.NewTabPage.OnSearchBoxScrollListener;
+import org.chromium.chrome.browser.profiles.MostVisitedSites.MostVisitedURLsObserver;
+import org.chromium.chrome.browser.profiles.MostVisitedSites.ThumbnailCallback;
+import org.chromium.chrome.browser.util.FeatureUtilities;
+import org.chromium.chrome.browser.util.ViewUtils;
+import org.chromium.chrome.browser.widget.RoundedIconGenerator;
+import org.chromium.ui.text.SpanApplier;
+import org.chromium.ui.text.SpanApplier.SpanInfo;
+
+import java.util.Locale;
+
+/**
+ * The native new tab page, represented by some basic data such as title and url, and an Android
+ * View that displays the page.
+ */
+public class NewTabPageView extends FrameLayout
+ implements MostVisitedURLsObserver, OnLayoutChangeListener {
+
+ static final int MAX_MOST_VISITED_SITES = 12;
+ private static final int SHADOW_COLOR = 0x11000000;
+ private static final long SNAP_SCROLL_DELAY_MS = 30;
+
+ private static final String ICON_NTP_FIELD_TRIAL_NAME = "IconNTP";
+ private static final String ICON_NTP_ENABLED_GROUP = "Enabled";
+
+ private ViewGroup mContentView;
+ private NewTabScrollView mScrollView;
+ private LogoView mSearchProviderLogoView;
+ private View mSearchBoxView;
+ private TextView mSearchBoxTextView;
+ private ImageView mVoiceSearchButton;
+ private ViewGroup mMostVisitedLayout;
+ private View mMostVisitedPlaceholder;
+ private View mOptOutView;
+ private View mNoSearchLogoSpacer;
+
+ private OnSearchBoxScrollListener mSearchBoxScrollListener;
+
+ private NewTabPageManager mManager;
+ private MostVisitedDesign mMostVisitedDesign;
+ private MostVisitedItem[] mMostVisitedItems;
+ private boolean mFirstShow = true;
+ private boolean mSearchProviderHasLogo = true;
+ private boolean mHasReceivedMostVisitedSites;
+ private boolean mPendingSnapScroll;
+
+ /**
+ * The number of asynchronous tasks that need to complete before the page is done loading.
+ * This starts at one to track when the view is finished attaching to the window.
+ */
+ private int mPendingLoadTasks = 1;
+ private boolean mLoadHasCompleted;
+
+ private float mUrlFocusChangePercent;
+ private boolean mDisableUrlFocusChangeAnimations;
+
+ private boolean mSnapshotMostVisitedChanged;
+ private int mSnapshotWidth;
+ private int mSnapshotHeight;
+ private int mSnapshotScrollY;
+
+ /**
+ * Manages the view interaction with the rest of the system.
+ */
+ public interface NewTabPageManager extends MostVisitedItemManager {
+ /** @return Whether the location bar is shown in the NTP. */
+ boolean isLocationBarShownInNTP();
+
+ /** @return Whether the document mode opt out promo should be shown. */
+ boolean shouldShowOptOutPromo();
+
+ /** Called when the document mode opt out promo is shown. */
+ void optOutPromoShown();
+
+ /** Called when the user clicks "settings" or "ok, got it" on the opt out promo. */
+ void optOutPromoClicked(boolean settingsClicked);
+
+ /** Opens the bookmarks page in the current tab. */
+ void navigateToBookmarks();
+
+ /** Opens the recent tabs page in the current tab. */
+ void navigateToRecentTabs();
+
+ /**
+ * Animates the search box up into the omnibox and bring up the keyboard.
+ * @param beginVoiceSearch Whether to begin a voice search.
+ * @param pastedText Text to paste in the omnibox after it's been focused. May be null.
+ */
+ void focusSearchBox(boolean beginVoiceSearch, String pastedText);
+
+ /**
+ * Gets the list of most visited sites.
+ * @param observer The observer to be notified with the list of sites.
+ * @param numResults The maximum number of sites to retrieve.
+ */
+ void setMostVisitedURLsObserver(MostVisitedURLsObserver observer, int numResults);
+
+ /**
+ * Gets a cached thumbnail of a URL.
+ * @param url The URL whose thumbnail is being retrieved.
+ * @param thumbnailCallback The callback to be notified when the thumbnail is available.
+ */
+ void getURLThumbnail(String url, ThumbnailCallback thumbnailCallback);
+
+ /**
+ * Gets the favicon image for a given URL.
+ * @param url The URL of the site whose favicon is being requested.
+ * @param size The desired size of the favicon in pixels.
+ * @param faviconCallback The callback to be notified when the favicon is available.
+ */
+ void getLocalFaviconImageForURL(
+ String url, int size, FaviconImageCallback faviconCallback);
+
+ /**
+ * Gets the large icon (e.g. favicon or touch icon) for a given URL.
+ * @param url The URL of the site whose icon is being requested.
+ * @param size The desired size of the icon in pixels.
+ * @param callback The callback to be notified when the icon is available.
+ */
+ void getLargeIconForUrl(String url, int size, LargeIconCallback callback);
+
+ /**
+ * Navigates to a URL chosen by the search provider when the user clicks on the logo.
+ */
+ void openLogoLink();
+
+ /**
+ * Gets the default search provider's logo and calls logoObserver with the result.
+ * @param logoObserver The callback to notify when the logo is available.
+ */
+ void getSearchProviderLogo(LogoObserver logoObserver);
+
+ /**
+ * Called when the NTP has completely finished loading (all views will be inflated
+ * and any dependent resources will have been loaded).
+ */
+ void onLoadingComplete();
+ }
+
+ /**
+ * Returns a title suitable for display for a link (e.g. a most visited item). If |title| is
+ * non-empty, this simply returns it. Otherwise, returns a shortened form of the URL.
+ */
+ static String getTitleForDisplay(String title, String url) {
+ if (TextUtils.isEmpty(title) && url != null) {
+ Uri uri = Uri.parse(url);
+ String host = uri.getHost();
+ String path = uri.getPath();
+ if (host == null) host = "";
+ if (TextUtils.isEmpty(path) || path.equals("/")) path = "";
+ title = host + path;
+ }
+ return title;
+ }
+
+ /**
+ * Default constructor required for XML inflation.
+ */
+ public NewTabPageView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ private boolean isIconNtpEnabled() {
+ // Query the field trial state first, to ensure that UMA reports the correct group.
+ String fieldTrialGroup = FieldTrialList.findFullName(ICON_NTP_FIELD_TRIAL_NAME);
+ CommandLine commandLine = CommandLine.getInstance();
+ if (commandLine.hasSwitch(ApplicationSwitches.DISABLE_ICON_NTP)) return false;
+ if (commandLine.hasSwitch(ApplicationSwitches.ENABLE_ICON_NTP)) return true;
+ return fieldTrialGroup.equals(ICON_NTP_ENABLED_GROUP);
+ }
+
+ /**
+ * Initializes the NTP. This must be called immediately after inflation, before this object is
+ * used in any other way.
+ *
+ * @param manager NewTabPageManager used to perform various actions when the user interacts
+ * with the page.
+ * @param isSingleUrlBarMode Whether the NTP is in single URL bar mode.
+ * @param searchProviderHasLogo Whether the search provider has a logo.
+ */
+ public void initialize(NewTabPageManager manager, boolean isSingleUrlBarMode,
+ boolean searchProviderHasLogo) {
+ mManager = manager;
+
+ mScrollView = (NewTabScrollView) findViewById(R.id.ntp_scrollview);
+ mScrollView.enableBottomShadow(SHADOW_COLOR);
+ mContentView = (ViewGroup) findViewById(R.id.ntp_content);
+
+ mMostVisitedDesign = isIconNtpEnabled()
+ ? new IconMostVisitedDesign(getContext())
+ : new ThumbnailMostVisitedDesign(getContext());
+ ViewStub mostVisitedLayoutStub = (ViewStub) findViewById(R.id.most_visited_layout_stub);
+ mostVisitedLayoutStub.setLayoutResource(mMostVisitedDesign.getMostVisitedLayoutId());
+ mMostVisitedLayout = (ViewGroup) mostVisitedLayoutStub.inflate();
+
+ mSearchProviderLogoView = (LogoView) findViewById(R.id.search_provider_logo);
+ mSearchBoxView = findViewById(R.id.search_box);
+ mNoSearchLogoSpacer = findViewById(R.id.no_search_logo_spacer);
+
+ mSearchBoxTextView = (TextView) mSearchBoxView.findViewById(R.id.search_box_text);
+ String hintText = getResources().getString(R.string.search_or_type_url);
+ if (isSingleUrlBarMode) {
+ mSearchBoxTextView.setHint(hintText);
+ } else {
+ mSearchBoxTextView.setContentDescription(hintText);
+ }
+ mSearchBoxTextView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mManager.focusSearchBox(false, null);
+ }
+ });
+ mSearchBoxTextView.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (s.length() == 0) return;
+ mManager.focusSearchBox(false, s.toString());
+ mSearchBoxTextView.setText("");
+ }
+ });
+
+ mVoiceSearchButton = (ImageView) findViewById(R.id.voice_search_button);
+ mVoiceSearchButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mManager.focusSearchBox(true, null);
+ }
+ });
+
+ NewTabPageToolbar toolbar = (NewTabPageToolbar) findViewById(R.id.ntp_toolbar);
+ toolbar.getRecentTabsButton().setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mManager.navigateToRecentTabs();
+ }
+ });
+ toolbar.getBookmarksButton().setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mManager.navigateToBookmarks();
+ }
+ });
+
+ initializeSearchBoxScrollHandling();
+ addOnLayoutChangeListener(this);
+ setSearchProviderHasLogo(searchProviderHasLogo);
+
+ mPendingLoadTasks++;
+ mManager.setMostVisitedURLsObserver(this,
+ mMostVisitedDesign.getNumberOfTiles(searchProviderHasLogo));
+
+ if (mManager.shouldShowOptOutPromo()) showOptOutPromo();
+ }
+
+ private int getTabsMovedIllustration() {
+ switch (Build.MANUFACTURER.toLowerCase(Locale.US)) {
+ case "samsung":
+ return R.drawable.tabs_moved_samsung;
+ case "htc":
+ return R.drawable.tabs_moved_htc;
+ default:
+ return R.drawable.tabs_moved_nexus;
+ }
+ }
+
+ private void showOptOutPromo() {
+ ViewStub optOutPromoStub = (ViewStub) findViewById(R.id.opt_out_promo_stub);
+ mOptOutView = optOutPromoStub.inflate();
+ // Fill in opt-out text with Settings link
+ TextView optOutText = (TextView) mOptOutView.findViewById(R.id.opt_out_text);
+
+ ClickableSpan settingsLink = new ClickableSpan() {
+ @Override
+ public void onClick(View view) {
+ mManager.optOutPromoClicked(true);
+ }
+
+ // Change link formatting to use our blue control color and no underline
+ @Override
+ public void updateDrawState(android.text.TextPaint textPaint) {
+ textPaint.setColor(getContext().getResources().getColor(
+ R.color.light_active_color));
+ textPaint.setUnderlineText(false);
+ }
+ };
+
+ optOutText.setText(SpanApplier.applySpans(
+ getContext().getString(R.string.tabs_and_apps_opt_out_text),
+ new SpanInfo("<link>", "</link>", settingsLink)));
+ optOutText.setMovementMethod(LinkMovementMethod.getInstance());
+
+ ImageView illustration = (ImageView) mOptOutView.findViewById(R.id.tabs_moved_illustration);
+ illustration.setImageResource(getTabsMovedIllustration());
+
+ mOptOutView.setVisibility(View.VISIBLE);
+ mMostVisitedLayout.setVisibility(View.GONE);
+
+ Button gotItButton = (Button) mOptOutView.findViewById(R.id.got_it_button);
+ gotItButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mOptOutView.setVisibility(View.GONE);
+ mMostVisitedLayout.setVisibility(View.VISIBLE);
+ mManager.optOutPromoClicked(false);
+ updateMostVisitedPlaceholderVisibility();
+ }
+ });
+ mManager.optOutPromoShown();
+ }
+
+ private void updateSearchBoxOnScroll() {
+ if (mDisableUrlFocusChangeAnimations) return;
+
+ float percentage = 0;
+ // During startup the view may not be fully initialized, so we only calculate the current
+ // percentage if some basic view properties are sane.
+ if (mScrollView.getHeight() != 0 && mSearchBoxView.getTop() != 0) {
+ int scrollY = mScrollView.getScrollY();
+ percentage = Math.max(
+ 0f, Math.min(1f, scrollY / (float) mSearchBoxView.getTop()));
+ }
+
+ updateVisualsForToolbarTransition(percentage);
+
+ if (mSearchBoxScrollListener != null) {
+ mSearchBoxScrollListener.onScrollChanged(percentage);
+ }
+ }
+
+ private void initializeSearchBoxScrollHandling() {
+ final Runnable mSnapScrollRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (!mPendingSnapScroll) return;
+ int scrollY = mScrollView.getScrollY();
+ int dividerTop = mMostVisitedLayout.getTop() - mContentView.getPaddingTop();
+ if (scrollY > 0 && scrollY < dividerTop) {
+ mScrollView.smoothScrollTo(0, scrollY < (dividerTop / 2) ? 0 : dividerTop);
+ }
+ mPendingSnapScroll = false;
+ }
+ };
+ mScrollView.setOnScrollListener(new NewTabScrollView.OnScrollListener() {
+ @Override
+ public void onScrollChanged(int l, int t, int oldl, int oldt) {
+ if (mPendingSnapScroll) {
+ mScrollView.removeCallbacks(mSnapScrollRunnable);
+ mScrollView.postDelayed(mSnapScrollRunnable, SNAP_SCROLL_DELAY_MS);
+ }
+ updateSearchBoxOnScroll();
+ }
+ });
+ mScrollView.setOnTouchListener(new OnTouchListener() {
+ @Override
+ @SuppressLint("ClickableViewAccessibility")
+ public boolean onTouch(View v, MotionEvent event) {
+ if (mScrollView.getHandler() == null) return false;
+ mScrollView.removeCallbacks(mSnapScrollRunnable);
+
+ if (event.getActionMasked() == MotionEvent.ACTION_CANCEL
+ || event.getActionMasked() == MotionEvent.ACTION_UP) {
+ mPendingSnapScroll = true;
+ mScrollView.postDelayed(mSnapScrollRunnable, SNAP_SCROLL_DELAY_MS);
+ } else {
+ mPendingSnapScroll = false;
+ }
+ return false;
+ }
+ });
+ }
+
+ /**
+ * Decrements the count of pending load tasks and notifies the manager when the page load
+ * is complete.
+ */
+ private void loadTaskCompleted() {
+ assert mPendingLoadTasks > 0;
+ mPendingLoadTasks--;
+ if (mPendingLoadTasks == 0) {
+ if (mLoadHasCompleted) {
+ assert false;
+ } else {
+ mLoadHasCompleted = true;
+ mManager.onLoadingComplete();
+ mMostVisitedDesign.onLoadingComplete();
+ // Load the logo after everything else is finished, since it's lower priority.
+ loadSearchProviderLogo();
+ }
+ }
+ }
+
+ /**
+ * Loads the search provider logo (e.g. Google doodle), if any.
+ */
+ private void loadSearchProviderLogo() {
+ mManager.getSearchProviderLogo(new LogoObserver() {
+ @Override
+ public void onLogoAvailable(Logo logo, boolean fromCache) {
+ if (logo == null && fromCache) return;
+ mSearchProviderLogoView.setMananger(mManager);
+ mSearchProviderLogoView.updateLogo(logo);
+ mSnapshotMostVisitedChanged = true;
+ }
+ });
+ }
+
+ /**
+ * Changes the layout depending on whether the selected search provider (e.g. Google, Bing)
+ * has a logo.
+ * @param hasLogo Whether the search provider has a logo.
+ */
+ public void setSearchProviderHasLogo(boolean hasLogo) {
+ if (hasLogo == mSearchProviderHasLogo) return;
+ mSearchProviderHasLogo = hasLogo;
+
+ mMostVisitedDesign.setSearchProviderHasLogo(mMostVisitedLayout, hasLogo);
+
+ if (!hasLogo) setUrlFocusChangeAnimationPercentInternal(0);
+
+ // Hide or show all the views above the Most Visited items.
+ int visibility = hasLogo ? View.VISIBLE : View.GONE;
+ int childCount = mContentView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = mContentView.getChildAt(i);
+ if (child == mMostVisitedLayout) break;
+ // Don't change the visibility of a ViewStub as that will automagically inflate it.
+ if (child instanceof ViewStub) continue;
+ child.setVisibility(visibility);
+ }
+
+ updateMostVisitedPlaceholderVisibility();
+
+ if (hasLogo) setUrlFocusChangeAnimationPercent(mUrlFocusChangePercent);
+ mSnapshotMostVisitedChanged = true;
+ }
+
+ /**
+ * Updates whether the NewTabPage should animate on URL focus changes.
+ * @param disable Whether to disable the animations.
+ */
+ void setUrlFocusAnimationsDisabled(boolean disable) {
+ if (disable == mDisableUrlFocusChangeAnimations) return;
+ mDisableUrlFocusChangeAnimations = disable;
+ if (!disable) setUrlFocusChangeAnimationPercent(mUrlFocusChangePercent);
+ }
+
+ /**
+ * @return Whether URL focus animations are currently disabled.
+ */
+ boolean urlFocusAnimationsDisabled() {
+ return mDisableUrlFocusChangeAnimations;
+ }
+
+ /**
+ * Specifies the percentage the URL is focused during an animation. 1.0 specifies that the URL
+ * bar has focus and has completed the focus animation. 0 is when the URL bar is does not have
+ * any focus.
+ *
+ * @param percent The percentage of the URL bar focus animation.
+ */
+ void setUrlFocusChangeAnimationPercent(float percent) {
+ mUrlFocusChangePercent = percent;
+ if (!mDisableUrlFocusChangeAnimations && mSearchProviderHasLogo) {
+ setUrlFocusChangeAnimationPercentInternal(percent);
+ }
+ }
+
+ /**
+ * @return The percentage that the URL bar is focused during an animation.
+ */
+ @VisibleForTesting
+ float getUrlFocusChangeAnimationPercent() {
+ return mUrlFocusChangePercent;
+ }
+
+ /**
+ * Unconditionally sets the percentage the URL is focused during an animation, without updating
+ * mUrlFocusChangePercent.
+ * @see #setUrlFocusChangeAnimationPercent
+ */
+ private void setUrlFocusChangeAnimationPercentInternal(float percent) {
+ mContentView.setTranslationY(percent * (-mMostVisitedLayout.getTop()
+ + mScrollView.getScrollY() + mContentView.getPaddingTop()));
+ updateVisualsForToolbarTransition(percent);
+ }
+
+ private void updateVisualsForToolbarTransition(float transitionPercentage) {
+ // Complete the full alpha transition in the first 40% of the animation.
+ float searchUiAlpha =
+ transitionPercentage >= 0.4f ? 0f : (0.4f - transitionPercentage) * 2.5f;
+ // Ensure there are no rounding issues when the animation percent is 0.
+ if (transitionPercentage == 0f) searchUiAlpha = 1f;
+
+ mSearchProviderLogoView.setAlpha(searchUiAlpha);
+ mSearchBoxView.setAlpha(searchUiAlpha);
+ }
+
+ /**
+ * Get the bounds of the search box in relation to the top level NewTabPage view.
+ *
+ * @param originalBounds The bounding region of the search box without external transforms
+ * applied. The delta between this and the transformed bounds determines
+ * the amount of scroll applied to this view.
+ * @param transformedBounds The bounding region of the search box including any transforms
+ * applied by the parent view hierarchy up to the NewTabPage view.
+ * This more accurately reflects the current drawing location of the
+ * search box.
+ */
+ void getSearchBoxBounds(Rect originalBounds, Rect transformedBounds) {
+ int searchBoxX = (int) mSearchBoxView.getX();
+ int searchBoxY = (int) mSearchBoxView.getY();
+ originalBounds.set(
+ searchBoxX + mSearchBoxView.getPaddingLeft(),
+ searchBoxY + mSearchBoxView.getPaddingTop(),
+ searchBoxX + mSearchBoxView.getWidth() - mSearchBoxView.getPaddingRight(),
+ searchBoxY + mSearchBoxView.getHeight() - mSearchBoxView.getPaddingBottom());
+
+ transformedBounds.set(originalBounds);
+ View view = (View) mSearchBoxView.getParent();
+ while (view != null) {
+ transformedBounds.offset(-view.getScrollX(), -view.getScrollY());
+ if (view == this) break;
+ transformedBounds.offset((int) view.getX(), (int) view.getY());
+ view = (View) view.getParent();
+ }
+ }
+
+ /**
+ * Sets the listener for search box scroll changes.
+ * @param listener The listener to be notified on changes.
+ */
+ void setSearchBoxScrollListener(OnSearchBoxScrollListener listener) {
+ mSearchBoxScrollListener = listener;
+ if (mSearchBoxScrollListener != null) updateSearchBoxOnScroll();
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ assert mManager != null;
+
+ if (mFirstShow) {
+ loadTaskCompleted();
+ mFirstShow = false;
+ } else {
+ // Trigger a scroll update when reattaching the window to signal the toolbar that
+ // it needs to reset the NTP state.
+ if (mManager.isLocationBarShownInNTP()) updateSearchBoxOnScroll();
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ setUrlFocusChangeAnimationPercent(0f);
+ }
+
+ @Override
+ protected void onWindowVisibilityChanged(int visibility) {
+ super.onWindowVisibilityChanged(visibility);
+
+ if (visibility == VISIBLE) {
+ mVoiceSearchButton.setVisibility(
+ FeatureUtilities.isRecognitionIntentPresent(getContext(), true)
+ ? VISIBLE : GONE);
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ // Make the search box and logo the same width as the most visited tiles.
+ if (mMostVisitedLayout.getVisibility() != GONE) {
+ int mostVisitedWidth = MeasureSpec.makeMeasureSpec(
+ mMostVisitedLayout.getMeasuredWidth(), MeasureSpec.EXACTLY);
+ int searchBoxHeight = MeasureSpec.makeMeasureSpec(
+ mSearchBoxView.getMeasuredHeight(), MeasureSpec.EXACTLY);
+ int logoHeight = MeasureSpec.makeMeasureSpec(
+ mSearchProviderLogoView.getMeasuredHeight(), MeasureSpec.EXACTLY);
+ mSearchBoxView.measure(mostVisitedWidth, searchBoxHeight);
+ mSearchProviderLogoView.measure(mostVisitedWidth, logoHeight);
+ }
+ }
+
+ /**
+ * @see org.chromium.chrome.browser.compositor.layouts.content.
+ * InvalidationAwareThumbnailProvider#shouldCaptureThumbnail()
+ */
+ boolean shouldCaptureThumbnail() {
+ if (getWidth() == 0 || getHeight() == 0) return false;
+
+ return mSnapshotMostVisitedChanged
+ || getWidth() != mSnapshotWidth
+ || getHeight() != mSnapshotHeight
+ || mScrollView.getScrollY() != mSnapshotScrollY;
+ }
+
+ /**
+ * @see org.chromium.chrome.browser.compositor.layouts.content.
+ * InvalidationAwareThumbnailProvider#captureThumbnail(Canvas)
+ */
+ void captureThumbnail(Canvas canvas) {
+ mSearchProviderLogoView.endAnimation();
+ ViewUtils.captureBitmap(this, canvas);
+ mSnapshotWidth = getWidth();
+ mSnapshotHeight = getHeight();
+ mSnapshotScrollY = mScrollView.getScrollY();
+ mSnapshotMostVisitedChanged = false;
+ }
+
+ // OnLayoutChangeListener overrides
+
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ int oldWidth = oldRight - oldLeft;
+ int newWidth = right - left;
+ if (oldWidth == newWidth) return;
+
+ // Re-apply the url focus change amount after a rotation to ensure the views are correctly
+ // placed with their new layout configurations.
+ setUrlFocusChangeAnimationPercent(mUrlFocusChangePercent);
+ }
+
+ // MostVisitedURLsObserver implementation
+
+ @Override
+ public void onMostVisitedURLsAvailable(String[] titles, String[] urls) {
+ mMostVisitedLayout.removeAllViews();
+
+ MostVisitedItem[] oldItems = mMostVisitedItems;
+ int oldItemCount = oldItems == null ? 0 : oldItems.length;
+ mMostVisitedItems = new MostVisitedItem[titles.length];
+
+ final boolean isInitialLoad = !mHasReceivedMostVisitedSites;
+ LayoutInflater inflater = LayoutInflater.from(getContext());
+
+ // Add the most visited items to the page.
+ for (int i = 0; i < titles.length; i++) {
+ final String url = urls[i];
+ final String title = titles[i];
+
+ // Look for an existing item to reuse.
+ MostVisitedItem item = null;
+ for (int j = 0; j < oldItemCount; j++) {
+ MostVisitedItem oldItem = oldItems[j];
+ if (oldItem != null && TextUtils.equals(url, oldItem.getUrl())
+ && TextUtils.equals(title, oldItem.getTitle())) {
+ item = oldItem;
+ item.setIndex(i);
+ oldItems[j] = null;
+ break;
+ }
+ }
+
+ // If nothing can be reused, create a new item.
+ if (item == null) {
+ String displayTitle = getTitleForDisplay(title, url);
+ View view = mMostVisitedDesign.createMostVisitedItemView(inflater, url, title,
+ displayTitle, i, isInitialLoad);
+ item = new MostVisitedItem(mManager, title, url, i, view);
+ }
+
+ mMostVisitedItems[i] = item;
+ mMostVisitedLayout.addView(item.getView());
+ }
+
+ mHasReceivedMostVisitedSites = true;
+ updateMostVisitedPlaceholderVisibility();
+
+ if (isInitialLoad) {
+ loadTaskCompleted();
+ // The page contents are initially hidden; otherwise they'll be drawn centered on the
+ // page before the most visited sites are available and then jump upwards to make space
+ // once the most visited sites are available.
+ mContentView.setVisibility(View.VISIBLE);
+ }
+ mSnapshotMostVisitedChanged = true;
+ }
+
+ /**
+ * Shows the most visited placeholder ("Nothing to see here") if there are no most visited
+ * items and there is no search provider logo.
+ */
+ private void updateMostVisitedPlaceholderVisibility() {
+ boolean showPlaceholder = mHasReceivedMostVisitedSites
+ && !mManager.shouldShowOptOutPromo()
+ && mMostVisitedLayout.getChildCount() == 0
+ && !mSearchProviderHasLogo;
+
+ mNoSearchLogoSpacer.setVisibility(
+ (mSearchProviderHasLogo || showPlaceholder) ? View.GONE : View.INVISIBLE);
+
+ if (showPlaceholder) {
+ if (mMostVisitedPlaceholder == null) {
+ ViewStub mostVisitedPlaceholderStub = (ViewStub) findViewById(
+ R.id.most_visited_placeholder_stub);
+ mMostVisitedPlaceholder = mostVisitedPlaceholderStub.inflate();
+ }
+ mMostVisitedLayout.setVisibility(GONE);
+ mMostVisitedPlaceholder.setVisibility(VISIBLE);
+ return;
+ } else if (mMostVisitedPlaceholder != null) {
+ mMostVisitedLayout.setVisibility(VISIBLE);
+ mMostVisitedPlaceholder.setVisibility(GONE);
+ }
+ }
+
+ /**
+ * Interface for creating the most visited layout and tiles.
+ * TODO(newt): delete this once a single design has been chosen.
+ */
+ private interface MostVisitedDesign {
+ int getNumberOfTiles(boolean searchProviderHasLogo);
+ int getMostVisitedLayoutId();
+ void setSearchProviderHasLogo(View mostVisitedLayout, boolean hasLogo);
+ View createMostVisitedItemView(LayoutInflater inflater, String url, String title,
+ String displayTitle, int index, boolean isInitialLoad);
+ void onLoadingComplete();
+ }
+
+ /**
+ * The old most visited design, where each tile shows a thumbnail of the page, a small favicon,
+ * and the title.
+ */
+ private class ThumbnailMostVisitedDesign implements MostVisitedDesign {
+
+ private static final int NUM_TILES = 6;
+ private static final int FAVICON_CORNER_RADIUS_DP = 2;
+ private static final int FAVICON_TEXT_SIZE_DP = 10;
+ private static final int FAVICON_BACKGROUND_COLOR = 0xff969696;
+
+ private int mDesiredFaviconSize;
+ private RoundedIconGenerator mFaviconGenerator;
+
+ ThumbnailMostVisitedDesign(Context context) {
+ Resources res = context.getResources();
+ mDesiredFaviconSize = res.getDimensionPixelSize(R.dimen.most_visited_favicon_size);
+ int desiredFaviconSizeDp = Math.round(
+ mDesiredFaviconSize / res.getDisplayMetrics().density);
+ mFaviconGenerator = new RoundedIconGenerator(
+ context, desiredFaviconSizeDp, desiredFaviconSizeDp, FAVICON_CORNER_RADIUS_DP,
+ FAVICON_BACKGROUND_COLOR, FAVICON_TEXT_SIZE_DP);
+ }
+
+ @Override
+ public int getNumberOfTiles(boolean searchProviderHasLogo) {
+ return NUM_TILES;
+ }
+
+ @Override
+ public int getMostVisitedLayoutId() {
+ return R.layout.most_visited_layout;
+ }
+
+ @Override
+ public void setSearchProviderHasLogo(View mostVisitedLayout, boolean hasLogo) {}
+
+ @Override
+ public View createMostVisitedItemView(LayoutInflater inflater, final String url,
+ String title, String displayTitle, int index, final boolean isInitialLoad) {
+ final MostVisitedItemView view = (MostVisitedItemView) inflater.inflate(
+ R.layout.most_visited_item, mMostVisitedLayout, false);
+ view.init(displayTitle);
+
+ ThumbnailCallback thumbnailCallback = new ThumbnailCallback() {
+ @Override
+ public void onMostVisitedURLsThumbnailAvailable(Bitmap thumbnail) {
+ view.setThumbnail(thumbnail);
+ mSnapshotMostVisitedChanged = true;
+ if (isInitialLoad) loadTaskCompleted();
+ }
+ };
+ if (isInitialLoad) mPendingLoadTasks++;
+ mManager.getURLThumbnail(url, thumbnailCallback);
+
+ FaviconImageCallback faviconCallback = new FaviconImageCallback() {
+ @Override
+ public void onFaviconAvailable(Bitmap image, String iconUrl) {
+ if (image == null) {
+ image = mFaviconGenerator.generateIconForUrl(url);
+ }
+ view.setFavicon(image);
+ mSnapshotMostVisitedChanged = true;
+ if (isInitialLoad) loadTaskCompleted();
+ }
+ };
+ if (isInitialLoad) mPendingLoadTasks++;
+ mManager.getLocalFaviconImageForURL(url, mDesiredFaviconSize, faviconCallback);
+
+ return view;
+ }
+
+ @Override
+ public void onLoadingComplete() {}
+ }
+
+ /**
+ * The new-fangled design for most visited tiles, where each tile shows a large icon and title.
+ */
+ private class IconMostVisitedDesign implements MostVisitedDesign {
+
+ private static final int NUM_TILES = 8;
+ private static final int NUM_TILES_NO_LOGO = 12;
+
+ private static final int ICON_CORNER_RADIUS_DP = 4;
+ private static final int ICON_TEXT_SIZE_DP = 20;
+ private static final int ICON_BACKGROUND_COLOR = 0xff787878;
+ private static final int ICON_MIN_SIZE_PX = 48;
+
+ private int mMinIconSize;
+ private int mDesiredIconSize;
+ private RoundedIconGenerator mIconGenerator;
+
+ private int mNumGrayIcons;
+ private int mNumColorIcons;
+ private int mNumRealIcons;
+
+ IconMostVisitedDesign(Context context) {
+ Resources res = context.getResources();
+ mDesiredIconSize = res.getDimensionPixelSize(R.dimen.icon_most_visited_icon_size);
+ // On ldpi devices, mDesiredIconSize could be even smaller than ICON_MIN_SIZE_PX.
+ mMinIconSize = Math.min(mDesiredIconSize, ICON_MIN_SIZE_PX);
+ int desiredIconSizeDp = Math.round(
+ mDesiredIconSize / res.getDisplayMetrics().density);
+ mIconGenerator = new RoundedIconGenerator(
+ context, desiredIconSizeDp, desiredIconSizeDp, ICON_CORNER_RADIUS_DP,
+ ICON_BACKGROUND_COLOR, ICON_TEXT_SIZE_DP);
+ }
+
+ @Override
+ public int getNumberOfTiles(boolean searchProviderHasLogo) {
+ return searchProviderHasLogo ? NUM_TILES : NUM_TILES_NO_LOGO;
+ }
+
+ @Override
+ public int getMostVisitedLayoutId() {
+ return R.layout.icon_most_visited_layout;
+ }
+
+ @Override
+ public void setSearchProviderHasLogo(View mostVisitedLayout, boolean hasLogo) {
+ if (hasLogo) {
+ int paddingTop = getResources().getDimensionPixelSize(
+ R.dimen.icon_most_visited_layout_padding_top);
+ int paddingSide = getResources().getDimensionPixelSize(
+ R.dimen.icon_most_visited_layout_padding_side);
+ mostVisitedLayout.setPadding(paddingSide, paddingTop, paddingSide, 0);
+ } else {
+ int paddingTop = getResources().getDimensionPixelSize(
+ R.dimen.icon_most_visited_layout_no_logo_padding_top);
+ mostVisitedLayout.setPadding(0, paddingTop, 0, 0);
+ }
+ }
+
+ @Override
+ public View createMostVisitedItemView(LayoutInflater inflater, final String url,
+ String title, String displayTitle, int index, final boolean isInitialLoad) {
+ final IconMostVisitedItemView view = (IconMostVisitedItemView) inflater.inflate(
+ R.layout.icon_most_visited_item, mMostVisitedLayout, false);
+ view.setTitle(displayTitle);
+
+ LargeIconCallback iconCallback = new LargeIconCallback() {
+ @Override
+ public void onLargeIconAvailable(Bitmap icon, int fallbackColor) {
+ if (icon == null) {
+ mIconGenerator.setBackgroundColor(fallbackColor);
+ icon = mIconGenerator.generateIconForUrl(url);
+ view.setIcon(new BitmapDrawable(getResources(), icon));
+ if (isInitialLoad) {
+ if (fallbackColor == ICON_BACKGROUND_COLOR) {
+ mNumGrayIcons++;
+ } else {
+ mNumColorIcons++;
+ }
+ }
+ } else {
+ RoundedBitmapDrawable roundedIcon = RoundedBitmapDrawableFactory.create(
+ getResources(), icon);
+ int cornerRadius = Math.round(ICON_CORNER_RADIUS_DP
+ * getResources().getDisplayMetrics().density * icon.getWidth()
+ / mDesiredIconSize);
+ roundedIcon.setCornerRadius(cornerRadius);
+ roundedIcon.setAntiAlias(true);
+ roundedIcon.setFilterBitmap(true);
+ view.setIcon(roundedIcon);
+ if (isInitialLoad) mNumRealIcons++;
+ }
+ mSnapshotMostVisitedChanged = true;
+ if (isInitialLoad) loadTaskCompleted();
+ }
+ };
+ if (isInitialLoad) mPendingLoadTasks++;
+ mManager.getLargeIconForUrl(url, mMinIconSize, iconCallback);
+
+ return view;
+ }
+
+ @Override
+ public void onLoadingComplete() {
+ RecordHistogram.recordCount100Histogram("NewTabPage.IconsGray", mNumGrayIcons);
+ RecordHistogram.recordCount100Histogram("NewTabPage.IconsColor", mNumColorIcons);
+ RecordHistogram.recordCount100Histogram("NewTabPage.IconsReal", mNumRealIcons);
+ }
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698