Lift loader to activity level.

Also fix a bug that leaves DocumentsUI in a weird state if it fails to
obtain root document.

Change-Id: Ibb67bfd0114f45f41c0000078ca56767b5a4542b
Tests: Manual tests and auto tests.
Bug: 35934082
diff --git a/src/com/android/documentsui/AbstractActionHandler.java b/src/com/android/documentsui/AbstractActionHandler.java
index 9e69c09..d4e964c 100644
--- a/src/com/android/documentsui/AbstractActionHandler.java
+++ b/src/com/android/documentsui/AbstractActionHandler.java
@@ -18,12 +18,17 @@
 
 import static com.android.documentsui.base.DocumentInfo.getCursorInt;
 import static com.android.documentsui.base.DocumentInfo.getCursorString;
+import static com.android.documentsui.base.Shared.DEBUG;
 
 import android.app.Activity;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.Context;
 import android.content.Intent;
+import android.content.Loader;
 import android.content.pm.ResolveInfo;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Parcelable;
 import android.provider.DocumentsContract;
 import android.support.annotation.VisibleForTesting;
@@ -44,7 +49,6 @@
 import com.android.documentsui.dirlist.AnimationView.AnimationType;
 import com.android.documentsui.dirlist.DocumentDetails;
 import com.android.documentsui.dirlist.FocusHandler;
-import com.android.documentsui.dirlist.Model;
 import com.android.documentsui.files.LauncherActivity;
 import com.android.documentsui.queries.SearchViewManager;
 import com.android.documentsui.roots.LoadRootTask;
@@ -66,6 +70,9 @@
 public abstract class AbstractActionHandler<T extends Activity & CommonAddons>
         implements ActionHandler {
 
+    @VisibleForTesting
+    static final int LOADER_ID = 42;
+
     private static final String TAG = "AbstractActionHandler";
     private static final int REFRESH_SPINNER_TIMEOUT = 500;
 
@@ -79,8 +86,12 @@
     protected final Lookup<String, Executor> mExecutors;
     protected final Injector mInjector;
 
+    private final LoaderBindings mBindings;
+
     private Runnable mDisplayStateChangedListener;
 
+    private DirectoryReloadLock mDirectoryReloadLock;
+
     @Override
     public void registerDisplayStateChangedListener(Runnable l) {
         mDisplayStateChangedListener = l;
@@ -117,6 +128,8 @@
         mSearchMgr = searchMgr;
         mExecutors = executors;
         mInjector = injector;
+
+        mBindings = new LoaderBindings();
     }
 
     @Override
@@ -243,6 +256,18 @@
     }
 
     @Override
+    public void openRootDocument(@Nullable DocumentInfo rootDoc) {
+        if (rootDoc == null) {
+            // There are 2 cases where rootDoc is null -- 1) loading recents; 2) failed to load root
+            // document. Either case we should call refreshCurrentRootAndDirectory() to let
+            // DirectoryFragment update UI.
+            mActivity.refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE);
+        } else {
+            openContainerDocument(rootDoc);
+        }
+    }
+
+    @Override
     public void openContainerDocument(DocumentInfo doc) {
         assert(doc.isContainer());
 
@@ -346,6 +371,22 @@
                 .executeOnExecutor(mExecutors.lookup(uri.getAuthority()));
     }
 
+    @Override
+    public void loadDocumentsForCurrentStack() {
+        DocumentStack stack = mState.stack;
+        if (!stack.isRecents() && stack.isEmpty()) {
+            DirectoryResult result = new DirectoryResult();
+
+            // TODO (b/35996595): Consider plumbing through the actual exception, though it might
+            // not be very useful (always pointing to DatabaseUtils#readExceptionFromParcel()).
+            result.exception = new IllegalStateException("Failed to load root document.");
+            mInjector.getModel().update(result);
+            return;
+        }
+
+        mActivity.getLoaderManager().restartLoader(LOADER_ID, null, mBindings);
+    }
+
     protected final boolean launchToDocument(Uri uri) {
         // We don't support launching to a document in an archive.
         if (!Providers.isArchiveUri(uri)) {
@@ -385,6 +426,65 @@
         return mSelectionMgr.getSelection(new Selection());
     }
 
+    public ActionHandler reset(DirectoryReloadLock reloadLock) {
+        mDirectoryReloadLock = reloadLock;
+        mActivity.getLoaderManager().destroyLoader(LOADER_ID);
+        return this;
+    }
+
+    private final class LoaderBindings implements LoaderCallbacks<DirectoryResult> {
+
+        @Override
+        public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) {
+            Context context = mActivity;
+
+            if (mState.stack.isRecents()) {
+
+                if (DEBUG) Log.d(TAG, "Creating new loader recents.");
+                return new RecentsLoader(context, mRoots, mState, mInjector.features);
+
+            } else {
+
+                Uri contentsUri = mSearchMgr.isSearching()
+                        ? DocumentsContract.buildSearchDocumentsUri(
+                            mState.stack.getRoot().authority,
+                            mState.stack.getRoot().rootId,
+                            mSearchMgr.getCurrentSearch())
+                        : DocumentsContract.buildChildDocumentsUri(
+                                mState.stack.peek().authority,
+                                mState.stack.peek().documentId);
+
+                if (mInjector.config.managedModeEnabled(mState.stack)) {
+                    contentsUri = DocumentsContract.setManageMode(contentsUri);
+                }
+
+                if (DEBUG) Log.d(TAG,
+                        "Creating new directory loader for: "
+                                + DocumentInfo.debugString(mState.stack.peek()));
+
+                return new DirectoryLoader(
+                        context,
+                        mState.stack.getRoot(),
+                        mState.stack.peek(),
+                        contentsUri,
+                        mState.sortModel,
+                        mDirectoryReloadLock,
+                        mSearchMgr.isSearching());
+            }
+        }
+
+        @Override
+        public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
+            if (DEBUG) Log.d(TAG, "Loader has finished for: "
+                    + DocumentInfo.debugString(mState.stack.peek()));
+            assert(result != null);
+
+            mInjector.getModel().update(result);
+        }
+
+        @Override
+        public void onLoaderReset(Loader<DirectoryResult> loader) {}
+    }
     /**
      * A class primarily for the support of isolating our tests
      * from our concrete activity implementations.
diff --git a/src/com/android/documentsui/ActionHandler.java b/src/com/android/documentsui/ActionHandler.java
index 7b0eef4..33f1d7d 100644
--- a/src/com/android/documentsui/ActionHandler.java
+++ b/src/com/android/documentsui/ActionHandler.java
@@ -27,7 +27,6 @@
 import com.android.documentsui.base.DocumentStack;
 import com.android.documentsui.base.RootInfo;
 import com.android.documentsui.dirlist.DocumentDetails;
-import com.android.documentsui.dirlist.Model;
 
 import javax.annotation.Nullable;
 
@@ -84,6 +83,8 @@
 
     void showChooserForDoc(DocumentInfo doc);
 
+    void openRootDocument(@Nullable DocumentInfo rootDoc);
+
     void openContainerDocument(DocumentInfo doc);
 
     void cutToClipboard();
@@ -107,9 +108,11 @@
     void registerDisplayStateChangedListener(Runnable l);
     void unregisterDisplayStateChangedListener(Runnable l);
 
+    void loadDocumentsForCurrentStack();
+
     /**
      * Allow action handler to be initialized in a new scope.
-     * @return
+     * @return this
      */
