Lift loader to activity level.
Also fix a bug that leaves DocumentsUI in a weird state if it fails to
obtain root document.
Change-Id: Ibb67bfd0114f45f41c0000078ca56767b5a4542b
Tests: Manual tests and auto tests.
Bug: 35934082
diff --git a/src/com/android/documentsui/AbstractActionHandler.java b/src/com/android/documentsui/AbstractActionHandler.java
index 9e69c09..d4e964c 100644
--- a/src/com/android/documentsui/AbstractActionHandler.java
+++ b/src/com/android/documentsui/AbstractActionHandler.java
@@ -18,12 +18,17 @@
import static com.android.documentsui.base.DocumentInfo.getCursorInt;
import static com.android.documentsui.base.DocumentInfo.getCursorString;
+import static com.android.documentsui.base.Shared.DEBUG;
import android.app.Activity;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.Context;
import android.content.Intent;
+import android.content.Loader;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Parcelable;
import android.provider.DocumentsContract;
import android.support.annotation.VisibleForTesting;
@@ -44,7 +49,6 @@
import com.android.documentsui.dirlist.AnimationView.AnimationType;
import com.android.documentsui.dirlist.DocumentDetails;
import com.android.documentsui.dirlist.FocusHandler;
-import com.android.documentsui.dirlist.Model;
import com.android.documentsui.files.LauncherActivity;
import com.android.documentsui.queries.SearchViewManager;
import com.android.documentsui.roots.LoadRootTask;
@@ -66,6 +70,9 @@
public abstract class AbstractActionHandler<T extends Activity & CommonAddons>
implements ActionHandler {
+ @VisibleForTesting
+ static final int LOADER_ID = 42;
+
private static final String TAG = "AbstractActionHandler";
private static final int REFRESH_SPINNER_TIMEOUT = 500;
@@ -79,8 +86,12 @@
protected final Lookup<String, Executor> mExecutors;
protected final Injector mInjector;
+ private final LoaderBindings mBindings;
+
private Runnable mDisplayStateChangedListener;
+ private DirectoryReloadLock mDirectoryReloadLock;
+
@Override
public void registerDisplayStateChangedListener(Runnable l) {
mDisplayStateChangedListener = l;
@@ -117,6 +128,8 @@
mSearchMgr = searchMgr;
mExecutors = executors;
mInjector = injector;
+
+ mBindings = new LoaderBindings();
}
@Override
@@ -243,6 +256,18 @@
}
@Override
+ public void openRootDocument(@Nullable DocumentInfo rootDoc) {
+ if (rootDoc == null) {
+ // There are 2 cases where rootDoc is null -- 1) loading recents; 2) failed to load root
+ // document. Either case we should call refreshCurrentRootAndDirectory() to let
+ // DirectoryFragment update UI.
+ mActivity.refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE);
+ } else {
+ openContainerDocument(rootDoc);
+ }
+ }
+
+ @Override
public void openContainerDocument(DocumentInfo doc) {
assert(doc.isContainer());
@@ -346,6 +371,22 @@
.executeOnExecutor(mExecutors.lookup(uri.getAuthority()));
}
+ @Override
+ public void loadDocumentsForCurrentStack() {
+ DocumentStack stack = mState.stack;
+ if (!stack.isRecents() && stack.isEmpty()) {
+ DirectoryResult result = new DirectoryResult();
+
+ // TODO (b/35996595): Consider plumbing through the actual exception, though it might
+ // not be very useful (always pointing to DatabaseUtils#readExceptionFromParcel()).
+ result.exception = new IllegalStateException("Failed to load root document.");
+ mInjector.getModel().update(result);
+ return;
+ }
+
+ mActivity.getLoaderManager().restartLoader(LOADER_ID, null, mBindings);
+ }
+
protected final boolean launchToDocument(Uri uri) {
// We don't support launching to a document in an archive.
if (!Providers.isArchiveUri(uri)) {
@@ -385,6 +426,65 @@
return mSelectionMgr.getSelection(new Selection());
}
+ public ActionHandler reset(DirectoryReloadLock reloadLock) {
+ mDirectoryReloadLock = reloadLock;
+ mActivity.getLoaderManager().destroyLoader(LOADER_ID);
+ return this;
+ }
+
+ private final class LoaderBindings implements LoaderCallbacks<DirectoryResult> {
+
+ @Override
+ public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) {
+ Context context = mActivity;
+
+ if (mState.stack.isRecents()) {
+
+ if (DEBUG) Log.d(TAG, "Creating new loader recents.");
+ return new RecentsLoader(context, mRoots, mState, mInjector.features);
+
+ } else {
+
+ Uri contentsUri = mSearchMgr.isSearching()
+ ? DocumentsContract.buildSearchDocumentsUri(
+ mState.stack.getRoot().authority,
+ mState.stack.getRoot().rootId,
+ mSearchMgr.getCurrentSearch())
+ : DocumentsContract.buildChildDocumentsUri(
+ mState.stack.peek().authority,
+ mState.stack.peek().documentId);
+
+ if (mInjector.config.managedModeEnabled(mState.stack)) {
+ contentsUri = DocumentsContract.setManageMode(contentsUri);
+ }
+
+ if (DEBUG) Log.d(TAG,
+ "Creating new directory loader for: "
+ + DocumentInfo.debugString(mState.stack.peek()));
+
+ return new DirectoryLoader(
+ context,
+ mState.stack.getRoot(),
+ mState.stack.peek(),
+ contentsUri,
+ mState.sortModel,
+ mDirectoryReloadLock,
+ mSearchMgr.isSearching());
+ }
+ }
+
+ @Override
+ public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
+ if (DEBUG) Log.d(TAG, "Loader has finished for: "
+ + DocumentInfo.debugString(mState.stack.peek()));
+ assert(result != null);
+
+ mInjector.getModel().update(result);
+ }
+
+ @Override
+ public void onLoaderReset(Loader<DirectoryResult> loader) {}
+ }
/**
* A class primarily for the support of isolating our tests
* from our concrete activity implementations.
diff --git a/src/com/android/documentsui/ActionHandler.java b/src/com/android/documentsui/ActionHandler.java
index 7b0eef4..33f1d7d 100644
--- a/src/com/android/documentsui/ActionHandler.java
+++ b/src/com/android/documentsui/ActionHandler.java
@@ -27,7 +27,6 @@
import com.android.documentsui.base.DocumentStack;
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.dirlist.DocumentDetails;
-import com.android.documentsui.dirlist.Model;
import javax.annotation.Nullable;
@@ -84,6 +83,8 @@
void showChooserForDoc(DocumentInfo doc);
+ void openRootDocument(@Nullable DocumentInfo rootDoc);
+
void openContainerDocument(DocumentInfo doc);
void cutToClipboard();
@@ -107,9 +108,11 @@
void registerDisplayStateChangedListener(Runnable l);
void unregisterDisplayStateChangedListener(Runnable l);
+ void loadDocumentsForCurrentStack();
+
/**
* Allow action handler to be initialized in a new scope.
- * @return
+ * @return this
*/
- <T extends ActionHandler> T reset(Model model);
+ <T extends ActionHandler> T reset(DirectoryReloadLock reloadLock);
}
diff --git a/src/com/android/documentsui/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java
index 05365d5..d49908b 100644
--- a/src/com/android/documentsui/BaseActivity.java
+++ b/src/com/android/documentsui/BaseActivity.java
@@ -22,7 +22,6 @@
import android.app.Activity;
import android.app.Fragment;
-import android.app.FragmentManager;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -32,7 +31,6 @@
import android.os.Bundle;
import android.os.MessageQueue.IdleHandler;
import android.provider.DocumentsContract;
-import android.provider.DocumentsContract.Root;
import android.support.annotation.CallSuper;
import android.support.annotation.LayoutRes;
import android.support.annotation.Nullable;
@@ -52,7 +50,6 @@
import com.android.documentsui.base.State.ViewMode;
import com.android.documentsui.dirlist.AnimationView;
import com.android.documentsui.dirlist.DirectoryFragment;
-import com.android.documentsui.dirlist.DocumentsAdapter;
import com.android.documentsui.prefs.LocalPreferences;
import com.android.documentsui.prefs.PreferencesMonitor;
import com.android.documentsui.queries.DebugCommandProcessor;
@@ -155,7 +152,11 @@
*/
@Override
public void onSearchChanged(@Nullable String query) {
- reloadSearch(query);
+ if (query != null) {
+ Metrics.logUserAction(BaseActivity.this, Metrics.USER_ACTION_SEARCH);
+ }
+
+ mInjector.actions.loadDocumentsForCurrentStack();
}
@Override
@@ -302,7 +303,7 @@
new GetRootDocumentTask(
root,
this,
- mInjector.actions::openContainerDocument)
+ mInjector.actions::openRootDocument)
.executeOnExecutor(getExecutorForCurrentDirectory());
}
}
@@ -405,14 +406,6 @@
invalidateOptionsMenu();
}
- private void reloadSearch(String query) {
- FragmentManager fm = getFragmentManager();
- RootInfo root = getCurrentRoot();
- DocumentInfo cwd = getCurrentDirectory();
-
- DirectoryFragment.reloadSearch(fm, root, cwd, query);
- }
-
private final List<String> getExcludedAuthorities() {
List<String> authorities = new ArrayList<>();
if (getIntent().getBooleanExtra(DocumentsContract.EXTRA_EXCLUDE_SELF, false)) {
diff --git a/src/com/android/documentsui/DirectoryLoader.java b/src/com/android/documentsui/DirectoryLoader.java
index 0a4dff1..4fbb3ff 100644
--- a/src/com/android/documentsui/DirectoryLoader.java
+++ b/src/com/android/documentsui/DirectoryLoader.java
@@ -29,6 +29,7 @@
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
+import android.os.Looper;
import android.os.OperationCanceledException;
import android.os.RemoteException;
import android.provider.DocumentsContract.Document;
@@ -220,7 +221,7 @@
private final Runnable mContentChangedCallback;
public LockingContentObserver(DirectoryReloadLock lock, Runnable contentChangedCallback) {
- super(new Handler());
+ super(new Handler(Looper.getMainLooper()));
mLock = lock;
mContentChangedCallback = contentChangedCallback;
}
diff --git a/src/com/android/documentsui/DragShadowBuilder.java b/src/com/android/documentsui/DragShadowBuilder.java
index 2bc6dde..3ba09d0 100644
--- a/src/com/android/documentsui/DragShadowBuilder.java
+++ b/src/com/android/documentsui/DragShadowBuilder.java
@@ -30,7 +30,6 @@
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.Shared;
import com.android.documentsui.dirlist.IconHelper;
-import com.android.documentsui.dirlist.Model;
import com.android.documentsui.selection.Selection;
import java.util.List;
diff --git a/src/com/android/documentsui/FocusManager.java b/src/com/android/documentsui/FocusManager.java
index 8126e4d..fe41aa9 100644
--- a/src/com/android/documentsui/FocusManager.java
+++ b/src/com/android/documentsui/FocusManager.java
@@ -47,8 +47,7 @@
import com.android.documentsui.dirlist.DocumentHolder;
import com.android.documentsui.dirlist.DocumentsAdapter;
import com.android.documentsui.dirlist.FocusHandler;
-import com.android.documentsui.dirlist.Model;
-import com.android.documentsui.dirlist.Model.Update;
+import com.android.documentsui.Model.Update;
import com.android.documentsui.selection.SelectionManager;
import java.util.ArrayList;
diff --git a/src/com/android/documentsui/Injector.java b/src/com/android/documentsui/Injector.java
index 0a3f5bb..8f22d22 100644
--- a/src/com/android/documentsui/Injector.java
+++ b/src/com/android/documentsui/Injector.java
@@ -27,12 +27,13 @@
import com.android.documentsui.base.EventHandler;
import com.android.documentsui.base.Features;
import com.android.documentsui.dirlist.DocumentsAdapter;
-import com.android.documentsui.dirlist.Model;
import com.android.documentsui.prefs.ScopedPreferences;
+import com.android.documentsui.queries.SearchViewManager;
import com.android.documentsui.selection.SelectionManager;
import com.android.documentsui.selection.SelectionManager.SelectionPredicate;
import com.android.documentsui.ui.DialogController;
import com.android.documentsui.ui.MessageBuilder;
+import com.android.internal.annotations.VisibleForTesting;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@@ -49,6 +50,7 @@
public MenuManager menuManager;
public DialogController dialogs;
+ public SearchViewManager searchManager;
@ContentScoped
public ActionModeController actionModeController;
@@ -72,14 +74,24 @@
ScopedPreferences prefs,
MessageBuilder messages,
DialogController dialogs) {
+ this(features, config, prefs, messages, dialogs, new Model(features));
+ }
+
+ @VisibleForTesting
+ public Injector(
+ Features features,
+ ActivityConfig config,
+ ScopedPreferences prefs,
+ MessageBuilder messages,
+ DialogController dialogs,
+ Model model) {
this.features = features;
this.config = config;
this.prefs = prefs;
this.messages = messages;
this.dialogs = dialogs;
-
- mModel = new Model(this.features);
+ this.mModel = model;
}
public Model getModel() {
@@ -101,14 +113,22 @@
return actionModeController.reset(selectionDetails, menuItemClicker, view);
}
- public T getActionHandler(@Nullable Model model) {
+ /**
+ * Obtains action handler and resets it if necessary.
+ * @param reloadLock the lock held by {@link com.android.documentsui.selection.BandController}
+ * to prevent loader from updating result during band selection. May be
+ * {@code null} if called from
+ * {@link com.android.documentsui.sidebar.RootsFragment}.
+ * @return the action handler
+ */
+ public T getActionHandler(@Nullable DirectoryReloadLock reloadLock) {
// provide our friend, RootsFragment, early access to this special feature!
- if (model == null) {
+ if (reloadLock == null) {
return actions;
}
- return actions.reset(model);
+ return actions.reset(reloadLock);
}
/**
diff --git a/src/com/android/documentsui/dirlist/Model.java b/src/com/android/documentsui/Model.java
similarity index 96%
rename from src/com/android/documentsui/dirlist/Model.java
rename to src/com/android/documentsui/Model.java
index 43aed9a..9cc6972 100644
--- a/src/com/android/documentsui/dirlist/Model.java
+++ b/src/com/android/documentsui/Model.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2017 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.documentsui.dirlist;
+package com.android.documentsui;
import static com.android.documentsui.base.DocumentInfo.getCursorString;
import static com.android.documentsui.base.Shared.DEBUG;
@@ -58,11 +58,12 @@
private static final String TAG = "Model";
- @Nullable String info;
- @Nullable String error;
- @Nullable DocumentInfo doc;
+ public @Nullable String info;
+ public @Nullable String error;
+ public @Nullable DocumentInfo doc;
private final Features mFeatures;
+
/** Maps Model ID to cursor positions, for looking up items by Model ID. */
private final Map<String, Integer> mPositions = new HashMap<>();
private final Set<String> mFileNames = new HashSet<>();
@@ -98,7 +99,7 @@
}
}
- void reset() {
+ public void reset() {
mCursor = null;
mCursorCount = 0;
mIds = new String[0];
@@ -111,7 +112,8 @@
notifyUpdateListeners();
}
- void update(DirectoryResult result) {
+ @VisibleForTesting
+ protected void update(DirectoryResult result) {
assert(result != null);
if (DEBUG) Log.i(TAG, "Updating model with new result set.");
@@ -139,7 +141,7 @@
}
@VisibleForTesting
- int getItemCount() {
+ public int getItemCount() {
return mCursorCount;
}
@@ -200,7 +202,7 @@
return mCursorCount == 0;
}
- boolean isLoading() {
+ public boolean isLoading() {
return mIsLoading;
}
diff --git a/src/com/android/documentsui/base/DocumentInfo.java b/src/com/android/documentsui/base/DocumentInfo.java
index 006553b..3f35ba3 100644
--- a/src/com/android/documentsui/base/DocumentInfo.java
+++ b/src/com/android/documentsui/base/DocumentInfo.java
@@ -272,6 +272,10 @@
return (flags & Document.FLAG_VIRTUAL_DOCUMENT) != 0;
}
+ public boolean prefersSortByLastModified() {
+ return (flags & Document.FLAG_DIR_PREFERS_LAST_MODIFIED) != 0;
+ }
+
@Override
public int hashCode() {
return derivedUri.hashCode() + mimeType.hashCode();
diff --git a/src/com/android/documentsui/base/DocumentStack.java b/src/com/android/documentsui/base/DocumentStack.java
index 1423666..9aa15c2 100644
--- a/src/com/android/documentsui/base/DocumentStack.java
+++ b/src/com/android/documentsui/base/DocumentStack.java
@@ -168,7 +168,7 @@
}
public boolean isRecents() {
- return isEmpty();
+ return mRoot != null && mRoot.isRecents();
}
public void updateRoot(Collection<RootInfo> matchingRoots) throws FileNotFoundException {
diff --git a/src/com/android/documentsui/base/Shared.java b/src/com/android/documentsui/base/Shared.java
index 7616edb..0e24eda 100644
--- a/src/com/android/documentsui/base/Shared.java
+++ b/src/com/android/documentsui/base/Shared.java
@@ -77,11 +77,6 @@
public static final String EXTRA_STATE = "state";
/**
- * Extra flag used to store type of DirectoryFragment's type ResultType type in the bundle.
- */
- public static final String EXTRA_TYPE = "type";
-
- /**
* Extra flag used to store root of type RootInfo in the bundle.
*/
public static final String EXTRA_ROOT = "root";
@@ -97,11 +92,6 @@
public static final String EXTRA_SELECTION = "selection";
/**
- * Extra flag used to store DirectoryFragment's search mode of boolean type in the bundle.
- */
- public static final String EXTRA_SEARCH_MODE = "searchMode";
-
- /**
* Extra flag used to store DirectoryFragment's ignore state of boolean type in the bundle.
*/
public static final String EXTRA_IGNORE_STATE = "ignoreState";
diff --git a/src/com/android/documentsui/clipping/DocumentClipper.java b/src/com/android/documentsui/clipping/DocumentClipper.java
index 1e307ce..0c1b59c 100644
--- a/src/com/android/documentsui/clipping/DocumentClipper.java
+++ b/src/com/android/documentsui/clipping/DocumentClipper.java
@@ -254,7 +254,7 @@
*/
public void copyFromClipData(
final RootInfo root,
- final DocumentInfo destination,
+ final @Nullable DocumentInfo destination,
final @Nullable ClipData clipData,
final FileOperations.Callback callback) {
DocumentStack dstStack = new DocumentStack(root, destination);
@@ -271,7 +271,7 @@
* @param callback callback to notify when operation finishes
*/
public void copyFromClipData(
- final DocumentInfo destination,
+ final @Nullable DocumentInfo destination,
final DocumentStack docStack,
final @Nullable ClipData clipData,
final FileOperations.Callback callback) {
@@ -283,7 +283,7 @@
/**
* Copies documents from given clip data to a folder.
*
- * @param docStack the document stack to the destination folder, including the destination
+ * @param dstStack the document stack to the destination folder, including the destination
* folder.
* @param clipData the clipData to copy from
* @param callback callback to notify when operation finishes
@@ -340,12 +340,8 @@
*
* @return true if the list of files can be copied to destination.
*/
- private static boolean canCopy(DocumentInfo dest) {
- if (dest == null || !dest.isDirectory() || !dest.isCreateSupported()) {
- return false;
- }
-
- return true;
+ private static boolean canCopy(@Nullable DocumentInfo dest) {
+ return dest != null && dest.isDirectory() && dest.isCreateSupported();
}
public static @OpType int getOpType(ClipData data) {
diff --git a/src/com/android/documentsui/dirlist/DirectoryAddonsAdapter.java b/src/com/android/documentsui/dirlist/DirectoryAddonsAdapter.java
index 51bf589..a551423 100644
--- a/src/com/android/documentsui/dirlist/DirectoryAddonsAdapter.java
+++ b/src/com/android/documentsui/dirlist/DirectoryAddonsAdapter.java
@@ -20,10 +20,11 @@
import android.support.v7.widget.RecyclerView.AdapterDataObserver;
import android.view.ViewGroup;
+import com.android.documentsui.Model;
import com.android.documentsui.base.EventListener;
import com.android.documentsui.dirlist.Message.HeaderMessage;
import com.android.documentsui.dirlist.Message.InflateMessage;
-import com.android.documentsui.dirlist.Model.Update;
+import com.android.documentsui.Model.Update;
import java.util.List;
@@ -174,7 +175,6 @@
return;
}
-
// Walk down the list of IDs till we encounter something that's not a directory, and
// insert a whitespace element - this introduces a visual break in the grid between
// folders and documents.
diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 3da4276..8a0ba0d 100644
--- a/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -31,11 +31,9 @@
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
-import android.app.LoaderManager.LoaderCallbacks;
import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
-import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
@@ -66,9 +64,7 @@
import com.android.documentsui.ActionModeController;
import com.android.documentsui.BaseActivity;
import com.android.documentsui.BaseActivity.RetainedState;
-import com.android.documentsui.DirectoryLoader;
import com.android.documentsui.DirectoryReloadLock;
-import com.android.documentsui.DirectoryResult;
import com.android.documentsui.DocumentsApplication;
import com.android.documentsui.DragAndDropHelper;
import com.android.documentsui.FocusManager;
@@ -77,8 +73,8 @@
import com.android.documentsui.Injector.Injected;
import com.android.documentsui.ItemDragListener;
import com.android.documentsui.Metrics;
+import com.android.documentsui.Model;
import com.android.documentsui.R;
-import com.android.documentsui.RecentsLoader;
import com.android.documentsui.ThumbnailCache;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.DocumentStack;
@@ -96,7 +92,6 @@
import com.android.documentsui.clipping.UrisSupplier;
import com.android.documentsui.dirlist.AnimationView.AnimationType;
import com.android.documentsui.picker.PickActivity;
-import com.android.documentsui.roots.RootsAccess;
import com.android.documentsui.selection.BandController;
import com.android.documentsui.selection.GestureSelector;
import com.android.documentsui.selection.Selection;
@@ -122,14 +117,8 @@
public class DirectoryFragment extends Fragment
implements ItemDragListener.DragHost, SwipeRefreshLayout.OnRefreshListener {
- @IntDef(flag = true, value = {
- TYPE_NORMAL,
- TYPE_RECENT_OPEN
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface ResultType {}
- public static final int TYPE_NORMAL = 1;
- public static final int TYPE_RECENT_OPEN = 2;
+ static final int TYPE_NORMAL = 1;
+ static final int TYPE_RECENT_OPEN = 2;
@IntDef(flag = true, value = {
REQUEST_COPY_DESTINATION
@@ -150,7 +139,6 @@
private Model mModel;
private final EventListener<Model.Update> mModelUpdateListener = new ModelUpdateListener();
private final DocumentsAdapter.Environment mAdapterEnv = new AdapterEnvironment();
- private final LoaderCallbacks<DirectoryResult> mLoaderCallbacks = new LoaderBindings();
@Injected
@ContentScoped
@@ -201,7 +189,7 @@
private SortModel.UpdateListener mSortListener = (model, updateType) -> {
// Only when sort order has changed do we need to trigger another loading.
if ((updateType & SortModel.UPDATE_TYPE_SORTING) != 0) {
- getLoaderManager().restartLoader(LOADER_ID, null, mLoaderCallbacks);
+ mActions.loadDocumentsForCurrentStack();
}
};
@@ -259,6 +247,9 @@
cancelThumbnailTask(view);
}
+ mModel.removeUpdateListener(mModelUpdateListener);
+ mModel.removeUpdateListener(mAdapter.getModelUpdateListener());
+
super.onDestroyView();
}
@@ -312,7 +303,7 @@
mSelectionMgr = mInjector.getSelectionManager(mAdapter, this::canSetSelectionState);
mFocusManager = mInjector.getFocusManager(mRecView, mModel);
- mActions = mInjector.getActionHandler(mModel);
+ mActions = mInjector.getActionHandler(mReloadLock);
mRecView.setAccessibilityDelegateCompat(
new AccessibilityEventRouter(mRecView,
@@ -382,14 +373,13 @@
final ActivityManager am = (ActivityManager) mActivity.getSystemService(
Context.ACTIVITY_SERVICE);
- boolean svelte = am.isLowRamDevice() && (mLocalState.mType == TYPE_RECENT_OPEN);
+ boolean svelte = am.isLowRamDevice() && (mState.stack.isRecents());
mIconHelper.setThumbnailsEnabled(!svelte);
// If mDocument is null, we sort it by last modified by default because it's in Recents.
final boolean prefersLastModified =
- (mLocalState.mDocument != null)
- ? (mLocalState.mDocument.flags & Document.FLAG_DIR_PREFERS_LAST_MODIFIED) != 0
- : true;
+ (mLocalState.mDocument == null)
+ || mLocalState.mDocument.prefersSortByLastModified();
// Call this before adding the listener to avoid restarting the loader one more time
mState.sortModel.setDefaultDimension(
prefersLastModified
@@ -397,7 +387,7 @@
: SortModel.SORT_DIMENSION_ID_TITLE);
// Kick off loader at least once
- getLoaderManager().restartLoader(LOADER_ID, null, mLoaderCallbacks);
+ mActions.loadDocumentsForCurrentStack();
}
@Override
@@ -1073,36 +1063,17 @@
public static void showDirectory(
FragmentManager fm, RootInfo root, DocumentInfo doc, int anim) {
if (DEBUG) Log.d(TAG, "Showing directory: " + DocumentInfo.debugString(doc));
- create(fm, TYPE_NORMAL, root, doc, null, anim);
+ create(fm, root, doc, anim);
}
public static void showRecentsOpen(FragmentManager fm, int anim) {
- create(fm, TYPE_RECENT_OPEN, null, null, null, anim);
- }
-
- public static void reloadSearch(FragmentManager fm, RootInfo root, DocumentInfo doc,
- String query) {
- DirectoryFragment df = get(fm);
-
- df.mLocalState.update(root, doc, query);
- df.getLoaderManager().restartLoader(LOADER_ID, null, df.mLoaderCallbacks);
- }
-
- public static void reload(FragmentManager fm, int type, RootInfo root, DocumentInfo doc,
- String query) {
- if (DEBUG) Log.d(TAG, "Reloading directory: " + DocumentInfo.debugString(doc));
- DirectoryFragment df = get(fm);
-
- df.mLocalState.update(type, root, doc, query);
- df.getLoaderManager().restartLoader(LOADER_ID, null, df.mLoaderCallbacks);
+ create(fm, null, null, anim);
}
public static void create(
FragmentManager fm,
- int type,
RootInfo root,
@Nullable DocumentInfo doc,
- String query,
@AnimationType int anim) {
if (DEBUG) {
@@ -1114,10 +1085,8 @@
}
final Bundle args = new Bundle();
- args.putInt(Shared.EXTRA_TYPE, type);
args.putParcelable(Shared.EXTRA_ROOT, root);
args.putParcelable(Shared.EXTRA_DOC, doc);
- args.putString(Shared.EXTRA_QUERY, query);
args.putParcelable(Shared.EXTRA_SELECTION, new Selection());
final FragmentTransaction ft = fm.beginTransaction();
@@ -1160,7 +1129,7 @@
mRefreshLayout.setRefreshing(false);
} else {
// If Refresh API isn't available, we will explicitly reload the loader
- getLoaderManager().restartLoader(LOADER_ID, null, mLoaderCallbacks);
+ mActions.loadDocumentsForCurrentStack();
}
});
}
@@ -1173,131 +1142,10 @@
mProgressBar.setVisibility(mModel.isLoading() ? View.VISIBLE : View.GONE);
- mAdapter.notifyDataSetChanged();
-
- if (!mModel.isLoading()) {
- mActivity.notifyDirectoryLoaded(
- mModel.doc != null ? mModel.doc.derivedUri : null);
- }
- }
- }
-
- private final class AdapterEnvironment implements DocumentsAdapter.Environment {
-
- @Override
- public Features getFeatures() {
- return mInjector.features;
- }
-
- @Override
- public Context getContext() {
- return mActivity;
- }
-
- @Override
- public State getDisplayState() {
- return mState;
- }
-
- @Override
- public boolean isInSearchMode() {
- return mLocalState.mSearchMode;
- }
-
- @Override
- public Model getModel() {
- return mModel;
- }
-
- @Override
- public int getColumnCount() {
- return mColumnCount;
- }
-
- @Override
- public boolean isSelected(String id) {
- return mSelectionMgr.getSelection().contains(id);
- }
-
- @Override
- public boolean isDocumentEnabled(String mimeType, int flags) {
- return mInjector.config.isDocumentEnabled(mimeType, flags, mState);
- }
-
- @Override
- public void initDocumentHolder(DocumentHolder holder) {
- holder.addKeyEventListener(mInputHandler);
- holder.itemView.setOnFocusChangeListener(mFocusManager);
- }
-
- @Override
- public void onBindDocumentHolder(DocumentHolder holder, Cursor cursor) {
- setupDragAndDropOnDocumentView(holder.itemView, cursor);
- }
- }
-
- private final class LoaderBindings implements LoaderCallbacks<DirectoryResult> {
-
- @Override
- public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) {
- Context context = getActivity();
-
- Uri contentsUri;
- switch (mLocalState.mType) {
- case TYPE_NORMAL:
- contentsUri = mLocalState.mSearchMode
- ? DocumentsContract.buildSearchDocumentsUri(
- mLocalState.mRoot.authority,
- mLocalState.mRoot.rootId,
- mLocalState.mQuery)
- : DocumentsContract.buildChildDocumentsUri(
- mLocalState.mDocument.authority,
- mLocalState.mDocument.documentId);
-
- if (mInjector.config.managedModeEnabled(mState.stack)) {
- contentsUri = DocumentsContract.setManageMode(contentsUri);
- }
-
- if (DEBUG) Log.d(TAG,
- "Creating new directory loader for: "
- + DocumentInfo.debugString(mLocalState.mDocument));
-
- return new DirectoryLoader(
- context,
- mLocalState.mRoot,
- mLocalState.mDocument,
- contentsUri,
- mState.sortModel,
- mReloadLock,
- mLocalState.mSearchMode);
-
- case TYPE_RECENT_OPEN:
- if (DEBUG) Log.d(TAG, "Creating new loader recents.");
- final RootsAccess roots = DocumentsApplication.getRootsCache(context);
- return new RecentsLoader(context, roots, mState, mInjector.features);
-
- default:
- throw new IllegalStateException("Unknown type " + mLocalState.mType);
- }
- }
-
- @Override
- public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
- if (DEBUG) Log.d(TAG, "Loader has finished for: "
- + DocumentInfo.debugString(mLocalState.mDocument));
- assert(result != null);
-
- if (!isAdded()) return;
-
- if (mLocalState.mSearchMode) {
- Metrics.logUserAction(getContext(), Metrics.USER_ACTION_SEARCH);
- }
-
- mAdapter.notifyDataSetChanged();
- mModel.update(result);
-
updateLayout(mState.derivedMode);
+ mAdapter.notifyDataSetChanged();
+
if (mRestoredSelection != null) {
mSelectionMgr.restoreSelection(mRestoredSelection);
// Note, we'll take care of cleaning up retained selection
@@ -1330,14 +1178,65 @@
() -> mRefreshLayout.setRefreshing(false),
REFRESH_SPINNER_TIMEOUT);
}
+
+ if (!mModel.isLoading()) {
+ mActivity.notifyDirectoryLoaded(
+ mModel.doc != null ? mModel.doc.derivedUri : null);
+ }
+ }
+ }
+
+ private final class AdapterEnvironment implements DocumentsAdapter.Environment {
+
+ @Override
+ public Features getFeatures() {
+ return mInjector.features;
}
@Override
- public void onLoaderReset(Loader<DirectoryResult> loader) {
- if (DEBUG) Log.d(TAG, "Resetting loader for: "
- + DocumentInfo.debugString(mLocalState.mDocument));
+ public Context getContext() {
+ return mActivity;
+ }
- mRefreshLayout.setRefreshing(false);
+ @Override
+ public State getDisplayState() {
+ return mState;
+ }
+
+ @Override
+ public boolean isInSearchMode() {
+ return mInjector.searchManager.isSearching();
+ }
+
+ @Override
+ public Model getModel() {
+ return mModel;
+ }
+
+ @Override
+ public int getColumnCount() {
+ return mColumnCount;
+ }
+
+ @Override
+ public boolean isSelected(String id) {
+ return mSelectionMgr.getSelection().contains(id);
+ }
+
+ @Override
+ public boolean isDocumentEnabled(String mimeType, int flags) {
+ return mInjector.config.isDocumentEnabled(mimeType, flags, mState);
+ }
+
+ @Override
+ public void initDocumentHolder(DocumentHolder holder) {
+ holder.addKeyEventListener(mInputHandler);
+ holder.itemView.setOnFocusChangeListener(mFocusManager);
+ }
+
+ @Override
+ public void onBindDocumentHolder(DocumentHolder holder, Cursor cursor) {
+ setupDragAndDropOnDocumentView(holder.itemView, cursor);
}
}
}
diff --git a/src/com/android/documentsui/dirlist/DirectoryState.java b/src/com/android/documentsui/dirlist/DirectoryState.java
index 6b849a2..b27a4b8 100644
--- a/src/com/android/documentsui/dirlist/DirectoryState.java
+++ b/src/com/android/documentsui/dirlist/DirectoryState.java
@@ -20,7 +20,6 @@
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.base.Shared;
-import com.android.documentsui.dirlist.DirectoryFragment.ResultType;
import com.android.documentsui.services.FileOperation;
import com.android.documentsui.services.FileOperationService;
import com.android.documentsui.sorting.SortModel;
@@ -33,38 +32,28 @@
private static final String EXTRA_SORT_DIMENSION_ID = "sortDimensionId";
private static final String EXTRA_SORT_DIRECTION = "sortDirection";
- // Directory fragment state is defined by: root, document, query, type, selection
- @ResultType int mType = DirectoryFragment.TYPE_NORMAL;
- RootInfo mRoot;
// Null when viewing Recents directory.
@Nullable DocumentInfo mDocument;
- String mQuery = null;
// Here we save the clip details of moveTo/copyTo actions when picker shows up.
// This will be written to saved instance.
@Nullable FileOperation mPendingOperation;
- boolean mSearchMode;
int mLastSortDimensionId = SortModel.SORT_DIMENSION_ID_UNKNOWN;
@SortDirection int mLastSortDirection;
+ private RootInfo mRoot;
private String mConfigKey;
public void restore(Bundle bundle) {
mRoot = bundle.getParcelable(Shared.EXTRA_ROOT);
mDocument = bundle.getParcelable(Shared.EXTRA_DOC);
- mQuery = bundle.getString(Shared.EXTRA_QUERY);
- mType = bundle.getInt(Shared.EXTRA_TYPE);
- mSearchMode = bundle.getBoolean(Shared.EXTRA_SEARCH_MODE);
mPendingOperation = bundle.getParcelable(FileOperationService.EXTRA_OPERATION);
mLastSortDimensionId = bundle.getInt(EXTRA_SORT_DIMENSION_ID);
mLastSortDirection = bundle.getInt(EXTRA_SORT_DIRECTION);
}
public void save(Bundle bundle) {
- bundle.putInt(Shared.EXTRA_TYPE, mType);
bundle.putParcelable(Shared.EXTRA_ROOT, mRoot);
bundle.putParcelable(Shared.EXTRA_DOC, mDocument);
- bundle.putString(Shared.EXTRA_QUERY, mQuery);
- bundle.putBoolean(Shared.EXTRA_SEARCH_MODE, mSearchMode);
bundle.putParcelable(FileOperationService.EXTRA_OPERATION, mPendingOperation);
bundle.putInt(EXTRA_SORT_DIMENSION_ID, mLastSortDimensionId);
bundle.putInt(EXTRA_SORT_DIRECTION, mLastSortDirection);
@@ -76,16 +65,9 @@
return op;
}
- public void update(int type, RootInfo root, DocumentInfo doc, String query) {
- mType = type;
- update(root, doc, query);
- }
-
- public void update(RootInfo root, DocumentInfo doc, String query) {
- mQuery = query;
+ public void update(RootInfo root, DocumentInfo doc) {
mRoot = root;
mDocument = doc;
- mSearchMode = query != null;
}
String getConfigKey() {
diff --git a/src/com/android/documentsui/dirlist/DocumentsAdapter.java b/src/com/android/documentsui/dirlist/DocumentsAdapter.java
index 9b1794c..297584f 100644
--- a/src/com/android/documentsui/dirlist/DocumentsAdapter.java
+++ b/src/com/android/documentsui/dirlist/DocumentsAdapter.java
@@ -24,6 +24,7 @@
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
+import com.android.documentsui.Model;
import com.android.documentsui.base.EventListener;
import com.android.documentsui.base.Features;
import com.android.documentsui.base.State;
diff --git a/src/com/android/documentsui/dirlist/DragStartListener.java b/src/com/android/documentsui/dirlist/DragStartListener.java
index 6598a87..a0b0f64 100644
--- a/src/com/android/documentsui/dirlist/DragStartListener.java
+++ b/src/com/android/documentsui/dirlist/DragStartListener.java
@@ -26,6 +26,7 @@
import android.view.View;
import com.android.documentsui.DragShadowBuilder;
+import com.android.documentsui.Model;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.Events;
import com.android.documentsui.base.Events.InputEvent;
diff --git a/src/com/android/documentsui/dirlist/Message.java b/src/com/android/documentsui/dirlist/Message.java
index d8044bb..fdaf786 100644
--- a/src/com/android/documentsui/dirlist/Message.java
+++ b/src/com/android/documentsui/dirlist/Message.java
@@ -25,7 +25,7 @@
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.base.Shared;
import com.android.documentsui.dirlist.DocumentsAdapter.Environment;
-import com.android.documentsui.dirlist.Model.Update;
+import com.android.documentsui.Model.Update;
/**
* Data object used by {@link InflateMessageDocumentHolder} and {@link HeaderMessageDocumentHolder}.
diff --git a/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java b/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java
index c7c2526..1e080bf 100644
--- a/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java
+++ b/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java
@@ -18,7 +18,6 @@
import static com.android.documentsui.base.DocumentInfo.getCursorInt;
import static com.android.documentsui.base.DocumentInfo.getCursorString;
-import static com.android.documentsui.base.Shared.DEBUG;
import static com.android.documentsui.base.State.MODE_GRID;
import static com.android.documentsui.base.State.MODE_LIST;
@@ -27,14 +26,13 @@
import android.util.Log;
import android.view.ViewGroup;
+import com.android.documentsui.Model;
import com.android.documentsui.base.EventListener;
import com.android.documentsui.base.State;
-import com.android.documentsui.dirlist.Model.Update;
+import com.android.documentsui.Model.Update;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
/**
* Adapts from dirlist.Model to something RecyclerView understands.
diff --git a/src/com/android/documentsui/files/ActionHandler.java b/src/com/android/documentsui/files/ActionHandler.java
index 5720ea1..6926fb8 100644
--- a/src/com/android/documentsui/files/ActionHandler.java
+++ b/src/com/android/documentsui/files/ActionHandler.java
@@ -53,7 +53,7 @@
import com.android.documentsui.clipping.UrisSupplier;
import com.android.documentsui.dirlist.AnimationView;
import com.android.documentsui.dirlist.DocumentDetails;
-import com.android.documentsui.dirlist.Model;
+import com.android.documentsui.Model;
import com.android.documentsui.files.ActionHandler.Addons;
import com.android.documentsui.queries.SearchViewManager;
import com.android.documentsui.roots.GetRootDocumentTask;
@@ -84,7 +84,7 @@
private final DialogController mDialogs;
private final DocumentClipper mClipper;
private final ClipStore mClipStore;
- private @Nullable Model mModel;
+ private final Model mModel;
ActionHandler(
T activity,
@@ -106,6 +106,7 @@
mDialogs = injector.dialogs;
mClipper = clipper;
mClipStore = clipStore;
+ mModel = injector.getModel();
}
@Override
@@ -113,7 +114,6 @@
new GetRootDocumentTask(
root,
mActivity,
- mActivity::isDestroyed,
(DocumentInfo rootDoc) -> dropOnCallback(event, rootDoc, root)
).executeOnExecutor(mExecutors.lookup(root.authority));
return true;
@@ -150,12 +150,11 @@
new GetRootDocumentTask(
root,
mActivity,
- mActivity::isDestroyed,
(DocumentInfo doc) -> pasteIntoFolder(root, doc)
).executeOnExecutor(mExecutors.lookup(root.authority));
}
- private void pasteIntoFolder(RootInfo root, DocumentInfo doc) {
+ private void pasteIntoFolder(RootInfo root, @Nullable DocumentInfo doc) {
DocumentClipper clipper = DocumentsApplication.getDocumentClipper(mActivity);
DocumentStack stack = new DocumentStack(root, doc);
clipper.copyFromClipboard(doc, stack, mDialogs::showFileOperationStatus);
@@ -620,16 +619,6 @@
return intent;
}
-
- @SuppressWarnings("unchecked")
- @Override
- public ActionHandler<T> reset(Model model) {
- assert(model != null);
- mModel = model;
-
- return this;
- }
-
public interface Addons extends CommonAddons {
}
}
diff --git a/src/com/android/documentsui/files/FilesActivity.java b/src/com/android/documentsui/files/FilesActivity.java
index b5989b2..c909488 100644
--- a/src/com/android/documentsui/files/FilesActivity.java
+++ b/src/com/android/documentsui/files/FilesActivity.java
@@ -134,6 +134,8 @@
DocumentsApplication.getClipStore(this),
mInjector);
+ mInjector.searchManager = mSearchManager;
+
mActivityInputHandler =
new ActivityInputHandler(mInjector.actions::deleteSelectedDocuments);
mSharedInputHandler =
@@ -274,7 +276,7 @@
assert(!mSearchManager.isSearching());
- if (cwd == null) {
+ if (mState.stack.isRecents()) {
DirectoryFragment.showRecentsOpen(fm, anim);
} else {
// Normal boring directory
diff --git a/src/com/android/documentsui/files/QuickViewIntentBuilder.java b/src/com/android/documentsui/files/QuickViewIntentBuilder.java
index c85af8a..189a889 100644
--- a/src/com/android/documentsui/files/QuickViewIntentBuilder.java
+++ b/src/com/android/documentsui/files/QuickViewIntentBuilder.java
@@ -38,7 +38,7 @@
import com.android.documentsui.R;
import com.android.documentsui.base.DebugFlags;
import com.android.documentsui.base.DocumentInfo;
-import com.android.documentsui.dirlist.Model;
+import com.android.documentsui.Model;
import com.android.documentsui.roots.RootCursorWrapper;
import java.util.ArrayList;
diff --git a/src/com/android/documentsui/picker/ActionHandler.java b/src/com/android/documentsui/picker/ActionHandler.java
index a92685a..c00345c 100644
--- a/src/com/android/documentsui/picker/ActionHandler.java
+++ b/src/com/android/documentsui/picker/ActionHandler.java
@@ -39,7 +39,7 @@
import com.android.documentsui.base.Shared;
import com.android.documentsui.base.State;
import com.android.documentsui.dirlist.DocumentDetails;
-import com.android.documentsui.dirlist.Model;
+import com.android.documentsui.Model;
import com.android.documentsui.picker.ActionHandler.Addons;
import com.android.documentsui.queries.SearchViewManager;
import com.android.documentsui.roots.RootsAccess;
@@ -190,16 +190,6 @@
return false;
}
- @SuppressWarnings("unchecked")
- @Override
- public ActionHandler<T> reset(Model model) {
- assert(model != null);
- mModel = model;
-
- return this;
- }
-
-
public interface Addons extends CommonAddons {
void onAppPicked(ResolveInfo info);
void onDocumentPicked(DocumentInfo doc);
diff --git a/src/com/android/documentsui/picker/PickActivity.java b/src/com/android/documentsui/picker/PickActivity.java
index 40c307a..1c7fc1e 100644
--- a/src/com/android/documentsui/picker/PickActivity.java
+++ b/src/com/android/documentsui/picker/PickActivity.java
@@ -111,11 +111,13 @@
getColor(R.color.accent_dark));
mInjector.menuManager = new MenuManager(mSearchManager, mState, new DirectoryDetails(this));
+
mInjector.actionModeController = new ActionModeController(
this,
mInjector.selectionMgr,
mInjector.menuManager,
mInjector.messages);
+
mInjector.actions = new ActionHandler<>(
this,
mState,
@@ -125,6 +127,8 @@
ProviderExecutor::forAuthority,
mInjector);
+ mInjector.searchManager = mSearchManager;
+
Intent intent = getIntent();
mSharedInputHandler =
@@ -277,8 +281,7 @@
final RootInfo root = getCurrentRoot();
final DocumentInfo cwd = getCurrentDirectory();
- if (cwd == null) {
- // No directory means recents
+ if (mState.stack.isRecents()) {
if (mState.action == ACTION_CREATE ||
mState.action == ACTION_PICK_COPY_DESTINATION) {
mInjector.actions.loadRoot(Shared.getDefaultRootUri(this));
diff --git a/src/com/android/documentsui/queries/SearchViewManager.java b/src/com/android/documentsui/queries/SearchViewManager.java
index 1b773ea..64ec168 100644
--- a/src/com/android/documentsui/queries/SearchViewManager.java
+++ b/src/com/android/documentsui/queries/SearchViewManager.java
@@ -307,7 +307,7 @@
return true;
}
- String getCurrentSearch() {
+ public String getCurrentSearch() {
return mCurrentSearch;
}
diff --git a/src/com/android/documentsui/roots/GetRootDocumentTask.java b/src/com/android/documentsui/roots/GetRootDocumentTask.java
index 1503ae1..e7fdadf 100644
--- a/src/com/android/documentsui/roots/GetRootDocumentTask.java
+++ b/src/com/android/documentsui/roots/GetRootDocumentTask.java
@@ -41,30 +41,15 @@
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) {
- this(rootInfo, activity, activity::isDestroyed, callback);
- }
-
- public GetRootDocumentTask(
- RootInfo rootInfo, Fragment fragment, Consumer<DocumentInfo> callback) {
- this(rootInfo, fragment.getContext(), fragment::isDetached, callback);
- }
-
- public GetRootDocumentTask(
- RootInfo rootInfo, Context context, Check check, Consumer<DocumentInfo> callback) {
- super(check);
+ super(activity::isDestroyed);
mRootInfo = rootInfo;
- mContext = context.getApplicationContext();
+ mContext = activity.getApplicationContext();
mCallback = callback;
}
- public void setForceCallback(boolean forceCallback) {
- mForceCallback = forceCallback;
- }
-
@Override
public @Nullable DocumentInfo run(Void... rootInfo) {
return mRootInfo.getRootDocumentBlocking(mContext);
@@ -74,11 +59,9 @@
public void finish(@Nullable DocumentInfo documentInfo) {
if (documentInfo == null) {
Log.e(TAG,
- "Cannot find document info for root: " + mRootInfo + " in the given timeout");
+ "Cannot find document info for root: " + mRootInfo);
}
- if (documentInfo != null || mForceCallback) {
- mCallback.accept(documentInfo);
- }
+ mCallback.accept(documentInfo);
}
}
diff --git a/src/com/android/documentsui/sidebar/RootsFragment.java b/src/com/android/documentsui/sidebar/RootsFragment.java
index 7fbe8e9..1d4950f 100644
--- a/src/com/android/documentsui/sidebar/RootsFragment.java
+++ b/src/com/android/documentsui/sidebar/RootsFragment.java
@@ -480,7 +480,6 @@
updater.updateDocInfoForRoot(doc);
});
task.setTimeout(CONTEXT_MENU_ITEM_TIMEOUT);
- task.setForceCallback(true);
task.executeOnExecutor(getBaseActivity().getExecutorForCurrentDirectory());
}
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index bdc0125..6df4c90 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -56,6 +56,19 @@
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
</intent-filter>
</provider>
+
+ <!-- Provider that has broken behavior -->
+ <provider
+ android:name="com.android.documentsui.BrokenProvider"
+ android:authorities="com.android.documentsui.broken"
+ android:exported="true"
+ android:grantUriPermissions="true"
+ android:permission="android.permission.MANAGE_DOCUMENTS"
+ android:enabled="true">
+ <intent-filter>
+ <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
+ </intent-filter>
+ </provider>
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/common/com/android/documentsui/BrokenProvider.java b/tests/common/com/android/documentsui/BrokenProvider.java
new file mode 100644
index 0000000..272645e
--- /dev/null
+++ b/tests/common/com/android/documentsui/BrokenProvider.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017 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.database.Cursor;
+import android.provider.DocumentsContract.Root;
+
+import java.io.FileNotFoundException;
+
+/**
+ * Test provider that provides different kinds of broken behaviors DocumentsUI may encounter.
+ */
+public class BrokenProvider extends TestRootProvider {
+
+ // Root information for a root that throws when querying its root document
+ private static final String BROKEN_ROOT_DOCUMENT_ID = "BROKEN_ROOT_DOCUMENT";
+ private static final String BROKEN_ROOT_DOCUMENT_TITLE = "Broken Root Doc";
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ public BrokenProvider() {
+ super(
+ BROKEN_ROOT_DOCUMENT_TITLE,
+ BROKEN_ROOT_DOCUMENT_ID,
+ Root.FLAG_SUPPORTS_CREATE,
+ BROKEN_ROOT_DOCUMENT_ID);
+ }
+
+ @Override
+ public Cursor queryDocument(String documentId, String[] projection)
+ throws FileNotFoundException {
+ if (BROKEN_ROOT_DOCUMENT_ID.equals(documentId)) {
+ throw new FileNotFoundException();
+ }
+
+ return null;
+ }
+
+ @Override
+ public Cursor queryChildDocuments(String parentDocumentId, String[] projection,
+ String sortOrder) throws FileNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/tests/common/com/android/documentsui/TestActivity.java b/tests/common/com/android/documentsui/TestActivity.java
index 15a724e..93e8b44 100644
--- a/tests/common/com/android/documentsui/TestActivity.java
+++ b/tests/common/com/android/documentsui/TestActivity.java
@@ -19,6 +19,7 @@
import static junit.framework.Assert.assertEquals;
import android.app.Activity;
+import android.app.LoaderManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -26,17 +27,16 @@
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.Uri;
-import android.os.Bundle;
-import android.test.mock.MockContentProvider;
import android.test.mock.MockContentResolver;
import com.android.documentsui.AbstractActionHandler.CommonAddons;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.RootInfo;
+import com.android.documentsui.testing.TestEnv;
import com.android.documentsui.testing.TestEventListener;
+import com.android.documentsui.testing.TestLoaderManager;
import com.android.documentsui.testing.TestPackageManager;
import com.android.documentsui.testing.TestResources;
-import com.android.documentsui.testing.TestRootsAccess;
import org.mockito.Mockito;
@@ -51,7 +51,7 @@
public Intent intent;
public RootInfo currentRoot;
public MockContentResolver contentResolver;
- public MockContentProvider contentProvider;
+ public TestLoaderManager loaderManager;
public TestEventListener<Intent> startActivity;
public TestEventListener<Intent> startService;
@@ -60,13 +60,13 @@
public TestEventListener<Boolean> setRootsDrawerOpen;
public TestEventListener<Uri> notifyDirectoryNavigated;
- public static TestActivity create() {
+ public static TestActivity create(TestEnv env) {
TestActivity activity = Mockito.mock(TestActivity.class, Mockito.CALLS_REAL_METHODS);
- activity.init();
+ activity.init(env);
return activity;
}
- public void init() {
+ public void init(TestEnv env) {
resources = TestResources.create();
packageMgr = TestPackageManager.create();
intent = new Intent();
@@ -77,10 +77,8 @@
refreshCurrentRootAndDirectory = new TestEventListener<>();
setRootsDrawerOpen = new TestEventListener<>();
notifyDirectoryNavigated = new TestEventListener<>();
- contentResolver = new MockContentResolver();
- contentProvider = new DocsMockContentProvider();
- contentResolver.addProvider(TestRootsAccess.HOME.authority, contentProvider);
-
+ contentResolver = env.contentResolver;
+ loaderManager = new TestLoaderManager();
}
@Override
@@ -169,15 +167,13 @@
@Override
public final void updateNavigator() {}
+
+ @Override
+ public final LoaderManager getLoaderManager() {
+ return loaderManager;
+ }
}
// Trick Mockito into finding our Addons methods correctly. W/o this
// hack, Mockito thinks Addons methods are not implemented.
abstract class AbstractBase extends Activity implements CommonAddons {}
-
-class DocsMockContentProvider extends MockContentProvider {
- @Override
- public boolean refresh(Uri url, Bundle args) {
- return true;
- }
-}
diff --git a/tests/common/com/android/documentsui/dirlist/TestContentProvider.java b/tests/common/com/android/documentsui/dirlist/TestContentProvider.java
deleted file mode 100644
index c0505f2..0000000
--- a/tests/common/com/android/documentsui/dirlist/TestContentProvider.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2015 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.dirlist;
-
-import static org.junit.Assert.assertTrue;
-
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.DocumentsContract;
-import android.test.mock.MockContentProvider;
-
-import com.android.documentsui.base.DocumentInfo;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A very simple test double for ContentProvider. Useful in this package only.
- */
-class TestContentProvider extends MockContentProvider {
- List<Uri> mDeleted = new ArrayList<>();
-
- @Override
- public Bundle call(String method, String arg, Bundle extras) {
- // Intercept and log delete method calls.
- if (DocumentsContract.METHOD_DELETE_DOCUMENT.equals(method)) {
- final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
- mDeleted.add(documentUri);
- return new Bundle();
- } else {
- return super.call(method, arg, extras);
- }
- }
-
- public void assertWasDeleted(DocumentInfo doc) {
- assertTrue(mDeleted.contains(doc.derivedUri));
- }
-}
\ No newline at end of file
diff --git a/tests/common/com/android/documentsui/dirlist/TestContext.java b/tests/common/com/android/documentsui/dirlist/TestContext.java
index 714062d..d5122f2 100644
--- a/tests/common/com/android/documentsui/dirlist/TestContext.java
+++ b/tests/common/com/android/documentsui/dirlist/TestContext.java
@@ -21,6 +21,8 @@
import android.content.ContextWrapper;
import android.test.mock.MockContentResolver;
+import com.android.documentsui.testing.TestDocumentsProvider;
+
public final class TestContext {
/**
@@ -28,7 +30,7 @@
*/
static Context createStorageTestContext(Context context, String authority) {
final MockContentResolver testResolver = new MockContentResolver();
- TestContentProvider provider = new TestContentProvider();
+ TestDocumentsProvider provider = new TestDocumentsProvider(authority);
testResolver.addProvider(authority, provider);
return new ContextWrapper(context) {
diff --git a/tests/common/com/android/documentsui/dirlist/TestDocumentsAdapter.java b/tests/common/com/android/documentsui/dirlist/TestDocumentsAdapter.java
index c702c9a..760d0e2 100644
--- a/tests/common/com/android/documentsui/dirlist/TestDocumentsAdapter.java
+++ b/tests/common/com/android/documentsui/dirlist/TestDocumentsAdapter.java
@@ -19,7 +19,7 @@
import android.view.ViewGroup;
import com.android.documentsui.base.EventListener;
-import com.android.documentsui.dirlist.Model.Update;
+import com.android.documentsui.Model.Update;
import com.android.documentsui.testing.TestEventListener;
import java.util.ArrayList;
diff --git a/tests/common/com/android/documentsui/testing/TestActionHandler.java b/tests/common/com/android/documentsui/testing/TestActionHandler.java
index 5dfc855..4912475 100644
--- a/tests/common/com/android/documentsui/testing/TestActionHandler.java
+++ b/tests/common/com/android/documentsui/testing/TestActionHandler.java
@@ -23,7 +23,7 @@
import com.android.documentsui.TestActivity;
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.dirlist.DocumentDetails;
-import com.android.documentsui.dirlist.Model;
+import com.android.documentsui.Model;
public class TestActionHandler extends AbstractActionHandler<TestActivity> {
@@ -38,7 +38,7 @@
public TestActionHandler(TestEnv env) {
super(
- TestActivity.create(),
+ TestActivity.create(env),
env.state,
env.roots,
env.docs,
@@ -81,9 +81,4 @@
protected void launchToDefaultLocation() {
throw new UnsupportedOperationException();
}
-
- @Override
- public <T extends ActionHandler> T reset(Model model) {
- return null;
- }
}
diff --git a/tests/common/com/android/documentsui/testing/TestActivityConfig.java b/tests/common/com/android/documentsui/testing/TestActivityConfig.java
new file mode 100644
index 0000000..1dc8985
--- /dev/null
+++ b/tests/common/com/android/documentsui/testing/TestActivityConfig.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 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.ActivityConfig;
+import com.android.documentsui.base.DocumentStack;
+import com.android.documentsui.base.State;
+
+public class TestActivityConfig extends ActivityConfig {
+
+ public boolean nextSelectType = false;
+ public boolean nextDocumentEnabled = false;
+ public boolean nextManagedModeEnabled = false;
+ public boolean nextDragAndDropEnabled = false;
+
+ public boolean canSelectType(String docMimeType, int docFlags, State state) {
+ return nextSelectType;
+ }
+
+ public boolean isDocumentEnabled(String docMimeType, int docFlags, State state) {
+ return nextDocumentEnabled;
+ }
+
+ /**
+ * When managed mode is enabled, active downloads will be visible in the UI.
+ * Presumably this should only be true when in the downloads directory.
+ */
+ public boolean managedModeEnabled(DocumentStack stack) {
+ return nextManagedModeEnabled;
+ }
+
+ /**
+ * Whether drag n' drop is allowed in this context
+ */
+ public boolean dragAndDropEnabled() {
+ return nextDragAndDropEnabled;
+ }
+}
diff --git a/tests/common/com/android/documentsui/testing/TestDocumentsProvider.java b/tests/common/com/android/documentsui/testing/TestDocumentsProvider.java
new file mode 100644
index 0000000..19c3c2b
--- /dev/null
+++ b/tests/common/com/android/documentsui/testing/TestDocumentsProvider.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2017 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.content.pm.ProviderInfo;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsProvider;
+
+import com.android.documentsui.base.DocumentInfo;
+
+import java.io.FileNotFoundException;
+
+/**
+ * Test doubles of {@link DocumentsProvider} to isolate document providers. This is not registered
+ * or exposed through AndroidManifest, but only used locally.
+ */
+public class TestDocumentsProvider extends DocumentsProvider {
+
+ private String[] DOCUMENTS_PROJECTION = new String[] {
+ Document.COLUMN_DOCUMENT_ID,
+ Document.COLUMN_MIME_TYPE,
+ Document.COLUMN_DISPLAY_NAME,
+ Document.COLUMN_LAST_MODIFIED,
+ Document.COLUMN_FLAGS,
+ Document.COLUMN_SUMMARY,
+ Document.COLUMN_SIZE,
+ Document.COLUMN_ICON
+ };
+
+ private Cursor mNextChildDocuments;
+
+ public TestDocumentsProvider(String authority) {
+ ProviderInfo info = new ProviderInfo();
+ info.authority = authority;
+ attachInfoForTesting(null, info);
+ }
+
+ @Override
+ public boolean refresh(Uri url, Bundle args, CancellationSignal signal) {
+ return true;
+ }
+
+ @Override
+ public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+ return null;
+ }
+
+ @Override
+ public Cursor queryDocument(String documentId, String[] projection)
+ throws FileNotFoundException {
+ return null;
+ }
+
+ @Override
+ public Cursor queryChildDocuments(String parentDocumentId, String[] projection,
+ String sortOrder) throws FileNotFoundException {
+ return mNextChildDocuments;
+ }
+
+ @Override
+ public ParcelFileDescriptor openDocument(String documentId, String mode,
+ CancellationSignal signal) throws FileNotFoundException {
+ return null;
+ }
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ /**
+ * Sets the next return value for {@link #queryChildDocuments(String, String[], String)}.
+ * @param docs docs to return for next query.
+ */
+ public void setNextChildDocumentsReturns(DocumentInfo... docs) {
+ mNextChildDocuments = createDocumentsCursor(docs);
+ }
+
+ private Cursor createDocumentsCursor(DocumentInfo... docs) {
+ MatrixCursor cursor = new MatrixCursor(DOCUMENTS_PROJECTION);
+ for (DocumentInfo doc : docs) {
+ cursor.newRow()
+ .add(Document.COLUMN_DOCUMENT_ID, doc.documentId)
+ .add(Document.COLUMN_MIME_TYPE, doc.mimeType)
+ .add(Document.COLUMN_DISPLAY_NAME, doc.displayName)
+ .add(Document.COLUMN_LAST_MODIFIED, doc.lastModified)
+ .add(Document.COLUMN_FLAGS, doc.flags)
+ .add(Document.COLUMN_SUMMARY, doc.summary)
+ .add(Document.COLUMN_SIZE, doc.size)
+ .add(Document.COLUMN_ICON, doc.icon);
+ }
+
+ return cursor;
+ }
+}
diff --git a/tests/common/com/android/documentsui/testing/TestEnv.java b/tests/common/com/android/documentsui/testing/TestEnv.java
index 3118487..3e177d5 100644
--- a/tests/common/com/android/documentsui/testing/TestEnv.java
+++ b/tests/common/com/android/documentsui/testing/TestEnv.java
@@ -18,21 +18,24 @@
import android.os.Handler;
import android.os.Looper;
import android.provider.DocumentsContract.Document;
+import android.test.mock.MockContentResolver;
import com.android.documentsui.FocusManager;
import com.android.documentsui.Injector;
import com.android.documentsui.base.DocumentInfo;
-import com.android.documentsui.base.Features;
+import com.android.documentsui.base.RootInfo;
import com.android.documentsui.base.State;
import com.android.documentsui.dirlist.TestFocusHandler;
-import com.android.documentsui.dirlist.TestModel;
import com.android.documentsui.selection.SelectionManager;
+import com.android.documentsui.sorting.SortModel;
import com.android.documentsui.ui.TestDialogController;
import junit.framework.Assert;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
@@ -62,7 +65,11 @@
public final Injector injector;
public final TestFeatures features;
+ public final MockContentResolver contentResolver;
+ public final Map<String, TestDocumentsProvider> providers;
+
private TestEnv(String authority) {
+ state.sortModel = SortModel.createModel();
mExecutor = new TestScheduledExecutorService();
features = new TestFeatures();
model = new TestModel(authority, features);
@@ -70,12 +77,28 @@
searchViewManager = new TestSearchViewManager();
injector = new Injector(
features,
- null, // a Config is not required for tests
- null, // ScopedPreferences are not required for tests
- null, // a MessageBuilder is not required for tests
- new TestDialogController());
+ new TestActivityConfig(),
+ null, //ScopedPreferences are not required for tests
+ null, //a MessageBuilder is not required for tests
+ new TestDialogController(),
+ model);
injector.selectionMgr = selectionMgr;
injector.focusManager = new FocusManager(features, selectionMgr, null, null, 0);
+ injector.searchManager = searchViewManager;
+
+ contentResolver = new MockContentResolver();
+ providers = new HashMap<>(roots.getRootsBlocking().size());
+ registerProviders();
+ }
+
+ private void registerProviders() {
+ for (RootInfo root : roots.getRootsBlocking()) {
+ if (!providers.containsKey(root.authority)) {
+ TestDocumentsProvider provider = new TestDocumentsProvider(root.authority);
+ contentResolver.addProvider(root.authority, provider);
+ providers.put(root.authority, provider);
+ }
+ }
}
public static TestEnv create() {
diff --git a/tests/common/com/android/documentsui/testing/TestLoaderManager.java b/tests/common/com/android/documentsui/testing/TestLoaderManager.java
new file mode 100644
index 0000000..3344398
--- /dev/null
+++ b/tests/common/com/android/documentsui/testing/TestLoaderManager.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2017 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.LoaderManager;
+import android.content.AsyncTaskLoader;
+import android.content.Loader;
+import android.content.Loader.OnLoadCompleteListener;
+import android.os.Bundle;
+import android.util.SparseArray;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * A test double of {@link LoaderManager} that doesn't kick off loading when {@link Loader} is
+ * created. If caller needs to kick off loading caller can obtain the loader initialized and
+ * explicitly call {@link Loader#startLoading()}.
+ */
+public class TestLoaderManager extends LoaderManager {
+
+ private final SparseArray<Loader> mLoaders = new SparseArray<>();
+ private final SparseArray<OnLoadCompleteListener> mListeners = new SparseArray<>();
+
+ @Override
+ public <D> Loader<D> initLoader(int id, Bundle args,
+ LoaderCallbacks<D> callback) {
+ Loader<D> loader = mLoaders.get(id);
+ OnLoadCompleteListener<D> listener = callback::onLoadFinished;
+ if (loader == null) {
+ loader = callback.onCreateLoader(id, args);
+ mLoaders.put(id, loader);
+ } else {
+ loader.unregisterListener(mListeners.get(id));
+ }
+
+ loader.registerListener(id, listener);
+ mListeners.put(id, listener);
+
+ return loader;
+ }
+
+ @Override
+ public <D> Loader<D> restartLoader(int id, Bundle args,
+ LoaderCallbacks<D> callback) {
+ if (mLoaders.get(id) != null) {
+ destroyLoader(id);
+ }
+
+ return initLoader(id, args, callback);
+ }
+
+ @Override
+ public void destroyLoader(int id) {
+ Loader loader = getLoader(id);
+ if (loader != null) {
+ loader.abandon();
+ mLoaders.remove(id);
+ mListeners.remove(id);
+ }
+ }
+
+ @Override
+ public <D> Loader<D> getLoader(int id) {
+ return mLoaders.get(id);
+ }
+
+ public <D> OnLoadCompleteListener<D> getListener(int id) {
+ return mListeners.get(id);
+ }
+
+ public void runAsyncTaskLoader(int id) {
+ AsyncTaskLoader loader = (AsyncTaskLoader) getLoader(id);
+ loader.startLoading();
+ loader.waitForLoader();
+ }
+
+ @Override
+ public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+
+ }
+}
diff --git a/tests/common/com/android/documentsui/dirlist/TestModel.java b/tests/common/com/android/documentsui/testing/TestModel.java
similarity index 96%
rename from tests/common/com/android/documentsui/dirlist/TestModel.java
rename to tests/common/com/android/documentsui/testing/TestModel.java
index 462f5bf..517b1f4 100644
--- a/tests/common/com/android/documentsui/dirlist/TestModel.java
+++ b/tests/common/com/android/documentsui/testing/TestModel.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2017 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.documentsui.dirlist;
+package com.android.documentsui.testing;
import android.database.MatrixCursor;
import android.os.Bundle;
@@ -22,6 +22,7 @@
import android.provider.DocumentsContract.Document;
import com.android.documentsui.DirectoryResult;
+import com.android.documentsui.Model;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.Features;
import com.android.documentsui.roots.RootCursorWrapper;
diff --git a/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java b/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java
index 18b4bb9..2a25953 100644
--- a/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java
+++ b/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java
@@ -28,17 +28,17 @@
import android.support.test.filters.MediumTest;
import android.support.test.runner.AndroidJUnit4;
-import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.DocumentStack;
-import com.android.documentsui.base.DocumentStackTest;
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.base.Shared;
import com.android.documentsui.dirlist.DocumentDetails;
-import com.android.documentsui.dirlist.Model;
import com.android.documentsui.files.LauncherActivity;
+import com.android.documentsui.sorting.SortDimension;
+import com.android.documentsui.sorting.SortModel;
import com.android.documentsui.testing.DocumentStackAsserts;
import com.android.documentsui.testing.Roots;
import com.android.documentsui.testing.TestEnv;
+import com.android.documentsui.testing.TestEventHandler;
import com.android.documentsui.testing.TestRootsAccess;
import org.junit.Before;
@@ -46,7 +46,6 @@
import org.junit.runner.RunWith;
import java.util.Arrays;
-import java.util.List;
/**
* A unit test *for* AbstractActionHandler, not an abstract test baseclass.
@@ -61,8 +60,8 @@
@Before
public void setUp() {
- mActivity = TestActivity.create();
mEnv = TestEnv.create();
+ mActivity = TestActivity.create(mEnv);
mHandler = new AbstractActionHandler<TestActivity>(
mActivity,
mEnv.state,
@@ -91,11 +90,6 @@
protected void launchToDefaultLocation() {
throw new UnsupportedOperationException();
}
-
- @Override
- public <T extends ActionHandler> T reset(Model model) {
- return null;
- }
};
}
@@ -204,4 +198,39 @@
mEnv.docs.lastUri.assertLastArgument(TestEnv.FILE_GIF.derivedUri);
mActivity.refreshCurrentRootAndDirectory.assertCalled();
}
+
+ @Test
+ public void testLoadChildrenDocuments() throws Exception {
+ mEnv.state.stack.changeRoot(TestRootsAccess.HOME);
+ mEnv.state.stack.push(TestEnv.FOLDER_0);
+
+ mEnv.state.sortModel.sortByUser(
+ SortModel.SORT_DIMENSION_ID_TITLE, SortDimension.SORT_DIRECTION_ASCENDING);
+
+ mEnv.providers.get(TestRootsAccess.HOME.authority)
+ .setNextChildDocumentsReturns(TestEnv.FILE_APK, TestEnv.FILE_GIF);
+
+ mHandler.loadDocumentsForCurrentStack();
+ mActivity.loaderManager.runAsyncTaskLoader(AbstractActionHandler.LOADER_ID);
+
+ assertEquals(2, mEnv.model.getItemCount());
+ String[] modelIds = mEnv.model.getModelIds();
+ assertEquals(TestEnv.FILE_APK, mEnv.model.getDocument(modelIds[0]));
+ assertEquals(TestEnv.FILE_GIF, mEnv.model.getDocument(modelIds[1]));
+ }
+
+ @Test
+ public void testLoadChildrenDocuments_failsWithNonRecentsAndEmptyStack() throws Exception {
+ mEnv.state.stack.changeRoot(TestRootsAccess.HOME);
+
+ mEnv.providers.get(TestRootsAccess.HOME.authority)
+ .setNextChildDocumentsReturns(TestEnv.FILE_APK, TestEnv.FILE_GIF);
+
+ TestEventHandler<Model.Update> listener = new TestEventHandler<>();
+ mEnv.model.addUpdateListener(listener::accept);
+
+ mHandler.loadDocumentsForCurrentStack();
+
+ assertTrue(listener.getLastValue().hasException());
+ }
}
diff --git a/tests/unit/com/android/documentsui/FocusManagerTest.java b/tests/unit/com/android/documentsui/FocusManagerTest.java
index 886e634..d4d7648 100644
--- a/tests/unit/com/android/documentsui/FocusManagerTest.java
+++ b/tests/unit/com/android/documentsui/FocusManagerTest.java
@@ -21,7 +21,7 @@
import com.android.documentsui.base.Features;
import com.android.documentsui.dirlist.TestData;
-import com.android.documentsui.dirlist.TestModel;
+import com.android.documentsui.testing.TestModel;
import com.android.documentsui.selection.SelectionManager;
import com.android.documentsui.testing.SelectionManagers;
import com.android.documentsui.testing.TestFeatures;
diff --git a/tests/unit/com/android/documentsui/dirlist/ModelTest.java b/tests/unit/com/android/documentsui/ModelTest.java
similarity index 88%
rename from tests/unit/com/android/documentsui/dirlist/ModelTest.java
rename to tests/unit/com/android/documentsui/ModelTest.java
index d004e13..c99b9cd 100644
--- a/tests/unit/com/android/documentsui/dirlist/ModelTest.java
+++ b/tests/unit/com/android/documentsui/ModelTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2017 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.
@@ -14,21 +14,16 @@
* limitations under the License.
*/
-package com.android.documentsui.dirlist;
+package com.android.documentsui;
-import android.content.ContentResolver;
-import android.content.ContextWrapper;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.MergeCursor;
import android.provider.DocumentsContract.Document;
import android.support.test.filters.SmallTest;
import android.test.AndroidTestCase;
-import android.test.mock.MockContentResolver;
-import com.android.documentsui.DirectoryResult;
import com.android.documentsui.base.DocumentInfo;
-import com.android.documentsui.base.Features;
import com.android.documentsui.roots.RootCursorWrapper;
import com.android.documentsui.testing.TestEventListener;
import com.android.documentsui.testing.TestFeatures;
@@ -67,21 +62,11 @@
private Cursor cursor;
private Model model;
- private TestContentProvider provider;
private TestFeatures features;
@Override
public void setUp() {
- final MockContentResolver resolver = new MockContentResolver();
features = new TestFeatures();
- new ContextWrapper(getContext()) {
- @Override
- public ContentResolver getContentResolver() {
- return resolver;
- }
- };
- provider = new TestContentProvider();
- resolver.addProvider(AUTHORITY, provider);
Random rand = new Random();
diff --git a/tests/unit/com/android/documentsui/dirlist/DirectoryAddonsAdapterTest.java b/tests/unit/com/android/documentsui/dirlist/DirectoryAddonsAdapterTest.java
index df66899..b61fea1 100644
--- a/tests/unit/com/android/documentsui/dirlist/DirectoryAddonsAdapterTest.java
+++ b/tests/unit/com/android/documentsui/dirlist/DirectoryAddonsAdapterTest.java
@@ -25,6 +25,7 @@
import android.test.AndroidTestCase;
import android.view.ViewGroup;
+import com.android.documentsui.Model;
import com.android.documentsui.base.Features;
import com.android.documentsui.base.State;
import com.android.documentsui.testing.TestEnv;
diff --git a/tests/unit/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java b/tests/unit/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java
index 548b0d0..0395732 100644
--- a/tests/unit/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java
+++ b/tests/unit/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java
@@ -21,6 +21,7 @@
import android.support.test.filters.MediumTest;
import android.test.AndroidTestCase;
+import com.android.documentsui.Model;
import com.android.documentsui.base.Features;
import com.android.documentsui.base.State;
import com.android.documentsui.testing.TestEnv;
diff --git a/tests/unit/com/android/documentsui/files/ActionHandlerTest.java b/tests/unit/com/android/documentsui/files/ActionHandlerTest.java
index 865a09f..3f1eb81 100644
--- a/tests/unit/com/android/documentsui/files/ActionHandlerTest.java
+++ b/tests/unit/com/android/documentsui/files/ActionHandlerTest.java
@@ -69,31 +69,18 @@
@Before
public void setUp() {
mEnv = TestEnv.create();
- mActivity = TestActivity.create();
+ mActivity = TestActivity.create(mEnv);
mActionModeAddons = new TestActionModeAddons();
mDialogs = new TestDialogController();
mCallback = new TestConfirmationCallback();
mEnv.roots.configurePm(mActivity.packageMgr);
mEnv.injector.dialogs = mDialogs;
- mHandler = new ActionHandler<>(
- mActivity,
- mEnv.state,
- mEnv.roots,
- mEnv.docs,
- mEnv.searchViewManager,
- mEnv::lookupExecutor,
- mActionModeAddons,
- null, // clipper, only used in drag/drop
- null, // clip storage, not utilized unless we venture into *jumbo* clip terratory.
- mEnv.injector
- );
+ mHandler = createHandler();
mDialogs.confirmNext();
mEnv.selectDocument(TestEnv.FILE_GIF);
-
- mHandler.reset(mEnv.model);
}
@Test
@@ -190,7 +177,7 @@
@Test
public void testShareSelectedDocuments_ArchivedFile() {
mEnv = TestEnv.create(ArchivesProvider.AUTHORITY);
- mHandler.reset(mEnv.model);
+ mHandler = createHandler();
mActivity.resources.strings.put(R.string.share_via, "Sharezilla!");
mEnv.selectionMgr.clearSelection();
@@ -407,4 +394,19 @@
assertNotNull(root);
assertEquals(expectedUri, root.getUri());
}
+
+ private ActionHandler<TestActivity> createHandler() {
+ return new ActionHandler<>(
+ mActivity,
+ mEnv.state,
+ mEnv.roots,
+ mEnv.docs,
+ mEnv.searchViewManager,
+ mEnv::lookupExecutor,
+ mActionModeAddons,
+ null, // clipper, only used in drag/drop
+ null, // clip storage, not utilized unless we venture into *jumbo* clip terratory.
+ mEnv.injector
+ );
+ }
}
diff --git a/tests/unit/com/android/documentsui/files/TestActivity.java b/tests/unit/com/android/documentsui/files/TestActivity.java
index 809830f..4d49649 100644
--- a/tests/unit/com/android/documentsui/files/TestActivity.java
+++ b/tests/unit/com/android/documentsui/files/TestActivity.java
@@ -17,14 +17,15 @@
package com.android.documentsui.files;
import com.android.documentsui.files.ActionHandler;
+import com.android.documentsui.testing.TestEnv;
import org.mockito.Mockito;
public abstract class TestActivity extends AbstractBase {
- public static TestActivity create() {
+ public static TestActivity create(TestEnv env) {
TestActivity activity = Mockito.mock(TestActivity.class, Mockito.CALLS_REAL_METHODS);
- activity.init();
+ activity.init(env);
return activity;
}
}
diff --git a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java
index d7d6405..a5bef37 100644
--- a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java
+++ b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java
@@ -58,7 +58,7 @@
@Before
public void setUp() {
mEnv = TestEnv.create();
- mActivity = TestActivity.create();
+ mActivity = TestActivity.create(mEnv);
mDialogs = new TestDialogController();
mEnv.roots.configurePm(mActivity.packageMgr);
@@ -75,8 +75,6 @@
mDialogs.confirmNext();
mEnv.selectionMgr.toggleSelection("1");
-
- mHandler.reset(mEnv.model);
}
@Test
diff --git a/tests/unit/com/android/documentsui/picker/TestActivity.java b/tests/unit/com/android/documentsui/picker/TestActivity.java
index 056eb18..b1622ae 100644
--- a/tests/unit/com/android/documentsui/picker/TestActivity.java
+++ b/tests/unit/com/android/documentsui/picker/TestActivity.java
@@ -16,13 +16,15 @@
package com.android.documentsui.picker;
+import com.android.documentsui.testing.TestEnv;
+
import org.mockito.Mockito;
public abstract class TestActivity extends AbstractBase {
- public static TestActivity create() {
+ public static TestActivity create(TestEnv env) {
TestActivity activity = Mockito.mock(TestActivity.class, Mockito.CALLS_REAL_METHODS);
- activity.init();
+ activity.init(env);
return activity;
}
}