Merge "Move openContainerDocument() into ActionHandlers." into nyc-andromeda-dev
diff --git a/src/com/android/documentsui/DirectoryLoader.java b/src/com/android/documentsui/DirectoryLoader.java
index d2c7d10..56e76bb 100644
--- a/src/com/android/documentsui/DirectoryLoader.java
+++ b/src/com/android/documentsui/DirectoryLoader.java
@@ -20,9 +20,11 @@
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
+import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.CancellationSignal;
+import android.os.Handler;
import android.os.OperationCanceledException;
import android.os.RemoteException;
import android.provider.DocumentsContract.Document;
@@ -42,8 +44,7 @@
private static final String[] SEARCH_REJECT_MIMES = new String[] { Document.MIME_TYPE_DIR };
- private final ForceLoadContentObserver mObserver = new ForceLoadContentObserver();
-
+ private final LockingContentObserver mObserver;
private final RootInfo mRoot;
private final Uri mUri;
private final SortModel mModel;
@@ -59,6 +60,7 @@
DocumentInfo doc,
Uri uri,
SortModel model,
+ DirectoryReloadLock lock,
boolean inSearchMode) {
super(context, ProviderExecutor.forAuthority(root.authority));
@@ -67,6 +69,7 @@
mModel = model;
mDoc = doc;
mSearchMode = inSearchMode;
+ mObserver = new LockingContentObserver(lock, this::onContentChanged);
}
@Override
@@ -181,4 +184,25 @@
getContext().getContentResolver().unregisterContentObserver(mObserver);
}
+
+ private static final class LockingContentObserver extends ContentObserver {
+ private final DirectoryReloadLock mLock;
+ private final Runnable mContentChangedCallback;
+
+ public LockingContentObserver(DirectoryReloadLock lock, Runnable contentChangedCallback) {
+ super(new Handler());
+ mLock = lock;
+ mContentChangedCallback = contentChangedCallback;
+ }
+
+ @Override
+ public boolean deliverSelfNotifications() {
+ return true;
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ mLock.tryUpdate(mContentChangedCallback);
+ }
+ }
}
diff --git a/src/com/android/documentsui/DirectoryReloadLock.java b/src/com/android/documentsui/DirectoryReloadLock.java
new file mode 100644
index 0000000..8033bb7
--- /dev/null
+++ b/src/com/android/documentsui/DirectoryReloadLock.java
@@ -0,0 +1,67 @@
+/*
+ * 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.MainThread;
+import android.annotation.Nullable;
+
+import com.android.documentsui.base.Shared;
+import com.android.documentsui.selection.BandController;
+
+/**
+ * A lock used by {@link DirectoryLoader} and {@link BandController} to ensure refresh is blocked
+ * while Band Selection is active.
+ */
+public final class DirectoryReloadLock {
+ private int mPauseCount = 0;
+ private @Nullable Runnable mCallback;
+
+ /**
+ * Increment the block count by 1
+ */
+ @MainThread
+ public void block() {
+ Shared.checkMainLoop();
+ mPauseCount++;
+ }
+
+ /**
+ * Decrement the block count by 1; If no other object is trying to block and there exists some
+ * callback, that callback will be run
+ */
+ @MainThread
+ public void unblock() {
+ Shared.checkMainLoop();
+ mPauseCount--;
+ if (mPauseCount == 0 && mCallback != null) {
+ mCallback.run();
+ mCallback = null;
+ }
+ }
+
+ /**
+ * Attempts to run the given Runnable if not-blocked, or else the Runnable is set to be ran next
+ * (replacing any previous set Runnables).
+ */
+ public void tryUpdate(Runnable update) {
+ if (mPauseCount == 0) {
+ update.run();
+ } else {
+ mCallback = update;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 3adb01e..e230311 100644
--- a/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -67,6 +67,7 @@
import com.android.documentsui.BaseActivity.RetainedState;
import com.android.documentsui.DirectoryLoader;
import com.android.documentsui.DirectoryResult;
+import com.android.documentsui.DirectoryReloadLock;
import com.android.documentsui.DocumentsApplication;
import com.android.documentsui.FocusManager;
import com.android.documentsui.ItemDragListener;
@@ -186,6 +187,7 @@
private View mProgressBar;
private DirectoryState mLocalState;
+ private DirectoryReloadLock mReloadLock = new DirectoryReloadLock();
// Note, we use !null to indicate that selection was restored (from rotation).
// So don't fiddle with this field unless you've got the bigger picture in mind.
@@ -307,13 +309,14 @@
mSelectionMetadata = new SelectionMetadata(mModel::getItem);
mSelectionMgr.addItemCallback(mSelectionMetadata);
- GestureSelector gestureSel = GestureSelector.create(mSelectionMgr, mRecView);
+ GestureSelector gestureSel = GestureSelector.create(mSelectionMgr, mRecView, mReloadLock);
if (mState.allowMultiple) {
mBandController = new BandController(
mRecView,
mAdapter,
mSelectionMgr,
+ mReloadLock,
(int pos) -> {
// The band selection model only operates on documents and directories.
// Exclude other types of adapter items like whitespace and dividers.
@@ -637,11 +640,10 @@
// Model must be accessed in UI thread, since underlying cursor is not threadsafe.
List<DocumentInfo> docs = mModel.getDocuments(selected);
- BaseActivity activity = mActivity;
if (docs.size() > 1) {
- activity.onDocumentsPicked(docs);
+ mActivity.onDocumentsPicked(docs);
} else {
- activity.onDocumentPicked(docs.get(0));
+ mActivity.onDocumentPicked(docs.get(0));
}
}
@@ -1232,6 +1234,7 @@
mLocalState.mDocument,
contentsUri,
mState.sortModel,
+ mReloadLock,
mLocalState.mSearchMode);
case TYPE_RECENT_OPEN:
diff --git a/src/com/android/documentsui/dirlist/Model.java b/src/com/android/documentsui/dirlist/Model.java
index ea51e91..8bdf079 100644
--- a/src/com/android/documentsui/dirlist/Model.java
+++ b/src/com/android/documentsui/dirlist/Model.java
@@ -59,6 +59,8 @@
&& (flags & Document.FLAG_PARTIAL) == 0;
};
+ private static final Predicate<Cursor> ANY_FILE_FILTER = (Cursor c) -> true;
+
private static final String TAG = "Model";
private boolean mIsLoading;
@@ -204,19 +206,7 @@
}
public List<DocumentInfo> getDocuments(Selection selection) {
- final int size = (selection != null) ? selection.size() : 0;
-
- final List<DocumentInfo> docs = new ArrayList<>(size);
- // NOTE: That as this now iterates over only final (non-provisional) selection.
- for (String modelId: selection) {
- DocumentInfo doc = getDocument(modelId);
- if (doc == null) {
- Log.w(TAG, "Unable to obtain document for modelId: " + modelId);
- continue;
- }
- docs.add(doc);
- }
- return docs;
+ return loadDocuments(selection, ANY_FILE_FILTER);
}
public @Nullable DocumentInfo getDocument(String modelId) {
diff --git a/src/com/android/documentsui/selection/BandController.java b/src/com/android/documentsui/selection/BandController.java
index a255e52..b6bca11 100644
--- a/src/com/android/documentsui/selection/BandController.java
+++ b/src/com/android/documentsui/selection/BandController.java
@@ -33,6 +33,7 @@
import android.util.SparseIntArray;
import android.view.View;
+import com.android.documentsui.DirectoryReloadLock;
import com.android.documentsui.R;
import com.android.documentsui.base.Events.InputEvent;
import com.android.documentsui.dirlist.DocumentsAdapter;
@@ -61,6 +62,7 @@
private final SelectionEnvironment mEnvironment;
private final DocumentsAdapter mAdapter;
private final SelectionManager mSelectionManager;
+ private final DirectoryReloadLock mLock;
private final Runnable mViewScroller;
private final GridModel.OnSelectionChangedListener mGridListener;
@@ -75,16 +77,19 @@
final RecyclerView view,
DocumentsAdapter adapter,
SelectionManager selectionManager,
+ DirectoryReloadLock lock,
IntPredicate gridItemTester) {
- this(new RuntimeSelectionEnvironment(view), adapter, selectionManager, gridItemTester);
+ this(new RuntimeSelectionEnvironment(view), adapter, selectionManager, lock, gridItemTester);
}
private BandController(
SelectionEnvironment env,
DocumentsAdapter adapter,
SelectionManager selectionManager,
+ DirectoryReloadLock lock,
IntPredicate gridItemTester) {
+ mLock = lock;
selectionManager.bindContoller(this);
mEnvironment = env;
@@ -265,6 +270,7 @@
private void startBandSelect(Point origin) {
if (DEBUG) Log.d(TAG, "Starting band select @ " + origin);
+ mLock.block();
mOrigin = origin;
mModelBuilder.run(); // Creates a new selection model.
mModel.startSelection(mOrigin);
@@ -314,6 +320,7 @@
mModel = null;
mOrigin = null;
+ mLock.unblock();
}
private void onSelectionChanged(Set<String> updatedSelection) {
diff --git a/src/com/android/documentsui/selection/GestureSelector.java b/src/com/android/documentsui/selection/GestureSelector.java
index 08afac5..959080c 100644
--- a/src/com/android/documentsui/selection/GestureSelector.java
+++ b/src/com/android/documentsui/selection/GestureSelector.java
@@ -21,6 +21,7 @@
import android.support.v7.widget.RecyclerView;
import android.view.View;
+import com.android.documentsui.DirectoryReloadLock;
import com.android.documentsui.base.Events.InputEvent;
import com.android.documentsui.ui.ViewAutoScroller;
import com.android.documentsui.ui.ViewAutoScroller.ScrollActionDelegate;
@@ -40,6 +41,7 @@
private final Runnable mDragScroller;
private final IntSupplier mHeight;
private final ViewFinder mViewFinder;
+ private final DirectoryReloadLock mLock;
private int mLastStartedItemPos = -1;
private boolean mStarted = false;
private Point mLastInterceptedPoint;
@@ -48,10 +50,12 @@
SelectionManager selectionMgr,
IntSupplier heightSupplier,
ViewFinder viewFinder,
- ScrollActionDelegate actionDelegate) {
+ ScrollActionDelegate actionDelegate,
+ DirectoryReloadLock lock) {
mSelectionMgr = selectionMgr;
mHeight = heightSupplier;
mViewFinder = viewFinder;
+ mLock = lock;
ScrollDistanceDelegate distanceDelegate = new ScrollDistanceDelegate() {
@Override
@@ -75,7 +79,8 @@
public static GestureSelector create(
SelectionManager selectionMgr,
- RecyclerView scrollView) {
+ RecyclerView scrollView,
+ DirectoryReloadLock lock) {
ScrollActionDelegate actionDelegate = new ScrollActionDelegate() {
@Override
public void scrollBy(int dy) {
@@ -97,7 +102,8 @@
selectionMgr,
scrollView::getHeight,
scrollView::findChildViewUnder,
- actionDelegate);
+ actionDelegate,
+ lock);
return helper;
}
@@ -162,6 +168,8 @@
mLastInterceptedPoint = e.getOrigin();
if (mStarted) {
mSelectionMgr.startRangeSelection(mLastStartedItemPos);
+ // Gesture Selection about to start
+ mLock.block();
return true;
}
return false;
@@ -173,6 +181,7 @@
mLastStartedItemPos = -1;
mStarted = false;
mSelectionMgr.getSelection().applyProvisionalSelection();
+ mLock.unblock();
return false;
}
diff --git a/tests/unit/com/android/documentsui/DirectoryReloadLockTest.java b/tests/unit/com/android/documentsui/DirectoryReloadLockTest.java
new file mode 100644
index 0000000..dbbc9c6
--- /dev/null
+++ b/tests/unit/com/android/documentsui/DirectoryReloadLockTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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 static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DirectoryReloadLockTest {
+
+ private DirectoryReloadLock lock = new DirectoryReloadLock();
+ private boolean called;
+ private Runnable callback = () -> {
+ called = true;
+ };
+
+ @Before
+ public void setUp() {
+ called = false;
+ }
+
+ @Test
+ public void testNotBlocking_callbackNotBlocked() {
+ lock.tryUpdate(callback);
+ assertTrue(called);
+ }
+
+ @Test
+ public void testToggleBlocking_callbackNotBlocked() {
+ lock.block();
+ lock.unblock();
+ lock.tryUpdate(callback);
+ assertTrue(called);
+ }
+
+ @Test
+ public void testBlocking_callbackBlocked() {
+ lock.block();
+ lock.tryUpdate(callback);
+ assertFalse(called);
+ }
+
+ @Test
+ public void testBlockthenUnblock_callbackNotBlocked() {
+ lock.block();
+ lock.tryUpdate(callback);
+ lock.unblock();
+ assertTrue(called);
+ }
+}