-    <T extends ActionHandler> T reset(Model model);
+    <T extends ActionHandler> T reset(DirectoryReloadLock reloadLock);
 }
diff --git a/src/com/android/documentsui/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java
index 05365d5..d49908b 100644
--- a/src/com/android/documentsui/BaseActivity.java
+++ b/src/com/android/documentsui/BaseActivity.java
@@ -22,7 +22,6 @@
 
 import android.app.Activity;
 import android.app.Fragment;
-import android.app.FragmentManager;
 import android.content.Intent;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -32,7 +31,6 @@
 import android.os.Bundle;
 import android.os.MessageQueue.IdleHandler;
 import android.provider.DocumentsContract;
-import android.provider.DocumentsContract.Root;
 import android.support.annotation.CallSuper;
 import android.support.annotation.LayoutRes;
 import android.support.annotation.Nullable;
@@ -52,7 +50,6 @@
 import com.android.documentsui.base.State.ViewMode;
 import com.android.documentsui.dirlist.AnimationView;
 import com.android.documentsui.dirlist.DirectoryFragment;
-import com.android.documentsui.dirlist.DocumentsAdapter;
 import com.android.documentsui.prefs.LocalPreferences;
 import com.android.documentsui.prefs.PreferencesMonitor;
 import com.android.documentsui.queries.DebugCommandProcessor;
@@ -155,7 +152,11 @@
              */
             @Override
             public void onSearchChanged(@Nullable String query) {
-                reloadSearch(query);
+                if (query != null) {
+                    Metrics.logUserAction(BaseActivity.this, Metrics.USER_ACTION_SEARCH);
+                }
+
+                mInjector.actions.loadDocumentsForCurrentStack();
             }
 
             @Override
@@ -302,7 +303,7 @@
             new GetRootDocumentTask(
                     root,
                     this,
-                    mInjector.actions::openContainerDocument)
+                    mInjector.actions::openRootDocument)
                     .executeOnExecutor(getExecutorForCurrentDirectory());
         }
     }
@@ -405,14 +406,6 @@
         invalidateOptionsMenu();
     }
 
