Replacing downloads backend with DownloadOfflineContentProvider

This CL attempts to replace the downloads backend for UI with offline
content provider behind a feature flag. The UI affected include
notifications, new download home and download infobar.

Bug : 939634

Change-Id: I207fced84a202c2ed8dcfa5c963993d6f4af40a7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1412483
Commit-Queue: Shakti Sahu <shaktisahu@chromium.org>
Reviewed-by: David Trainor <dtrainor@chromium.org>
Cr-Commit-Position: refs/heads/master@{#638931}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadBroadcastManager.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadBroadcastManager.java
index 2986804..2d0879b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadBroadcastManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadBroadcastManager.java
@@ -33,6 +33,7 @@
 import org.chromium.base.library_loader.LibraryProcessType;
 import org.chromium.base.library_loader.ProcessInitException;
 import org.chromium.chrome.browser.ChromeApplication;
+import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.download.DownloadNotificationUmaHelper.UmaDownloadResumption;
 import org.chromium.chrome.browser.download.items.OfflineContentAggregatorNotificationBridgeUiFactory;
 import org.chromium.chrome.browser.init.BrowserParts;
@@ -312,7 +313,9 @@
      * @return delegate for interactions with the entry
      */
     static DownloadServiceDelegate getServiceDelegate(ContentId id) {
-        if (LegacyHelpers.isLegacyDownload(id)) {
+        if (LegacyHelpers.isLegacyDownload(id)
+                && !ChromeFeatureList.isEnabled(
+                        ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER)) {
             return DownloadManagerService.getDownloadManagerService();
         }
         return OfflineContentAggregatorNotificationBridgeUiFactory.instance();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadController.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadController.java
index 764e012..cb70925 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadController.java
@@ -20,6 +20,7 @@
 import org.chromium.base.library_loader.LibraryProcessType;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.util.FeatureUtilities;
@@ -70,7 +71,9 @@
     private static DownloadNotificationService sDownloadNotificationService;
 
     public static void setDownloadNotificationService(DownloadNotificationService service) {
-        sDownloadNotificationService = service;
+        if (!ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER)) {
+            sDownloadNotificationService = service;
+        }
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java
index 4c8b00f..e5d898f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java
@@ -221,6 +221,7 @@
         }
     }
 
+    private final boolean mUseNewDownloadPath;
     private final boolean mIsIncognito;
     private final Handler mHandler = new Handler();
     private final DownloadProgressInfoBar.Client mClient = new DownloadProgressInfoBarClient();
@@ -258,6 +259,8 @@
 
     /** Constructor. */
     public DownloadInfoBarController(boolean isIncognito) {
+        mUseNewDownloadPath =
+                ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER);
         mIsIncognito = isIncognito;
         mHandler.post(() -> getOfflineContentProvider().addObserver(this));
     }
