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:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user