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 |