Merge "Move openContainerDocument() into ActionHandlers." into nyc-andromeda-dev
diff --git a/src/com/android/documentsui/AbstractActionHandler.java b/src/com/android/documentsui/AbstractActionHandler.java
index fec6268..0809a37 100644
--- a/src/com/android/documentsui/AbstractActionHandler.java
+++ b/src/com/android/documentsui/AbstractActionHandler.java
@@ -22,6 +22,7 @@
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
 import android.os.Parcelable;
+import android.support.annotation.VisibleForTesting;
 
 import com.android.documentsui.AbstractActionHandler.CommonAddons;
 import com.android.documentsui.base.BooleanConsumer;
@@ -31,6 +32,7 @@
 import com.android.documentsui.base.RootInfo;
 import com.android.documentsui.base.Shared;
 import com.android.documentsui.base.State;
+import com.android.documentsui.dirlist.AnimationView;
 import com.android.documentsui.dirlist.AnimationView.AnimationType;
 import com.android.documentsui.dirlist.DocumentDetails;
 import com.android.documentsui.files.LauncherActivity;
@@ -55,6 +57,7 @@
     protected final RootsAccess mRoots;
     protected final DocumentsAccess mDocs;
     protected final SelectionManager mSelectionMgr;
+    protected final SearchViewManager mSearchMgr;
     protected final Lookup<String, Executor> mExecutors;
 
     public AbstractActionHandler(
@@ -63,6 +66,7 @@
             RootsAccess roots,
             DocumentsAccess docs,
             SelectionManager selectionMgr,
+            SearchViewManager searchMgr,
             Lookup<String, Executor> executors) {
 
         assert(activity != null);
@@ -76,6 +80,7 @@
         mRoots = roots;
         mDocs = docs;
         mSelectionMgr = selectionMgr;
+        mSearchMgr = searchMgr;
         mExecutors = executors;
     }
 
@@ -151,6 +156,21 @@
     }
 
     @Override
+    public void openContainerDocument(DocumentInfo doc) {
+        assert(doc.isContainer());
+
+        mActivity.notifyDirectoryNavigated(doc.derivedUri);
+
+        mState.pushDocument(doc);
+        // Show an opening animation only if pressing "back" would get us back to the
+        // previous directory. Especially after opening a root document, pressing
+        // back, wouldn't go to the previous root, but close the activity.
+        final int anim = (mState.hasLocationChanged() && mState.stack.size() > 1)
+                ? AnimationView.ANIM_ENTER : AnimationView.ANIM_NONE;
+        mActivity.refreshCurrentRootAndDirectory(anim);
+    }
+
+    @Override
     public void deleteSelectedDocuments() {
         throw new UnsupportedOperationException("Delete not supported!");
     }
@@ -179,6 +199,7 @@
     protected Selection getStableSelection() {
         return mSelectionMgr.getSelection(new Selection());
     }
