summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Tony Huang <tonyychuang@google.com> 2019-08-15 14:25:10 +0800
committer Tony Huang <tonyychuang@google.com> 2019-08-27 03:32:41 +0000
commit4d179b7a41545c87b113e4c331fc8141f72b07ca (patch)
tree1c8a14a0ab9e59ae129a6dcaa00f71190b1db169
parent1ccfd4f49ce7b890ce8989d457d7f8ace28c4802 (diff)
Make Recent root know contents changed
Add ContentObserver on each cursor return from different providers to make Recent root observe files are downloaded or photos are taken. Fix: 139446177 Test: manual Test: atest DocumentsUIGoogleTests Change-Id: I158d99a480ef25b9b52ce2e050d4a2a1ff3085d1
-rw-r--r--src/com/android/documentsui/AbstractActionHandler.java10
-rw-r--r--src/com/android/documentsui/DirectoryLoader.java28
-rw-r--r--src/com/android/documentsui/LockingContentObserver.java53
-rw-r--r--src/com/android/documentsui/MultiRootDocumentsLoader.java18
-rw-r--r--tests/common/com/android/documentsui/testing/TestCursor.java30
-rw-r--r--tests/common/com/android/documentsui/testing/TestDocumentsProvider.java2
-rw-r--r--tests/unit/com/android/documentsui/RecentsLoaderTests.java29
7 files changed, 138 insertions, 32 deletions
diff --git a/src/com/android/documentsui/AbstractActionHandler.java b/src/com/android/documentsui/AbstractActionHandler.java
index 423565d78..824d2090e 100644
--- a/src/com/android/documentsui/AbstractActionHandler.java
+++ b/src/com/android/documentsui/AbstractActionHandler.java
@@ -576,11 +576,15 @@ public abstract class AbstractActionHandler<T extends FragmentActivity & CommonA
Context context = mActivity;
if (mState.stack.isRecents()) {
+ final LockingContentObserver observer = new LockingContentObserver(
+ mContentLock, AbstractActionHandler.this::loadDocumentsForCurrentStack);
+ MultiRootDocumentsLoader loader;
+
if (mSearchMgr.isSearching()) {
if (DEBUG) {
Log.d(TAG, "Creating new GlobalSearchLoader.");
}
- return new GlobalSearchLoader(
+ loader = new GlobalSearchLoader(
context,
mProviders,
mState,
@@ -591,13 +595,15 @@ public abstract class AbstractActionHandler<T extends FragmentActivity & CommonA
if (DEBUG) {
Log.d(TAG, "Creating new loader recents.");
}
- return new RecentsLoader(
+ loader = new RecentsLoader(
context,
mProviders,
mState,
mExecutors,
mInjector.fileTypeLookup);
}
+ loader.setObserver(observer);
+ return loader;
} else {
Uri contentsUri = mSearchMgr.isSearching()
? DocumentsContract.buildSearchDocumentsUri(
diff --git a/src/com/android/documentsui/DirectoryLoader.java b/src/com/android/documentsui/DirectoryLoader.java
index d65b4c353..63c3bf66a 100644
--- a/src/com/android/documentsui/DirectoryLoader.java
+++ b/src/com/android/documentsui/DirectoryLoader.java
@@ -16,20 +16,16 @@
package com.android.documentsui;
-import static com.android.documentsui.base.SharedMinimal.DEBUG;
import static com.android.documentsui.base.SharedMinimal.VERBOSE;
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.Bundle;
import android.os.CancellationSignal;
import android.os.FileUtils;
-import android.os.Handler;
-import android.os.Looper;
import android.os.OperationCanceledException;
import android.os.RemoteException;
import android.provider.DocumentsContract.Document;
@@ -241,28 +237,4 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
getContext().getContentResolver().unregisterContentObserver(mObserver);
}
-
- private static final class LockingContentObserver extends ContentObserver {
- private final ContentLock mLock;
- private final Runnable mContentChangedCallback;
-
- public LockingContentObserver(ContentLock lock, Runnable contentChangedCallback) {
- super(new Handler(Looper.getMainLooper()));
- mLock = lock;
- mContentChangedCallback = contentChangedCallback;
- }
-
- @Override
- public boolean deliverSelfNotifications() {
- return true;
- }
-
- @Override
- public void onChange(boolean selfChange) {
- if (DEBUG) {
- Log.d(TAG, "Directory content updated.");
- }
- mLock.runWhenUnlocked(mContentChangedCallback);
- }
- }
}
diff --git a/src/com/android/documentsui/LockingContentObserver.java b/src/com/android/documentsui/LockingContentObserver.java
new file mode 100644
index 000000000..bbd7a98cd
--- /dev/null
+++ b/src/com/android/documentsui/LockingContentObserver.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 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 com.android.documentsui.base.SharedMinimal.DEBUG;
+import static com.android.documentsui.base.SharedMinimal.TAG;
+
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+/**
+ * A custom {@link ContentObserver} which constructed by a {@link ContentLock}
+ * and a {@link Runnable} callback. It will callback when it's onChange and ContentLock is unlock.
+ */
+public final class LockingContentObserver extends ContentObserver {
+ private final ContentLock mLock;
+ private final Runnable mContentChangedCallback;
+
+ public LockingContentObserver(ContentLock lock, Runnable contentChangedCallback) {
+ super(new Handler(Looper.getMainLooper()));
+ mLock = lock;
+ mContentChangedCallback = contentChangedCallback;
+ }
+
+ @Override
+ public boolean deliverSelfNotifications() {
+ return true;
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ if (DEBUG) {
+ Log.d(TAG, "Content updated.");
+ }
+ mLock.runWhenUnlocked(mContentChangedCallback);
+ }
+}
diff --git a/src/com/android/documentsui/MultiRootDocumentsLoader.java b/src/com/android/documentsui/MultiRootDocumentsLoader.java
index 777efe0b6..11208c16b 100644
--- a/src/com/android/documentsui/MultiRootDocumentsLoader.java
+++ b/src/com/android/documentsui/MultiRootDocumentsLoader.java
@@ -17,7 +17,6 @@
package com.android.documentsui;
import static com.android.documentsui.base.SharedMinimal.DEBUG;
-import static com.android.documentsui.base.SharedMinimal.TAG;
import android.app.ActivityManager;
import android.content.ContentProviderClient;
@@ -65,6 +64,9 @@ import java.util.concurrent.TimeUnit;
* and return the combined result.
*/
public abstract class MultiRootDocumentsLoader extends AsyncTaskLoader<DirectoryResult> {
+
+ private static final String TAG = "MultiRootDocsLoader";
+
// TODO: clean up cursor ownership so background thread doesn't traverse
// previously returned cursors for filtering/sorting; this currently races
// with the UI thread.
@@ -83,6 +85,7 @@ public abstract class MultiRootDocumentsLoader extends AsyncTaskLoader<Directory
private final ProvidersAccess mProviders;
private final Lookup<String, Executor> mExecutors;
private final Lookup<String, String> mFileTypeMap;
+ private LockingContentObserver mObserver;
@GuardedBy("mTasks")
/** A authority -> QueryTask map */
@@ -101,6 +104,8 @@ public abstract class MultiRootDocumentsLoader extends AsyncTaskLoader<Directory
* @param state current state
* @param executors the executors of authorities
* @param fileTypeMap the map of mime types and file types.
+ * @param lock the selection lock
+ * @param contentChangedCallback callback when content changed
*/
public MultiRootDocumentsLoader(Context context, ProvidersAccess providers, State state,
Lookup<String, Executor> executors, Lookup<String, String> fileTypeMap) {
@@ -126,6 +131,10 @@ public abstract class MultiRootDocumentsLoader extends AsyncTaskLoader<Directory
}
}
+ public void setObserver(LockingContentObserver observer) {
+ mObserver = observer;
+ }
+
private DirectoryResult loadInBackgroundLocked() {
if (mFirstPassLatch == null) {
// First time through we kick off all the recent tasks, and wait
@@ -322,6 +331,10 @@ public abstract class MultiRootDocumentsLoader extends AsyncTaskLoader<Directory
FileUtils.closeQuietly(mResult);
mResult = null;
+
+ if (mObserver != null) {
+ getContext().getContentResolver().unregisterContentObserver(mObserver);
+ }
}
// TODO: create better transfer of ownership around cursor to ensure its
@@ -406,6 +419,9 @@ public abstract class MultiRootDocumentsLoader extends AsyncTaskLoader<Directory
mState.sortModel.addQuerySortArgs(queryArgs);
addQueryArgs(queryArgs);
res[i] = client.query(uri, null, queryArgs, null);
+ if (mObserver != null) {
+ res[i].registerContentObserver(mObserver);
+ }
mCursors[i] = generateResultCursor(rootInfos.get(i), res[i]);
} catch (Exception e) {
Log.w(TAG, "Failed to load " + authority + ", " + rootInfos.get(i).rootId,
diff --git a/tests/common/com/android/documentsui/testing/TestCursor.java b/tests/common/com/android/documentsui/testing/TestCursor.java
new file mode 100644
index 000000000..56f42695a
--- /dev/null
+++ b/tests/common/com/android/documentsui/testing/TestCursor.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 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.database.MatrixCursor;
+
+public class TestCursor extends MatrixCursor {
+
+ public TestCursor(String[] columnNames) {
+ super(columnNames);
+ }
+
+ public void mockOnChange() {
+ onChange(false);
+ }
+}
diff --git a/tests/common/com/android/documentsui/testing/TestDocumentsProvider.java b/tests/common/com/android/documentsui/testing/TestDocumentsProvider.java
index 794d1c9fe..3e4dffd49 100644
--- a/tests/common/com/android/documentsui/testing/TestDocumentsProvider.java
+++ b/tests/common/com/android/documentsui/testing/TestDocumentsProvider.java
@@ -117,7 +117,7 @@ public class TestDocumentsProvider extends DocumentsProvider {
}
private Cursor createDocumentsCursor(DocumentInfo... docs) {
- MatrixCursor cursor = new MatrixCursor(DOCUMENTS_PROJECTION);
+ TestCursor cursor = new TestCursor(DOCUMENTS_PROJECTION);
for (DocumentInfo doc : docs) {
cursor.newRow()
.add(Document.COLUMN_DOCUMENT_ID, doc.documentId)
diff --git a/tests/unit/com/android/documentsui/RecentsLoaderTests.java b/tests/unit/com/android/documentsui/RecentsLoaderTests.java
index f044b92e1..73ba36f3c 100644
--- a/tests/unit/com/android/documentsui/RecentsLoaderTests.java
+++ b/tests/unit/com/android/documentsui/RecentsLoaderTests.java
@@ -29,6 +29,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.State;
import com.android.documentsui.testing.ActivityManagers;
+import com.android.documentsui.testing.TestCursor;
import com.android.documentsui.testing.TestEnv;
import com.android.documentsui.testing.TestFileTypeLookup;
import com.android.documentsui.testing.TestImmediateExecutor;
@@ -38,6 +39,9 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
@RunWith(AndroidJUnit4.class)
@MediumTest
public class RecentsLoaderTests {
@@ -45,6 +49,7 @@ public class RecentsLoaderTests {
private TestEnv mEnv;
private TestActivity mActivity;
private RecentsLoader mLoader;
+ private boolean mContentChanged;
@Before
public void setUp() {
@@ -105,4 +110,28 @@ public class RecentsLoaderTests {
assertEquals(0, flags & Document.FLAG_SUPPORTS_MOVE);
}
}
+
+ @Test
+ public void testContentsUpdate_observable() throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+ Runnable callback = () -> {
+ latch.countDown();
+ mContentChanged = true;
+ };
+ mLoader.setObserver(new LockingContentObserver(new ContentLock(), callback));
+
+ final DocumentInfo doc = mEnv.model.createFile("freddy.jpg");
+ doc.lastModified = System.currentTimeMillis();
+ mEnv.mockProviders.get(TestProvidersAccess.HOME.authority)
+ .setNextRecentDocumentsReturns(doc);
+
+ mLoader.loadInBackground();
+
+ TestCursor c = (TestCursor) mEnv.mockProviders.get(TestProvidersAccess.HOME.authority)
+ .queryRecentDocuments(null, null);
+ c.mockOnChange();
+
+ latch.await(1, TimeUnit.SECONDS);
+ assertTrue(mContentChanged);
+ }
}