Merge "Address comments in ag/1514806." into nyc-andromeda-dev
diff --git a/src/com/android/documentsui/AbstractActionHandler.java b/src/com/android/documentsui/AbstractActionHandler.java
index 814899b..1a26bcc 100644
--- a/src/com/android/documentsui/AbstractActionHandler.java
+++ b/src/com/android/documentsui/AbstractActionHandler.java
@@ -39,6 +39,7 @@
 import com.android.documentsui.dirlist.AnimationView;
 import com.android.documentsui.dirlist.DocumentDetails;
 import com.android.documentsui.files.LauncherActivity;
+import com.android.documentsui.queries.SearchViewManager;
 import com.android.documentsui.roots.LoadRootTask;
 import com.android.documentsui.roots.RootsAccess;
 import com.android.documentsui.selection.Selection;
diff --git a/src/com/android/documentsui/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java
index 1ea3b0f..d0bc30c 100644
--- a/src/com/android/documentsui/BaseActivity.java
+++ b/src/com/android/documentsui/BaseActivity.java
@@ -18,11 +18,6 @@
 
 import static com.android.documentsui.base.Shared.DEBUG;
 import static com.android.documentsui.base.Shared.EXTRA_BENCHMARK;
-import static com.android.documentsui.base.State.ACTION_CREATE;
-import static com.android.documentsui.base.State.ACTION_GET_CONTENT;
-import static com.android.documentsui.base.State.ACTION_OPEN;
-import static com.android.documentsui.base.State.ACTION_OPEN_TREE;
-import static com.android.documentsui.base.State.ACTION_PICK_COPY_DESTINATION;
 import static com.android.documentsui.base.State.MODE_GRID;
 
 import android.app.Activity;
@@ -52,12 +47,12 @@
 import com.android.documentsui.AbstractActionHandler.CommonAddons;
 import com.android.documentsui.MenuManager.SelectionDetails;
 import com.android.documentsui.NavigationViewManager.Breadcrumb;
-import com.android.documentsui.SearchViewManager.SearchManagerListener;
 import com.android.documentsui.base.DocumentInfo;
 import com.android.documentsui.base.EventHandler;
 import com.android.documentsui.base.Events;
 import com.android.documentsui.base.LocalPreferences;
 import com.android.documentsui.base.RootInfo;
+import com.android.documentsui.base.ScopedPreferences;
 import com.android.documentsui.base.Shared;
 import com.android.documentsui.base.State;
 import com.android.documentsui.base.State.ViewMode;
@@ -65,6 +60,8 @@
 import com.android.documentsui.dirlist.DirectoryFragment;
 import com.android.documentsui.dirlist.DocumentsAdapter;
 import com.android.documentsui.dirlist.Model;
+import com.android.documentsui.queries.SearchViewManager;
+import com.android.documentsui.queries.SearchViewManager.SearchManagerListener;
 import com.android.documentsui.roots.GetRootDocumentTask;
 import com.android.documentsui.roots.RootsCache;
 import com.android.documentsui.selection.Selection;
@@ -106,11 +103,16 @@
     @LayoutRes
     private int mLayoutId;
 
-    private RootsMonitor<BaseActivity> mRootsMonitor;
+    private RootsMonitor<BaseActivity<?>> mRootsMonitor;
 
     private boolean mNavDrawerHasFocus;
     private long mStartTime;
 
+    public BaseActivity(@LayoutRes int layoutId, String tag) {
+        mLayoutId = layoutId;
+        mTag = tag;
+    }
+
     protected abstract void onTaskFinished(Uri... uris);
     protected abstract void refreshDirectory(int anim);
     /** Allows sub-classes to include information in a newly created State instance. */
@@ -127,6 +129,12 @@
      * Provides Activity a means of injection into and specialization of
      * DirectoryFragment.
      */
+    public abstract ScopedPreferences getScopedPreferences();
+
+    /**
+     * Provides Activity a means of injection into and specialization of
+     * DirectoryFragment.
+     */
     public abstract SelectionManager getSelectionManager(
             DocumentsAdapter adapter, SelectionPredicate canSetState);
 
@@ -167,11 +175,6 @@
         return mMessages;
     }
 