-    private void reloadSearch(String query) {
-        FragmentManager fm = getFragmentManager();
-        RootInfo root = getCurrentRoot();
-        DocumentInfo cwd = getCurrentDirectory();
-
-        DirectoryFragment.reloadSearch(fm, root, cwd, query);
-    }
-
     private final List<String> getExcludedAuthorities() {
         List<String> authorities = new ArrayList<>();
         if (getIntent().getBooleanExtra(DocumentsContract.EXTRA_EXCLUDE_SELF, false)) {
diff --git a/src/com/android/documentsui/DirectoryLoader.java b/src/com/android/documentsui/DirectoryLoader.java
index 0a4dff1..4fbb3ff 100644
--- a/src/com/android/documentsui/DirectoryLoader.java
+++ b/src/com/android/documentsui/DirectoryLoader.java
@@ -29,6 +29,7 @@
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.OperationCanceledException;
 import android.os.RemoteException;
 import android.provider.DocumentsContract.Document;
@@ -220,7 +221,7 @@
         private final Runnable mContentChangedCallback;
 
         public LockingContentObserver(DirectoryReloadLock lock, Runnable contentChangedCallback) {
-            super(new Handler());
+            super(new Handler(Looper.getMainLooper()));
             mLock = lock;
             mContentChangedCallback = contentChangedCallback;
         }
diff --git a/src/com/android/documentsui/DragShadowBuilder.java b/src/com/android/documentsui/DragShadowBuilder.java
index 2bc6dde..3ba09d0 100644
--- a/src/com/android/documentsui/DragShadowBuilder.java
+++ b/src/com/android/documentsui/DragShadowBuilder.java
@@ -30,7 +30,6 @@
 import com.android.documentsui.base.DocumentInfo;
 import com.android.documentsui.base.Shared;
 import com.android.documentsui.dirlist.IconHelper;
-import com.android.documentsui.dirlist.Model;
 import com.android.documentsui.selection.Selection;
 
 import java.util.List;
diff --git a/src/com/android/documentsui/FocusManager.java b/src/com/android/documentsui/FocusManager.java
index 8126e4d..fe41aa9 100644
--- a/src/com/android/documentsui/FocusManager.java
+++ b/src/com/android/documentsui/FocusManager.java
@@ -47,8 +47,7 @@
 import com.android.documentsui.dirlist.DocumentHolder;
 import com.android.documentsui.dirlist.DocumentsAdapter;
 import com.android.documentsui.dirlist.FocusHandler;
-import com.android.documentsui.dirlist.Model;
-import com.android.documentsui.dirlist.Model.Update;
+import com.android.documentsui.Model.Update;
 import com.android.documentsui.selection.SelectionManager;
 
 import java.util.ArrayList;
diff --git a/src/com/android/documentsui/Injector.java b/src/com/android/documentsui/Injector.java
index 0a3f5bb..8f22d22 100644
--- a/src/com/android/documentsui/Injector.java
+++ b/src/com/android/documentsui/Injector.java
@@ -27,12 +27,13 @@
 import com.android.documentsui.base.EventHandler;
 import com.android.documentsui.base.Features;
 import com.android.documentsui.dirlist.DocumentsAdapter;
-import com.android.documentsui.dirlist.Model;
 import com.android.documentsui.prefs.ScopedPreferences;
+import com.android.documentsui.queries.SearchViewManager;
 import com.android.documentsui.selection.SelectionManager;
 import com.android.documentsui.selection.SelectionManager.SelectionPredicate;
 import com.android.documentsui.ui.DialogController;
 import com.android.documentsui.ui.MessageBuilder;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
@@ -49,6 +50,7 @@
 
     public MenuManager menuManager;
     public DialogController dialogs;
+    public SearchViewManager searchManager;
 
     @ContentScoped
     public ActionModeController actionModeController;
@@ -72,14 +74,24 @@
             ScopedPreferences prefs,
             MessageBuilder messages,
             DialogController dialogs) {
+        this(features, config, prefs, messages, dialogs, new Model(features));
+    }
+
+    @VisibleForTesting
+    public Injector(
+            Features features,
+            ActivityConfig config,
+            ScopedPreferences prefs,
+            MessageBuilder messages,
+            DialogController dialogs,
+            Model model) {
 
         this.features = features;
         this.config = config;
         this.prefs = prefs;
         this.messages = messages;
         this.dialogs = dialogs;
-
-        mModel = new Model(this.features);
+        this.mModel = model;
     }
 
     public Model getModel() {
@@ -101,14 +113,22 @@
         return actionModeController.reset(selectionDetails, menuItemClicker, view);
     }
 
-    public T getActionHandler(@Nullable Model model) {
+    /**
+     * Obtains action handler and resets it if necessary.
+     * @param reloadLock the lock held by {@link com.android.documentsui.selection.BandController}
+     *                   to prevent loader from updating result during band selection. May be
+     *                   {@code null} if called from
+     *                   {@link com.android.documentsui.sidebar.RootsFragment}.
+     * @return the action handler
+     */
+    public T getActionHandler(@Nullable DirectoryReloadLock reloadLock) {
 
         // provide our friend, RootsFragment, early access to this special feature!
-        if (model == null) {
+        if (reloadLock == null) {
             return actions;
         }
 
-        return actions.reset(model);
+        return actions.reset(reloadLock);
     }
 
     /**
diff --git a/src/com/android/documentsui/dirlist/Model.java b/src/com/android/documentsui/Model.java
similarity index 96%
rename from src/com/android/documentsui/dirlist/Model.java
rename to src/com/android/documentsui/Model.java
index 43aed9a..9cc6972 100644
--- a/src/com/android/documentsui/dirlist/Model.java
+++ b/src/com/android/documentsui/Model.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.documentsui.dirlist;
+package com.android.documentsui;
 
 import static com.android.documentsui.base.DocumentInfo.getCursorString;
 import static com.android.documentsui.base.Shared.DEBUG;
@@ -58,11 +58,12 @@
 
     private static final String TAG = "Model";
 
-    @Nullable String info;
-    @Nullable String error;
-    @Nullable DocumentInfo doc;
+    public @Nullable String info;
+    public @Nullable String error;
+    public @Nullable DocumentInfo doc;
 
     private final Features mFeatures;
+
     /** Maps Model ID to cursor positions, for looking up items by Model ID. */
     private final Map<String, Integer> mPositions = new HashMap<>();
     private final Set<String> mFileNames = new HashSet<>();
@@ -98,7 +99,7 @@
         }
     }
 
-    void reset() {
+    public void reset() {
         mCursor = null;
         mCursorCount = 0;
         mIds = new String[0];
@@ -111,7 +112,8 @@
         notifyUpdateListeners();
     }
 
-    void update(DirectoryResult result) {
+    @VisibleForTesting
+    protected void update(DirectoryResult result) {
         assert(result != null);
 
         if (DEBUG) Log.i(TAG, "Updating model with new result set.");
@@ -139,7 +141,7 @@
     }
 
     @VisibleForTesting
-    int getItemCount() {
+    public int getItemCount() {
         return mCursorCount;
     }
 
@@ -200,7 +202,7 @@
         return mCursorCount == 0;
     }
 
-    boolean isLoading() {
+    public boolean isLoading() {
         return mIsLoading;
     }
 
diff --git a/src/com/android/documentsui/base/DocumentInfo.java b/src/com/android/documentsui/base/DocumentInfo.java
index 006553b..3f35ba3 100644
--- a/src/com/android/documentsui/base/DocumentInfo.java
+++ b/src/com/android/documentsui/base/DocumentInfo.java
@@ -272,6 +272,10 @@
         return (flags & Document.FLAG_VIRTUAL_DOCUMENT) != 0;
     }
 
+    public boolean prefersSortByLastModified() {
+        return (flags & Document.FLAG_DIR_PREFERS_LAST_MODIFIED) != 0;
+    }
+
     @Override
     public int hashCode() {
         return derivedUri.hashCode() + mimeType.hashCode();
diff --git a/src/com/android/documentsui/base/DocumentStack.java b/src/com/android/documentsui/base/DocumentStack.java
index 1423666..9aa15c2 100644
--- a/src/com/android/documentsui/base/DocumentStack.java
+++ b/src/com/android/documentsui/base/DocumentStack.java
@@ -168,7 +168,7 @@
     }
 
     public boolean isRecents() {
-        return isEmpty();
+        return mRoot != null && mRoot.isRecents();
     }
 
     public void updateRoot(Collection<RootInfo> matchingRoots) throws FileNotFoundException {
diff --git a/src/com/android/documentsui/base/Shared.java b/src/com/android/documentsui/base/Shared.java
index 7616edb..0e24eda 100644
--- a/src/com/android/documentsui/base/Shared.java
+++ b/src/com/android/documentsui/base/Shared.java
@@ -77,11 +77,6 @@
     public static final String EXTRA_STATE = "state";
 
     /**
-     * Extra flag used to store type of DirectoryFragment's type ResultType type in the bundle.
-     */
-    public static final String EXTRA_TYPE = "type";
-
-    /**
      * Extra flag used to store root of type RootInfo in the bundle.
      */
     public static final String EXTRA_ROOT = "root";
@@ -97,11 +92,6 @@
     public static final String EXTRA_SELECTION = "selection";
 
     /**
-     * Extra flag used to store DirectoryFragment's search mode of boolean type in the bundle.
-     */
-    public static final String EXTRA_SEARCH_MODE = "searchMode";
-
-    /**
      * Extra flag used to store DirectoryFragment's ignore state of boolean type in the bundle.
      */
     public static final String EXTRA_IGNORE_STATE = "ignoreState";
diff --git a/src/com/android/documentsui/clipping/DocumentClipper.java b/src/com/android/documentsui/clipping/DocumentClipper.java
index 1e307ce..0c1b59c 100644
--- a/src/com/android/documentsui/clipping/DocumentClipper.java
+++ b/src/com/android/documentsui/clipping/DocumentClipper.java
@@ -254,7 +254,7 @@
      */
     public void copyFromClipData(
             final RootInfo root,
-            final DocumentInfo destination,
+            final @Nullable DocumentInfo destination,
             final @Nullable ClipData clipData,
             final FileOperations.Callback callback) {
         DocumentStack dstStack = new DocumentStack(root, destination);
@@ -271,7 +271,7 @@
      * @param callback callback to notify when operation finishes
      */
     public void copyFromClipData(
-            final DocumentInfo destination,
+            final @Nullable DocumentInfo destination,
             final DocumentStack docStack,
             final @Nullable ClipData clipData,
             final FileOperations.Callback callback) {
@@ -283,7 +283,7 @@
     /**
      * Copies documents from given clip data to a folder.
      *
-     * @param docStack the document stack to the destination folder, including the destination
+     * @param dstStack the document stack to the destination folder, including the destination
      *            folder.
      * @param clipData the clipData to copy from
      * @param callback callback to notify when operation finishes
@@ -340,12 +340,8 @@
      *
      * @return true if the list of files can be copied to destination.
      */
-    private static boolean canCopy(DocumentInfo dest) {
-        if (dest == null || !dest.isDirectory() || !dest.isCreateSupported()) {
-            return false;
-        }
-
-        return true;
+    private static boolean canCopy(@Nullable DocumentInfo dest) {
+        return dest != null && dest.isDirectory() && dest.isCreateSupported();
     }
 
     public static @OpType int getOpType(ClipData data) {
diff --git a/src/com/android/documentsui/dirlist/DirectoryAddonsAdapter.java b/src/com/android/documentsui/dirlist/DirectoryAddonsAdapter.java
index 51bf589..a551423 100644
--- a/src/com/android/documentsui/dirlist/DirectoryAddonsAdapter.java
+++ b/src/com/android/documentsui/dirlist/DirectoryAddonsAdapter.java
@@ -20,10 +20,11 @@
 import android.support.v7.widget.RecyclerView.AdapterDataObserver;
 import android.view.ViewGroup;
 
+import com.android.documentsui.Model;
 import com.android.documentsui.base.EventListener;
 import com.android.documentsui.dirlist.Message.HeaderMessage;
 import com.android.documentsui.dirlist.Message.InflateMessage;
-import com.android.documentsui.dirlist.Model.Update;
+import com.android.documentsui.Model.Update;
 
 import java.util.List;
 
@@ -174,7 +175,6 @@
             return;
         }
 
-
         // Walk down the list of IDs till we encounter something that's not a directory, and
         // insert a whitespace element - this introduces a visual break in the grid between
         // folders and documents.
diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 3da4276..8a0ba0d 100644
--- a/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -31,11 +31,9 @@
 import android.app.Fragment;
 import android.app.FragmentManager;
 import android.app.FragmentTransaction;
-import android.app.LoaderManager.LoaderCallbacks;
 import android.content.ClipData;
 import android.content.Context;
 import android.content.Intent;
-import android.content.Loader;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Build;
@@ -66,9 +64,7 @@
 import com.android.documentsui.ActionModeController;
 import com.android.documentsui.BaseActivity;
 import com.android.documentsui.BaseActivity.RetainedState;
-import com.android.documentsui.DirectoryLoader;
 import com.android.documentsui.DirectoryReloadLock;
-import com.android.documentsui.DirectoryResult;
 import com.android.documentsui.DocumentsApplication;
 import com.android.documentsui.DragAndDropHelper;
 import com.android.documentsui.FocusManager;
@@ -77,8 +73,8 @@
 import com.android.documentsui.Injector.Injected;
 import com.android.documentsui.ItemDragListener;
 import com.android.documentsui.Metrics;
+import com.android.documentsui.Model;
 import com.android.documentsui.R;
-import com.android.documentsui.RecentsLoader;
 import com.android.documentsui.ThumbnailCache;
 import com.android.documentsui.base.DocumentInfo;
 import com.android.documentsui.base.DocumentStack;
@@ -96,7 +92,6 @@
 import com.android.documentsui.clipping.UrisSupplier;
 import com.android.documentsui.dirlist.AnimationView.AnimationType;
 import com.android.documentsui.picker.PickActivity;
-import com.android.documentsui.roots.RootsAccess;
 import com.android.documentsui.selection.BandController;
 import com.android.documentsui.selection.GestureSelector;
 import com.android.documentsui.selection.Selection;
@@ -122,14 +117,8 @@
 public class DirectoryFragment extends Fragment
         implements ItemDragListener.DragHost, SwipeRefreshLayout.OnRefreshListener {
 
-    @IntDef(flag = true, value = {
-            TYPE_NORMAL,
-            TYPE_RECENT_OPEN
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ResultType {}
-    public static final int TYPE_NORMAL = 1;
-    public static final int TYPE_RECENT_OPEN = 2;
+    static final int TYPE_NORMAL = 1;
+    static final int TYPE_RECENT_OPEN = 2;
 
     @IntDef(flag = true, value = {
             REQUEST_COPY_DESTINATION
@@ -150,7 +139,6 @@
     private Model mModel;
     private final EventListener<Model.Update> mModelUpdateListener = new ModelUpdateListener();
     private final DocumentsAdapter.Environment mAdapterEnv = new AdapterEnvironment();
-    private final LoaderCallbacks<DirectoryResult> mLoaderCallbacks = new LoaderBindings();
 
     @Injected
     @ContentScoped
@@ -201,7 +189,7 @@
     private SortModel.UpdateListener mSortListener = (model, updateType) -> {
         // Only when sort order has changed do we need to trigger another loading.
         if ((updateType & SortModel.UPDATE_TYPE_SORTING) != 0) {
-            getLoaderManager().restartLoader(LOADER_ID, null, mLoaderCallbacks);
+            mActions.loadDocumentsForCurrentStack();
         }
     };
 
@@ -259,6 +247,9 @@
             cancelThumbnailTask(view);
         }
 
+        mModel.removeUpdateListener(mModelUpdateListener);
+        mModel.removeUpdateListener(mAdapter.getModelUpdateListener());
+
         super.onDestroyView();
     }
 
@@ -312,7 +303,7 @@
 
         mSelectionMgr = mInjector.getSelectionManager(mAdapter, this::canSetSelectionState);
         mFocusManager = mInjector.getFocusManager(mRecView, mModel);
-        mActions = mInjector.getActionHandler(mModel);
+        mActions = mInjector.getActionHandler(mReloadLock);
 
         mRecView.setAccessibilityDelegateCompat(
                 new AccessibilityEventRouter(mRecView,
@@ -382,14 +373,13 @@
 
         final ActivityManager am = (ActivityManager) mActivity.getSystemService(
                 Context.ACTIVITY_SERVICE);
-        boolean svelte = am.isLowRamDevice() && (mLocalState.mType == TYPE_RECENT_OPEN);
+        boolean svelte = am.isLowRamDevice() && (mState.stack.isRecents());
         mIconHelper.setThumbnailsEnabled(!svelte);
 
         // If mDocument is null, we sort it by last modified by default because it's in Recents.
         final boolean prefersLastModified =
-                (mLocalState.mDocument != null)
-                        ? (mLocalState.mDocument.flags & Document.FLAG_DIR_PREFERS_LAST_MODIFIED) != 0
-                        : true;
+                (mLocalState.mDocument == null)
+                || mLocalState.mDocument.prefersSortByLastModified();
         // Call this before adding the listener to avoid restarting the loader one more time
         mState.sortModel.setDefaultDimension(
                 prefersLastModified
@@ -397,7 +387,7 @@
                         : SortModel.SORT_DIMENSION_ID_TITLE);
 
         // Kick off loader at least once
-        getLoaderManager().restartLoader(LOADER_ID, null, mLoaderCallbacks);
+        mActions.loadDocumentsForCurrentStack();
     }
 
     @Override
@@ -1073,36 +1063,17 @@
     public static void showDirectory(
             FragmentManager fm, RootInfo root, DocumentInfo doc, int anim) {
         if (DEBUG) Log.d(TAG, "Showing directory: " + DocumentInfo.debugString(doc));
-        create(fm, TYPE_NORMAL, root, doc, null, anim);
+        create(fm, root, doc, anim);
     }
 
     public static void showRecentsOpen(FragmentManager fm, int anim) {
-        create(fm, TYPE_RECENT_OPEN, null, null, null, anim);
-    }
-
-    public static void reloadSearch(FragmentManager fm, RootInfo root, DocumentInfo doc,
-            String query) {
-        DirectoryFragment df = get(fm);
-
-        df.mLocalState.update(root, doc, query);
-        df.getLoaderManager().restartLoader(LOADER_ID, null, df.mLoaderCallbacks);
-    }
-
-    public static void reload(FragmentManager fm, int type, RootInfo root, DocumentInfo doc,
-            String query) {
-        if (DEBUG) Log.d(TAG, "Reloading directory: " + DocumentInfo.debugString(doc));
-        DirectoryFragment df = get(fm);
-
-        df.mLocalState.update(type, root, doc, query);
-        df.getLoaderManager().restartLoader(LOADER_ID, null, df.mLoaderCallbacks);
+        create(fm, null, null, anim);
     }
 
     public static void create(
             FragmentManager fm,
-            int type,
             RootInfo root,
             @Nullable DocumentInfo doc,
-            String query,
             @AnimationType int anim) {
 
         if (DEBUG) {
@@ -1114,10 +1085,8 @@
         }
 
         final Bundle args = new Bundle();
-        args.putInt(Shared.EXTRA_TYPE, type);
         args.putParcelable(Shared.EXTRA_ROOT, root);
         args.putParcelable(Shared.EXTRA_DOC, doc);
-        args.putString(Shared.EXTRA_QUERY, query);
         args.putParcelable(Shared.EXTRA_SELECTION, new Selection());
 
         final FragmentTransaction ft = fm.beginTransaction();
@@ -1160,7 +1129,7 @@
                 mRefreshLayout.setRefreshing(false);
             } else {
                 // If Refresh API isn't available, we will explicitly reload the loader
-                getLoaderManager().restartLoader(LOADER_ID, null, mLoaderCallbacks);
+                mActions.loadDocumentsForCurrentStack();
             }
         });
     }
@@ -1173,131 +1142,10 @@
 
             mProgressBar.setVisibility(mModel.isLoading() ? View.VISIBLE : View.GONE);
 
-            mAdapter.notifyDataSetChanged();
-
-            if (!mModel.isLoading()) {
-                mActivity.notifyDirectoryLoaded(
-                        mModel.doc != null ? mModel.doc.derivedUri : null);
-            }
-        }
-    }
-
-    private final class AdapterEnvironment implements DocumentsAdapter.Environment {
-
-        @Override
-        public Features getFeatures() {
-            return mInjector.features;
-        }
-
-        @Override
-        public Context getContext() {
-            return mActivity;
-        }
-
-        @Override
-        public State getDisplayState() {
-            return mState;
-        }
-
-        @Override
-        public boolean isInSearchMode() {
-            return mLocalState.mSearchMode;
-        }
-
-        @Override
-        public Model getModel() {
-            return mModel;
-        }
-
-        @Override
-        public int getColumnCount() {
-            return mColumnCount;
-        }
-
-        @Override
-        public boolean isSelected(String id) {
-            return mSelectionMgr.getSelection().contains(id);
-        }
-
-        @Override
-        public boolean isDocumentEnabled(String mimeType, int flags) {
-            return mInjector.config.isDocumentEnabled(mimeType, flags, mState);
-        }
-
-        @Override
-        public void initDocumentHolder(DocumentHolder holder) {
-            holder.addKeyEventListener(mInputHandler);
-            holder.itemView.setOnFocusChangeListener(mFocusManager);
-        }
-
-        @Override
-        public void onBindDocumentHolder(DocumentHolder holder, Cursor cursor) {
-            setupDragAndDropOnDocumentView(holder.itemView, cursor);
-        }
-    }
-
-    private final class LoaderBindings implements LoaderCallbacks<DirectoryResult> {
-
-        @Override
-        public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) {
-            Context context = getActivity();
-
-            Uri contentsUri;
-            switch (mLocalState.mType) {
-                case TYPE_NORMAL:
-                    contentsUri = mLocalState.mSearchMode
-                            ? DocumentsContract.buildSearchDocumentsUri(
-                                    mLocalState.mRoot.authority,
-                                    mLocalState.mRoot.rootId,
-                                    mLocalState.mQuery)
-                            : DocumentsContract.buildChildDocumentsUri(
-                                    mLocalState.mDocument.authority,
-                                    mLocalState.mDocument.documentId);
-
-                    if (mInjector.config.managedModeEnabled(mState.stack)) {
-                        contentsUri = DocumentsContract.setManageMode(contentsUri);
-                    }
-
-                    if (DEBUG) Log.d(TAG,
-                            "Creating new directory loader for: "
-                            + DocumentInfo.debugString(mLocalState.mDocument));
-
-                    return new DirectoryLoader(
-                            context,
-                            mLocalState.mRoot,
-                            mLocalState.mDocument,
-                            contentsUri,
-                            mState.sortModel,
-                            mReloadLock,
-                            mLocalState.mSearchMode);
-
-                case TYPE_RECENT_OPEN:
-                    if (DEBUG) Log.d(TAG, "Creating new loader recents.");
-                    final RootsAccess roots = DocumentsApplication.getRootsCache(context);
-                    return new RecentsLoader(context, roots, mState, mInjector.features);
-
-                default:
-                    throw new IllegalStateException("Unknown type " + mLocalState.mType);
-            }
-        }
-
-        @Override
-        public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
-            if (DEBUG) Log.d(TAG, "Loader has finished for: "
-                    + DocumentInfo.debugString(mLocalState.mDocument));
-            assert(result != null);
-
-            if (!isAdded()) return;
-
-            if (mLocalState.mSearchMode) {
-                Metrics.logUserAction(getContext(), Metrics.USER_ACTION_SEARCH);
-            }
-
-            mAdapter.notifyDataSetChanged();
-            mModel.update(result);
-
             updateLayout(mState.derivedMode);
 
+            mAdapter.notifyDataSetChanged();
+
             if (mRestoredSelection != null) {
                 mSelectionMgr.restoreSelection(mRestoredSelection);
                 // Note, we'll take care of cleaning up retained selection
@@ -1330,14 +1178,65 @@
                         () -> mRefreshLayout.setRefreshing(false),
                         REFRESH_SPINNER_TIMEOUT);
             }
+
+            if (!mModel.isLoading()) {
+                mActivity.notifyDirectoryLoaded(
+                        mModel.doc != null ? mModel.doc.derivedUri : null);
+            }
+        }
+    }
+
+    private final class AdapterEnvironment implements DocumentsAdapter.Environment {
+
+        @Override
+        public Features getFeatures() {
+            return mInjector.features;
         }
 
         @Override
-        public void onLoaderReset(Loader<DirectoryResult> loader) {
-            if (DEBUG) Log.d(TAG, "Resetting loader for: "
-                        + DocumentInfo.debugString(mLocalState.mDocument));
+        public Context getContext() {
+            return mActivity;
+        }
 
-            mRefreshLayout.setRefreshing(false);
+        @Override
+        public State getDisplayState() {
+            return mState;
+        }
+
+        @Override
+        public boolean isInSearchMode() {
+            return mInjector.searchManager.isSearching();
+        }
+
+        @Override
+        public Model getModel() {
+            return mModel;
+        }
+
+        @Override
+        public int getColumnCount() {
+            return mColumnCount;
+        }
+
+        @Override
+        public boolean isSelected(String id) {
+            return mSelectionMgr.getSelection().contains(id);
+        }
+
+        @Override
+        public boolean isDocumentEnabled(String mimeType, int flags) {
+            return mInjector.config.isDocumentEnabled(mimeType, flags, mState);
+        }
+
+        @Override
+        public void initDocumentHolder(DocumentHolder holder) {
+            holder.addKeyEventListener(mInputHandler);
+            holder.itemView.setOnFocusChangeListener(mFocusManager);
+        }
+
+        @Override
+        public void onBindDocumentHolder(DocumentHolder holder, Cursor cursor) {
+            setupDragAndDropOnDocumentView(holder.itemView, cursor);
         }
     }
 }
diff --git a/src/com/android/documentsui/dirlist/DirectoryState.java b/src/com/android/documentsui/dirlist/DirectoryState.java
index 6b849a2..b27a4b8 100644
--- a/src/com/android/documentsui/dirlist/DirectoryState.java
+++ b/src/com/android/documentsui/dirlist/DirectoryState.java
@@ -20,7 +20,6 @@
 import com.android.documentsui.base.DocumentInfo;
 import com.android.documentsui.base.RootInfo;
 import com.android.documentsui.base.Shared;
-import com.android.documentsui.dirlist.DirectoryFragment.ResultType;
 import com.android.documentsui.services.FileOperation;
 import com.android.documentsui.services.FileOperationService;
 import com.android.documentsui.sorting.SortModel;
@@ -33,38 +32,28 @@
     private static final String EXTRA_SORT_DIMENSION_ID = "sortDimensionId";
     private static final String EXTRA_SORT_DIRECTION = "sortDirection";
 
-    // Directory fragment state is defined by: root, document, query, type, selection
-    @ResultType int mType = DirectoryFragment.TYPE_NORMAL;
-    RootInfo mRoot;
     // Null when viewing Recents directory.
     @Nullable DocumentInfo mDocument;
-    String mQuery = null;
     // Here we save the clip details of moveTo/copyTo actions when picker shows up.
     // This will be written to saved instance.
     @Nullable FileOperation mPendingOperation;
-    boolean mSearchMode;
     int mLastSortDimensionId = SortModel.SORT_DIMENSION_ID_UNKNOWN;
     @SortDirection int mLastSortDirection;
 
+    private RootInfo mRoot;
     private String mConfigKey;
 
     public void restore(Bundle bundle) {
         mRoot = bundle.getParcelable(Shared.EXTRA_ROOT);
         mDocument = bundle.getParcelable(Shared.EXTRA_DOC);
-        mQuery = bundle.getString(Shared.EXTRA_QUERY);
-        mType = bundle.getInt(Shared.EXTRA_TYPE);
-        mSearchMode = bundle.getBoolean(Shared.EXTRA_SEARCH_MODE);
         mPendingOperation = bundle.getParcelable(FileOperationService.EXTRA_OPERATION);
         mLastSortDimensionId = bundle.getInt(EXTRA_SORT_DIMENSION_ID);
         mLastSortDirection = bundle.getInt(EXTRA_SORT_DIRECTION);
     }
 
     public void save(Bundle bundle) {
-        bundle.putInt(Shared.EXTRA_TYPE, mType);
         bundle.putParcelable(Shared.EXTRA_ROOT, mRoot);
         bundle.putParcelable(Shared.EXTRA_DOC, mDocument);
-        bundle.putString(Shared.EXTRA_QUERY, mQuery);
-        bundle.putBoolean(Shared.EXTRA_SEARCH_MODE, mSearchMode);
         bundle.putParcelable(FileOperationService.EXTRA_OPERATION, mPendingOperation);
         bundle.putInt(EXTRA_SORT_DIMENSION_ID, mLastSortDimensionId);
         bundle.putInt(EXTRA_SORT_DIRECTION, mLastSortDirection);
@@ -76,16 +65,9 @@
         return op;
     }
 
-    public void update(int type, RootInfo root, DocumentInfo doc, String query) {
-        mType = type;
-        update(root, doc, query);
-    }
-
-    public void update(RootInfo root, DocumentInfo doc, String query) {
-        mQuery = query;
+    public void update(RootInfo root, DocumentInfo doc) {
         mRoot = root;
         mDocument = doc;
-        mSearchMode =  query != null;
     }
 
     String getConfigKey() {
diff --git a/src/com/android/documentsui/dirlist/DocumentsAdapter.java b/src/com/android/documentsui/dirlist/DocumentsAdapter.java
index 9b1794c..297584f 100644
--- a/src/com/android/documentsui/dirlist/DocumentsAdapter.java
+++ b/src/com/android/documentsui/dirlist/DocumentsAdapter.java
@@ -24,6 +24,7 @@
 import android.support.v7.widget.GridLayoutManager;
 import android.support.v7.widget.RecyclerView;
 
+import com.android.documentsui.Model;
 import com.android.documentsui.base.EventListener;
 import com.android.documentsui.base.Features;
 import com.android.documentsui.base.State;
diff --git a/src/com/android/documentsui/dirlist/DragStartListener.java b/src/com/android/documentsui/dirlist/DragStartListener.java
index 6598a87..a0b0f64 100644
--- a/src/com/android/documentsui/dirlist/DragStartListener.java
+++ b/src/com/android/documentsui/dirlist/DragStartListener.java
@@ -26,6 +26,7 @@
 import android.view.View;
 
 import com.android.documentsui.DragShadowBuilder;
+import com.android.documentsui.Model;
 import com.android.documentsui.base.DocumentInfo;
 import com.android.documentsui.base.Events;
 import com.android.documentsui.base.Events.InputEvent;
diff --git a/src/com/android/documentsui/dirlist/Message.java b/src/com/android/documentsui/dirlist/Message.java
index d8044bb..fdaf786 100644
--- a/src/com/android/documentsui/dirlist/Message.java
+++ b/src/com/android/documentsui/dirlist/Message.java
@@ -25,7 +25,7 @@
 import com.android.documentsui.base.RootInfo;
 import com.android.documentsui.base.Shared;
 import com.android.documentsui.dirlist.DocumentsAdapter.Environment;
-import com.android.documentsui.dirlist.Model.Update;
+import com.android.documentsui.Model.Update;
 
 /**
  * Data object used by {@link InflateMessageDocumentHolder} and {@link HeaderMessageDocumentHolder}.
diff --git a/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java b/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java
index c7c2526..1e080bf 100644
--- a/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java
+++ b/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java
@@ -18,7 +18,6 @@
 
 import static com.android.documentsui.base.DocumentInfo.getCursorInt;
 import static com.android.documentsui.base.DocumentInfo.getCursorString;
-import static com.android.documentsui.base.Shared.DEBUG;
 import static com.android.documentsui.base.State.MODE_GRID;
 import static com.android.documentsui.base.State.MODE_LIST;
 
@@ -27,14 +26,13 @@
 import android.util.Log;
 import android.view.ViewGroup;
 
+import com.android.documentsui.Model;
 import com.android.documentsui.base.EventListener;
 import com.android.documentsui.base.State;
-import com.android.documentsui.dirlist.Model.Update;
+import com.android.documentsui.Model.Update;
 
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 
 /**
  * Adapts from dirlist.Model to something RecyclerView understands.
diff --git a/src/com/android/documentsui/files/ActionHandler.java b/src/com/android/documentsui/files/ActionHandler.java
index 5720ea1..6926fb8 100644
--- a/src/com/android/documentsui/files/ActionHandler.java
+++ b/src/com/android/documentsui/files/ActionHandler.java
@@ -53,7 +53,7 @@
 import com.android.documentsui.clipping.UrisSupplier;
 import com.android.documentsui.dirlist.AnimationView;
 import com.android.documentsui.dirlist.DocumentDetails;
-import com.android.documentsui.dirlist.Model;
+import com.android.documentsui.Model;
 import com.android.documentsui.files.ActionHandler.Addons;
 import com.android.documentsui.queries.SearchViewManager;
 import com.android.documentsui.roots.GetRootDocumentTask;
@@ -84,7 +84,7 @@
     private final DialogController mDialogs;
     private final DocumentClipper mClipper;
     private final ClipStore mClipStore;
-    private @Nullable Model mModel;
+    private final Model mModel;
 
     ActionHandler(
             T activity,
@@ -106,6 +106,7 @@
         mDialogs = injector.dialogs;
         mClipper = clipper;
         mClipStore = clipStore;
+        mModel = injector.getModel();
     }
 
     @Override
@@ -113,7 +114,6 @@
         new GetRootDocumentTask(
                 root,
                 mActivity,
-                mActivity::isDestroyed,
                 (DocumentInfo rootDoc) -> dropOnCallback(event, rootDoc, root)
         ).executeOnExecutor(mExecutors.lookup(root.authority));
         return true;
@@ -150,12 +150,11 @@
         new GetRootDocumentTask(
                 root,
                 mActivity,
-                mActivity::isDestroyed,
                 (DocumentInfo doc) -> pasteIntoFolder(root, doc)
         ).executeOnExecutor(mExecutors.lookup(root.authority));
     }
 
-    private void pasteIntoFolder(RootInfo root, DocumentInfo doc) {
+    private void pasteIntoFolder(RootInfo root, @Nullable DocumentInfo doc) {
         DocumentClipper clipper = DocumentsApplication.getDocumentClipper(mActivity);
         DocumentStack stack = new DocumentStack(root, doc);
         clipper.copyFromClipboard(doc, stack, mDialogs::showFileOperationStatus);
@@ -620,16 +619,6 @@
         return intent;
     }
 
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public ActionHandler<T> reset(Model model) {
-        assert(model != null);
-        mModel = model;
-
-        return this;
-    }
-
     public interface Addons extends CommonAddons {
     }
 }
diff --git a/src/com/android/documentsui/files/FilesActivity.java b/src/com/android/documentsui/files/FilesActivity.java
index b5989b2..c909488 100644
--- a/src/com/android/documentsui/files/FilesActivity.java
+++ b/src/com/android/documentsui/files/FilesActivity.java
@@ -134,6 +134,8 @@
                 DocumentsApplication.getClipStore(this),
                 mInjector);
 
+        mInjector.searchManager = mSearchManager;
+
         mActivityInputHandler =
                 new ActivityInputHandler(mInjector.actions::deleteSelectedDocuments);
         mSharedInputHandler =
@@ -274,7 +276,7 @@
 
         assert(!mSearchManager.isSearching());
 
-        if (cwd == null) {
+        if (mState.stack.isRecents()) {
             DirectoryFragment.showRecentsOpen(fm, anim);
         } else {
             // Normal boring directory
diff --git a/src/com/android/documentsui/files/QuickViewIntentBuilder.java b/src/com/android/documentsui/files/QuickViewIntentBuilder.java
index c85af8a..189a889 100644
--- a/src/com/android/documentsui/files/QuickViewIntentBuilder.java
+++ b/src/com/android/documentsui/files/QuickViewIntentBuilder.java
@@ -38,7 +38,7 @@
 import com.android.documentsui.R;
 import com.android.documentsui.base.DebugFlags;
 import com.android.documentsui.base.DocumentInfo;
-import com.android.documentsui.dirlist.Model;
+import com.android.documentsui.Model;
 import com.android.documentsui.roots.RootCursorWrapper;
 
 import java.util.ArrayList;
diff --git a/src/com/android/documentsui/picker/ActionHandler.java b/src/com/android/documentsui/picker/ActionHandler.java
index a92685a..c00345c 100644
--- a/src/com/android/documentsui/picker/ActionHandler.java
+++ b/src/com/android/documentsui/picker/ActionHandler.java
@@ -39,7 +39,7 @@
 import com.android.documentsui.base.Shared;
 import com.android.documentsui.base.State;
 import com.android.documentsui.dirlist.DocumentDetails;
-import com.android.documentsui.dirlist.Model;
+import com.android.documentsui.Model;
 import com.android.documentsui.picker.ActionHandler.Addons;
 import com.android.documentsui.queries.SearchViewManager;
 import com.android.documentsui.roots.RootsAccess;
@@ -190,16 +190,6 @@
         return false;
     }
 
-    @SuppressWarnings("unchecked")
-    @Override
-    public ActionHandler<T> reset(Model model) {
-        assert(model != null);
-        mModel = model;
-
-        return this;
-    }
-
-
     public interface Addons extends CommonAddons {
         void onAppPicked(ResolveInfo info);
         void onDocumentPicked(DocumentInfo doc);
diff --git a/src/com/android/documentsui/picker/PickActivity.java b/src/com/android/documentsui/picker/PickActivity.java
index 40c307a..1c7fc1e 100644
--- a/src/com/android/documentsui/picker/PickActivity.java
+++ b/src/com/android/documentsui/picker/PickActivity.java
@@ -111,11 +111,13 @@
                 getColor(R.color.accent_dark));
 
         mInjector.menuManager = new MenuManager(mSearchManager, mState, new DirectoryDetails(this));
+
         mInjector.actionModeController = new ActionModeController(
                 this,
                 mInjector.selectionMgr,
                 mInjector.menuManager,
                 mInjector.messages);
+
         mInjector.actions = new ActionHandler<>(
                 this,
                 mState,
@@ -125,6 +127,8 @@
                 ProviderExecutor::forAuthority,
                 mInjector);
 
+        mInjector.searchManager = mSearchManager;
+
         Intent intent = getIntent();
 
         mSharedInputHandler =
@@ -277,8 +281,7 @@
         final RootInfo root = getCurrentRoot();
         final DocumentInfo cwd = getCurrentDirectory();
 
-        if (cwd == null) {
-            // No directory means recents
+        if (mState.stack.isRecents()) {
             if (mState.action == ACTION_CREATE ||
                 mState.action == ACTION_PICK_COPY_DESTINATION) {
                 mInjector.actions.loadRoot(Shared.getDefaultRootUri(this));
diff --git a/src/com/android/documentsui/queries/SearchViewManager.java b/src/com/android/documentsui/queries/SearchViewManager.java
index 1b773ea..64ec168 100644
--- a/src/com/android/documentsui/queries/SearchViewManager.java
+++ b/src/com/android/documentsui/queries/SearchViewManager.java
@@ -307,7 +307,7 @@
         return true;
     }
 
-    String getCurrentSearch() {
+    public String getCurrentSearch() {
         return mCurrentSearch;
     }
 
diff --git a/src/com/android/documentsui/roots/GetRootDocumentTask.java b/src/com/android/documentsui/roots/GetRootDocumentTask.java
index 1503ae1..e7fdadf 100644
--- a/src/com/android/documentsui/roots/GetRootDocumentTask.java
+++ b/src/com/android/documentsui/roots/GetRootDocumentTask.java
@@ -41,30 +41,15 @@
     private final RootInfo mRootInfo;
     private final Context mContext;
     private final Consumer<DocumentInfo> mCallback;
-    private boolean mForceCallback;
 
     public GetRootDocumentTask(
             RootInfo rootInfo, Activity activity, Consumer<DocumentInfo> callback) {
-        this(rootInfo, activity, activity::isDestroyed, callback);
-    }
-
-    public GetRootDocumentTask(
-            RootInfo rootInfo, Fragment fragment, Consumer<DocumentInfo> callback) {
-        this(rootInfo, fragment.getContext(), fragment::isDetached, callback);
-    }
-
-    public GetRootDocumentTask(
-            RootInfo rootInfo, Context context, Check check, Consumer<DocumentInfo> callback) {
-        super(check);
+        super(activity::isDestroyed);
         mRootInfo = rootInfo;
-        mContext = context.getApplicationContext();
+        mContext = activity.getApplicationContext();
         mCallback = callback;
     }
 
-    public void setForceCallback(boolean forceCallback) {
-        mForceCallback = forceCallback;
-    }
-
     @Override
     public @Nullable DocumentInfo run(Void... rootInfo) {
         return mRootInfo.getRootDocumentBlocking(mContext);
@@ -74,11 +59,9 @@
     public void finish(@Nullable DocumentInfo documentInfo) {
         if (documentInfo == null) {
             Log.e(TAG,
-                    "Cannot find document info for root: " + mRootInfo + " in the given timeout");
+                    "Cannot find document info for root: " + mRootInfo);
         }
 
-        if (documentInfo != null || mForceCallback) {
-            mCallback.accept(documentInfo);
-        }
+        mCallback.accept(documentInfo);
     }
 }
diff --git a/src/com/android/documentsui/sidebar/RootsFragment.java b/src/com/android/documentsui/sidebar/RootsFragment.java
index 7fbe8e9..1d4950f 100644
--- a/src/com/android/documentsui/sidebar/RootsFragment.java
+++ b/src/com/android/documentsui/sidebar/RootsFragment.java
@@ -480,7 +480,6 @@
                     updater.updateDocInfoForRoot(doc);
                 });
         task.setTimeout(CONTEXT_MENU_ITEM_TIMEOUT);
-        task.setForceCallback(true);
         task.executeOnExecutor(getBaseActivity().getExecutorForCurrentDirectory());
     }