Merge "Add pick ActionHandler test." into nyc-andromeda-dev
diff --git a/src/com/android/documentsui/AbstractActionHandler.java b/src/com/android/documentsui/AbstractActionHandler.java
index f867a02..6b341b8 100644
--- a/src/com/android/documentsui/AbstractActionHandler.java
+++ b/src/com/android/documentsui/AbstractActionHandler.java
@@ -163,13 +163,13 @@
      * from our concrete activity implementations.
      */
     public interface CommonAddons {
-       void refreshCurrentRootAndDirectory(@AnimationType int anim);
-       void onRootPicked(RootInfo root);
-       // 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 refreshCurrentRootAndDirectory(@AnimationType int anim);
+        void onRootPicked(RootInfo root);
+        // 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();
     }
 }
diff --git a/src/com/android/documentsui/OnRootsChangedTask.java b/src/com/android/documentsui/OnRootsChangedTask.java
new file mode 100644
index 0000000..0776e8f
--- /dev/null
+++ b/src/com/android/documentsui/OnRootsChangedTask.java
@@ -0,0 +1,81 @@
+/*
+ * 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/base/RootInfo.java b/src/com/android/documentsui/base/RootInfo.java
index 2c32b9e..f84f915 100644
--- a/src/com/android/documentsui/base/RootInfo.java
+++ b/src/com/android/documentsui/base/RootInfo.java
@@ -35,9 +35,9 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.documentsui.DocumentsAccess;
 import com.android.documentsui.IconUtils;
 import com.android.documentsui.R;
-import com.android.documentsui.roots.RootsAccess;
 
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
@@ -350,7 +350,7 @@
     }
 
     /**
-     * @deprecate use {@link RootsAccess#getRootDocumentBlocking}.
+     * @deprecate use {@link DocumentsAccess#getRootDocumentBlocking}.
      */
     @Deprecated
     public @Nullable DocumentInfo getRootDocumentBlocking(Context context) {
diff --git a/src/com/android/documentsui/files/ActionHandler.java b/src/com/android/documentsui/files/ActionHandler.java
index feb70d0..1214097 100644
--- a/src/com/android/documentsui/files/ActionHandler.java
+++ b/src/com/android/documentsui/files/ActionHandler.java
@@ -461,8 +461,6 @@
 
         public void reset(Model model, MultiSelectManager selectionMgr) {
             assert(model != null);
-            // Allowed to be null in testing, for now.
-            // assert(selectionMgr != null);
 
             this.model = model;
             this.selectionMgr = selectionMgr;
diff --git a/src/com/android/documentsui/files/QuickViewIntentBuilder.java b/src/com/android/documentsui/files/QuickViewIntentBuilder.java
index 995d280..b8d9fcb 100644
--- a/src/com/android/documentsui/files/QuickViewIntentBuilder.java
+++ b/src/com/android/documentsui/files/QuickViewIntentBuilder.java
@@ -49,9 +49,10 @@
 public final class QuickViewIntentBuilder {
 
     // trusted quick view package can be set via system property on debug builds.
-    // Unfortunately when the value is set, it interferes with testing.
+    // Unfortunately when the value is set, it interferes with testing (supercedes
+    // any value set in the resource system).
     // For that reason when trusted quick view package is set to this magic value
-    // we won't the system property. It's a gross hack, but stuff's gotta get done.
+    // we won't honor the system property.
     public static final String IGNORE_DEBUG_PROP = "*disabled*";
 
     private static final String TAG = "QuickViewIntentBuilder";
diff --git a/src/com/android/documentsui/picker/ActionHandler.java b/src/com/android/documentsui/picker/ActionHandler.java
index 369b489..d5f7bbe 100644
--- a/src/com/android/documentsui/picker/ActionHandler.java
+++ b/src/com/android/documentsui/picker/ActionHandler.java
@@ -17,7 +17,6 @@
 package com.android.documentsui.picker;
 
 import static com.android.documentsui.base.Shared.DEBUG;
-import static com.android.documentsui.base.State.ACTION_PICK_COPY_DESTINATION;
 
 import android.app.Activity;
 import android.content.Intent;
@@ -33,6 +32,7 @@
 import com.android.documentsui.base.DocumentStack;
 import com.android.documentsui.base.Lookup;
 import com.android.documentsui.base.RootInfo;
+import com.android.documentsui.base.Shared;
 import com.android.documentsui.base.State;
 import com.android.documentsui.dirlist.DocumentDetails;
 import com.android.documentsui.dirlist.FragmentTuner;
@@ -83,7 +83,7 @@
             // Concensus was that the experice was too confusing.
             // In all other cases, where the user is visiting us from another app
             // we restore the stack as last used from that app.
-            if (mState.action == ACTION_PICK_COPY_DESTINATION) {
+            if (Shared.ACTION_PICK_COPY_DESTINATION.equals(intent.getAction())) {
                 if (DEBUG) Log.d(TAG, "Launching directly into Home directory.");
                 loadHomeDir();
             } else {
@@ -155,7 +155,6 @@
 
         public void reset(Model model, MultiSelectManager selectionMgr) {
             assert(model != null);
-            assert(selectionMgr != null);
 
             this.model = model;
             this.selectionMgr = selectionMgr;
diff --git a/src/com/android/documentsui/roots/RootsAccess.java b/src/com/android/documentsui/roots/RootsAccess.java
index ee5e522..6323840 100644
--- a/src/com/android/documentsui/roots/RootsAccess.java
+++ b/src/com/android/documentsui/roots/RootsAccess.java
@@ -42,6 +42,10 @@
 
     Collection<RootInfo> getMatchingRootsBlocking(State state);
 
+    Collection<RootInfo> getRootsBlocking();
+
+    RootInfo getDefaultRootBlocking(State state);
+
     /**
      * Returns a list of roots for the specified authority. If not found, then
      * an empty list is returned.
diff --git a/src/com/android/documentsui/roots/RootsCache.java b/src/com/android/documentsui/roots/RootsCache.java
index 2fda191..ea08b40 100644
--- a/src/com/android/documentsui/roots/RootsCache.java
+++ b/src/com/android/documentsui/roots/RootsCache.java
@@ -307,6 +307,7 @@
         return mRecentsRoot.equals(root);
     }
 
+    @Override
     public Collection<RootInfo> getRootsBlocking() {
         waitForFirstLoad();
         loadStoppedAuthorities();
diff --git a/tests/common/com/android/documentsui/TestActivity.java b/tests/common/com/android/documentsui/TestActivity.java
index 3987636..7d73589 100644
--- a/tests/common/com/android/documentsui/TestActivity.java
+++ b/tests/common/com/android/documentsui/TestActivity.java
@@ -23,6 +23,7 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
+import android.net.Uri;
 
 import com.android.documentsui.AbstractActionHandler.CommonAddons;
 import com.android.documentsui.base.DocumentInfo;
diff --git a/tests/common/com/android/documentsui/testing/TestRootsAccess.java b/tests/common/com/android/documentsui/testing/TestRootsAccess.java
index 5678cf3..734d521 100644
--- a/tests/common/com/android/documentsui/testing/TestRootsAccess.java
+++ b/tests/common/com/android/documentsui/testing/TestRootsAccess.java
@@ -102,4 +102,18 @@
     public Collection<RootInfo> getRootsForAuthorityBlocking(String authority) {
         return roots.get(authority);
     }
+
+    @Override
+    public Collection<RootInfo> getRootsBlocking() {
+        List<RootInfo> result = new ArrayList<>();
+        for (Collection<RootInfo> vals : roots.values()) {
+            result.addAll(vals);
+        }
+        return result;
+    }
+
+    @Override
+    public RootInfo getDefaultRootBlocking(State state) {
+        return DOWNLOADS;
+    }
 }
diff --git a/tests/common/com/android/documentsui/testing/android/TestResources.java b/tests/common/com/android/documentsui/testing/android/TestResources.java
index 246b4e8..c962e77 100644
--- a/tests/common/com/android/documentsui/testing/android/TestResources.java
+++ b/tests/common/com/android/documentsui/testing/android/TestResources.java
@@ -78,7 +78,8 @@
     }
 
     @NonNull
-    public final String getString(@StringRes int id, Object... formatArgs) throws NotFoundException {
+    public final String getString(
+            @StringRes int id, Object... formatArgs) throws NotFoundException {
         return getString(id);
     }
 }
diff --git a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java
new file mode 100644
index 0000000..c7afff1
--- /dev/null
+++ b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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.picker;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.documentsui.R;
+import com.android.documentsui.base.RootInfo;
+import com.android.documentsui.base.Shared;
+import com.android.documentsui.dirlist.MultiSelectManager.Selection;
+import com.android.documentsui.testing.TestEnv;
+import com.android.documentsui.testing.TestRootsAccess;
+import com.android.documentsui.ui.TestDialogController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class ActionHandlerTest {
+
+    private TestEnv mEnv;
+    private TestActivity mActivity;
+    private TestDialogController mDialogs;
+    private ActionHandler<TestActivity> mHandler;
+    private Selection mSelection;
+
+    @Before
+    public void setUp() {
+        mEnv = TestEnv.create();
+        mActivity = TestActivity.create();
+        mDialogs = new TestDialogController();
+        mEnv.roots.configurePm(mActivity.packageMgr);
+
+        mHandler = new ActionHandler<>(
+                mActivity,
+                mEnv.state,
+                mEnv.roots,
+                mEnv.docs,
+                mEnv::lookupExecutor,
+                null  // tuner, not currently used.
+                );
+
+        mDialogs.confirmNext();
+
+        mSelection = new Selection();
+        mSelection.add("1");
+
+        mHandler.reset(mEnv.model, null);
+    }
+
+    @Test
+    public void testInitLocation_CopyDestination_DefaultsToDownloads() throws Exception {
+        mActivity.resources.bools.put(R.bool.productivity_device, false);
+
+        Intent intent = mActivity.getIntent();
+        intent.setAction(Shared.ACTION_PICK_COPY_DESTINATION);
+        mHandler.initLocation(mActivity.getIntent());
+        assertRootPicked(TestRootsAccess.DOWNLOADS.getUri());
+    }
+
+    @Test
+    public void testInitLocation_CopyDestination_DefaultsToHome() throws Exception {
+        mActivity.resources.bools.put(R.bool.productivity_device, true);
+
+        Intent intent = mActivity.getIntent();
+        intent.setAction(Shared.ACTION_PICK_COPY_DESTINATION);
+        mHandler.initLocation(intent);
+        assertRootPicked(TestRootsAccess.HOME.getUri());
+    }
+
+    private void assertRootPicked(Uri expectedUri) throws Exception {
+        mEnv.beforeAsserts();
+
+        mActivity.rootPicked.assertCalled();
+        RootInfo root = mActivity.rootPicked.getLastValue();
+        assertNotNull(root);
+        assertEquals(expectedUri, root.getUri());
+    }
+}
diff --git a/tests/unit/com/android/documentsui/picker/TestActivity.java b/tests/unit/com/android/documentsui/picker/TestActivity.java
new file mode 100644
index 0000000..056eb18
--- /dev/null
+++ b/tests/unit/com/android/documentsui/picker/TestActivity.java
@@ -0,0 +1,33 @@
+/*
+ * 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.picker;
+
+import org.mockito.Mockito;
+
+public abstract class TestActivity extends AbstractBase {
+
+    public static TestActivity create() {
+        TestActivity activity = Mockito.mock(TestActivity.class, Mockito.CALLS_REAL_METHODS);
+        activity.init();
+        return activity;
+    }
+}
+
+// Trick Mockito into finding our Addons methods correctly. W/o this
+// hack, Mockito thinks Addons methods are not implemented.
+abstract class AbstractBase extends com.android.documentsui.TestActivity
+        implements ActionHandler.Addons {}