blob: 4684317c13d928313dad30f718f751cf85876d85 [file] [log] [blame]
Jeff Sharkeye22d02e2013-04-26 16:54:55 -07001/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Steve McKayf68210e2015-11-03 15:23:16 -080017package com.android.documentsui.dirlist;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070018
Steve McKayd0805062016-09-15 14:30:38 -070019import static com.android.documentsui.base.DocumentInfo.getCursorInt;
20import static com.android.documentsui.base.DocumentInfo.getCursorString;
Steve McKayd9caa6a2016-09-15 16:36:45 -070021import static com.android.documentsui.base.Shared.DEBUG;
22import static com.android.documentsui.base.State.MODE_GRID;
23import static com.android.documentsui.base.State.MODE_LIST;
Steve McKay0599a442015-05-05 14:50:00 -070024
Steve McKaycb9eb422016-02-09 16:17:24 -080025import android.annotation.IntDef;
Steve McKay2bab2f82016-06-03 09:23:39 -070026import android.annotation.StringRes;
Ben Kwaf5858932015-04-07 15:43:39 -070027import android.app.Activity;
Jeff Sharkeyf63b7772013-10-01 17:57:41 -070028import android.app.ActivityManager;
Steve McKay003097d2016-02-23 10:06:50 -080029import android.app.AlertDialog;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070030import android.app.Fragment;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070031import android.app.FragmentManager;
32import android.app.FragmentTransaction;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070033import android.app.LoaderManager.LoaderCallbacks;
Vladislav Kaznacheev89b90332015-05-01 13:46:57 -070034import android.content.ClipData;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070035import android.content.Context;
Steve McKay003097d2016-02-23 10:06:50 -080036import android.content.DialogInterface;
Jeff Sharkey873daa32013-08-18 17:38:20 -070037import android.content.Intent;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070038import android.content.Loader;
Jeff Sharkeyac9e6272013-08-31 21:27:44 -070039import android.database.Cursor;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070040import android.net.Uri;
41import android.os.Bundle;
Garfield, Taneba5bb92016-07-22 10:30:49 -070042import android.os.Handler;
Jeff Sharkeyc8ae7a52013-09-18 16:26:49 -070043import android.os.Parcelable;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070044import android.provider.DocumentsContract;
Jeff Sharkeyac9e6272013-08-31 21:27:44 -070045import android.provider.DocumentsContract.Document;
Garfield, Tanca7c0882016-07-18 16:45:27 -070046import android.support.v4.widget.SwipeRefreshLayout;
Steve McKayef280152015-06-11 10:10:49 -070047import android.support.v7.widget.GridLayoutManager;
Steve McKayc5ecf892015-12-22 18:15:31 -080048import android.support.v7.widget.GridLayoutManager.SpanSizeLookup;
Steve McKayef280152015-06-11 10:10:49 -070049import android.support.v7.widget.RecyclerView;
Steve McKayef280152015-06-11 10:10:49 -070050import android.support.v7.widget.RecyclerView.RecyclerListener;
51import android.support.v7.widget.RecyclerView.ViewHolder;
Steve McKay927d8002016-05-18 16:25:41 -070052import android.text.BidiFormatter;
Jeff Sharkey8a8fb672013-05-07 12:41:33 -070053import android.util.Log;
Jeff Sharkeyc8ae7a52013-09-18 16:26:49 -070054import android.util.SparseArray;
Ben Linebf2a172016-06-03 13:46:52 -070055import android.view.ContextMenu;
Vladislav Kaznacheevb6da7222015-05-01 14:18:57 -070056import android.view.DragEvent;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070057import android.view.LayoutInflater;
Ben Linebf2a172016-06-03 13:46:52 -070058import android.view.MenuInflater;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070059import android.view.MenuItem;
Steve McKay74956af2016-06-30 21:03:06 -070060import android.view.MotionEvent;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070061import android.view.View;
62import android.view.ViewGroup;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070063import android.widget.ImageView;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070064import android.widget.TextView;
65
Steve McKay6d20d192016-09-21 17:57:10 -070066import com.android.documentsui.ActionHandler;
Steve McKayf68210e2015-11-03 15:23:16 -080067import com.android.documentsui.BaseActivity;
Steve McKayd9caa6a2016-09-15 16:36:45 -070068import com.android.documentsui.BaseActivity.RetainedState;
Steve McKayf68210e2015-11-03 15:23:16 -080069import com.android.documentsui.DirectoryLoader;
70import com.android.documentsui.DirectoryResult;
Steve McKayf68210e2015-11-03 15:23:16 -080071import com.android.documentsui.DocumentsApplication;
Steve McKay17b761e2016-09-20 17:26:46 -070072import com.android.documentsui.FocusManager;
Garfield, Tan804133e2016-04-20 15:13:56 -070073import com.android.documentsui.ItemDragListener;
Ben Lin7c35b032016-05-31 13:24:01 -070074import com.android.documentsui.MenuManager;
Steve McKayf68210e2015-11-03 15:23:16 -080075import com.android.documentsui.MessageBar;
Aga Wronskab0289052016-03-31 16:37:40 -070076import com.android.documentsui.Metrics;
Steve McKayf68210e2015-11-03 15:23:16 -080077import com.android.documentsui.R;
Steve McKay1aeb3952016-02-18 09:48:39 -080078import com.android.documentsui.RecentsLoader;
Steve McKayf68210e2015-11-03 15:23:16 -080079import com.android.documentsui.Snackbars;
Garfield, Taneba5bb92016-07-22 10:30:49 -070080import com.android.documentsui.ThumbnailCache;
Steve McKayd9caa6a2016-09-15 16:36:45 -070081import com.android.documentsui.base.DocumentInfo;
Ben Lin2d6caf32016-09-19 17:48:54 -070082import com.android.documentsui.base.EventHandler;
Steve McKay990f76e2016-09-16 12:36:58 -070083import com.android.documentsui.base.EventListener;
Steve McKayd9caa6a2016-09-15 16:36:45 -070084import com.android.documentsui.base.Events.InputEvent;
85import com.android.documentsui.base.Events.MotionInputEvent;
86import com.android.documentsui.base.RootInfo;
87import com.android.documentsui.base.Shared;
88import com.android.documentsui.base.State;
89import com.android.documentsui.base.State.ViewMode;
Ben Linc5e3e8e2016-07-13 18:16:36 -070090import com.android.documentsui.clipping.DocumentClipper;
Garfield, Tan9666ce62016-07-12 11:02:09 -070091import com.android.documentsui.clipping.UrisSupplier;
Steve McKayf68210e2015-11-03 15:23:16 -080092import com.android.documentsui.dirlist.MultiSelectManager.Selection;
Steve McKay16e0c1f2016-09-15 12:41:13 -070093import com.android.documentsui.picker.PickActivity;
Steve McKayd9caa6a2016-09-15 16:36:45 -070094import com.android.documentsui.roots.RootsCache;
Garfield, Tan48334772016-06-28 17:17:38 -070095import com.android.documentsui.services.FileOperation;
Steve McKayc83baa02016-01-06 18:32:13 -080096import com.android.documentsui.services.FileOperationService;
Ben Kwabc7df442016-02-02 23:00:02 -080097import com.android.documentsui.services.FileOperationService.OpType;
Steve McKayc83baa02016-01-06 18:32:13 -080098import com.android.documentsui.services.FileOperations;
Garfield, Tan11d23482016-08-05 09:33:29 -070099import com.android.documentsui.sorting.SortDimension;
100import com.android.documentsui.sorting.SortDimension.SortDirection;
101import com.android.documentsui.sorting.SortModel;
Steve McKayce0b14c2016-05-03 11:25:52 -0700102
Garfield, Tanb7e5f6b2016-06-30 18:27:47 -0700103import java.io.IOException;
Steve McKaycb9eb422016-02-09 16:17:24 -0800104import java.lang.annotation.Retention;
105import java.lang.annotation.RetentionPolicy;
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700106import java.util.ArrayList;
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700107import java.util.List;
Steve McKay2bab2f82016-06-03 09:23:39 -0700108
109import javax.annotation.Nullable;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700110
111/**
112 * Display the documents inside a single directory.
113 */
Aga Wronskaaf5ace52016-02-17 13:50:42 -0800114public class DirectoryFragment extends Fragment
Garfield, Tan804133e2016-04-20 15:13:56 -0700115 implements DocumentsAdapter.Environment, LoaderCallbacks<DirectoryResult>,
Garfield, Tanca7c0882016-07-18 16:45:27 -0700116 ItemDragListener.DragHost, SwipeRefreshLayout.OnRefreshListener {
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700117
Steve McKaycb9eb422016-02-09 16:17:24 -0800118 @IntDef(flag = true, value = {
119 TYPE_NORMAL,
Steve McKaycb9eb422016-02-09 16:17:24 -0800120 TYPE_RECENT_OPEN
121 })
122 @Retention(RetentionPolicy.SOURCE)
123 public @interface ResultType {}
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700124 public static final int TYPE_NORMAL = 1;
Aga Wronskaaf5ace52016-02-17 13:50:42 -0800125 public static final int TYPE_RECENT_OPEN = 2;
Jeff Sharkey5b535922013-08-02 15:55:26 -0700126
Aga Wronskaaf5ace52016-02-17 13:50:42 -0800127 @IntDef(flag = true, value = {
Steve McKayf1719342016-02-17 15:02:01 -0800128 REQUEST_COPY_DESTINATION
129 })
130 @Retention(RetentionPolicy.SOURCE)
131 public @interface RequestCode {}
Ben Kwaf5858932015-04-07 15:43:39 -0700132 public static final int REQUEST_COPY_DESTINATION = 1;
133
Steve McKay97b4be42016-01-20 15:09:35 -0800134 private static final String TAG = "DirectoryFragment";
135 private static final int LOADER_ID = 42;
Steve McKay97b4be42016-01-20 15:09:35 -0800136
Garfield, Taneba5bb92016-07-22 10:30:49 -0700137 private static final int CACHE_EVICT_LIMIT = 100;
138 private static final int REFRESH_SPINNER_DISMISS_DELAY = 500;
139
Ben Lin26819d72016-08-17 12:45:05 -0700140 private final Model mModel = new Model();
Steve McKay990f76e2016-09-16 12:36:58 -0700141 private final EventListener<Model.Update> mModelUpdateListener = new ModelUpdateListener();
Steve McKayc8f165c2016-09-20 13:54:05 -0700142
143 // This dependency is informally "injected" from the owning Activity in our onCreate method.
144 private FragmentTuner mTuner;
145
146 // This dependency is informally "injected" from the owning Activity in our onCreate method.
Steve McKay6d20d192016-09-21 17:57:10 -0700147 private FocusManager mFocusManager;
148
149 // This dependency is informally "injected" from the owning Activity in our onCreate method.
150 private ActionHandler<?> mActionHandler;
151
152 // This dependency is informally "injected" from the owning Activity in our onCreate method.
Steve McKayc8f165c2016-09-20 13:54:05 -0700153 private MenuManager mMenuManager;
154
Steve McKay74956af2016-06-30 21:03:06 -0700155 private MultiSelectManager mSelectionMgr;
Garfield Tan84bd0f12016-09-12 14:18:32 -0700156 private ActionModeController mActionModeController;
157 private SelectionMetadata mSelectionMetadata;
Steve McKay2d1e4a32016-07-13 13:03:16 -0700158 private UserInputHandler<InputEvent> mInputHandler;
Steve McKayc8f165c2016-09-20 13:54:05 -0700159 private @Nullable BandController mBandController;
160 private DragHoverListener mDragHoverListener;
Ben Kwa8e3fd762015-12-17 10:37:00 -0800161 private IconHelper mIconHelper;
Garfield, Tanca7c0882016-07-18 16:45:27 -0700162 private SwipeRefreshLayout mRefreshLayout;
Steve McKayef280152015-06-11 10:10:49 -0700163 private View mEmptyView;
164 private RecyclerView mRecView;
Garfield, Tan171e6f52016-07-29 14:44:58 -0700165 private View mFileList;
Steve McKayef280152015-06-11 10:10:49 -0700166
Steve McKayef280152015-06-11 10:10:49 -0700167 private DocumentsAdapter mAdapter;
Steve McKay1f199482015-05-20 15:58:42 -0700168 private DocumentClipper mClipper;
Ben Kwabc7df442016-02-02 23:00:02 -0800169 private GridLayoutManager mLayout;
Steve McKayd57f5fa2015-07-23 16:33:41 -0700170 private int mColumnCount = 1; // This will get updated when layout changes.
Steve McKay1f199482015-05-20 15:58:42 -0700171
Steve McKayf63e9382016-03-01 08:28:02 -0800172 private LayoutInflater mInflater;
Ben Kwac42fa402015-09-16 08:04:37 -0700173 private MessageBar mMessageBar;
Ben Kwa379e1762015-09-21 10:49:52 -0700174 private View mProgressBar;
Ben Kwac42fa402015-09-16 08:04:37 -0700175
Ben Lin2d67ee22016-09-20 16:01:51 -0700176 private Config mConfig;
Steve McKayc8f165c2016-09-20 13:54:05 -0700177
Steve McKay2bab2f82016-06-03 09:23:39 -0700178 // Note, we use !null to indicate that selection was restored (from rotation).
179 // So don't fiddle with this field unless you've got the bigger picture in mind.
180 private @Nullable Selection mRestoredSelection = null;
Garfield, Tan804133e2016-04-20 15:13:56 -0700181
Garfield, Tan11d23482016-08-05 09:33:29 -0700182 private SortModel.UpdateListener mSortListener = (model, updateType) -> {
183 // Only when sort order has changed do we need to trigger another loading.
Garfield, Tan61f564b2016-08-16 13:36:15 -0700184 if ((updateType & SortModel.UPDATE_TYPE_SORTING) != 0) {
Garfield, Tan11d23482016-08-05 09:33:29 -0700185 getLoaderManager().restartLoader(LOADER_ID, null, this);
186 }
187 };
Garfield, Tan171e6f52016-07-29 14:44:58 -0700188
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700189 @Override
190 public View onCreateView(
191 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Steve McKayf63e9382016-03-01 08:28:02 -0800192 mInflater = inflater;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700193 final View view = inflater.inflate(R.layout.fragment_directory, container, false);
194
Ben Kwac42fa402015-09-16 08:04:37 -0700195 mMessageBar = MessageBar.create(getChildFragmentManager());
Steve McKay9de0da62016-08-25 15:18:23 -0700196 mProgressBar = getActivity().findViewById(R.id.progressbar);
197 assert(mProgressBar != null);
Garfield, Tanca7c0882016-07-18 16:45:27 -0700198
199 mRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.refresh_layout);
200 mRefreshLayout.setOnRefreshListener(this);
201
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700202 mEmptyView = view.findViewById(android.R.id.empty);
Ben Kwa74e5d412016-02-10 07:46:35 -0800203 mRecView = (RecyclerView) view.findViewById(R.id.dir_list);
Steve McKayef280152015-06-11 10:10:49 -0700204 mRecView.setRecyclerListener(
205 new RecyclerListener() {
206 @Override
207 public void onViewRecycled(ViewHolder holder) {
208 cancelThumbnailTask(holder.itemView);
209 }
210 });
Ben Kwa60d91e42015-10-20 15:02:33 -0700211 mRecView.setItemAnimator(new DirectoryItemAnimator(getActivity()));
Garfield, Tan171e6f52016-07-29 14:44:58 -0700212 mFileList = view.findViewById(R.id.file_list);
Ben Kwa98bb6cf2015-10-14 08:00:27 -0700213
Ben Lin2abb4c72016-08-01 18:05:23 -0700214 final int edgeHeight = (int) getResources().getDimension(R.dimen.autoscroll_edge_height);
Ben Lin35f99e02016-08-31 12:46:04 -0700215 mDragHoverListener = DragHoverListener.create(
Ben Lin2abb4c72016-08-01 18:05:23 -0700216 edgeHeight, new DirectoryDragListener(this), mRecView);
Garfield, Tan804133e2016-04-20 15:13:56 -0700217
Ben Kwaa8c0da42016-02-25 14:10:40 -0800218 // Make the recycler and the empty views responsive to drop events.
Ben Lin35f99e02016-08-31 12:46:04 -0700219 mRecView.setOnDragListener(mDragHoverListener);
220 mEmptyView.setOnDragListener(mDragHoverListener);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700221
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700222 return view;
223 }
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700224
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700225 @Override
Jeff Sharkeyf63b7772013-10-01 17:57:41 -0700226 public void onDestroyView() {
Steve McKay74956af2016-06-30 21:03:06 -0700227 mSelectionMgr.clearSelection();
Jeff Sharkeyf63b7772013-10-01 17:57:41 -0700228
229 // Cancel any outstanding thumbnail requests
Steve McKayef280152015-06-11 10:10:49 -0700230 final int count = mRecView.getChildCount();
Jeff Sharkeyf63b7772013-10-01 17:57:41 -0700231 for (int i = 0; i < count; i++) {
Steve McKayef280152015-06-11 10:10:49 -0700232 final View view = mRecView.getChildAt(i);
233 cancelThumbnailTask(view);
Jeff Sharkeyf63b7772013-10-01 17:57:41 -0700234 }
Aga Wronska7e5b9632016-02-26 11:36:07 -0800235
236 super.onDestroyView();
Jeff Sharkeyf63b7772013-10-01 17:57:41 -0700237 }
238
239 @Override
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700240 public void onActivityCreated(Bundle savedInstanceState) {
241 super.onActivityCreated(savedInstanceState);
242
243 final Context context = getActivity();
Steve McKayb85f90c2015-11-09 17:38:35 +0900244 final State state = getDisplayState();
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700245
Aga Wronskaaf5ace52016-02-17 13:50:42 -0800246 // Read arguments when object created for the first time.
247 // Restore state if fragment recreated.
248 Bundle args = savedInstanceState == null ? getArguments() : savedInstanceState;
Steve McKayc8f165c2016-09-20 13:54:05 -0700249
Ben Lin2d67ee22016-09-20 16:01:51 -0700250 mConfig = new Config();
251 mConfig.restore(args);
Jeff Sharkey9656a532013-09-13 13:42:19 -0700252
Steve McKay2bab2f82016-06-03 09:23:39 -0700253 // Restore any selection we may have squirreled away in retained state.
254 @Nullable RetainedState retained = getBaseActivity().getRetainedState();
255 if (retained != null && retained.hasSelection()) {
256 // We claim the selection for ourselves and null it out once used
257 // so we don't have a rando selection hanging around in RetainedState.
258 mRestoredSelection = retained.selection;
259 retained.selection = null;
260 }
261
Steve McKay7776aa52016-01-25 19:00:22 -0800262 mIconHelper = new IconHelper(context, MODE_GRID);
Ben Lin35f99e02016-08-31 12:46:04 -0700263 mClipper = DocumentsApplication.getDocumentClipper(getContext());
Steve McKayc5ecf892015-12-22 18:15:31 -0800264
265 mAdapter = new SectionBreakDocumentsAdapterWrapper(
266 this, new ModelBackedDocumentsAdapter(this, mIconHelper));
267
Steve McKayef280152015-06-11 10:10:49 -0700268 mRecView.setAdapter(mAdapter);
269
Ben Lin81afd7f2016-08-22 18:24:03 -0700270 mLayout = new GridLayoutManager(getContext(), mColumnCount) {
271 @Override
272 public void onLayoutCompleted(RecyclerView.State state) {
273 super.onLayoutCompleted(state);
274 mFocusManager.onLayoutCompleted();
275 }
276 };
277
Ben Kwabc7df442016-02-02 23:00:02 -0800278 SpanSizeLookup lookup = mAdapter.createSpanSizeLookup();
279 if (lookup != null) {
280 mLayout.setSpanSizeLookup(lookup);
281 }
282 mRecView.setLayoutManager(mLayout);
283
Ben Kwa24be5d32015-08-27 16:04:46 -0700284 // TODO: instead of inserting the view into the constructor, extract listener-creation code
285 // and set the listener on the view after the fact. Then the view doesn't need to be passed
Ben Kwaa017bc12015-10-07 14:15:12 -0700286 // into the selection manager.
Steve McKay74956af2016-06-30 21:03:06 -0700287 mSelectionMgr = new MultiSelectManager(
Steve McKaye45d1f02016-01-05 15:27:17 -0800288 mAdapter,
Steve McKay57394872015-08-12 14:48:34 -0700289 state.allowMultiple
290 ? MultiSelectManager.MODE_MULTIPLE
Garfield Tan84bd0f12016-09-12 14:18:32 -0700291 : MultiSelectManager.MODE_SINGLE,
292 this::canSetSelectionState);
Garfield Tan06c4b112016-09-14 11:05:58 -0700293 mSelectionMetadata = new SelectionMetadata(mModel::getItem);
Garfield Tan84bd0f12016-09-12 14:18:32 -0700294 mSelectionMgr.addItemCallback(mSelectionMetadata);
Steve McKay79a6fe02016-06-30 12:09:34 -0700295
Steve McKay990f76e2016-09-16 12:36:58 -0700296 mModel.addUpdateListener(mAdapter.getModelUpdateListener());
Ben Lin26819d72016-08-17 12:45:05 -0700297 mModel.addUpdateListener(mModelUpdateListener);
298
Ben Lin42728802016-08-19 16:07:39 -0700299 final int edgeHeight = (int) getResources().getDimension(R.dimen.autoscroll_edge_height);
300 GestureSelector gestureSel = GestureSelector.create(
Steve McKay6d20d192016-09-21 17:57:10 -0700301 edgeHeight, mSelectionMgr, mRecView);
Ben Lin42728802016-08-19 16:07:39 -0700302
Steve McKay990f76e2016-09-16 12:36:58 -0700303 final BaseActivity activity = getBaseActivity();
Steve McKay6d20d192016-09-21 17:57:10 -0700304 mTuner = activity.getFragmentTuner(mModel, mConfig.mSearchMode);
Steve McKay17b761e2016-09-20 17:26:46 -0700305 mFocusManager = activity.getFocusManager(mRecView, mModel);
Steve McKay6d20d192016-09-21 17:57:10 -0700306 mActionHandler = activity.getActionHandler(mModel, mSelectionMgr);
Steve McKay990f76e2016-09-16 12:36:58 -0700307 mMenuManager = activity.getMenuManager();
Steve McKayf0fceb42016-09-07 17:35:55 -0700308
Ben Lin35f99e02016-08-31 12:46:04 -0700309 if (state.allowMultiple) {
310 mBandController = new BandController(mRecView, mAdapter, mSelectionMgr);
311 }
312
Steve McKayf0fceb42016-09-07 17:35:55 -0700313 DragStartListener mDragStartListener = mTuner.dragAndDropEnabled()
314 ? DragStartListener.create(
315 mIconHelper,
316 getContext(),
317 mModel,
318 mSelectionMgr,
319 mClipper,
320 getDisplayState(),
321 this::getModelId,
322 mRecView::findChildViewUnder,
323 getContext().getDrawable(com.android.internal.R.drawable.ic_doc_generic))
324 : DragStartListener.DUMMY;
325
Ben Lin2d6caf32016-09-19 17:48:54 -0700326 EventHandler<InputEvent> gestureHandler = state.allowMultiple
327 ? gestureSel::start
328 : EventHandler.createStub(false);
Steve McKay2d1e4a32016-07-13 13:03:16 -0700329 mInputHandler = new UserInputHandler<>(
Steve McKay6d20d192016-09-21 17:57:10 -0700330 mActionHandler,
Steve McKay74956af2016-06-30 21:03:06 -0700331 mFocusManager,
Steve McKay6d20d192016-09-21 17:57:10 -0700332 mSelectionMgr,
Garfield Tane6d61e32016-09-08 10:26:59 -0700333 (MotionEvent t) -> MotionInputEvent.obtain(t, mRecView),
Steve McKay74956af2016-06-30 21:03:06 -0700334 this::canSelect,
335 this::onRightClick,
Garfield Tane6d61e32016-09-08 10:26:59 -0700336 (DocumentDetails ignored) -> onDeleteSelectedDocuments(), // delete handler
Steve McKayf0fceb42016-09-07 17:35:55 -0700337 mDragStartListener::onTouchDragEvent,
Ben Lin2d6caf32016-09-19 17:48:54 -0700338 gestureHandler);
Steve McKay79a6fe02016-06-30 12:09:34 -0700339
Garfield Tane6d61e32016-09-08 10:26:59 -0700340 new ListeningGestureDetector(
Ben Lin35f99e02016-08-31 12:46:04 -0700341 this.getContext(),
342 mRecView,
343 mEmptyView,
Steve McKayf0fceb42016-09-07 17:35:55 -0700344 mDragStartListener::onMouseDragEvent,
Ben Lin35f99e02016-08-31 12:46:04 -0700345 gestureSel,
346 mInputHandler,
347 mBandController);
Steve McKayd5869592016-05-26 12:04:43 -0700348
Garfield Tan84bd0f12016-09-12 14:18:32 -0700349 mMenuManager = activity.getMenuManager();
350
351 mActionModeController = ActionModeController.create(
352 getContext(),
353 mSelectionMgr,
354 mMenuManager,
355 mSelectionMetadata,
356 getActivity(),
357 mRecView,
358 this::handleMenuItemClick);
359 mSelectionMgr.addCallback(mActionModeController);
Ben Kwa7461a952015-09-01 11:03:01 -0700360
Jeff Sharkeyf63b7772013-10-01 17:57:41 -0700361 final ActivityManager am = (ActivityManager) context.getSystemService(
362 Context.ACTIVITY_SERVICE);
Ben Lin2d67ee22016-09-20 16:01:51 -0700363 boolean svelte = am.isLowRamDevice() && (mConfig.mType == TYPE_RECENT_OPEN);
Ben Kwa8e3fd762015-12-17 10:37:00 -0800364 mIconHelper.setThumbnailsEnabled(!svelte);
Jeff Sharkeyf63b7772013-10-01 17:57:41 -0700365
Garfield, Tan11d23482016-08-05 09:33:29 -0700366 // If mDocument is null, we sort it by last modified by default because it's in Recents.
367 final boolean prefersLastModified =
Ben Lin2d67ee22016-09-20 16:01:51 -0700368 (mConfig.mDocument != null)
369 ? (mConfig.mDocument.flags & Document.FLAG_DIR_PREFERS_LAST_MODIFIED) != 0
Garfield, Tan11d23482016-08-05 09:33:29 -0700370 : true;
371 // Call this before adding the listener to avoid restarting the loader one more time
372 state.sortModel.setDefaultDimension(
373 prefersLastModified
374 ? SortModel.SORT_DIMENSION_ID_DATE
375 : SortModel.SORT_DIMENSION_ID_TITLE);
Garfield, Tan171e6f52016-07-29 14:44:58 -0700376
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700377 // Kick off loader at least once
Aga Wronskaaf5ace52016-02-17 13:50:42 -0800378 getLoaderManager().restartLoader(LOADER_ID, null, this);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700379 }
380
Garfield, Tan11d23482016-08-05 09:33:29 -0700381 @Override
382 public void onStart() {
383 super.onStart();
384
Garfield, Tan11d23482016-08-05 09:33:29 -0700385 // Add listener to update contents on sort model change
386 getDisplayState().sortModel.addListener(mSortListener);
387 }
388
389 @Override
390 public void onStop() {
391 super.onStop();
392
Garfield, Tan11d23482016-08-05 09:33:29 -0700393 getDisplayState().sortModel.removeListener(mSortListener);
394
395 // Remember last scroll location
Steve McKay17b761e2016-09-20 17:26:46 -0700396 final SparseArray<Parcelable> container = new SparseArray<>();
Garfield, Tan11d23482016-08-05 09:33:29 -0700397 getView().saveHierarchyState(container);
398 final State state = getDisplayState();
Ben Lin2d67ee22016-09-20 16:01:51 -0700399 state.dirConfigs.put(mConfig.getConfigKey(), container);
Garfield, Tan11d23482016-08-05 09:33:29 -0700400 }
401
Steve McKay2bab2f82016-06-03 09:23:39 -0700402 public void retainState(RetainedState state) {
Steve McKay74956af2016-06-30 21:03:06 -0700403 state.selection = mSelectionMgr.getSelection(new Selection());
Steve McKay2bab2f82016-06-03 09:23:39 -0700404 }
405
Jeff Sharkey42d26792013-09-06 13:22:09 -0700406 @Override
Steve McKay84fa3712016-02-08 19:09:42 -0800407 public void onSaveInstanceState(Bundle outState) {
Aga Wronskaaf5ace52016-02-17 13:50:42 -0800408 super.onSaveInstanceState(outState);
409
Ben Lin2d67ee22016-09-20 16:01:51 -0700410 mConfig.save(outState);
Steve McKay84fa3712016-02-08 19:09:42 -0800411 }
412
413 @Override
Steve McKayf1719342016-02-17 15:02:01 -0800414 public void onActivityResult(@RequestCode int requestCode, int resultCode, Intent data) {
Tomasz Mikolajewskia468d342016-04-07 11:12:07 +0900415 switch (requestCode) {
Steve McKayf1719342016-02-17 15:02:01 -0800416 case REQUEST_COPY_DESTINATION:
417 handleCopyResult(resultCode, data);
418 break;
419 default:
420 throw new UnsupportedOperationException("Unknown request code: " + requestCode);
Ben Kwaf5858932015-04-07 15:43:39 -0700421 }
Steve McKayf1719342016-02-17 15:02:01 -0800422 }
423
Ben Linebf2a172016-06-03 13:46:52 -0700424 @Override
425 public void onCreateContextMenu(ContextMenu menu,
426 View v,
427 ContextMenu.ContextMenuInfo menuInfo) {
428 super.onCreateContextMenu(menu, v, menuInfo);
Garfield Tan06c4b112016-09-14 11:05:58 -0700429 final MenuInflater inflater = getActivity().getMenuInflater();
Ben Linebf2a172016-06-03 13:46:52 -0700430
Garfield Tan06c4b112016-09-14 11:05:58 -0700431 final String modelId = getModelId(v);
432 if (modelId == null) {
Steve McKay990f76e2016-09-16 12:36:58 -0700433 // TODO: inject DirectoryDetails into MenuManager constructor
434 // Since both classes are supplied by Activity and created
435 // at the same time.
Steve McKaye5d6ca22016-09-19 20:27:24 -0700436 mMenuManager.inflateContextMenuForContainer(menu, inflater);
Garfield Tan239ab972016-09-16 13:33:48 -0700437 } else {
438 mMenuManager.inflateContextMenuForDocs(menu, inflater, mSelectionMetadata);
Ben Linebf2a172016-06-03 13:46:52 -0700439 }
440 }
441
442 @Override
443 public boolean onContextItemSelected(MenuItem item) {
444 return handleMenuItemClick(item);
445 }
446
Steve McKayf1719342016-02-17 15:02:01 -0800447 private void handleCopyResult(int resultCode, Intent data) {
Garfield, Tanedce5542016-06-17 15:32:28 -0700448
Ben Lin2d67ee22016-09-20 16:01:51 -0700449 FileOperation operation = mConfig.claimPendingOperation();
Garfield, Tanedce5542016-06-17 15:32:28 -0700450
Ben Kwaf5858932015-04-07 15:43:39 -0700451 if (resultCode == Activity.RESULT_CANCELED || data == null) {
452 // User pressed the back button or otherwise cancelled the destination pick. Don't
453 // proceed with the copy.
Garfield, Tanb7e5f6b2016-06-30 18:27:47 -0700454 operation.dispose();
Ben Kwaf5858932015-04-07 15:43:39 -0700455 return;
456 }
457
Garfield, Tan48334772016-06-28 17:17:38 -0700458 operation.setDestination(data.getParcelableExtra(Shared.EXTRA_STACK));
459
Garfield, Tana5588b62016-07-13 09:23:04 -0700460 BaseActivity activity = getBaseActivity();
461 FileOperations.start(activity, operation, activity.fileOpCallback);
Ben Kwaf5858932015-04-07 15:43:39 -0700462 }
463
Steve McKay74956af2016-06-30 21:03:06 -0700464 protected boolean onRightClick(InputEvent e) {
Garfield Tan239ab972016-09-16 13:33:48 -0700465 final View v;
466 final float x, y;
Steve McKay1597e942016-09-09 11:54:05 -0700467 if (e.isOverModelItem()) {
468 DocumentHolder doc = (DocumentHolder) e.getDocumentDetails();
Ben Linebf2a172016-06-03 13:46:52 -0700469
Garfield Tan239ab972016-09-16 13:33:48 -0700470 v = doc.itemView;
471 x = e.getX() - v.getLeft();
472 y = e.getY() - v.getTop();
473 } else {
474 v = (mEmptyView.getVisibility() == View.VISIBLE)
475 ? mEmptyView
476 : mRecView;
477 x = e.getX();
478 y = e.getY();
Steve McKay79a6fe02016-06-30 12:09:34 -0700479 }
Steve McKay74956af2016-06-30 21:03:06 -0700480
Garfield Tan239ab972016-09-16 13:33:48 -0700481 mMenuManager.showContextMenu(this, v, x, y);
Steve McKay1597e942016-09-09 11:54:05 -0700482
Ben Linebf2a172016-06-03 13:46:52 -0700483 return true;
484 }
485
Steve McKay7776aa52016-01-25 19:00:22 -0800486 public void onViewModeChanged() {
487 // Mode change is just visual change; no need to kick loader.
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700488 updateDisplayState();
489 }
490
491 private void updateDisplayState() {
Steve McKay7776aa52016-01-25 19:00:22 -0800492 State state = getDisplayState();
Steve McKayef280152015-06-11 10:10:49 -0700493 updateLayout(state.derivedMode);
Steve McKayef280152015-06-11 10:10:49 -0700494 mRecView.setAdapter(mAdapter);
495 }
496
497 /**
Ben Kwabc7df442016-02-02 23:00:02 -0800498 * Updates the layout after the view mode switches.
499 * @param mode The new view mode.
Steve McKayef280152015-06-11 10:10:49 -0700500 */
Ben Kwabc7df442016-02-02 23:00:02 -0800501 private void updateLayout(@ViewMode int mode) {
502 mColumnCount = calculateColumnCount(mode);
503 if (mLayout != null) {
504 mLayout.setSpanCount(mColumnCount);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700505 }
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700506
Steve McKayac155ab2016-01-12 11:14:33 -0800507 int pad = getDirectoryPadding(mode);
508 mRecView.setPadding(pad, pad, pad, pad);
Ben Kwabc7df442016-02-02 23:00:02 -0800509 mRecView.requestLayout();
Steve McKayd5869592016-05-26 12:04:43 -0700510 if (mBandController != null) {
511 mBandController.handleLayoutChanged();
512 }
Steve McKay7776aa52016-01-25 19:00:22 -0800513 mIconHelper.setViewMode(mode);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700514 }
515
Ben Kwabc7df442016-02-02 23:00:02 -0800516 private int calculateColumnCount(@ViewMode int mode) {
517 if (mode == MODE_LIST) {
518 // List mode is a "grid" with 1 column.
519 return 1;
520 }
521
Steve McKayfefcd702015-08-20 16:19:38 +0000522 int cellWidth = getResources().getDimensionPixelSize(R.dimen.grid_width);
523 int cellMargin = 2 * getResources().getDimensionPixelSize(R.dimen.grid_item_margin);
Steve McKayd57f5fa2015-07-23 16:33:41 -0700524 int viewPadding = mRecView.getPaddingLeft() + mRecView.getPaddingRight();
Steve McKayfefcd702015-08-20 16:19:38 +0000525
Ben Kwa2e3d4182016-03-16 13:01:20 -0700526 // RecyclerView sometimes gets a width of 0 (see b/27150284). Clamp so that we always lay
527 // out the grid with at least 2 columns.
528 int columnCount = Math.max(2,
Steve McKayfefcd702015-08-20 16:19:38 +0000529 (mRecView.getWidth() - viewPadding) / (cellWidth + cellMargin));
530
Steve McKayd57f5fa2015-07-23 16:33:41 -0700531 return columnCount;
532 }
533
Ben Kwabc7df442016-02-02 23:00:02 -0800534 private int getDirectoryPadding(@ViewMode int mode) {
Steve McKayac155ab2016-01-12 11:14:33 -0800535 switch (mode) {
536 case MODE_GRID:
Ben Kwabc7df442016-02-02 23:00:02 -0800537 return getResources().getDimensionPixelSize(R.dimen.grid_container_padding);
Steve McKayac155ab2016-01-12 11:14:33 -0800538 case MODE_LIST:
Ben Kwabc7df442016-02-02 23:00:02 -0800539 return getResources().getDimensionPixelSize(R.dimen.list_container_padding);
Steve McKayac155ab2016-01-12 11:14:33 -0800540 default:
541 throw new IllegalArgumentException("Unsupported layout mode: " + mode);
542 }
543 }
544
Steve McKayc5ecf892015-12-22 18:15:31 -0800545 @Override
546 public int getColumnCount() {
547 return mColumnCount;
548 }
549
Steve McKay2bab2f82016-06-03 09:23:39 -0700550 // Support method to replace getOwner().foo() with something
551 // slightly less clumsy like: getOwner().foo().
552 private BaseActivity getBaseActivity() {
553 return (BaseActivity) getActivity();
554 }
555
Ben Linebf2a172016-06-03 13:46:52 -0700556 private boolean handleMenuItemClick(MenuItem item) {
Steve McKay74956af2016-06-30 21:03:06 -0700557 Selection selection = mSelectionMgr.getSelection(new Selection());
Jeff Sharkey873daa32013-08-18 17:38:20 -0700558
Ben Linebf2a172016-06-03 13:46:52 -0700559 switch (item.getItemId()) {
560 case R.id.menu_open:
561 openDocuments(selection);
Garfield Tan84bd0f12016-09-12 14:18:32 -0700562 mActionModeController.finishActionMode();
Ben Linebf2a172016-06-03 13:46:52 -0700563 return true;
Jeff Sharkey873daa32013-08-18 17:38:20 -0700564
Garfield Tan239ab972016-09-16 13:33:48 -0700565 case R.id.menu_open_with:
566 showChooserForDoc(selection);
567 return true;
568
569 case R.id.menu_open_in_new_window:
570 openInNewWindow(selection);
571 return true;
572
Ben Linebf2a172016-06-03 13:46:52 -0700573 case R.id.menu_share:
574 shareDocuments(selection);
575 // TODO: Only finish selection if share action is completed.
Garfield Tan84bd0f12016-09-12 14:18:32 -0700576 mActionModeController.finishActionMode();
Ben Linebf2a172016-06-03 13:46:52 -0700577 return true;
Jeff Sharkey873daa32013-08-18 17:38:20 -0700578
Ben Linebf2a172016-06-03 13:46:52 -0700579 case R.id.menu_delete:
580 // deleteDocuments will end action mode if the documents are deleted.
581 // It won't end action mode if user cancels the delete.
582 deleteDocuments(selection);
583 return true;
Ben Kwacb4461f2015-05-05 11:50:11 -0700584
Ben Linebf2a172016-06-03 13:46:52 -0700585 case R.id.menu_copy_to:
586 transferDocuments(selection, FileOperationService.OPERATION_COPY);
587 // TODO: Only finish selection mode if copy-to is not canceled.
588 // Need to plum down into handling the way we do with deleteDocuments.
Garfield Tan84bd0f12016-09-12 14:18:32 -0700589 mActionModeController.finishActionMode();
Ben Linebf2a172016-06-03 13:46:52 -0700590 return true;
Ben Kwa41b26c12015-03-31 10:11:43 -0700591
Ben Linebf2a172016-06-03 13:46:52 -0700592 case R.id.menu_move_to:
593 // Exit selection mode first, so we avoid deselecting deleted documents.
Garfield Tan84bd0f12016-09-12 14:18:32 -0700594 mActionModeController.finishActionMode();
Ben Linebf2a172016-06-03 13:46:52 -0700595 transferDocuments(selection, FileOperationService.OPERATION_MOVE);
596 return true;
Steve McKay1f199482015-05-20 15:58:42 -0700597
Ben Linebf2a172016-06-03 13:46:52 -0700598 case R.id.menu_cut_to_clipboard:
599 cutSelectedToClipboard();
600 return true;
Ben Kwa512a6ba2015-03-31 08:15:21 -0700601
Ben Linebf2a172016-06-03 13:46:52 -0700602 case R.id.menu_copy_to_clipboard:
603 copySelectedToClipboard();
604 return true;
Aga Wronska3c237182016-01-20 16:32:33 -0800605
Ben Linebf2a172016-06-03 13:46:52 -0700606 case R.id.menu_paste_from_clipboard:
607 pasteFromClipboard();
608 return true;
609
Ben Linb2dfa122016-08-11 17:42:57 -0700610 case R.id.menu_paste_into_folder:
611 pasteIntoFolder();
612 return true;
613
Ben Linebf2a172016-06-03 13:46:52 -0700614 case R.id.menu_select_all:
615 selectAllFiles();
616 return true;
617
618 case R.id.menu_rename:
619 // Exit selection mode first, so we avoid deselecting deleted
620 // (renamed) documents.
Garfield Tan84bd0f12016-09-12 14:18:32 -0700621 mActionModeController.finishActionMode();
Ben Linebf2a172016-06-03 13:46:52 -0700622 renameDocuments(selection);
623 return true;
624
625 default:
626 // See if BaseActivity can handle this particular MenuItem
627 if (!getBaseActivity().onOptionsItemSelected(item)) {
Steve McKay3abb0e32015-12-01 17:02:42 -0800628 if (DEBUG) Log.d(TAG, "Unhandled menu item selected: " + item);
629 return false;
Ben Linebf2a172016-06-03 13:46:52 -0700630 }
631 return true;
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700632 }
Steve McKayef280152015-06-11 10:10:49 -0700633 }
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700634
Steve McKay86c05762016-01-28 15:30:10 -0800635 public final boolean onBackPressed() {
Steve McKay74956af2016-06-30 21:03:06 -0700636 if (mSelectionMgr.hasSelection()) {
Steve McKayb8fd8842016-05-06 12:07:54 -0700637 if (DEBUG) Log.d(TAG, "Clearing selection on selection manager.");
Steve McKay74956af2016-06-30 21:03:06 -0700638 mSelectionMgr.clearSelection();
Steve McKay86c05762016-01-28 15:30:10 -0800639 return true;
640 }
641 return false;
642 }
643
Ben Kwa8e3fd762015-12-17 10:37:00 -0800644 private void cancelThumbnailTask(View view) {
Steve McKayef280152015-06-11 10:10:49 -0700645 final ImageView iconThumb = (ImageView) view.findViewById(R.id.icon_thumb);
646 if (iconThumb != null) {
Ben Kwa8e3fd762015-12-17 10:37:00 -0800647 mIconHelper.stopLoading(iconThumb);
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700648 }
Steve McKayef280152015-06-11 10:10:49 -0700649 }
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700650
Steve McKayf61f93a2016-09-01 15:39:18 -0700651 // Support for opening multiple documents is currently exclusive to DocumentsActivity.
Steve McKayef280152015-06-11 10:10:49 -0700652 private void openDocuments(final Selection selected) {
Aga Wronska94e53e42016-04-07 13:09:58 -0700653 Metrics.logUserAction(getContext(), Metrics.USER_ACTION_OPEN);
654
Steve McKayf61f93a2016-09-01 15:39:18 -0700655 // Model must be accessed in UI thread, since underlying cursor is not threadsafe.
656 List<DocumentInfo> docs = mModel.getDocuments(selected);
Garfield Tan06c4b112016-09-14 11:05:58 -0700657 BaseActivity activity = getBaseActivity();
658 if (docs.size() > 1) {
659 activity.onDocumentsPicked(docs);
660 } else {
661 activity.onDocumentPicked(docs.get(0), mModel);
662 }
Jeff Sharkey873daa32013-08-18 17:38:20 -0700663 }
664
Garfield Tan239ab972016-09-16 13:33:48 -0700665 private void showChooserForDoc(final Selection selected) {
666 Metrics.logUserAction(getContext(), Metrics.USER_ACTION_OPEN);
667
668 assert(selected.size() == 1);
669 DocumentInfo doc =
670 DocumentInfo.fromDirectoryCursor(mModel.getItem(selected.iterator().next()));
671 mTuner.showChooserForDoc(doc);
672 }
673
674 private void openInNewWindow(final Selection selected) {
675 assert(selected.size() == 1);
676 DocumentInfo doc =
677 DocumentInfo.fromDirectoryCursor(mModel.getItem(selected.iterator().next()));
678 mTuner.openInNewWindow(getDisplayState().stack, doc);
679 }
680
Steve McKayef280152015-06-11 10:10:49 -0700681 private void shareDocuments(final Selection selected) {
Aga Wronska94e53e42016-04-07 13:09:58 -0700682 Metrics.logUserAction(getContext(), Metrics.USER_ACTION_SHARE);
683
Steve McKayf61f93a2016-09-01 15:39:18 -0700684 // Model must be accessed in UI thread, since underlying cursor is not threadsafe.
685 List<DocumentInfo> docs = mModel.getDocuments(selected);
Steve McKay9276f3b2015-05-27 16:11:42 -0700686
Steve McKayf61f93a2016-09-01 15:39:18 -0700687 Intent intent;
Steve McKay9276f3b2015-05-27 16:11:42 -0700688
Steve McKayf61f93a2016-09-01 15:39:18 -0700689 // Filter out directories and virtual files - those can't be shared.
690 List<DocumentInfo> docsForSend = new ArrayList<>();
691 for (DocumentInfo doc: docs) {
692 if (!doc.isDirectory() && !doc.isVirtualDocument()) {
693 docsForSend.add(doc);
Steve McKay9276f3b2015-05-27 16:11:42 -0700694 }
Steve McKayf61f93a2016-09-01 15:39:18 -0700695 }
696
697 if (docsForSend.size() == 1) {
698 final DocumentInfo doc = docsForSend.get(0);
699
700 intent = new Intent(Intent.ACTION_SEND);
701 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
702 intent.addCategory(Intent.CATEGORY_DEFAULT);
703 intent.setType(doc.mimeType);
704 intent.putExtra(Intent.EXTRA_STREAM, doc.derivedUri);
705
706 } else if (docsForSend.size() > 1) {
707 intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
708 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
709 intent.addCategory(Intent.CATEGORY_DEFAULT);
710
711 final ArrayList<String> mimeTypes = new ArrayList<>();
712 final ArrayList<Uri> uris = new ArrayList<>();
713 for (DocumentInfo doc : docsForSend) {
714 mimeTypes.add(doc.mimeType);
715 uris.add(doc.derivedUri);
716 }
717
718 intent.setType(findCommonMimeType(mimeTypes));
719 intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
720
721 } else {
722 return;
723 }
724
725 intent = Intent.createChooser(intent, getActivity().getText(R.string.share_via));
726 startActivity(intent);
Steve McKay9276f3b2015-05-27 16:11:42 -0700727 }
728
Aga Wronska57ab0462016-03-17 13:45:32 -0700729 private String generateDeleteMessage(final List<DocumentInfo> docs) {
730 String message;
731 int dirsCount = 0;
732
733 for (DocumentInfo doc : docs) {
734 if (doc.isDirectory()) {
735 ++dirsCount;
736 }
737 }
738
739 if (docs.size() == 1) {
740 // Deleteing 1 file xor 1 folder in cwd
Steve McKay927d8002016-05-18 16:25:41 -0700741
742 // Address b/28772371, where including user strings in message can result in
743 // broken bidirectional support.
744 String displayName = BidiFormatter.getInstance().unicodeWrap(docs.get(0).displayName);
Aga Wronska57ab0462016-03-17 13:45:32 -0700745 message = dirsCount == 0
746 ? getActivity().getString(R.string.delete_filename_confirmation_message,
Steve McKay927d8002016-05-18 16:25:41 -0700747 displayName)
Aga Wronska57ab0462016-03-17 13:45:32 -0700748 : getActivity().getString(R.string.delete_foldername_confirmation_message,
Steve McKay927d8002016-05-18 16:25:41 -0700749 displayName);
Aga Wronska57ab0462016-03-17 13:45:32 -0700750 } else if (dirsCount == 0) {
751 // Deleting only files in cwd
752 message = Shared.getQuantityString(getActivity(),
753 R.plurals.delete_files_confirmation_message, docs.size());
754 } else if (dirsCount == docs.size()) {
755 // Deleting only folders in cwd
756 message = Shared.getQuantityString(getActivity(),
757 R.plurals.delete_folders_confirmation_message, docs.size());
758 } else {
759 // Deleting mixed items (files and folders) in cwd
760 message = Shared.getQuantityString(getActivity(),
761 R.plurals.delete_items_confirmation_message, docs.size());
762 }
763 return message;
764 }
765
Steve McKay74956af2016-06-30 21:03:06 -0700766 private boolean onDeleteSelectedDocuments() {
767 if (mSelectionMgr.hasSelection()) {
768 deleteDocuments(mSelectionMgr.getSelection(new Selection()));
769 }
770 return false;
771 }
772
Ben Kwa68f4bfb2016-03-14 15:12:12 -0700773 private void deleteDocuments(final Selection selected) {
Aga Wronska94e53e42016-04-07 13:09:58 -0700774 Metrics.logUserAction(getContext(), Metrics.USER_ACTION_DELETE);
775
Steve McKay0af8afd2016-02-25 13:34:03 -0800776 assert(!selected.isEmpty());
Jeff Sharkey873daa32013-08-18 17:38:20 -0700777
Tomasz Mikolajewskie0094412016-01-25 16:20:15 +0900778 final DocumentInfo srcParent = getDisplayState().stack.peek();
Steve McKayf63e9382016-03-01 08:28:02 -0800779
Steve McKayf61f93a2016-09-01 15:39:18 -0700780 // Model must be accessed in UI thread, since underlying cursor is not threadsafe.
781 List<DocumentInfo> docs = mModel.getDocuments(selected);
Steve McKayf63e9382016-03-01 08:28:02 -0800782
Steve McKayf61f93a2016-09-01 15:39:18 -0700783 TextView message =
784 (TextView) mInflater.inflate(R.layout.dialog_delete_confirmation, null);
785 message.setText(generateDeleteMessage(docs));
Garfield, Tan810fb822016-06-01 11:08:12 -0700786
Steve McKayf61f93a2016-09-01 15:39:18 -0700787 // For now, we implement this dialog NOT
788 // as a fragment (which can survive rotation and have its own state),
789 // but as a simple runtime dialog. So rotating a device with an
790 // active delete dialog...results in that dialog disappearing.
791 // We can do better, but don't have cycles for it now.
792 new AlertDialog.Builder(getActivity())
793 .setView(message)
794 .setPositiveButton(
795 android.R.string.ok,
796 new DialogInterface.OnClickListener() {
797 @Override
798 public void onClick(DialogInterface dialog, int id) {
799 // Finish selection mode first which clears selection so we
800 // don't end up trying to deselect deleted documents.
801 // This is done here, rather in the onActionItemClicked
802 // so we can avoid de-selecting items in the case where
803 // the user cancels the delete.
Garfield Tan84bd0f12016-09-12 14:18:32 -0700804 mActionModeController.finishActionMode();
Garfield, Tan48334772016-06-28 17:17:38 -0700805
Steve McKayf61f93a2016-09-01 15:39:18 -0700806 UrisSupplier srcs;
807 try {
808 srcs = UrisSupplier.create(
809 selected,
810 mModel::getItemUri,
811 getContext());
812 } catch(IOException e) {
813 throw new RuntimeException("Failed to create uri supplier.", e);
814 }
Garfield, Tan48334772016-06-28 17:17:38 -0700815
Steve McKayf61f93a2016-09-01 15:39:18 -0700816 FileOperation operation = new FileOperation.Builder()
817 .withOpType(FileOperationService.OPERATION_DELETE)
818 .withDestination(getDisplayState().stack)
819 .withSrcs(srcs)
820 .withSrcParent(srcParent.derivedUri)
821 .build();
822
823 BaseActivity activity = getBaseActivity();
824 FileOperations.start(activity, operation, activity.fileOpCallback);
825 }
826 })
827 .setNegativeButton(android.R.string.cancel, null)
828 .show();
Jeff Sharkey873daa32013-08-18 17:38:20 -0700829 }
830
Ben Kwabc7df442016-02-02 23:00:02 -0800831 private void transferDocuments(final Selection selected, final @OpType int mode) {
Aga Wronska94e53e42016-04-07 13:09:58 -0700832 if(mode == FileOperationService.OPERATION_COPY) {
833 Metrics.logUserAction(getContext(), Metrics.USER_ACTION_COPY_TO);
834 } else if (mode == FileOperationService.OPERATION_MOVE) {
835 Metrics.logUserAction(getContext(), Metrics.USER_ACTION_MOVE_TO);
836 }
837
Ben Kwaf5858932015-04-07 15:43:39 -0700838 // Pop up a dialog to pick a destination. This is inadequate but works for now.
839 // TODO: Implement a picker that is to spec.
Daichi Hironocaadd412015-04-10 15:50:38 +0900840 final Intent intent = new Intent(
Ben Kwaae967802015-09-25 14:48:29 -0700841 Shared.ACTION_PICK_COPY_DESTINATION,
Daichi Hironocaadd412015-04-10 15:50:38 +0900842 Uri.EMPTY,
843 getActivity(),
Steve McKay16e0c1f2016-09-15 12:41:13 -0700844 PickActivity.class);
Steve McKay9276f3b2015-05-27 16:11:42 -0700845
Garfield, Tanb7e5f6b2016-06-30 18:27:47 -0700846 UrisSupplier srcs;
847 try {
848 srcs = UrisSupplier.create(selected, mModel::getItemUri, getContext());
849 } catch(IOException e) {
850 throw new RuntimeException("Failed to create uri supplier.", e);
851 }
Garfield, Tan48334772016-06-28 17:17:38 -0700852
Garfield, Tanedce5542016-06-17 15:32:28 -0700853 Uri srcParent = getDisplayState().stack.peek().derivedUri;
Ben Lin2d67ee22016-09-20 16:01:51 -0700854 mConfig.mPendingOperation = new FileOperation.Builder()
Garfield, Tan48334772016-06-28 17:17:38 -0700855 .withOpType(mode)
856 .withSrcParent(srcParent)
857 .withSrcs(srcs)
858 .build();
Steve McKay1abf6cf2016-04-08 17:34:11 -0700859
860 // Relay any config overrides bits present in the original intent.
861 Intent original = getActivity().getIntent();
862 if (original != null && original.hasExtra(Shared.EXTRA_PRODUCTIVITY_MODE)) {
863 intent.putExtra(
864 Shared.EXTRA_PRODUCTIVITY_MODE,
865 original.getBooleanExtra(Shared.EXTRA_PRODUCTIVITY_MODE, false));
866 }
867
Steve McKayf1719342016-02-17 15:02:01 -0800868 // Set an appropriate title on the drawer when it is shown in the picker.
869 // Coupled with the fact that we auto-open the drawer for copy/move operations
870 // it should basically be the thing people see first.
871 int drawerTitleId = mode == FileOperationService.OPERATION_MOVE
872 ? R.string.menu_move : R.string.menu_copy;
873 intent.putExtra(DocumentsContract.EXTRA_PROMPT, getResources().getString(drawerTitleId));
874
Steve McKayf61f93a2016-09-01 15:39:18 -0700875 // Model must be accessed in UI thread, since underlying cursor is not threadsafe.
876 List<DocumentInfo> docs = mModel.getDocuments(selected);
Steve McKayf1719342016-02-17 15:02:01 -0800877
Steve McKayf61f93a2016-09-01 15:39:18 -0700878 // Determine if there is a directory in the set of documents
879 // to be copied? Why? Directory creation isn't supported by some roots
880 // (like Downloads). This informs DocumentsActivity (the "picker")
881 // to restrict available roots to just those with support.
882 intent.putExtra(Shared.EXTRA_DIRECTORY_COPY, hasDirectory(docs));
883 intent.putExtra(FileOperationService.EXTRA_OPERATION_TYPE, mode);
Steve McKayf1719342016-02-17 15:02:01 -0800884
Steve McKayf61f93a2016-09-01 15:39:18 -0700885 // This just identifies the type of request...we'll check it
886 // when we reveive a response.
887 startActivityForResult(intent, REQUEST_COPY_DESTINATION);
Ben Kwa41b26c12015-03-31 10:11:43 -0700888 }
889
Steve McKayf1719342016-02-17 15:02:01 -0800890 private static boolean hasDirectory(List<DocumentInfo> docs) {
891 for (DocumentInfo info : docs) {
892 if (Document.MIME_TYPE_DIR.equals(info.mimeType)) {
893 return true;
894 }
895 }
896 return false;
897 }
898
Aga Wronska3c237182016-01-20 16:32:33 -0800899 private void renameDocuments(Selection selected) {
Aga Wronska94e53e42016-04-07 13:09:58 -0700900 Metrics.logUserAction(getContext(), Metrics.USER_ACTION_RENAME);
901
Aga Wronska3c237182016-01-20 16:32:33 -0800902 // Batch renaming not supported
903 // Rename option is only available in menu when 1 document selected
Steve McKay0af8afd2016-02-25 13:34:03 -0800904 assert(selected.size() == 1);
Aga Wronska3c237182016-01-20 16:32:33 -0800905
Steve McKayf61f93a2016-09-01 15:39:18 -0700906 // Model must be accessed in UI thread, since underlying cursor is not threadsafe.
907 List<DocumentInfo> docs = mModel.getDocuments(selected);
908 RenameDocumentFragment.show(getFragmentManager(), docs.get(0));
Aga Wronska3c237182016-01-20 16:32:33 -0800909 }
910
Steve McKayc5ecf892015-12-22 18:15:31 -0800911 @Override
912 public void initDocumentHolder(DocumentHolder holder) {
Steve McKay74956af2016-06-30 21:03:06 -0700913 holder.addKeyEventListener(mInputHandler);
Ben Kwa74e5d412016-02-10 07:46:35 -0800914 holder.itemView.setOnFocusChangeListener(mFocusManager);
Steve McKayc5ecf892015-12-22 18:15:31 -0800915 }
916
917 @Override
918 public void onBindDocumentHolder(DocumentHolder holder, Cursor cursor) {
Ben Kwaa8c0da42016-02-25 14:10:40 -0800919 setupDragAndDropOnDocumentView(holder.itemView, cursor);
Steve McKayc5ecf892015-12-22 18:15:31 -0800920 }
921
922 @Override
923 public State getDisplayState() {
Steve McKay2bab2f82016-06-03 09:23:39 -0700924 return getBaseActivity().getDisplayState();
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700925 }
926
Steve McKayc5ecf892015-12-22 18:15:31 -0800927 @Override
928 public Model getModel() {
929 return mModel;
930 }
931
932 @Override
933 public boolean isDocumentEnabled(String docMimeType, int docFlags) {
934 return mTuner.isDocumentEnabled(docMimeType, docFlags);
935 }
936
Steve McKay096c78b2016-01-21 18:46:15 -0800937 private void showEmptyDirectory() {
Ben Kwa80351c92016-02-04 16:35:27 -0800938 showEmptyView(R.string.empty, R.drawable.cabinet);
Ben Kwa2f975262015-09-16 13:15:38 -0700939 }
940
Steve McKay096c78b2016-01-21 18:46:15 -0800941 private void showNoResults(RootInfo root) {
942 CharSequence msg = getContext().getResources().getText(R.string.no_results);
Ben Kwa80351c92016-02-04 16:35:27 -0800943 showEmptyView(String.format(String.valueOf(msg), root.title), R.drawable.cabinet);
Ben Kwa2f975262015-09-16 13:15:38 -0700944 }
945
Steve McKay096c78b2016-01-21 18:46:15 -0800946 private void showQueryError() {
Ben Kwa80351c92016-02-04 16:35:27 -0800947 showEmptyView(R.string.query_error, R.drawable.hourglass);
Steve McKay096c78b2016-01-21 18:46:15 -0800948 }
949
Ben Kwa80351c92016-02-04 16:35:27 -0800950 private void showEmptyView(@StringRes int id, int drawable) {
951 showEmptyView(getContext().getResources().getText(id), drawable);
Steve McKay096c78b2016-01-21 18:46:15 -0800952 }
953
Ben Kwa80351c92016-02-04 16:35:27 -0800954 private void showEmptyView(CharSequence msg, int drawable) {
Steve McKay096c78b2016-01-21 18:46:15 -0800955 View content = mEmptyView.findViewById(R.id.content);
956 TextView msgView = (TextView) mEmptyView.findViewById(R.id.message);
Ben Kwa80351c92016-02-04 16:35:27 -0800957 ImageView imageView = (ImageView) mEmptyView.findViewById(R.id.artwork);
Steve McKay096c78b2016-01-21 18:46:15 -0800958 msgView.setText(msg);
Ben Kwa80351c92016-02-04 16:35:27 -0800959 imageView.setImageResource(drawable);
Steve McKay096c78b2016-01-21 18:46:15 -0800960
Steve McKay096c78b2016-01-21 18:46:15 -0800961 mEmptyView.setVisibility(View.VISIBLE);
Aga Wronska9406c172016-02-12 09:15:32 -0800962 mEmptyView.requestFocus();
Garfield, Tan171e6f52016-07-29 14:44:58 -0700963 mFileList.setVisibility(View.GONE);
Steve McKay096c78b2016-01-21 18:46:15 -0800964 }
965
966 private void showDirectory() {
Ben Kwa2f975262015-09-16 13:15:38 -0700967 mEmptyView.setVisibility(View.GONE);
Garfield, Tan171e6f52016-07-29 14:44:58 -0700968 mFileList.setVisibility(View.VISIBLE);
Aga Wronska9406c172016-02-12 09:15:32 -0800969 mRecView.requestFocus();
Ben Kwa2f975262015-09-16 13:15:38 -0700970 }
971
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700972 private String findCommonMimeType(List<String> mimeTypes) {
973 String[] commonType = mimeTypes.get(0).split("/");
974 if (commonType.length != 2) {
975 return "*/*";
976 }
977
978 for (int i = 1; i < mimeTypes.size(); i++) {
979 String[] type = mimeTypes.get(i).split("/");
980 if (type.length != 2) continue;
981
982 if (!commonType[1].equals(type[1])) {
983 commonType[1] = "*";
984 }
985
986 if (!commonType[0].equals(type[0])) {
987 commonType[0] = "*";
988 commonType[1] = "*";
989 break;
990 }
991 }
992
993 return commonType[0] + "/" + commonType[1];
994 }
Jeff Sharkeyaed873d2013-09-09 16:51:06 -0700995
Steve McKayf68210e2015-11-03 15:23:16 -0800996 public void copySelectedToClipboard() {
Aga Wronska94e53e42016-04-07 13:09:58 -0700997 Metrics.logUserAction(getContext(), Metrics.USER_ACTION_COPY_CLIPBOARD);
998
Steve McKay74956af2016-06-30 21:03:06 -0700999 Selection selection = mSelectionMgr.getSelection(new Selection());
Ben Linff4d5842016-04-18 14:35:28 -07001000 if (selection.isEmpty()) {
1001 return;
Steve McKaye11ef242015-12-07 16:31:42 -08001002 }
Steve McKay74956af2016-06-30 21:03:06 -07001003 mSelectionMgr.clearSelection();
Steve McKay84769b82016-06-09 10:46:07 -07001004
Garfield, Tanedce5542016-06-17 15:32:28 -07001005 mClipper.clipDocumentsForCopy(mModel::getItemUri, selection);
1006
1007 Snackbars.showDocumentsClipped(getActivity(), selection.size());
Ben Linff4d5842016-04-18 14:35:28 -07001008 }
1009
1010 public void cutSelectedToClipboard() {
1011 Metrics.logUserAction(getContext(), Metrics.USER_ACTION_CUT_CLIPBOARD);
Garfield, Tan4d009142016-05-12 14:12:18 -07001012
Steve McKay74956af2016-06-30 21:03:06 -07001013 Selection selection = mSelectionMgr.getSelection(new Selection());
Ben Linff4d5842016-04-18 14:35:28 -07001014 if (selection.isEmpty()) {
1015 return;
1016 }
Steve McKay74956af2016-06-30 21:03:06 -07001017 mSelectionMgr.clearSelection();
Steve McKay84769b82016-06-09 10:46:07 -07001018
Garfield, Tanedce5542016-06-17 15:32:28 -07001019 mClipper.clipDocumentsForCut(mModel::getItemUri, selection, getDisplayState().stack.peek());
1020
1021 Snackbars.showDocumentsClipped(getActivity(), selection.size());
Steve McKay0599a442015-05-05 14:50:00 -07001022 }
1023
Steve McKayf68210e2015-11-03 15:23:16 -08001024 public void pasteFromClipboard() {
Aga Wronska94e53e42016-04-07 13:09:58 -07001025 Metrics.logUserAction(getContext(), Metrics.USER_ACTION_PASTE_CLIPBOARD);
1026
Garfield, Tan4d009142016-05-12 14:12:18 -07001027 BaseActivity activity = (BaseActivity) getActivity();
1028 DocumentInfo destination = activity.getCurrentDirectory();
Garfield, Tana5588b62016-07-13 09:23:04 -07001029 mClipper.copyFromClipboard(
1030 destination, activity.getDisplayState().stack, activity.fileOpCallback);
Steve McKay1f199482015-05-20 15:58:42 -07001031 getActivity().invalidateOptionsMenu();
Steve McKay0599a442015-05-05 14:50:00 -07001032 }
1033
Ben Linb2dfa122016-08-11 17:42:57 -07001034 public void pasteIntoFolder() {
1035 assert (mSelectionMgr.getSelection().size() == 1);
1036
1037 String modelId = mSelectionMgr.getSelection().iterator().next();
1038 Cursor dstCursor = mModel.getItem(modelId);
1039 if (dstCursor == null) {
1040 Log.w(TAG, "Invalid destination. Can't obtain cursor for modelId: " + modelId);
1041 return;
1042 }
1043 BaseActivity activity = getBaseActivity();
1044 DocumentInfo destination = DocumentInfo.fromDirectoryCursor(dstCursor);
1045 mClipper.copyFromClipboard(
1046 destination, activity.getDisplayState().stack, activity.fileOpCallback);
1047 getActivity().invalidateOptionsMenu();
1048 }
1049
Steve McKayf68210e2015-11-03 15:23:16 -08001050 public void selectAllFiles() {
Aga Wronska94e53e42016-04-07 13:09:58 -07001051 Metrics.logUserAction(getContext(), Metrics.USER_ACTION_SELECT_ALL);
1052
Steve McKay2bab2f82016-06-03 09:23:39 -07001053 // Exclude disabled files
Steve McKay17b761e2016-09-20 17:26:46 -07001054 List<String> enabled = new ArrayList<>();
Steve McKay2bab2f82016-06-03 09:23:39 -07001055 for (String id : mAdapter.getModelIds()) {
Aga Wronska87abbcd2016-04-01 13:16:11 -07001056 Cursor cursor = getModel().getItem(id);
Tomasz Mikolajewski7773cba2016-04-13 16:18:15 +09001057 if (cursor == null) {
Steve McKay5a22a112016-04-12 11:29:10 -07001058 Log.w(TAG, "Skipping selection. Can't obtain cursor for modeId: " + id);
1059 continue;
1060 }
Aga Wronska87abbcd2016-04-01 13:16:11 -07001061 String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
1062 int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
Steve McKay2bab2f82016-06-03 09:23:39 -07001063 if (isDocumentEnabled(docMimeType, docFlags)) {
Aga Wronska87abbcd2016-04-01 13:16:11 -07001064 enabled.add(id);
1065 }
1066 }
1067
Ben Kwab8a5e082015-12-07 13:25:27 -08001068 // Only select things currently visible in the adapter.
Steve McKay74956af2016-06-30 21:03:06 -07001069 boolean changed = mSelectionMgr.setItemsSelected(enabled, true);
Steve McKay9459a7c2015-07-24 13:14:20 -07001070 if (changed) {
1071 updateDisplayState();
1072 }
Steve McKay0599a442015-05-05 14:50:00 -07001073 }
1074
Ben Kwa74e5d412016-02-10 07:46:35 -08001075 /**
1076 * Attempts to restore focus on the directory listing.
1077 */
1078 public void requestFocus() {
1079 mFocusManager.restoreLastFocus();
1080 }
1081
Vladislav Kaznacheevb6da7222015-05-01 14:18:57 -07001082 private void setupDragAndDropOnDocumentView(View view, Cursor cursor) {
1083 final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
1084 if (Document.MIME_TYPE_DIR.equals(docMimeType)) {
1085 // Make a directory item a drop target. Drop on non-directories and empty space
1086 // is handled at the list/grid view level.
Ben Lin35f99e02016-08-31 12:46:04 -07001087 view.setOnDragListener(mDragHoverListener);
Vladislav Kaznacheevb6da7222015-05-01 14:18:57 -07001088 }
Vladislav Kaznacheevb6da7222015-05-01 14:18:57 -07001089 }
1090
Garfield, Tanb7e5f6b2016-06-30 18:27:47 -07001091 void dragStarted() {
1092 // When files are selected for dragging, ActionMode is started. This obscures the breadcrumb
1093 // with an ActionBar. In order to make drag and drop to the breadcrumb possible, we first
1094 // end ActionMode so the breadcrumb is visible to the user.
Garfield Tan58dced42016-09-15 19:16:04 -07001095 //
1096 // mActionModeController is null when dragStarted() is called on spring loaded
1097 // folders/roots.
1098 if (mActionModeController != null) {
1099 mActionModeController.finishActionMode();
1100 }
Garfield, Tanb7e5f6b2016-06-30 18:27:47 -07001101 }
1102
1103 void dragStopped(boolean result) {
1104 if (result) {
Steve McKay74956af2016-06-30 21:03:06 -07001105 mSelectionMgr.clearSelection();
Garfield, Tanb7e5f6b2016-06-30 18:27:47 -07001106 }
Garfield, Tan804133e2016-04-20 15:13:56 -07001107 }
Vladislav Kaznacheevb6da7222015-05-01 14:18:57 -07001108
Garfield, Tan804133e2016-04-20 15:13:56 -07001109 @Override
1110 public void runOnUiThread(Runnable runnable) {
1111 getActivity().runOnUiThread(runnable);
1112 }
Ben Kwa13e26052016-02-18 16:45:45 -08001113
Garfield, Tan804133e2016-04-20 15:13:56 -07001114 /**
1115 * {@inheritDoc}
1116 *
1117 * In DirectoryFragment, we spring loads the hovered folder.
1118 */
1119 @Override
1120 public void onViewHovered(View view) {
Garfield, Tan7d66a862016-05-11 10:28:41 -07001121 BaseActivity activity = (BaseActivity) getActivity();
Garfield, Tan804133e2016-04-20 15:13:56 -07001122 if (getModelId(view) != null) {
Garfield, Tan7d66a862016-05-11 10:28:41 -07001123 activity.springOpenDirectory(getDestination(view));
Garfield, Tan804133e2016-04-20 15:13:56 -07001124 }
Garfield, Tan7d66a862016-05-11 10:28:41 -07001125 activity.setRootsDrawerOpen(false);
Garfield, Tan804133e2016-04-20 15:13:56 -07001126 }
Ben Kwaa8c0da42016-02-25 14:10:40 -08001127
Garfield, Tanedce5542016-06-17 15:32:28 -07001128 boolean handleDropEvent(View v, DragEvent event) {
Garfield, Tan7d66a862016-05-11 10:28:41 -07001129 BaseActivity activity = (BaseActivity) getActivity();
1130 activity.setRootsDrawerOpen(false);
1131
Garfield, Tan804133e2016-04-20 15:13:56 -07001132 ClipData clipData = event.getClipData();
1133 assert (clipData != null);
Vladislav Kaznacheevb6da7222015-05-01 14:18:57 -07001134
Garfield, Tan48334772016-06-28 17:17:38 -07001135 assert(DocumentClipper.getOpType(clipData) == FileOperationService.OPERATION_COPY);
Garfield, Tan804133e2016-04-20 15:13:56 -07001136
Ben Lin5a305b42016-09-08 11:33:07 -07001137 if (!shouldCopyTo(event.getLocalState(), v)) {
Vladislav Kaznacheevb6da7222015-05-01 14:18:57 -07001138 return false;
1139 }
Ben Kwa13e26052016-02-18 16:45:45 -08001140
Garfield, Tan804133e2016-04-20 15:13:56 -07001141 // Recognize multi-window drag and drop based on the fact that localState is not
1142 // carried between processes. It will stop working when the localsState behavior
1143 // is changed. The info about window should be passed in the localState then.
1144 // The localState could also be null for copying from Recents in single window
1145 // mode, but Recents doesn't offer this functionality (no directories).
1146 Metrics.logUserAction(getContext(),
Ben Lin5a305b42016-09-08 11:33:07 -07001147 event.getLocalState() == null ? Metrics.USER_ACTION_DRAG_N_DROP_MULTI_WINDOW
Garfield, Tan804133e2016-04-20 15:13:56 -07001148 : Metrics.USER_ACTION_DRAG_N_DROP);
Ben Linff4d5842016-04-18 14:35:28 -07001149
Ben Lin5a305b42016-09-08 11:33:07 -07001150 DocumentInfo dst = getDestination(v);
Garfield, Tana5588b62016-07-13 09:23:04 -07001151 mClipper.copyFromClipData(dst, getDisplayState().stack, clipData, activity.fileOpCallback);
Garfield, Tan804133e2016-04-20 15:13:56 -07001152 return true;
1153 }
1154
Ben Lin5a305b42016-09-08 11:33:07 -07001155 // Don't copy from the cwd into a provided list of prohibited directories. (ie. into cwd, into
1156 // a selected directory). Note: this currently doesn't work for multi-window drag, because
1157 // localState isn't carried over from one process to another.
1158 boolean shouldCopyTo(Object dragLocalState, View destinationView) {
1159 if (dragLocalState == null || !(dragLocalState instanceof List<?>)) {
1160 if (DEBUG) Log.d(TAG, "Invalid local state object. Will allow copy.");
1161 return true;
1162 }
1163 DocumentInfo dst = getDestination(destinationView);
1164 List<?> src = (List<?>) dragLocalState;
1165 if (src.contains(dst)) {
1166 if (DEBUG) Log.d(TAG, "Drop target same as source. Ignoring.");
1167 return false;
1168 }
1169 return true;
1170 }
1171
Garfield, Tan804133e2016-04-20 15:13:56 -07001172 private DocumentInfo getDestination(View v) {
1173 String id = getModelId(v);
1174 if (id != null) {
1175 Cursor dstCursor = mModel.getItem(id);
1176 if (dstCursor == null) {
1177 Log.w(TAG, "Invalid destination. Can't obtain cursor for modelId: " + id);
1178 return null;
Ben Linff4d5842016-04-18 14:35:28 -07001179 }
Garfield, Tan804133e2016-04-20 15:13:56 -07001180 return DocumentInfo.fromDirectoryCursor(dstCursor);
Ben Linff4d5842016-04-18 14:35:28 -07001181 }
1182
Garfield, Tan804133e2016-04-20 15:13:56 -07001183 if (v == mRecView || v == mEmptyView) {
1184 return getDisplayState().stack.peek();
Ben Kwaa8c0da42016-02-25 14:10:40 -08001185 }
1186
Garfield, Tan804133e2016-04-20 15:13:56 -07001187 return null;
1188 }
1189
1190 @Override
1191 public void setDropTargetHighlight(View v, boolean highlight) {
1192 // Note: use exact comparison - this code is searching for views which are children of
1193 // the RecyclerView instance in the UI.
1194 if (v.getParent() == mRecView) {
1195 RecyclerView.ViewHolder vh = mRecView.getChildViewHolder(v);
1196 if (vh instanceof DocumentHolder) {
1197 ((DocumentHolder) vh).setHighlighted(highlight);
Ben Kwa13e26052016-02-18 16:45:45 -08001198 }
1199 }
Garfield, Tan804133e2016-04-20 15:13:56 -07001200 }
Vladislav Kaznacheevb6da7222015-05-01 14:18:57 -07001201
Ben Kwa0497da82015-11-30 23:00:02 -08001202 /**
1203 * Gets the model ID for a given RecyclerView item.
1204 * @param view A View that is a document item view, or a child of a document item view.
1205 * @return The Model ID for the given document, or null if the given view is not associated with
1206 * a document item view.
1207 */
Ben Linebf2a172016-06-03 13:46:52 -07001208 protected @Nullable String getModelId(View view) {
Ben Kwa13e26052016-02-18 16:45:45 -08001209 View itemView = mRecView.findContainingItemView(view);
1210 if (itemView != null) {
1211 RecyclerView.ViewHolder vh = mRecView.getChildViewHolder(itemView);
1212 if (vh instanceof DocumentHolder) {
Steve McKaybde20e12016-09-08 19:12:54 -07001213 return ((DocumentHolder) vh).getModelId();
Vladislav Kaznacheev9400b892015-09-04 09:17:37 -07001214 }
Vladislav Kaznacheev9400b892015-09-04 09:17:37 -07001215 }
Ben Kwa13e26052016-02-18 16:45:45 -08001216 return null;
Vladislav Kaznacheev9400b892015-09-04 09:17:37 -07001217 }
1218
Steve McKayc5ecf892015-12-22 18:15:31 -08001219 @Override
1220 public boolean isSelected(String modelId) {
Steve McKay74956af2016-06-30 21:03:06 -07001221 return mSelectionMgr.getSelection().contains(modelId);
Ben Kwa379e1762015-09-21 10:49:52 -07001222 }
Ben Kwa7461a952015-09-01 11:03:01 -07001223
Steve McKay990f76e2016-09-16 12:36:58 -07001224 private final class ModelUpdateListener implements EventListener<Model.Update> {
Steve McKay9de0da62016-08-25 15:18:23 -07001225
Steve McKay990f76e2016-09-16 12:36:58 -07001226 @Override
1227 public void accept(Model.Update update) {
1228 if (update.hasError()) {
1229 showQueryError();
1230 return;
1231 }
1232
1233 if (DEBUG) Log.d(TAG, "Received model update. Loading=" + mModel.isLoading());
1234
1235 if (mModel.info != null || mModel.error != null) {
1236 mMessageBar.setInfo(mModel.info);
1237 mMessageBar.setError(mModel.error);
Ben Kwa379e1762015-09-21 10:49:52 -07001238 mMessageBar.show();
1239 }
Ben Kwa7461a952015-09-01 11:03:01 -07001240
Steve McKay990f76e2016-09-16 12:36:58 -07001241 mProgressBar.setVisibility(mModel.isLoading() ? View.VISIBLE : View.GONE);
Ben Kwa379e1762015-09-21 10:49:52 -07001242
Steve McKay990f76e2016-09-16 12:36:58 -07001243 if (mModel.isEmpty()) {
Ben Lin2d67ee22016-09-20 16:01:51 -07001244 if (mConfig.mSearchMode) {
Steve McKay096c78b2016-01-21 18:46:15 -08001245 showNoResults(getDisplayState().stack.root);
1246 } else {
1247 showEmptyDirectory();
1248 }
Ben Kwa379e1762015-09-21 10:49:52 -07001249 } else {
Steve McKay096c78b2016-01-21 18:46:15 -08001250 showDirectory();
Ben Kwa2f975262015-09-16 13:15:38 -07001251 mAdapter.notifyDataSetChanged();
Ben Kwa379e1762015-09-21 10:49:52 -07001252 }
Tomasz Mikolajewskie29e3412016-02-24 12:53:44 +09001253
Steve McKay990f76e2016-09-16 12:36:58 -07001254 if (!mModel.isLoading()) {
Steve McKay2bab2f82016-06-03 09:23:39 -07001255 getBaseActivity().notifyDirectoryLoaded(
Steve McKay990f76e2016-09-16 12:36:58 -07001256 mModel.doc != null ? mModel.doc.derivedUri : null);
Tomasz Mikolajewskie29e3412016-02-24 12:53:44 +09001257 }
Ben Kwa379e1762015-09-21 10:49:52 -07001258 }
Ben Kwa24be5d32015-08-27 16:04:46 -07001259 }
Ben Kwa9fe25a72016-01-15 10:43:24 -08001260
Steve McKay74956af2016-06-30 21:03:06 -07001261 private boolean canSelect(DocumentDetails doc) {
Garfield Tan84bd0f12016-09-12 14:18:32 -07001262 return canSetSelectionState(doc.getModelId(), true);
Steve McKay74956af2016-06-30 21:03:06 -07001263 }
1264
Garfield Tan84bd0f12016-09-12 14:18:32 -07001265 private boolean canSetSelectionState(String modelId, boolean nextState) {
1266 if (nextState) {
1267 // Check if an item can be selected
1268 final Cursor cursor = mModel.getItem(modelId);
1269 if (cursor == null) {
1270 Log.w(TAG, "Couldn't obtain cursor for modelId: " + modelId);
1271 return false;
1272 }
Steve McKayf7944892016-01-21 15:10:39 -08001273
Garfield Tan84bd0f12016-09-12 14:18:32 -07001274 final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
1275 final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
1276 return mTuner.canSelectType(docMimeType, docFlags);
1277 } else {
1278 // Right now all selected items can be deselected.
1279 return true;
Steve McKayf7944892016-01-21 15:10:39 -08001280 }
Steve McKayf7944892016-01-21 15:10:39 -08001281 }
1282
Steve McKay096c78b2016-01-21 18:46:15 -08001283 public static void showDirectory(
1284 FragmentManager fm, RootInfo root, DocumentInfo doc, int anim) {
Steve McKay7c662092016-08-26 12:17:41 -07001285 if (DEBUG) Log.d(TAG, "Showing directory: " + DocumentInfo.debugString(doc));
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001286 create(fm, TYPE_NORMAL, root, doc, null, anim);
Steve McKay096c78b2016-01-21 18:46:15 -08001287 }
1288
1289 public static void showRecentsOpen(FragmentManager fm, int anim) {
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001290 create(fm, TYPE_RECENT_OPEN, null, null, null, anim);
Steve McKay096c78b2016-01-21 18:46:15 -08001291 }
1292
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001293 public static void reloadSearch(FragmentManager fm, RootInfo root, DocumentInfo doc,
1294 String query) {
1295 DirectoryFragment df = get(fm);
1296
Ben Lin2d67ee22016-09-20 16:01:51 -07001297 df.mConfig.update(root, doc, query);
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001298 df.getLoaderManager().restartLoader(LOADER_ID, null, df);
1299 }
1300
1301 public static void reload(FragmentManager fm, int type, RootInfo root, DocumentInfo doc,
1302 String query) {
Steve McKay7c662092016-08-26 12:17:41 -07001303 if (DEBUG) Log.d(TAG, "Reloading directory: " + DocumentInfo.debugString(doc));
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001304 DirectoryFragment df = get(fm);
Steve McKayc8f165c2016-09-20 13:54:05 -07001305
Ben Lin2d67ee22016-09-20 16:01:51 -07001306 df.mConfig.update(type, root, doc, query);
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001307 df.getLoaderManager().restartLoader(LOADER_ID, null, df);
1308 }
1309
Steve McKay7c662092016-08-26 12:17:41 -07001310 public static void create(
1311 FragmentManager fm,
1312 int type,
1313 RootInfo root,
1314 @Nullable DocumentInfo doc,
1315 String query,
1316 int anim) {
1317
1318 if (DEBUG) {
1319 if (doc == null) {
1320 Log.d(TAG, "Creating new fragment null directory");
1321 } else {
1322 Log.d(TAG, "Creating new fragment for directory: " + DocumentInfo.debugString(doc));
1323 }
1324 }
1325
Steve McKay096c78b2016-01-21 18:46:15 -08001326 final Bundle args = new Bundle();
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001327 args.putInt(Shared.EXTRA_TYPE, type);
1328 args.putParcelable(Shared.EXTRA_ROOT, root);
1329 args.putParcelable(Shared.EXTRA_DOC, doc);
1330 args.putString(Shared.EXTRA_QUERY, query);
Aga Wronska7e5b9632016-02-26 11:36:07 -08001331 args.putParcelable(Shared.EXTRA_SELECTION, new Selection());
Steve McKay096c78b2016-01-21 18:46:15 -08001332
1333 final FragmentTransaction ft = fm.beginTransaction();
Steve McKayfb4fd2f2016-03-11 10:49:32 -08001334 AnimationView.setupAnimations(ft, anim, args);
Steve McKay096c78b2016-01-21 18:46:15 -08001335
1336 final DirectoryFragment fragment = new DirectoryFragment();
1337 fragment.setArguments(args);
1338
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001339 ft.replace(getFragmentId(), fragment);
Steve McKay096c78b2016-01-21 18:46:15 -08001340 ft.commitAllowingStateLoss();
1341 }
1342
Steve McKay096c78b2016-01-21 18:46:15 -08001343 public static @Nullable DirectoryFragment get(FragmentManager fm) {
1344 // TODO: deal with multiple directories shown at once
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001345 Fragment fragment = fm.findFragmentById(getFragmentId());
Steve McKay096c78b2016-01-21 18:46:15 -08001346 return fragment instanceof DirectoryFragment
1347 ? (DirectoryFragment) fragment
1348 : null;
1349 }
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001350
1351 private static int getFragmentId() {
1352 return R.id.container_directory;
1353 }
1354
1355 @Override
Garfield, Tanca7c0882016-07-18 16:45:27 -07001356 public void onRefresh() {
Garfield, Taneba5bb92016-07-22 10:30:49 -07001357 // Remove thumbnail cache. We do this not because we're worried about stale thumbnails as it
1358 // should be covered by last modified value we store in thumbnail cache, but rather to give
1359 // the user a greater sense that contents are being reloaded.
1360 ThumbnailCache cache = DocumentsApplication.getThumbnailCache(getContext());
1361 String[] ids = mModel.getModelIds();
1362 int numOfEvicts = Math.min(ids.length, CACHE_EVICT_LIMIT);
1363 for (int i = 0; i < numOfEvicts; ++i) {
1364 cache.removeUri(mModel.getItemUri(ids[i]));
1365 }
1366
1367 // Trigger loading
Garfield, Tanca7c0882016-07-18 16:45:27 -07001368 getLoaderManager().restartLoader(LOADER_ID, null, this);
1369 }
1370
1371 @Override
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001372 public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) {
1373 Context context = getActivity();
1374 State state = getDisplayState();
1375
1376 Uri contentsUri;
Ben Lin2d67ee22016-09-20 16:01:51 -07001377 switch (mConfig.mType) {
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001378 case TYPE_NORMAL:
Ben Lin2d67ee22016-09-20 16:01:51 -07001379 contentsUri = mConfig.mSearchMode ? DocumentsContract.buildSearchDocumentsUri(
1380 mConfig.mRoot.authority, mConfig.mRoot.rootId, mConfig.mQuery)
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001381 : DocumentsContract.buildChildDocumentsUri(
Ben Lin2d67ee22016-09-20 16:01:51 -07001382 mConfig.mDocument.authority, mConfig.mDocument.documentId);
Steve McKayce0b14c2016-05-03 11:25:52 -07001383 if (mTuner.managedModeEnabled()) {
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001384 contentsUri = DocumentsContract.setManageMode(contentsUri);
1385 }
Steve McKayc88f83c2016-08-31 12:01:43 -07001386 if (DEBUG) Log.d(TAG, "Creating new directory loader for: "
Ben Lin2d67ee22016-09-20 16:01:51 -07001387 + DocumentInfo.debugString(mConfig.mDocument));
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001388 return new DirectoryLoader(
Ben Lin2d67ee22016-09-20 16:01:51 -07001389 context, mConfig.mRoot, mConfig.mDocument, contentsUri, state.sortModel,
1390 mConfig.mSearchMode);
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001391 case TYPE_RECENT_OPEN:
Steve McKay9de0da62016-08-25 15:18:23 -07001392 if (DEBUG) Log.d(TAG, "Creating new loader recents.");
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001393 final RootsCache roots = DocumentsApplication.getRootsCache(context);
1394 return new RecentsLoader(context, roots, state);
Steve McKay9d7085f2016-03-07 11:51:31 -08001395
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001396 default:
Ben Lin2d67ee22016-09-20 16:01:51 -07001397 throw new IllegalStateException("Unknown type " + mConfig.mType);
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001398 }
1399 }
1400
1401 @Override
1402 public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
Steve McKayc8f165c2016-09-20 13:54:05 -07001403 if (DEBUG) Log.d(TAG, "Loader has finished for: "
Ben Lin2d67ee22016-09-20 16:01:51 -07001404 + DocumentInfo.debugString(mConfig.mDocument));
Steve McKay9de0da62016-08-25 15:18:23 -07001405 assert(result != null);
1406
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001407 if (!isAdded()) return;
Aga Wronska94e53e42016-04-07 13:09:58 -07001408
Ben Lin2d67ee22016-09-20 16:01:51 -07001409 if (mConfig.mSearchMode) {
Aga Wronska94e53e42016-04-07 13:09:58 -07001410 Metrics.logUserAction(getContext(), Metrics.USER_ACTION_SEARCH);
Aga Wronskab0289052016-03-31 16:37:40 -07001411 }
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001412
1413 State state = getDisplayState();
1414
1415 mAdapter.notifyDataSetChanged();
1416 mModel.update(result);
1417
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001418 updateLayout(state.derivedMode);
1419
Steve McKay2bab2f82016-06-03 09:23:39 -07001420 if (mRestoredSelection != null) {
Steve McKay74956af2016-06-30 21:03:06 -07001421 mSelectionMgr.restoreSelection(mRestoredSelection);
Steve McKay2bab2f82016-06-03 09:23:39 -07001422 // Note, we'll take care of cleaning up retained selection
1423 // in the selection handler where we already have some
1424 // specialized code to handle when selection was restored.
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001425 }
1426
1427 // Restore any previous instance state
Ben Lin2d67ee22016-09-20 16:01:51 -07001428 final SparseArray<Parcelable> container = state.dirConfigs.remove(mConfig.getConfigKey());
Garfield, Tan11d23482016-08-05 09:33:29 -07001429 final int curSortedDimensionId = state.sortModel.getSortedDimensionId();
Steve McKayc8f165c2016-09-20 13:54:05 -07001430
Garfield, Tan11d23482016-08-05 09:33:29 -07001431 final SortDimension curSortedDimension =
1432 state.sortModel.getDimensionById(curSortedDimensionId);
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001433 if (container != null && !getArguments().getBoolean(Shared.EXTRA_IGNORE_STATE, false)) {
1434 getView().restoreHierarchyState(container);
Ben Lin2d67ee22016-09-20 16:01:51 -07001435 } else if (mConfig.mLastSortDimensionId != curSortedDimension.getId()
1436 || mConfig.mLastSortDimensionId == SortModel.SORT_DIMENSION_ID_UNKNOWN
1437 || mConfig.mLastSortDirection != curSortedDimension.getSortDirection()) {
Garfield, Tan11d23482016-08-05 09:33:29 -07001438 // Scroll to the top if the sort order actually changed.
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001439 mRecView.smoothScrollToPosition(0);
1440 }
1441
Ben Lin2d67ee22016-09-20 16:01:51 -07001442 mConfig.mLastSortDimensionId = curSortedDimension.getId();
1443 mConfig.mLastSortDirection = curSortedDimension.getSortDirection();
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001444
Garfield, Taneba5bb92016-07-22 10:30:49 -07001445 if (mRefreshLayout.isRefreshing()) {
1446 new Handler().postDelayed(
1447 () -> mRefreshLayout.setRefreshing(false),
1448 REFRESH_SPINNER_DISMISS_DELAY);
1449 }
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001450 }
1451
1452 @Override
1453 public void onLoaderReset(Loader<DirectoryResult> loader) {
Steve McKayc8f165c2016-09-20 13:54:05 -07001454 if (DEBUG) Log.d(TAG, "Resetting loader for: "
Ben Lin2d67ee22016-09-20 16:01:51 -07001455 + DocumentInfo.debugString(mConfig.mDocument));
Steve McKay9de0da62016-08-25 15:18:23 -07001456 mModel.onLoaderReset();
Garfield, Tanca7c0882016-07-18 16:45:27 -07001457
1458 mRefreshLayout.setRefreshing(false);
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001459 }
Steve McKayc8f165c2016-09-20 13:54:05 -07001460
Ben Lin2d67ee22016-09-20 16:01:51 -07001461 private static final class Config {
Steve McKayc8f165c2016-09-20 13:54:05 -07001462
1463 private static final String EXTRA_SORT_DIMENSION_ID = "sortDimensionId";
1464 private static final String EXTRA_SORT_DIRECTION = "sortDirection";
1465
1466 // Directory fragment state is defined by: root, document, query, type, selection
1467 private @ResultType int mType = TYPE_NORMAL;
1468 private RootInfo mRoot;
1469 // Null when viewing Recents directory.
1470 private @Nullable DocumentInfo mDocument;
1471 private String mQuery = null;
1472 // Here we save the clip details of moveTo/copyTo actions when picker shows up.
1473 // This will be written to saved instance.
1474 private @Nullable FileOperation mPendingOperation;
1475 private boolean mSearchMode;
1476 private int mLastSortDimensionId = SortModel.SORT_DIMENSION_ID_UNKNOWN;
1477 private @SortDirection int mLastSortDirection;
1478
Ben Lin2d67ee22016-09-20 16:01:51 -07001479 private String mConfigKey;
Steve McKayc8f165c2016-09-20 13:54:05 -07001480
1481 public void restore(Bundle bundle) {
1482 mRoot = bundle.getParcelable(Shared.EXTRA_ROOT);
1483 mDocument = bundle.getParcelable(Shared.EXTRA_DOC);
1484 mQuery = bundle.getString(Shared.EXTRA_QUERY);
1485 mType = bundle.getInt(Shared.EXTRA_TYPE);
1486 mSearchMode = bundle.getBoolean(Shared.EXTRA_SEARCH_MODE);
1487 mPendingOperation = bundle.getParcelable(FileOperationService.EXTRA_OPERATION);
1488 mLastSortDimensionId = bundle.getInt(EXTRA_SORT_DIMENSION_ID);
1489 mLastSortDirection = bundle.getInt(EXTRA_SORT_DIRECTION);
1490 }
1491
1492 public void save(Bundle bundle) {
1493 bundle.putInt(Shared.EXTRA_TYPE, mType);
1494 bundle.putParcelable(Shared.EXTRA_ROOT, mRoot);
1495 bundle.putParcelable(Shared.EXTRA_DOC, mDocument);
1496 bundle.putString(Shared.EXTRA_QUERY, mQuery);
1497 bundle.putBoolean(Shared.EXTRA_SEARCH_MODE, mSearchMode);
1498 bundle.putParcelable(FileOperationService.EXTRA_OPERATION, mPendingOperation);
1499 bundle.putInt(EXTRA_SORT_DIMENSION_ID, mLastSortDimensionId);
1500 bundle.putInt(EXTRA_SORT_DIRECTION, mLastSortDirection);
1501 }
1502
1503 public FileOperation claimPendingOperation() {
1504 FileOperation op = mPendingOperation;
1505 mPendingOperation = null;
1506 return op;
1507 }
1508
1509 public void update(int type, RootInfo root, DocumentInfo doc, String query) {
1510 mType = type;
1511 update(root, doc, query);
1512 }
1513
1514 public void update(RootInfo root, DocumentInfo doc, String query) {
1515 mQuery = query;
1516 mRoot = root;
1517 mDocument = doc;
1518 mSearchMode = query != null;
1519 }
1520
Ben Lin2d67ee22016-09-20 16:01:51 -07001521 private String getConfigKey() {
1522 if (mConfigKey == null) {
Steve McKayc8f165c2016-09-20 13:54:05 -07001523 final StringBuilder builder = new StringBuilder();
1524 builder.append(mRoot != null ? mRoot.authority : "null").append(';');
1525 builder.append(mRoot != null ? mRoot.rootId : "null").append(';');
1526 builder.append(mDocument != null ? mDocument.documentId : "null");
Ben Lin2d67ee22016-09-20 16:01:51 -07001527 mConfigKey = builder.toString();
Steve McKayc8f165c2016-09-20 13:54:05 -07001528 }
Ben Lin2d67ee22016-09-20 16:01:51 -07001529 return mConfigKey;
Steve McKayc8f165c2016-09-20 13:54:05 -07001530 }
1531 }
1532}