-    public BaseActivity(@LayoutRes int layoutId, String tag) {
-        mLayoutId = layoutId;
-        mTag = tag;
-    }
-
     @CallSuper
     @Override
     public void onCreate(Bundle icicle) {
@@ -296,19 +299,11 @@
 
         includeState(state);
 
-        // Advanced roots are shown by default without menu option if forced by config or intent.
-        boolean forceAdvanced = Shared.shouldShowDeviceRoot(this, intent);
-        boolean chosenAdvanced = LocalPreferences.getShowDeviceRoot(this, state.action);
-        state.showAdvanced = forceAdvanced || chosenAdvanced;
+        state.showAdvanced =
+                Shared.mustShowDeviceRoot(intent) || getScopedPreferences().getShowDeviceRoot();
 
-        // Menu option is shown for whitelisted intents if advanced roots are not shown by default.
-        state.showAdvancedOption = !forceAdvanced && (
-                Shared.shouldShowFancyFeatures(this)
-                || state.action == ACTION_OPEN
-                || state.action == ACTION_CREATE
-                || state.action == ACTION_OPEN_TREE
-                || state.action == ACTION_PICK_COPY_DESTINATION
-                || state.action == ACTION_GET_CONTENT);
+        // Only show the toggle if advanced isn't forced enabled.
+        state.showAdvancedOption = !Shared.mustShowDeviceRoot(intent);
 
         if (DEBUG) Log.d(mTag, "Created new state object: " + state);
 
@@ -477,8 +472,8 @@
         return (root.flags & Root.FLAG_SUPPORTS_SEARCH) != 0;
     }
 
