diff options
author | 2017-03-06 18:33:23 -0800 | |
---|---|---|
committer | 2017-03-07 15:43:30 -0800 | |
commit | e967033315ed64bca8c89d601d187fd12754f1fb (patch) | |
tree | f19558fc828e7c6cff4df48482b3a04ecaae3238 | |
parent | 0c1f58a368a4762b93be7109638aaa592dc24a3e (diff) |
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
48 files changed, 701 insertions, 426 deletions
diff --git a/src/com/android/documentsui/AbstractActionHandler.java b/src/com/android/documentsui/AbstractActionHandler.java index 9e69c09b3..d4e964c2b 100644 --- a/src/com/android/documentsui/AbstractActionHandler.java +++ b/src/com/android/documentsui/AbstractActionHandler.java @@ -18,12 +18,17 @@ package com.android.documentsui; 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; 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 @@ import javax.annotation.Nullable; 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 @@ public abstract class AbstractActionHandler<T extends Activity & CommonAddons> 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 @@ public abstract class AbstractActionHandler<T extends Activity & CommonAddons> mSearchMgr = searchMgr; mExecutors = executors; mInjector = injector; + + mBindings = new LoaderBindings(); } @Override @@ -243,6 +256,18 @@ public abstract class AbstractActionHandler<T extends Activity & CommonAddons> } @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 @@ public abstract class AbstractActionHandler<T extends Activity & CommonAddons> .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 @@ public abstract class AbstractActionHandler<T extends Activity & CommonAddons> 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 7b0eef40e..33f1d7dcb 100644 --- a/src/com/android/documentsui/ActionHandler.java +++ b/src/com/android/documentsui/ActionHandler.java @@ -27,7 +27,6 @@ import com.android.documentsui.base.DocumentInfo; 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 @@ public interface ActionHandler { void showChooserForDoc(DocumentInfo doc); + void openRootDocument(@Nullable DocumentInfo rootDoc); + void openContainerDocument(DocumentInfo doc); void cutToClipboard(); @@ -107,9 +108,11 @@ public interface ActionHandler { 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 05365d589..d49908b83 100644 --- a/src/com/android/documentsui/BaseActivity.java +++ b/src/com/android/documentsui/BaseActivity.java @@ -22,7 +22,6 @@ import static com.android.documentsui.base.State.MODE_GRID; 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.AsyncTask; 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; 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 @@ public abstract class BaseActivity */ @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 @@ public abstract class BaseActivity new GetRootDocumentTask( root, this, - mInjector.actions::openContainerDocument) + mInjector.actions::openRootDocument) .executeOnExecutor(getExecutorForCurrentDirectory()); } } @@ -405,14 +406,6 @@ public abstract class BaseActivity 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 0a4dff163..4fbb3ffc5 100644 --- a/src/com/android/documentsui/DirectoryLoader.java +++ b/src/com/android/documentsui/DirectoryLoader.java @@ -29,6 +29,7 @@ import android.net.Uri; 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 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> { 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 2bc6dde65..3ba09d0d7 100644 --- a/src/com/android/documentsui/DragShadowBuilder.java +++ b/src/com/android/documentsui/DragShadowBuilder.java @@ -30,7 +30,6 @@ import android.widget.TextView; 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 8126e4df0..fe41aa97e 100644 --- a/src/com/android/documentsui/FocusManager.java +++ b/src/com/android/documentsui/FocusManager.java @@ -47,8 +47,7 @@ import com.android.documentsui.base.Procedure; 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 0a3f5bb12..8f22d2207 100644 --- a/src/com/android/documentsui/Injector.java +++ b/src/com/android/documentsui/Injector.java @@ -27,12 +27,13 @@ import com.android.documentsui.MenuManager.SelectionDetails; 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 class Injector<T extends ActionHandler> { public MenuManager menuManager; public DialogController dialogs; + public SearchViewManager searchManager; @ContentScoped public ActionModeController actionModeController; @@ -72,14 +74,24 @@ public class Injector<T extends ActionHandler> { 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 @@ public class Injector<T extends ActionHandler> { 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 index 43aed9a5e..9cc697209 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 @@ public class Model { 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 @@ public class Model { } } - void reset() { + public void reset() { mCursor = null; mCursorCount = 0; mIds = new String[0]; @@ -111,7 +112,8 @@ public class Model { 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 @@ public class Model { } @VisibleForTesting - int getItemCount() { + public int getItemCount() { return mCursorCount; } @@ -200,7 +202,7 @@ public class Model { 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 006553b97..3f35ba3ae 100644 --- a/src/com/android/documentsui/base/DocumentInfo.java +++ b/src/com/android/documentsui/base/DocumentInfo.java @@ -272,6 +272,10 @@ public class DocumentInfo implements Durable, Parcelable { 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 1423666c4..9aa15c2c4 100644 --- a/src/com/android/documentsui/base/DocumentStack.java +++ b/src/com/android/documentsui/base/DocumentStack.java @@ -168,7 +168,7 @@ public class DocumentStack implements Durable, Parcelable { } 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 7616edb42..0e24eda22 100644 --- a/src/com/android/documentsui/base/Shared.java +++ b/src/com/android/documentsui/base/Shared.java @@ -77,11 +77,6 @@ public final class Shared { 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 final class Shared { 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 1e307cebe..0c1b59c14 100644 --- a/src/com/android/documentsui/clipping/DocumentClipper.java +++ b/src/com/android/documentsui/clipping/DocumentClipper.java @@ -254,7 +254,7 @@ public final class DocumentClipper { */ 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 @@ public final class DocumentClipper { * @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 @@ public final class DocumentClipper { /** * 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 @@ public final class DocumentClipper { * * @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 51bf5892c..a55142367 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.GridLayoutManager; 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 @@ final class DirectoryAddonsAdapter extends DocumentsAdapter { 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 3da4276d9..8a0ba0def 100644 --- a/src/com/android/documentsui/dirlist/DirectoryFragment.java +++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java @@ -31,11 +31,9 @@ import android.app.ActivityManager; 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.ActionHandler; 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.ContentScoped; 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.DocumentClipper; 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 @@ import javax.annotation.Nullable; 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 @@ public class DirectoryFragment extends Fragment 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 @@ public class DirectoryFragment extends Fragment 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 @@ public class DirectoryFragment extends Fragment cancelThumbnailTask(view); } + mModel.removeUpdateListener(mModelUpdateListener); + mModel.removeUpdateListener(mAdapter.getModelUpdateListener()); + super.onDestroyView(); } @@ -312,7 +303,7 @@ public class DirectoryFragment extends Fragment 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 @@ public class DirectoryFragment extends Fragment 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 @@ public class DirectoryFragment extends Fragment : 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 class DirectoryFragment extends Fragment 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 @@ public class DirectoryFragment extends Fragment } 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 @@ public class DirectoryFragment extends Fragment 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,8 +1142,43 @@ public class DirectoryFragment extends Fragment mProgressBar.setVisibility(mModel.isLoading() ? View.VISIBLE : View.GONE); + updateLayout(mState.derivedMode); + mAdapter.notifyDataSetChanged(); + if (mRestoredSelection != null) { + mSelectionMgr.restoreSelection(mRestoredSelection); + // Note, we'll take care of cleaning up retained selection + // in the selection handler where we already have some + // specialized code to handle when selection was restored. + } + + // Restore any previous instance state + final SparseArray<Parcelable> container = + mState.dirConfigs.remove(mLocalState.getConfigKey()); + final int curSortedDimensionId = mState.sortModel.getSortedDimensionId(); + + final SortDimension curSortedDimension = + mState.sortModel.getDimensionById(curSortedDimensionId); + if (container != null + && !getArguments().getBoolean(Shared.EXTRA_IGNORE_STATE, false)) { + getView().restoreHierarchyState(container); + } else if (mLocalState.mLastSortDimensionId != curSortedDimension.getId() + || mLocalState.mLastSortDimensionId == SortModel.SORT_DIMENSION_ID_UNKNOWN + || mLocalState.mLastSortDirection != curSortedDimension.getSortDirection()) { + // Scroll to the top if the sort order actually changed. + mRecView.smoothScrollToPosition(0); + } + + mLocalState.mLastSortDimensionId = curSortedDimension.getId(); + mLocalState.mLastSortDirection = curSortedDimension.getSortDirection(); + + if (mRefreshLayout.isRefreshing()) { + new Handler().postDelayed( + () -> mRefreshLayout.setRefreshing(false), + REFRESH_SPINNER_TIMEOUT); + } + if (!mModel.isLoading()) { mActivity.notifyDirectoryLoaded( mModel.doc != null ? mModel.doc.derivedUri : null); @@ -1201,7 +1205,7 @@ public class DirectoryFragment extends Fragment @Override public boolean isInSearchMode() { - return mLocalState.mSearchMode; + return mInjector.searchManager.isSearching(); } @Override @@ -1235,109 +1239,4 @@ public class DirectoryFragment extends Fragment 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); - - if (mRestoredSelection != null) { - mSelectionMgr.restoreSelection(mRestoredSelection); - // Note, we'll take care of cleaning up retained selection - // in the selection handler where we already have some - // specialized code to handle when selection was restored. - } - - // Restore any previous instance state - final SparseArray<Parcelable> container = - mState.dirConfigs.remove(mLocalState.getConfigKey()); - final int curSortedDimensionId = mState.sortModel.getSortedDimensionId(); - - final SortDimension curSortedDimension = - mState.sortModel.getDimensionById(curSortedDimensionId); - if (container != null - && !getArguments().getBoolean(Shared.EXTRA_IGNORE_STATE, false)) { - getView().restoreHierarchyState(container); - } else if (mLocalState.mLastSortDimensionId != curSortedDimension.getId() - || mLocalState.mLastSortDimensionId == SortModel.SORT_DIMENSION_ID_UNKNOWN - || mLocalState.mLastSortDirection != curSortedDimension.getSortDirection()) { - // Scroll to the top if the sort order actually changed. - mRecView.smoothScrollToPosition(0); - } - - mLocalState.mLastSortDimensionId = curSortedDimension.getId(); - mLocalState.mLastSortDirection = curSortedDimension.getSortDirection(); - - if (mRefreshLayout.isRefreshing()) { - new Handler().postDelayed( - () -> mRefreshLayout.setRefreshing(false), - REFRESH_SPINNER_TIMEOUT); - } - } - - @Override - public void onLoaderReset(Loader<DirectoryResult> loader) { - if (DEBUG) Log.d(TAG, "Resetting loader for: " - + DocumentInfo.debugString(mLocalState.mDocument)); - - mRefreshLayout.setRefreshing(false); - } - } } diff --git a/src/com/android/documentsui/dirlist/DirectoryState.java b/src/com/android/documentsui/dirlist/DirectoryState.java index 6b849a2b4..b27a4b8f4 100644 --- a/src/com/android/documentsui/dirlist/DirectoryState.java +++ b/src/com/android/documentsui/dirlist/DirectoryState.java @@ -20,7 +20,6 @@ import android.os.Bundle; 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 @@ final class DirectoryState { 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 @@ final class DirectoryState { 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 9b1794c15..297584f83 100644 --- a/src/com/android/documentsui/dirlist/DocumentsAdapter.java +++ b/src/com/android/documentsui/dirlist/DocumentsAdapter.java @@ -24,6 +24,7 @@ import android.provider.DocumentsContract.Document; 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 6598a87bf..a0b0f645d 100644 --- a/src/com/android/documentsui/dirlist/DragStartListener.java +++ b/src/com/android/documentsui/dirlist/DragStartListener.java @@ -26,6 +26,7 @@ import android.util.Log; 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 d8044bb5f..fdaf7862e 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.R; 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 c7c2526cf..1e080bf7a 100644 --- a/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java +++ b/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java @@ -18,7 +18,6 @@ package com.android.documentsui.dirlist; 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.provider.DocumentsContract.Document; 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 5720ea15b..6926fb81e 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.DocumentClipper; 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 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa 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 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa mDialogs = injector.dialogs; mClipper = clipper; mClipStore = clipStore; + mModel = injector.getModel(); } @Override @@ -113,7 +114,6 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa new GetRootDocumentTask( root, mActivity, - mActivity::isDestroyed, (DocumentInfo rootDoc) -> dropOnCallback(event, rootDoc, root) ).executeOnExecutor(mExecutors.lookup(root.authority)); return true; @@ -150,12 +150,11 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa 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 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa 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 b5989b2d3..c90948859 100644 --- a/src/com/android/documentsui/files/FilesActivity.java +++ b/src/com/android/documentsui/files/FilesActivity.java @@ -134,6 +134,8 @@ public class FilesActivity extends BaseActivity implements ActionHandler.Addons DocumentsApplication.getClipStore(this), mInjector); + mInjector.searchManager = mSearchManager; + mActivityInputHandler = new ActivityInputHandler(mInjector.actions::deleteSelectedDocuments); mSharedInputHandler = @@ -274,7 +276,7 @@ public class FilesActivity extends BaseActivity implements ActionHandler.Addons 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 c85af8a52..189a88904 100644 --- a/src/com/android/documentsui/files/QuickViewIntentBuilder.java +++ b/src/com/android/documentsui/files/QuickViewIntentBuilder.java @@ -38,7 +38,7 @@ import android.util.Range; 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 a92685a4d..c00345c17 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.RootInfo; 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 @@ class ActionHandler<T extends Activity & Addons> extends AbstractActionHandler<T 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 40c307aa0..1c7fc1e1c 100644 --- a/src/com/android/documentsui/picker/PickActivity.java +++ b/src/com/android/documentsui/picker/PickActivity.java @@ -111,11 +111,13 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons { 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 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons { ProviderExecutor::forAuthority, mInjector); + mInjector.searchManager = mSearchManager; + Intent intent = getIntent(); mSharedInputHandler = @@ -277,8 +281,7 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons { 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 1b773eacd..64ec16877 100644 --- a/src/com/android/documentsui/queries/SearchViewManager.java +++ b/src/com/android/documentsui/queries/SearchViewManager.java @@ -307,7 +307,7 @@ public class SearchViewManager implements 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 1503ae19f..e7fdadf75 100644 --- a/src/com/android/documentsui/roots/GetRootDocumentTask.java +++ b/src/com/android/documentsui/roots/GetRootDocumentTask.java @@ -41,30 +41,15 @@ public class GetRootDocumentTask extends TimeoutTask<Void, DocumentInfo> { 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 class GetRootDocumentTask extends TimeoutTask<Void, DocumentInfo> { 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 7fbe8e9ee..1d4950f82 100644 --- a/src/com/android/documentsui/sidebar/RootsFragment.java +++ b/src/com/android/documentsui/sidebar/RootsFragment.java @@ -480,7 +480,6 @@ public class RootsFragment extends Fragment implements ItemDragListener.DragHost 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 bdc01251d..6df4c9019 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 000000000..272645ee5 --- /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 15a724e63..93e8b4481 100644 --- a/tests/common/com/android/documentsui/TestActivity.java +++ b/tests/common/com/android/documentsui/TestActivity.java @@ -19,6 +19,7 @@ package com.android.documentsui; 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.Intent; 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 abstract class TestActivity extends AbstractBase { 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 abstract class TestActivity extends AbstractBase { 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 @@ public abstract class TestActivity extends AbstractBase { 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 @@ public abstract class TestActivity extends AbstractBase { @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 c0505f279..000000000 --- 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 714062d50..d5122f271 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.Context; import android.content.ContextWrapper; import android.test.mock.MockContentResolver; +import com.android.documentsui.testing.TestDocumentsProvider; + public final class TestContext { /** @@ -28,7 +30,7 @@ public final class TestContext { */ 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 c702c9abb..760d0e271 100644 --- a/tests/common/com/android/documentsui/dirlist/TestDocumentsAdapter.java +++ b/tests/common/com/android/documentsui/dirlist/TestDocumentsAdapter.java @@ -19,7 +19,7 @@ package com.android.documentsui.dirlist; 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 5dfc8552f..4912475f1 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.ActionHandler; 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 class TestActionHandler extends AbstractActionHandler<TestActivity> { public TestActionHandler(TestEnv env) { super( - TestActivity.create(), + TestActivity.create(env), env.state, env.roots, env.docs, @@ -81,9 +81,4 @@ public class TestActionHandler extends AbstractActionHandler<TestActivity> { 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 000000000..1dc89852c --- /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 000000000..19c3c2b7a --- /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 3118487f0..3e177d587 100644 --- a/tests/common/com/android/documentsui/testing/TestEnv.java +++ b/tests/common/com/android/documentsui/testing/TestEnv.java @@ -18,21 +18,24 @@ package com.android.documentsui.testing; 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 class TestEnv { 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 @@ public class TestEnv { 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 000000000..334439830 --- /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 index 462f5bf28..517b1f47f 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; 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 18b4bb9d2..2a2595314 100644 --- a/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java +++ b/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java @@ -28,17 +28,17 @@ import android.provider.DocumentsContract.Path; 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.Test; 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 @@ public class AbstractActionHandlerTest { @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 @@ public class AbstractActionHandlerTest { protected void launchToDefaultLocation() { throw new UnsupportedOperationException(); } - - @Override - public <T extends ActionHandler> T reset(Model model) { - return null; - } }; } @@ -204,4 +198,39 @@ public class AbstractActionHandlerTest { 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 886e63488..d4d76489a 100644 --- a/tests/unit/com/android/documentsui/FocusManagerTest.java +++ b/tests/unit/com/android/documentsui/FocusManagerTest.java @@ -21,7 +21,7 @@ import android.test.suitebuilder.annotation.SmallTest; 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 index d004e13f2..c99b9cd2d 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 @@ public class ModelTest extends AndroidTestCase { 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 df66899bc..b61fea154 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.support.v7.widget.RecyclerView; 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 548b0d07c..03957325e 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.database.Cursor; 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 865a09f91..3f1eb8169 100644 --- a/tests/unit/com/android/documentsui/files/ActionHandlerTest.java +++ b/tests/unit/com/android/documentsui/files/ActionHandlerTest.java @@ -69,31 +69,18 @@ public class ActionHandlerTest { @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 @@ public class ActionHandlerTest { @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 @@ public class ActionHandlerTest { 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 809830fe0..4d49649a5 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 d7d64056f..a5bef3756 100644 --- a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java +++ b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java @@ -58,7 +58,7 @@ public class ActionHandlerTest { @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 @@ public class ActionHandlerTest { 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 056eb18df..b1622ae16 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; } } |