diff options
author | 2019-08-15 14:25:10 +0800 | |
---|---|---|
committer | 2019-08-27 03:32:41 +0000 | |
commit | 4d179b7a41545c87b113e4c331fc8141f72b07ca (patch) | |
tree | 1c8a14a0ab9e59ae129a6dcaa00f71190b1db169 | |
parent | 1ccfd4f49ce7b890ce8989d457d7f8ace28c4802 (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
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); + } } |