@@ -273,6 +276,8 @@
 
     /** Updates the InfoBar when new information about a download comes in. */
     public void onDownloadItemUpdated(DownloadItem downloadItem) {
+        if (mUseNewDownloadPath) return;
+
         OfflineItem offlineItem = DownloadInfo.createOfflineItem(downloadItem.getDownloadInfo());
         if (!isVisibleToUser(offlineItem)) return;
 
@@ -291,6 +296,7 @@
 
     /** Updates the InfoBar after a download has been removed. */
     public void onDownloadItemRemoved(ContentId contentId) {
+        if (mUseNewDownloadPath) return;
         onItemRemoved(contentId);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerBridge.java
index c200116..0dbba8f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerBridge.java
@@ -13,8 +13,10 @@
 import android.os.Environment;
 import android.support.v4.app.NotificationManagerCompat;
 import android.text.TextUtils;
+import android.util.Pair;
 
 import org.chromium.base.Callback;
+import org.chromium.base.ContentUriUtils;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
 import org.chromium.base.StrictModeContext;
@@ -262,20 +264,35 @@
         return ContextUtils.getApplicationContext();
     }
 
+    /**
+     * This function is meant to be called as the last step of a download. It will add the download
+     * to the android's DownloadManager and determine if the download can be resolved to any
+     * activity so that it can be auto-opened.
+     */
     @CalledByNative
-    private static void addCompletedDownload(String fileName, String description, String mimeType,
-            String filePath, long fileSizeBytes, String originalUrl, String referrer,
-            String downloadGuid, long callbackId) {
-        AsyncTask<Long> task = new AsyncTask<Long>() {
+    private static void addCompletedDownload(String fileName, String description,
+            String originalMimeType, String filePath, long fileSizeBytes, String originalUrl,
+            String referrer, String downloadGuid, long callbackId) {
+        final String mimeType = ChromeDownloadDelegate.remapGenericMimeType(
+                originalMimeType, originalUrl, fileName);
+        AsyncTask<Pair<Long, Boolean>> task = new AsyncTask<Pair<Long, Boolean>>() {
             @Override
-            protected Long doInBackground() {
-                return addCompletedDownload(fileName, description, mimeType, filePath,
-                        fileSizeBytes, originalUrl, referrer, downloadGuid);
+            protected Pair<Long, Boolean> doInBackground() {
+                long downloadId = ContentUriUtils.isContentUri(filePath)
+                        ? DownloadItem.INVALID_DOWNLOAD_ID
+                        : addCompletedDownload(fileName, description, mimeType, filePath,
+                                fileSizeBytes, originalUrl, referrer, downloadGuid);
+                boolean success = ContentUriUtils.isContentUri(filePath)
+                        || downloadId != DownloadItem.INVALID_DOWNLOAD_ID;
+                boolean canResolve = success
+                        && DownloadManagerService.canResolveDownload(
+                                filePath, mimeType, downloadId);
+                return Pair.create(downloadId, canResolve);
             }
 
             @Override
-            protected void onPostExecute(Long downloadId) {
-                nativeOnAddCompletedDownloadDone(callbackId, downloadId);
+            protected void onPostExecute(Pair<Long, Boolean> result) {
+                nativeOnAddCompletedDownloadDone(callbackId, result.first, result.second);
             }
         };
         try {
@@ -283,7 +300,7 @@
         } catch (RejectedExecutionException e) {
             // Reaching thread limit, update will be reschduled for the next run.
             Log.e(TAG, "Thread limit reached, reschedule notification update later.");
-            nativeOnAddCompletedDownloadDone(callbackId, DownloadItem.INVALID_DOWNLOAD_ID);
+            nativeOnAddCompletedDownloadDone(callbackId, DownloadItem.INVALID_DOWNLOAD_ID, false);
         }
     }
 
@@ -424,5 +441,6 @@
         }
     }
 
-    private static native void nativeOnAddCompletedDownloadDone(long callbackId, long downloadId);
+    private static native void nativeOnAddCompletedDownloadDone(
+            long callbackId, long downloadId, boolean canResolve);
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java
index 72b0684..d648b8a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java
@@ -886,6 +886,23 @@
                                 : ExternalNavigationDelegateImpl.resolveIntent(intent, true);
     }
 
+    /**
+     * Return whether a download item can be resolved to any activity.
+     * @param filePath The file path for the download.
+     * @param mimeType The mime type of the download
+     * @param systemDownloadId They download ID generated from the android DownloadManager.
+     * @return True, if the download can be handled by an activity, false otherwise
+     */
+    public static boolean canResolveDownload(
+            String filePath, String mimeType, long systemDownloadId) {
+        assert !ThreadUtils.runningOnUiThread();
+        if (isOMADownloadDescription(mimeType)) return true;
+
+        Intent intent = getLaunchIntentForDownload(filePath, systemDownloadId,
+                DownloadManagerService.isSupportedMimeType(mimeType), null, null);
+        return intent != null && ExternalNavigationDelegateImpl.resolveIntent(intent, true);
+    }
+
     /** See {@link #openDownloadedContent(Context, String, boolean, boolean, String, long)}. */
     protected void openDownloadedContent(final DownloadInfo downloadInfo, final long downloadId,
             @DownloadOpenSource int source) {
@@ -1196,18 +1213,25 @@
      */
     public void onSuccessNotificationShown(
             DownloadInfo info, boolean canResolve, int notificationId, long systemDownloadId) {
-        if (canResolve && shouldOpenAfterDownload(info.getMimeType(), info.hasUserGesture())) {
-            DownloadItem item = new DownloadItem(false, info);
-            item.setSystemDownloadId(systemDownloadId);
-            handleAutoOpenAfterDownload(item);
-        } else {
-            DownloadInfoBarController infobarController =
-                    getInfoBarController(info.isOffTheRecord());
-            if (infobarController != null) {
-                infobarController.onNotificationShown(info.getContentId(), notificationId);
+        if (!ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER)) {
+            if (canResolve && shouldOpenAfterDownload(info.getMimeType(), info.hasUserGesture())) {
+                DownloadItem item = new DownloadItem(false, info);
+                item.setSystemDownloadId(systemDownloadId);
+                handleAutoOpenAfterDownload(item);
+            } else {
+                DownloadInfoBarController infobarController =
+                        getInfoBarController(info.isOffTheRecord());
+                if (infobarController != null) {
+                    infobarController.onNotificationShown(info.getContentId(), notificationId);
+                }
+                mDownloadSnackbarController.onDownloadSucceeded(
+                        info, notificationId, systemDownloadId, canResolve, false);
             }
-            mDownloadSnackbarController.onDownloadSucceeded(
-                    info, notificationId, systemDownloadId, canResolve, false);
+        } else {
+            if (getInfoBarController(info.isOffTheRecord()) != null) {
+                getInfoBarController(info.isOffTheRecord())
+                        .onNotificationShown(info.getContentId(), notificationId);
+            }
         }
 
         if (BrowserStartupController.get(LibraryProcessType.PROCESS_BROWSER)
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationFactory.java
index ef0ae9f..2b65a3e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationFactory.java
@@ -33,6 +33,7 @@
 
 import org.chromium.base.ContentUriUtils;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.media.MediaViewerUtils;
 import org.chromium.chrome.browser.notifications.ChromeNotificationBuilder;
 import org.chromium.chrome.browser.notifications.NotificationBuilderFactory;
@@ -242,7 +243,9 @@
 
                 if (downloadUpdate.getIsOpenable()) {
                     Intent intent;
-                    if (LegacyHelpers.isLegacyDownload(downloadUpdate.getContentId())) {
+                    if (LegacyHelpers.isLegacyDownload(downloadUpdate.getContentId())
+                            && !ChromeFeatureList.isEnabled(
+                                    ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER)) {
                         Preconditions.checkNotNull(downloadUpdate.getContentId());
                         Preconditions.checkArgument(downloadUpdate.getSystemDownloadId() != -1
                                 || ContentUriUtils.isContentUri(downloadUpdate.getFilePath()));
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/DownloadManagerUiConfig.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/DownloadManagerUiConfig.java
index c70a6f77..cbad533 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/DownloadManagerUiConfig.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/DownloadManagerUiConfig.java
@@ -88,6 +88,8 @@
             mSupportFullWidthImages = !DeviceFormFactor.isNonMultiDisplayContextOnTablet(
                     ContextUtils.getApplicationContext());
             mUseGenericViewTypes = SysUtils.isLowEndDevice();
+            mUseNewDownloadPath = ChromeFeatureList.isEnabled(
+                    ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER);
         }
 
         public Builder setIsOffTheRecord(boolean isOffTheRecord) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/UiUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/UiUtils.java
index a8a466d34..4666b99 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/UiUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/UiUtils.java
@@ -242,7 +242,7 @@
                 shownState = CircularProgressView.UiState.PAUSED;
                 break;
             case OfflineItemState.INTERRUPTED:
-                shownState = item.isResumable ? CircularProgressView.UiState.PAUSED
+                shownState = item.isResumable ? CircularProgressView.UiState.RUNNING
                                               : CircularProgressView.UiState.RETRY;
                 break;
             case OfflineItemState.COMPLETE: // Intentional fallthrough.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorFactory.java
index 81930c7..d884cda 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorFactory.java
@@ -7,6 +7,7 @@
 import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
 
+import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.components.offline_items_collection.OfflineContentProvider;
 
@@ -17,7 +18,7 @@
 public class OfflineContentAggregatorFactory {
     // TODO(crbug.com/857543): Remove this after downloads have implemented it.
     // We need only one provider, since OfflineContentAggregator lives in the original profile.
-    private static DownloadBlockedOfflineContentProvider sBlockedProvider;
+    private static OfflineContentProvider sProvider;
 
     private OfflineContentAggregatorFactory() {}
 
@@ -32,9 +33,9 @@
     public static void setOfflineContentProviderForTests(
             @Nullable OfflineContentProvider provider) {
         if (provider == null) {
-            sBlockedProvider = null;
+            sProvider = null;
         } else {
-            sBlockedProvider = new DownloadBlockedOfflineContentProvider(provider);
+            sProvider = getProvider(provider);
         }
     }
 
@@ -45,11 +46,18 @@
      * @return An {@link OfflineContentProvider} instance.
      */
     public static OfflineContentProvider forProfile(Profile profile) {
-        if (sBlockedProvider == null) {
-            sBlockedProvider = new DownloadBlockedOfflineContentProvider(
-                    nativeGetOfflineContentAggregatorForProfile(profile));
+        if (sProvider == null) {
+            sProvider = getProvider(nativeGetOfflineContentAggregatorForProfile(profile));
         }
-        return sBlockedProvider;
+        return sProvider;
+    }
+
+    private static OfflineContentProvider getProvider(OfflineContentProvider provider) {
+        if (ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER)) {
+            return provider;
+        } else {
+            return new DownloadBlockedOfflineContentProvider(provider);
+        }
     }
 
     private static native OfflineContentProvider nativeGetOfflineContentAggregatorForProfile(
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadActivityTest.java
index 125cbdf..bc7e60b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadActivityTest.java
@@ -145,6 +145,7 @@
         features.put(ChromeFeatureList.DOWNLOAD_HOME_SHOW_STORAGE_INFO, false);
         features.put(ChromeFeatureList.DOWNLOAD_HOME_V2, false);
         features.put(ChromeFeatureList.OVERSCROLL_HISTORY_NAVIGATION, false);
+        features.put(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER, false);
         ChromeFeatureList.setTestFeatures(features);
 
         mStubbedProvider = new StubbedProvider();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadInfoBarControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadInfoBarControllerTest.java
index 7618ab2b..1c7c920 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadInfoBarControllerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadInfoBarControllerTest.java
@@ -164,6 +164,7 @@
     @Test
     @SmallTest
     @Feature({"Download"})
+    @Features.DisableFeatures(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER)
     public void testAccelerated() {
         OfflineItem offlineItem = createOfflineItem(OfflineItemState.IN_PROGRESS);
         offlineItem.isAccelerated = true;
@@ -174,6 +175,7 @@
     @Test
     @SmallTest
     @Feature({"Download"})
+    @Features.DisableFeatures(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER)
     public void testMultipleDownloadInProgress() {
         OfflineItem item1 = createOfflineItem(OfflineItemState.IN_PROGRESS);
         mTestController.onDownloadItemUpdated(createDownloadItem(item1));
@@ -187,6 +189,7 @@
     @Test
     @SmallTest
     @Feature({"Download"})
+    @Features.DisableFeatures(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER)
     public void testAcceleratedChangesToDownloadingAfterDelay() {
         OfflineItem item1 = createOfflineItem(OfflineItemState.IN_PROGRESS);
         item1.isAccelerated = true;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadNotificationServiceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadNotificationServiceTest.java
index b612ce8..26eb375 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadNotificationServiceTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadNotificationServiceTest.java
@@ -45,6 +45,7 @@
  */
 @RunWith(ParameterizedRunner.class)
 @UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class)
+@Features.DisableFeatures(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER)
 public class DownloadNotificationServiceTest {
     private static final ContentId ID1 =
             LegacyHelpers.buildLegacyContentId(false, UUID.randomUUID().toString());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadTestRule.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadTestRule.java
index 8d4ba0a..1c678b0 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadTestRule.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadTestRule.java
@@ -20,12 +20,18 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.download.items.OfflineContentAggregatorFactory;
 import org.chromium.chrome.browser.preferences.PrefServiceBridge;
 import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.components.offline_items_collection.ContentId;
+import org.chromium.components.offline_items_collection.OfflineContentProvider;
+import org.chromium.components.offline_items_collection.OfflineItem;
+import org.chromium.components.offline_items_collection.OfflineItemState;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
@@ -194,6 +200,22 @@
         }
     }
 
+    private class TestDownloadBackendObserver implements OfflineContentProvider.Observer {
+        @Override
+        public void onItemsAdded(ArrayList<OfflineItem> items) {}
+
+        @Override
+        public void onItemRemoved(ContentId id) {}
+
+        @Override
+        public void onItemUpdated(OfflineItem item) {
+            if (item.state == OfflineItemState.COMPLETE) {
+                mLastDownloadFilePath = item.filePath;
+                mHttpDownloadFinished.notifyCalled();
+            }
+        }
+    }
+
     @Override
     public Statement apply(final Statement base, Description description) {
         return super.apply(new Statement() {
@@ -222,6 +244,8 @@
                             new SystemDownloadNotifier(), new Handler(), UPDATE_DELAY_MILLIS));
             DownloadController.setDownloadNotificationService(
                     DownloadManagerService.getDownloadManagerService());
+            OfflineContentAggregatorFactory.forProfile(null).addObserver(
+                    new TestDownloadBackendObserver());
         });
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/home/DownloadActivityV2Test.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/home/DownloadActivityV2Test.java
index 5ab4ff8..0666e59 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/home/DownloadActivityV2Test.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/home/DownloadActivityV2Test.java
@@ -63,6 +63,16 @@
     public void setUpTest() throws Exception {
         super.setUpTest();
         MockitoAnnotations.initMocks(this);
+
+        Map<String, Boolean> features = new HashMap<>();
+        features.put(ChromeFeatureList.DOWNLOADS_LOCATION_CHANGE, true);
+        features.put(ChromeFeatureList.DOWNLOAD_HOME_SHOW_STORAGE_INFO, true);
+        features.put(ChromeFeatureList.DOWNLOAD_HOME_V2, true);
+        features.put(ChromeFeatureList.OFFLINE_PAGES_PREFETCHING, true);
+        features.put(ChromeFeatureList.OVERSCROLL_HISTORY_NAVIGATION, false);
+        features.put(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER, false);
+        ChromeFeatureList.setTestFeatures(features);
+
         StubbedOfflineContentProvider stubbedOfflineContentProvider =
                 new StubbedOfflineContentProvider();
         OfflineContentAggregatorFactory.setOfflineContentProviderForTests(
@@ -79,14 +89,6 @@
         stubbedOfflineContentProvider.addItem(item3);
 
         TrackerFactory.setTrackerForTests(mTracker);
-
-        Map<String, Boolean> features = new HashMap<>();
-        features.put(ChromeFeatureList.DOWNLOADS_LOCATION_CHANGE, true);
-        features.put(ChromeFeatureList.DOWNLOAD_HOME_SHOW_STORAGE_INFO, true);
-        features.put(ChromeFeatureList.DOWNLOAD_HOME_V2, true);
-        features.put(ChromeFeatureList.OFFLINE_PAGES_PREFETCHING, true);
-        features.put(ChromeFeatureList.OVERSCROLL_HISTORY_NAVIGATION, false);
-        ChromeFeatureList.setTestFeatures(features);
     }
 
     private void setUpUi() {
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutatorTest.java
index 13576e7..a3bc770 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutatorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutatorTest.java
@@ -58,6 +58,7 @@
     public void setUp() {
         mModel = new ListItemModel();
         Map<String, Boolean> testFeatures = new HashMap<>();
+        testFeatures.put(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER, true);
         ChromeFeatureList.setTestFeatures(testFeatures);
     }
 
diff --git a/chrome/browser/android/download/download_manager_bridge.cc b/chrome/browser/android/download/download_manager_bridge.cc
index e4f494a..a0f7423 100644
--- a/chrome/browser/android/download/download_manager_bridge.cc
+++ b/chrome/browser/android/download/download_manager_bridge.cc
@@ -24,14 +24,15 @@
 static void JNI_DownloadManagerBridge_OnAddCompletedDownloadDone(
     JNIEnv* env,
     jlong callback_id,
-    jlong download_id) {
+    jlong download_id,
+    jboolean can_resolve) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(callback_id);
 
   // Convert java long long int to c++ pointer, take ownership.
   std::unique_ptr<AddCompletedDownloadCallback> cb(
       reinterpret_cast<AddCompletedDownloadCallback*>(callback_id));
-  std::move(*cb).Run(download_id);
+  std::move(*cb).Run(download_id, can_resolve);
 }
 
 void DownloadManagerBridge::AddCompletedDownload(
diff --git a/chrome/browser/android/download/download_manager_bridge.h b/chrome/browser/android/download/download_manager_bridge.h
index 5c87f8c..c726b1d5 100644
--- a/chrome/browser/android/download/download_manager_bridge.h
+++ b/chrome/browser/android/download/download_manager_bridge.h
@@ -9,7 +9,7 @@
 #include "components/download/public/common/download_item.h"
 
 using DownloadItem = download::DownloadItem;
-using AddCompletedDownloadCallback = base::OnceCallback<void(int64_t)>;
+using AddCompletedDownloadCallback = base::OnceCallback<void(int64_t, bool)>;
 
 // This class pairs with DownloadManagerBridge on Java side, that handles all
 // the android DownloadManager related functionalities. Both classes have only
diff --git a/chrome/browser/download/download_offline_content_provider.cc b/chrome/browser/download/download_offline_content_provider.cc
index 679721e..22dcce7 100644
--- a/chrome/browser/download/download_offline_content_provider.cc
+++ b/chrome/browser/download/download_offline_content_provider.cc
@@ -198,7 +198,6 @@
   if (item->GetState() == DownloadItem::COMPLETE) {
     // TODO(crbug.com/938152): May be move this to DownloadItem.
     AddCompletedDownload(item);
-    return;
   }
 
   UpdateObservers(item);
@@ -219,23 +218,23 @@
 
 void DownloadOfflineContentProvider::AddCompletedDownload(DownloadItem* item) {
 #if defined(OS_ANDROID)
-  if (!item->GetTargetFilePath().IsContentUri()) {
-    DownloadManagerBridge::AddCompletedDownload(
-        item, base::BindOnce(
-                  &DownloadOfflineContentProvider::AddCompletedDownloadDone,
-                  weak_ptr_factory_.GetWeakPtr(), item));
-  } else {
-    AddCompletedDownloadDone(item, -1);
-  }
-#else
-  AddCompletedDownloadDone(item, -1);
+  if (completed_downloads_.find(item->GetGuid()) != completed_downloads_.end())
+    return;
+  completed_downloads_.insert(item->GetGuid());
+
+  DownloadManagerBridge::AddCompletedDownload(
+      item,
+      base::BindOnce(&DownloadOfflineContentProvider::AddCompletedDownloadDone,
+                     weak_ptr_factory_.GetWeakPtr(), item));
 #endif
 }
 
 void DownloadOfflineContentProvider::AddCompletedDownloadDone(
     DownloadItem* item,
-    int64_t system_download_id) {
-  UpdateObservers(item);
+    int64_t system_download_id,
+    bool can_resolve) {
+  if (can_resolve && item->HasUserGesture())
+    item->OpenDownload();
 }
 
 DownloadItem* DownloadOfflineContentProvider::GetDownload(
diff --git a/chrome/browser/download/download_offline_content_provider.h b/chrome/browser/download/download_offline_content_provider.h
index 8a4f9d40..e2acc7d 100644
--- a/chrome/browser/download/download_offline_content_provider.h
+++ b/chrome/browser/download/download_offline_content_provider.h
@@ -72,13 +72,16 @@
                             VisualsCallback callback,
                             const SkBitmap& bitmap);
   void AddCompletedDownload(DownloadItem* item);
-  void AddCompletedDownloadDone(DownloadItem* item, int64_t system_download_id);
+  void AddCompletedDownloadDone(DownloadItem* item,
+                                int64_t system_download_id,
+                                bool can_resolve);
   void UpdateObservers(DownloadItem* item);
 
   base::ObserverList<OfflineContentProvider::Observer>::Unchecked observers_;
   OfflineContentAggregator* aggregator_;
   std::string name_space_;
   DownloadManager* manager_;
+  std::set<std::string> completed_downloads_;
 
   base::WeakPtrFactory<DownloadOfflineContentProvider> weak_ptr_factory_;
 
diff --git a/chrome/browser/download/offline_item_utils.cc b/chrome/browser/download/offline_item_utils.cc
index bb44fa4..3e72fb6 100644
--- a/chrome/browser/download/offline_item_utils.cc
+++ b/chrome/browser/download/offline_item_utils.cc
@@ -81,6 +81,7 @@
   item.is_openable = download_item->CanOpenDownload();
   item.file_path = download_item->GetTargetFilePath();
   item.mime_type = download_item->GetMimeType();
+  // TODO(shaktisahu): Handle any null or generic mime types.
 
   item.page_url = download_item->GetTabUrl();
   item.original_url = download_item->GetOriginalUrl();