Merge "Make RecentsLoader load on a per-authority basis instead of per-root." into nyc-andromeda-dev
diff --git a/src/com/android/documentsui/AbstractActionHandler.java b/src/com/android/documentsui/AbstractActionHandler.java
new file mode 100644
index 0000000..65f324e
--- /dev/null
+++ b/src/com/android/documentsui/AbstractActionHandler.java
@@ -0,0 +1,104 @@
+/*
+ * 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.ClipData;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.os.Parcelable;
+
+import com.android.documentsui.base.BooleanConsumer;
+import com.android.documentsui.base.DocumentStack;
+import com.android.documentsui.base.RootInfo;
+import com.android.documentsui.base.Shared;
+import com.android.documentsui.dirlist.DocumentDetails;
+import com.android.documentsui.manager.LauncherActivity;
+import com.android.documentsui.sidebar.EjectRootTask;
+
+/**
+ * Provides support for specializing the actions (viewDocument etc.) to the host activity.
+ */
+public abstract class AbstractActionHandler<T extends Activity> implements ActionHandler {
+
+ protected final T mActivity;
+
+ public AbstractActionHandler(T activity) {
+ mActivity = activity;
+ }
+
+ @Override
+ public void openSettings(RootInfo root) {
+ throw new UnsupportedOperationException("Can't open settings.");
+ }
+
+ @Override
+ public void ejectRoot(RootInfo root, BooleanConsumer listener) {
+ new EjectRootTask(
+ mActivity.getContentResolver(),
+ root.authority,
+ root.rootId,
+ listener).executeOnExecutor(ProviderExecutor.forAuthority(root.authority));
+ }
+
+ @Override
+ public void openRoot(ResolveInfo app) {
+ throw new UnsupportedOperationException("Can't open an app.");
+ }
+
+ @Override
+ public void showAppDetails(ResolveInfo info) {
+ throw new UnsupportedOperationException("Can't show app details.");
+ }
+
+ @Override
+ public boolean dropOn(ClipData data, RootInfo root) {
+ throw new UnsupportedOperationException("Can't open an app.");
+ }
+
+ @Override
+ public void openInNewWindow(DocumentStack path) {
+ Metrics.logUserAction(mActivity, Metrics.USER_ACTION_NEW_WINDOW);
+
+ Intent intent = LauncherActivity.createLaunchIntent(mActivity);
+ intent.putExtra(Shared.EXTRA_STACK, (Parcelable) path);
+
+ // Multi-window necessitates we pick how we are launched.
+ // By default we'd be launched in-place above the existing app.
+ // By setting launch-to-side ActivityManager will open us to side.
+ if (mActivity.isInMultiWindowMode()) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
+ }
+
+ mActivity.startActivity(intent);
+ }
+
+ @Override
+ public void pasteIntoFolder(RootInfo root) {
+ throw new UnsupportedOperationException("Can't paste into folder.");
+ }
+
+ @Override
+ public boolean viewDocument(DocumentDetails doc) {
+ throw new UnsupportedOperationException("Direct view not supported!");
+ }
+
+ @Override
+ public boolean previewDocument(DocumentDetails doc) {
+ throw new UnsupportedOperationException("Preview not supported!");
+ }
+}
diff --git a/src/com/android/documentsui/ActionHandler.java b/src/com/android/documentsui/ActionHandler.java
index 6e8f90b..dddd374 100644
--- a/src/com/android/documentsui/ActionHandler.java
+++ b/src/com/android/documentsui/ActionHandler.java
@@ -19,102 +19,39 @@
import android.content.ClipData;
import android.content.pm.ResolveInfo;
-import com.android.documentsui.base.CheckedTask.Check;
-import com.android.documentsui.base.DocumentInfo;
+import com.android.documentsui.base.BooleanConsumer;
+import com.android.documentsui.base.DocumentStack;
import com.android.documentsui.base.RootInfo;
-import com.android.documentsui.clipping.DocumentClipper;
import com.android.documentsui.dirlist.DocumentDetails;
-import com.android.documentsui.sidebar.EjectRootTask;
-import java.util.function.BooleanSupplier;
-import java.util.function.Consumer;
+public interface ActionHandler {
-/**
- * Provides support for specializing the actions (viewDocument etc.) to the host activity.
- */
-public abstract class ActionHandler<T extends BaseActivity> {
-
- protected T mActivity;
-
- public ActionHandler(T activity) {
- mActivity = activity;
- }
-
- public void openSettings(RootInfo root) {
- throw new UnsupportedOperationException("Can't open settings.");
- }
+ void openSettings(RootInfo root);
/**
* Drops documents on a root.
* @param check The check to make sure RootsFragment is not detached from activity.
*/
- public boolean dropOn(ClipData data, RootInfo root) {
- new GetRootDocumentTask(
- root,
- mActivity,
- mActivity::isDestroyed,
- (DocumentInfo doc) -> dropOn(data, root, doc)
- ).executeOnExecutor(ProviderExecutor.forAuthority(root.authority));
- return true;
- }
+ boolean dropOn(ClipData data, RootInfo root);
- private void dropOn(ClipData data, RootInfo root, DocumentInfo doc) {
- DocumentClipper clipper
- = DocumentsApplication.getDocumentClipper(mActivity);
- clipper.copyFromClipData(root, doc, data, mActivity.fileOpCallback);
- }
+ /**
+ * Attempts to eject the identified root. Returns a boolean answer to listener.
+ */
+ void ejectRoot(RootInfo root, BooleanConsumer listener);
- public void ejectRoot(
- RootInfo root, BooleanSupplier ejectCanceledCheck, Consumer<Boolean> listener) {
- assert(ejectCanceledCheck != null);
- ejectRoot(
- root.authority,
- root.rootId,
- ejectCanceledCheck,
- listener);
- }
+ void showAppDetails(ResolveInfo info);
- private void ejectRoot(
- String authority,
- String rootId,
- BooleanSupplier ejectCanceledCheck,
- Consumer<Boolean> listener) {
- new EjectRootTask(
- mActivity,
- authority,
- rootId,
- ejectCanceledCheck,
- listener).executeOnExecutor(ProviderExecutor.forAuthority(authority));
- }
+ void openRoot(RootInfo root);
- public void showAppDetails(ResolveInfo info) {
- throw new UnsupportedOperationException("Can't show app details.");
- }
+ void openRoot(ResolveInfo app);
- public void openRoot(RootInfo root) {
- Metrics.logRootVisited(mActivity, root);
- mActivity.onRootPicked(root);
- }
+ void openInNewWindow(DocumentStack path);
- public void openRoot(ResolveInfo app) {
- throw new UnsupportedOperationException("Can't open an app.");
- }
+ void pasteIntoFolder(RootInfo root);
- public void openInNewWindow(RootInfo root) {
- throw new UnsupportedOperationException("Can't open in new window");
- }
+ boolean viewDocument(DocumentDetails doc);
- public void pasteIntoFolder(RootInfo root) {
- throw new UnsupportedOperationException("Can't paste into folder.");
- }
+ boolean previewDocument(DocumentDetails doc);
- public boolean viewDocument(DocumentDetails doc) {
- throw new UnsupportedOperationException("Direct view not supported!");
- }
-
- public boolean previewDocument(DocumentDetails doc) {
- throw new UnsupportedOperationException("Preview not supported!");
- }
-
- public abstract boolean openDocument(DocumentDetails doc);
+ boolean openDocument(DocumentDetails doc);
}
diff --git a/src/com/android/documentsui/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java
index f7032aa..21570e4 100644
--- a/src/com/android/documentsui/BaseActivity.java
+++ b/src/com/android/documentsui/BaseActivity.java
@@ -158,10 +158,9 @@
* Provides Activity a means of injection into and specialization of
* fragment actions.
*
- * Args can be nullable when called from a contact without this information such as
- * RootsFragment.
+ * Args can be nullable when called from a context lacking them, such as RootsFragment.
*/
- public abstract ActionHandler<? extends BaseActivity> getActionHandler(
+ public abstract ActionHandler getActionHandler(
@Nullable Model model, @Nullable MultiSelectManager selectionMgr);
public abstract void onDocumentPicked(DocumentInfo doc, Model model);
diff --git a/src/com/android/documentsui/base/BooleanConsumer.java b/src/com/android/documentsui/base/BooleanConsumer.java
new file mode 100644
index 0000000..3b4dca8
--- /dev/null
+++ b/src/com/android/documentsui/base/BooleanConsumer.java
@@ -0,0 +1,22 @@
+/*
+ * 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;
+
+@FunctionalInterface
+public interface BooleanConsumer {
+
+ void accept(boolean value);
+}
diff --git a/src/com/android/documentsui/base/CheckedTask.java b/src/com/android/documentsui/base/CheckedTask.java
index 940763c..2dd6a2a 100644
--- a/src/com/android/documentsui/base/CheckedTask.java
+++ b/src/com/android/documentsui/base/CheckedTask.java
@@ -50,7 +50,7 @@
protected abstract void finish(Output output);
@Override
- final protected void onPreExecute() {
+ protected final void onPreExecute() {
if (mCheck.stop()) {
return;
}
@@ -58,7 +58,7 @@
}
@Override
- final protected Output doInBackground(Input... input) {
+ protected final Output doInBackground(Input... input) {
if (mCheck.stop()) {
return null;
}
@@ -66,7 +66,7 @@
}
@Override
- final protected void onPostExecute(Output result) {
+ protected final void onPostExecute(Output result) {
if (mCheck.stop()) {
return;
}
diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 4684317..6b86e5f 100644
--- a/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -79,6 +79,7 @@
import com.android.documentsui.Snackbars;
import com.android.documentsui.ThumbnailCache;
import com.android.documentsui.base.DocumentInfo;
+import com.android.documentsui.base.DocumentStack;
import com.android.documentsui.base.EventHandler;
import com.android.documentsui.base.EventListener;
import com.android.documentsui.base.Events.InputEvent;
@@ -147,7 +148,7 @@
private FocusManager mFocusManager;
// This dependency is informally "injected" from the owning Activity in our onCreate method.
- private ActionHandler<?> mActionHandler;
+ private ActionHandler mActionHandler;
// This dependency is informally "injected" from the owning Activity in our onCreate method.
private MenuManager mMenuManager;
@@ -671,11 +672,14 @@
mTuner.showChooserForDoc(doc);
}
+ // TODO: Once selection manager is activity owned, move this logic into
+ // a new method: ActionHandler#openWindowOnSelectedDocument
private void openInNewWindow(final Selection selected) {
assert(selected.size() == 1);
DocumentInfo doc =
DocumentInfo.fromDirectoryCursor(mModel.getItem(selected.iterator().next()));
- mTuner.openInNewWindow(getDisplayState().stack, doc);
+ assert(doc != null);
+ mActionHandler.openInNewWindow(new DocumentStack(getDisplayState().stack, doc));
}
private void shareDocuments(final Selection selected) {
diff --git a/src/com/android/documentsui/dirlist/FragmentTuner.java b/src/com/android/documentsui/dirlist/FragmentTuner.java
index 8d77f6e..2451bd2 100644
--- a/src/com/android/documentsui/dirlist/FragmentTuner.java
+++ b/src/com/android/documentsui/dirlist/FragmentTuner.java
@@ -17,7 +17,6 @@
package com.android.documentsui.dirlist;
import com.android.documentsui.base.DocumentInfo;
-import com.android.documentsui.base.DocumentStack;
/**
* Providers support for specializing the DirectoryFragment to the "host" Activity.
@@ -56,10 +55,4 @@
protected void showChooserForDoc(DocumentInfo doc) {
throw new UnsupportedOperationException("Show chooser not supported!");
}
-
- // TODO: Move to action handler.
- @Deprecated
- protected void openInNewWindow(DocumentStack stack, DocumentInfo doc) {
- throw new UnsupportedOperationException("Open in new window not supported!");
- }
}
diff --git a/src/com/android/documentsui/dirlist/UserInputHandler.java b/src/com/android/documentsui/dirlist/UserInputHandler.java
index 71308e4..36d0330 100644
--- a/src/com/android/documentsui/dirlist/UserInputHandler.java
+++ b/src/com/android/documentsui/dirlist/UserInputHandler.java
@@ -44,7 +44,7 @@
private static final String TAG = "UserInputHandler";
- private ActionHandler<?> mActionHandler;
+ private ActionHandler mActionHandler;
private final FocusHandler mFocusHandler;
private final MultiSelectManager mSelectionMgr;
private final Function<MotionEvent, T> mEventConverter;
@@ -61,7 +61,7 @@
private final KeyInputHandler mKeyListener;
public UserInputHandler(
- ActionHandler<?> actionHandler,
+ ActionHandler actionHandler,
FocusHandler focusHandler,
MultiSelectManager selectionMgr,
Function<MotionEvent, T> eventConverter,
diff --git a/src/com/android/documentsui/manager/ActionHandler.java b/src/com/android/documentsui/manager/ActionHandler.java
index 710bc48..c6022f2 100644
--- a/src/com/android/documentsui/manager/ActionHandler.java
+++ b/src/com/android/documentsui/manager/ActionHandler.java
@@ -16,10 +16,15 @@
package com.android.documentsui.manager;
+import android.content.ClipData;
+import android.content.Intent;
+import android.provider.DocumentsContract;
import android.util.Log;
+import com.android.documentsui.AbstractActionHandler;
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.DocumentInfo;
import com.android.documentsui.base.DocumentStack;
@@ -35,36 +40,40 @@
/**
* Provides {@link ManageActivity} action specializations to fragments.
*/
-public class ActionHandler extends com.android.documentsui.ActionHandler<ManageActivity> {
+public class ActionHandler extends AbstractActionHandler<ManageActivity> {
private static final String TAG = "ManagerActionHandler";
private final FragmentTuner mTuner;
+ private final DocumentClipper mClipper;
private final Config mConfig;
- ActionHandler(ManageActivity activity, FragmentTuner tuner) {
+
+ ActionHandler(ManageActivity activity, FragmentTuner tuner, DocumentClipper clipper) {
super(activity);
mTuner = tuner;
+ mClipper = clipper;
mConfig = new Config();
}
@Override
- public void openSettings(RootInfo root) {
- mActivity.openRootSettings(root);
- }
-
- @Override
- public void openInNewWindow(RootInfo root) {
+ public boolean dropOn(ClipData data, RootInfo root) {
new GetRootDocumentTask(
root,
mActivity,
mActivity::isDestroyed,
- (DocumentInfo doc) -> openInNewWindow(root, doc)
+ (DocumentInfo doc) -> mClipper.copyFromClipData(
+ root, doc, data, mActivity.fileOpCallback)
).executeOnExecutor(ProviderExecutor.forAuthority(root.authority));
+ return true;
}
- private void openInNewWindow(RootInfo root, DocumentInfo doc) {
- mActivity.openInNewWindow(new DocumentStack(root), doc);
+ @Override
+ public void openSettings(RootInfo root) {
+ Metrics.logUserAction(mActivity, Metrics.USER_ACTION_SETTINGS);
+ final Intent intent = new Intent(DocumentsContract.ACTION_DOCUMENT_ROOT_SETTINGS);
+ intent.setDataAndType(root.getUri(), DocumentsContract.Root.MIME_TYPE_ITEM);
+ mActivity.startActivity(intent);
}
@Override
@@ -84,6 +93,12 @@
}
@Override
+ public void openRoot(RootInfo root) {
+ Metrics.logRootVisited(mActivity, root);
+ mActivity.onRootPicked(root);
+ }
+
+ @Override
public boolean openDocument(DocumentDetails details) {
DocumentInfo doc = mConfig.model.getDocument(details.getModelId());
if (doc == null) {
diff --git a/src/com/android/documentsui/manager/LauncherActivity.java b/src/com/android/documentsui/manager/LauncherActivity.java
index 542c2c3..8bf3c5c 100644
--- a/src/com/android/documentsui/manager/LauncherActivity.java
+++ b/src/com/android/documentsui/manager/LauncherActivity.java
@@ -98,7 +98,7 @@
startActivity(intent);
}
- static final Intent createLaunchIntent(Activity activity) {
+ public static final Intent createLaunchIntent(Activity activity) {
Intent intent = new Intent(activity, ManageActivity.class);
intent.setData(buildLaunchUri());
diff --git a/src/com/android/documentsui/manager/ManageActivity.java b/src/com/android/documentsui/manager/ManageActivity.java
index 332a3b0..ff82869 100644
--- a/src/com/android/documentsui/manager/ManageActivity.java
+++ b/src/com/android/documentsui/manager/ManageActivity.java
@@ -19,7 +19,6 @@
import static com.android.documentsui.OperationDialogFragment.DIALOG_TYPE_UNKNOWN;
import static com.android.documentsui.base.Shared.DEBUG;
-import android.annotation.Nullable;
import android.app.Activity;
import android.app.FragmentManager;
import android.content.ActivityNotFoundException;
@@ -27,7 +26,6 @@
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Parcelable;
import android.provider.DocumentsContract;
import android.support.design.widget.Snackbar;
import android.support.v7.widget.RecyclerView;
@@ -40,7 +38,6 @@
import com.android.documentsui.DocumentsApplication;
import com.android.documentsui.FocusManager;
import com.android.documentsui.MenuManager.DirectoryDetails;
-import com.android.documentsui.Metrics;
import com.android.documentsui.OperationDialogFragment;
import com.android.documentsui.OperationDialogFragment.DialogType;
import com.android.documentsui.ProviderExecutor;
@@ -102,7 +99,7 @@
mTuner = new Tuner(this, mState);
// Make sure this is done after the RecyclerView and the Model are set up.
mFocusManager = new FocusManager(getColor(R.color.accent_dark));
- mActionHandler = new ActionHandler(this, mTuner);
+ mActionHandler = new ActionHandler(this, mTuner, mClipper);
mClipper = DocumentsApplication.getDocumentClipper(this);
RootsFragment.show(getFragmentManager(), null);
@@ -112,7 +109,7 @@
if (mState.restored) {
if (DEBUG) Log.d(TAG, "Stack already resolved for uri: " + intent.getData());
- } else if (!mState.stack.isEmpty()) {
+ } else if (mState.stack.root != null) {
// If a non-empty stack is present in our state, it was read (presumably)
// from EXTRA_STACK intent extra. In this case, we'll skip other means of
// loading or restoring the stack (like URI).
@@ -124,16 +121,22 @@
//
// Any other URI is *sorta* unexpected...except when browsing an archive
// in downloads.
- if(uri != null
- && uri.getAuthority() != null
- && !uri.equals(mState.stack.peek())
- && !LauncherActivity.isLaunchUri(uri)) {
- if (DEBUG) Log.w(TAG,
- "Launching with non-empty stack. Ignoring unexpected uri: " + uri);
- } else {
- if (DEBUG) Log.d(TAG, "Launching with non-empty stack.");
+ if (DEBUG) {
+ if (uri != null
+ && uri.getAuthority() != null
+ && !uri.equals(mState.stack.peek())
+ && !LauncherActivity.isLaunchUri(uri)) {
+ Log.w(TAG, "Launching with non-empty stack. Ignoring unexpected uri: " + uri);
+ } else {
+ Log.d(TAG, "Launching with non-empty stack.");
+ }
}
- refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE);
+
+ if (!mState.stack.isEmpty()) {
+ refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE);
+ } else {
+ onRootPicked(mState.stack.root);
+ }
} else if (Intent.ACTION_VIEW.equals(intent.getAction())) {
assert(uri != null);
new OpenUriForViewTask(this).executeOnExecutor(
@@ -237,7 +240,7 @@
showCreateDirectoryDialog();
break;
case R.id.menu_new_window:
- openInNewWindow(mState.stack, null);
+ mActionHandler.openInNewWindow(mState.stack);
break;
case R.id.menu_paste_from_clipboard:
DirectoryFragment dir = getDirectoryFragment();
@@ -246,8 +249,7 @@
}
break;
case R.id.menu_settings:
- final RootInfo root = getCurrentRoot();
- openRootSettings(root);
+ mActionHandler.openSettings(getCurrentRoot());
break;
default:
return super.onOptionsItemSelected(item);
@@ -255,35 +257,6 @@
return true;
}
- void openRootSettings(RootInfo root) {
- Metrics.logUserAction(this, Metrics.USER_ACTION_SETTINGS);
- final Intent intent = new Intent(DocumentsContract.ACTION_DOCUMENT_ROOT_SETTINGS);
- intent.setDataAndType(root.getUri(), DocumentsContract.Root.MIME_TYPE_ITEM);
- startActivity(intent);
- }
-
- /**
- * Opens a new window at given location. If doc is null then it opens the stack. If doc is not
- * null it pushes the doc to the stack and opens it.
- */
- public void openInNewWindow(DocumentStack stack, @Nullable DocumentInfo doc) {
- Metrics.logUserAction(this, Metrics.USER_ACTION_NEW_WINDOW);
-
- Intent intent = LauncherActivity.createLaunchIntent(this);
-
- stack = (doc == null) ? stack : new DocumentStack(stack, doc);
- intent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
-
- // With new multi-window mode we have to pick how we are launched.
- // By default we'd be launched in-place above the existing app.
- // By setting launch-to-side ActivityManager will open us to side.
- if (isInMultiWindowMode()) {
- intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
- }
-
- startActivity(intent);
- }
-
@Override
public void refreshDirectory(int anim) {
final FragmentManager fm = getFragmentManager();
diff --git a/src/com/android/documentsui/manager/Tuner.java b/src/com/android/documentsui/manager/Tuner.java
index abf8abb..f783a42 100644
--- a/src/com/android/documentsui/manager/Tuner.java
+++ b/src/com/android/documentsui/manager/Tuner.java
@@ -17,7 +17,6 @@
package com.android.documentsui.manager;
import com.android.documentsui.base.DocumentInfo;
-import com.android.documentsui.base.DocumentStack;
import com.android.documentsui.base.EventListener;
import com.android.documentsui.base.State;
import com.android.documentsui.dirlist.FragmentTuner;
@@ -31,8 +30,6 @@
*/
public final class Tuner extends FragmentTuner {
- private static final String TAG = "ManageTuner";
-
private final ManageActivity mActivity;
private final State mState;
@@ -83,12 +80,6 @@
mActivity.showChooserForDoc(doc);
}
- // TODO: Move to action handler.
- @Override
- public void openInNewWindow(DocumentStack stack, DocumentInfo doc) {
- mActivity.openInNewWindow(stack, doc);
- }
-
Tuner reset(Model model, boolean searchMode) {
mConfig.reset(model, searchMode);
return this;
diff --git a/src/com/android/documentsui/picker/ActionHandler.java b/src/com/android/documentsui/picker/ActionHandler.java
index 0353169..e661ac5 100644
--- a/src/com/android/documentsui/picker/ActionHandler.java
+++ b/src/com/android/documentsui/picker/ActionHandler.java
@@ -22,8 +22,11 @@
import android.provider.Settings;
import android.util.Log;
+import com.android.documentsui.AbstractActionHandler;
import com.android.documentsui.Metrics;
import com.android.documentsui.base.DocumentInfo;
+import com.android.documentsui.base.DocumentStack;
+import com.android.documentsui.base.RootInfo;
import com.android.documentsui.dirlist.DocumentDetails;
import com.android.documentsui.dirlist.FragmentTuner;
import com.android.documentsui.dirlist.Model;
@@ -34,7 +37,7 @@
/**
* Provides {@link PickActivity} action specializations to fragments.
*/
-class ActionHandler extends com.android.documentsui.ActionHandler<PickActivity> {
+class ActionHandler extends AbstractActionHandler<PickActivity> {
private static final String TAG = "PickerActionHandler";
@@ -56,6 +59,20 @@
}
@Override
+ public void openInNewWindow(DocumentStack path) {
+ // Open new window support only depends on vanilla Activity, so it is
+ // implemented in our parent class. But we don't support that in
+ // picking. So as a matter of defensiveness, we override that here.
+ throw new UnsupportedOperationException("Can't open in new window");
+ }
+
+ @Override
+ public void openRoot(RootInfo root) {
+ Metrics.logRootVisited(mActivity, root);
+ mActivity.onRootPicked(root);
+ }
+
+ @Override
public void openRoot(ResolveInfo info) {
Metrics.logAppVisited(mActivity, info);
mActivity.onAppPicked(info);
diff --git a/src/com/android/documentsui/picker/PickActivity.java b/src/com/android/documentsui/picker/PickActivity.java
index 112af76..9756c83 100644
--- a/src/com/android/documentsui/picker/PickActivity.java
+++ b/src/com/android/documentsui/picker/PickActivity.java
@@ -81,8 +81,8 @@
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
+
mTuner = new Tuner(this, mState);
- // Make sure this is done after the RecyclerView and the Model are set up.
mFocusManager = new FocusManager(getColor(R.color.accent_dark));
mMenuManager = new MenuManager(mSearchManager, mState, new DirectoryDetails(this));
mActionHandler = new ActionHandler(this, mTuner);
diff --git a/src/com/android/documentsui/picker/Tuner.java b/src/com/android/documentsui/picker/Tuner.java
index 4804d75..cccb01f 100644
--- a/src/com/android/documentsui/picker/Tuner.java
+++ b/src/com/android/documentsui/picker/Tuner.java
@@ -30,7 +30,6 @@
import com.android.documentsui.dirlist.FragmentTuner;
import com.android.documentsui.dirlist.Model;
import com.android.documentsui.dirlist.Model.Update;
-import com.android.documentsui.dirlist.MultiSelectManager;
import javax.annotation.Nullable;
@@ -135,7 +134,6 @@
private static final class Config {
@Nullable Model model;
- @Nullable MultiSelectManager selectionMgr;
boolean searchMode;
private final EventListener<Update> mModelUpdateListener;
@@ -149,12 +147,10 @@
private boolean modelLoadObserved;
public void reset(Model model, boolean searchMode) {
- this.searchMode = searchMode;
assert(model != null);
- assert(selectionMgr != null);
+ this.searchMode = searchMode;
this.model = model;
- this.selectionMgr = selectionMgr;
model.addUpdateListener(mModelUpdateListener);
modelLoadObserved = false;
diff --git a/src/com/android/documentsui/sidebar/AppItem.java b/src/com/android/documentsui/sidebar/AppItem.java
index 4af4022..d13b475 100644
--- a/src/com/android/documentsui/sidebar/AppItem.java
+++ b/src/com/android/documentsui/sidebar/AppItem.java
@@ -24,8 +24,8 @@
import android.widget.ImageView;
import android.widget.TextView;
-import com.android.documentsui.R;
import com.android.documentsui.ActionHandler;
+import com.android.documentsui.R;
/**
* An {@link Item} for apps that supports some picking actions like
@@ -36,9 +36,9 @@
public final ResolveInfo info;
- private final ActionHandler<?> mActionHandler;
+ private final ActionHandler mActionHandler;
- public AppItem(ResolveInfo info, ActionHandler<?> actionHandler) {
+ public AppItem(ResolveInfo info, ActionHandler actionHandler) {
super(R.layout.item_root, getStringId(info));
this.info = info;
diff --git a/src/com/android/documentsui/sidebar/EjectRootTask.java b/src/com/android/documentsui/sidebar/EjectRootTask.java
index 1f486ff..2ce0658 100644
--- a/src/com/android/documentsui/sidebar/EjectRootTask.java
+++ b/src/com/android/documentsui/sidebar/EjectRootTask.java
@@ -18,50 +18,45 @@
import android.content.ContentProviderClient;
import android.content.ContentResolver;
-import android.content.Context;
import android.net.Uri;
+import android.os.AsyncTask;
import android.provider.DocumentsContract;
import android.util.Log;
import com.android.documentsui.DocumentsApplication;
-import com.android.documentsui.base.CheckedTask;
+import com.android.documentsui.base.BooleanConsumer;
import com.android.documentsui.base.Shared;
-import java.util.function.BooleanSupplier;
-import java.util.function.Consumer;
+public final class EjectRootTask extends AsyncTask<Void, Void, Boolean> {
-public final class EjectRootTask extends CheckedTask<Void, Boolean> {
+ private final ContentResolver mResolver;
private final String mAuthority;
private final String mRootId;
- private final Consumer<Boolean> mCallback;
- private Context mContext;
+ private final BooleanConsumer mCallback;
/**
* @param ejectCanceledCheck The method reference we use to see whether eject should be stopped
* at any point
* @param finishCallback The end callback necessary when the eject task finishes
*/
- public EjectRootTask(Context context,
+ public EjectRootTask(
+ ContentResolver resolver,
String authority,
String rootId,
- BooleanSupplier ejectCanceledCheck,
- Consumer<Boolean> finishCallback) {
- super(ejectCanceledCheck::getAsBoolean);
+ BooleanConsumer finishCallback) {
+ mResolver = resolver;
mAuthority = authority;
mRootId = rootId;
- mContext = context;
mCallback = finishCallback;
}
@Override
- protected Boolean run(Void... params) {
- final ContentResolver resolver = mContext.getContentResolver();
-
+ protected Boolean doInBackground(Void... args) {
Uri rootUri = DocumentsContract.buildRootUri(mAuthority, mRootId);
ContentProviderClient client = null;
try {
client = DocumentsApplication.acquireUnstableProviderOrThrow(
- resolver, mAuthority);
+ mResolver, mAuthority);
return DocumentsContract.ejectRoot(client, rootUri);
} catch (Exception e) {
Log.w(Shared.TAG, "Failed to eject root", e);
@@ -73,7 +68,7 @@
}
@Override
- protected void finish(Boolean ejected) {
+ protected void onPostExecute(Boolean ejected) {
mCallback.accept(ejected);
}
}
\ No newline at end of file
diff --git a/src/com/android/documentsui/sidebar/RootItem.java b/src/com/android/documentsui/sidebar/RootItem.java
index 30984ec..4410b46 100644
--- a/src/com/android/documentsui/sidebar/RootItem.java
+++ b/src/com/android/documentsui/sidebar/RootItem.java
@@ -31,7 +31,6 @@
import com.android.documentsui.ActionHandler;
import com.android.documentsui.MenuManager;
import com.android.documentsui.R;
-import com.android.documentsui.base.CheckedTask.Check;
import com.android.documentsui.base.RootInfo;
/**
@@ -42,9 +41,9 @@
public final RootInfo root;
- private final ActionHandler<?> mActionHandler;
+ private final ActionHandler mActionHandler;
- public RootItem(RootInfo root, ActionHandler<?> actionHandler) {
+ public RootItem(RootInfo root, ActionHandler actionHandler) {
super(R.layout.item_root, getStringId(root));
this.root = root;
mActionHandler = actionHandler;
diff --git a/src/com/android/documentsui/sidebar/RootsFragment.java b/src/com/android/documentsui/sidebar/RootsFragment.java
index 9e81ed0..1cbed8e 100644
--- a/src/com/android/documentsui/sidebar/RootsFragment.java
+++ b/src/com/android/documentsui/sidebar/RootsFragment.java
@@ -51,6 +51,8 @@
import com.android.documentsui.DocumentsApplication;
import com.android.documentsui.ItemDragListener;
import com.android.documentsui.R;
+import com.android.documentsui.base.BooleanConsumer;
+import com.android.documentsui.base.DocumentStack;
import com.android.documentsui.base.Events;
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.base.Shared;
@@ -64,7 +66,6 @@
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
-import java.util.function.Consumer;
/**
* Display list of known storage backend roots.
@@ -107,7 +108,7 @@
private ListView mList;
private RootsAdapter mAdapter;
private LoaderCallbacks<Collection<RootInfo>> mCallbacks;
- private ActionHandler<?> mActionHandler;
+ private ActionHandler mActionHandler;
public static RootsFragment show(FragmentManager fm, Intent includeApps) {
final Bundle args = new Bundle();
@@ -374,7 +375,7 @@
ejectClicked(ejectIcon, rootItem.root, mActionHandler);
return true;
case R.id.menu_open_in_new_window:
- mActionHandler.openInNewWindow(rootItem.root);
+ mActionHandler.openInNewWindow(new DocumentStack(rootItem.root));
return true;
case R.id.menu_paste_into_folder:
mActionHandler.pasteIntoFolder(rootItem.root);
@@ -388,19 +389,24 @@
}
}
- static void ejectClicked(View ejectIcon, RootInfo root, ActionHandler<?> actionHandler) {
+ static void ejectClicked(View ejectIcon, RootInfo root, ActionHandler actionHandler) {
assert(ejectIcon != null);
assert(!root.ejecting);
ejectIcon.setEnabled(false);
root.ejecting = true;
actionHandler.ejectRoot(
root,
- () -> ejectIcon.getVisibility() != View.VISIBLE,
- new Consumer<Boolean>() {
+ new BooleanConsumer() {
@Override
- public void accept(Boolean ejected) {
- ejectIcon.setEnabled(!ejected);
+ public void accept(boolean ejected) {
+ // Event if ejected is false, we should reset, since the op failed.
+ // Either way, we are no longer attempting to eject the device.
root.ejecting = false;
+
+ // If the view is still visible, we update its state.
+ if (ejectIcon.getVisibility() == View.VISIBLE) {
+ ejectIcon.setEnabled(!ejected);
+ }
}
});
}
diff --git a/tests/common/com/android/documentsui/testing/Roots.java b/tests/common/com/android/documentsui/testing/Roots.java
new file mode 100644
index 0000000..576301a
--- /dev/null
+++ b/tests/common/com/android/documentsui/testing/Roots.java
@@ -0,0 +1,30 @@
+/*
+ * 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.testing;
+
+import com.android.documentsui.base.RootInfo;
+
+public final class Roots {
+
+ private Roots() {}
+
+ public static RootInfo create(String id) {
+ RootInfo root = new RootInfo();
+ root.authority = "test-authority";
+ root.rootId = id;
+ return root;
+ }
+}
diff --git a/tests/common/com/android/documentsui/testing/TestActionHandler.java b/tests/common/com/android/documentsui/testing/TestActionHandler.java
index 6914127..c617a43 100644
--- a/tests/common/com/android/documentsui/testing/TestActionHandler.java
+++ b/tests/common/com/android/documentsui/testing/TestActionHandler.java
@@ -16,11 +16,11 @@
package com.android.documentsui.testing;
-import com.android.documentsui.ActionHandler;
-import com.android.documentsui.BaseActivity;
+import com.android.documentsui.AbstractActionHandler;
+import com.android.documentsui.base.RootInfo;
import com.android.documentsui.dirlist.DocumentDetails;
-public class TestActionHandler extends ActionHandler<BaseActivity> {
+public class TestActionHandler extends AbstractActionHandler {
public final TestEventHandler<DocumentDetails> open = new TestEventHandler<>();
public final TestEventHandler<DocumentDetails> view = new TestEventHandler<>();
@@ -44,4 +44,9 @@
public boolean previewDocument(DocumentDetails doc) {
return preview.accept(doc);
}
+
+ @Override
+ public void openRoot(RootInfo root) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/tests/common/com/android/documentsui/testing/TestActivity.java b/tests/common/com/android/documentsui/testing/TestActivity.java
new file mode 100644
index 0000000..8cdc7d5
--- /dev/null
+++ b/tests/common/com/android/documentsui/testing/TestActivity.java
@@ -0,0 +1,49 @@
+/*
+ * 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.testing;
+
+import android.app.Activity;
+import android.content.Intent;
+
+import junit.framework.Assert;
+
+import org.mockito.Mockito;
+
+import javax.annotation.Nullable;
+
+public abstract class TestActivity extends Activity {
+
+ private @Nullable Intent mLastStarted;
+
+ public static TestActivity create() {
+ return Mockito.mock(TestActivity.class);
+ }
+
+ @Override
+ public String getPackageName() {
+ return "TestActivity";
+ }
+
+ @Override
+ public void startActivity(Intent intent) {
+ mLastStarted = intent;
+ }
+
+ public void assertStarted(Intent expected) {
+ Assert.assertEquals(expected, mLastStarted);
+ }
+}
diff --git a/tests/common/com/android/documentsui/testing/TestConsumer.java b/tests/common/com/android/documentsui/testing/TestConsumer.java
new file mode 100644
index 0000000..ce89292
--- /dev/null
+++ b/tests/common/com/android/documentsui/testing/TestConsumer.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.testing;
+
+import com.android.documentsui.base.EventHandler;
+
+import java.util.function.Consumer;
+
+/**
+ * Test {@link EventHandler} that can be used to spy on, control responses from,
+ * and make assertions against values tested.
+ */
+public class TestConsumer<T> extends TestPredicate<T> implements Consumer<T> {
+
+ @Override
+ public void accept(T event) {
+ test(event);
+ }
+}
diff --git a/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java b/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java
new file mode 100644
index 0000000..c6c66d0
--- /dev/null
+++ b/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.content.Intent;
+import android.os.Parcelable;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.documentsui.base.DocumentStack;
+import com.android.documentsui.base.RootInfo;
+import com.android.documentsui.base.Shared;
+import com.android.documentsui.dirlist.DocumentDetails;
+import com.android.documentsui.manager.LauncherActivity;
+import com.android.documentsui.testing.Roots;
+import com.android.documentsui.testing.TestActivity;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class AbstractActionHandlerTest {
+
+ private TestActivity mActivity;
+ private AbstractActionHandler<TestActivity> mHandler;
+
+ @Before
+ public void setUp() {
+ mActivity = TestActivity.create();
+ mHandler = new AbstractActionHandler<TestActivity>(mActivity) {
+
+ @Override
+ public void openRoot(RootInfo root) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean openDocument(DocumentDetails doc) {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ @Test
+ public void testOpenNewWindow() {
+ DocumentStack path = new DocumentStack(Roots.create("123"));
+ mHandler.openInNewWindow(path);
+
+ Intent expected = LauncherActivity.createLaunchIntent(mActivity);
+ expected.putExtra(Shared.EXTRA_STACK, (Parcelable) path);
+ mActivity.assertStarted(expected);
+ }
+}