On-demand fetch for DocumentInfo when right-click on a root.
RootInfo has information regarding the whole root, but not scoped
narrowly enough for just the top-level document. We need the top level
DocumentInfo to know specifics such as is it writable. This does a fetch
of that when right clicking on a root, and defaults to a timeout of 1000
ms if it takes too long to answer back for whatever reason (Provider
went bad/non-responding, etc).
Bug: 31662523
Change-Id: I2278ea0171fb63a5783f9be619732c857558a1b6
diff --git a/src/com/android/documentsui/GetRootDocumentTask.java b/src/com/android/documentsui/GetRootDocumentTask.java
index 79468ba..e56b110 100644
--- a/src/com/android/documentsui/GetRootDocumentTask.java
+++ b/src/com/android/documentsui/GetRootDocumentTask.java
@@ -33,13 +33,14 @@
* {@link DocumentInfo} of its root document and call supplied callback to handle the
* {@link DocumentInfo}.
*/
-public class GetRootDocumentTask extends CheckedTask<Void, DocumentInfo> {
+public class GetRootDocumentTask extends TimeoutTask<Void, DocumentInfo> {
private final static String TAG = "GetRootDocumentTask";
private final RootInfo mRootInfo;
private final Context mContext;
private final Consumer<DocumentInfo> mCallback;
+ private boolean mForceCallback;
public GetRootDocumentTask(
RootInfo rootInfo, Activity activity, Consumer<DocumentInfo> callback) {
@@ -59,6 +60,10 @@
mCallback = callback;
}
+ public void setForceCallback(boolean forceCallback) {
+ mForceCallback = forceCallback;
+ }
+
@Override
public @Nullable DocumentInfo run(Void... rootInfo) {
return mRootInfo.getRootDocumentBlocking(mContext);
@@ -66,10 +71,13 @@
@Override
public void finish(@Nullable DocumentInfo documentInfo) {
- if (documentInfo != null) {
+ if (documentInfo == null) {
+ Log.e(TAG,
+ "Cannot find document info for root: " + mRootInfo + " in the given timeout");
+ }
+
+ if (documentInfo != null || mForceCallback) {
mCallback.accept(documentInfo);
- } else {
- Log.e(TAG, "Cannot find document info for root: " + mRootInfo);
}
}
}
diff --git a/src/com/android/documentsui/MenuManager.java b/src/com/android/documentsui/MenuManager.java
index 56f4214..c6e9514 100644
--- a/src/com/android/documentsui/MenuManager.java
+++ b/src/com/android/documentsui/MenuManager.java
@@ -22,6 +22,7 @@
import android.view.MenuItem;
import android.view.View;
+import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.Menus;
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.base.Shared;
@@ -181,14 +182,14 @@
/**
* @see RootsFragment#onCreateContextMenu
*/
- public void updateRootContextMenu(Menu menu, RootInfo root) {
+ public void updateRootContextMenu(Menu menu, RootInfo root, DocumentInfo docInfo) {
MenuItem eject = menu.findItem(R.id.menu_eject_root);
MenuItem pasteInto = menu.findItem(R.id.menu_paste_into_folder);
MenuItem openInNewWindow = menu.findItem(R.id.menu_open_in_new_window);
MenuItem settings = menu.findItem(R.id.menu_settings);
updateEject(eject, root);
- updatePasteInto(pasteInto, root);
+ updatePasteInto(pasteInto, root, docInfo);
updateOpenInNewWindow(openInNewWindow, root);
updateSettings(settings, root);
}
@@ -262,7 +263,7 @@
pasteInto.setVisible(false);
}
- protected void updatePasteInto(MenuItem pasteInto, RootInfo root) {
+ protected void updatePasteInto(MenuItem pasteInto, RootInfo root, DocumentInfo docInfo) {
pasteInto.setVisible(false);
}
diff --git a/src/com/android/documentsui/TimeoutTask.java b/src/com/android/documentsui/TimeoutTask.java
new file mode 100644
index 0000000..1d36b48
--- /dev/null
+++ b/src/com/android/documentsui/TimeoutTask.java
@@ -0,0 +1,60 @@
+/*
+ * 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.annotation.CallSuper;
+import android.os.AsyncTask;
+import android.os.Handler;
+
+import com.android.documentsui.base.CheckedTask;
+import com.android.documentsui.base.DocumentInfo;
+
+/**
+ * A {@link CheckedTask} that takes and query SAF to obtain the
+ * {@link DocumentInfo} of its root document and call supplied callback to handle the
+ * {@link DocumentInfo}.
+ */
+public abstract class TimeoutTask<Input, Output> extends CheckedTask<Input, Output> {
+ private static final int DEFAULT_TIMEOUT = -1;
+
+ private long mTimeout = DEFAULT_TIMEOUT;
+
+ public TimeoutTask(Check check) {
+ super(check);
+ }
+
+ public void setTimeout(long timeout) {
+ mTimeout = timeout;
+ }
+
+ @CallSuper
+ @Override
+ protected void prepare() {
+ if (mTimeout < 0) {
+ return;
+ }
+
+ Handler handler = new Handler();
+ handler.postDelayed(() -> {
+ if (getStatus() == AsyncTask.Status.RUNNING) {
+ cancel(true);
+ this.finish(null);
+ }
+ }, mTimeout);
+ }
+
+}
diff --git a/src/com/android/documentsui/manager/MenuManager.java b/src/com/android/documentsui/manager/MenuManager.java
index bc81c3e..f898b6d 100644
--- a/src/com/android/documentsui/manager/MenuManager.java
+++ b/src/com/android/documentsui/manager/MenuManager.java
@@ -24,6 +24,7 @@
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;
@@ -145,9 +146,11 @@
}
@Override
- protected void updatePasteInto(MenuItem pasteInto, RootInfo root) {
- // TODO(b/31658763): Check the root document as well.
- pasteInto.setEnabled(root.supportsCreate() && mDirDetails.hasItemsToPaste());
+ protected void updatePasteInto(MenuItem pasteInto, RootInfo root, DocumentInfo docInfo) {
+ pasteInto.setEnabled(root.supportsCreate()
+ && docInfo != null
+ && docInfo.isCreateSupported()
+ && mDirDetails.hasItemsToPaste());
}
@Override
diff --git a/src/com/android/documentsui/sidebar/RootItem.java b/src/com/android/documentsui/sidebar/RootItem.java
index 4410b46..97ec89f 100644
--- a/src/com/android/documentsui/sidebar/RootItem.java
+++ b/src/com/android/documentsui/sidebar/RootItem.java
@@ -16,6 +16,7 @@
package com.android.documentsui.sidebar;
+import android.annotation.Nullable;
import android.content.ClipData;
import android.content.Context;
import android.provider.DocumentsProvider;
@@ -31,6 +32,7 @@
import com.android.documentsui.ActionHandler;
import com.android.documentsui.MenuManager;
import com.android.documentsui.R;
+import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.RootInfo;
/**
@@ -40,6 +42,7 @@
private static final String STRING_ID_FORMAT = "RootItem{%s/%s}";
public final RootInfo root;
+ public @Nullable DocumentInfo docInfo;
private final ActionHandler mActionHandler;
@@ -110,6 +113,6 @@
@Override
void createContextMenu(Menu menu, MenuInflater inflater, MenuManager menuManager) {
inflater.inflate(R.menu.root_context_menu, menu);
- menuManager.updateRootContextMenu(menu, root);
+ menuManager.updateRootContextMenu(menu, root, docInfo);
}
}
diff --git a/src/com/android/documentsui/sidebar/RootsFragment.java b/src/com/android/documentsui/sidebar/RootsFragment.java
index 1cbed8e..7b9c99c 100644
--- a/src/com/android/documentsui/sidebar/RootsFragment.java
+++ b/src/com/android/documentsui/sidebar/RootsFragment.java
@@ -29,7 +29,9 @@
import android.content.Loader;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.Handler;
import android.util.Log;
import android.view.ContextMenu;
import android.view.DragEvent;
@@ -49,9 +51,11 @@
import com.android.documentsui.ActionHandler;
import com.android.documentsui.BaseActivity;
import com.android.documentsui.DocumentsApplication;
+import com.android.documentsui.GetRootDocumentTask;
import com.android.documentsui.ItemDragListener;
import com.android.documentsui.R;
import com.android.documentsui.base.BooleanConsumer;
+import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.DocumentStack;
import com.android.documentsui.base.Events;
import com.android.documentsui.base.RootInfo;
@@ -74,6 +78,7 @@
private static final String TAG = "RootsFragment";
private static final String EXTRA_INCLUDE_APPS = "includeApps";
+ private static final int CONTEXT_MENU_ITEM_TIMEOUT = 500;
private final OnDragListener mDragListener = new ItemDragListener<RootsFragment>(this) {
@Override
@@ -143,21 +148,53 @@
// All other motion events will then get passed to OnItemClickListener.
mList.setOnGenericMotionListener(
new OnGenericMotionListener() {
- @Override
- public boolean onGenericMotion(View v, MotionEvent event) {
- if (Events.isMouseEvent(event)
- && event.getButtonState() == MotionEvent.BUTTON_SECONDARY) {
- getBaseActivity().getMenuManager().showContextMenu(
- RootsFragment.this, v, event.getX(), event.getY());
- return true;
- }
- return false;
+ @Override
+ public boolean onGenericMotion(View v, MotionEvent event) {
+ if (Events.isMouseEvent(event)
+ && event.getButtonState() == MotionEvent.BUTTON_SECONDARY) {
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+ return onRightClick(v, x, y, () -> {
+ getBaseActivity().getMenuManager()
+ .showContextMenu(RootsFragment.this, v, x, y);
+ });
+ }
+ return false;
}
});
mList.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
return view;
}
+ private boolean onRightClick(View v, int x, int y, Runnable callback) {
+ int pos = mList.pointToPosition(x, y);
+ final Item item = mAdapter.getItem(pos);
+ if (!(item instanceof RootItem)) {
+ return false;
+ }
+ final RootItem rootItem = (RootItem) item;
+
+ if (!rootItem.root.supportsCreate()) {
+ // If a read-only root, no need to see if top level is writable (it's not)
+ callback.run();
+ return true;
+ }
+ // We need to start a GetRootDocumentTask so we can know whether items can be directly
+ // pasted into root
+ GetRootDocumentTask task = new GetRootDocumentTask(
+ rootItem.root,
+ getBaseActivity(),
+ (DocumentInfo doc) -> {
+ rootItem.docInfo = doc;
+ callback.run();
+ });
+ task.setTimeout(CONTEXT_MENU_ITEM_TIMEOUT);
+ task.setForceCallback(true);
+ task.executeOnExecutor(getBaseActivity().getExecutorForCurrentDirectory());
+
+ return true;
+ }
+
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
diff --git a/tests/unit/com/android/documentsui/manager/MenuManagerTest.java b/tests/unit/com/android/documentsui/manager/MenuManagerTest.java
index 8049203..cd1c247 100644
--- a/tests/unit/com/android/documentsui/manager/MenuManagerTest.java
+++ b/tests/unit/com/android/documentsui/manager/MenuManagerTest.java
@@ -17,14 +17,15 @@
package com.android.documentsui.manager;
import static junit.framework.Assert.assertEquals;
-
import static org.junit.Assert.assertTrue;
+import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import com.android.documentsui.R;
+import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.base.State;
import com.android.documentsui.testing.TestDirectoryDetails;
@@ -65,6 +66,7 @@
private TestDirectoryDetails dirDetails;
private TestSearchViewManager testSearchManager;
private RootInfo testRootInfo;
+ private DocumentInfo testDocInfo;
private State state = new State();
private MenuManager mgr;
@@ -100,6 +102,7 @@
mgr = new MenuManager(testSearchManager, state, dirDetails);
testRootInfo = new RootInfo();
+ testDocInfo = new DocumentInfo();
}
@Test
@@ -364,7 +367,7 @@
public void testRootContextMenu() {
testRootInfo.flags = Root.FLAG_SUPPORTS_CREATE;
- mgr.updateRootContextMenu(testMenu, testRootInfo);
+ mgr.updateRootContextMenu(testMenu, testRootInfo, testDocInfo);
eject.assertInvisible();
@@ -381,7 +384,7 @@
@Test
public void testRootContextMenu_hasRootSettings() {
testRootInfo.flags = Root.FLAG_HAS_SETTINGS;
- mgr.updateRootContextMenu(testMenu, testRootInfo);
+ mgr.updateRootContextMenu(testMenu, testRootInfo, testDocInfo);
settings.assertEnabled();
}
@@ -389,7 +392,7 @@
@Test
public void testRootContextMenu_nonWritableRoot() {
dirDetails.hasItemsToPaste = true;
- mgr.updateRootContextMenu(testMenu, testRootInfo);
+ mgr.updateRootContextMenu(testMenu, testRootInfo, testDocInfo);
pasteInto.assertVisible();
pasteInto.assertDisabled();
@@ -398,17 +401,29 @@
@Test
public void testRootContextMenu_nothingToPaste() {
testRootInfo.flags = Root.FLAG_SUPPORTS_CREATE;
+ testDocInfo.flags = Document.FLAG_DIR_SUPPORTS_CREATE;
dirDetails.hasItemsToPaste = false;
- mgr.updateRootContextMenu(testMenu, testRootInfo);
+ mgr.updateRootContextMenu(testMenu, testRootInfo, testDocInfo);
pasteInto.assertVisible();
pasteInto.assertDisabled();
}
@Test
+ public void testRootContextMenu_pasteIntoWritableRoot() {
+ testRootInfo.flags = Root.FLAG_SUPPORTS_CREATE;
+ testDocInfo.flags = Document.FLAG_DIR_SUPPORTS_CREATE;
+ dirDetails.hasItemsToPaste = true;
+ mgr.updateRootContextMenu(testMenu, testRootInfo, testDocInfo);
+
+ pasteInto.assertVisible();
+ pasteInto.assertEnabled();
+ }
+
+ @Test
public void testRootContextMenu_eject() {
testRootInfo.flags = Root.FLAG_SUPPORTS_EJECT;
- mgr.updateRootContextMenu(testMenu, testRootInfo);
+ mgr.updateRootContextMenu(testMenu, testRootInfo, testDocInfo);
eject.assertEnabled();
}
@@ -417,7 +432,7 @@
public void testRootContextMenu_ejectInProcess() {
testRootInfo.flags = Root.FLAG_SUPPORTS_EJECT;
testRootInfo.ejecting = true;
- mgr.updateRootContextMenu(testMenu, testRootInfo);
+ mgr.updateRootContextMenu(testMenu, testRootInfo, testDocInfo);
eject.assertDisabled();
}
diff --git a/tests/unit/com/android/documentsui/picker/MenuManagerTest.java b/tests/unit/com/android/documentsui/picker/MenuManagerTest.java
index f23e0ac..bf9f5cf 100644
--- a/tests/unit/com/android/documentsui/picker/MenuManagerTest.java
+++ b/tests/unit/com/android/documentsui/picker/MenuManagerTest.java
@@ -18,7 +18,6 @@
import static com.android.documentsui.base.State.ACTION_CREATE;
import static com.android.documentsui.base.State.ACTION_OPEN;
-
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -27,6 +26,7 @@
import android.support.test.runner.AndroidJUnit4;
import com.android.documentsui.R;
+import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.base.State;
import com.android.documentsui.testing.TestDirectoryDetails;
@@ -67,6 +67,7 @@
private TestSearchViewManager testSearchManager;
private State state = new State();
private RootInfo testRootInfo;
+ private DocumentInfo testDocInfo;
private MenuManager mgr;
@Before
@@ -97,6 +98,7 @@
mgr = new MenuManager(testSearchManager, state, dirDetails);
testRootInfo = new RootInfo();
+ testDocInfo = new DocumentInfo();
state.action = ACTION_CREATE;
state.allowMultiple = true;
}
@@ -258,7 +260,7 @@
@Test
public void testRootContextMenu() {
- mgr.updateRootContextMenu(testMenu, testRootInfo);
+ mgr.updateRootContextMenu(testMenu, testRootInfo, testDocInfo);
eject.assertInvisible();
openInNewWindow.assertInvisible();
@@ -269,7 +271,7 @@
@Test
public void testRootContextMenu_hasRootSettings() {
testRootInfo.flags = Root.FLAG_HAS_SETTINGS;
- mgr.updateRootContextMenu(testMenu, testRootInfo);
+ mgr.updateRootContextMenu(testMenu, testRootInfo, testDocInfo);
settings.assertInvisible();
}
@@ -277,7 +279,7 @@
@Test
public void testRootContextMenu_nonWritableRoot() {
dirDetails.hasItemsToPaste = true;
- mgr.updateRootContextMenu(testMenu, testRootInfo);
+ mgr.updateRootContextMenu(testMenu, testRootInfo, testDocInfo);
pasteInto.assertInvisible();
}
@@ -286,7 +288,7 @@
public void testRootContextMenu_nothingToPaste() {
testRootInfo.flags = Root.FLAG_SUPPORTS_CREATE;
dirDetails.hasItemsToPaste = false;
- mgr.updateRootContextMenu(testMenu, testRootInfo);
+ mgr.updateRootContextMenu(testMenu, testRootInfo, testDocInfo);
pasteInto.assertInvisible();
}
@@ -294,7 +296,7 @@
@Test
public void testRootContextMenu_canEject() {
testRootInfo.flags = Root.FLAG_SUPPORTS_EJECT;
- mgr.updateRootContextMenu(testMenu, testRootInfo);
+ mgr.updateRootContextMenu(testMenu, testRootInfo, testDocInfo);
eject.assertInvisible();
}