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

Side by Side 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 unified diff | Download patch
OLDNEW
(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.annotation.SuppressLint;
8 import android.content.Context;
9 import android.content.res.Resources;
10 import android.graphics.Bitmap;
11 import android.graphics.Canvas;
12 import android.graphics.Rect;
13 import android.graphics.drawable.BitmapDrawable;
14 import android.net.Uri;
15 import android.os.Build;
16 import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
17 import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
18 import android.text.Editable;
19 import android.text.TextUtils;
20 import android.text.TextWatcher;
21 import android.text.method.LinkMovementMethod;
22 import android.text.style.ClickableSpan;
23 import android.util.AttributeSet;
24 import android.view.LayoutInflater;
25 import android.view.MotionEvent;
26 import android.view.View;
27 import android.view.View.OnLayoutChangeListener;
28 import android.view.ViewGroup;
29 import android.view.ViewStub;
30 import android.widget.Button;
31 import android.widget.FrameLayout;
32 import android.widget.ImageView;
33 import android.widget.TextView;
34
35 import com.google.android.apps.chrome.R;
36
37 import org.chromium.base.CommandLine;
38 import org.chromium.base.FieldTrialList;
39 import org.chromium.base.VisibleForTesting;
40 import org.chromium.base.metrics.RecordHistogram;
41 import org.chromium.chrome.browser.ApplicationSwitches;
42 import org.chromium.chrome.browser.LogoBridge.Logo;
43 import org.chromium.chrome.browser.LogoBridge.LogoObserver;
44 import org.chromium.chrome.browser.favicon.FaviconHelper.FaviconImageCallback;
45 import org.chromium.chrome.browser.favicon.LargeIconBridge.LargeIconCallback;
46 import org.chromium.chrome.browser.ntp.MostVisitedItem.MostVisitedItemManager;
47 import org.chromium.chrome.browser.ntp.NewTabPage.OnSearchBoxScrollListener;
48 import org.chromium.chrome.browser.profiles.MostVisitedSites.MostVisitedURLsObse rver;
49 import org.chromium.chrome.browser.profiles.MostVisitedSites.ThumbnailCallback;
50 import org.chromium.chrome.browser.util.FeatureUtilities;
51 import org.chromium.chrome.browser.util.ViewUtils;
52 import org.chromium.chrome.browser.widget.RoundedIconGenerator;
53 import org.chromium.ui.text.SpanApplier;
54 import org.chromium.ui.text.SpanApplier.SpanInfo;
55
56 import java.util.Locale;
57
58 /**
59 * The native new tab page, represented by some basic data such as title and url , and an Android
60 * View that displays the page.
61 */
62 public class NewTabPageView extends FrameLayout
63 implements MostVisitedURLsObserver, OnLayoutChangeListener {
64
65 static final int MAX_MOST_VISITED_SITES = 12;
66 private static final int SHADOW_COLOR = 0x11000000;
67 private static final long SNAP_SCROLL_DELAY_MS = 30;
68
69 private static final String ICON_NTP_FIELD_TRIAL_NAME = "IconNTP";
70 private static final String ICON_NTP_ENABLED_GROUP = "Enabled";
71
72 private ViewGroup mContentView;
73 private NewTabScrollView mScrollView;
74 private LogoView mSearchProviderLogoView;
75 private View mSearchBoxView;
76 private TextView mSearchBoxTextView;
77 private ImageView mVoiceSearchButton;
78 private ViewGroup mMostVisitedLayout;
79 private View mMostVisitedPlaceholder;
80 private View mOptOutView;
81 private View mNoSearchLogoSpacer;
82
83 private OnSearchBoxScrollListener mSearchBoxScrollListener;
84
85 private NewTabPageManager mManager;
86 private MostVisitedDesign mMostVisitedDesign;
87 private MostVisitedItem[] mMostVisitedItems;
88 private boolean mFirstShow = true;
89 private boolean mSearchProviderHasLogo = true;
90 private boolean mHasReceivedMostVisitedSites;
91 private boolean mPendingSnapScroll;
92
93 /**
94 * The number of asynchronous tasks that need to complete before the page is done loading.
95 * This starts at one to track when the view is finished attaching to the wi ndow.
96 */
97 private int mPendingLoadTasks = 1;
98 private boolean mLoadHasCompleted;
99
100 private float mUrlFocusChangePercent;
101 private boolean mDisableUrlFocusChangeAnimations;
102
103 private boolean mSnapshotMostVisitedChanged;
104 private int mSnapshotWidth;
105 private int mSnapshotHeight;
106 private int mSnapshotScrollY;
107
108 /**
109 * Manages the view interaction with the rest of the system.
110 */
111 public interface NewTabPageManager extends MostVisitedItemManager {
112 /** @return Whether the location bar is shown in the NTP. */
113 boolean isLocationBarShownInNTP();
114
115 /** @return Whether the document mode opt out promo should be shown. */
116 boolean shouldShowOptOutPromo();
117
118 /** Called when the document mode opt out promo is shown. */
119 void optOutPromoShown();
120
121 /** Called when the user clicks "settings" or "ok, got it" on the opt ou t promo. */
122 void optOutPromoClicked(boolean settingsClicked);
123
124 /** Opens the bookmarks page in the current tab. */
125 void navigateToBookmarks();
126
127 /** Opens the recent tabs page in the current tab. */
128 void navigateToRecentTabs();
129
130 /**
131 * Animates the search box up into the omnibox and bring up the keyboard .
132 * @param beginVoiceSearch Whether to begin a voice search.
133 * @param pastedText Text to paste in the omnibox after it's been focuse d. May be null.
134 */
135 void focusSearchBox(boolean beginVoiceSearch, String pastedText);
136
137 /**
138 * Gets the list of most visited sites.
139 * @param observer The observer to be notified with the list of sites.
140 * @param numResults The maximum number of sites to retrieve.
141 */
142 void setMostVisitedURLsObserver(MostVisitedURLsObserver observer, int nu mResults);
143
144 /**
145 * Gets a cached thumbnail of a URL.
146 * @param url The URL whose thumbnail is being retrieved.
147 * @param thumbnailCallback The callback to be notified when the thumbna il is available.
148 */
149 void getURLThumbnail(String url, ThumbnailCallback thumbnailCallback);
150
151 /**
152 * Gets the favicon image for a given URL.
153 * @param url The URL of the site whose favicon is being requested.
154 * @param size The desired size of the favicon in pixels.
155 * @param faviconCallback The callback to be notified when the favicon i s available.
156 */
157 void getLocalFaviconImageForURL(
158 String url, int size, FaviconImageCallback faviconCallback);
159
160 /**
161 * Gets the large icon (e.g. favicon or touch icon) for a given URL.
162 * @param url The URL of the site whose icon is being requested.
163 * @param size The desired size of the icon in pixels.
164 * @param callback The callback to be notified when the icon is availabl e.
165 */
166 void getLargeIconForUrl(String url, int size, LargeIconCallback callback );
167
168 /**
169 * Navigates to a URL chosen by the search provider when the user clicks on the logo.
170 */
171 void openLogoLink();
172
173 /**
174 * Gets the default search provider's logo and calls logoObserver with t he result.
175 * @param logoObserver The callback to notify when the logo is available .
176 */
177 void getSearchProviderLogo(LogoObserver logoObserver);
178
179 /**
180 * Called when the NTP has completely finished loading (all views will b e inflated
181 * and any dependent resources will have been loaded).
182 */
183 void onLoadingComplete();
184 }
185
186 /**
187 * Returns a title suitable for display for a link (e.g. a most visited item ). If |title| is
188 * non-empty, this simply returns it. Otherwise, returns a shortened form of the URL.
189 */
190 static String getTitleForDisplay(String title, String url) {
191 if (TextUtils.isEmpty(title) && url != null) {
192 Uri uri = Uri.parse(url);
193 String host = uri.getHost();
194 String path = uri.getPath();
195 if (host == null) host = "";
196 if (TextUtils.isEmpty(path) || path.equals("/")) path = "";
197 title = host + path;
198 }
199 return title;
200 }
201
202 /**
203 * Default constructor required for XML inflation.
204 */
205 public NewTabPageView(Context context, AttributeSet attrs) {
206 super(context, attrs);
207 }
208
209 private boolean isIconNtpEnabled() {
210 // Query the field trial state first, to ensure that UMA reports the cor rect group.
211 String fieldTrialGroup = FieldTrialList.findFullName(ICON_NTP_FIELD_TRIA L_NAME);
212 CommandLine commandLine = CommandLine.getInstance();
213 if (commandLine.hasSwitch(ApplicationSwitches.DISABLE_ICON_NTP)) return false;
214 if (commandLine.hasSwitch(ApplicationSwitches.ENABLE_ICON_NTP)) return t rue;
215 return fieldTrialGroup.equals(ICON_NTP_ENABLED_GROUP);
216 }
217
218 /**
219 * Initializes the NTP. This must be called immediately after inflation, bef ore this object is
220 * used in any other way.
221 *
222 * @param manager NewTabPageManager used to perform various actions when the user interacts
223 * with the page.
224 * @param isSingleUrlBarMode Whether the NTP is in single URL bar mode.
225 * @param searchProviderHasLogo Whether the search provider has a logo.
226 */
227 public void initialize(NewTabPageManager manager, boolean isSingleUrlBarMode ,
228 boolean searchProviderHasLogo) {
229 mManager = manager;
230
231 mScrollView = (NewTabScrollView) findViewById(R.id.ntp_scrollview);
232 mScrollView.enableBottomShadow(SHADOW_COLOR);
233 mContentView = (ViewGroup) findViewById(R.id.ntp_content);
234
235 mMostVisitedDesign = isIconNtpEnabled()
236 ? new IconMostVisitedDesign(getContext())
237 : new ThumbnailMostVisitedDesign(getContext());
238 ViewStub mostVisitedLayoutStub = (ViewStub) findViewById(R.id.most_visit ed_layout_stub);
239 mostVisitedLayoutStub.setLayoutResource(mMostVisitedDesign.getMostVisite dLayoutId());
240 mMostVisitedLayout = (ViewGroup) mostVisitedLayoutStub.inflate();
241
242 mSearchProviderLogoView = (LogoView) findViewById(R.id.search_provider_l ogo);
243 mSearchBoxView = findViewById(R.id.search_box);
244 mNoSearchLogoSpacer = findViewById(R.id.no_search_logo_spacer);
245
246 mSearchBoxTextView = (TextView) mSearchBoxView.findViewById(R.id.search_ box_text);
247 String hintText = getResources().getString(R.string.search_or_type_url);
248 if (isSingleUrlBarMode) {
249 mSearchBoxTextView.setHint(hintText);
250 } else {
251 mSearchBoxTextView.setContentDescription(hintText);
252 }
253 mSearchBoxTextView.setOnClickListener(new View.OnClickListener() {
254 @Override
255 public void onClick(View v) {
256 mManager.focusSearchBox(false, null);
257 }
258 });
259 mSearchBoxTextView.addTextChangedListener(new TextWatcher() {
260 @Override
261 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
262 }
263
264 @Override
265 public void onTextChanged(CharSequence s, int start, int before, int count) {
266 }
267
268 @Override
269 public void afterTextChanged(Editable s) {
270 if (s.length() == 0) return;
271 mManager.focusSearchBox(false, s.toString());
272 mSearchBoxTextView.setText("");
273 }
274 });
275
276 mVoiceSearchButton = (ImageView) findViewById(R.id.voice_search_button);
277 mVoiceSearchButton.setOnClickListener(new View.OnClickListener() {
278 @Override
279 public void onClick(View v) {
280 mManager.focusSearchBox(true, null);
281 }
282 });
283
284 NewTabPageToolbar toolbar = (NewTabPageToolbar) findViewById(R.id.ntp_to olbar);
285 toolbar.getRecentTabsButton().setOnClickListener(new View.OnClickListene r() {
286 @Override
287 public void onClick(View v) {
288 mManager.navigateToRecentTabs();
289 }
290 });
291 toolbar.getBookmarksButton().setOnClickListener(new View.OnClickListener () {
292 @Override
293 public void onClick(View v) {
294 mManager.navigateToBookmarks();
295 }
296 });
297
298 initializeSearchBoxScrollHandling();
299 addOnLayoutChangeListener(this);
300 setSearchProviderHasLogo(searchProviderHasLogo);
301
302 mPendingLoadTasks++;
303 mManager.setMostVisitedURLsObserver(this,
304 mMostVisitedDesign.getNumberOfTiles(searchProviderHasLogo));
305
306 if (mManager.shouldShowOptOutPromo()) showOptOutPromo();
307 }
308
309 private int getTabsMovedIllustration() {
310 switch (Build.MANUFACTURER.toLowerCase(Locale.US)) {
311 case "samsung":
312 return R.drawable.tabs_moved_samsung;
313 case "htc":
314 return R.drawable.tabs_moved_htc;
315 default:
316 return R.drawable.tabs_moved_nexus;
317 }
318 }
319
320 private void showOptOutPromo() {
321 ViewStub optOutPromoStub = (ViewStub) findViewById(R.id.opt_out_promo_st ub);
322 mOptOutView = optOutPromoStub.inflate();
323 // Fill in opt-out text with Settings link
324 TextView optOutText = (TextView) mOptOutView.findViewById(R.id.opt_out_t ext);
325
326 ClickableSpan settingsLink = new ClickableSpan() {
327 @Override
328 public void onClick(View view) {
329 mManager.optOutPromoClicked(true);
330 }
331
332 // Change link formatting to use our blue control color and no under line
333 @Override
334 public void updateDrawState(android.text.TextPaint textPaint) {
335 textPaint.setColor(getContext().getResources().getColor(
336 R.color.light_active_color));
337 textPaint.setUnderlineText(false);
338 }
339 };
340
341 optOutText.setText(SpanApplier.applySpans(
342 getContext().getString(R.string.tabs_and_apps_opt_out_text),
343 new SpanInfo("<link>", "</link>", settingsLink)));
344 optOutText.setMovementMethod(LinkMovementMethod.getInstance());
345
346 ImageView illustration = (ImageView) mOptOutView.findViewById(R.id.tabs_ moved_illustration);
347 illustration.setImageResource(getTabsMovedIllustration());
348
349 mOptOutView.setVisibility(View.VISIBLE);
350 mMostVisitedLayout.setVisibility(View.GONE);
351
352 Button gotItButton = (Button) mOptOutView.findViewById(R.id.got_it_butto n);
353 gotItButton.setOnClickListener(new View.OnClickListener() {
354 @Override
355 public void onClick(View v) {
356 mOptOutView.setVisibility(View.GONE);
357 mMostVisitedLayout.setVisibility(View.VISIBLE);
358 mManager.optOutPromoClicked(false);
359 updateMostVisitedPlaceholderVisibility();
360 }
361 });
362 mManager.optOutPromoShown();
363 }
364
365 private void updateSearchBoxOnScroll() {
366 if (mDisableUrlFocusChangeAnimations) return;
367
368 float percentage = 0;
369 // During startup the view may not be fully initialized, so we only calc ulate the current
370 // percentage if some basic view properties are sane.
371 if (mScrollView.getHeight() != 0 && mSearchBoxView.getTop() != 0) {
372 int scrollY = mScrollView.getScrollY();
373 percentage = Math.max(
374 0f, Math.min(1f, scrollY / (float) mSearchBoxView.getTop())) ;
375 }
376
377 updateVisualsForToolbarTransition(percentage);
378
379 if (mSearchBoxScrollListener != null) {
380 mSearchBoxScrollListener.onScrollChanged(percentage);
381 }
382 }
383
384 private void initializeSearchBoxScrollHandling() {
385 final Runnable mSnapScrollRunnable = new Runnable() {
386 @Override
387 public void run() {
388 if (!mPendingSnapScroll) return;
389 int scrollY = mScrollView.getScrollY();
390 int dividerTop = mMostVisitedLayout.getTop() - mContentView.getP addingTop();
391 if (scrollY > 0 && scrollY < dividerTop) {
392 mScrollView.smoothScrollTo(0, scrollY < (dividerTop / 2) ? 0 : dividerTop);
393 }
394 mPendingSnapScroll = false;
395 }
396 };
397 mScrollView.setOnScrollListener(new NewTabScrollView.OnScrollListener() {
398 @Override
399 public void onScrollChanged(int l, int t, int oldl, int oldt) {
400 if (mPendingSnapScroll) {
401 mScrollView.removeCallbacks(mSnapScrollRunnable);
402 mScrollView.postDelayed(mSnapScrollRunnable, SNAP_SCROLL_DEL AY_MS);
403 }
404 updateSearchBoxOnScroll();
405 }
406 });
407 mScrollView.setOnTouchListener(new OnTouchListener() {
408 @Override
409 @SuppressLint("ClickableViewAccessibility")
410 public boolean onTouch(View v, MotionEvent event) {
411 if (mScrollView.getHandler() == null) return false;
412 mScrollView.removeCallbacks(mSnapScrollRunnable);
413
414 if (event.getActionMasked() == MotionEvent.ACTION_CANCEL
415 || event.getActionMasked() == MotionEvent.ACTION_UP) {
416 mPendingSnapScroll = true;
417 mScrollView.postDelayed(mSnapScrollRunnable, SNAP_SCROLL_DEL AY_MS);
418 } else {
419 mPendingSnapScroll = false;
420 }
421 return false;
422 }
423 });
424 }
425
426 /**
427 * Decrements the count of pending load tasks and notifies the manager when the page load
428 * is complete.
429 */
430 private void loadTaskCompleted() {
431 assert mPendingLoadTasks > 0;
432 mPendingLoadTasks--;
433 if (mPendingLoadTasks == 0) {
434 if (mLoadHasCompleted) {
435 assert false;
436 } else {
437 mLoadHasCompleted = true;
438 mManager.onLoadingComplete();
439 mMostVisitedDesign.onLoadingComplete();
440 // Load the logo after everything else is finished, since it's l ower priority.
441 loadSearchProviderLogo();
442 }
443 }
444 }
445
446 /**
447 * Loads the search provider logo (e.g. Google doodle), if any.
448 */
449 private void loadSearchProviderLogo() {
450 mManager.getSearchProviderLogo(new LogoObserver() {
451 @Override
452 public void onLogoAvailable(Logo logo, boolean fromCache) {
453 if (logo == null && fromCache) return;
454 mSearchProviderLogoView.setMananger(mManager);
455 mSearchProviderLogoView.updateLogo(logo);
456 mSnapshotMostVisitedChanged = true;
457 }
458 });
459 }
460
461 /**
462 * Changes the layout depending on whether the selected search provider (e.g . Google, Bing)
463 * has a logo.
464 * @param hasLogo Whether the search provider has a logo.
465 */
466 public void setSearchProviderHasLogo(boolean hasLogo) {
467 if (hasLogo == mSearchProviderHasLogo) return;
468 mSearchProviderHasLogo = hasLogo;
469
470 mMostVisitedDesign.setSearchProviderHasLogo(mMostVisitedLayout, hasLogo) ;
471
472 if (!hasLogo) setUrlFocusChangeAnimationPercentInternal(0);
473
474 // Hide or show all the views above the Most Visited items.
475 int visibility = hasLogo ? View.VISIBLE : View.GONE;
476 int childCount = mContentView.getChildCount();
477 for (int i = 0; i < childCount; i++) {
478 View child = mContentView.getChildAt(i);
479 if (child == mMostVisitedLayout) break;
480 // Don't change the visibility of a ViewStub as that will automagica lly inflate it.
481 if (child instanceof ViewStub) continue;
482 child.setVisibility(visibility);
483 }
484
485 updateMostVisitedPlaceholderVisibility();
486
487 if (hasLogo) setUrlFocusChangeAnimationPercent(mUrlFocusChangePercent);
488 mSnapshotMostVisitedChanged = true;
489 }
490
491 /**
492 * Updates whether the NewTabPage should animate on URL focus changes.
493 * @param disable Whether to disable the animations.
494 */
495 void setUrlFocusAnimationsDisabled(boolean disable) {
496 if (disable == mDisableUrlFocusChangeAnimations) return;
497 mDisableUrlFocusChangeAnimations = disable;
498 if (!disable) setUrlFocusChangeAnimationPercent(mUrlFocusChangePercent);
499 }
500
501 /**
502 * @return Whether URL focus animations are currently disabled.
503 */
504 boolean urlFocusAnimationsDisabled() {
505 return mDisableUrlFocusChangeAnimations;
506 }
507
508 /**
509 * Specifies the percentage the URL is focused during an animation. 1.0 spe cifies that the URL
510 * bar has focus and has completed the focus animation. 0 is when the URL b ar is does not have
511 * any focus.
512 *
513 * @param percent The percentage of the URL bar focus animation.
514 */
515 void setUrlFocusChangeAnimationPercent(float percent) {
516 mUrlFocusChangePercent = percent;
517 if (!mDisableUrlFocusChangeAnimations && mSearchProviderHasLogo) {
518 setUrlFocusChangeAnimationPercentInternal(percent);
519 }
520 }
521
522 /**
523 * @return The percentage that the URL bar is focused during an animation.
524 */
525 @VisibleForTesting
526 float getUrlFocusChangeAnimationPercent() {
527 return mUrlFocusChangePercent;
528 }
529
530 /**
531 * Unconditionally sets the percentage the URL is focused during an animatio n, without updating
532 * mUrlFocusChangePercent.
533 * @see #setUrlFocusChangeAnimationPercent
534 */
535 private void setUrlFocusChangeAnimationPercentInternal(float percent) {
536 mContentView.setTranslationY(percent * (-mMostVisitedLayout.getTop()
537 + mScrollView.getScrollY() + mContentView.getPaddingTop()));
538 updateVisualsForToolbarTransition(percent);
539 }
540
541 private void updateVisualsForToolbarTransition(float transitionPercentage) {
542 // Complete the full alpha transition in the first 40% of the animation.
543 float searchUiAlpha =
544 transitionPercentage >= 0.4f ? 0f : (0.4f - transitionPercentage ) * 2.5f;
545 // Ensure there are no rounding issues when the animation percent is 0.
546 if (transitionPercentage == 0f) searchUiAlpha = 1f;
547
548 mSearchProviderLogoView.setAlpha(searchUiAlpha);
549 mSearchBoxView.setAlpha(searchUiAlpha);
550 }
551
552 /**
553 * Get the bounds of the search box in relation to the top level NewTabPage view.
554 *
555 * @param originalBounds The bounding region of the search box without exter nal transforms
556 * applied. The delta between this and the transforme d bounds determines
557 * the amount of scroll applied to this view.
558 * @param transformedBounds The bounding region of the search box including any transforms
559 * applied by the parent view hierarchy up to the N ewTabPage view.
560 * This more accurately reflects the current drawin g location of the
561 * search box.
562 */
563 void getSearchBoxBounds(Rect originalBounds, Rect transformedBounds) {
564 int searchBoxX = (int) mSearchBoxView.getX();
565 int searchBoxY = (int) mSearchBoxView.getY();
566 originalBounds.set(
567 searchBoxX + mSearchBoxView.getPaddingLeft(),
568 searchBoxY + mSearchBoxView.getPaddingTop(),
569 searchBoxX + mSearchBoxView.getWidth() - mSearchBoxView.getPaddi ngRight(),
570 searchBoxY + mSearchBoxView.getHeight() - mSearchBoxView.getPadd ingBottom());
571
572 transformedBounds.set(originalBounds);
573 View view = (View) mSearchBoxView.getParent();
574 while (view != null) {
575 transformedBounds.offset(-view.getScrollX(), -view.getScrollY());
576 if (view == this) break;
577 transformedBounds.offset((int) view.getX(), (int) view.getY());
578 view = (View) view.getParent();
579 }
580 }
581
582 /**
583 * Sets the listener for search box scroll changes.
584 * @param listener The listener to be notified on changes.
585 */
586 void setSearchBoxScrollListener(OnSearchBoxScrollListener listener) {
587 mSearchBoxScrollListener = listener;
588 if (mSearchBoxScrollListener != null) updateSearchBoxOnScroll();
589 }
590
591 @Override
592 protected void onAttachedToWindow() {
593 super.onAttachedToWindow();
594 assert mManager != null;
595
596 if (mFirstShow) {
597 loadTaskCompleted();
598 mFirstShow = false;
599 } else {
600 // Trigger a scroll update when reattaching the window to signal the toolbar that
601 // it needs to reset the NTP state.
602 if (mManager.isLocationBarShownInNTP()) updateSearchBoxOnScroll();
603 }
604 }
605
606 @Override
607 protected void onDetachedFromWindow() {
608 super.onDetachedFromWindow();
609 setUrlFocusChangeAnimationPercent(0f);
610 }
611
612 @Override
613 protected void onWindowVisibilityChanged(int visibility) {
614 super.onWindowVisibilityChanged(visibility);
615
616 if (visibility == VISIBLE) {
617 mVoiceSearchButton.setVisibility(
618 FeatureUtilities.isRecognitionIntentPresent(getContext(), tr ue)
619 ? VISIBLE : GONE);
620 }
621 }
622
623 @Override
624 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
625 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
626
627 // Make the search box and logo the same width as the most visited tiles .
628 if (mMostVisitedLayout.getVisibility() != GONE) {
629 int mostVisitedWidth = MeasureSpec.makeMeasureSpec(
630 mMostVisitedLayout.getMeasuredWidth(), MeasureSpec.EXACTLY);
631 int searchBoxHeight = MeasureSpec.makeMeasureSpec(
632 mSearchBoxView.getMeasuredHeight(), MeasureSpec.EXACTLY);
633 int logoHeight = MeasureSpec.makeMeasureSpec(
634 mSearchProviderLogoView.getMeasuredHeight(), MeasureSpec.EXA CTLY);
635 mSearchBoxView.measure(mostVisitedWidth, searchBoxHeight);
636 mSearchProviderLogoView.measure(mostVisitedWidth, logoHeight);
637 }
638 }
639
640 /**
641 * @see org.chromium.chrome.browser.compositor.layouts.content.
642 * InvalidationAwareThumbnailProvider#shouldCaptureThumbnail()
643 */
644 boolean shouldCaptureThumbnail() {
645 if (getWidth() == 0 || getHeight() == 0) return false;
646
647 return mSnapshotMostVisitedChanged
648 || getWidth() != mSnapshotWidth
649 || getHeight() != mSnapshotHeight
650 || mScrollView.getScrollY() != mSnapshotScrollY;
651 }
652
653 /**
654 * @see org.chromium.chrome.browser.compositor.layouts.content.
655 * InvalidationAwareThumbnailProvider#captureThumbnail(Canvas)
656 */
657 void captureThumbnail(Canvas canvas) {
658 mSearchProviderLogoView.endAnimation();
659 ViewUtils.captureBitmap(this, canvas);
660 mSnapshotWidth = getWidth();
661 mSnapshotHeight = getHeight();
662 mSnapshotScrollY = mScrollView.getScrollY();
663 mSnapshotMostVisitedChanged = false;
664 }
665
666 // OnLayoutChangeListener overrides
667
668 @Override
669 public void onLayoutChange(View v, int left, int top, int right, int bottom,
670 int oldLeft, int oldTop, int oldRight, int oldBottom) {
671 int oldWidth = oldRight - oldLeft;
672 int newWidth = right - left;
673 if (oldWidth == newWidth) return;
674
675 // Re-apply the url focus change amount after a rotation to ensure the v iews are correctly
676 // placed with their new layout configurations.
677 setUrlFocusChangeAnimationPercent(mUrlFocusChangePercent);
678 }
679
680 // MostVisitedURLsObserver implementation
681
682 @Override
683 public void onMostVisitedURLsAvailable(String[] titles, String[] urls) {
684 mMostVisitedLayout.removeAllViews();
685
686 MostVisitedItem[] oldItems = mMostVisitedItems;
687 int oldItemCount = oldItems == null ? 0 : oldItems.length;
688 mMostVisitedItems = new MostVisitedItem[titles.length];
689
690 final boolean isInitialLoad = !mHasReceivedMostVisitedSites;
691 LayoutInflater inflater = LayoutInflater.from(getContext());
692
693 // Add the most visited items to the page.
694 for (int i = 0; i < titles.length; i++) {
695 final String url = urls[i];
696 final String title = titles[i];
697
698 // Look for an existing item to reuse.
699 MostVisitedItem item = null;
700 for (int j = 0; j < oldItemCount; j++) {
701 MostVisitedItem oldItem = oldItems[j];
702 if (oldItem != null && TextUtils.equals(url, oldItem.getUrl())
703 && TextUtils.equals(title, oldItem.getTitle())) {
704 item = oldItem;
705 item.setIndex(i);
706 oldItems[j] = null;
707 break;
708 }
709 }
710
711 // If nothing can be reused, create a new item.
712 if (item == null) {
713 String displayTitle = getTitleForDisplay(title, url);
714 View view = mMostVisitedDesign.createMostVisitedItemView(inflate r, url, title,
715 displayTitle, i, isInitialLoad);
716 item = new MostVisitedItem(mManager, title, url, i, view);
717 }
718
719 mMostVisitedItems[i] = item;
720 mMostVisitedLayout.addView(item.getView());
721 }
722
723 mHasReceivedMostVisitedSites = true;
724 updateMostVisitedPlaceholderVisibility();
725
726 if (isInitialLoad) {
727 loadTaskCompleted();
728 // The page contents are initially hidden; otherwise they'll be draw n centered on the
729 // page before the most visited sites are available and then jump up wards to make space
730 // once the most visited sites are available.
731 mContentView.setVisibility(View.VISIBLE);
732 }
733 mSnapshotMostVisitedChanged = true;
734 }
735
736 /**
737 * Shows the most visited placeholder ("Nothing to see here") if there are n o most visited
738 * items and there is no search provider logo.
739 */
740 private void updateMostVisitedPlaceholderVisibility() {
741 boolean showPlaceholder = mHasReceivedMostVisitedSites
742 && !mManager.shouldShowOptOutPromo()
743 && mMostVisitedLayout.getChildCount() == 0
744 && !mSearchProviderHasLogo;
745
746 mNoSearchLogoSpacer.setVisibility(
747 (mSearchProviderHasLogo || showPlaceholder) ? View.GONE : View.I NVISIBLE);
748
749 if (showPlaceholder) {
750 if (mMostVisitedPlaceholder == null) {
751 ViewStub mostVisitedPlaceholderStub = (ViewStub) findViewById(
752 R.id.most_visited_placeholder_stub);
753 mMostVisitedPlaceholder = mostVisitedPlaceholderStub.inflate();
754 }
755 mMostVisitedLayout.setVisibility(GONE);
756 mMostVisitedPlaceholder.setVisibility(VISIBLE);
757 return;
758 } else if (mMostVisitedPlaceholder != null) {
759 mMostVisitedLayout.setVisibility(VISIBLE);
760 mMostVisitedPlaceholder.setVisibility(GONE);
761 }
762 }
763
764 /**
765 * Interface for creating the most visited layout and tiles.
766 * TODO(newt): delete this once a single design has been chosen.
767 */
768 private interface MostVisitedDesign {
769 int getNumberOfTiles(boolean searchProviderHasLogo);
770 int getMostVisitedLayoutId();
771 void setSearchProviderHasLogo(View mostVisitedLayout, boolean hasLogo);
772 View createMostVisitedItemView(LayoutInflater inflater, String url, Stri ng title,
773 String displayTitle, int index, boolean isInitialLoad);
774 void onLoadingComplete();
775 }
776
777 /**
778 * The old most visited design, where each tile shows a thumbnail of the pag e, a small favicon,
779 * and the title.
780 */
781 private class ThumbnailMostVisitedDesign implements MostVisitedDesign {
782
783 private static final int NUM_TILES = 6;
784 private static final int FAVICON_CORNER_RADIUS_DP = 2;
785 private static final int FAVICON_TEXT_SIZE_DP = 10;
786 private static final int FAVICON_BACKGROUND_COLOR = 0xff969696;
787
788 private int mDesiredFaviconSize;
789 private RoundedIconGenerator mFaviconGenerator;
790
791 ThumbnailMostVisitedDesign(Context context) {
792 Resources res = context.getResources();
793 mDesiredFaviconSize = res.getDimensionPixelSize(R.dimen.most_visited _favicon_size);
794 int desiredFaviconSizeDp = Math.round(
795 mDesiredFaviconSize / res.getDisplayMetrics().density);
796 mFaviconGenerator = new RoundedIconGenerator(
797 context, desiredFaviconSizeDp, desiredFaviconSizeDp, FAVICON _CORNER_RADIUS_DP,
798 FAVICON_BACKGROUND_COLOR, FAVICON_TEXT_SIZE_DP);
799 }
800
801 @Override
802 public int getNumberOfTiles(boolean searchProviderHasLogo) {
803 return NUM_TILES;
804 }
805
806 @Override
807 public int getMostVisitedLayoutId() {
808 return R.layout.most_visited_layout;
809 }
810
811 @Override
812 public void setSearchProviderHasLogo(View mostVisitedLayout, boolean has Logo) {}
813
814 @Override
815 public View createMostVisitedItemView(LayoutInflater inflater, final Str ing url,
816 String title, String displayTitle, int index, final boolean isIn itialLoad) {
817 final MostVisitedItemView view = (MostVisitedItemView) inflater.infl ate(
818 R.layout.most_visited_item, mMostVisitedLayout, false);
819 view.init(displayTitle);
820
821 ThumbnailCallback thumbnailCallback = new ThumbnailCallback() {
822 @Override
823 public void onMostVisitedURLsThumbnailAvailable(Bitmap thumbnail ) {
824 view.setThumbnail(thumbnail);
825 mSnapshotMostVisitedChanged = true;
826 if (isInitialLoad) loadTaskCompleted();
827 }
828 };
829 if (isInitialLoad) mPendingLoadTasks++;
830 mManager.getURLThumbnail(url, thumbnailCallback);
831
832 FaviconImageCallback faviconCallback = new FaviconImageCallback() {
833 @Override
834 public void onFaviconAvailable(Bitmap image, String iconUrl) {
835 if (image == null) {
836 image = mFaviconGenerator.generateIconForUrl(url);
837 }
838 view.setFavicon(image);
839 mSnapshotMostVisitedChanged = true;
840 if (isInitialLoad) loadTaskCompleted();
841 }
842 };
843 if (isInitialLoad) mPendingLoadTasks++;
844 mManager.getLocalFaviconImageForURL(url, mDesiredFaviconSize, favico nCallback);
845
846 return view;
847 }
848
849 @Override
850 public void onLoadingComplete() {}
851 }
852
853 /**
854 * The new-fangled design for most visited tiles, where each tile shows a la rge icon and title.
855 */
856 private class IconMostVisitedDesign implements MostVisitedDesign {
857
858 private static final int NUM_TILES = 8;
859 private static final int NUM_TILES_NO_LOGO = 12;
860
861 private static final int ICON_CORNER_RADIUS_DP = 4;
862 private static final int ICON_TEXT_SIZE_DP = 20;
863 private static final int ICON_BACKGROUND_COLOR = 0xff787878;
864 private static final int ICON_MIN_SIZE_PX = 48;
865
866 private int mMinIconSize;
867 private int mDesiredIconSize;
868 private RoundedIconGenerator mIconGenerator;
869
870 private int mNumGrayIcons;
871 private int mNumColorIcons;
872 private int mNumRealIcons;
873
874 IconMostVisitedDesign(Context context) {
875 Resources res = context.getResources();
876 mDesiredIconSize = res.getDimensionPixelSize(R.dimen.icon_most_visit ed_icon_size);
877 // On ldpi devices, mDesiredIconSize could be even smaller than ICON _MIN_SIZE_PX.
878 mMinIconSize = Math.min(mDesiredIconSize, ICON_MIN_SIZE_PX);
879 int desiredIconSizeDp = Math.round(
880 mDesiredIconSize / res.getDisplayMetrics().density);
881 mIconGenerator = new RoundedIconGenerator(
882 context, desiredIconSizeDp, desiredIconSizeDp, ICON_CORNER_R ADIUS_DP,
883 ICON_BACKGROUND_COLOR, ICON_TEXT_SIZE_DP);
884 }
885
886 @Override
887 public int getNumberOfTiles(boolean searchProviderHasLogo) {
888 return searchProviderHasLogo ? NUM_TILES : NUM_TILES_NO_LOGO;
889 }
890
891 @Override
892 public int getMostVisitedLayoutId() {
893 return R.layout.icon_most_visited_layout;
894 }
895
896 @Override
897 public void setSearchProviderHasLogo(View mostVisitedLayout, boolean has Logo) {
898 if (hasLogo) {
899 int paddingTop = getResources().getDimensionPixelSize(
900 R.dimen.icon_most_visited_layout_padding_top);
901 int paddingSide = getResources().getDimensionPixelSize(
902 R.dimen.icon_most_visited_layout_padding_side);
903 mostVisitedLayout.setPadding(paddingSide, paddingTop, paddingSid e, 0);
904 } else {
905 int paddingTop = getResources().getDimensionPixelSize(
906 R.dimen.icon_most_visited_layout_no_logo_padding_top);
907 mostVisitedLayout.setPadding(0, paddingTop, 0, 0);
908 }
909 }
910
911 @Override
912 public View createMostVisitedItemView(LayoutInflater inflater, final Str ing url,
913 String title, String displayTitle, int index, final boolean isIn itialLoad) {
914 final IconMostVisitedItemView view = (IconMostVisitedItemView) infla ter.inflate(
915 R.layout.icon_most_visited_item, mMostVisitedLayout, false);
916 view.setTitle(displayTitle);
917
918 LargeIconCallback iconCallback = new LargeIconCallback() {
919 @Override
920 public void onLargeIconAvailable(Bitmap icon, int fallbackColor) {
921 if (icon == null) {
922 mIconGenerator.setBackgroundColor(fallbackColor);
923 icon = mIconGenerator.generateIconForUrl(url);
924 view.setIcon(new BitmapDrawable(getResources(), icon));
925 if (isInitialLoad) {
926 if (fallbackColor == ICON_BACKGROUND_COLOR) {
927 mNumGrayIcons++;
928 } else {
929 mNumColorIcons++;
930 }
931 }
932 } else {
933 RoundedBitmapDrawable roundedIcon = RoundedBitmapDrawabl eFactory.create(
934 getResources(), icon);
935 int cornerRadius = Math.round(ICON_CORNER_RADIUS_DP
936 * getResources().getDisplayMetrics().density * i con.getWidth()
937 / mDesiredIconSize);
938 roundedIcon.setCornerRadius(cornerRadius);
939 roundedIcon.setAntiAlias(true);
940 roundedIcon.setFilterBitmap(true);
941 view.setIcon(roundedIcon);
942 if (isInitialLoad) mNumRealIcons++;
943 }
944 mSnapshotMostVisitedChanged = true;
945 if (isInitialLoad) loadTaskCompleted();
946 }
947 };
948 if (isInitialLoad) mPendingLoadTasks++;
949 mManager.getLargeIconForUrl(url, mMinIconSize, iconCallback);
950
951 return view;
952 }
953
954 @Override
955 public void onLoadingComplete() {
956 RecordHistogram.recordCount100Histogram("NewTabPage.IconsGray", mNum GrayIcons);
957 RecordHistogram.recordCount100Histogram("NewTabPage.IconsColor", mNu mColorIcons);
958 RecordHistogram.recordCount100Histogram("NewTabPage.IconsReal", mNum RealIcons);
959 }
960 }
961 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698