-    public static BaseActivity get(Fragment fragment) {
-        return (BaseActivity) fragment.getActivity();
+    public static BaseActivity<?> get(Fragment fragment) {
+        return (BaseActivity<?>) fragment.getActivity();
     }
 
     public State getDisplayState() {
@@ -497,7 +492,7 @@
         Metrics.logUserAction(this,
                 display ? Metrics.USER_ACTION_SHOW_ADVANCED : Metrics.USER_ACTION_HIDE_ADVANCED);
 
-        LocalPreferences.setShowDeviceRoot(this, mState.action, display);
+        getScopedPreferences().setShowDeviceRoot(display);
         mState.showAdvanced = display;
         RootsFragment.get(getFragmentManager()).onDisplayStateChanged();
         invalidateOptionsMenu();
diff --git a/src/com/android/documentsui/MenuManager.java b/src/com/android/documentsui/MenuManager.java
index 05e7f17..e261c9f 100644
--- a/src/com/android/documentsui/MenuManager.java
+++ b/src/com/android/documentsui/MenuManager.java
@@ -28,6 +28,7 @@
 import com.android.documentsui.base.Shared;
 import com.android.documentsui.base.State;
 import com.android.documentsui.dirlist.DirectoryFragment;
+import com.android.documentsui.queries.SearchViewManager;
 import com.android.documentsui.sidebar.RootsFragment;
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -296,9 +297,9 @@
     }
 
     public static class DirectoryDetails {
-        private final BaseActivity mActivity;
+        private final BaseActivity<?> mActivity;
 
-        public DirectoryDetails(BaseActivity activity) {
+        public DirectoryDetails(BaseActivity<?> activity) {
             mActivity = activity;
         }
 
diff --git a/src/com/android/documentsui/RootsMonitor.java b/src/com/android/documentsui/RootsMonitor.java
index d13f3b5..a0d4d0b 100644
--- a/src/com/android/documentsui/RootsMonitor.java
+++ b/src/com/android/documentsui/RootsMonitor.java
@@ -29,6 +29,7 @@
 import com.android.documentsui.base.RootInfo;
 import com.android.documentsui.base.State;
 import com.android.documentsui.dirlist.AnimationView;
+import com.android.documentsui.queries.SearchViewManager;
 import com.android.documentsui.roots.RootsAccess;
 
 import java.util.Collection;
diff --git a/src/com/android/documentsui/base/LocalPreferences.java b/src/com/android/documentsui/base/LocalPreferences.java
index e9d8350..0d1e639 100644
--- a/src/com/android/documentsui/base/LocalPreferences.java
+++ b/src/com/android/documentsui/base/LocalPreferences.java
@@ -26,14 +26,12 @@
 import android.os.UserHandle;
 import android.preference.PreferenceManager;
 
-import com.android.documentsui.base.State.ActionType;
 import com.android.documentsui.base.State.ViewMode;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
 public class LocalPreferences {
-    private static final String INCLUDE_DEVICE_ROOT = "includeDeviceRoot-";
     private static final String ROOT_VIEW_MODE_PREFIX = "rootViewMode-";
 
     public static @ViewMode int getViewMode(Context context, RootInfo root,
@@ -41,15 +39,6 @@
         return getPrefs(context).getInt(createKey(root), fallback);
     }
 
-    public static boolean getShowDeviceRoot(Context context, @ActionType int action) {
-        return getPrefs(context).getBoolean(INCLUDE_DEVICE_ROOT + action, false);
-    }
-
-    public static void setShowDeviceRoot(
-            Context context, @ActionType int action, boolean display) {
-        getPrefs(context).edit().putBoolean(INCLUDE_DEVICE_ROOT + action, display).apply();
-    }
-
     public static void setViewMode(Context context, RootInfo root, @ViewMode int viewMode) {
         assert(viewMode != MODE_UNKNOWN);
         getPrefs(context).edit().putInt(createKey(root), viewMode).apply();
diff --git a/src/com/android/documentsui/base/ScopedPreferences.java b/src/com/android/documentsui/base/ScopedPreferences.java
new file mode 100644
index 0000000..e933ad0
--- /dev/null
+++ b/src/com/android/documentsui/base/ScopedPreferences.java
@@ -0,0 +1,54 @@
+/*
+ * 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.base;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+
+public interface ScopedPreferences {
+
+    boolean getShowDeviceRoot();
+    void setShowDeviceRoot(boolean display);
+
+    public static ScopedPreferences create(Context context, String scope) {
+        return new RuntimeScopedPreferences(
+                PreferenceManager.getDefaultSharedPreferences(context), scope);
+    }
+
+    static final class RuntimeScopedPreferences implements ScopedPreferences {
+
+        private static final String INCLUDE_DEVICE_ROOT = "includeDeviceRoot-";
+
+        private SharedPreferences mSharedPrefs;
+        private String mScope;
+
+        private RuntimeScopedPreferences(SharedPreferences sharedPrefs, String scope)  {
+            mSharedPrefs = sharedPrefs;
+            mScope = scope;
+        }
+
+        @Override
+        public boolean getShowDeviceRoot() {
+            return mSharedPrefs.getBoolean(INCLUDE_DEVICE_ROOT + mScope, false);
+        }
+
+        @Override
+        public void setShowDeviceRoot(boolean display) {
+            mSharedPrefs.edit().putBoolean(INCLUDE_DEVICE_ROOT + mScope, display).apply();
+        }
+    }
+}
diff --git a/src/com/android/documentsui/base/Shared.java b/src/com/android/documentsui/base/Shared.java
index 63f2886..14bb081 100644
--- a/src/com/android/documentsui/base/Shared.java
+++ b/src/com/android/documentsui/base/Shared.java
@@ -251,9 +251,8 @@
     /*
      * Returns true if device root should be shown.
      */
-    public static boolean shouldShowDeviceRoot(Context context, Intent intent) {
-        return isProductivityMode(context, intent)
-                || intent.getBooleanExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, false);
+    public static boolean mustShowDeviceRoot(Intent intent) {
+        return intent.getBooleanExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, false);
     }
 
     /**
diff --git a/src/com/android/documentsui/files/ActionHandler.java b/src/com/android/documentsui/files/ActionHandler.java
index 838a431..afea4cd 100644
--- a/src/com/android/documentsui/files/ActionHandler.java
+++ b/src/com/android/documentsui/files/ActionHandler.java
@@ -33,7 +33,6 @@
 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;
@@ -42,6 +41,7 @@
 import com.android.documentsui.base.Lookup;
 import com.android.documentsui.base.MimeTypes;
 import com.android.documentsui.base.RootInfo;
+import com.android.documentsui.base.Shared;
 import com.android.documentsui.base.State;
 import com.android.documentsui.clipping.ClipStore;
 import com.android.documentsui.clipping.DocumentClipper;
@@ -51,6 +51,7 @@
 import com.android.documentsui.dirlist.Model;
 import com.android.documentsui.dirlist.Model.Update;
 import com.android.documentsui.files.ActionHandler.Addons;
+import com.android.documentsui.queries.SearchViewManager;
 import com.android.documentsui.roots.GetRootDocumentTask;
 import com.android.documentsui.roots.RootsAccess;
 import com.android.documentsui.selection.Selection;
@@ -355,7 +356,9 @@
         }
 
         Intent intent = Intent.createChooser(buildViewIntent(doc), null);
-        intent.putExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, false);
+        if (Shared.ENABLE_OMC_API_FEATURES) {
+            intent.putExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, false);
+        }
         try {
             mActivity.startActivity(intent);
         } catch (ActivityNotFoundException e) {
diff --git a/src/com/android/documentsui/files/FilesActivity.java b/src/com/android/documentsui/files/FilesActivity.java
index 823c9ea..42afa7f 100644
--- a/src/com/android/documentsui/files/FilesActivity.java
+++ b/src/com/android/documentsui/files/FilesActivity.java
@@ -48,6 +48,7 @@
 import com.android.documentsui.base.DocumentStack;
 import com.android.documentsui.base.EventHandler;
 import com.android.documentsui.base.RootInfo;
+import com.android.documentsui.base.ScopedPreferences;
 import com.android.documentsui.base.Shared;
 import com.android.documentsui.base.State;
 import com.android.documentsui.clipping.DocumentClipper;
@@ -71,9 +72,12 @@
 public class FilesActivity
         extends BaseActivity<ActionHandler<FilesActivity>> implements ActionHandler.Addons {
 
-    public static final String TAG = "FilesActivity";
+    private static final String TAG = "FilesActivity";
+    private static final String PREFERENCES_SCOPE = "files";
 
     private final Config mConfig = new Config();
+
+    private ScopedPreferences mPrefs;
     private SelectionManager mSelectionMgr;
     private MenuManager mMenuManager;
     private DialogController mDialogs;
@@ -88,6 +92,11 @@
 
     @Override
     public void onCreate(Bundle icicle) {
+
+        // must be initialized before calling super.onCreate because prefs
+        // are used in State initialization.
+        mPrefs = ScopedPreferences.create(this, PREFERENCES_SCOPE);
+
         super.onCreate(icicle);
 
         mClipper = DocumentsApplication.getDocumentClipper(this);
@@ -367,6 +376,11 @@
     }
 
     @Override
+    public ScopedPreferences getScopedPreferences() {
+        return mPrefs;
+    }
+
+    @Override
     public SelectionManager getSelectionManager(
             DocumentsAdapter adapter, SelectionPredicate canSetState) {
         return mSelectionMgr.reset(adapter, canSetState);
diff --git a/src/com/android/documentsui/files/MenuManager.java b/src/com/android/documentsui/files/MenuManager.java
index 61d57b5..839c245 100644
--- a/src/com/android/documentsui/files/MenuManager.java
+++ b/src/com/android/documentsui/files/MenuManager.java
@@ -25,12 +25,11 @@
 import android.view.MenuItem;
 import android.view.View;
 
-import com.android.documentsui.BaseActivity;
 import com.android.documentsui.R;
-import com.android.documentsui.SearchViewManager;
 import com.android.documentsui.base.DocumentInfo;
 import com.android.documentsui.base.RootInfo;
 import com.android.documentsui.base.State;
+import com.android.documentsui.queries.SearchViewManager;
 
 import java.util.List;
 import java.util.function.IntFunction;
diff --git a/src/com/android/documentsui/files/QuickViewIntentBuilder.java b/src/com/android/documentsui/files/QuickViewIntentBuilder.java
index b8d9fcb..3304cc1 100644
--- a/src/com/android/documentsui/files/QuickViewIntentBuilder.java
+++ b/src/com/android/documentsui/files/QuickViewIntentBuilder.java
@@ -38,6 +38,7 @@
 import com.android.documentsui.R;
 import com.android.documentsui.base.DocumentInfo;
 import com.android.documentsui.dirlist.Model;
+import com.android.documentsui.queries.SetQuickViewerCommand;
 import com.android.documentsui.roots.RootCursorWrapper;
 
 import java.util.ArrayList;
@@ -54,7 +55,6 @@
     // For that reason when trusted quick view package is set to this magic value
     // we won't honor the system property.
     public static final String IGNORE_DEBUG_PROP = "*disabled*";
-
     private static final String TAG = "QuickViewIntentBuilder";
 
     private final DocumentInfo mDocument;
@@ -142,6 +142,9 @@
         // Allow users of debug devices to override default quick viewer
         // for the purposes of testing.
         if (Build.IS_DEBUGGABLE) {
+            if (SetQuickViewerCommand.sQuickViewer != null) {
+                return SetQuickViewerCommand.sQuickViewer;
+            }
             return android.os.SystemProperties.get("debug.quick_viewer", resValue);
         }
         return resValue;
diff --git a/src/com/android/documentsui/picker/ActionHandler.java b/src/com/android/documentsui/picker/ActionHandler.java
index e6b1144..6e1b9d4 100644
--- a/src/com/android/documentsui/picker/ActionHandler.java
+++ b/src/com/android/documentsui/picker/ActionHandler.java
@@ -31,7 +31,6 @@
 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;
@@ -44,6 +43,7 @@
 import com.android.documentsui.dirlist.Model;
 import com.android.documentsui.dirlist.Model.Update;
 import com.android.documentsui.picker.ActionHandler.Addons;
+import com.android.documentsui.queries.SearchViewManager;
 import com.android.documentsui.roots.RootsAccess;
 import com.android.documentsui.selection.SelectionManager;
 
diff --git a/src/com/android/documentsui/picker/MenuManager.java b/src/com/android/documentsui/picker/MenuManager.java
index a61081b..8bdd150 100644
--- a/src/com/android/documentsui/picker/MenuManager.java
+++ b/src/com/android/documentsui/picker/MenuManager.java
@@ -25,8 +25,8 @@
 import android.view.Menu;
 import android.view.MenuItem;
 
-import com.android.documentsui.SearchViewManager;
 import com.android.documentsui.base.State;
+import com.android.documentsui.queries.SearchViewManager;
 
 public final class MenuManager extends com.android.documentsui.MenuManager {
 
diff --git a/src/com/android/documentsui/picker/PickActivity.java b/src/com/android/documentsui/picker/PickActivity.java
index 17e6bb1..268d801 100644
--- a/src/com/android/documentsui/picker/PickActivity.java
+++ b/src/com/android/documentsui/picker/PickActivity.java
@@ -56,6 +56,7 @@
 import com.android.documentsui.base.MimeTypes;
 import com.android.documentsui.base.PairedTask;
 import com.android.documentsui.base.RootInfo;
+import com.android.documentsui.base.ScopedPreferences;
 import com.android.documentsui.base.Shared;
 import com.android.documentsui.base.State;
 import com.android.documentsui.dirlist.DirectoryFragment;
@@ -75,10 +76,13 @@
 public class PickActivity
         extends BaseActivity<ActionHandler<PickActivity>> implements ActionHandler.Addons {
 
-    private static final int CODE_FORWARD = 42;
     private static final String TAG = "PickActivity";
+    private static final String PREFERENCES_SCOPE = "picker";
+    private static final int CODE_FORWARD = 42;
+
     private final Config mConfig = new Config();
 
+    private ScopedPreferences mPrefs;
     private SelectionManager mSelectionMgr;
     private MenuManager mMenuManager;
     private ActionModeController mActionModeController;
@@ -89,6 +93,11 @@
 
     @Override
     public void onCreate(Bundle icicle) {
+
+        // must be initialized before calling super.onCreate because prefs
+        // are used in State initialization.
+        mPrefs = ScopedPreferences.create(this, PREFERENCES_SCOPE);
+
         super.onCreate(icicle);
 
         mSelectionMgr = new SelectionManager(
@@ -415,6 +424,11 @@
         return mConfig;
     }
 
+    @Override
+    public ScopedPreferences getScopedPreferences() {
+        return mPrefs;
+    }
+
     public SelectionManager getSelectionManager(
             DocumentsAdapter adapter, SelectionPredicate canSetState) {
         return mSelectionMgr.reset(adapter, canSetState);
diff --git a/src/com/android/documentsui/queries/DebugCommandProcessor.java b/src/com/android/documentsui/queries/DebugCommandProcessor.java
new file mode 100644
index 0000000..70d9d64
--- /dev/null
+++ b/src/com/android/documentsui/queries/DebugCommandProcessor.java
@@ -0,0 +1,57 @@
+/*
+ * 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.queries;
+
+import android.os.Build;
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
+
+import com.android.documentsui.base.EventHandler;
+
+import java.util.ArrayList;
+import java.util.List;
+
+final class DebugCommandProcessor implements EventHandler<String> {
+
+    private final List<EventHandler<String[]>> mCommands = new ArrayList<>();
+
+    public DebugCommandProcessor() {
+        if (Build.IS_DEBUGGABLE) {
+            mCommands.add(new SetQuickViewerCommand());
+        }
+    }
+
+    @VisibleForTesting
+    DebugCommandProcessor(EventHandler<String[]>... commands) {
+        for (EventHandler<String[]> c : commands) {
+            mCommands.add(c);
+        }
+    }
+
+    @Override
+    public boolean accept(String query) {
+        if (query.length() > 6 && query.substring(0, 6).equals("#debug")) {
+            String[] tokens = query.substring(7).split("\\s+");
+            for (EventHandler<String[]> command : mCommands) {
+                if (command.accept(tokens)) {
+                    return true;
+                }
+            }
+            Log.d(SearchViewManager.TAG, "Unrecognized debug command: " + query);
+        }
+        return false;
+    }
+}
diff --git a/src/com/android/documentsui/SearchViewManager.java b/src/com/android/documentsui/queries/SearchViewManager.java
similarity index 90%
rename from src/com/android/documentsui/SearchViewManager.java
rename to src/com/android/documentsui/queries/SearchViewManager.java
index 51db34f..6ade64d 100644
--- a/src/com/android/documentsui/SearchViewManager.java
+++ b/src/com/android/documentsui/queries/SearchViewManager.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.documentsui;
+package com.android.documentsui.queries;
 
 import static com.android.documentsui.base.Shared.DEBUG;
 
@@ -32,6 +32,8 @@
 import android.widget.SearchView;
 import android.widget.SearchView.OnQueryTextListener;
 
+import com.android.documentsui.DocumentsToolbar;
+import com.android.documentsui.R;
 import com.android.documentsui.base.RootInfo;
 import com.android.documentsui.base.Shared;
 
@@ -42,14 +44,9 @@
         SearchView.OnCloseListener, OnQueryTextListener, OnClickListener, OnFocusChangeListener,
         OnActionExpandListener {
 
-    public interface SearchManagerListener {
-        void onSearchChanged(@Nullable String query);
-        void onSearchFinished();
-        void onSearchViewChanged(boolean opened);
-    }
-
-    private static final String TAG = "SearchManager";
+    static final String TAG = "SearchManager";
     private final SearchManagerListener mListener;
+    private final DebugCommandProcessor mCommandProcessor;
 
     private boolean mSearchExpanded;
     private String mCurrentSearch;
@@ -66,6 +63,12 @@
         assert (listener != null);
         mListener = listener;
         mCurrentSearch = savedState != null ? savedState.getString(Shared.EXTRA_QUERY) : null;
+
+        // "Commands" are meta input for controlling system behavior.
+        // We piggy back on search input as it is the only text input
+        // area in the app. But the functionality is independent
+        // of "regular" search query processing.
+        mCommandProcessor = new DebugCommandProcessor();
     }
 
     public void install(DocumentsToolbar actionBar, boolean isFullBarSearch) {
@@ -102,7 +105,7 @@
     /**
      * @param root Info about the current directory.
      */
-    void update(RootInfo root) {
+    public void update(RootInfo root) {
         if (mMenuItem == null) {
             if (DEBUG) Log.d(TAG, "update called before Search MenuItem installed.");
             return;
@@ -148,7 +151,7 @@
      *
      * @return True if it cancels search. False if it does not operate search currently.
      */
-    boolean cancelSearch() {
+    public boolean cancelSearch() {
         if (isExpanded() || isSearching()) {
             // If the query string is not empty search view won't get iconified
             mSearchView.setQuery("", false);
@@ -239,9 +242,15 @@
 
     @Override
     public boolean onQueryTextSubmit(String query) {
-        mCurrentSearch = query;
-        mSearchView.clearFocus();
-        mListener.onSearchChanged(mCurrentSearch);
+
+        if (mCommandProcessor.accept(query)) {
+            cancelSearch();
+        } else {
+            mCurrentSearch = query;
+            mSearchView.clearFocus();
+            mListener.onSearchChanged(mCurrentSearch);
+        }
+
         return true;
     }
 
@@ -290,7 +299,13 @@
         return mCurrentSearch != null;
     }
 
-    boolean isExpanded() {
+    public boolean isExpanded() {
         return mSearchExpanded;
     }
+
+    public interface SearchManagerListener {
+        void onSearchChanged(@Nullable String query);
+        void onSearchFinished();
+        void onSearchViewChanged(boolean opened);
+    }
 }
diff --git a/src/com/android/documentsui/queries/SetQuickViewerCommand.java b/src/com/android/documentsui/queries/SetQuickViewerCommand.java
new file mode 100644
index 0000000..37fe0db
--- /dev/null
+++ b/src/com/android/documentsui/queries/SetQuickViewerCommand.java
@@ -0,0 +1,48 @@
+/*
+ * 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.queries;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.documentsui.base.EventHandler;
+
+public class SetQuickViewerCommand implements EventHandler<String[]> {
+
+    // This is a quick/easy shortcut to sharing quick viewer debug settings
+    // with QuickViewIntent builder. Tried setting at a system property
+    // but got a native error. This being quick and easy, didn't investigate that err.
+    public static String sQuickViewer;
+    private static final String TAG = "SetQuickViewerCommand";
+
+    @Override
+    public boolean accept(String[] tokens) {
+        if ("setqv".equals(tokens[0])) {
+            if (tokens.length == 2 && !TextUtils.isEmpty(tokens[1])) {
+                sQuickViewer = tokens[1];
+                Log.i(TAG, "Set quick viewer to: " + sQuickViewer);
+                return true;
+            } else {
+                Log.w(TAG, "Invalid command structure: " + tokens);
+            }
+        } else if ("unsetqv".equals(tokens[0])) {
+            Log.i(TAG, "Unset quick viewer");
+            sQuickViewer = null;
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/tests/common/com/android/documentsui/testing/TestSearchViewManager.java b/tests/common/com/android/documentsui/testing/TestSearchViewManager.java
index 3be8f6a..560c783 100644
--- a/tests/common/com/android/documentsui/testing/TestSearchViewManager.java
+++ b/tests/common/com/android/documentsui/testing/TestSearchViewManager.java
@@ -16,10 +16,10 @@
 
 package com.android.documentsui.testing;
 
-import com.android.documentsui.SearchViewManager;
+import com.android.documentsui.queries.SearchViewManager;
 
 /**
- * Test copy of {@link com.android.documentsui.SearchViewManager}
+ * Test copy of {@link com.android.documentsui.queries.SearchViewManager}
  *
  * Specficially used to test whether {@link #showMenu(boolean)}
  * and {@link #updateMenu()} are called.
diff --git a/tests/functional/com/android/documentsui/BandSelectionUiTest.java b/tests/functional/com/android/documentsui/BandSelectionUiTest.java
index 61bb442..c1b44b4 100644
--- a/tests/functional/com/android/documentsui/BandSelectionUiTest.java
+++ b/tests/functional/com/android/documentsui/BandSelectionUiTest.java
@@ -18,7 +18,7 @@
 
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.test.suitebuilder.annotation.LargeTest;
+import android.support.test.filters.LargeTest;
 
 import com.android.documentsui.files.FilesActivity;
 
diff --git a/tests/functional/com/android/documentsui/FileManagementUiTest.java b/tests/functional/com/android/documentsui/FileManagementUiTest.java
index dd186cb..bd62550 100644
--- a/tests/functional/com/android/documentsui/FileManagementUiTest.java
+++ b/tests/functional/com/android/documentsui/FileManagementUiTest.java
@@ -21,11 +21,10 @@
 
 import android.net.Uri;
 import android.os.RemoteException;
+import android.support.test.filters.LargeTest;
 import android.support.test.filters.Suppress;
-import android.test.suitebuilder.annotation.LargeTest;
 import android.view.KeyEvent;
 
-import com.android.documentsui.R;
 import com.android.documentsui.files.FilesActivity;
 
 @LargeTest
diff --git a/tests/functional/com/android/documentsui/FilesActivityDefaultsUiTest.java b/tests/functional/com/android/documentsui/FilesActivityDefaultsUiTest.java
index 8fc6d27..ff86a7f 100644
--- a/tests/functional/com/android/documentsui/FilesActivityDefaultsUiTest.java
+++ b/tests/functional/com/android/documentsui/FilesActivityDefaultsUiTest.java
@@ -21,7 +21,7 @@
 
 import android.content.Intent;
 import android.provider.DocumentsContract;
-import android.test.suitebuilder.annotation.LargeTest;
+import android.support.test.filters.LargeTest;
 
 import com.android.documentsui.base.RootInfo;
 import com.android.documentsui.base.Shared;
diff --git a/tests/functional/com/android/documentsui/FilesActivityUiTest.java b/tests/functional/com/android/documentsui/FilesActivityUiTest.java
index 4f0ce13..a798f16 100644
--- a/tests/functional/com/android/documentsui/FilesActivityUiTest.java
+++ b/tests/functional/com/android/documentsui/FilesActivityUiTest.java
@@ -18,7 +18,7 @@
 
 import android.net.Uri;
 import android.os.RemoteException;
-import android.test.suitebuilder.annotation.LargeTest;
+import android.support.test.filters.LargeTest;
 
 import com.android.documentsui.files.FilesActivity;
 
diff --git a/tests/functional/com/android/documentsui/GestureSelectionUiTest.java b/tests/functional/com/android/documentsui/GestureSelectionUiTest.java
index 16c92f3..a8a1aec 100644
--- a/tests/functional/com/android/documentsui/GestureSelectionUiTest.java
+++ b/tests/functional/com/android/documentsui/GestureSelectionUiTest.java
@@ -16,7 +16,7 @@
 
 package com.android.documentsui;
 
-import android.test.suitebuilder.annotation.LargeTest;
+import android.support.test.filters.LargeTest;
 
 import com.android.documentsui.files.FilesActivity;
 
diff --git a/tests/functional/com/android/documentsui/IntegratedDownloadsUiTest.java b/tests/functional/com/android/documentsui/IntegratedDownloadsUiTest.java
index da340c2..62e5fb6 100644
--- a/tests/functional/com/android/documentsui/IntegratedDownloadsUiTest.java
+++ b/tests/functional/com/android/documentsui/IntegratedDownloadsUiTest.java
@@ -20,10 +20,10 @@
 import android.app.DownloadManager.Request;
 import android.content.Context;
 import android.net.Uri;
+import android.support.test.filters.LargeTest;
+import android.support.test.filters.Suppress;
 import android.support.test.uiautomator.Configurator;
 import android.support.test.uiautomator.UiObject;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.Suppress;
 import android.view.MotionEvent;
 
 import com.android.documentsui.files.FilesActivity;
diff --git a/tests/functional/com/android/documentsui/KeyboardNavigationUiTest.java b/tests/functional/com/android/documentsui/KeyboardNavigationUiTest.java
index f70918d..d9e2065 100644
--- a/tests/functional/com/android/documentsui/KeyboardNavigationUiTest.java
+++ b/tests/functional/com/android/documentsui/KeyboardNavigationUiTest.java
@@ -16,10 +16,9 @@
 
 package com.android.documentsui;
 
-import android.net.Uri;
 import android.os.RemoteException;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.Suppress;
+import android.support.test.filters.LargeTest;
+import android.support.test.filters.Suppress;
 import android.view.KeyEvent;
 
 import com.android.documentsui.files.FilesActivity;
diff --git a/tests/functional/com/android/documentsui/RenameDocumentUiTest.java b/tests/functional/com/android/documentsui/RenameDocumentUiTest.java
index 0330ac3..9848bd5 100644
--- a/tests/functional/com/android/documentsui/RenameDocumentUiTest.java
+++ b/tests/functional/com/android/documentsui/RenameDocumentUiTest.java
@@ -16,10 +16,9 @@
 
 package com.android.documentsui;
 
+import android.support.test.filters.LargeTest;
 import android.support.test.uiautomator.UiObjectNotFoundException;
-import android.test.suitebuilder.annotation.LargeTest;
 
-import com.android.documentsui.R;
 import com.android.documentsui.files.FilesActivity;
 
 @LargeTest
diff --git a/tests/functional/com/android/documentsui/SearchViewUiTest.java b/tests/functional/com/android/documentsui/SearchViewUiTest.java
index 1b248a9..8e54512 100644
--- a/tests/functional/com/android/documentsui/SearchViewUiTest.java
+++ b/tests/functional/com/android/documentsui/SearchViewUiTest.java
@@ -19,9 +19,9 @@
 import static com.android.documentsui.StubProvider.ROOT_0_ID;
 import static com.android.documentsui.StubProvider.ROOT_1_ID;
 
+import android.support.test.filters.LargeTest;
 import android.support.test.filters.Suppress;
 import android.support.v7.recyclerview.R;
-import android.test.suitebuilder.annotation.LargeTest;
 
 import com.android.documentsui.files.FilesActivity;
 
diff --git a/tests/functional/com/android/documentsui/SidebarUiTest.java b/tests/functional/com/android/documentsui/SidebarUiTest.java
index 79dacea..c21c853 100644
--- a/tests/functional/com/android/documentsui/SidebarUiTest.java
+++ b/tests/functional/com/android/documentsui/SidebarUiTest.java
@@ -19,8 +19,8 @@
 import static com.android.documentsui.StubProvider.ROOT_0_ID;
 import static com.android.documentsui.StubProvider.ROOT_1_ID;
 
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.Suppress;
+import android.support.test.filters.LargeTest;
+import android.support.test.filters.Suppress;
 
 import com.android.documentsui.files.FilesActivity;
 
diff --git a/tests/unit/com/android/documentsui/queries/DebugCommandProcessorTest.java b/tests/unit/com/android/documentsui/queries/DebugCommandProcessorTest.java
new file mode 100644
index 0000000..0f6e996
--- /dev/null
+++ b/tests/unit/com/android/documentsui/queries/DebugCommandProcessorTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.queries;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.documentsui.testing.TestEventHandler;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class DebugCommandProcessorTest {
+
+    private TestEventHandler<String[]> mCommand0;
+    private TestEventHandler<String[]> mCommand1;
+    private DebugCommandProcessor mProcessor;
+
+    @Before
+    public void setUp() {
+        mCommand0 = new TestEventHandler<>();
+        mCommand1 = new TestEventHandler<>();
+        mProcessor = new DebugCommandProcessor(mCommand0, mCommand1);
+    }
+
+    @Test
+    public void testTriesAllCommands() {
+        mProcessor.accept("#debug poodles");
+        mCommand0.assertCalled();
+        mCommand1.assertCalled();
+    }
+
+    @Test
+    public void testStopsAfterCommandHandled() {
+        mCommand0.nextReturn(true);
+        mProcessor.accept("#debug poodles");
+        mCommand0.assertCalled();
+        mCommand1.assertNotCalled();
+    }
+
+    @Test
+    public void testMissingCommand() {
+        mProcessor.accept("#debug");
+        mCommand0.assertNotCalled();
+        mCommand1.assertNotCalled();
+    }
+
+    @Test
+    public void testEmptyInput() {
+        mProcessor.accept("#debug");
+        mCommand0.assertNotCalled();
+        mCommand1.assertNotCalled();
+    }
+}