Preserve selection in memory during rotation.
Using onRetainNonConfigurationInstance Eliminate 1k limit on selections. Bug: 28194201 Failed cherrypick merge from https://googleplex-android-review.git.corp.google.com/#/c/1105581/ Change-Id: Id986c23867df33f1e53bb2999ba51babde5d535b
This commit is contained in:
@@ -247,9 +247,4 @@
|
||||
<item quantity="one">Delete <xliff:g id="count" example="1">%1$d</xliff:g> item?</item>
|
||||
<item quantity="other">Delete <xliff:g id="count" example="3">%1$d</xliff:g> items?</item>
|
||||
</plurals>
|
||||
<!-- Snackbar shown to users who wanted to select more than 1000 items (files or directories). -->
|
||||
<string name="too_many_selected">Sorry, you can only select up to 1000 items at a time</string>
|
||||
<!-- Snackbar shown to users who wanted to select all, but there were too many items (files or directories).
|
||||
Only the first 1000 items are selected in such case. -->
|
||||
<string name="too_many_in_select_all">Could only select 1000 items</string>
|
||||
</resources>
|
||||
|
||||
@@ -72,6 +72,7 @@ public abstract class BaseActivity extends Activity
|
||||
private static final String BENCHMARK_TESTING_PACKAGE = "com.android.documentsui.appperftests";
|
||||
|
||||
State mState;
|
||||
@Nullable RetainedState mRetainedState;
|
||||
RootsCache mRoots;
|
||||
SearchViewManager mSearchManager;
|
||||
DrawerController mDrawer;
|
||||
@@ -123,6 +124,10 @@ public abstract class BaseActivity extends Activity
|
||||
mState = getState(icicle);
|
||||
Metrics.logActivityLaunch(this, mState, intent);
|
||||
|
||||
// we're really interested in retainining state in our very complex
|
||||
// DirectoryFragment. So we do a little code yoga to extend
|
||||
// support to that fragment.
|
||||
mRetainedState = (RetainedState) getLastNonConfigurationInstance();
|
||||
mRoots = DocumentsApplication.getRootsCache(this);
|
||||
|
||||
getContentResolver().registerContentObserver(
|
||||
@@ -576,6 +581,24 @@ public abstract class BaseActivity extends Activity
|
||||
super.onRestoreInstanceState(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate ths call to the current fragment so it can save selection.
|
||||
* Feel free to expand on this with other useful state.
|
||||
*/
|
||||
@Override
|
||||
public RetainedState onRetainNonConfigurationInstance() {
|
||||
RetainedState retained = new RetainedState();
|
||||
DirectoryFragment fragment = DirectoryFragment.get(getFragmentManager());
|
||||
if (fragment != null) {
|
||||
fragment.retainState(retained);
|
||||
}
|
||||
return retained;
|
||||
}
|
||||
|
||||
public @Nullable RetainedState getRetainedState() {
|
||||
return mRetainedState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSearchExpanded() {
|
||||
return mSearchManager.isExpanded();
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.support.annotation.Nullable;
|
||||
|
||||
import com.android.documentsui.dirlist.MultiSelectManager.Selection;
|
||||
|
||||
/**
|
||||
* Object used to collect retained state from activity and fragments. Used
|
||||
* with Activity#onRetainNonConfigurationInstance. Information stored in
|
||||
* this class should be primarily ephemeral as instances of the class
|
||||
* only last across configuration changes (like device rotation). When
|
||||
* an application is fully town down, all instances are lost, fa-evah!
|
||||
*/
|
||||
public final class RetainedState {
|
||||
public @Nullable Selection selection;
|
||||
|
||||
public boolean hasSelection() {
|
||||
return selection != null;
|
||||
}
|
||||
}
|
||||
@@ -17,16 +17,14 @@
|
||||
package com.android.documentsui.dirlist;
|
||||
|
||||
import static com.android.documentsui.Shared.DEBUG;
|
||||
import static com.android.documentsui.Shared.MAX_DOCS_IN_INTENT;
|
||||
import static com.android.documentsui.State.MODE_GRID;
|
||||
import static com.android.documentsui.State.MODE_LIST;
|
||||
import static com.android.documentsui.State.SORT_ORDER_UNKNOWN;
|
||||
import static com.android.documentsui.model.DocumentInfo.getCursorInt;
|
||||
import static com.android.documentsui.model.DocumentInfo.getCursorString;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.StringRes;
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.AlertDialog;
|
||||
@@ -47,12 +45,9 @@ import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v13.view.DragStartHelper;
|
||||
import android.support.v7.widget.GridLayoutManager;
|
||||
@@ -96,6 +91,7 @@ import com.android.documentsui.Metrics;
|
||||
import com.android.documentsui.MimePredicate;
|
||||
import com.android.documentsui.R;
|
||||
import com.android.documentsui.RecentsLoader;
|
||||
import com.android.documentsui.RetainedState;
|
||||
import com.android.documentsui.RootsCache;
|
||||
import com.android.documentsui.Shared;
|
||||
import com.android.documentsui.Snackbars;
|
||||
@@ -109,14 +105,16 @@ import com.android.documentsui.services.FileOperationService;
|
||||
import com.android.documentsui.services.FileOperationService.OpType;
|
||||
import com.android.documentsui.services.FileOperations;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Display the documents inside a single directory.
|
||||
@@ -174,8 +172,9 @@ public class DirectoryFragment extends Fragment
|
||||
private RootInfo mRoot;
|
||||
private DocumentInfo mDocument;
|
||||
private String mQuery = null;
|
||||
// Save selection found during creation so it can be restored during directory loading.
|
||||
private Selection mSelection = null;
|
||||
// Note, we use !null to indicate that selection was restored (from rotation).
|
||||
// So don't fiddle with this field unless you've got the bigger picture in mind.
|
||||
private @Nullable Selection mRestoredSelection = null;
|
||||
private boolean mSearchMode = false;
|
||||
|
||||
private @Nullable BandController mBandController;
|
||||
@@ -267,10 +266,17 @@ public class DirectoryFragment extends Fragment
|
||||
mStateKey = buildStateKey(mRoot, mDocument);
|
||||
mQuery = args.getString(Shared.EXTRA_QUERY);
|
||||
mType = args.getInt(Shared.EXTRA_TYPE);
|
||||
final Selection selection = args.getParcelable(Shared.EXTRA_SELECTION);
|
||||
mSelection = selection != null ? selection : new Selection();
|
||||
mSearchMode = args.getBoolean(Shared.EXTRA_SEARCH_MODE);
|
||||
|
||||
// Restore any selection we may have squirreled away in retained state.
|
||||
@Nullable RetainedState retained = getBaseActivity().getRetainedState();
|
||||
if (retained != null && retained.hasSelection()) {
|
||||
// We claim the selection for ourselves and null it out once used
|
||||
// so we don't have a rando selection hanging around in RetainedState.
|
||||
mRestoredSelection = retained.selection;
|
||||
retained.selection = null;
|
||||
}
|
||||
|
||||
mIconHelper = new IconHelper(context, MODE_GRID);
|
||||
|
||||
mAdapter = new SectionBreakDocumentsAdapterWrapper(
|
||||
@@ -326,29 +332,18 @@ public class DirectoryFragment extends Fragment
|
||||
getLoaderManager().restartLoader(LOADER_ID, null, this);
|
||||
}
|
||||
|
||||
public void retainState(RetainedState state) {
|
||||
state.selection = mSelectionManager.getSelection(new Selection());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
mSelectionManager.getSelection(mSelection);
|
||||
|
||||
outState.putInt(Shared.EXTRA_TYPE, mType);
|
||||
outState.putParcelable(Shared.EXTRA_ROOT, mRoot);
|
||||
outState.putParcelable(Shared.EXTRA_DOC, mDocument);
|
||||
outState.putString(Shared.EXTRA_QUERY, mQuery);
|
||||
|
||||
// Workaround. To avoid crash, write only up to 512 KB of selection.
|
||||
// If more files are selected, then the selection will be lost.
|
||||
final Parcel parcel = Parcel.obtain();
|
||||
try {
|
||||
mSelection.writeToParcel(parcel, 0);
|
||||
if (parcel.dataSize() <= 512 * 1024) {
|
||||
outState.putParcelable(Shared.EXTRA_SELECTION, mSelection);
|
||||
}
|
||||
} finally {
|
||||
parcel.recycle();
|
||||
}
|
||||
|
||||
outState.putBoolean(Shared.EXTRA_SEARCH_MODE, mSearchMode);
|
||||
}
|
||||
|
||||
@@ -405,7 +400,7 @@ public class DirectoryFragment extends Fragment
|
||||
final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
|
||||
if (mTuner.isDocumentEnabled(docMimeType, docFlags)) {
|
||||
final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor);
|
||||
((BaseActivity) getActivity()).onDocumentPicked(doc, mModel);
|
||||
getBaseActivity().onDocumentPicked(doc, mModel);
|
||||
mSelectionManager.clearSelection();
|
||||
return true;
|
||||
}
|
||||
@@ -497,6 +492,12 @@ public class DirectoryFragment extends Fragment
|
||||
return mColumnCount;
|
||||
}
|
||||
|
||||
// Support method to replace getOwner().foo() with something
|
||||
// slightly less clumsy like: getOwner().foo().
|
||||
private BaseActivity getBaseActivity() {
|
||||
return (BaseActivity) getActivity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages the integration between our ActionMode and MultiSelectManager, initiating
|
||||
* ActionMode when there is a selection, canceling it when there is no selection,
|
||||
@@ -529,15 +530,7 @@ public class DirectoryFragment extends Fragment
|
||||
if (!mTuner.canSelectType(docMimeType, docFlags)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mSelected.size() >= MAX_DOCS_IN_INTENT) {
|
||||
Snackbars.makeSnackbar(
|
||||
getActivity(),
|
||||
R.string.too_many_selected,
|
||||
Snackbar.LENGTH_SHORT)
|
||||
.show();
|
||||
return false;
|
||||
}
|
||||
return mTuner.canSelectType(docMimeType, docFlags);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -624,7 +617,15 @@ public class DirectoryFragment extends Fragment
|
||||
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||
mRecView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||
if (mRestoredSelection != null) {
|
||||
// This is a careful little song and dance to avoid haptic feedback
|
||||
// when selection has been restored after rotation. We're
|
||||
// also responsible for cleaning up restored selection so the
|
||||
// object dones't unnecessarily hang around.
|
||||
mRestoredSelection = null;
|
||||
} else {
|
||||
mRecView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||
}
|
||||
|
||||
int size = mSelectionManager.getSelection().size();
|
||||
mode.getMenuInflater().inflate(R.menu.mode_directory, menu);
|
||||
@@ -990,7 +991,7 @@ public class DirectoryFragment extends Fragment
|
||||
|
||||
@Override
|
||||
public State getDisplayState() {
|
||||
return ((BaseActivity) getActivity()).getDisplayState();
|
||||
return getBaseActivity().getDisplayState();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1120,16 +1121,9 @@ public class DirectoryFragment extends Fragment
|
||||
public void selectAllFiles() {
|
||||
Metrics.logUserAction(getContext(), Metrics.USER_ACTION_SELECT_ALL);
|
||||
|
||||
// Exclude disabled files.
|
||||
Set<String> enabled = new HashSet<String>();
|
||||
List<String> modelIds = mAdapter.getModelIds();
|
||||
|
||||
// Get the current selection.
|
||||
for (String id : mSelectionManager.getSelection().getAll()) {
|
||||
enabled.add(id);
|
||||
}
|
||||
|
||||
for (String id : modelIds) {
|
||||
// Exclude disabled files
|
||||
List<String> enabled = new ArrayList<String>();
|
||||
for (String id : mAdapter.getModelIds()) {
|
||||
Cursor cursor = getModel().getItem(id);
|
||||
if (cursor == null) {
|
||||
Log.w(TAG, "Skipping selection. Can't obtain cursor for modeId: " + id);
|
||||
@@ -1137,15 +1131,7 @@ public class DirectoryFragment extends Fragment
|
||||
}
|
||||
String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
|
||||
int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
|
||||
if (mTuner.canSelectType(docMimeType, docFlags)) {
|
||||
if (enabled.size() >= MAX_DOCS_IN_INTENT) {
|
||||
Snackbars.makeSnackbar(
|
||||
getActivity(),
|
||||
R.string.too_many_in_select_all,
|
||||
Snackbar.LENGTH_SHORT)
|
||||
.show();
|
||||
break;
|
||||
}
|
||||
if (isDocumentEnabled(docMimeType, docFlags)) {
|
||||
enabled.add(id);
|
||||
}
|
||||
}
|
||||
@@ -1527,7 +1513,7 @@ public class DirectoryFragment extends Fragment
|
||||
}
|
||||
|
||||
if (!model.isLoading()) {
|
||||
((BaseActivity) getActivity()).notifyDirectoryLoaded(
|
||||
getBaseActivity().notifyDirectoryLoaded(
|
||||
model.doc != null ? model.doc.derivedUri : null);
|
||||
}
|
||||
}
|
||||
@@ -1797,9 +1783,11 @@ public class DirectoryFragment extends Fragment
|
||||
|
||||
updateLayout(state.derivedMode);
|
||||
|
||||
if (mSelection != null) {
|
||||
mSelectionManager.setItemsSelected(mSelection.getAll(), true);
|
||||
mSelection.clear();
|
||||
if (mRestoredSelection != null) {
|
||||
mSelectionManager.setItemsSelected(mRestoredSelection.getAll(), true);
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user