Populate quick view intent w/ file uris.

Change-Id: Ie4f15b11be1939f8b71752505caa9d74ab9f9680
diff --git a/src/com/android/documentsui/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java
index a813ce7..9b8d847 100644
--- a/src/com/android/documentsui/BaseActivity.java
+++ b/src/com/android/documentsui/BaseActivity.java
@@ -16,10 +16,13 @@
 
 package com.android.documentsui;
 
+import static com.android.documentsui.DirectoryFragment.ANIM_DOWN;
 import static com.android.documentsui.DirectoryFragment.ANIM_NONE;
 import static com.android.documentsui.DirectoryFragment.ANIM_SIDE;
 import static com.android.documentsui.DirectoryFragment.ANIM_UP;
+import static com.android.internal.util.Preconditions.checkArgument;
 
+import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.Fragment;
 import android.content.Intent;
@@ -79,8 +82,9 @@
     private final String mTag;
 
     public abstract State getDisplayState();
-    public abstract void onDocumentPicked(DocumentInfo doc);
+    public abstract void onDocumentPicked(DocumentInfo doc, @Nullable DocumentContext siblings);
     public abstract void onDocumentsPicked(List<DocumentInfo> docs);
+
     abstract void onTaskFinished(Uri... uris);
     abstract void onDirectoryChanged(int anim);
     abstract void updateActionBar();
@@ -258,6 +262,17 @@
                 && !root.isDownloads();
     }
 
+    void onDirectoryCreated(DocumentInfo doc) {
+        checkArgument(doc.isDirectory());
+        openDirectory(doc);
+    }
+
+    void openDirectory(DocumentInfo doc) {
+        getDisplayState().stack.push(doc);
+        getDisplayState().stackTouched = true;
+        onCurrentDirectoryChanged(ANIM_DOWN);
+    }
+
     /**
      * Call this when directory changes. Prior to root fragment update
      * the (abstract) directoryChanged method will be called.
@@ -605,7 +620,6 @@
             if (isDestroyed()) return;
             getDisplayState().restored = true;
             onCurrentDirectoryChanged(ANIM_NONE);
-
             onStackRestored(mRestoredStack, mExternal);
         }
     }
@@ -843,4 +857,17 @@
             updateActionBar();
         }
     }
+
+    /**
+     * Interface providing access to current view of documents
+     * even when all documents are not homed to the same parent.
+     */
+    interface DocumentContext {
+        /**
+         * Returns the cursor for the selected document. The cursor can be used to retrieve
+         * details about a document and its siblings.
+         * @return
+         */
+        Cursor getCursor();
+    }
 }
diff --git a/src/com/android/documentsui/CreateDirectoryFragment.java b/src/com/android/documentsui/CreateDirectoryFragment.java
index 1b6d642..f927595 100644
--- a/src/com/android/documentsui/CreateDirectoryFragment.java
+++ b/src/com/android/documentsui/CreateDirectoryFragment.java
@@ -146,7 +146,7 @@
         protected void onPostExecute(DocumentInfo result) {
             if (result != null) {
                 // Navigate into newly created child
-                mActivity.onDocumentPicked(result);
+                mActivity.onDirectoryCreated(result);
             } else {
                 Toast.makeText(mActivity, R.string.create_error, Toast.LENGTH_SHORT).show();
             }
diff --git a/src/com/android/documentsui/DirectoryFragment.java b/src/com/android/documentsui/DirectoryFragment.java
index 9468cfd..7e6ec8b 100644
--- a/src/com/android/documentsui/DirectoryFragment.java
+++ b/src/com/android/documentsui/DirectoryFragment.java
@@ -89,6 +89,7 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
+import com.android.documentsui.BaseActivity.DocumentContext;
 import com.android.documentsui.BaseActivity.State;
 import com.android.documentsui.MultiSelectManager.Selection;
 import com.android.documentsui.ProviderExecutor.Preemptable;
@@ -457,7 +458,7 @@
         final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
         if (isDocumentEnabled(docMimeType, docFlags)) {
             final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor);
-            ((BaseActivity) getActivity()).onDocumentPicked(doc);
+            ((BaseActivity) getActivity()).onDocumentPicked(doc, mAdapter);
             mSelectionManager.clearSelection();
             return true;
         }
