Merge "Directory fragment refactoring. First attempt to to refactor fragments handling, state and app lifecycle." into nyc-dev

am: f0febdcf90

* commit 'f0febdcf90af31f8e5a0b86dc079108e99ebd514':
  Directory fragment refactoring. First attempt to to refactor fragments handling, state and app lifecycle.
This commit is contained in:
Aga Wronska
2016-02-22 18:35:22 +00:00
committed by android-build-merger
11 changed files with 281 additions and 200 deletions

View File

@@ -23,9 +23,11 @@ import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_LEAVE;
import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_NONE;
import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_SIDE;
import static com.android.internal.util.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
@@ -46,7 +48,7 @@ import android.view.Menu;
import android.view.MenuItem;
import android.widget.Spinner;
import com.android.documentsui.SearchManager.SearchManagerListener;
import com.android.documentsui.SearchViewManager.SearchManagerListener;
import com.android.documentsui.State.ViewMode;
import com.android.documentsui.dirlist.DirectoryFragment;
import com.android.documentsui.dirlist.Model;
@@ -64,14 +66,12 @@ import java.util.concurrent.Executor;
public abstract class BaseActivity extends Activity
implements SearchManagerListener, NavigationView.Environment {
static final String EXTRA_STATE = "state";
// See comments where this const is referenced for details.
private static final int DRAWER_NO_FIDDLE_DELAY = 1500;
State mState;
RootsCache mRoots;
SearchManager mSearchManager;
SearchViewManager mSearchManager;
DrawerController mDrawer;
NavigationView mNavigator;
@@ -121,7 +121,7 @@ public abstract class BaseActivity extends Activity
}
});
mSearchManager = new SearchManager(this);
mSearchManager = new SearchViewManager(this, icicle);
DocumentsToolbar toolbar = (DocumentsToolbar) findViewById(R.id.toolbar);
setActionBar(toolbar);
@@ -141,6 +141,7 @@ public abstract class BaseActivity extends Activity
boolean showMenu = super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.activity, menu);
mNavigator.update();
mSearchManager.install((DocumentsToolbar) findViewById(R.id.toolbar));
return showMenu;
@@ -188,7 +189,7 @@ public abstract class BaseActivity extends Activity
private State getState(@Nullable Bundle icicle) {
if (icicle != null) {
State state = icicle.<State>getParcelable(EXTRA_STATE);
State state = icicle.<State>getParcelable(Shared.EXTRA_STATE);
if (DEBUG) Log.d(mTag, "Recovered existing state object: " + state);
return state;
}
@@ -224,6 +225,9 @@ public abstract class BaseActivity extends Activity
}
void onRootPicked(RootInfo root) {
// Clicking on the current root removes search
mSearchManager.cancelSearch();
// Skip refreshing if root nor directory didn't change
if (root.equals(getCurrentRoot()) && mState.stack.size() == 1) {
return;
@@ -233,7 +237,6 @@ public abstract class BaseActivity extends Activity
// Clear entire backstack and start in new root
mState.onRootChanged(root);
mSearchManager.update(root);
// Recents is always in memory, so we just load it directly.
// Otherwise we delegate loading data from disk to a task
@@ -370,18 +373,18 @@ public abstract class BaseActivity extends Activity
* e.g. The current directory name displayed on the action bar won't get updated.
*/
@Override
public void onSearchChanged() {
refreshDirectory(ANIM_NONE);
public void onSearchChanged(@Nullable String query) {
// We should not get here if root is not searchable
checkState(canSearchRoot());
reloadSearch(query);
}
/**
* Called when search query changed.
* Updates the state object.
* @param query - New query
*/
@Override
public void onSearchQueryChanged(String query) {
mState.currentSearch = query;
private void reloadSearch(String query) {
FragmentManager fm = getFragmentManager();
RootInfo root = getCurrentRoot();
DocumentInfo cwd = getCurrentDirectory();
DirectoryFragment.reloadSearch(fm, root, cwd, query);
}
final List<String> getExcludedAuthorities() {
@@ -486,7 +489,8 @@ public abstract class BaseActivity extends Activity
@Override
protected void onSaveInstanceState(Bundle state) {
super.onSaveInstanceState(state);
state.putParcelable(EXTRA_STATE, mState);
state.putParcelable(Shared.EXTRA_STATE, mState);
mSearchManager.onSaveInstanceState(state);
}
@Override

View File

@@ -53,19 +53,22 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
private final RootInfo mRoot;
private final Uri mUri;
private final int mUserSortOrder;
private final boolean mSearchMode;
private DocumentInfo mDoc;
private CancellationSignal mSignal;
private DirectoryResult mResult;
public DirectoryLoader(Context context, int type, RootInfo root, DocumentInfo doc, Uri uri,
int userSortOrder) {
int userSortOrder, boolean inSearchMode) {
super(context, ProviderExecutor.forAuthority(root.authority));
mType = type;
mRoot = root;
mUri = uri;
mUserSortOrder = userSortOrder;
mDoc = doc;
mSearchMode = inSearchMode;
}
@Override
@@ -83,7 +86,7 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
final DirectoryResult result = new DirectoryResult();
// Use default document when searching
if (mType == DirectoryFragment.TYPE_SEARCH) {
if (mSearchMode) {
final Uri docUri = DocumentsContract.buildDocumentUri(
mRoot.authority, mRoot.documentId);
try {
@@ -106,7 +109,7 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
}
// Search always uses ranking from provider
if (mType == DirectoryFragment.TYPE_SEARCH) {
if (mSearchMode) {
result.sortOrder = State.SORT_ORDER_UNKNOWN;
}
@@ -127,7 +130,7 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
cursor = new RootCursorWrapper(mUri.getAuthority(), mRoot.rootId, cursor, -1);
if (mType == DirectoryFragment.TYPE_SEARCH) {
if (mSearchMode) {
// Filter directories out of search results, for now
cursor = new FilteringCursorWrapper(cursor, null, SEARCH_REJECT_MIMES);
}

View File

@@ -290,13 +290,8 @@ public class DocumentsActivity extends BaseActivity {
mState.derivedMode = visualMimes ? State.MODE_GRID : State.MODE_LIST;
}
} else {
if (mSearchManager.isSearching()) {
// Ongoing search
DirectoryFragment.showSearch(fm, root, mState.currentSearch, anim);
} else {
// Normal boring directory
DirectoryFragment.showDirectory(fm, root, cwd, anim);
}
}
// Forget any replacement target

View File

@@ -16,8 +16,10 @@
package com.android.documentsui;
import static com.android.documentsui.Shared.DEBUG;
import static com.android.documentsui.State.ACTION_MANAGE;
import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_NONE;
import static com.android.internal.util.Preconditions.checkState;
import android.app.Activity;
import android.app.Fragment;
@@ -119,17 +121,14 @@ public class DownloadsActivity extends BaseActivity {
final RootInfo root = getCurrentRoot();
final DocumentInfo cwd = getCurrentDirectory();
if (DEBUG) checkState(!mSearchManager.isSearching());
// If started in manage roots mode, there has to be a cwd (i.e. the root dir of the managed
// root).
Preconditions.checkNotNull(cwd);
if (mState.currentSearch != null) {
// Ongoing search
DirectoryFragment.showSearch(fm, root, mState.currentSearch, anim);
} else {
// Normal boring directory
DirectoryFragment.showDirectory(fm, root, cwd, anim);
}
// Normal boring directory
DirectoryFragment.showDirectory(fm, root, cwd, anim);
}
@Override

View File

@@ -82,7 +82,6 @@ public class FilesActivity extends BaseActivity {
if (mState.restored) {
if (DEBUG) Log.d(TAG, "Stack already resolved for uri: " + intent.getData());
refreshCurrentRootAndDirectory(ANIM_NONE);
} else if (!mState.stack.isEmpty()) {
// If a non-empty stack is present in our state it was read (presumably)
// from EXTRA_STACK intent extra. In this case, we'll skip other means of
@@ -248,16 +247,13 @@ public class FilesActivity extends BaseActivity {
final RootInfo root = getCurrentRoot();
final DocumentInfo cwd = getCurrentDirectory();
if (DEBUG) checkState(!mSearchManager.isSearching());
if (cwd == null) {
DirectoryFragment.showRecentsOpen(fm, anim);
} else {
if (mState.currentSearch != null) {
// Ongoing search
DirectoryFragment.showSearch(fm, root, mState.currentSearch, anim);
} else {
// Normal boring directory
DirectoryFragment.showDirectory(fm, root, cwd, anim);
}
// Normal boring directory
DirectoryFragment.showDirectory(fm, root, cwd, anim);
}
}

View File

@@ -16,6 +16,8 @@
package com.android.documentsui;
import android.annotation.Nullable;
import android.os.Bundle;
import android.provider.DocumentsContract.Root;
import android.text.TextUtils;
import android.util.Log;
@@ -31,28 +33,27 @@ import com.android.documentsui.model.RootInfo;
/**
* Manages searching UI behavior.
*/
final class SearchManager implements
final class SearchViewManager implements
SearchView.OnCloseListener, OnQueryTextListener, OnClickListener, OnFocusChangeListener {
public interface SearchManagerListener {
void onSearchChanged();
void onSearchQueryChanged(String query);
void onSearchChanged(@Nullable String query);
}
public static final String TAG = "SearchManger";
private SearchManagerListener mListener;
private String currentSearch;
private boolean mSearchExpanded;
private String mCurrentSearch;
private boolean mIgnoreNextClose;
private DocumentsToolbar mActionBar;
private MenuItem mMenu;
private SearchView mView;
public SearchManager(SearchManagerListener listener) {
public SearchViewManager(SearchManagerListener listener, @Nullable Bundle savedState) {
mListener = listener;
mCurrentSearch = savedState != null ? savedState.getString(Shared.EXTRA_QUERY) : null;
}
public void setSearchMangerListener(SearchManagerListener listener) {
@@ -69,6 +70,8 @@ final class SearchManager implements
mView.setOnCloseListener(this);
mView.setOnSearchClickListener(this);
mView.setOnQueryTextFocusChangeListener(this);
restoreSearch();
}
/**
@@ -80,12 +83,12 @@ final class SearchManager implements
return;
}
if (currentSearch != null) {
if (mCurrentSearch != null) {
mMenu.expandActionView();
mView.setIconified(false);
mView.clearFocus();
mView.setQuery(currentSearch, false);
mView.setQuery(mCurrentSearch, false);
} else {
mView.clearFocus();
if (!mView.isIconified()) {
@@ -108,13 +111,11 @@ final class SearchManager implements
return;
}
mMenu.setVisible(visible);
if (!visible) {
currentSearch = null;
if (mListener != null) {
mListener.onSearchQueryChanged(currentSearch);
}
mCurrentSearch = null;
}
mMenu.setVisible(visible);
}
/**
@@ -133,14 +134,37 @@ final class SearchManager implements
return false;
}
private void restoreSearch() {
if (isSearching()) {
onSearchExpanded();
mView.setIconified(false);
mView.setQuery(mCurrentSearch, false);
mView.clearFocus();
}
}
private void onSearchExpanded() {
mSearchExpanded = true;
mView.setBackgroundColor(
mView.getResources().getColor(R.color.menu_search_background, null));
}
boolean isSearching() {
return currentSearch != null;
return mCurrentSearch != null;
}
boolean isExpanded() {
return mSearchExpanded;
}
/**
* Called when owning activity is saving state to be used to restore state during creation.
* @param state Bundle to save state too
*/
public void onSaveInstanceState(Bundle state) {
state.putString(Shared.EXTRA_QUERY, mCurrentSearch);
}
/**
* Clears the search. Clears the SearchView background color. Triggers refreshing of the
* directory content.
@@ -159,11 +183,10 @@ final class SearchManager implements
mView.getResources().getColor(android.R.color.transparent, null));
// Refresh the directory if a search was done
if (currentSearch != null) {
currentSearch = null;
if (mCurrentSearch != null) {
mCurrentSearch = null;
if (mListener != null) {
mListener.onSearchQueryChanged(currentSearch);
mListener.onSearchChanged();
mListener.onSearchChanged(mCurrentSearch);
}
}
return false;
@@ -176,18 +199,15 @@ final class SearchManager implements
*/
@Override
public void onClick(View v) {
mSearchExpanded = true;
mView.setBackgroundColor(
mView.getResources().getColor(R.color.menu_search_background, null));
onSearchExpanded();
}
@Override
public boolean onQueryTextSubmit(String query) {
currentSearch = query;
mCurrentSearch = query;
mView.clearFocus();
if (mListener != null) {
mListener.onSearchQueryChanged(currentSearch);
mListener.onSearchChanged();
mListener.onSearchChanged(mCurrentSearch);
}
return true;
}
@@ -195,7 +215,7 @@ final class SearchManager implements
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
if (currentSearch == null) {
if (mCurrentSearch == null) {
mView.setIconified(true);
} else if (TextUtils.isEmpty(mView.getQuery())) {
cancelSearch();
@@ -207,4 +227,9 @@ final class SearchManager implements
public boolean onQueryTextChange(String newText) {
return false;
}
String getCurrentSearch() {
return mCurrentSearch;
}
}

View File

@@ -37,10 +37,50 @@ public final class Shared {
* specifies if the destination directory needs to create new directory or not.
*/
public static final String EXTRA_DIRECTORY_COPY = "com.android.documentsui.DIRECTORY_COPY";
public static final String EXTRA_STACK = "com.android.documentsui.STACK";
/**
* Extra flag used to store query of type String in the bundle.
*/
public static final String EXTRA_QUERY = "query";
/**
* Extra flag used to store state of type State in the bundle.
*/
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";
/**
* Extra flag used to store document of DocumentInfo type in the bundle.
*/
public static final String EXTRA_DOC = "document";
/**
* Extra flag used to store DirectoryFragment's selection of Selection type in the bundle.
*/
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";
public static final boolean DEBUG = true;
public static final String TAG = "Documents";
public static final String EXTRA_STACK = "com.android.documentsui.STACK";
/**

View File

@@ -105,9 +105,6 @@ public class State implements android.os.Parcelable {
private boolean mInitialRootChanged;
private boolean mInitialDocChanged;
/** Currently active search, overriding any stack. */
public String currentSearch;
/** Instance state for every shown directory */
public HashMap<String, SparseArray<Parcelable>> dirState = new HashMap<>();
@@ -186,7 +183,6 @@ public class State implements android.os.Parcelable {
out.writeInt(showAdvanced ? 1 : 0);
out.writeInt(restored ? 1 : 0);
DurableUtils.writeToParcel(out, stack);
out.writeString(currentSearch);
out.writeMap(dirState);
out.writeParcelable(selectedDocuments, 0);
out.writeList(selectedDocumentsForCopy);
@@ -217,7 +213,6 @@ public class State implements android.os.Parcelable {
state.showAdvanced = in.readInt() != 0;
state.restored = in.readInt() != 0;
DurableUtils.readFromParcel(in, state.stack);
state.currentSearch = in.readString();
in.readMap(state.dirState, loader);
state.selectedDocuments = in.readParcelable(loader);
in.readList(state.selectedDocumentsForCopy, loader);

View File

@@ -113,19 +113,26 @@ import java.util.List;
/**
* Display the documents inside a single directory.
*/
public class DirectoryFragment extends Fragment implements DocumentsAdapter.Environment {
public class DirectoryFragment extends Fragment
implements DocumentsAdapter.Environment, LoaderCallbacks<DirectoryResult> {
@IntDef(flag = true, value = {
TYPE_NORMAL,
TYPE_SEARCH,
TYPE_RECENT_OPEN
})
@Retention(RetentionPolicy.SOURCE)
public @interface ResultType {}
public static final int TYPE_NORMAL = 1;
public static final int TYPE_SEARCH = 2;
public static final int TYPE_RECENT_OPEN = 3;
public static final int TYPE_RECENT_OPEN = 2;
@IntDef(flag = true, value = {
ANIM_NONE,
ANIM_SIDE,
ANIM_LEAVE,
ANIM_ENTER
})
@Retention(RetentionPolicy.SOURCE)
public @interface AnimationType {}
public static final int ANIM_NONE = 1;
public static final int ANIM_SIDE = 2;
public static final int ANIM_LEAVE = 3;
@@ -146,12 +153,6 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi
private static final int DELETE_JOB_DELAY = 5500;
private static final int EMPTY_REVEAL_DURATION = 250;
private static final String EXTRA_TYPE = "type";
private static final String EXTRA_ROOT = "root";
private static final String EXTRA_DOC = "doc";
private static final String EXTRA_QUERY = "query";
private static final String EXTRA_IGNORE_STATE = "ignoreState";
private Model mModel;
private MultiSelectManager mSelectionManager;
private Model.UpdateListener mModelUpdateListener = new ModelUpdateListener();
@@ -164,12 +165,10 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi
private RecyclerView mRecView;
private ListeningGestureDetector mGestureDetector;
private @ResultType int mType = TYPE_NORMAL;
private String mStateKey;
private int mLastSortOrder = SORT_ORDER_UNKNOWN;
private DocumentsAdapter mAdapter;
private LoaderCallbacks<DirectoryResult> mCallbacks;
private FragmentTuner mTuner;
private DocumentClipper mClipper;
private GridLayoutManager mLayout;
@@ -178,6 +177,14 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi
private MessageBar mMessageBar;
private View mProgressBar;
// Directory fragment state is defined by: root, document, query, type, selection
private @ResultType int mType = TYPE_NORMAL;
private RootInfo mRoot;
private DocumentInfo mDocument;
private String mQuery = null;
private Selection mSelection = null;
private boolean mSearchMode = false;
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@@ -226,9 +233,16 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi
final Context context = getActivity();
final State state = getDisplayState();
final RootInfo root = getArguments().getParcelable(EXTRA_ROOT);
final DocumentInfo doc = getArguments().getParcelable(EXTRA_DOC);
mStateKey = buildStateKey(root, doc);
// Read arguments when object created for the first time.
// Restore state if fragment recreated.
Bundle args = savedInstanceState == null ? getArguments() : savedInstanceState;
mRoot = args.getParcelable(Shared.EXTRA_ROOT);
mDocument = args.getParcelable(Shared.EXTRA_DOC);
mStateKey = buildStateKey(mRoot, mDocument);
mQuery = args.getString(Shared.EXTRA_QUERY);
mType = args.getInt(Shared.EXTRA_TYPE);
mSelection = args.getParcelable(Shared.EXTRA_SELECTION);
mSearchMode = args.getBoolean(Shared.EXTRA_SEARCH_MODE);
mIconHelper = new IconHelper(context, MODE_GRID);
@@ -248,13 +262,6 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi
mRecView.addOnItemTouchListener(mGestureDetector);
// final here because we'll manually bump the listener iwhen we had an initial selection,
// but only after the model is fully loaded.
final SelectionModeListener selectionListener = new SelectionModeListener();
final Selection initialSelection = state.selectedDocuments.hasDirectoryKey(mStateKey)
? state.selectedDocuments
: null;
// TODO: instead of inserting the view into the constructor, extract listener-creation code
// and set the listener on the view after the fact. Then the view doesn't need to be passed
// into the selection manager.
@@ -264,9 +271,9 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi
state.allowMultiple
? MultiSelectManager.MODE_MULTIPLE
: MultiSelectManager.MODE_SINGLE,
initialSelection);
null);
mSelectionManager.addCallback(selectionListener);
mSelectionManager.addCallback(new SelectionModeListener());
mModel = new Model();
mModel.addUpdateListener(mAdapter);
@@ -275,8 +282,6 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi
// Make sure this is done after the RecyclerView is set up.
mFocusManager = new FocusManager(context, mRecView, mModel);
mType = getArguments().getInt(EXTRA_TYPE);
mTuner = FragmentTuner.pick(getContext(), state);
mClipper = new DocumentClipper(context);
@@ -286,7 +291,7 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi
hideGridTitles = MimePredicate.mimeMatches(
MimePredicate.VISUAL_MIMES, state.acceptMimes);
} else {
hideGridTitles = (doc != null) && doc.isGridTitlesHidden();
hideGridTitles = (mDocument != null) && mDocument.isGridTitlesHidden();
}
GridDocumentHolder.setHideTitles(hideGridTitles);
@@ -295,86 +300,20 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi
boolean svelte = am.isLowRamDevice() && (mType == TYPE_RECENT_OPEN);
mIconHelper.setThumbnailsEnabled(!svelte);
mCallbacks = new LoaderCallbacks<DirectoryResult>() {
@Override
public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) {
final String query = getArguments().getString(EXTRA_QUERY);
Uri contentsUri;
switch (mType) {
case TYPE_NORMAL:
contentsUri = DocumentsContract.buildChildDocumentsUri(
doc.authority, doc.documentId);
if (state.action == ACTION_MANAGE) {
contentsUri = DocumentsContract.setManageMode(contentsUri);
}
return new DirectoryLoader(
context, mType, root, doc, contentsUri, state.userSortOrder);
case TYPE_SEARCH:
contentsUri = DocumentsContract.buildSearchDocumentsUri(
root.authority, root.rootId, query);
if (state.action == ACTION_MANAGE) {
contentsUri = DocumentsContract.setManageMode(contentsUri);
}
return new DirectoryLoader(
context, mType, root, doc, contentsUri, state.userSortOrder);
case TYPE_RECENT_OPEN:
final RootsCache roots = DocumentsApplication.getRootsCache(context);
return new RecentsLoader(context, roots, state);
default:
throw new IllegalStateException("Unknown type " + mType);
}
}
@Override
public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
if (!isAdded()) return;
mModel.update(result);
state.derivedSortOrder = result.sortOrder;
updateDisplayState();
if (initialSelection != null) {
selectionListener.onSelectionChanged();
}
// Restore any previous instance state
final SparseArray<Parcelable> container = state.dirState.remove(mStateKey);
if (container != null && !getArguments().getBoolean(EXTRA_IGNORE_STATE, false)) {
getView().restoreHierarchyState(container);
} else if (mLastSortOrder != state.derivedSortOrder) {
// The derived sort order takes the user sort order into account, but applies
// directory-specific defaults when the user doesn't explicitly set the sort
// order. Scroll to the top if the sort order actually changed.
mRecView.smoothScrollToPosition(0);
}
mLastSortOrder = state.derivedSortOrder;
mTuner.onModelLoaded(mModel, mType);
}
@Override
public void onLoaderReset(Loader<DirectoryResult> loader) {
mModel.update(null);
}
};
// Kick off loader at least once
getLoaderManager().restartLoader(LOADER_ID, null, mCallbacks);
getLoaderManager().restartLoader(LOADER_ID, null, this);
}
@Override
public void onSaveInstanceState(Bundle outState) {
State state = getDisplayState();
if (mSelectionManager.hasSelection()) {
mSelectionManager.getSelection(state.selectedDocuments);
state.selectedDocuments.setDirectoryKey(mStateKey);
if (!state.selectedDocuments.isEmpty()) {
if (DEBUG) Log.d(TAG, "Persisted selection: " + state.selectedDocuments);
}
}
super.onSaveInstanceState(outState);
outState.putInt(Shared.EXTRA_TYPE, mType);
outState.putParcelable(Shared.EXTRA_ROOT, mRoot);
outState.putParcelable(Shared.EXTRA_DOC, mDocument);
outState.putString(Shared.EXTRA_QUERY, mQuery);
outState.putParcelable(Shared.EXTRA_SELECTION, mSelectionManager.getSelection());
outState.putBoolean(Shared.EXTRA_SEARCH_MODE, mSearchMode);
}
@Override
@@ -449,7 +388,7 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi
public void onSortOrderChanged() {
// Sort order is implemented as a sorting wrapper around directory
// results. So when sort order changes, we force a reload of the directory.
getLoaderManager().restartLoader(LOADER_ID, null, mCallbacks);
getLoaderManager().restartLoader(LOADER_ID, null, this);
}
public void onViewModeChanged() {
@@ -1342,7 +1281,7 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi
mProgressBar.setVisibility(model.isLoading() ? View.VISIBLE : View.GONE);
if (model.isEmpty()) {
if (getDisplayState().currentSearch != null) {
if (mSearchMode) {
showNoResults(getDisplayState().stack.root);
} else {
showEmptyDirectory();
@@ -1468,32 +1407,50 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi
public static void showDirectory(
FragmentManager fm, RootInfo root, DocumentInfo doc, int anim) {
show(fm, TYPE_NORMAL, root, doc, null, anim);
}
public static void showSearch(FragmentManager fm, RootInfo root, String query, int anim) {
show(fm, TYPE_SEARCH, root, null, query, anim);
create(fm, TYPE_NORMAL, root, doc, null, anim);
}
public static void showRecentsOpen(FragmentManager fm, int anim) {
show(fm, TYPE_RECENT_OPEN, null, null, null, anim);
create(fm, TYPE_RECENT_OPEN, null, null, null, anim);
}
private static void show(FragmentManager fm, int type, RootInfo root, DocumentInfo doc,
public static void reloadSearch(FragmentManager fm, RootInfo root, DocumentInfo doc,
String query) {
DirectoryFragment df = get(fm);
df.mQuery = query;
df.mRoot = root;
df.mDocument = doc;
df.mSearchMode = query != null;
df.getLoaderManager().restartLoader(LOADER_ID, null, df);
}
public static void reload(FragmentManager fm, int type, RootInfo root, DocumentInfo doc,
String query) {
DirectoryFragment df = get(fm);
df.mType = type;
df.mQuery = query;
df.mRoot = root;
df.mDocument = doc;
df.mSearchMode = query != null;
df.getLoaderManager().restartLoader(LOADER_ID, null, df);
}
public static void create(FragmentManager fm, int type, RootInfo root, DocumentInfo doc,
String query, int anim) {
final Bundle args = new Bundle();
args.putInt(EXTRA_TYPE, type);
args.putParcelable(EXTRA_ROOT, root);
args.putParcelable(EXTRA_DOC, doc);
args.putString(EXTRA_QUERY, query);
args.putInt(Shared.EXTRA_TYPE, type);
args.putParcelable(Shared.EXTRA_ROOT, root);
args.putParcelable(Shared.EXTRA_DOC, doc);
args.putString(Shared.EXTRA_QUERY, query);
final FragmentTransaction ft = fm.beginTransaction();
switch (anim) {
case ANIM_SIDE:
args.putBoolean(EXTRA_IGNORE_STATE, true);
args.putBoolean(Shared.EXTRA_IGNORE_STATE, true);
break;
case ANIM_ENTER:
args.putBoolean(EXTRA_IGNORE_STATE, true);
args.putBoolean(Shared.EXTRA_IGNORE_STATE, true);
ft.setCustomAnimations(R.animator.dir_enter, R.animator.dir_frozen);
break;
case ANIM_LEAVE:
@@ -1504,7 +1461,7 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi
final DirectoryFragment fragment = new DirectoryFragment();
fragment.setArguments(args);
ft.replace(R.id.container_directory, fragment);
ft.replace(getFragmentId(), fragment);
ft.commitAllowingStateLoss();
}
@@ -1518,9 +1475,77 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi
public static @Nullable DirectoryFragment get(FragmentManager fm) {
// TODO: deal with multiple directories shown at once
Fragment fragment = fm.findFragmentById(R.id.container_directory);
Fragment fragment = fm.findFragmentById(getFragmentId());
return fragment instanceof DirectoryFragment
? (DirectoryFragment) fragment
: null;
}
}
private static int getFragmentId() {
return R.id.container_directory;
}
@Override
public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) {
Context context = getActivity();
State state = getDisplayState();
Uri contentsUri;
switch (mType) {
case TYPE_NORMAL:
contentsUri = mSearchMode ? DocumentsContract.buildSearchDocumentsUri(
mRoot.authority, mRoot.rootId, mQuery)
: DocumentsContract.buildChildDocumentsUri(
mDocument.authority, mDocument.documentId);
if (state.action == ACTION_MANAGE) {
contentsUri = DocumentsContract.setManageMode(contentsUri);
}
return new DirectoryLoader(
context, mType, mRoot, mDocument, contentsUri, state.userSortOrder, mSearchMode);
case TYPE_RECENT_OPEN:
final RootsCache roots = DocumentsApplication.getRootsCache(context);
return new RecentsLoader(context, roots, state);
default:
throw new IllegalStateException("Unknown type " + mType);
}
}
@Override
public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
if (!isAdded()) return;
State state = getDisplayState();
mAdapter.notifyDataSetChanged();
mModel.update(result);
state.derivedSortOrder = result.sortOrder;
updateLayout(state.derivedMode);
if (mSelection != null) {
mSelectionManager.setItemsSelected(mSelection.toList(), true);
}
// Restore any previous instance state
final SparseArray<Parcelable> container = state.dirState.remove(mStateKey);
if (container != null && !getArguments().getBoolean(Shared.EXTRA_IGNORE_STATE, false)) {
getView().restoreHierarchyState(container);
} else if (mLastSortOrder != state.derivedSortOrder) {
// The derived sort order takes the user sort order into account, but applies
// directory-specific defaults when the user doesn't explicitly set the sort
// order. Scroll to the top if the sort order actually changed.
mRecView.smoothScrollToPosition(0);
}
mLastSortOrder = state.derivedSortOrder;
mTuner.onModelLoaded(mModel, mType, mSearchMode);
}
@Override
public void onLoaderReset(Loader<DirectoryResult> loader) {
mModel.update(null);
}
}

View File

@@ -84,7 +84,7 @@ public abstract class FragmentTuner {
return MimePredicate.mimeMatches(mState.acceptMimes, docMimeType);
}
abstract void onModelLoaded(Model model, @ResultType int resultType);
abstract void onModelLoaded(Model model, @ResultType int resultType, boolean isSearch);
/**
* Provides support for Platform specific specializations of DirectoryFragment.
@@ -166,7 +166,7 @@ public abstract class FragmentTuner {
}
@Override
void onModelLoaded(Model model, @ResultType int resultType) {
void onModelLoaded(Model model, @ResultType int resultType, boolean isSearch) {
// When launched into empty recents, show drawer
if (resultType == DirectoryFragment.TYPE_RECENT_OPEN
&& model.isEmpty()
@@ -211,7 +211,7 @@ public abstract class FragmentTuner {
}
@Override
void onModelLoaded(Model model, @ResultType int resultType) {}
void onModelLoaded(Model model, @ResultType int resultType, boolean isSearch) {}
}
/**
@@ -248,11 +248,10 @@ public abstract class FragmentTuner {
}
@Override
void onModelLoaded(Model model, @ResultType int resultType) {
void onModelLoaded(Model model, @ResultType int resultType, boolean isSearch) {
if (DEBUG) Log.d(TAG, "Handling model loaded. Has Location shcnage: " + mState.initialLocationHasChanged());
// When launched into empty root, open drawer.
if (model.isEmpty() && !mState.initialLocationHasChanged()
&& resultType != DirectoryFragment.TYPE_SEARCH) {
if (model.isEmpty() && !mState.initialLocationHasChanged() && !isSearch) {
if (DEBUG) Log.d(TAG, "Showing roots drawer cuz stuffs empty.");
// This noops on layouts without drawer, so no need to guard.

View File

@@ -687,7 +687,7 @@ public final class MultiSelectManager {
* Returns an unordered array of selected positions (including any
* provisional selections current in effect).
*/
private List<String> toList() {
public List<String> toList() {
ArrayList<String> selection = new ArrayList<String>(mSelection);
selection.addAll(mProvisionalSelection);
return selection;