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. The |
| 55 * callback's argument is the URL being res
tored. |
| 56 */ |
| 57 void removeMostVisitedItem(Tile tile, Callback<String> removalUndoneCall
back); |
52 | 58 |
53 void openMostVisitedItem(int windowDisposition, Tile tile); | 59 void openMostVisitedItem(int windowDisposition, Tile tile); |
54 | 60 |
55 /** | 61 /** |
56 * Gets the list of most visited sites. | 62 * Gets the list of most visited sites. |
57 * @param observer The observer to be notified with the list of sites. | 63 * @param observer The observer to be notified with the list of sites. |
58 * @param maxResults The maximum number of sites to retrieve. | 64 * @param maxResults The maximum number of sites to retrieve. |
59 */ | 65 */ |
60 void setMostVisitedSitesObserver(MostVisitedSites.Observer observer, int
maxResults); | 66 void setMostVisitedSitesObserver(MostVisitedSites.Observer observer, int
maxResults); |
61 | 67 |
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
146 @Nullable | 152 @Nullable |
147 private Tile[] mPendingTileData; | 153 private Tile[] mPendingTileData; |
148 | 154 |
149 /** | 155 /** |
150 * URL of the most recently removed tile. Used to identify when a tile remov
al is confirmed by | 156 * URL of the most recently removed tile. Used to identify when a tile remov
al is confirmed by |
151 * the tile backend. | 157 * the tile backend. |
152 */ | 158 */ |
153 @Nullable | 159 @Nullable |
154 private String mPendingRemovalUrl; | 160 private String mPendingRemovalUrl; |
155 | 161 |
| 162 /** |
| 163 * URL of the most recently added tile. Used to identify when a given tile's
insertion is |
| 164 * confirmed by the tile backend. This is relevant when a previously existin
g tile is removed, |
| 165 * then the user undoes the action and wants that tile back. |
| 166 */ |
| 167 @Nullable |
| 168 private String mPendingInsertionUrl; |
| 169 |
156 private boolean mHasReceivedData; | 170 private boolean mHasReceivedData; |
157 | 171 |
158 /** | 172 /** |
159 * @param context Used for initialisation and resolving resources. | 173 * @param context Used for initialisation and resolving resources. |
160 * @param uiDelegate Delegate used to interact with the rest of the system. | 174 * @param uiDelegate Delegate used to interact with the rest of the system. |
161 * @param contextMenuManager Used to handle context menu invocations on the
tiles. | 175 * @param contextMenuManager Used to handle context menu invocations on the
tiles. |
162 * @param tileGroupDelegate Used for interactions with the Most Visited back
end. | 176 * @param tileGroupDelegate Used for interactions with the Most Visited back
end. |
163 * @param observer Will be notified of changes to the tile data. | 177 * @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. | 178 * @param titleLines The number of text lines to use for each tile title. |
165 */ | 179 */ |
(...skipping 23 matching lines...) Expand all Loading... |
189 mUiDelegate.addDestructionObserver(mOfflineModelObserver); | 203 mUiDelegate.addDestructionObserver(mOfflineModelObserver); |
190 } else { | 204 } else { |
191 mOfflineModelObserver = null; | 205 mOfflineModelObserver = null; |
192 } | 206 } |
193 } | 207 } |
194 | 208 |
195 @Override | 209 @Override |
196 public void onMostVisitedURLsAvailable(final String[] titles, final String[]
urls, | 210 public void onMostVisitedURLsAvailable(final String[] titles, final String[]
urls, |
197 final String[] whitelistIconPaths, final int[] sources) { | 211 final String[] whitelistIconPaths, final int[] sources) { |
198 boolean removalCompleted = mPendingRemovalUrl != null; | 212 boolean removalCompleted = mPendingRemovalUrl != null; |
| 213 boolean insertionCompleted = mPendingInsertionUrl == null; |
| 214 |
199 Set<String> addedUrls = new HashSet<>(); | 215 Set<String> addedUrls = new HashSet<>(); |
200 mPendingTileData = new Tile[titles.length]; | 216 mPendingTileData = new Tile[titles.length]; |
201 for (int i = 0; i < titles.length; i++) { | 217 for (int i = 0; i < titles.length; i++) { |
202 assert urls[i] != null; // We assume everywhere that the url is not
null. | 218 assert urls[i] != null; // We assume everywhere that the url is not
null. |
203 | 219 |
204 // TODO(dgn): Add UMA to track the cause of https://crbug.com/690926
. Checking this | 220 // 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. | 221 // should not even be necessary as the backend is supposed to send n
on dupes URLs. |
206 if (addedUrls.contains(urls[i])) { | 222 if (addedUrls.contains(urls[i])) { |
207 assert false : "Incoming NTP Tiles are not unique. Dupe on " + u
rls[i]; | 223 assert false : "Incoming NTP Tiles are not unique. Dupe on " + u
rls[i]; |
208 continue; | 224 continue; |
209 } | 225 } |
210 | 226 |
211 mPendingTileData[i] = | 227 mPendingTileData[i] = |
212 new Tile(titles[i], urls[i], whitelistIconPaths[i], i, sourc
es[i]); | 228 new Tile(titles[i], urls[i], whitelistIconPaths[i], i, sourc
es[i]); |
213 addedUrls.add(urls[i]); | 229 addedUrls.add(urls[i]); |
214 | 230 |
215 if (urls[i].equals(mPendingRemovalUrl)) removalCompleted = false; | 231 if (urls[i].equals(mPendingRemovalUrl)) removalCompleted = false; |
| 232 if (urls[i].equals(mPendingInsertionUrl)) insertionCompleted = true; |
216 } | 233 } |
217 | 234 |
218 if (removalCompleted) mPendingRemovalUrl = null; | 235 boolean expectedChangeCompleted = false; |
219 if (!mHasReceivedData || !mUiDelegate.isVisible() || removalCompleted) l
oadTiles(); | 236 if (mPendingRemovalUrl != null && removalCompleted) { |
| 237 mPendingRemovalUrl = null; |
| 238 expectedChangeCompleted = true; |
| 239 } |
| 240 if (mPendingInsertionUrl != null && insertionCompleted) { |
| 241 mPendingInsertionUrl = null; |
| 242 expectedChangeCompleted = true; |
| 243 } |
| 244 |
| 245 if (!mHasReceivedData || !mUiDelegate.isVisible() || expectedChangeCompl
eted) loadTiles(); |
220 } | 246 } |
221 | 247 |
222 @Override | 248 @Override |
223 public void onIconMadeAvailable(String siteUrl) { | 249 public void onIconMadeAvailable(String siteUrl) { |
224 Tile tile = getTile(siteUrl); | 250 Tile tile = getTile(siteUrl); |
225 if (tile == null) return; // The tile might have been removed. | 251 if (tile == null) return; // The tile might have been removed. |
226 | 252 |
227 LargeIconCallback iconCallback = | 253 LargeIconCallback iconCallback = |
228 new LargeIconCallbackImpl(siteUrl, /* trackLoadTask = */ false); | 254 new LargeIconCallbackImpl(siteUrl, /* trackLoadTask = */ false); |
229 mUiDelegate.getLargeIconForUrl(siteUrl, mMinIconSize, iconCallback); | 255 mUiDelegate.getLargeIconForUrl(siteUrl, mMinIconSize, iconCallback); |
(...skipping 199 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
429 } | 455 } |
430 | 456 |
431 @Override | 457 @Override |
432 public void removeItem() { | 458 public void removeItem() { |
433 Tile tile = getTile(mUrl); | 459 Tile tile = getTile(mUrl); |
434 if (tile == null) return; | 460 if (tile == null) return; |
435 | 461 |
436 // Note: This does not track all the removals, but will track the mo
st recent one. If | 462 // 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. | 463 // that removal is committed, it's good enough for change detection. |
438 mPendingRemovalUrl = mUrl; | 464 mPendingRemovalUrl = mUrl; |
439 mTileGroupDelegate.removeMostVisitedItem(tile); | 465 mTileGroupDelegate.removeMostVisitedItem(tile, new RemovalUndoneCall
back()); |
440 } | 466 } |
441 | 467 |
442 @Override | 468 @Override |
443 public String getUrl() { | 469 public String getUrl() { |
444 return mUrl; | 470 return mUrl; |
445 } | 471 } |
446 | 472 |
447 @Override | 473 @Override |
448 public boolean isItemSupported(@ContextMenuItemId int menuItemId) { | 474 public boolean isItemSupported(@ContextMenuItemId int menuItemId) { |
449 return true; | 475 return true; |
450 } | 476 } |
451 | 477 |
452 @Override | 478 @Override |
453 public void onContextMenuCreated() {} | 479 public void onContextMenuCreated() {} |
454 | 480 |
455 @Override | 481 @Override |
456 public void onCreateContextMenu( | 482 public void onCreateContextMenu( |
457 ContextMenu contextMenu, View view, ContextMenuInfo contextMenuI
nfo) { | 483 ContextMenu contextMenu, View view, ContextMenuInfo contextMenuI
nfo) { |
458 mContextMenuManager.createContextMenu(contextMenu, view, this); | 484 mContextMenuManager.createContextMenu(contextMenu, view, this); |
459 } | 485 } |
460 } | 486 } |
461 | 487 |
| 488 private class RemovalUndoneCallback extends Callback<String> { |
| 489 @Override |
| 490 public void onResult(String restoredUrl) { |
| 491 mPendingInsertionUrl = restoredUrl; |
| 492 } |
| 493 } |
| 494 |
462 private class OfflineModelObserver extends SuggestionsOfflineModelObserver<T
ile> { | 495 private class OfflineModelObserver extends SuggestionsOfflineModelObserver<T
ile> { |
463 public OfflineModelObserver(OfflinePageBridge bridge) { | 496 public OfflineModelObserver(OfflinePageBridge bridge) { |
464 super(bridge); | 497 super(bridge); |
465 } | 498 } |
466 | 499 |
467 @Override | 500 @Override |
468 public void onSuggestionOfflineIdChanged(Tile suggestion, @Nullable Long
id) { | 501 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. | 502 // Retrieve a tile from the internal data, to make sure we don't upd
ate a stale object. |
470 Tile tile = getTile(suggestion.getUrl()); | 503 Tile tile = getTile(suggestion.getUrl()); |
471 if (tile == null) return; | 504 if (tile == null) return; |
472 | 505 |
473 boolean oldOfflineAvailable = tile.isOfflineAvailable(); | 506 boolean oldOfflineAvailable = tile.isOfflineAvailable(); |
474 tile.setOfflinePageOfflineId(id); | 507 tile.setOfflinePageOfflineId(id); |
475 | 508 |
476 // Only notify to update the view if there will be a visible change. | 509 // Only notify to update the view if there will be a visible change. |
477 if (oldOfflineAvailable == tile.isOfflineAvailable()) return; | 510 if (oldOfflineAvailable == tile.isOfflineAvailable()) return; |
478 mObserver.onTileOfflineBadgeVisibilityChanged(tile); | 511 mObserver.onTileOfflineBadgeVisibilityChanged(tile); |
479 } | 512 } |
480 | 513 |
481 @Override | 514 @Override |
482 public Iterable<Tile> getOfflinableSuggestions() { | 515 public Iterable<Tile> getOfflinableSuggestions() { |
483 return Arrays.asList(mTiles); | 516 return Arrays.asList(mTiles); |
484 } | 517 } |
485 } | 518 } |
486 } | 519 } |
OLD | NEW |