Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. | 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package org.chromium.chrome.browser.suggestions; | 5 package org.chromium.chrome.browser.suggestions; |
| 6 | 6 |
| 7 import android.content.Context; | 7 import android.content.Context; |
| 8 import android.content.res.Resources; | 8 import android.content.res.Resources; |
| 9 import android.graphics.Bitmap; | 9 import android.graphics.Bitmap; |
| 10 import android.graphics.BitmapFactory; | 10 import android.graphics.BitmapFactory; |
| 11 import android.graphics.Color; | 11 import android.graphics.Color; |
| 12 import android.graphics.drawable.BitmapDrawable; | 12 import android.graphics.drawable.BitmapDrawable; |
| 13 import android.support.annotation.Nullable; | 13 import android.support.annotation.Nullable; |
| 14 import android.support.v4.graphics.drawable.RoundedBitmapDrawable; | 14 import android.support.v4.graphics.drawable.RoundedBitmapDrawable; |
| 15 import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory; | 15 import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory; |
| 16 import android.view.ContextMenu; | 16 import android.view.ContextMenu; |
| 17 import android.view.ContextMenu.ContextMenuInfo; | 17 import android.view.ContextMenu.ContextMenuInfo; |
| 18 import android.view.LayoutInflater; | 18 import android.view.LayoutInflater; |
| 19 import android.view.View; | 19 import android.view.View; |
| 20 import android.view.View.OnClickListener; | 20 import android.view.View.OnClickListener; |
| 21 import android.view.View.OnCreateContextMenuListener; | 21 import android.view.View.OnCreateContextMenuListener; |
| 22 import android.view.ViewGroup; | 22 import android.view.ViewGroup; |
| 23 | 23 |
| 24 import org.chromium.base.ApiCompatibilityUtils; | 24 import org.chromium.base.ApiCompatibilityUtils; |
| 25 import org.chromium.base.Callback; | |
| 25 import org.chromium.base.Log; | 26 import org.chromium.base.Log; |
| 26 import org.chromium.base.VisibleForTesting; | 27 import org.chromium.base.VisibleForTesting; |
| 27 import org.chromium.chrome.R; | 28 import org.chromium.chrome.R; |
| 28 import org.chromium.chrome.browser.ChromeFeatureList; | 29 import org.chromium.chrome.browser.ChromeFeatureList; |
| 29 import org.chromium.chrome.browser.favicon.LargeIconBridge.LargeIconCallback; | 30 import org.chromium.chrome.browser.favicon.LargeIconBridge.LargeIconCallback; |
| 30 import org.chromium.chrome.browser.ntp.ContextMenuManager; | 31 import org.chromium.chrome.browser.ntp.ContextMenuManager; |
| 31 import org.chromium.chrome.browser.ntp.ContextMenuManager.ContextMenuItemId; | 32 import org.chromium.chrome.browser.ntp.ContextMenuManager.ContextMenuItemId; |
| 32 import org.chromium.chrome.browser.ntp.MostVisitedTileType; | 33 import org.chromium.chrome.browser.ntp.MostVisitedTileType; |
| 33 import org.chromium.chrome.browser.offlinepages.OfflinePageBridge; | 34 import org.chromium.chrome.browser.offlinepages.OfflinePageBridge; |
| 34 import org.chromium.chrome.browser.widget.RoundedIconGenerator; | 35 import org.chromium.chrome.browser.widget.RoundedIconGenerator; |
| 35 import org.chromium.ui.mojom.WindowOpenDisposition; | 36 import org.chromium.ui.mojom.WindowOpenDisposition; |
| 36 | 37 |
| 37 import java.util.Arrays; | 38 import java.util.Arrays; |
| 38 import java.util.HashMap; | 39 import java.util.HashMap; |
| 39 import java.util.HashSet; | 40 import java.util.HashSet; |
| 40 import java.util.Map; | 41 import java.util.Map; |
| 41 import java.util.Set; | 42 import java.util.Set; |
| 42 | 43 |
| 43 /** | 44 /** |
| 44 * The model and controller for a group of site suggestion tiles. | 45 * The model and controller for a group of site suggestion tiles. |
| 45 */ | 46 */ |
| 46 public class TileGroup implements MostVisitedSites.Observer { | 47 public class TileGroup implements MostVisitedSites.Observer { |
| 47 /** | 48 /** |
| 48 * Performs work in other parts of the system that the {@link TileGroup} sho uld not know about. | 49 * Performs work in other parts of the system that the {@link TileGroup} sho uld not know about. |
| 49 */ | 50 */ |
| 50 public interface Delegate { | 51 public interface Delegate { |
| 51 void removeMostVisitedItem(Tile tile); | 52 /** |
| 53 * @param tile The tile corresponding to the most visited item to remove . | |
| 54 * @param removalUndoneCallback The callback to invoke if the removal is reverted. | |
| 55 */ | |
| 56 void removeMostVisitedItem(Tile tile, @Nullable Callback<String> removal UndoneCallback); | |
|
Michael van Ouwerkerk
2017/03/17 15:53:07
Why is the callback nullable? Please document what
dgn
2017/03/17 16:13:45
I intended the callback to be optional but there i
| |
| 52 | 57 |
| 53 void openMostVisitedItem(int windowDisposition, Tile tile); | 58 void openMostVisitedItem(int windowDisposition, Tile tile); |
| 54 | 59 |
| 55 /** | 60 /** |
| 56 * Gets the list of most visited sites. | 61 * Gets the list of most visited sites. |
| 57 * @param observer The observer to be notified with the list of sites. | 62 * @param observer The observer to be notified with the list of sites. |
| 58 * @param maxResults The maximum number of sites to retrieve. | 63 * @param maxResults The maximum number of sites to retrieve. |
| 59 */ | 64 */ |
| 60 void setMostVisitedSitesObserver(MostVisitedSites.Observer observer, int maxResults); | 65 void setMostVisitedSitesObserver(MostVisitedSites.Observer observer, int maxResults); |
| 61 | 66 |
| (...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 146 @Nullable | 151 @Nullable |
| 147 private Tile[] mPendingTileData; | 152 private Tile[] mPendingTileData; |
| 148 | 153 |
| 149 /** | 154 /** |
| 150 * URL of the most recently removed tile. Used to identify when a tile remov al is confirmed by | 155 * URL of the most recently removed tile. Used to identify when a tile remov al is confirmed by |
| 151 * the tile backend. | 156 * the tile backend. |
| 152 */ | 157 */ |
| 153 @Nullable | 158 @Nullable |
| 154 private String mPendingRemovalUrl; | 159 private String mPendingRemovalUrl; |
| 155 | 160 |
| 161 /** | |
| 162 * URL of the most recently added tile. Used to identify when a given tile's insertion is | |
| 163 * confirmed by the tile backend. This is relevant when a previously existin g tile is removed, | |
| 164 * then the user undoes the action and wants that tile back. | |
| 165 */ | |
| 166 @Nullable | |
| 167 private String mPendingInsertionUrl; | |
| 168 | |
| 156 private boolean mHasReceivedData; | 169 private boolean mHasReceivedData; |
| 157 | 170 |
| 158 /** | 171 /** |
| 159 * @param context Used for initialisation and resolving resources. | 172 * @param context Used for initialisation and resolving resources. |
| 160 * @param uiDelegate Delegate used to interact with the rest of the system. | 173 * @param uiDelegate Delegate used to interact with the rest of the system. |
| 161 * @param contextMenuManager Used to handle context menu invocations on the tiles. | 174 * @param contextMenuManager Used to handle context menu invocations on the tiles. |
| 162 * @param tileGroupDelegate Used for interactions with the Most Visited back end. | 175 * @param tileGroupDelegate Used for interactions with the Most Visited back end. |
| 163 * @param observer Will be notified of changes to the tile data. | 176 * @param observer Will be notified of changes to the tile data. |
| 164 * @param titleLines The number of text lines to use for each tile title. | 177 * @param titleLines The number of text lines to use for each tile title. |
| 165 */ | 178 */ |
| (...skipping 23 matching lines...) Expand all Loading... | |
| 189 mUiDelegate.addDestructionObserver(mOfflineModelObserver); | 202 mUiDelegate.addDestructionObserver(mOfflineModelObserver); |
| 190 } else { | 203 } else { |
| 191 mOfflineModelObserver = null; | 204 mOfflineModelObserver = null; |
| 192 } | 205 } |
| 193 } | 206 } |
| 194 | 207 |
| 195 @Override | 208 @Override |
| 196 public void onMostVisitedURLsAvailable(final String[] titles, final String[] urls, | 209 public void onMostVisitedURLsAvailable(final String[] titles, final String[] urls, |
| 197 final String[] whitelistIconPaths, final int[] sources) { | 210 final String[] whitelistIconPaths, final int[] sources) { |
| 198 boolean removalCompleted = mPendingRemovalUrl != null; | 211 boolean removalCompleted = mPendingRemovalUrl != null; |
| 212 boolean insertionCompleted = mPendingInsertionUrl == null; | |
| 213 | |
| 199 Set<String> addedUrls = new HashSet<>(); | 214 Set<String> addedUrls = new HashSet<>(); |
| 200 mPendingTileData = new Tile[titles.length]; | 215 mPendingTileData = new Tile[titles.length]; |
| 201 for (int i = 0; i < titles.length; i++) { | 216 for (int i = 0; i < titles.length; i++) { |
| 202 assert urls[i] != null; // We assume everywhere that the url is not null. | 217 assert urls[i] != null; // We assume everywhere that the url is not null. |
| 203 | 218 |
| 204 // TODO(dgn): Add UMA to track the cause of https://crbug.com/690926 . Checking this | 219 // TODO(dgn): Add UMA to track the cause of https://crbug.com/690926 . Checking this |
| 205 // should not even be necessary as the backend is supposed to send n on dupes URLs. | 220 // should not even be necessary as the backend is supposed to send n on dupes URLs. |
| 206 if (addedUrls.contains(urls[i])) { | 221 if (addedUrls.contains(urls[i])) { |
| 207 assert false : "Incoming NTP Tiles are not unique. Dupe on " + u rls[i]; | 222 assert false : "Incoming NTP Tiles are not unique. Dupe on " + u rls[i]; |
| 208 continue; | 223 continue; |
| 209 } | 224 } |
| 210 | 225 |
| 211 mPendingTileData[i] = | 226 mPendingTileData[i] = |
| 212 new Tile(titles[i], urls[i], whitelistIconPaths[i], i, sourc es[i]); | 227 new Tile(titles[i], urls[i], whitelistIconPaths[i], i, sourc es[i]); |
| 213 addedUrls.add(urls[i]); | 228 addedUrls.add(urls[i]); |
| 214 | 229 |
| 215 if (urls[i].equals(mPendingRemovalUrl)) removalCompleted = false; | 230 if (urls[i].equals(mPendingRemovalUrl)) removalCompleted = false; |
| 231 if (urls[i].equals(mPendingInsertionUrl)) insertionCompleted = true; | |
| 216 } | 232 } |
| 217 | 233 |
| 218 if (removalCompleted) mPendingRemovalUrl = null; | 234 boolean expectedChangeCompleted = false; |
| 219 if (!mHasReceivedData || !mUiDelegate.isVisible() || removalCompleted) l oadTiles(); | 235 if (mPendingRemovalUrl != null && removalCompleted) { |
| 236 mPendingRemovalUrl = null; | |
| 237 expectedChangeCompleted = true; | |
| 238 } | |
| 239 if (mPendingInsertionUrl != null && insertionCompleted) { | |
| 240 mPendingInsertionUrl = null; | |
| 241 expectedChangeCompleted = true; | |
| 242 } | |
| 243 | |
| 244 if (!mHasReceivedData || !mUiDelegate.isVisible() || expectedChangeCompl eted) loadTiles(); | |
| 220 } | 245 } |
| 221 | 246 |
| 222 @Override | 247 @Override |
| 223 public void onIconMadeAvailable(String siteUrl) { | 248 public void onIconMadeAvailable(String siteUrl) { |
| 224 Tile tile = getTile(siteUrl); | 249 Tile tile = getTile(siteUrl); |
| 225 if (tile == null) return; // The tile might have been removed. | 250 if (tile == null) return; // The tile might have been removed. |
| 226 | 251 |
| 227 LargeIconCallback iconCallback = | 252 LargeIconCallback iconCallback = |
| 228 new LargeIconCallbackImpl(siteUrl, /* trackLoadTask = */ false); | 253 new LargeIconCallbackImpl(siteUrl, /* trackLoadTask = */ false); |
| 229 mUiDelegate.getLargeIconForUrl(siteUrl, mMinIconSize, iconCallback); | 254 mUiDelegate.getLargeIconForUrl(siteUrl, mMinIconSize, iconCallback); |
| (...skipping 199 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 429 } | 454 } |
| 430 | 455 |
| 431 @Override | 456 @Override |
| 432 public void removeItem() { | 457 public void removeItem() { |
| 433 Tile tile = getTile(mUrl); | 458 Tile tile = getTile(mUrl); |
| 434 if (tile == null) return; | 459 if (tile == null) return; |
| 435 | 460 |
| 436 // Note: This does not track all the removals, but will track the mo st recent one. If | 461 // Note: This does not track all the removals, but will track the mo st recent one. If |
| 437 // that removal is committed, it's good enough for change detection. | 462 // that removal is committed, it's good enough for change detection. |
| 438 mPendingRemovalUrl = mUrl; | 463 mPendingRemovalUrl = mUrl; |
| 439 mTileGroupDelegate.removeMostVisitedItem(tile); | 464 mTileGroupDelegate.removeMostVisitedItem(tile, new RemovalUndoneCall back()); |
| 440 } | 465 } |
| 441 | 466 |
| 442 @Override | 467 @Override |
| 443 public String getUrl() { | 468 public String getUrl() { |
| 444 return mUrl; | 469 return mUrl; |
| 445 } | 470 } |
| 446 | 471 |
| 447 @Override | 472 @Override |
| 448 public boolean isItemSupported(@ContextMenuItemId int menuItemId) { | 473 public boolean isItemSupported(@ContextMenuItemId int menuItemId) { |
| 449 return true; | 474 return true; |
| 450 } | 475 } |
| 451 | 476 |
| 452 @Override | 477 @Override |
| 453 public void onContextMenuCreated() {} | 478 public void onContextMenuCreated() {} |
| 454 | 479 |
| 455 @Override | 480 @Override |
| 456 public void onCreateContextMenu( | 481 public void onCreateContextMenu( |
| 457 ContextMenu contextMenu, View view, ContextMenuInfo contextMenuI nfo) { | 482 ContextMenu contextMenu, View view, ContextMenuInfo contextMenuI nfo) { |
| 458 mContextMenuManager.createContextMenu(contextMenu, view, this); | 483 mContextMenuManager.createContextMenu(contextMenu, view, this); |
| 459 } | 484 } |
| 460 } | 485 } |
| 461 | 486 |
| 487 private class RemovalUndoneCallback extends Callback<String> { | |
| 488 @Override | |
| 489 public void onResult(String result) { | |
| 490 mPendingInsertionUrl = result; | |
| 491 } | |
| 492 } | |
| 493 | |
| 462 private class OfflineModelObserver extends SuggestionsOfflineModelObserver<T ile> { | 494 private class OfflineModelObserver extends SuggestionsOfflineModelObserver<T ile> { |
| 463 public OfflineModelObserver(OfflinePageBridge bridge) { | 495 public OfflineModelObserver(OfflinePageBridge bridge) { |
| 464 super(bridge); | 496 super(bridge); |
| 465 } | 497 } |
| 466 | 498 |
| 467 @Override | 499 @Override |
| 468 public void onSuggestionOfflineIdChanged(Tile suggestion, @Nullable Long id) { | 500 public void onSuggestionOfflineIdChanged(Tile suggestion, @Nullable Long id) { |
| 469 // Retrieve a tile from the internal data, to make sure we don't upd ate a stale object. | 501 // Retrieve a tile from the internal data, to make sure we don't upd ate a stale object. |
| 470 Tile tile = getTile(suggestion.getUrl()); | 502 Tile tile = getTile(suggestion.getUrl()); |
| 471 if (tile == null) return; | 503 if (tile == null) return; |
| 472 | 504 |
| 473 boolean oldOfflineAvailable = tile.isOfflineAvailable(); | 505 boolean oldOfflineAvailable = tile.isOfflineAvailable(); |
| 474 tile.setOfflinePageOfflineId(id); | 506 tile.setOfflinePageOfflineId(id); |
| 475 | 507 |
| 476 // Only notify to update the view if there will be a visible change. | 508 // Only notify to update the view if there will be a visible change. |
| 477 if (oldOfflineAvailable == tile.isOfflineAvailable()) return; | 509 if (oldOfflineAvailable == tile.isOfflineAvailable()) return; |
| 478 mObserver.onTileOfflineBadgeVisibilityChanged(tile); | 510 mObserver.onTileOfflineBadgeVisibilityChanged(tile); |
| 479 } | 511 } |
| 480 | 512 |
| 481 @Override | 513 @Override |
| 482 public Iterable<Tile> getOfflinableSuggestions() { | 514 public Iterable<Tile> getOfflinableSuggestions() { |
| 483 return Arrays.asList(mTiles); | 515 return Arrays.asList(mTiles); |
| 484 } | 516 } |
| 485 } | 517 } |
| 486 } | 518 } |
| OLD | NEW |