Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 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.download.ui; | 5 package org.chromium.chrome.browser.download.ui; |
| 6 | 6 |
| 7 import android.content.ComponentName; | 7 import android.content.ComponentName; |
| 8 import android.support.v7.widget.RecyclerView.ViewHolder; | 8 import android.support.v7.widget.RecyclerView.ViewHolder; |
| 9 import android.text.TextUtils; | 9 import android.text.TextUtils; |
| 10 import android.view.LayoutInflater; | 10 import android.view.LayoutInflater; |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 21 import org.chromium.chrome.browser.download.ui.DownloadHistoryItemWrapper.Downlo adItemWrapper; | 21 import org.chromium.chrome.browser.download.ui.DownloadHistoryItemWrapper.Downlo adItemWrapper; |
| 22 import org.chromium.chrome.browser.download.ui.DownloadHistoryItemWrapper.Offlin ePageItemWrapper; | 22 import org.chromium.chrome.browser.download.ui.DownloadHistoryItemWrapper.Offlin ePageItemWrapper; |
| 23 import org.chromium.chrome.browser.download.ui.DownloadManagerUi.DownloadUiObser ver; | 23 import org.chromium.chrome.browser.download.ui.DownloadManagerUi.DownloadUiObser ver; |
| 24 import org.chromium.chrome.browser.offlinepages.downloads.OfflinePageDownloadBri dge; | 24 import org.chromium.chrome.browser.offlinepages.downloads.OfflinePageDownloadBri dge; |
| 25 import org.chromium.chrome.browser.offlinepages.downloads.OfflinePageDownloadIte m; | 25 import org.chromium.chrome.browser.offlinepages.downloads.OfflinePageDownloadIte m; |
| 26 import org.chromium.chrome.browser.widget.DateDividedAdapter; | 26 import org.chromium.chrome.browser.widget.DateDividedAdapter; |
| 27 import org.chromium.chrome.browser.widget.selection.SelectionDelegate; | 27 import org.chromium.chrome.browser.widget.selection.SelectionDelegate; |
| 28 import org.chromium.content_public.browser.DownloadState; | 28 import org.chromium.content_public.browser.DownloadState; |
| 29 | 29 |
| 30 import java.util.ArrayList; | 30 import java.util.ArrayList; |
| 31 import java.util.Calendar; | |
| 32 import java.util.Date; | |
| 33 import java.util.HashMap; | |
| 34 import java.util.Iterator; | |
| 31 import java.util.List; | 35 import java.util.List; |
| 36 import java.util.Map; | |
| 37 import java.util.Map.Entry; | |
| 32 import java.util.Set; | 38 import java.util.Set; |
| 33 | 39 |
| 34 /** Bridges the user's download history and the UI used to display it. */ | 40 /** Bridges the user's download history and the UI used to display it. */ |
| 35 public class DownloadHistoryAdapter extends DateDividedAdapter | 41 public class DownloadHistoryAdapter extends DateDividedAdapter |
| 36 implements DownloadUiObserver, DownloadSharedPreferenceHelper.Observer { | 42 implements DownloadUiObserver, DownloadSharedPreferenceHelper.Observer { |
| 37 | 43 |
| 38 /** Alerted about changes to internal state. */ | 44 /** Alerted about changes to internal state. */ |
| 39 static interface TestObserver { | 45 static interface TestObserver { |
| 40 abstract void onDownloadItemCreated(DownloadItem item); | 46 abstract void onDownloadItemCreated(DownloadItem item); |
| 41 abstract void onDownloadItemUpdated(DownloadItem item); | 47 abstract void onDownloadItemUpdated(DownloadItem item); |
| 42 } | 48 } |
| 43 | 49 |
| 44 private class BackendItemsImpl extends BackendItems { | 50 private class BackendItemsImpl extends BackendItems { |
| 45 @Override | 51 @Override |
| 46 public DownloadHistoryItemWrapper removeItem(String guid) { | 52 public DownloadHistoryItemWrapper removeItem(String guid) { |
| 47 DownloadHistoryItemWrapper wrapper = super.removeItem(guid); | 53 DownloadHistoryItemWrapper wrapper = super.removeItem(guid); |
| 48 | 54 |
| 49 if (wrapper != null) { | 55 if (wrapper != null) { |
| 50 mFilePathsToItemsMap.removeItem(wrapper); | 56 mFilePathsToItemsMap.removeItem(wrapper); |
| 51 if (getSelectionDelegate().isItemSelected(wrapper)) { | 57 if (getSelectionDelegate().isItemSelected(wrapper)) { |
| 52 getSelectionDelegate().toggleSelectionForItem(wrapper); | 58 getSelectionDelegate().toggleSelectionForItem(wrapper); |
| 53 } | 59 } |
| 54 } | 60 } |
| 55 | 61 |
| 56 return wrapper; | 62 return wrapper; |
| 57 } | 63 } |
| 58 } | 64 } |
| 59 | 65 |
| 66 /** Represents the subsection header of the suggested pages for a given date . */ | |
| 67 public class SubsectionHeader extends TimedItem { | |
| 68 private final Date mDate; | |
| 69 private final int mItemCount; | |
| 70 private final long mTotalFileSize; | |
| 71 private final Long mStableId; | |
| 72 | |
| 73 public SubsectionHeader(Date date, int itemCount, long totalFileSize) { | |
| 74 mDate = date; | |
| 75 mItemCount = itemCount; | |
| 76 mTotalFileSize = totalFileSize; | |
| 77 | |
| 78 // Generate a stable ID based on timestamp. | |
| 79 mStableId = 0xFFFFFFFF00000000L + (getTimestamp() & 0x0FFFFFFFF); | |
| 80 } | |
| 81 | |
| 82 @Override | |
| 83 public long getTimestamp() { | |
| 84 return mDate.getTime(); | |
| 85 } | |
| 86 | |
| 87 public int getItemCount() { | |
| 88 return mItemCount; | |
| 89 } | |
| 90 | |
| 91 public long getTotalFileSize() { | |
| 92 return mTotalFileSize; | |
| 93 } | |
| 94 | |
| 95 @Override | |
| 96 public long getStableId() { | |
| 97 return mStableId; | |
| 98 } | |
| 99 } | |
| 100 | |
| 60 /** | 101 /** |
| 61 * Tracks externally deleted items that have been removed from downloads his tory. | 102 * Tracks externally deleted items that have been removed from downloads his tory. |
| 62 * Shared across instances. | 103 * Shared across instances. |
| 63 */ | 104 */ |
| 64 private static final DeletedFileTracker sDeletedFileTracker = new DeletedFil eTracker(); | 105 private static final DeletedFileTracker sDeletedFileTracker = new DeletedFil eTracker(); |
| 65 | 106 |
| 66 private static final String EMPTY_QUERY = null; | 107 private static final String EMPTY_QUERY = null; |
| 67 | 108 |
| 68 private final BackendItems mRegularDownloadItems = new BackendItemsImpl(); | 109 private final BackendItems mRegularDownloadItems = new BackendItemsImpl(); |
| 69 private final BackendItems mIncognitoDownloadItems = new BackendItemsImpl(); | 110 private final BackendItems mIncognitoDownloadItems = new BackendItemsImpl(); |
| 70 private final BackendItems mOfflinePageItems = new BackendItemsImpl(); | 111 private final BackendItems mOfflinePageItems = new BackendItemsImpl(); |
| 71 | 112 |
| 72 private final BackendItems mFilteredItems = new BackendItemsImpl(); | |
| 73 private final FilePathsToDownloadItemsMap mFilePathsToItemsMap = | 113 private final FilePathsToDownloadItemsMap mFilePathsToItemsMap = |
| 74 new FilePathsToDownloadItemsMap(); | 114 new FilePathsToDownloadItemsMap(); |
| 75 | 115 |
| 116 private final Map<Date, Boolean> mSubsectionExpanded = new HashMap<>(); | |
| 76 private final ComponentName mParentComponent; | 117 private final ComponentName mParentComponent; |
| 77 private final boolean mShowOffTheRecord; | 118 private final boolean mShowOffTheRecord; |
| 78 private final LoadingStateDelegate mLoadingDelegate; | 119 private final LoadingStateDelegate mLoadingDelegate; |
| 79 private final ObserverList<TestObserver> mObservers = new ObserverList<>(); | 120 private final ObserverList<TestObserver> mObservers = new ObserverList<>(); |
| 80 private final List<DownloadItemView> mViews = new ArrayList<>(); | 121 private final List<DownloadItemView> mViews = new ArrayList<>(); |
| 81 | 122 |
| 82 private BackendProvider mBackendProvider; | 123 private BackendProvider mBackendProvider; |
| 83 private OfflinePageDownloadBridge.Observer mOfflinePageObserver; | 124 private OfflinePageDownloadBridge.Observer mOfflinePageObserver; |
| 84 private int mFilter = DownloadFilter.FILTER_ALL; | 125 private int mFilter = DownloadFilter.FILTER_ALL; |
| 85 private String mSearchQuery = EMPTY_QUERY; | 126 private String mSearchQuery = EMPTY_QUERY; |
| (...skipping 115 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 201 totalSize += mOfflinePageItems.getTotalBytes(); | 242 totalSize += mOfflinePageItems.getTotalBytes(); |
| 202 return totalSize; | 243 return totalSize; |
| 203 } | 244 } |
| 204 | 245 |
| 205 @Override | 246 @Override |
| 206 protected int getTimedItemViewResId() { | 247 protected int getTimedItemViewResId() { |
| 207 return R.layout.date_view; | 248 return R.layout.date_view; |
| 208 } | 249 } |
| 209 | 250 |
| 210 @Override | 251 @Override |
| 252 protected SubsectionHeaderViewHolder createSubsectionHeader(ViewGroup parent ) { | |
| 253 OfflineGroupHeaderView offlineHeader = | |
| 254 (OfflineGroupHeaderView) LayoutInflater.from(parent.getContext() ) | |
| 255 .inflate(R.layout.offline_download_header, parent, false ); | |
| 256 offlineHeader.setAdapter(this); | |
| 257 return new SubsectionHeaderViewHolder(offlineHeader); | |
| 258 } | |
| 259 | |
| 260 @Override | |
| 261 protected void bindViewHolderForSubsectionHeader( | |
| 262 SubsectionHeaderViewHolder holder, TimedItem timedItem) { | |
| 263 SubsectionHeader headerItem = (SubsectionHeader) timedItem; | |
| 264 Date date = new Date(headerItem.getTimestamp()); | |
| 265 OfflineGroupHeaderView headerView = (OfflineGroupHeaderView) holder.getV iew(); | |
| 266 headerView.update(date, isSubsectionExpanded(date), headerItem.getItemCo unt(), | |
| 267 headerItem.getTotalFileSize()); | |
| 268 } | |
| 269 | |
| 270 @Override | |
| 211 public ViewHolder createViewHolder(ViewGroup parent) { | 271 public ViewHolder createViewHolder(ViewGroup parent) { |
| 212 DownloadItemView v = (DownloadItemView) LayoutInflater.from(parent.getCo ntext()).inflate( | 272 DownloadItemView v = (DownloadItemView) LayoutInflater.from(parent.getCo ntext()).inflate( |
| 213 R.layout.download_item_view, parent, false); | 273 R.layout.download_item_view, parent, false); |
| 214 v.setSelectionDelegate(getSelectionDelegate()); | 274 v.setSelectionDelegate(getSelectionDelegate()); |
| 215 mViews.add(v); | 275 mViews.add(v); |
| 216 return new DownloadHistoryItemViewHolder(v); | 276 return new DownloadHistoryItemViewHolder(v); |
| 217 } | 277 } |
| 218 | 278 |
| 219 @Override | 279 @Override |
| 220 public void bindViewHolderForTimedItem(ViewHolder current, TimedItem timedIt em) { | 280 public void bindViewHolderForTimedItem(ViewHolder current, TimedItem timedIt em) { |
| 221 final DownloadHistoryItemWrapper item = (DownloadHistoryItemWrapper) tim edItem; | 281 final DownloadHistoryItemWrapper item = (DownloadHistoryItemWrapper) tim edItem; |
| 222 | 282 |
| 223 DownloadHistoryItemViewHolder holder = (DownloadHistoryItemViewHolder) c urrent; | 283 DownloadHistoryItemViewHolder holder = (DownloadHistoryItemViewHolder) c urrent; |
| 224 holder.getItemView().displayItem(mBackendProvider, item); | 284 holder.getItemView().displayItem(mBackendProvider, item); |
| 225 } | 285 } |
| 226 | 286 |
| 287 @Override | |
| 288 protected ItemGroup createGroup(long timeStamp) { | |
| 289 return new DownloadItemGroup(timeStamp); | |
| 290 } | |
| 291 | |
| 227 /** Called when a new DownloadItem has been created by the native DownloadMa nager. */ | 292 /** Called when a new DownloadItem has been created by the native DownloadMa nager. */ |
| 228 public void onDownloadItemCreated(DownloadItem item) { | 293 public void onDownloadItemCreated(DownloadItem item) { |
| 229 boolean isOffTheRecord = item.getDownloadInfo().isOffTheRecord(); | 294 boolean isOffTheRecord = item.getDownloadInfo().isOffTheRecord(); |
| 230 if (isOffTheRecord && !mShowOffTheRecord) return; | 295 if (isOffTheRecord && !mShowOffTheRecord) return; |
| 231 | 296 |
| 232 BackendItems list = getDownloadItemList(isOffTheRecord); | 297 BackendItems list = getDownloadItemList(isOffTheRecord); |
| 233 assert list.findItemIndex(item.getId()) == BackendItems.INVALID_INDEX; | 298 assert list.findItemIndex(item.getId()) == BackendItems.INVALID_INDEX; |
| 234 | 299 |
| 235 DownloadItemWrapper wrapper = createDownloadItemWrapper(item); | 300 DownloadItemWrapper wrapper = createDownloadItemWrapper(item); |
| 236 boolean wasAdded = addDownloadHistoryItemWrapper(wrapper); | 301 boolean wasAdded = addDownloadHistoryItemWrapper(wrapper); |
| (...skipping 148 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 385 return mBackendProvider.getOfflinePageBridge(); | 450 return mBackendProvider.getOfflinePageBridge(); |
| 386 } | 451 } |
| 387 | 452 |
| 388 private SelectionDelegate<DownloadHistoryItemWrapper> getSelectionDelegate() { | 453 private SelectionDelegate<DownloadHistoryItemWrapper> getSelectionDelegate() { |
| 389 return mBackendProvider.getSelectionDelegate(); | 454 return mBackendProvider.getSelectionDelegate(); |
| 390 } | 455 } |
| 391 | 456 |
| 392 /** Filters the list of downloads to show only files of a specific type. */ | 457 /** Filters the list of downloads to show only files of a specific type. */ |
| 393 private void filter(int filterType) { | 458 private void filter(int filterType) { |
| 394 mFilter = filterType; | 459 mFilter = filterType; |
| 395 mFilteredItems.clear(); | 460 |
| 396 mRegularDownloadItems.filter(mFilter, mSearchQuery, mFilteredItems); | 461 List<TimedItem> filteredTimedItems = new ArrayList<>(); |
| 397 mIncognitoDownloadItems.filter(mFilter, mSearchQuery, mFilteredItems); | 462 mRegularDownloadItems.filter(mFilter, mSearchQuery, filteredTimedItems); |
| 398 mOfflinePageItems.filter(mFilter, mSearchQuery, mFilteredItems); | 463 mIncognitoDownloadItems.filter(mFilter, mSearchQuery, filteredTimedItems ); |
| 464 | |
|
gone
2017/02/21 18:32:27
nit: remove newline
shaktisahu
2017/02/21 21:17:13
Done.
| |
| 465 filterOfflinePageItems(filteredTimedItems); | |
| 466 | |
| 399 clear(false); | 467 clear(false); |
| 400 loadItems(mFilteredItems); | 468 loadItems(filteredTimedItems); |
| 469 } | |
| 470 | |
| 471 /** | |
| 472 * Filters the offline pages based on the current filter and search text. | |
| 473 * If there are suggested pages, they are filtered based on whether or not t he subsection for | |
| 474 * that date is expanded. Also a TimedItem is added to each subsection to re present the header | |
| 475 * for the suggested pages. | |
| 476 * @param filteredTimedItems List for appending items that match the filter. | |
| 477 */ | |
| 478 private void filterOfflinePageItems(List<TimedItem> filteredTimedItems) { | |
| 479 Map<Date, Integer> suggestedPageCountMap = new HashMap<>(); | |
| 480 Map<Date, Long> suggestedPageTotalSizeMap = new HashMap<>(); | |
| 481 | |
| 482 List<TimedItem> filteredOfflinePageItems = new ArrayList<>(); | |
| 483 mOfflinePageItems.filter(mFilter, mSearchQuery, filteredOfflinePageItems ); | |
| 484 | |
| 485 for (TimedItem item : filteredOfflinePageItems) { | |
| 486 OfflinePageItemWrapper offlineItem = (OfflinePageItemWrapper) item; | |
| 487 | |
| 488 // Add the suggested pages to the adapter only if the section is exp anded for that date. | |
| 489 if (offlineItem.isSuggested()) { | |
| 490 incrementSuggestedPageCount( | |
| 491 offlineItem, suggestedPageCountMap, suggestedPageTotalSi zeMap); | |
| 492 // TODO(shaktisahu): Check with UX if we need to skip this check and the subsection | |
| 493 // headers when filtering for active search text. | |
| 494 if (!isSubsectionExpanded(getDateWithoutTime(offlineItem.getTime stamp()))) continue; | |
| 495 } | |
| 496 filteredTimedItems.add(offlineItem); | |
| 497 } | |
| 498 | |
| 499 generateSubsectionHeaders( | |
| 500 filteredTimedItems, suggestedPageCountMap, suggestedPageTotalSiz eMap); | |
| 501 } | |
| 502 | |
| 503 // Updates the total number of suggested pages and file size grouped by date . | |
| 504 private void incrementSuggestedPageCount(OfflinePageItemWrapper offlineItem, | |
| 505 Map<Date, Integer> pageCountMap, Map<Date, Long> fileSizeMap) { | |
| 506 Date date = getDateWithoutTime(offlineItem.getTimestamp()); | |
| 507 | |
| 508 int count = pageCountMap.containsKey(date) ? pageCountMap.get(date) : 0; | |
| 509 pageCountMap.put(date, count + 1); | |
| 510 | |
| 511 long fileSize = fileSizeMap.containsKey(date) ? fileSizeMap.get(date) : 0; | |
| 512 fileSizeMap.put(date, fileSize + offlineItem.getFileSize()); | |
| 513 } | |
| 514 | |
| 515 // Creates subsection headers for each date and appends to |filteredTimedIte ms|. | |
| 516 private void generateSubsectionHeaders(List<TimedItem> filteredTimedItems, | |
| 517 Map<Date, Integer> pageCountMap, Map<Date, Long> fileSizeMap) { | |
| 518 for (Date date : pageCountMap.keySet()) { | |
| 519 filteredTimedItems.add( | |
| 520 new SubsectionHeader(date, pageCountMap.get(date), fileSizeM ap.get(date))); | |
| 521 } | |
| 522 | |
| 523 // Remove entry from |mSubsectionExpanded| if there are no more suggeste d pages. | |
| 524 Iterator<Entry<Date, Boolean>> iter = mSubsectionExpanded.entrySet().ite rator(); | |
| 525 while (iter.hasNext()) { | |
|
Theresa
2017/02/21 17:07:26
nit: can this be:
for (Entry<Date, Booleean> entr
shaktisahu
2017/02/21 21:17:13
I can't call remove on |mSubsectionExpanded| while
| |
| 526 Entry<Date, Boolean> entry = iter.next(); | |
| 527 if (!pageCountMap.containsKey(entry.getKey())) { | |
| 528 iter.remove(); | |
| 529 } | |
| 530 } | |
| 531 } | |
| 532 | |
| 533 /** | |
| 534 * Whether the suggested pages section is expanded for a given date. | |
| 535 * @param date The download date. | |
| 536 * @return Whether the suggested pages section is expanded. | |
| 537 */ | |
| 538 public boolean isSubsectionExpanded(Date date) { | |
| 539 // Default state is collpased. | |
| 540 if (mSubsectionExpanded.get(date) == null) { | |
| 541 mSubsectionExpanded.put(date, false); | |
| 542 } | |
| 543 | |
| 544 return mSubsectionExpanded.get(date); | |
| 545 } | |
| 546 | |
| 547 /** | |
| 548 * Sets the state of a subsection for a particular date and updates the adap ter. | |
| 549 * @param date The download date. | |
| 550 * @param expanded Whether the suggested pages should be expanded. | |
| 551 */ | |
| 552 public void setSubsectionExpanded(Date date, boolean expanded) { | |
| 553 mSubsectionExpanded.put(date, expanded); | |
| 554 clear(false); | |
| 555 filter(mFilter); | |
| 556 } | |
| 557 | |
| 558 @Override | |
| 559 protected boolean isSubsectionHeader(TimedItem timedItem) { | |
| 560 return timedItem instanceof SubsectionHeader; | |
| 401 } | 561 } |
| 402 | 562 |
| 403 private void initializeOfflinePageBridge() { | 563 private void initializeOfflinePageBridge() { |
| 404 mOfflinePageObserver = new OfflinePageDownloadBridge.Observer() { | 564 mOfflinePageObserver = new OfflinePageDownloadBridge.Observer() { |
| 405 @Override | 565 @Override |
| 406 public void onItemsLoaded() { | 566 public void onItemsLoaded() { |
| 407 onAllOfflinePagesRetrieved(getOfflinePageBridge().getAllItems()) ; | 567 onAllOfflinePagesRetrieved(getOfflinePageBridge().getAllItems()) ; |
| 408 } | 568 } |
| 409 | 569 |
| 410 @Override | 570 @Override |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 470 RecordHistogram.recordCountHistogram("Android.DownloadManager.InitialCou nt.Video", | 630 RecordHistogram.recordCountHistogram("Android.DownloadManager.InitialCou nt.Video", |
| 471 itemCounts[DownloadFilter.FILTER_VIDEO]); | 631 itemCounts[DownloadFilter.FILTER_VIDEO]); |
| 472 } | 632 } |
| 473 | 633 |
| 474 private void recordTotalDownloadCountHistogram() { | 634 private void recordTotalDownloadCountHistogram() { |
| 475 // The total count intentionally leaves out incognito downloads. This sh ould be revisited | 635 // The total count intentionally leaves out incognito downloads. This sh ould be revisited |
| 476 // if/when incognito downloads are persistently available in downloads h ome. | 636 // if/when incognito downloads are persistently available in downloads h ome. |
| 477 RecordHistogram.recordCountHistogram("Android.DownloadManager.InitialCou nt.Total", | 637 RecordHistogram.recordCountHistogram("Android.DownloadManager.InitialCou nt.Total", |
| 478 mRegularDownloadItems.size() + mOfflinePageItems.size()); | 638 mRegularDownloadItems.size() + mOfflinePageItems.size()); |
| 479 } | 639 } |
| 640 | |
| 641 /** | |
| 642 * Calculates the {@link Date} for midnight of the date represented by the t imestamp. | |
| 643 */ | |
| 644 private Date getDateWithoutTime(long timestamp) { | |
| 645 Calendar cal = Calendar.getInstance(); | |
| 646 cal.setTimeInMillis(timestamp); | |
| 647 cal.set(Calendar.HOUR_OF_DAY, 0); | |
| 648 cal.set(Calendar.MINUTE, 0); | |
| 649 cal.set(Calendar.SECOND, 0); | |
| 650 cal.set(Calendar.MILLISECOND, 0); | |
| 651 return cal.getTime(); | |
| 652 } | |
| 480 } | 653 } |
| OLD | NEW |