diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml index e67cc8a06da27..be21b55c4066e 100644 --- a/packages/DocumentsUI/res/values/strings.xml +++ b/packages/DocumentsUI/res/values/strings.xml @@ -247,9 +247,4 @@ Delete %1$d item? Delete %1$d items? - - Sorry, you can only select up to 1000 items at a time - - Could only select 1000 items diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java index d1285c83cc89d..efb83f20267ea 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java @@ -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(); diff --git a/packages/DocumentsUI/src/com/android/documentsui/RetainedState.java b/packages/DocumentsUI/src/com/android/documentsui/RetainedState.java new file mode 100644 index 0000000000000..57cf3b438b653 --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/RetainedState.java @@ -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; + } +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java index 161d70f745e6f..da0b347f4f93b 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java @@ -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 enabled = new HashSet(); - List modelIds = mAdapter.getModelIds(); - - // Get the current selection. - for (String id : mSelectionManager.getSelection().getAll()) { - enabled.add(id); - } - - for (String id : modelIds) { + // Exclude disabled files + List enabled = new ArrayList(); + 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