Commit 36e2d941 authored by ianwen's avatar ianwen Committed by Commit bot

[Android Download] Show a progress bar before backend is loaded

This CL implements a loading view (aka progress bar) in download manager
UI on Android. It is slightly trickier to handle state changes on
Tablets, because when the download ui is initialized, a filter is
already set in the URL. Therefore we cache the filter temporarily in the
manager, and will show it after we load everything.

There are three possible types of backend the UI needs to wait:
incognito downloads, downloads and offline apges. The UI will only claim
it is loaded if all of them are loaed.

TODO(ianwen): add a timeout mechanisim to either the
DownloadLoadingDelegate or to the backend so that if it takes forever to
load one of the backend, users are still able to see the other two.

BUG=616324

Review-Url: https://codereview.chromium.org/2269353004
Cr-Commit-Position: refs/heads/master@{#414369}
parent 3e80255c
......@@ -26,7 +26,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentBottom="true"
android:layout_below="@id/action_bar" />
android:layout_below="@id/action_bar"
android:visibility="gone" />
<org.chromium.chrome.browser.widget.FadingShadowView
android:id="@+id/shadow"
......@@ -56,4 +57,10 @@
android:textColor="#5B5B5B"
android:textSize="16sp" />
</LinearLayout>
</RelativeLayout>
\ No newline at end of file
<org.chromium.chrome.browser.widget.LoadingView
android:id="@+id/loading_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
</RelativeLayout>
......@@ -87,6 +87,7 @@ public class DownloadHistoryAdapter extends DateDividedAdapter implements Downlo
private final List<DownloadHistoryItemWrapper> mFilteredItems = new ArrayList<>();
private final ComponentName mParentComponent;
private final boolean mShowOffTheRecord;
private final LoadingStateDelegate mLoadingDelegate;
private BackendProvider mBackendProvider;
private OfflinePageDownloadBridge.Observer mOfflinePageObserver;
......@@ -96,6 +97,7 @@ public class DownloadHistoryAdapter extends DateDividedAdapter implements Downlo
mShowOffTheRecord = showOffTheRecord;
mParentComponent = parentComponent;
setHasStableIds(true);
mLoadingDelegate = new LoadingStateDelegate(mShowOffTheRecord);
}
public void initialize(BackendProvider provider) {
......@@ -116,6 +118,10 @@ public class DownloadHistoryAdapter extends DateDividedAdapter implements Downlo
public void onAllDownloadsRetrieved(List<DownloadItem> result, boolean isOffTheRecord) {
if (isOffTheRecord && !mShowOffTheRecord) return;
mLoadingDelegate.updateLoadingState(
isOffTheRecord ? LoadingStateDelegate.OFF_THE_RECORD_HISTORY_LOADED
: LoadingStateDelegate.DOWNLOAD_HISTORY_LOADED);
List<DownloadItemWrapper> list = getDownloadItemList(isOffTheRecord);
list.clear();
int[] mItemCounts = new int[DownloadFilter.FILTER_BOUNDARY];
......@@ -135,19 +141,20 @@ public class DownloadHistoryAdapter extends DateDividedAdapter implements Downlo
}
}
filter(DownloadFilter.FILTER_ALL);
if (!isOffTheRecord) recordDownloadCountHistograms(mItemCounts, result.size());
if (mLoadingDelegate.isLoaded()) filter(mLoadingDelegate.getPendingFilter());
}
/** Called when the user's offline page history has been gathered. */
private void onAllOfflinePagesRetrieved(List<OfflinePageDownloadItem> result) {
mLoadingDelegate.updateLoadingState(LoadingStateDelegate.OFFLINE_PAGE_LOADED);
mOfflinePageItems.clear();
for (OfflinePageDownloadItem item : result) {
mOfflinePageItems.add(createOfflinePageItemWrapper(item));
}
// TODO(ianwen): Implement a loading screen to prevent filter-changing wonkiness.
filter(DownloadFilter.FILTER_ALL);
if (mLoadingDelegate.isLoaded()) filter(mLoadingDelegate.getPendingFilter());
RecordHistogram.recordCountHistogram("Android.DownloadManager.InitialCount.OfflinePage",
result.size());
......@@ -266,7 +273,13 @@ public class DownloadHistoryAdapter extends DateDividedAdapter implements Downlo
@Override
public void onFilterChanged(int filter) {
filter(filter);
if (mLoadingDelegate.isLoaded()) {
filter(filter);
} else {
// On tablets, this method might be called before anything is loaded. In this case,
// cache the filter, and wait till the backends are loaded.
mLoadingDelegate.setPendingFilter(filter);
}
}
@Override
......@@ -425,4 +438,58 @@ public class DownloadHistoryAdapter extends DateDividedAdapter implements Downlo
private Map<String, Boolean> getExternallyDeletedItemsMap(boolean isOffTheRecord) {
return isOffTheRecord ? sExternallyDeletedOffTheRecordItems : sExternallyDeletedItems;
}
/**
* Determines when the data from all of the backends has been loaded.
* <p>
* TODO(ianwen): add a timeout mechanism to either the DownloadLoadingDelegate or to the
* backend so that if it takes forever to load one of the backend, users are still able to see
* the other two.
*/
private static class LoadingStateDelegate {
public static final int DOWNLOAD_HISTORY_LOADED = 0b001;
public static final int OFF_THE_RECORD_HISTORY_LOADED = 0b010;
public static final int OFFLINE_PAGE_LOADED = 0b100;
private static final int ALL_LOADED = 0b111;
private int mState;
private int mPendingFilter = DownloadFilter.FILTER_ALL;
/**
* @param offTheRecord Whether this delegate needs to consider incognito.
*/
public LoadingStateDelegate(boolean offTheRecord) {
// If we don't care about incognito, mark it as loaded.
mState = offTheRecord ? 0 : OFF_THE_RECORD_HISTORY_LOADED;
}
/**
* Tells this delegate one of the three backends has been loaded.
*/
public void updateLoadingState(int flagToUpdate) {
mState |= flagToUpdate;
}
/**
* @return Whether all backends are loaded.
*/
public boolean isLoaded() {
return mState == ALL_LOADED;
}
/**
* Caches a filter for when the backends have loaded.
*/
public void setPendingFilter(int filter) {
mPendingFilter = filter;
}
/**
* @return The cached filter. If there are no such filter, fall back to
* {@link DownloadFilter#FILTER_ALL}.
*/
public int getPendingFilter() {
return mPendingFilter;
}
}
}
......@@ -42,6 +42,7 @@ import org.chromium.chrome.browser.offlinepages.downloads.OfflinePageDownloadBri
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.widget.FadingShadow;
import org.chromium.chrome.browser.widget.FadingShadowView;
import org.chromium.chrome.browser.widget.LoadingView;
import org.chromium.chrome.browser.widget.selection.SelectionDelegate;
import org.chromium.ui.base.DeviceFormFactor;
......@@ -117,6 +118,7 @@ public class DownloadManagerUi implements OnMenuItemClickListener {
private final ListView mFilterView;
private final RecyclerView mRecyclerView;
private final View mEmptyView;
private final LoadingView mLoadingView;
private BasicNativePage mNativePage;
private final AtomicInteger mNumberOfFilesBeingDeleted = new AtomicInteger();
......@@ -131,6 +133,9 @@ public class DownloadManagerUi implements OnMenuItemClickListener {
mEmptyView.setVisibility(View.GONE);
mRecyclerView.setVisibility(View.VISIBLE);
}
// At inflation, the RecyclerView is set to gone, and the loading view is visible. As
// long as the adapter data changes, we show the recycler view, and hide loading view.
mLoadingView.hideLoadingUI();
}
};
......@@ -150,6 +155,9 @@ public class DownloadManagerUi implements OnMenuItemClickListener {
mEmptyView = mMainView.findViewById(R.id.empty_view);
mLoadingView = (LoadingView) mMainView.findViewById(R.id.loading_view);
mLoadingView.showLoadingUI();
mSpaceDisplay = new SpaceDisplay(mMainView, mHistoryAdapter);
mHistoryAdapter.registerAdapterDataObserver(mSpaceDisplay);
mSpaceDisplay.onChanged();
......@@ -186,8 +194,6 @@ public class DownloadManagerUi implements OnMenuItemClickListener {
}
mToolbar.setTitle(R.string.menu_downloads);
// TODO(ianwen): add support for loading state.
}
/**
......
......@@ -62,7 +62,7 @@ public class DownloadHistoryAdapterTest extends NativeLibraryTestBase {
}
});
mDownloadDelegate.addCallback.waitForCallback(0);
mObserver.onChangedCallback.waitForCallback(callCount, showOffTheRecord ? 3 : 2);
mObserver.onChangedCallback.waitForCallback(callCount, 1);
}
/** Nothing downloaded, nothing shown. */
......@@ -156,49 +156,49 @@ public class DownloadHistoryAdapterTest extends NativeLibraryTestBase {
assertEquals(0, mAdapter.getTotalDownloadSize());
// Add the first item.
assertEquals(2, mObserver.onChangedCallback.getCallCount());
assertEquals(1, mObserver.onChangedCallback.getCallCount());
DownloadItem item0 = StubbedProvider.createDownloadItem(0, "19840116 12:00");
mAdapter.onDownloadItemUpdated(item0, false);
mObserver.onChangedCallback.waitForCallback(2);
mObserver.onChangedCallback.waitForCallback(1);
checkAdapterContents(null, item0);
assertEquals(1, mAdapter.getTotalDownloadSize());
// Add a second item with a different date.
assertEquals(3, mObserver.onChangedCallback.getCallCount());
assertEquals(2, mObserver.onChangedCallback.getCallCount());
DownloadItem item1 = StubbedProvider.createDownloadItem(1, "19840117 12:00");
mAdapter.onDownloadItemUpdated(item1, false);
mObserver.onChangedCallback.waitForCallback(3);
mObserver.onChangedCallback.waitForCallback(2);
checkAdapterContents(null, item1, null, item0);
assertEquals(11, mAdapter.getTotalDownloadSize());
// Add a third item with the same date as the second item.
assertEquals(4, mObserver.onChangedCallback.getCallCount());
assertEquals(3, mObserver.onChangedCallback.getCallCount());
DownloadItem item2 = StubbedProvider.createDownloadItem(2, "19840117 18:00");
mAdapter.onDownloadItemUpdated(item2, false);
mObserver.onChangedCallback.waitForCallback(4);
mObserver.onChangedCallback.waitForCallback(3);
checkAdapterContents(null, item2, item1, null, item0);
assertEquals(111, mAdapter.getTotalDownloadSize());
// An item with the same download ID as the second item should just update the old one.
assertEquals(5, mObserver.onChangedCallback.getCallCount());
assertEquals(4, mObserver.onChangedCallback.getCallCount());
DownloadItem item3 = StubbedProvider.createDownloadItem(2, "19840117 18:00");
mAdapter.onDownloadItemUpdated(item3, false);
mObserver.onChangedCallback.waitForCallback(5);
mObserver.onChangedCallback.waitForCallback(4);
checkAdapterContents(null, item3, item1, null, item0);
assertEquals(111, mAdapter.getTotalDownloadSize());
// Throw on a new OfflinePageItem.
assertEquals(6, mObserver.onChangedCallback.getCallCount());
assertEquals(5, mObserver.onChangedCallback.getCallCount());
OfflinePageDownloadItem item4 = StubbedProvider.createOfflineItem(0, "19840117 19:00");
mOfflineDelegate.observer.onItemAdded(item4);
mObserver.onChangedCallback.waitForCallback(6);
mObserver.onChangedCallback.waitForCallback(5);
checkAdapterContents(null, item4, item3, item1, null, item0);
// Update the existing OfflinePageItem.
assertEquals(7, mObserver.onChangedCallback.getCallCount());
assertEquals(6, mObserver.onChangedCallback.getCallCount());
OfflinePageDownloadItem item5 = StubbedProvider.createOfflineItem(0, "19840117 19:00");
mOfflineDelegate.observer.onItemUpdated(item5);
mObserver.onChangedCallback.waitForCallback(7);
mObserver.onChangedCallback.waitForCallback(6);
checkAdapterContents(null, item5, item3, item1, null, item0);
}
......@@ -218,23 +218,23 @@ public class DownloadHistoryAdapterTest extends NativeLibraryTestBase {
assertEquals(100011, mAdapter.getTotalDownloadSize());
// Remove an item from the date bucket with two items.
assertEquals(3, mObserver.onChangedCallback.getCallCount());
assertEquals(1, mObserver.onChangedCallback.getCallCount());
mAdapter.onDownloadItemRemoved(offTheRecordItem.getId(), true);
mObserver.onChangedCallback.waitForCallback(3);
mObserver.onChangedCallback.waitForCallback(1);
checkAdapterContents(null, offlineItem, null, regularItem);
assertEquals(100001, mAdapter.getTotalDownloadSize());
// Remove an item from the second bucket, which removes the bucket entirely.
assertEquals(4, mObserver.onChangedCallback.getCallCount());
assertEquals(2, mObserver.onChangedCallback.getCallCount());
mOfflineDelegate.observer.onItemDeleted(offlineItem.getGuid());
mObserver.onChangedCallback.waitForCallback(4);
mObserver.onChangedCallback.waitForCallback(2);
checkAdapterContents(null, regularItem);
assertEquals(1, mAdapter.getTotalDownloadSize());
// Remove the last item in the list.
assertEquals(5, mObserver.onChangedCallback.getCallCount());
assertEquals(3, mObserver.onChangedCallback.getCallCount());
mAdapter.onDownloadItemRemoved(regularItem.getId(), false);
mObserver.onChangedCallback.waitForCallback(5);
mObserver.onChangedCallback.waitForCallback(3);
assertEquals(0, mAdapter.getItemCount());
assertEquals(0, mAdapter.getTotalDownloadSize());
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment