Test location init with doc URI.

Make a more complete test environment.
Move GetRootDocumentTask to roots package.

Change-Id: Icb8f4c42c38c17aa97ec428ef9d46d76d2286fa7
diff --git a/src/com/android/documentsui/AbstractActionHandler.java b/src/com/android/documentsui/AbstractActionHandler.java
index 81463d8..59d4bb9 100644
--- a/src/com/android/documentsui/AbstractActionHandler.java
+++ b/src/com/android/documentsui/AbstractActionHandler.java
@@ -37,6 +37,7 @@
 import com.android.documentsui.dirlist.Model;
 import com.android.documentsui.dirlist.MultiSelectManager.Selection;
 import com.android.documentsui.files.LauncherActivity;
+import com.android.documentsui.files.OpenUriForViewTask;
 import com.android.documentsui.roots.LoadRootTask;
 import com.android.documentsui.roots.RootsAccess;
 import com.android.documentsui.sidebar.EjectRootTask;
@@ -53,21 +54,25 @@
     protected final T mActivity;
     protected final State mState;
     protected final RootsAccess mRoots;
+    protected final DocumentsAccess mDocs;
     protected final Lookup<String, Executor> mExecutors;
 
     public AbstractActionHandler(
             T activity,
             State state,
             RootsAccess roots,
+            DocumentsAccess docs,
             Lookup<String, Executor> executors) {
 
         assert(activity != null);
         assert(state != null);
         assert(roots != null);
+        assert(docs != null);
 
         mActivity = activity;
         mState = state;
         mRoots = roots;
+        mDocs = docs;
         mExecutors = executors;
     }
 
@@ -138,9 +143,15 @@
     }
 
     @Override
+    public final void loadDocument(Uri uri) {
+        new OpenUriForViewTask<>(mActivity, mState, mRoots, mDocs, uri)
+                .executeOnExecutor(mExecutors.lookup(uri.getAuthority()));
+    }
+
+    @Override
     public final void loadRoot(Uri uri) {
-        new LoadRootTask<>(mActivity, mRoots, mState, uri).executeOnExecutor(
-                mExecutors.lookup(uri.getAuthority()));
+        new LoadRootTask<>(mActivity, mRoots, mState, uri)
+                .executeOnExecutor(mExecutors.lookup(uri.getAuthority()));
     }
 
     protected final void loadHomeDir() {
@@ -152,10 +163,10 @@
      * from our concrete activity implementations.
      */
     public interface CommonAddons {
+       void refreshCurrentRootAndDirectory(@AnimationType int anim);
        void onRootPicked(RootInfo root);
-       // TODO: Move this to PickAddons.
+       // TODO: Move this to PickAddons as multi-document picking is exclusive to that activity.
        void onDocumentsPicked(List<DocumentInfo> docs);
        void onDocumentPicked(DocumentInfo doc, Model model);
-       void refreshCurrentRootAndDirectory(@AnimationType int anim);
     }
 }
diff --git a/src/com/android/documentsui/ActionHandler.java b/src/com/android/documentsui/ActionHandler.java
index f4aa0ca..0c33f6c 100644
--- a/src/com/android/documentsui/ActionHandler.java
+++ b/src/com/android/documentsui/ActionHandler.java
@@ -52,6 +52,8 @@
 
     void loadRoot(Uri uri);
 
+    void loadDocument(Uri uri);
+
     void openInNewWindow(DocumentStack path);
 
     void pasteIntoFolder(RootInfo root);
diff --git a/src/com/android/documentsui/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java
index d9d6cf4..340cd6b 100644
--- a/src/com/android/documentsui/BaseActivity.java
+++ b/src/com/android/documentsui/BaseActivity.java
@@ -68,6 +68,7 @@
 import com.android.documentsui.dirlist.Model;
 import com.android.documentsui.dirlist.MultiSelectManager;
 import com.android.documentsui.dirlist.MultiSelectManager.Selection;
