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

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/suggestions/TileGroup.java

Issue 2651673010: 🍝🏠 Refactor MostVisited UI management, extract it for reuse in Home. (Closed)
Patch Set: Minor cleanups. Comments, order of members. Created 3 years, 11 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/src/org/chromium/chrome/browser/suggestions/TileGroup.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/TileGroup.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/TileGroup.java
new file mode 100644
index 0000000000000000000000000000000000000000..e5735c4cc666652431315c7cbac8bf3a7d40db9c
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/TileGroup.java
@@ -0,0 +1,365 @@
+// Copyright 2017 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.suggestions;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
+import android.support.annotation.Nullable;
+import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
+import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
+import android.text.TextUtils;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnCreateContextMenuListener;
+import android.view.ViewGroup;
+
+import org.chromium.base.ApiCompatibilityUtils;
+import org.chromium.base.Callback;
+import org.chromium.base.ContextUtils;
+import org.chromium.base.Log;
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.favicon.LargeIconBridge.LargeIconCallback;
+import org.chromium.chrome.browser.ntp.ContextMenuManager;
+import org.chromium.chrome.browser.ntp.ContextMenuManager.ContextMenuItemId;
+import org.chromium.chrome.browser.ntp.MostVisitedItemView;
+import org.chromium.chrome.browser.ntp.MostVisitedTileType;
+import org.chromium.chrome.browser.ntp.TitleUtil;
+import org.chromium.chrome.browser.profiles.MostVisitedSites.MostVisitedURLsObserver;
+import org.chromium.chrome.browser.widget.RoundedIconGenerator;
+import org.chromium.ui.mojom.WindowOpenDisposition;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * The model and controller for a group of site suggestion tiles.
+ */
+public class TileGroup implements MostVisitedURLsObserver {
+ /**
+ * Performs work in other parts of the system that the {@link TileGroup} should not know about.
+ */
+ public interface Delegate {
+ void removeMostVisitedItem(Tile tile);
+
+ void openMostVisitedItem(int windowDisposition, Tile tile);
+
+ /**
+ * Gets the list of most visited sites.
+ * @param observer The observer to be notified with the list of sites.
+ * @param maxResults The maximum number of sites to retrieve.
+ */
+ void setMostVisitedURLsObserver(MostVisitedURLsObserver observer, int maxResults);
+
+ /**
+ * Called when the NTP has completely finished loading (all views will be inflated
+ * and any dependent resources will have been loaded).
+ * @param tiles The tiles owned by the {@link TileGroup}. Used to record metrics.
+ */
+ void onLoadingComplete(Tile[] tiles);
+
+ /**
+ * To be called before this instance is abandoned to the garbage collector so it can do any
+ * necessary cleanups. This instance must not be used after this method is called.
+ */
+ void destroy();
+ }
+
+ /**
+ * An observer for events in the {@link TileGroup}.
+ */
+ public interface Observer {
+ /**
+ * Called when the first data has been received and processed.
+ */
+ void onInitialTileDataLoaded();
+
+ /**
+ * Called when any of the tile data has changed, such as an icon, url, or title.
+ */
+ void onTileDataChanged();
+
+ /**
+ * Called when the number of tiles has changed.
+ */
+ void onTileCountChanged();
+
+ /**
+ * Called when a tile icon has changed.
+ * @param tile The tile for which the icon has changed.
+ */
+ void onTileIconChanged(Tile tile);
+
+ /**
+ * Called when an asynchronous loading task has started.
+ */
+ void onLoadTaskAdded();
+
+ /**
+ * Called when an asynchronous loading task has completed.
+ */
+ void onLoadTaskCompleted();
+ }
+
+ private static final String TAG = "TileGroup";
+
+ private static final int ICON_CORNER_RADIUS_DP = 4;
+ private static final int ICON_TEXT_SIZE_DP = 20;
+ private static final int ICON_MIN_SIZE_PX = 48;
+
+ private final Context mContext;
+ private final SuggestionsUiDelegate mUiDelegate;
+ private final ContextMenuManager mContextMenuManager;
+ private final Delegate mTileGroupDelegate;
+ private final Observer mObserver;
+ private final RoundedIconGenerator mIconGenerator;
+
+ private Tile[] mTiles;
+ private int mMinIconSize;
+ private int mDesiredIconSize;
+ boolean mHasReceivedData;
+
+ public TileGroup(SuggestionsUiDelegate uiDelegate, ContextMenuManager contextMenuManager,
+ Delegate tileGroupDelegate, Observer observer) {
+ mContext = ContextUtils.getApplicationContext();
+ mUiDelegate = uiDelegate;
+ mContextMenuManager = contextMenuManager;
+ mTileGroupDelegate = tileGroupDelegate;
+ mObserver = observer;
+
+ Resources resources = mContext.getResources();
+ mDesiredIconSize = resources.getDimensionPixelSize(R.dimen.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 / resources.getDisplayMetrics().density);
+ int iconColor =
+ ApiCompatibilityUtils.getColor(resources, R.color.default_favicon_background_color);
+ mIconGenerator = new RoundedIconGenerator(mContext, desiredIconSizeDp, desiredIconSizeDp,
+ ICON_CORNER_RADIUS_DP, iconColor, ICON_TEXT_SIZE_DP);
+ }
+
+ @Override
+ public void onMostVisitedURLsAvailable(final String[] titles, final String[] urls,
+ final String[] whitelistIconPaths, final int[] sources) {
+ // If no tiles have been built yet, this is the initial load. Build the tiles immediately so
+ // the layout is stable during initial rendering. They can be
+ // replaced later if there are offline urls, but that will not affect the layout widths and
+ // heights. A stable layout enables reliable scroll position initialization.
+ if (!mHasReceivedData) {
+ buildTiles(titles, urls, whitelistIconPaths, null, sources);
+ }
+
+ // TODO(https://crbug.com/607573): We should show offline-available content in a nonblocking
+ // way so that responsiveness of the NTP does not depend on ready availability of offline
+ // pages.
+ Set<String> urlSet = new HashSet<>(Arrays.asList(urls));
+ mUiDelegate.getUrlsAvailableOffline(urlSet, new Callback<Set<String>>() {
+ @Override
+ public void onResult(Set<String> offlineUrls) {
+ buildTiles(titles, urls, whitelistIconPaths, offlineUrls, sources);
+ }
+ });
+ }
+
+ @Override
+ public void onIconMadeAvailable(String siteUrl) {
+ // Get a large icon for the matching tile.
+ for (Tile tile : mTiles) {
+ if (tile.getUrl().equals(siteUrl)) {
+ LargeIconCallback iconCallback =
+ new LargeIconCallbackImpl(tile, /* trackLoadTask = */ false);
+ mUiDelegate.getLargeIconForUrl(siteUrl, mMinIconSize, iconCallback);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Instructs this instance to start listening for data. The {@link TileGroup.Observer} may be
+ * called immediately if new data is received synchronously.
+ * @param maxResults The maximum number of sites to retrieve.
+ */
+ public void startObserving(int maxResults) {
+ mTileGroupDelegate.setMostVisitedURLsObserver(this, maxResults);
+ }
+
+ public void renderTileViews(ViewGroup parentView, boolean trackLoadTasks) {
+ parentView.removeAllViews();
+
+ for (final Tile tile : mTiles) {
+ View tileView = buildTileView(tile, parentView, trackLoadTasks);
+
+ tileView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mTileGroupDelegate.openMostVisitedItem(WindowOpenDisposition.CURRENT_TAB, tile);
+ }
+ });
+
+ tileView.setOnCreateContextMenuListener(new OnCreateContextMenuListener() {
+ @Override
+ public void onCreateContextMenu(
+ ContextMenu menu, View view, ContextMenuInfo menuInfo) {
+ mContextMenuManager.createContextMenu(
+ menu, view, new ContextMenuManager.Delegate() {
+ @Override
+ public void openItem(int windowDisposition) {
+ mTileGroupDelegate.openMostVisitedItem(windowDisposition, tile);
+ }
+
+ @Override
+ public void removeItem() {
+ mTileGroupDelegate.removeMostVisitedItem(tile);
+ }
+
+ @Override
+ public String getUrl() {
+ return tile.getUrl();
+ }
+
+ @Override
+ public boolean isItemSupported(@ContextMenuItemId int menuItemId) {
+ return true;
+ }
+
+ @Override
+ public void onContextMenuCreated() {}
+ });
+ }
+ });
+
+ parentView.addView(tileView);
+ }
+ }
+
+ public Tile[] getTiles() {
+ return mTiles;
+ }
+
+ public boolean hasReceivedData() {
+ return mHasReceivedData;
+ }
+
+ private void buildTiles(String[] titles, String[] urls, String[] whitelistIconPaths,
+ @Nullable Set<String> offlineUrls, int[] sources) {
+ Tile[] oldTiles = mTiles;
+ int oldTileCount = oldTiles == null ? 0 : oldTiles.length;
+ mTiles = new Tile[titles.length];
+
+ boolean isInitialLoad = !mHasReceivedData;
+ mHasReceivedData = true;
+
+ // Add the tile views to the layout.
+ for (int i = 0; i < titles.length; i++) {
+ String url = urls[i];
+ String title = titles[i];
+ String whitelistIconPath = whitelistIconPaths[i];
+ int source = sources[i];
+ boolean offlineAvailable = offlineUrls != null && offlineUrls.contains(url);
+
+ // Look for an existing tile to reuse.
dgn 2017/01/31 18:21:35 this is useful only to keep the drawable, right? h
Michael van Ouwerkerk 2017/02/03 12:37:48 We currently recreate the views and reload the ico
+ Tile tile = null;
+ for (int j = 0; j < oldTileCount; j++) {
+ Tile oldTile = oldTiles[j];
+ if (oldTile != null && TextUtils.equals(url, oldTile.getUrl())
+ && TextUtils.equals(title, oldTile.getTitle())
+ && offlineAvailable == oldTile.isOfflineAvailable()
+ && whitelistIconPath.equals(oldTile.getWhitelistIconPath())) {
+ tile = oldTile;
+ tile.setIndex(i);
+ oldTiles[j] = null;
+ break;
+ }
+ }
+
+ // If nothing can be reused, create a new tile.
+ if (tile == null) {
+ tile = new Tile(title, url, whitelistIconPath, offlineAvailable, i, source);
+ }
+ mTiles[i] = tile;
+ }
+
+ if (oldTileCount != mTiles.length) mObserver.onTileCountChanged();
+ if (isInitialLoad) {
+ mObserver.onLoadTaskCompleted();
+ mObserver.onInitialTileDataLoaded();
+ }
+ mObserver.onTileDataChanged();
+ }
+
+ private View buildTileView(Tile tile, ViewGroup parentView, boolean trackLoadTask) {
+ MostVisitedItemView view =
+ (MostVisitedItemView) LayoutInflater.from(parentView.getContext())
+ .inflate(R.layout.most_visited_item, parentView, false);
+ view.setTitle(TitleUtil.getTitleForDisplay(tile.getTitle(), tile.getUrl()));
+ view.setOfflineAvailable(tile.isOfflineAvailable());
+ view.setIcon(tile.getIcon());
+ view.setUrl(tile.getUrl());
+
+ LargeIconCallback iconCallback = new LargeIconCallbackImpl(tile, trackLoadTask);
+ if (trackLoadTask) mObserver.onLoadTaskAdded();
+ if (!loadWhitelistIcon(tile, iconCallback)) {
+ mUiDelegate.getLargeIconForUrl(tile.getUrl(), mMinIconSize, iconCallback);
+ }
+
+ return view;
+ }
+
+ private boolean loadWhitelistIcon(Tile tile, LargeIconCallback iconCallback) {
+ if (tile.getWhitelistIconPath().isEmpty()) return false;
+
+ // TODO(mvanouwerkerk): Consider using an AsyncTask to reduce rendering jank.
+ Bitmap bitmap = BitmapFactory.decodeFile(tile.getWhitelistIconPath());
+ if (bitmap == null) {
+ Log.d(TAG, "Image decoding failed: %s", tile.getWhitelistIconPath());
+ return false;
+ }
+ iconCallback.onLargeIconAvailable(bitmap, Color.BLACK, false);
+ return true;
+ }
+
+ private class LargeIconCallbackImpl implements LargeIconCallback {
+ private final Tile mTile;
+ private final boolean mTrackLoadTask;
+
+ private LargeIconCallbackImpl(Tile tile, boolean trackLoadTask) {
+ mTile = tile;
+ mTrackLoadTask = trackLoadTask;
+ }
+
+ @Override
+ public void onLargeIconAvailable(
+ @Nullable Bitmap icon, int fallbackColor, boolean isFallbackColorDefault) {
+ if (icon == null) {
+ mIconGenerator.setBackgroundColor(fallbackColor);
+ icon = mIconGenerator.generateIconForUrl(mTile.getUrl());
+ mTile.setIcon(new BitmapDrawable(mContext.getResources(), icon));
+ mTile.setTileType(isFallbackColorDefault ? MostVisitedTileType.ICON_DEFAULT
+ : MostVisitedTileType.ICON_COLOR);
+ } else {
+ RoundedBitmapDrawable roundedIcon =
+ RoundedBitmapDrawableFactory.create(mContext.getResources(), icon);
+ int cornerRadius = Math.round(ICON_CORNER_RADIUS_DP
+ * mContext.getResources().getDisplayMetrics().density * icon.getWidth()
+ / mDesiredIconSize);
+ roundedIcon.setCornerRadius(cornerRadius);
+ roundedIcon.setAntiAlias(true);
+ roundedIcon.setFilterBitmap(true);
+ mTile.setIcon(roundedIcon);
+ mTile.setTileType(MostVisitedTileType.ICON_REAL);
+ }
+ mObserver.onTileIconChanged(mTile);
+ if (mTrackLoadTask) mObserver.onLoadTaskCompleted();
+ }
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698