Loading res/values/strings.xml +0 −5 Original line number Diff line number Diff line Loading @@ -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> src/com/android/documentsui/BaseActivity.java +23 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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( Loading Loading @@ -578,6 +583,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(); Loading src/com/android/documentsui/RetainedState.java 0 → 100644 +36 −0 Original line number Diff line number Diff line /* * 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; } } src/com/android/documentsui/dirlist/DirectoryFragment.java +50 −63 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading @@ -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. Loading Loading @@ -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; Loading Loading @@ -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( Loading Loading @@ -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); } Loading Loading @@ -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; } Loading Loading @@ -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, Loading Loading @@ -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; } Loading Loading @@ -624,7 +617,15 @@ public class DirectoryFragment extends Fragment @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { 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); Loading Loading @@ -990,7 +991,7 @@ public class DirectoryFragment extends Fragment @Override public State getDisplayState() { return ((BaseActivity) getActivity()).getDisplayState(); return getBaseActivity().getDisplayState(); } @Override Loading Loading @@ -1120,17 +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. String[] alreadySelected = mSelectionManager.getSelection().getAll(); for (String id : alreadySelected) { 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); Loading @@ -1138,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); } } Loading Loading @@ -1528,7 +1513,7 @@ public class DirectoryFragment extends Fragment } if (!model.isLoading()) { ((BaseActivity) getActivity()).notifyDirectoryLoaded( getBaseActivity().notifyDirectoryLoaded( model.doc != null ? model.doc.derivedUri : null); } } Loading Loading @@ -1789,9 +1774,11 @@ public class DirectoryFragment extends Fragment updateLayout(state.derivedMode); if (mSelection != null) { mSelectionManager.setItemsSelected(mSelection.toList(), true); mSelection.clear(); if (mRestoredSelection != null) { mSelectionManager.setItemsSelected(mRestoredSelection.toList(), 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 Loading Loading
res/values/strings.xml +0 −5 Original line number Diff line number Diff line Loading @@ -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>
src/com/android/documentsui/BaseActivity.java +23 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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( Loading Loading @@ -578,6 +583,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(); Loading
src/com/android/documentsui/RetainedState.java 0 → 100644 +36 −0 Original line number Diff line number Diff line /* * 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; } }
src/com/android/documentsui/dirlist/DirectoryFragment.java +50 −63 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading @@ -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. Loading Loading @@ -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; Loading Loading @@ -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( Loading Loading @@ -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); } Loading Loading @@ -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; } Loading Loading @@ -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, Loading Loading @@ -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; } Loading Loading @@ -624,7 +617,15 @@ public class DirectoryFragment extends Fragment @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { 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); Loading Loading @@ -990,7 +991,7 @@ public class DirectoryFragment extends Fragment @Override public State getDisplayState() { return ((BaseActivity) getActivity()).getDisplayState(); return getBaseActivity().getDisplayState(); } @Override Loading Loading @@ -1120,17 +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. String[] alreadySelected = mSelectionManager.getSelection().getAll(); for (String id : alreadySelected) { 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); Loading @@ -1138,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); } } Loading Loading @@ -1528,7 +1513,7 @@ public class DirectoryFragment extends Fragment } if (!model.isLoading()) { ((BaseActivity) getActivity()).notifyDirectoryLoaded( getBaseActivity().notifyDirectoryLoaded( model.doc != null ? model.doc.derivedUri : null); } } Loading Loading @@ -1789,9 +1774,11 @@ public class DirectoryFragment extends Fragment updateLayout(state.derivedMode); if (mSelection != null) { mSelectionManager.setItemsSelected(mSelection.toList(), true); mSelection.clear(); if (mRestoredSelection != null) { mSelectionManager.setItemsSelected(mRestoredSelection.toList(), 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 Loading