+import com.android.documentsui.roots.GetRootDocumentTask;
 import com.android.documentsui.roots.RootsCache;
 import com.android.documentsui.sidebar.RootsFragment;
 import com.android.documentsui.sorting.SortController;
diff --git a/src/com/android/documentsui/DocumentsAccess.java b/src/com/android/documentsui/DocumentsAccess.java
new file mode 100644
index 0000000..cd9c4c9
--- /dev/null
+++ b/src/com/android/documentsui/DocumentsAccess.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2013 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.annotation.Nullable;
+import android.content.Context;
+import android.net.Uri;
+import android.provider.DocumentsContract;
+import android.util.Log;
+
+import com.android.documentsui.base.DocumentInfo;
+import com.android.documentsui.base.RootInfo;
+
+import java.io.FileNotFoundException;
+
+/**
+ * Provides synchronous access to {@link DocumentInfo} instances given some identifying information.
+ */
+public interface DocumentsAccess {
+
+    @Nullable DocumentInfo getRootDocument(RootInfo root);
+    @Nullable DocumentInfo getRootDocument(Uri uri);
+    @Nullable DocumentInfo getDocument(Uri uri);
+
+    public static DocumentsAccess create(Context context) {
+        return new RuntimeDocumentAccess(context);
+    }
+
+    public final class RuntimeDocumentAccess implements DocumentsAccess {
+
+        private static final String TAG = "DocumentAccess";
+
+        private final Context mContext;
+
+        private RuntimeDocumentAccess(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        public @Nullable DocumentInfo getRootDocument(RootInfo root) {
+            return getRootDocument(
+                    DocumentsContract.buildDocumentUri(root.authority, root.documentId));
+        }
+
+        @Override
+        public @Nullable DocumentInfo getRootDocument(Uri uri) {
+            try {
+                return DocumentInfo.fromUri(mContext.getContentResolver(), uri);
+            } catch (FileNotFoundException e) {
+                Log.w(TAG, "Failed to find root", e);
+                return null;
+            }
+        }
+
+        @Override
+        public DocumentInfo getDocument(Uri uri) {
+            try {
+                return DocumentInfo.fromUri(mContext.getContentResolver(), uri);
+            } catch (FileNotFoundException e) {
+                Log.w(TAG, "Couldn't create DocumentInfo for uri: " + uri);
+            }
+
+            return null;
+        }
+    }
+}
diff --git a/src/com/android/documentsui/base/RootInfo.java b/src/com/android/documentsui/base/RootInfo.java
index 1b4656a..2c32b9e 100644
--- a/src/com/android/documentsui/base/RootInfo.java
+++ b/src/com/android/documentsui/base/RootInfo.java
@@ -37,6 +37,7 @@
 
 import com.android.documentsui.IconUtils;
 import com.android.documentsui.R;
+import com.android.documentsui.roots.RootsAccess;
 
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
@@ -349,8 +350,9 @@
     }
 
     /**
-     * Gets the {@link DocumentInfo} of the root folder of this root.
+     * @deprecate use {@link RootsAccess#getRootDocumentBlocking}.
      */
+    @Deprecated
     public @Nullable DocumentInfo getRootDocumentBlocking(Context context) {
         try {
             final Uri uri = DocumentsContract.buildDocumentUri(authority, documentId);
diff --git a/src/com/android/documentsui/files/ActionHandler.java b/src/com/android/documentsui/files/ActionHandler.java
index d6c4f42..67b5a53 100644
--- a/src/com/android/documentsui/files/ActionHandler.java
+++ b/src/com/android/documentsui/files/ActionHandler.java
@@ -26,10 +26,9 @@
 import android.util.Log;
 
 import com.android.documentsui.AbstractActionHandler;
+import com.android.documentsui.DocumentsAccess;
 import com.android.documentsui.DocumentsApplication;
-import com.android.documentsui.GetRootDocumentTask;
 import com.android.documentsui.Metrics;
-import com.android.documentsui.ProviderExecutor;
 import com.android.documentsui.base.ConfirmationCallback;
 import com.android.documentsui.base.ConfirmationCallback.Result;
 import com.android.documentsui.base.DocumentInfo;
@@ -47,6 +46,7 @@
 import com.android.documentsui.dirlist.MultiSelectManager;
 import com.android.documentsui.dirlist.MultiSelectManager.Selection;
 import com.android.documentsui.files.ActionHandler.Addons;
+import com.android.documentsui.roots.GetRootDocumentTask;
 import com.android.documentsui.roots.RootsAccess;
 import com.android.documentsui.services.FileOperation;
 import com.android.documentsui.services.FileOperationService;
@@ -77,13 +77,14 @@
             T activity,
             State state,
             RootsAccess roots,
+            DocumentsAccess docs,
             Lookup<String, Executor> executors,
             DialogController dialogs,
             FragmentTuner tuner,
             DocumentClipper clipper,
             ClipStore clipStore) {
 
-        super(activity, state, roots, executors);
+        super(activity, state, roots, docs, executors);
 
         mDialogs = dialogs;
         mTuner = tuner;
@@ -101,7 +102,7 @@
                 mActivity::isDestroyed,
                 (DocumentInfo doc) -> mClipper.copyFromClipData(
                         root, doc, data, mDialogs::showFileOperationFailures)
-        ).executeOnExecutor(ProviderExecutor.forAuthority(root.authority));
+        ).executeOnExecutor(mExecutors.lookup(root.authority));
         return true;
     }
 
@@ -120,7 +121,7 @@
                 mActivity,
                 mActivity::isDestroyed,
                 (DocumentInfo doc) -> pasteIntoFolder(root, doc)