@@ -949,7 +950,8 @@
         }
     }
 
-    private final class DocumentsAdapter extends RecyclerView.Adapter<DocumentHolder> {
+    private final class DocumentsAdapter extends RecyclerView.Adapter<DocumentHolder>
+            implements DocumentContext {
 
         private final Context mContext;
         private final LayoutInflater mInflater;
@@ -1213,6 +1215,14 @@
             }
         }
 
+        @Override
+        public Cursor getCursor() {
+            if (Looper.myLooper() != Looper.getMainLooper()) {
+                throw new IllegalStateException("Can't call getCursor from non-main thread.");
+            }
+            return mCursor;
+        }
+
         private Cursor getItem(int position) {
             if (position < mCursorCount) {
                 mCursor.moveToPosition(position);
diff --git a/src/com/android/documentsui/DocumentsActivity.java b/src/com/android/documentsui/DocumentsActivity.java
index 272700b..2de7fc4 100644
--- a/src/com/android/documentsui/DocumentsActivity.java
+++ b/src/com/android/documentsui/DocumentsActivity.java
@@ -26,12 +26,8 @@
 import static com.android.documentsui.DirectoryFragment.ANIM_DOWN;
 import static com.android.documentsui.DirectoryFragment.ANIM_NONE;
 import static com.android.documentsui.DirectoryFragment.ANIM_UP;
+import static com.android.internal.util.Preconditions.checkArgument;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import android.app.ActionBar;
 import android.app.Activity;
 import android.app.Fragment;
 import android.app.FragmentManager;
@@ -608,12 +604,10 @@
     }
 
     @Override
-    public void onDocumentPicked(DocumentInfo doc) {
+    public void onDocumentPicked(DocumentInfo doc, DocumentContext context) {
         final FragmentManager fm = getFragmentManager();
         if (doc.isDirectory()) {
-            mState.stack.push(doc);
-            mState.stackTouched = true;
-            onCurrentDirectoryChanged(ANIM_DOWN);
+            openDirectory(doc);
         } else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
             // Explicit file picked, return
             new ExistingFinishTask(doc.derivedUri).executeOnExecutor(getCurrentExecutor());
diff --git a/src/com/android/documentsui/QuickViewIntentBuilder.java b/src/com/android/documentsui/QuickViewIntentBuilder.java
new file mode 100644
index 0000000..878c4c2
--- /dev/null
+++ b/src/com/android/documentsui/QuickViewIntentBuilder.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2015 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 static com.android.documentsui.model.DocumentInfo.getCursorString;
+
+import android.annotation.Nullable;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Document;
+import android.util.Log;
+
+import com.android.documentsui.BaseActivity.DocumentContext;
+import com.android.documentsui.model.DocumentInfo;
+
+/**
+ * Provides support for gather a list of quick-viewable files into a quick view intent.
+ */
+final class QuickViewIntentBuilder {
+
+    private static final String TAG = "QvIntentBuilder";
+    private static final boolean DEBUG = false;
+
+    private final DocumentInfo mDocument;
+    private final DocumentContext mContext;
+
+    public ClipData mClipData;
+    public int mDocumentLocation;
+    private PackageManager mPkgManager;
+
+    public QuickViewIntentBuilder(
+            PackageManager pkgManager, DocumentInfo doc, DocumentContext context) {
+        mPkgManager = pkgManager;
+        mDocument = doc;
+        mContext = context;
+    }
+
+    /**
+     * Builds the intent for quick viewing. Short circuits building if a handler cannot
+     * be resolved; in this case {@code null} is returned.
+     */
+    @Nullable Intent build() {
+        if (DEBUG) Log.d(TAG, "Preparing intent for doc:" + mDocument.documentId);
+
+        Intent intent = new Intent(Intent.ACTION_QUICK_VIEW);
+        intent.setDataAndType(mDocument.derivedUri, mDocument.mimeType);
+
+        // Try to resolve the intent. If a matching app isn't installed, it won't resolve.
+        ComponentName handler = intent.resolveActivity(mPkgManager);
+        if (handler == null) {
+            return null;
+        }
+
+        Cursor cursor = mContext.getCursor();
+        for (int i = 0; i < cursor.getCount(); i++) {
+            onNextItem(i, cursor);
+        }
+
+        intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        intent.putExtra(Intent.EXTRA_INDEX, mDocumentLocation);
+        intent.setClipData(mClipData);
+
+        return intent;
+    }
+
+    private void onNextItem(int index, Cursor cursor) {
+        cursor.moveToPosition(index);
+
+        String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
+        if (Document.MIME_TYPE_DIR.equals(mimeType)) {
+            return;
+        }
+
+        String id = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
+        String authority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY);
+        Uri uri = DocumentsContract.buildDocumentUri(authority, id);
+        if (DEBUG) Log.d(TAG, "Including file[" + id + "] @ " + uri);
+
+        if (id.equals(mDocument.documentId)) {
+            if (DEBUG) Log.d(TAG, "Found starting point for QV. " + index);
+            mDocumentLocation = index;
+        }
+
+        ClipData.Item item = new ClipData.Item(uri);
+        if (mClipData == null) {
+            mClipData = new ClipData(
+                    "URIs", new String[]{ClipDescription.MIMETYPE_TEXT_URILIST}, item);
+        } else {
+            mClipData.addItem(item);
+        }
+    }
+}
diff --git a/src/com/android/documentsui/StandaloneActivity.java b/src/com/android/documentsui/StandaloneActivity.java
index 5f40dab..1ca277d 100644
--- a/src/com/android/documentsui/StandaloneActivity.java
+++ b/src/com/android/documentsui/StandaloneActivity.java
@@ -25,7 +25,6 @@
 import android.app.FragmentManager;
 import android.content.ActivityNotFoundException;
 import android.content.ClipData;
-import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
@@ -61,6 +60,7 @@
 public class StandaloneActivity extends BaseActivity {
 
     public static final String TAG = "StandaloneFileManagement";
+    static final boolean DEBUG = false;
 
     private Toolbar mToolbar;
     private Spinner mToolbarStack;
@@ -284,31 +284,41 @@
     }
 
     @Override
-    public void onDocumentPicked(DocumentInfo doc) {
-        if (doc.isDirectory()) {
-            openFolder(doc);
-        } else {
-            openDocument(doc);
-        }
+    public void onDocumentsPicked(List<DocumentInfo> docs) {
+        throw new UnsupportedOperationException();
     }
 
-    private void openFolder(DocumentInfo doc) {
-        mState.stack.push(doc);
-        mState.stackTouched = true;
-        onCurrentDirectoryChanged(ANIM_DOWN);
+    @Override
+    public void onDocumentPicked(DocumentInfo doc, @Nullable DocumentContext siblings) {
+        if (doc.isDirectory()) {
+            openDirectory(doc);
+        } else {
+            openDocument(doc, siblings);
+        }
     }
 
     /**
      * Launches an intent to view the specified document.
      */
-    private void openDocument(DocumentInfo doc) {
-        Intent intent = getQuickViewIntent(doc);
+    private void openDocument(DocumentInfo doc, @Nullable DocumentContext siblings) {
+        Intent intent = null;
+        if (siblings != null) {
+            QuickViewIntentBuilder builder =
+                    new QuickViewIntentBuilder(getPackageManager(), doc, siblings);
+            intent = builder.build();
+        }
+
+        // fallback to traditional VIEW action...
         if (intent == null) {
             intent = new Intent(Intent.ACTION_VIEW);
             intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
             intent.setData(doc.derivedUri);
         }
 
+        if (DEBUG && intent.getClipData() != null) {
+            Log.d(TAG, "Starting intent w/ clip data: " + intent.getClipData());
+        }
+
         try {
             startActivity(intent);
         } catch (ActivityNotFoundException ex2) {
@@ -316,24 +326,6 @@
         }
     }
 
-    private @Nullable Intent getQuickViewIntent(DocumentInfo doc) {
-        Intent intent = new Intent(Intent.ACTION_QUICK_VIEW);
-        intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        intent.setData(doc.derivedUri);
-
-        ComponentName handler = intent.resolveActivity(getPackageManager());
-        if (handler != null) {
-            return intent;
-        }
-
-        return null;
-    }
-
-    @Override
-    public void onDocumentsPicked(List<DocumentInfo> docs) {
-        // TODO
-    }
-
     @Override
     public boolean onKeyShortcut(int keyCode, KeyEvent event) {
         DirectoryFragment dir;