+
     /**
      * A class primarily for the support of isolating our tests
      * from our concrete activity implementations.
@@ -189,9 +210,11 @@
         // TODO: Move this to PickAddons as multi-document picking is exclusive to that activity.
         void onDocumentsPicked(List<DocumentInfo> docs);
         void onDocumentPicked(DocumentInfo doc);
-        void openContainerDocument(DocumentInfo doc);
         RootInfo getCurrentRoot();
         DocumentInfo getCurrentDirectory();
         void setRootsDrawerOpen(boolean open);
+
+        @VisibleForTesting
+        void notifyDirectoryNavigated(Uri docUri);
     }
 }
diff --git a/src/com/android/documentsui/ActionHandler.java b/src/com/android/documentsui/ActionHandler.java
index 94a81ce..98d24ed 100644
--- a/src/com/android/documentsui/ActionHandler.java
+++ b/src/com/android/documentsui/ActionHandler.java
@@ -65,6 +65,8 @@
 
     void showChooserForDoc(DocumentInfo doc);
 
+    void openContainerDocument(DocumentInfo doc);
+
     void deleteSelectedDocuments();
 
     void shareSelectedDocuments();
diff --git a/src/com/android/documentsui/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java
index 005f887..235757e 100644
--- a/src/com/android/documentsui/BaseActivity.java
+++ b/src/com/android/documentsui/BaseActivity.java
@@ -32,11 +32,9 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
-import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.MessageQueue.IdleHandler;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Root;
@@ -59,7 +57,6 @@
 import com.android.documentsui.base.EventHandler;
 import com.android.documentsui.base.Events;
 import com.android.documentsui.base.LocalPreferences;
-import com.android.documentsui.base.PairedTask;
 import com.android.documentsui.base.RootInfo;
 import com.android.documentsui.base.Shared;
 import com.android.documentsui.base.State;
@@ -80,12 +77,11 @@
 import com.android.documentsui.ui.MessageBuilder;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Date;
 import java.util.List;
 import java.util.concurrent.Executor;
 
-public abstract class BaseActivity
+public abstract class BaseActivity<T extends ActionHandler>
         extends Activity implements CommonAddons, NavigationViewManager.Environment {
 
     private static final String BENCHMARK_TESTING_PACKAGE = "com.android.documentsui.appperftests";
@@ -95,25 +91,23 @@
 
     protected @Nullable RetainedState mRetainedState;
     protected RootsCache mRoots;
+    protected DocumentsAccess mDocs;
     protected MessageBuilder mMessages;
     protected DrawerController mDrawer;
     protected NavigationViewManager mNavigator;
     protected FocusManager mFocusManager;
     protected SortController mSortController;
 
+    protected T mActions;
+
     private final List<EventListener> mEventListeners = new ArrayList<>();
     private final String mTag;
-    private final ContentObserver mRootsCacheObserver = new ContentObserver(
-            new Handler()) {
-                @Override
-                public void onChange(boolean selfChange) {
-                    new HandleRootsChangedTask(BaseActivity.this).execute(getCurrentRoot());
-                }
-            };
 
     @LayoutRes
     private int mLayoutId;
 
+    private RootsMonitor<BaseActivity> mRootsMonitor;
+
     private boolean mNavDrawerHasFocus;
     private long mStartTime;
 
@@ -202,9 +196,8 @@
         // support to that fragment.
         mRetainedState = (RetainedState) getLastNonConfigurationInstance();
         mRoots = DocumentsApplication.getRootsCache(this);
+        mDocs = DocumentsAccess.create(this);
         mMessages = new MessageBuilder(this);
-        getContentResolver().registerContentObserver(
-                RootsCache.sNotificationUri, false, mRootsCacheObserver);
 
         DocumentsToolbar toolbar = (DocumentsToolbar) findViewById(R.id.toolbar);
         setActionBar(toolbar);
@@ -246,6 +239,20 @@
     }
 
     @Override
+    protected void onPostCreate(Bundle savedInstanceState) {
+        super.onPostCreate(savedInstanceState);
+
+        mRootsMonitor = new RootsMonitor<>(
+                this,
+                mActions,
+                mRoots,
+                mDocs,
+                mState,
+                mSearchManager);
+        mRootsMonitor.start();
+    }
+
+    @Override
     public boolean onCreateOptionsMenu(Menu menu) {
         boolean showMenu = super.onCreateOptionsMenu(menu);
 
@@ -267,7 +274,7 @@
 
     @Override
     protected void onDestroy() {
-        getContentResolver().unregisterContentObserver(mRootsCacheObserver);
+        mRootsMonitor.stop();
         super.onDestroy();
     }
 
@@ -344,7 +351,7 @@
             new GetRootDocumentTask(
                     root,
                     this,
-                    this::openContainerDocument)
+                    mActions::openContainerDocument)
                     .executeOnExecutor(getExecutorForCurrentDirectory());
         }
     }
@@ -406,22 +413,6 @@
                 && !root.isDownloads();
     }
 
-    // TODO: Move to ActionHandler...currently blocked by the notifyDirectory....business.
-    @Override
-    public void openContainerDocument(DocumentInfo doc) {
-        assert(doc.isContainer());
-
-        notifyDirectoryNavigated(doc.derivedUri);
-
-        mState.pushDocument(doc);
-        // Show an opening animation only if pressing "back" would get us back to the
-        // previous directory. Especially after opening a root document, pressing
-        // back, wouldn't go to the previous root, but close the activity.
-        final int anim = (mState.hasLocationChanged() && mState.stack.size() > 1)
-                ? AnimationView.ANIM_ENTER : AnimationView.ANIM_NONE;
-        refreshCurrentRootAndDirectory(anim);
-    }
-
     /**
      * Refreshes the content of the director and the menu/action bar.
      * The current directory name and selection will get updated.
@@ -638,21 +629,26 @@
         return super.onKeyDown(keyCode, event);
     }
 
+    @VisibleForTesting
     public void addEventListener(EventListener listener) {
         mEventListeners.add(listener);
     }
 
+    @VisibleForTesting
     public void removeEventListener(EventListener listener) {
         mEventListeners.remove(listener);
     }
 
+    @VisibleForTesting
     public void notifyDirectoryLoaded(Uri uri) {
         for (EventListener listener : mEventListeners) {
             listener.onDirectoryLoaded(uri);
         }
     }
 
-    void notifyDirectoryNavigated(Uri uri) {
+    @VisibleForTesting
+    @Override
+    public void notifyDirectoryNavigated(Uri uri) {
         for (EventListener listener : mEventListeners) {
             listener.onDirectoryNavigated(uri);
         }
@@ -728,62 +724,6 @@
         });
     }
 
-    private static final class HandleRootsChangedTask
-            extends PairedTask<BaseActivity, RootInfo, RootInfo> {
-        RootInfo mCurrentRoot;
-        DocumentInfo mDefaultRootDocument;
-
-        public HandleRootsChangedTask(BaseActivity activity) {
-            super(activity);
-        }
-
-        @Override
-        protected RootInfo run(RootInfo... roots) {
-            assert(roots.length == 1);
-            mCurrentRoot = roots[0];
-            final Collection<RootInfo> cachedRoots = mOwner.mRoots.getRootsBlocking();
-            for (final RootInfo root : cachedRoots) {
-                if (root.getUri().equals(mCurrentRoot.getUri())) {
-                    // We don't need to change the current root as the current root was not removed.
-                    return null;
-                }
-            }
-
-            // Choose the default root.
-            final RootInfo defaultRoot = mOwner.mRoots.getDefaultRootBlocking(mOwner.mState);
-            assert(defaultRoot != null);
-            if (!defaultRoot.isRecents()) {
-                mDefaultRootDocument = defaultRoot.getRootDocumentBlocking(mOwner);
-            }
-            return defaultRoot;
-        }
-
-        @Override
-        protected void finish(RootInfo defaultRoot) {
-            if (defaultRoot == null) {
-                return;
-            }
-
-            // If the activity has been launched for the specific root and it is removed, finish the
-            // activity.
-            final Uri uri = mOwner.getIntent().getData();
-            if (uri != null && uri.equals(mCurrentRoot.getUri())) {
-                mOwner.finish();
-                return;
-            }
-
-            // Clear entire backstack and start in new root.
-            mOwner.mState.onRootChanged(defaultRoot);
-            mOwner.mSearchManager.update(defaultRoot);
-
-            if (defaultRoot.isRecents()) {
-                mOwner.refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE);
-            } else {
-                mOwner.openContainerDocument(mDefaultRootDocument);
-            }
-        }
-    }
-
     public final class RetainedState {
         public @Nullable Selection selection;
 
diff --git a/src/com/android/documentsui/OnRootsChangedTask.java b/src/com/android/documentsui/OnRootsChangedTask.java
deleted file mode 100644
index 0776e8f..0000000
--- a/src/com/android/documentsui/OnRootsChangedTask.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2016 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.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.documentsui;
-
-import android.net.Uri;
-
-import com.android.documentsui.base.DocumentInfo;
-import com.android.documentsui.base.PairedTask;
-import com.android.documentsui.base.RootInfo;
-import com.android.documentsui.dirlist.AnimationView;
-
-import java.util.Collection;
-
-final class OnRootsChangedTask
-        extends PairedTask<BaseActivity, RootInfo, RootInfo> {
-    RootInfo mCurrentRoot;
-    DocumentInfo mDefaultRootDocument;
-
-    public OnRootsChangedTask(BaseActivity activity) {
-        super(activity);
-    }
-
-    @Override
-    protected RootInfo run(RootInfo... roots) {
-        assert(roots.length == 1);
-        mCurrentRoot = roots[0];
-        final Collection<RootInfo> cachedRoots = mOwner.mRoots.getRootsBlocking();
-        for (final RootInfo root : cachedRoots) {
-            if (root.getUri().equals(mCurrentRoot.getUri())) {
-                // We don't need to change the current root as the current root was not removed.
-                return null;
-            }
-        }
-
-        // Choose the default root.
-        final RootInfo defaultRoot = mOwner.mRoots.getDefaultRootBlocking(mOwner.mState);
-        assert(defaultRoot != null);
-        if (!defaultRoot.isRecents()) {
-            mDefaultRootDocument = defaultRoot.getRootDocumentBlocking(mOwner);
-        }
-        return defaultRoot;
-    }
-
-    @Override
-    protected void finish(RootInfo defaultRoot) {
-        if (defaultRoot == null) {
-            return;
-        }
-
-        // If the activity has been launched for the specific root and it is removed, finish the
-        // activity.
-        final Uri uri = mOwner.getIntent().getData();
-        if (uri != null && uri.equals(mCurrentRoot.getUri())) {
-            mOwner.finish();
-            return;
-        }
-
-        // Clear entire backstack and start in new root.
-        mOwner.mState.onRootChanged(defaultRoot);
-        mOwner.mSearchManager.update(defaultRoot);
-
-        if (defaultRoot.isRecents()) {
-            mOwner.refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE);
-        } else {
-            mOwner.openContainerDocument(mDefaultRootDocument);
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/com/android/documentsui/RootsMonitor.java b/src/com/android/documentsui/RootsMonitor.java
new file mode 100644
index 0000000..feead7a
--- /dev/null
+++ b/src/com/android/documentsui/RootsMonitor.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2016 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.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+
+import com.android.documentsui.AbstractActionHandler.CommonAddons;
+import com.android.documentsui.base.DocumentInfo;
+import com.android.documentsui.base.PairedTask;
+import com.android.documentsui.base.RootInfo;
+import com.android.documentsui.base.State;
+import com.android.documentsui.dirlist.AnimationView;
+import com.android.documentsui.roots.RootsAccess;
+
+import java.util.Collection;
+
+/**
+ * Monitors roots change and refresh the page when necessary.
+ */
+final class RootsMonitor<T extends Activity & CommonAddons> {
+
+    private final ContentResolver mResolver;
+    private final ContentObserver mObserver;
+
+    RootsMonitor(
+            final T activity,
+            final ActionHandler actions,
+            final RootsAccess roots,
+            final DocumentsAccess docs,
+            final State state,
+            final SearchViewManager searchMgr) {
+        mResolver = activity.getContentResolver();
+
+        mObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
+            @Override
+            public void onChange(boolean selfChange) {
+                new HandleRootsChangedTask<T>(
+                        activity,
+                        actions,
+                        roots,
+                        docs,
+                        state,
+                        searchMgr).execute(activity.getCurrentRoot());
+            }
+        };
+    }
+
+    void start() {
+        mResolver.registerContentObserver(RootsAccess.NOTIFICATION_URI, false, mObserver);
+    }
+
+    void stop() {
+        mResolver.unregisterContentObserver(mObserver);
+    }
+
+    private static class HandleRootsChangedTask<T extends Activity & CommonAddons>
+            extends PairedTask<T, RootInfo, RootInfo> {
+        private final ActionHandler mActions;
+        private final RootsAccess mRoots;
+        private final DocumentsAccess mDocs;
+        private final State mState;
+        private final SearchViewManager mSearchMgr;
+
+        private RootInfo mCurrentRoot;
+        private DocumentInfo mDefaultRootDocument;
+
+        private HandleRootsChangedTask(
+                T activity,
+                ActionHandler actions,
+                RootsAccess roots,
+                DocumentsAccess docs,
+                State state,
+                SearchViewManager searchMgr) {
+            super(activity);
+            mActions = actions;
+            mRoots = roots;
+            mDocs = docs;
+            mState = state;
+            mSearchMgr = searchMgr;
+        }
+
+        @Override
+        protected RootInfo run(RootInfo... roots) {
+            assert (roots.length == 1);
+            mCurrentRoot = roots[0];
+            final Collection<RootInfo> cachedRoots = mRoots.getRootsBlocking();
+            for (final RootInfo root : cachedRoots) {
+                if (root.getUri().equals(mCurrentRoot.getUri())) {
+                    // We don't need to change the current root as the current root was not removed.
+                    return null;
+                }
+            }
+
+            // Choose the default root.
+            final RootInfo defaultRoot = mRoots.getDefaultRootBlocking(mState);
+            assert (defaultRoot != null);
+            if (!defaultRoot.isRecents()) {
+                mDefaultRootDocument = mDocs.getRootDocument(defaultRoot);
+            }
+            return defaultRoot;
+        }
+
+        @Override
+        protected void finish(RootInfo defaultRoot) {
+            if (defaultRoot == null) {
+                return;
+            }
+
+            // If the activity has been launched for the specific root and it is removed, finish the
+            // activity.
+            final Uri uri = mOwner.getIntent().getData();
+            if (uri != null && uri.equals(mCurrentRoot.getUri())) {
+                mOwner.finish();
+                return;
+            }
+
+            // Clear entire backstack and start in new root.
+            mState.onRootChanged(defaultRoot);
+            mSearchMgr.update(defaultRoot);
+
+            if (defaultRoot.isRecents()) {
+                mOwner.refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE);
+            } else {
+                mActions.openContainerDocument(mDefaultRootDocument);
+            }
+        }
+    }
+}
diff --git a/src/com/android/documentsui/base/RootInfo.java b/src/com/android/documentsui/base/RootInfo.java
index f84f915..3a19ef7 100644
--- a/src/com/android/documentsui/base/RootInfo.java
+++ b/src/com/android/documentsui/base/RootInfo.java
@@ -350,7 +350,7 @@
     }
 
     /**
-     * @deprecate use {@link DocumentsAccess#getRootDocumentBlocking}.
+     * @deprecate use {@link DocumentsAccess#getRootDocument}.
      */
     @Deprecated
     public @Nullable DocumentInfo getRootDocumentBlocking(Context context) {
diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java
index b8836ad..e230311 100644
--- a/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -142,7 +142,7 @@
     private static final int CACHE_EVICT_LIMIT = 100;
     private static final int REFRESH_SPINNER_DISMISS_DELAY = 500;
 
-    private BaseActivity mActivity;
+    private BaseActivity<?> mActivity;
     private State mState;
     private final Model mModel = new Model();
     private final EventListener<Model.Update> mModelUpdateListener = new ModelUpdateListener();
@@ -204,7 +204,7 @@
     public View onCreateView(
             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 
-        BaseActivity activity = (BaseActivity) getActivity();
+        BaseActivity activity = (BaseActivity<?>) getActivity();
         final View view = inflater.inflate(R.layout.fragment_directory, container, false);
 
         mMessageBar = MessageBar.create(getChildFragmentManager());
@@ -300,7 +300,6 @@
         mModel.addUpdateListener(mAdapter.getModelUpdateListener());
         mModel.addUpdateListener(mModelUpdateListener);
 
-
         mSelectionMgr = mActivity.getSelectionManager(mAdapter, this::canSetSelectionState);
         mFocusManager = mActivity.getFocusManager(mRecView, mModel);
         mActions = mActivity.getActionHandler(mModel, mLocalState.mSearchMode);
diff --git a/src/com/android/documentsui/files/ActionHandler.java b/src/com/android/documentsui/files/ActionHandler.java
index a9891d0..d59e2a7 100644
--- a/src/com/android/documentsui/files/ActionHandler.java
+++ b/src/com/android/documentsui/files/ActionHandler.java
@@ -33,6 +33,7 @@
 import com.android.documentsui.DocumentsApplication;
 import com.android.documentsui.Metrics;
 import com.android.documentsui.R;
+import com.android.documentsui.SearchViewManager;
 import com.android.documentsui.base.ConfirmationCallback;
 import com.android.documentsui.base.ConfirmationCallback.Result;
 import com.android.documentsui.base.DocumentInfo;
@@ -86,6 +87,7 @@
             RootsAccess roots,
             DocumentsAccess docs,
             SelectionManager selectionMgr,
+            SearchViewManager searchMgr,
             Lookup<String, Executor> executors,
             ActionModeAddons actionModeAddons,
             DialogController dialogs,
@@ -93,7 +95,7 @@
             DocumentClipper clipper,
             ClipStore clipStore) {
 
-        super(activity, state, roots, docs, selectionMgr, executors);
+        super(activity, state, roots, docs, selectionMgr, searchMgr, executors);
 
         mActionModeAddons = actionModeAddons;
         mDialogs = dialogs;
@@ -380,7 +382,7 @@
 
     public void onDocumentPicked(DocumentInfo doc) {
         if (doc.isContainer()) {
-            mActivity.openContainerDocument(doc);
+            openContainerDocument(doc);
             return;
         }
 
@@ -402,7 +404,7 @@
         }
 
         if (doc.isContainer()) {
-            mActivity.openContainerDocument(doc);
+            openContainerDocument(doc);
             return true;
         }
 
diff --git a/src/com/android/documentsui/files/FilesActivity.java b/src/com/android/documentsui/files/FilesActivity.java
index c70b5b2..c02fced 100644
--- a/src/com/android/documentsui/files/FilesActivity.java
+++ b/src/com/android/documentsui/files/FilesActivity.java
@@ -35,7 +35,6 @@
 import com.android.documentsui.ActionModeController;
 import com.android.documentsui.ActivityConfig;
 import com.android.documentsui.BaseActivity;
-import com.android.documentsui.DocumentsAccess;
 import com.android.documentsui.DocumentsApplication;
 import com.android.documentsui.MenuManager.DirectoryDetails;
 import com.android.documentsui.MenuManager.SelectionDetails;
@@ -67,14 +66,14 @@
 /**
  * Standalone file management activity.
  */
-public class FilesActivity extends BaseActivity implements ActionHandler.Addons {
+public class FilesActivity
+        extends BaseActivity<ActionHandler<FilesActivity>> implements ActionHandler.Addons {
 
     public static final String TAG = "FilesActivity";
 
     private final Config mConfig = new Config();
     private SelectionManager mSelectionMgr;
     private MenuManager mMenuManager;
-    private ActionHandler<FilesActivity> mActions;
     private DialogController mDialogs;
     private DocumentClipper mClipper;
     private ActionModeController mActionModeController;
@@ -111,8 +110,9 @@
                 this,
                 mState,
                 mRoots,
-                DocumentsAccess.create(this),
+                mDocs,
                 mSelectionMgr,
+                mSearchManager,
                 ProviderExecutor::forAuthority,
                 mActionModeController,
                 mDialogs,
@@ -281,16 +281,7 @@
     public void springOpenDirectory(DocumentInfo doc) {
         assert(doc.isContainer());
         assert(!doc.isArchive());
-        openContainerDocument(doc);
-    }
-
-    /**
-     * @deprecated use {@link ActionHandler#showChooserForDoc(DocumentInfo)}
-     * @param doc
-     */
-    @Deprecated
-    public void showChooserForDoc(DocumentInfo doc) {
-        mActions.showChooserForDoc(doc);
+        mActions.openContainerDocument(doc);
     }
 
     @Override
diff --git a/src/com/android/documentsui/picker/ActionHandler.java b/src/com/android/documentsui/picker/ActionHandler.java
index 2615363..03c8a90 100644
--- a/src/com/android/documentsui/picker/ActionHandler.java
+++ b/src/com/android/documentsui/picker/ActionHandler.java
@@ -31,6 +31,7 @@
 import com.android.documentsui.ActivityConfig;
 import com.android.documentsui.DocumentsAccess;
 import com.android.documentsui.Metrics;
+import com.android.documentsui.SearchViewManager;
 import com.android.documentsui.base.DocumentInfo;
 import com.android.documentsui.base.DocumentStack;
 import com.android.documentsui.base.EventListener;
@@ -66,10 +67,11 @@
             RootsAccess roots,
             DocumentsAccess docs,
             SelectionManager selectionMgr,
+            SearchViewManager searchMgr,
             Lookup<String, Executor> executors,
             ActivityConfig activityConfig) {
 
-        super(activity, state, roots, docs, selectionMgr, executors);
+        super(activity, state, roots, docs, selectionMgr, searchMgr, executors);
 
         mConfig = activityConfig;
         mScope = new ContentScope(this::onModelLoaded);
diff --git a/src/com/android/documentsui/picker/PickActivity.java b/src/com/android/documentsui/picker/PickActivity.java
index 72dc47a..17e6bb1 100644
--- a/src/com/android/documentsui/picker/PickActivity.java
+++ b/src/com/android/documentsui/picker/PickActivity.java
@@ -46,7 +46,6 @@
 import com.android.documentsui.ActionModeController;
 import com.android.documentsui.ActivityConfig;
 import com.android.documentsui.BaseActivity;
-import com.android.documentsui.DocumentsAccess;
 import com.android.documentsui.DocumentsApplication;
 import com.android.documentsui.MenuManager.DirectoryDetails;
 import com.android.documentsui.MenuManager.SelectionDetails;
@@ -73,7 +72,8 @@
 import java.util.Arrays;
 import java.util.List;
 
-public class PickActivity extends BaseActivity implements ActionHandler.Addons {
+public class PickActivity
+        extends BaseActivity<ActionHandler<PickActivity>> implements ActionHandler.Addons {
 
     private static final int CODE_FORWARD = 42;
     private static final String TAG = "PickActivity";
@@ -81,7 +81,6 @@
 
     private SelectionManager mSelectionMgr;
     private MenuManager mMenuManager;
-    private ActionHandler<PickActivity> mActionHandler;
     private ActionModeController mActionModeController;
 
     public PickActivity() {
@@ -97,12 +96,13 @@
                         ? SelectionManager.MODE_MULTIPLE
                         : SelectionManager.MODE_SINGLE);
         mMenuManager = new MenuManager(mSearchManager, mState, new DirectoryDetails(this));
-        mActionHandler = new ActionHandler<>(
+        mActions = new ActionHandler<>(
                 this,
                 mState,
-                DocumentsApplication.getRootsCache(this),
-                DocumentsAccess.create(this),
+                mRoots,
+                mDocs,
                 mSelectionMgr,
+                mSearchManager,
                 ProviderExecutor::forAuthority,
                 mConfig);
 
@@ -115,7 +115,7 @@
         Intent intent = getIntent();
 
         setupLayout(intent);
-        mActionHandler.initLocation(intent);
+        mActions.initLocation(intent);
     }
 
     private void setupLayout(Intent intent) {
@@ -262,7 +262,7 @@
             // No directory means recents
             if (mState.action == ACTION_CREATE ||
                 mState.action == ACTION_PICK_COPY_DESTINATION) {
-                mActionHandler.loadRoot(Shared.getDefaultRootUri(this));
+                mActions.loadRoot(Shared.getDefaultRootUri(this));
             } else {
                 DirectoryFragment.showRecentsOpen(fm, anim);
 
@@ -312,7 +312,7 @@
     @Override
     protected void onDirectoryCreated(DocumentInfo doc) {
         assert(doc.isDirectory());
-        openContainerDocument(doc);
+        mActions.openContainerDocument(doc);
     }
 
     void onSaveRequested(String mimeType, String displayName) {
@@ -324,7 +324,7 @@
     public void onDocumentPicked(DocumentInfo doc) {
         final FragmentManager fm = getFragmentManager();
         if (doc.isContainer()) {
-            openContainerDocument(doc);
+            mActions.openContainerDocument(doc);
         } else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
             // Explicit file picked, return
             new ExistingFinishTask(this, doc.derivedUri)
@@ -436,10 +436,10 @@
 
         // provide our friend, RootsFragment, early access to this special feature!
         if (model == null) {
-            return mActionHandler;
+            return mActions;
         }
 
-        return mActionHandler.reset(model, searchMode);
+        return mActions.reset(model, searchMode);
     }
 
     @Override
diff --git a/src/com/android/documentsui/roots/RootsAccess.java b/src/com/android/documentsui/roots/RootsAccess.java
index 57dfb48..0d0a975 100644
--- a/src/com/android/documentsui/roots/RootsAccess.java
+++ b/src/com/android/documentsui/roots/RootsAccess.java
@@ -18,6 +18,7 @@
 
 import static com.android.documentsui.base.Shared.DEBUG;
 
+import android.net.Uri;
 import android.util.Log;
 
 import com.android.documentsui.base.MimeTypes;
@@ -33,6 +34,8 @@
  */
 public interface RootsAccess {
 
+    Uri NOTIFICATION_URI = Uri.parse("content://com.android.documentsui.roots/");
+
     /**
      * Return the requested {@link RootInfo}, but only loading the roots for the
      * requested authority. This is useful when we want to load fast without
diff --git a/src/com/android/documentsui/roots/RootsCache.java b/src/com/android/documentsui/roots/RootsCache.java
index ea08b40..4111a95 100644
--- a/src/com/android/documentsui/roots/RootsCache.java
+++ b/src/com/android/documentsui/roots/RootsCache.java
@@ -44,11 +44,11 @@
 import com.android.documentsui.base.State;
 import com.android.internal.annotations.GuardedBy;
 
+import libcore.io.IoUtils;
+
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.Multimap;
 
-import libcore.io.IoUtils;
-
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -62,9 +62,6 @@
  * Cache of known storage backends and their roots.
  */
 public class RootsCache implements RootsAccess {
-    public static final Uri sNotificationUri = Uri.parse(
-            "content://com.android.documentsui.roots/");
-
     private static final String TAG = "RootsCache";
 
     private final Context mContext;
@@ -393,7 +390,7 @@
                 mStoppedAuthorities = mTaskStoppedAuthorities;
             }
             mFirstLoad.countDown();
-            resolver.notifyChange(sNotificationUri, null, false);
+            resolver.notifyChange(NOTIFICATION_URI, null, false);
             return null;
         }
 
diff --git a/src/com/android/documentsui/roots/RootsLoader.java b/src/com/android/documentsui/roots/RootsLoader.java
index 7e55be4..cae2639 100644
--- a/src/com/android/documentsui/roots/RootsLoader.java
+++ b/src/com/android/documentsui/roots/RootsLoader.java
@@ -38,7 +38,7 @@
         mState = state;
 
         context.getContentResolver().registerContentObserver(
-                RootsCache.sNotificationUri, false, mObserver);
+                RootsAccess.NOTIFICATION_URI, false, mObserver);
     }
 
     @Override
diff --git a/src/com/android/documentsui/sidebar/RootsFragment.java b/src/com/android/documentsui/sidebar/RootsFragment.java
index f6a4db3..ecdc053 100644
--- a/src/com/android/documentsui/sidebar/RootsFragment.java
+++ b/src/com/android/documentsui/sidebar/RootsFragment.java
@@ -361,8 +361,8 @@
         mList.requestFocus();
     }
 
-    private BaseActivity getBaseActivity() {
-        return (BaseActivity) getActivity();
+    private BaseActivity<?> getBaseActivity() {
+        return (BaseActivity<?>) getActivity();
     }
 
     @Override
@@ -409,7 +409,7 @@
         AdapterContextMenuInfo adapterMenuInfo = (AdapterContextMenuInfo) menuInfo;
         final Item item = mAdapter.getItem(adapterMenuInfo.position);
 
-        BaseActivity activity = getBaseActivity();
+        BaseActivity<?> activity = getBaseActivity();
         item.createContextMenu(menu, activity.getMenuInflater(), activity.getMenuManager());
     }
 
diff --git a/tests/common/com/android/documentsui/TestActivity.java b/tests/common/com/android/documentsui/TestActivity.java
index 9fc4fca..37bd60b 100644
--- a/tests/common/com/android/documentsui/TestActivity.java
+++ b/tests/common/com/android/documentsui/TestActivity.java
@@ -48,9 +48,9 @@
     public TestEventListener<Intent> startActivity;
     public TestEventListener<Intent> startService;
     public TestEventListener<RootInfo> rootPicked;
-    public TestEventListener<DocumentInfo> openContainer;
     public TestEventListener<Integer> refreshCurrentRootAndDirectory;
     public TestEventListener<Boolean> setRootsDrawerOpen;
+    public TestEventListener<Uri> notifyDirectoryNavigated;
 
     public static TestActivity create() {
         TestActivity activity = Mockito.mock(TestActivity.class, Mockito.CALLS_REAL_METHODS);
@@ -66,9 +66,9 @@
        startActivity = new TestEventListener<>();
        startService = new TestEventListener<>();
        rootPicked = new TestEventListener<>();
-       openContainer = new TestEventListener<>();
        refreshCurrentRootAndDirectory =  new TestEventListener<>();
        setRootsDrawerOpen = new TestEventListener<>();
+       notifyDirectoryNavigated = new TestEventListener<>();
    }
 
     @Override
@@ -121,8 +121,8 @@
     }
 
     @Override
-    public final void openContainerDocument(DocumentInfo doc) {
-        openContainer.accept(doc);
+    public final void notifyDirectoryNavigated(Uri uri) {
+        notifyDirectoryNavigated.accept(uri);
     }
 
     @Override
diff --git a/tests/common/com/android/documentsui/dirlist/TestModel.java b/tests/common/com/android/documentsui/dirlist/TestModel.java
index 82849a3..abe4d5f 100644
--- a/tests/common/com/android/documentsui/dirlist/TestModel.java
+++ b/tests/common/com/android/documentsui/dirlist/TestModel.java
@@ -22,13 +22,10 @@
 
 import com.android.documentsui.DirectoryResult;
 import com.android.documentsui.base.DocumentInfo;
-import com.android.documentsui.base.EventListener;
 import com.android.documentsui.roots.RootCursorWrapper;
 
 import libcore.net.MimeUtils;
 
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Random;
 
 public class TestModel extends Model {
@@ -46,7 +43,6 @@
     private int mLastId = 0;
     private Random mRand = new Random();
     private MatrixCursor mCursor;
-    private List<EventListener<Update>> mUpdateListeners = new ArrayList<>();
 
     public TestModel(String authority) {
         super();
diff --git a/tests/common/com/android/documentsui/testing/TestActionHandler.java b/tests/common/com/android/documentsui/testing/TestActionHandler.java
index 381b738..fd199ce 100644
--- a/tests/common/com/android/documentsui/testing/TestActionHandler.java
+++ b/tests/common/com/android/documentsui/testing/TestActionHandler.java
@@ -40,6 +40,7 @@
                 env.roots,
                 env.docs,
                 env.selectionMgr,
+                env.searchViewManager,
                 (String authority) -> null);
     }
 
diff --git a/tests/common/com/android/documentsui/testing/TestEnv.java b/tests/common/com/android/documentsui/testing/TestEnv.java
index e49031c..566f3eb 100644
--- a/tests/common/com/android/documentsui/testing/TestEnv.java
+++ b/tests/common/com/android/documentsui/testing/TestEnv.java
@@ -19,6 +19,7 @@
 import android.os.Looper;
 import android.provider.DocumentsContract.Document;
 
+import com.android.documentsui.SearchViewManager;
 import com.android.documentsui.base.DocumentInfo;
 import com.android.documentsui.base.State;
 import com.android.documentsui.dirlist.TestModel;
@@ -52,11 +53,13 @@
     public final TestDocumentsAccess docs = new TestDocumentsAccess();
     public final TestModel model;
     public final SelectionManager selectionMgr;
+    public final SearchViewManager searchViewManager;
 
     private TestEnv(String authority) {
         mExecutor = new TestScheduledExecutorService();
         model = new TestModel(authority);
         selectionMgr = SelectionManagers.createTestInstance();
+        searchViewManager = new TestSearchViewManager();
     }
 
     public static TestEnv create() {
diff --git a/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java b/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java
index 254fdbf..1577ae2 100644
--- a/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java
+++ b/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java
@@ -56,6 +56,7 @@
                 mEnv.roots,
                 mEnv.docs,
                 mEnv.selectionMgr,
+                mEnv.searchViewManager,
                 mEnv::lookupExecutor) {
 
             @Override
diff --git a/tests/unit/com/android/documentsui/files/ActionHandlerTest.java b/tests/unit/com/android/documentsui/files/ActionHandlerTest.java
index b78f565..7de09b1 100644
--- a/tests/unit/com/android/documentsui/files/ActionHandlerTest.java
+++ b/tests/unit/com/android/documentsui/files/ActionHandlerTest.java
@@ -72,6 +72,7 @@
                 mEnv.roots,
                 mEnv.docs,
                 mEnv.selectionMgr,
+                mEnv.searchViewManager,
                 mEnv::lookupExecutor,
                 mActionModeAddons,
                 mDialogs,
@@ -221,7 +222,7 @@
         mActivity.currentRoot = TestRootsAccess.HOME;
 
         mHandler.onDocumentPicked(TestEnv.FILE_ARCHIVE);
-        mActivity.openContainer.assertLastArgument(TestEnv.FILE_ARCHIVE);
+        assertEquals(TestEnv.FILE_ARCHIVE, mEnv.state.stack.peek());
     }
 
     @Test
@@ -229,7 +230,7 @@
         mActivity.currentRoot = TestRootsAccess.HOME;
 
         mHandler.onDocumentPicked(TestEnv.FOLDER_1);
-        mActivity.openContainer.assertLastArgument(TestEnv.FOLDER_1);
+        assertEquals(TestEnv.FOLDER_1, mEnv.state.stack.peek());
     }
 
     @Test
diff --git a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java
index 35199c3..8311443 100644
--- a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java
+++ b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java
@@ -18,6 +18,7 @@
 
 import static com.android.documentsui.base.State.ACTION_GET_CONTENT;
 import static com.android.documentsui.base.State.ACTION_PICK_COPY_DESTINATION;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
@@ -59,6 +60,7 @@
                 mEnv.roots,
                 mEnv.docs,
                 mEnv.selectionMgr,
+                mEnv.searchViewManager,
                 mEnv::lookupExecutor,
                 null  // tuner, not currently used.
                 );
@@ -119,6 +121,15 @@
         assertRootPicked(TestRootsAccess.HOME.getUri());
     }
 
+    @Test
+    public void testOpenContainerDocument() {
+        mHandler.openContainerDocument(TestEnv.FOLDER_0);
+
+        assertEquals(TestEnv.FOLDER_0, mEnv.state.stack.peek());
+
+        mActivity.refreshCurrentRootAndDirectory.assertCalled();
+    }
+
     private void assertRootPicked(Uri expectedUri) throws Exception {
         mEnv.beforeAsserts();