-        ).executeOnExecutor(ProviderExecutor.forAuthority(root.authority));
+        ).executeOnExecutor(mExecutors.lookup(root.authority));
     }
 
     private void pasteIntoFolder(RootInfo root, DocumentInfo doc) {
@@ -174,6 +175,7 @@
         assert(!selected.isEmpty());
 
         final DocumentInfo srcParent = mState.stack.peek();
+        assert(srcParent != null);
 
         // Model must be accessed in UI thread, since underlying cursor is not threadsafe.
         List<DocumentInfo> docs = model.getDocuments(selected);
@@ -268,8 +270,7 @@
         if (Intent.ACTION_VIEW.equals(intent.getAction())) {
             Uri uri = intent.getData();
             assert(uri != null);
-            new OpenUriForViewTask<>(mActivity, mState).executeOnExecutor(
-                    ProviderExecutor.forAuthority(uri.getAuthority()), uri);
+            loadDocument(uri);
             return true;
         }
 
diff --git a/src/com/android/documentsui/files/FilesActivity.java b/src/com/android/documentsui/files/FilesActivity.java
index 2d1ed94..0f497eb 100644
--- a/src/com/android/documentsui/files/FilesActivity.java
+++ b/src/com/android/documentsui/files/FilesActivity.java
@@ -36,6 +36,7 @@
 import android.view.MenuItem;
 
 import com.android.documentsui.BaseActivity;
+import com.android.documentsui.DocumentsAccess;
 import com.android.documentsui.DocumentsApplication;
 import com.android.documentsui.FocusManager;
 import com.android.documentsui.MenuManager.DirectoryDetails;
@@ -104,6 +105,7 @@
                 this,
                 mState,
                 mRoots,
+                DocumentsAccess.create(this),
                 ProviderExecutor::forAuthority,
                 mDialogs,
                 mTuner,
diff --git a/src/com/android/documentsui/files/OpenUriForViewTask.java b/src/com/android/documentsui/files/OpenUriForViewTask.java
index f8424c1..2a830c5 100644
--- a/src/com/android/documentsui/files/OpenUriForViewTask.java
+++ b/src/com/android/documentsui/files/OpenUriForViewTask.java
@@ -20,7 +20,7 @@
 import android.util.Log;
 
 import com.android.documentsui.AbstractActionHandler.CommonAddons;
-import com.android.documentsui.DocumentsApplication;
+import com.android.documentsui.DocumentsAccess;
 import com.android.documentsui.base.DocumentInfo;
 import com.android.documentsui.base.PairedTask;
 import com.android.documentsui.base.RootInfo;
@@ -28,44 +28,58 @@
 import com.android.documentsui.dirlist.AnimationView;
 import com.android.documentsui.roots.RootsAccess;
 
-import java.io.FileNotFoundException;
 import java.util.Collection;
 
+import javax.annotation.Nullable;
+
 /**
  * Builds a stack for the specific Uris. Multi roots are not supported, as it's impossible
  * to know which root to select. Also, the stack doesn't contain intermediate directories.
  * It's primarly used for opening ZIP archives from Downloads app.
  */
-final class OpenUriForViewTask<T extends Activity & CommonAddons>
-        extends PairedTask<T, Uri, Void> {
+public final class OpenUriForViewTask<T extends Activity & CommonAddons>
+        extends PairedTask<T, Void, Void> {
 
+    private static final String TAG = "OpenUriForViewTask";
+
+    private final T mActivity;
     private final State mState;
-    public OpenUriForViewTask(T activity, State state) {
+    private final RootsAccess mRoots;
+    private final DocumentsAccess mDocs;
+    private final Uri mUri;
+
+    public OpenUriForViewTask(
+            T activity, State state, RootsAccess roots, DocumentsAccess docs, Uri uri) {
         super(activity);
+        mActivity = activity;
         mState = state;
+        mRoots = roots;
+        mDocs = docs;
+        mUri = uri;
     }
 
     @Override
-    public Void run(Uri... params) {
-        final Uri uri = params[0];
+    public Void run(Void... params) {
 
-        final RootsAccess rootsCache = DocumentsApplication.getRootsCache(mOwner);
-        final String authority = uri.getAuthority();
+        final String authority = mUri.getAuthority();
+        final Collection<RootInfo> roots = mRoots.getRootsForAuthorityBlocking(authority);
 
-        final Collection<RootInfo> roots =
-                rootsCache.getRootsForAuthorityBlocking(authority);
         if (roots.isEmpty()) {
-            Log.e(FilesActivity.TAG, "Failed to find root for the requested Uri: " + uri);
+            Log.e(TAG, "Failed to find root for the requested Uri: " + mUri);
             return null;
         }
 
+        assert(mState.stack.isEmpty());
+
+        // NOTE: There's no guarantee that this root will be the correct root for the doc.
         final RootInfo root = roots.iterator().next();
         mState.stack.root = root;
-        mState.stack.add(root.getRootDocumentBlocking(mOwner));
-        try {
-            mState.stack.add(DocumentInfo.fromUri(mOwner.getContentResolver(), uri));
-        } catch (FileNotFoundException e) {
-            Log.e(FilesActivity.TAG, "Failed to resolve DocumentInfo from Uri: " + uri);
+        mState.stack.add(mDocs.getRootDocument(root));
+        @Nullable DocumentInfo doc = mDocs.getDocument(mUri);
+        if (doc == null) {
+            Log.e(TAG, "Failed to resolve DocumentInfo from Uri: " + mUri);
+        } else {
+            mState.stack.add(doc);
         }
 
         return null;
@@ -73,6 +87,6 @@
 
     @Override
     public void finish(Void result) {
-        mOwner.refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE);
+        mActivity.refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE);
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/documentsui/picker/ActionHandler.java b/src/com/android/documentsui/picker/ActionHandler.java
index 59d0b48..cec1397 100644
--- a/src/com/android/documentsui/picker/ActionHandler.java
+++ b/src/com/android/documentsui/picker/ActionHandler.java
@@ -27,6 +27,7 @@
 import android.util.Log;
 
 import com.android.documentsui.AbstractActionHandler;
+import com.android.documentsui.DocumentsAccess;
 import com.android.documentsui.Metrics;
 import com.android.documentsui.base.DocumentInfo;
 import com.android.documentsui.base.DocumentStack;
@@ -58,10 +59,11 @@
             T activity,
             State state,
             RootsAccess roots,
+            DocumentsAccess docs,
             Lookup<String, Executor> executors,
             FragmentTuner tuner) {
 
-        super(activity, state, roots, executors);
+        super(activity, state, roots, docs, executors);
 
         mTuner = tuner;
         mConfig = new Config();
diff --git a/src/com/android/documentsui/picker/LoadLastAccessedStackTask.java b/src/com/android/documentsui/picker/LoadLastAccessedStackTask.java
index 3dadca4..bb22e08 100644
--- a/src/com/android/documentsui/picker/LoadLastAccessedStackTask.java
+++ b/src/com/android/documentsui/picker/LoadLastAccessedStackTask.java
@@ -106,4 +106,4 @@
         mState.external = mExternal;
         mOwner.refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE);
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/documentsui/picker/PickActivity.java b/src/com/android/documentsui/picker/PickActivity.java
index 1f2f69f..d1b1ec1 100644
--- a/src/com/android/documentsui/picker/PickActivity.java
+++ b/src/com/android/documentsui/picker/PickActivity.java
@@ -43,6 +43,7 @@
 import android.view.Menu;
 
 import com.android.documentsui.BaseActivity;
+import com.android.documentsui.DocumentsAccess;
 import com.android.documentsui.DocumentsApplication;
 import com.android.documentsui.FocusManager;
 import com.android.documentsui.MenuManager.DirectoryDetails;
@@ -91,6 +92,7 @@
                 this,
                 mState,
                 DocumentsApplication.getRootsCache(this),
+                DocumentsAccess.create(this),
                 ProviderExecutor::forAuthority,
                 mTuner);
 
diff --git a/src/com/android/documentsui/GetRootDocumentTask.java b/src/com/android/documentsui/roots/GetRootDocumentTask.java
similarity index 96%
rename from src/com/android/documentsui/GetRootDocumentTask.java
rename to src/com/android/documentsui/roots/GetRootDocumentTask.java
index e56b110..1503ae1 100644
--- a/src/com/android/documentsui/GetRootDocumentTask.java
+++ b/src/com/android/documentsui/roots/GetRootDocumentTask.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.documentsui;
+package com.android.documentsui.roots;
 
 import android.annotation.Nullable;
 import android.app.Activity;
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.util.Log;
 
+import com.android.documentsui.TimeoutTask;
 import com.android.documentsui.base.CheckedTask;
 import com.android.documentsui.base.DocumentInfo;
 import com.android.documentsui.base.RootInfo;
diff --git a/src/com/android/documentsui/sidebar/RootsFragment.java b/src/com/android/documentsui/sidebar/RootsFragment.java
index 1174503..a250a8d 100644
--- a/src/com/android/documentsui/sidebar/RootsFragment.java
+++ b/src/com/android/documentsui/sidebar/RootsFragment.java
@@ -49,7 +49,6 @@
 import com.android.documentsui.ActionHandler;
 import com.android.documentsui.BaseActivity;
 import com.android.documentsui.DocumentsApplication;
-import com.android.documentsui.GetRootDocumentTask;
 import com.android.documentsui.ItemDragListener;
 import com.android.documentsui.R;
 import com.android.documentsui.base.BooleanConsumer;
@@ -59,6 +58,7 @@
 import com.android.documentsui.base.RootInfo;
 import com.android.documentsui.base.Shared;
 import com.android.documentsui.base.State;
+import com.android.documentsui.roots.GetRootDocumentTask;
 import com.android.documentsui.roots.RootsCache;
 import com.android.documentsui.roots.RootsLoader;