blob: 3bff8124ce08bf3b4715bf3a80a5d1d2e7234111 [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;
Steve McKay30535bc2016-11-04 14:16:58 -070022import static com.android.documentsui.base.Shared.VERBOSE;
Steve McKayd9caa6a2016-09-15 16:36:45 -070023import static com.android.documentsui.base.State.MODE_GRID;
24import static com.android.documentsui.base.State.MODE_LIST;
Steve McKay0599a442015-05-05 14:50:00 -070025
Steve McKayb03a59c2016-10-19 15:59:53 -070026import android.annotation.DimenRes;
27import android.annotation.FractionRes;
Steve McKaycb9eb422016-02-09 16:17:24 -080028import android.annotation.IntDef;
Ben Kwaf5858932015-04-07 15:43:39 -070029import android.app.Activity;
Jeff Sharkeyf63b7772013-10-01 17:57:41 -070030import android.app.ActivityManager;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070031import android.app.Fragment;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070032import android.app.FragmentManager;
33import android.app.FragmentTransaction;
Vladislav Kaznacheev89b90332015-05-01 13:46:57 -070034import android.content.ClipData;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070035import android.content.Context;
Jeff Sharkey873daa32013-08-18 17:38:20 -070036import android.content.Intent;
Ben Lin743fe592017-03-01 14:36:15 -080037import android.content.res.Resources;
Jeff Sharkeyac9e6272013-08-31 21:27:44 -070038import android.database.Cursor;
Ben Lin743fe592017-03-01 14:36:15 -080039import android.graphics.drawable.StateListDrawable;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070040import android.net.Uri;
Steve McKayb03a59c2016-10-19 15:59:53 -070041import android.os.Build;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070042import android.os.Bundle;
Garfield, Taneba5bb92016-07-22 10:30:49 -070043import android.os.Handler;
Jeff Sharkeyc8ae7a52013-09-18 16:26:49 -070044import android.os.Parcelable;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070045import android.provider.DocumentsContract;
Jeff Sharkeyac9e6272013-08-31 21:27:44 -070046import android.provider.DocumentsContract.Document;
Garfield, Tanca7c0882016-07-18 16:45:27 -070047import android.support.v4.widget.SwipeRefreshLayout;
Steve McKayef280152015-06-11 10:10:49 -070048import android.support.v7.widget.GridLayoutManager;
Steve McKayc5ecf892015-12-22 18:15:31 -080049import android.support.v7.widget.GridLayoutManager.SpanSizeLookup;
Steve McKayef280152015-06-11 10:10:49 -070050import android.support.v7.widget.RecyclerView;
Steve McKayef280152015-06-11 10:10:49 -070051import android.support.v7.widget.RecyclerView.RecyclerListener;
52import android.support.v7.widget.RecyclerView.ViewHolder;
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 -070064
Steve McKay6d20d192016-09-21 17:57:10 -070065import com.android.documentsui.ActionHandler;
Steve McKaybd9f05a2016-10-10 10:18:36 -070066import com.android.documentsui.ActionModeController;
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;
Ben Lin9fea3122016-10-10 18:32:26 -070069import com.android.documentsui.DirectoryReloadLock;
Steve McKayf68210e2015-11-03 15:23:16 -080070import com.android.documentsui.DocumentsApplication;
Ben Lind0202122016-11-10 18:00:12 -080071import com.android.documentsui.DragAndDropHelper;
Steve McKay17b761e2016-09-20 17:26:46 -070072import com.android.documentsui.FocusManager;
Steve McKay92ae43d2016-11-08 12:06:58 -080073import com.android.documentsui.Injector;
74import com.android.documentsui.Injector.ContentScoped;
75import com.android.documentsui.Injector.Injected;
Garfield, Tan804133e2016-04-20 15:13:56 -070076import com.android.documentsui.ItemDragListener;
Aga Wronskab0289052016-03-31 16:37:40 -070077import com.android.documentsui.Metrics;
Garfield Tane9670332017-03-06 18:33:23 -080078import com.android.documentsui.Model;
Steve McKayf68210e2015-11-03 15:23:16 -080079import com.android.documentsui.R;
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;
Tomasz Mikolajewski26640ab2017-02-06 10:37:59 +090082import com.android.documentsui.base.DocumentStack;
Ben Lin2d6caf32016-09-19 17:48:54 -070083import com.android.documentsui.base.EventHandler;
Steve McKay990f76e2016-09-16 12:36:58 -070084import com.android.documentsui.base.EventListener;
Steve McKayd9caa6a2016-09-15 16:36:45 -070085import com.android.documentsui.base.Events.InputEvent;
86import com.android.documentsui.base.Events.MotionInputEvent;
Steve McKay98f8c5f2017-03-03 13:52:14 -080087import com.android.documentsui.base.Features;
Steve McKayd9caa6a2016-09-15 16:36:45 -070088import com.android.documentsui.base.RootInfo;
89import com.android.documentsui.base.Shared;
90import com.android.documentsui.base.State;
91import com.android.documentsui.base.State.ViewMode;
Steve McKayc8889af2016-09-23 11:22:41 -070092import com.android.documentsui.clipping.ClipStore;
Ben Linc5e3e8e2016-07-13 18:16:36 -070093import com.android.documentsui.clipping.DocumentClipper;
Garfield, Tan9666ce62016-07-12 11:02:09 -070094import com.android.documentsui.clipping.UrisSupplier;
Steve McKay988d8a32016-09-27 09:41:17 -070095import com.android.documentsui.dirlist.AnimationView.AnimationType;
Steve McKay16e0c1f2016-09-15 12:41:13 -070096import com.android.documentsui.picker.PickActivity;
Steve McKay4f78ba62016-10-04 16:48:49 -070097import com.android.documentsui.selection.BandController;
98import com.android.documentsui.selection.GestureSelector;
99import com.android.documentsui.selection.Selection;
100import com.android.documentsui.selection.SelectionManager;
101import com.android.documentsui.selection.SelectionMetadata;
Garfield, Tan48334772016-06-28 17:17:38 -0700102import com.android.documentsui.services.FileOperation;
Steve McKayc83baa02016-01-06 18:32:13 -0800103import com.android.documentsui.services.FileOperationService;
Ben Kwabc7df442016-02-02 23:00:02 -0800104import com.android.documentsui.services.FileOperationService.OpType;
Steve McKayc83baa02016-01-06 18:32:13 -0800105import com.android.documentsui.services.FileOperations;
Garfield, Tan11d23482016-08-05 09:33:29 -0700106import com.android.documentsui.sorting.SortDimension;
Garfield, Tan11d23482016-08-05 09:33:29 -0700107import com.android.documentsui.sorting.SortModel;
Steve McKayce0b14c2016-05-03 11:25:52 -0700108
Garfield, Tanb7e5f6b2016-06-30 18:27:47 -0700109import java.io.IOException;
Steve McKaycb9eb422016-02-09 16:17:24 -0800110import java.lang.annotation.Retention;
111import java.lang.annotation.RetentionPolicy;
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700112import java.util.List;
Steve McKay2bab2f82016-06-03 09:23:39 -0700113
114import javax.annotation.Nullable;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700115
116/**
117 * Display the documents inside a single directory.
118 */
Aga Wronskaaf5ace52016-02-17 13:50:42 -0800119public class DirectoryFragment extends Fragment
Steve McKayd0718952016-10-10 13:43:36 -0700120 implements ItemDragListener.DragHost, SwipeRefreshLayout.OnRefreshListener {
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700121
Garfield Tane9670332017-03-06 18:33:23 -0800122 static final int TYPE_NORMAL = 1;
123 static final int TYPE_RECENT_OPEN = 2;
Jeff Sharkey5b535922013-08-02 15:55:26 -0700124
Aga Wronskaaf5ace52016-02-17 13:50:42 -0800125 @IntDef(flag = true, value = {
Steve McKayf1719342016-02-17 15:02:01 -0800126 REQUEST_COPY_DESTINATION
127 })
128 @Retention(RetentionPolicy.SOURCE)
129 public @interface RequestCode {}
Ben Kwaf5858932015-04-07 15:43:39 -0700130 public static final int REQUEST_COPY_DESTINATION = 1;
131
Steve McKay97b4be42016-01-20 15:09:35 -0800132 private static final String TAG = "DirectoryFragment";
133 private static final int LOADER_ID = 42;
Steve McKay97b4be42016-01-20 15:09:35 -0800134
Garfield, Taneba5bb92016-07-22 10:30:49 -0700135 private static final int CACHE_EVICT_LIMIT = 100;
Ben Linbbb7d032016-11-15 13:35:41 -0800136 private static final int REFRESH_SPINNER_TIMEOUT = 500;
Garfield, Taneba5bb92016-07-22 10:30:49 -0700137
Steve McKay92ae43d2016-11-08 12:06:58 -0800138 private BaseActivity mActivity;
139
Steve McKayd0718952016-10-10 13:43:36 -0700140 private State mState;
Jon Mann30d8c792017-02-21 17:44:49 -0800141 private Model mModel;
Steve McKay990f76e2016-09-16 12:36:58 -0700142 private final EventListener<Model.Update> mModelUpdateListener = new ModelUpdateListener();
Steve McKayd0718952016-10-10 13:43:36 -0700143 private final DocumentsAdapter.Environment mAdapterEnv = new AdapterEnvironment();
Steve McKayc8f165c2016-09-20 13:54:05 -0700144
Steve McKay92ae43d2016-11-08 12:06:58 -0800145 @Injected
146 @ContentScoped
147 private Injector<?> mInjector;
Steve McKayc8f165c2016-09-20 13:54:05 -0700148
Steve McKay92ae43d2016-11-08 12:06:58 -0800149 @Injected
150 @ContentScoped
Steve McKay4f78ba62016-10-04 16:48:49 -0700151 private SelectionManager mSelectionMgr;
152
Steve McKay92ae43d2016-11-08 12:06:58 -0800153 @Injected
154 @ContentScoped
Steve McKay6d20d192016-09-21 17:57:10 -0700155 private FocusManager mFocusManager;
156
Steve McKay92ae43d2016-11-08 12:06:58 -0800157 @Injected
158 @ContentScoped
Steve McKayc8889af2016-09-23 11:22:41 -0700159 private ActionHandler mActions;
Steve McKay6d20d192016-09-21 17:57:10 -0700160
Steve McKay92ae43d2016-11-08 12:06:58 -0800161 @Injected
162 @ContentScoped
Garfield Tan84bd0f12016-09-12 14:18:32 -0700163 private ActionModeController mActionModeController;
Steve McKay92ae43d2016-11-08 12:06:58 -0800164
Garfield Tan84bd0f12016-09-12 14:18:32 -0700165 private SelectionMetadata mSelectionMetadata;
Steve McKay2d1e4a32016-07-13 13:03:16 -0700166 private UserInputHandler<InputEvent> mInputHandler;
Steve McKayc8f165c2016-09-20 13:54:05 -0700167 private @Nullable BandController mBandController;
Garfield Tan208945c2016-10-04 14:36:38 -0700168 private @Nullable DragHoverListener mDragHoverListener;
Ben Kwa8e3fd762015-12-17 10:37:00 -0800169 private IconHelper mIconHelper;
Garfield, Tanca7c0882016-07-18 16:45:27 -0700170 private SwipeRefreshLayout mRefreshLayout;
Steve McKayef280152015-06-11 10:10:49 -0700171 private RecyclerView mRecView;
Garfield, Tan171e6f52016-07-29 14:44:58 -0700172 private View mFileList;
Steve McKayef280152015-06-11 10:10:49 -0700173
Steve McKayef280152015-06-11 10:10:49 -0700174 private DocumentsAdapter mAdapter;
Steve McKay1f199482015-05-20 15:58:42 -0700175 private DocumentClipper mClipper;
Ben Kwabc7df442016-02-02 23:00:02 -0800176 private GridLayoutManager mLayout;
Steve McKayd57f5fa2015-07-23 16:33:41 -0700177 private int mColumnCount = 1; // This will get updated when layout changes.
Steve McKay1f199482015-05-20 15:58:42 -0700178
Steve McKayb03a59c2016-10-19 15:59:53 -0700179 private float mLiveScale = 1.0f;
Steve McKay358c3ec2016-10-21 09:16:57 -0700180 private @ViewMode int mMode;
Steve McKayb03a59c2016-10-19 15:59:53 -0700181
Ben Kwa379e1762015-09-21 10:49:52 -0700182 private View mProgressBar;
Ben Kwac42fa402015-09-16 08:04:37 -0700183
Steve McKaybd9f05a2016-10-10 10:18:36 -0700184 private DirectoryState mLocalState;
Ben Lin9fea3122016-10-10 18:32:26 -0700185 private DirectoryReloadLock mReloadLock = new DirectoryReloadLock();
Steve McKayc8f165c2016-09-20 13:54:05 -0700186
Steve McKay2bab2f82016-06-03 09:23:39 -0700187 // Note, we use !null to indicate that selection was restored (from rotation).
188 // So don't fiddle with this field unless you've got the bigger picture in mind.
189 private @Nullable Selection mRestoredSelection = null;
Garfield, Tan804133e2016-04-20 15:13:56 -0700190
Garfield, Tan11d23482016-08-05 09:33:29 -0700191 private SortModel.UpdateListener mSortListener = (model, updateType) -> {
192 // Only when sort order has changed do we need to trigger another loading.
Garfield, Tan61f564b2016-08-16 13:36:15 -0700193 if ((updateType & SortModel.UPDATE_TYPE_SORTING) != 0) {
Garfield Tane9670332017-03-06 18:33:23 -0800194 mActions.loadDocumentsForCurrentStack();
Garfield, Tan11d23482016-08-05 09:33:29 -0700195 }
196 };
Garfield, Tan171e6f52016-07-29 14:44:58 -0700197
Jon Mann30d8c792017-02-21 17:44:49 -0800198 private final Runnable mOnDisplayStateChanged = this::onDisplayStateChanged;
199
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700200 @Override
201 public View onCreateView(
202 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Garfield Tan208945c2016-10-04 14:36:38 -0700203
Steve McKay92ae43d2016-11-08 12:06:58 -0800204 BaseActivity activity = (BaseActivity) getActivity();
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700205 final View view = inflater.inflate(R.layout.fragment_directory, container, false);
206
Tomasz Mikolajewskif5ed16b2016-11-24 12:49:33 +0900207 mProgressBar = view.findViewById(R.id.progressbar);
Steve McKay9de0da62016-08-25 15:18:23 -0700208 assert(mProgressBar != null);
Garfield, Tanca7c0882016-07-18 16:45:27 -0700209
Ben Kwa74e5d412016-02-10 07:46:35 -0800210 mRecView = (RecyclerView) view.findViewById(R.id.dir_list);
Steve McKayef280152015-06-11 10:10:49 -0700211 mRecView.setRecyclerListener(
212 new RecyclerListener() {
213 @Override
214 public void onViewRecycled(ViewHolder holder) {
215 cancelThumbnailTask(holder.itemView);
216 }
217 });
Ben Lin743fe592017-03-01 14:36:15 -0800218
Ben Lind79edd12017-03-14 17:39:36 -0700219 mRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.refresh_layout);
220 mRefreshLayout.setOnRefreshListener(this);
221
Ben Lin743fe592017-03-01 14:36:15 -0800222 Resources resources = getContext().getResources();
223 new FastScroller(mRecView,
224 (StateListDrawable) resources.getDrawable(R.drawable.fast_scroll_thumb_drawable),
225 resources.getDrawable(R.drawable.fast_scroll_track_drawable),
226 (StateListDrawable) resources.getDrawable(R.drawable.fast_scroll_thumb_drawable),
227 resources.getDrawable(R.drawable.fast_scroll_track_drawable),
228 resources.getDimensionPixelSize(R.dimen.fastscroll_default_thickness),
229 resources.getDimensionPixelSize(R.dimen.fastscroll_minimum_range),
230 resources.getDimensionPixelOffset(R.dimen.fastscroll_margin)
231 );
Steve McKayd0718952016-10-10 13:43:36 -0700232 mRecView.setItemAnimator(new DirectoryItemAnimator(activity));
Garfield, Tan171e6f52016-07-29 14:44:58 -0700233 mFileList = view.findViewById(R.id.file_list);
Ben Kwa98bb6cf2015-10-14 08:00:27 -0700234
Steve McKay92ae43d2016-11-08 12:06:58 -0800235 mInjector = activity.getInjector();
Jon Mann30d8c792017-02-21 17:44:49 -0800236 mModel = mInjector.getModel();
237 mModel.reset();
238
239 mInjector.actions.registerDisplayStateChangedListener(mOnDisplayStateChanged);
240
Steve McKay92ae43d2016-11-08 12:06:58 -0800241 mDragHoverListener = mInjector.config.dragAndDropEnabled()
Garfield Tan208945c2016-10-04 14:36:38 -0700242 ? DragHoverListener.create(new DirectoryDragListener(this), mRecView)
243 : null;
Garfield, Tan804133e2016-04-20 15:13:56 -0700244
Garfield Tan208945c2016-10-04 14:36:38 -0700245 // Make the recycler and the empty views responsive to drop events when allowed.
Ben Lin35f99e02016-08-31 12:46:04 -0700246 mRecView.setOnDragListener(mDragHoverListener);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700247
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700248 return view;
249 }
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700250
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700251 @Override
Jeff Sharkeyf63b7772013-10-01 17:57:41 -0700252 public void onDestroyView() {
Steve McKay74956af2016-06-30 21:03:06 -0700253 mSelectionMgr.clearSelection();
Jon Mann30d8c792017-02-21 17:44:49 -0800254 mInjector.actions.unregisterDisplayStateChangedListener(mOnDisplayStateChanged);
Jeff Sharkeyf63b7772013-10-01 17:57:41 -0700255
256 // Cancel any outstanding thumbnail requests
Steve McKayef280152015-06-11 10:10:49 -0700257 final int count = mRecView.getChildCount();
Jeff Sharkeyf63b7772013-10-01 17:57:41 -0700258 for (int i = 0; i < count; i++) {
Steve McKayef280152015-06-11 10:10:49 -0700259 final View view = mRecView.getChildAt(i);
260 cancelThumbnailTask(view);
Jeff Sharkeyf63b7772013-10-01 17:57:41 -0700261 }
Aga Wronska7e5b9632016-02-26 11:36:07 -0800262
Garfield Tane9670332017-03-06 18:33:23 -0800263 mModel.removeUpdateListener(mModelUpdateListener);
264 mModel.removeUpdateListener(mAdapter.getModelUpdateListener());
265
Aga Wronska7e5b9632016-02-26 11:36:07 -0800266 super.onDestroyView();
Jeff Sharkeyf63b7772013-10-01 17:57:41 -0700267 }
268
269 @Override
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700270 public void onActivityCreated(Bundle savedInstanceState) {
271 super.onActivityCreated(savedInstanceState);
272
Steve McKayd0718952016-10-10 13:43:36 -0700273 mActivity = (BaseActivity) getActivity();
274 mState = mActivity.getDisplayState();
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700275
Aga Wronskaaf5ace52016-02-17 13:50:42 -0800276 // Read arguments when object created for the first time.
277 // Restore state if fragment recreated.
278 Bundle args = savedInstanceState == null ? getArguments() : savedInstanceState;
Steve McKayc8f165c2016-09-20 13:54:05 -0700279
Steve McKaybd9f05a2016-10-10 10:18:36 -0700280 mLocalState = new DirectoryState();
281 mLocalState.restore(args);
Jeff Sharkey9656a532013-09-13 13:42:19 -0700282
Steve McKay2bab2f82016-06-03 09:23:39 -0700283 // Restore any selection we may have squirreled away in retained state.
Steve McKayd0718952016-10-10 13:43:36 -0700284 @Nullable RetainedState retained = mActivity.getRetainedState();
Steve McKay2bab2f82016-06-03 09:23:39 -0700285 if (retained != null && retained.hasSelection()) {
286 // We claim the selection for ourselves and null it out once used
287 // so we don't have a rando selection hanging around in RetainedState.
288 mRestoredSelection = retained.selection;
289 retained.selection = null;
290 }
291
Steve McKayd0718952016-10-10 13:43:36 -0700292 mIconHelper = new IconHelper(mActivity, MODE_GRID);
Ben Lin35f99e02016-08-31 12:46:04 -0700293 mClipper = DocumentsApplication.getDocumentClipper(getContext());
Steve McKayc5ecf892015-12-22 18:15:31 -0800294
Ben Lin7b38f342016-12-14 11:52:35 -0800295 mAdapter = new DirectoryAddonsAdapter(
Steve McKayd0718952016-10-10 13:43:36 -0700296 mAdapterEnv, new ModelBackedDocumentsAdapter(mAdapterEnv, mIconHelper));
Steve McKayc5ecf892015-12-22 18:15:31 -0800297
Steve McKayef280152015-06-11 10:10:49 -0700298 mRecView.setAdapter(mAdapter);
299
Ben Lin81afd7f2016-08-22 18:24:03 -0700300 mLayout = new GridLayoutManager(getContext(), mColumnCount) {
301 @Override
302 public void onLayoutCompleted(RecyclerView.State state) {
303 super.onLayoutCompleted(state);
304 mFocusManager.onLayoutCompleted();
305 }
306 };
307
Ben Kwabc7df442016-02-02 23:00:02 -0800308 SpanSizeLookup lookup = mAdapter.createSpanSizeLookup();
309 if (lookup != null) {
310 mLayout.setSpanSizeLookup(lookup);
311 }
312 mRecView.setLayoutManager(mLayout);
313
Steve McKay990f76e2016-09-16 12:36:58 -0700314 mModel.addUpdateListener(mAdapter.getModelUpdateListener());
Ben Lin26819d72016-08-17 12:45:05 -0700315 mModel.addUpdateListener(mModelUpdateListener);
316
Steve McKay92ae43d2016-11-08 12:06:58 -0800317 mSelectionMgr = mInjector.getSelectionManager(mAdapter, this::canSetSelectionState);
318 mFocusManager = mInjector.getFocusManager(mRecView, mModel);
Garfield Tane9670332017-03-06 18:33:23 -0800319 mActions = mInjector.getActionHandler(mReloadLock);
Steve McKayf0fceb42016-09-07 17:35:55 -0700320
Ben Line4ffd062017-01-19 15:10:49 -0800321 mRecView.setAccessibilityDelegateCompat(
Ben Lina355d192017-03-03 14:11:57 -0800322 new AccessibilityEventRouter(mRecView,
Ben Line4ffd062017-01-19 15:10:49 -0800323 (View child) -> onAccessibilityClick(child)));
Steve McKay4f78ba62016-10-04 16:48:49 -0700324 mSelectionMetadata = new SelectionMetadata(mModel::getItem);
325 mSelectionMgr.addItemCallback(mSelectionMetadata);
326
Ben Lin9fea3122016-10-10 18:32:26 -0700327 GestureSelector gestureSel = GestureSelector.create(mSelectionMgr, mRecView, mReloadLock);
Steve McKay4f78ba62016-10-04 16:48:49 -0700328
Steve McKayd0718952016-10-10 13:43:36 -0700329 if (mState.allowMultiple) {
Steve McKay4f78ba62016-10-04 16:48:49 -0700330 mBandController = new BandController(
331 mRecView,
332 mAdapter,
333 mSelectionMgr,
Ben Lin9fea3122016-10-10 18:32:26 -0700334 mReloadLock,
Steve McKay4f78ba62016-10-04 16:48:49 -0700335 (int pos) -> {
336 // The band selection model only operates on documents and directories.
337 // Exclude other types of adapter items like whitespace and dividers.
338 RecyclerView.ViewHolder vh = mRecView.findViewHolderForAdapterPosition(pos);
339 return ModelBackedDocumentsAdapter.isContentType(vh.getItemViewType());
340 });
Ben Lin35f99e02016-08-31 12:46:04 -0700341 }
342
Steve McKay92ae43d2016-11-08 12:06:58 -0800343 DragStartListener mDragStartListener = mInjector.config.dragAndDropEnabled()
Steve McKayf0fceb42016-09-07 17:35:55 -0700344 ? DragStartListener.create(
345 mIconHelper,
Steve McKayd0718952016-10-10 13:43:36 -0700346 mActivity,
Steve McKayf0fceb42016-09-07 17:35:55 -0700347 mModel,
348 mSelectionMgr,
349 mClipper,
Steve McKayd0718952016-10-10 13:43:36 -0700350 mState,
Steve McKayf0fceb42016-09-07 17:35:55 -0700351 this::getModelId,
352 mRecView::findChildViewUnder,
Ben Linfc84b342017-02-17 11:44:45 -0800353 getContext().getDrawable(R.drawable.ic_doc_generic),
Ben Lin1c456292016-10-07 16:43:18 -0700354 mActivity.getShadowBuilder())
Steve McKayf0fceb42016-09-07 17:35:55 -0700355 : DragStartListener.DUMMY;
356
Steve McKayd0718952016-10-10 13:43:36 -0700357 EventHandler<InputEvent> gestureHandler = mState.allowMultiple
Ben Lin2d6caf32016-09-19 17:48:54 -0700358 ? gestureSel::start
359 : EventHandler.createStub(false);
Steve McKayb03a59c2016-10-19 15:59:53 -0700360
Steve McKay2d1e4a32016-07-13 13:03:16 -0700361 mInputHandler = new UserInputHandler<>(
Steve McKayc8889af2016-09-23 11:22:41 -0700362 mActions,
Steve McKay74956af2016-06-30 21:03:06 -0700363 mFocusManager,
Steve McKay6d20d192016-09-21 17:57:10 -0700364 mSelectionMgr,
Garfield Tane6d61e32016-09-08 10:26:59 -0700365 (MotionEvent t) -> MotionInputEvent.obtain(t, mRecView),
Steve McKay74956af2016-06-30 21:03:06 -0700366 this::canSelect,
Ben Linaa66c432016-10-12 12:11:24 -0700367 this::onContextMenuClick,
Steve McKayf0fceb42016-09-07 17:35:55 -0700368 mDragStartListener::onTouchDragEvent,
Ben Lin2d6caf32016-09-19 17:48:54 -0700369 gestureHandler);
Steve McKay79a6fe02016-06-30 12:09:34 -0700370
Garfield Tane6d61e32016-09-08 10:26:59 -0700371 new ListeningGestureDetector(
Steve McKayaad78832017-03-09 10:11:12 -0800372 mInjector.features,
Ben Lin35f99e02016-08-31 12:46:04 -0700373 this.getContext(),
374 mRecView,
Steve McKayf0fceb42016-09-07 17:35:55 -0700375 mDragStartListener::onMouseDragEvent,
Ben Lind79edd12017-03-14 17:39:36 -0700376 mRefreshLayout::setEnabled,
Ben Lin35f99e02016-08-31 12:46:04 -0700377 gestureSel,
378 mInputHandler,
Steve McKayb03a59c2016-10-19 15:59:53 -0700379 mBandController,
380 this::scaleLayout);
Steve McKayd5869592016-05-26 12:04:43 -0700381
Steve McKay92ae43d2016-11-08 12:06:58 -0800382 mActionModeController = mInjector.getActionModeController(
Garfield Tan84bd0f12016-09-12 14:18:32 -0700383 mSelectionMetadata,
Steve McKay5b0a2c12016-10-07 11:22:31 -0700384 this::handleMenuItemClick,
385 mRecView);
386
Garfield Tan84bd0f12016-09-12 14:18:32 -0700387 mSelectionMgr.addCallback(mActionModeController);
Ben Kwa7461a952015-09-01 11:03:01 -0700388
Steve McKayd0718952016-10-10 13:43:36 -0700389 final ActivityManager am = (ActivityManager) mActivity.getSystemService(
Jeff Sharkeyf63b7772013-10-01 17:57:41 -0700390 Context.ACTIVITY_SERVICE);
Garfield Tane9670332017-03-06 18:33:23 -0800391 boolean svelte = am.isLowRamDevice() && (mState.stack.isRecents());
Ben Kwa8e3fd762015-12-17 10:37:00 -0800392 mIconHelper.setThumbnailsEnabled(!svelte);
Jeff Sharkeyf63b7772013-10-01 17:57:41 -0700393
Garfield, Tan11d23482016-08-05 09:33:29 -0700394 // If mDocument is null, we sort it by last modified by default because it's in Recents.
395 final boolean prefersLastModified =
Garfield Tane9670332017-03-06 18:33:23 -0800396 (mLocalState.mDocument == null)
397 || mLocalState.mDocument.prefersSortByLastModified();
Garfield, Tan11d23482016-08-05 09:33:29 -0700398 // Call this before adding the listener to avoid restarting the loader one more time
Steve McKayd0718952016-10-10 13:43:36 -0700399 mState.sortModel.setDefaultDimension(
Garfield, Tan11d23482016-08-05 09:33:29 -0700400 prefersLastModified
401 ? SortModel.SORT_DIMENSION_ID_DATE
402 : SortModel.SORT_DIMENSION_ID_TITLE);
Garfield, Tan171e6f52016-07-29 14:44:58 -0700403
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700404 // Kick off loader at least once
Garfield Tane9670332017-03-06 18:33:23 -0800405 mActions.loadDocumentsForCurrentStack();
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700406 }
407
Garfield, Tan11d23482016-08-05 09:33:29 -0700408 @Override
409 public void onStart() {
410 super.onStart();
411
Garfield, Tan11d23482016-08-05 09:33:29 -0700412 // Add listener to update contents on sort model change
Steve McKayd0718952016-10-10 13:43:36 -0700413 mState.sortModel.addListener(mSortListener);
Garfield, Tan11d23482016-08-05 09:33:29 -0700414 }
415
416 @Override
417 public void onStop() {
418 super.onStop();
419
Steve McKayd0718952016-10-10 13:43:36 -0700420 mState.sortModel.removeListener(mSortListener);
Garfield, Tan11d23482016-08-05 09:33:29 -0700421
422 // Remember last scroll location
Steve McKay17b761e2016-09-20 17:26:46 -0700423 final SparseArray<Parcelable> container = new SparseArray<>();
Garfield, Tan11d23482016-08-05 09:33:29 -0700424 getView().saveHierarchyState(container);
Steve McKayd0718952016-10-10 13:43:36 -0700425 mState.dirConfigs.put(mLocalState.getConfigKey(), container);
Garfield, Tan11d23482016-08-05 09:33:29 -0700426 }
427
Steve McKay2bab2f82016-06-03 09:23:39 -0700428 public void retainState(RetainedState state) {
Steve McKay74956af2016-06-30 21:03:06 -0700429 state.selection = mSelectionMgr.getSelection(new Selection());
Steve McKay2bab2f82016-06-03 09:23:39 -0700430 }
431
Jeff Sharkey42d26792013-09-06 13:22:09 -0700432 @Override
Steve McKay84fa3712016-02-08 19:09:42 -0800433 public void onSaveInstanceState(Bundle outState) {
Aga Wronskaaf5ace52016-02-17 13:50:42 -0800434 super.onSaveInstanceState(outState);
435
Steve McKaybd9f05a2016-10-10 10:18:36 -0700436 mLocalState.save(outState);
Steve McKay84fa3712016-02-08 19:09:42 -0800437 }
438
439 @Override
Ben Linebf2a172016-06-03 13:46:52 -0700440 public void onCreateContextMenu(ContextMenu menu,
441 View v,
442 ContextMenu.ContextMenuInfo menuInfo) {
443 super.onCreateContextMenu(menu, v, menuInfo);
Garfield Tan06c4b112016-09-14 11:05:58 -0700444 final MenuInflater inflater = getActivity().getMenuInflater();
Ben Linebf2a172016-06-03 13:46:52 -0700445
Garfield Tan06c4b112016-09-14 11:05:58 -0700446 final String modelId = getModelId(v);
447 if (modelId == null) {
Steve McKay990f76e2016-09-16 12:36:58 -0700448 // TODO: inject DirectoryDetails into MenuManager constructor
449 // Since both classes are supplied by Activity and created
450 // at the same time.
Steve McKay92ae43d2016-11-08 12:06:58 -0800451 mInjector.menuManager.inflateContextMenuForContainer(menu, inflater);
Garfield Tan239ab972016-09-16 13:33:48 -0700452 } else {
Steve McKay92ae43d2016-11-08 12:06:58 -0800453 mInjector.menuManager.inflateContextMenuForDocs(menu, inflater, mSelectionMetadata);
Ben Linebf2a172016-06-03 13:46:52 -0700454 }
455 }
456
457 @Override
458 public boolean onContextItemSelected(MenuItem item) {
459 return handleMenuItemClick(item);
460 }
461
Steve McKayf1719342016-02-17 15:02:01 -0800462 private void handleCopyResult(int resultCode, Intent data) {
Garfield, Tanedce5542016-06-17 15:32:28 -0700463
Steve McKaybd9f05a2016-10-10 10:18:36 -0700464 FileOperation operation = mLocalState.claimPendingOperation();
Garfield, Tanedce5542016-06-17 15:32:28 -0700465
Ben Kwaf5858932015-04-07 15:43:39 -0700466 if (resultCode == Activity.RESULT_CANCELED || data == null) {
467 // User pressed the back button or otherwise cancelled the destination pick. Don't
468 // proceed with the copy.
Garfield, Tanb7e5f6b2016-06-30 18:27:47 -0700469 operation.dispose();
Ben Kwaf5858932015-04-07 15:43:39 -0700470 return;
471 }
472
Garfield, Tan48334772016-06-28 17:17:38 -0700473 operation.setDestination(data.getParcelableExtra(Shared.EXTRA_STACK));
Steve McKay92ae43d2016-11-08 12:06:58 -0800474 FileOperations.start(
475 mActivity,
476 operation,
Ben Lin68689622016-11-09 16:10:38 -0800477 mInjector.dialogs::showFileOperationStatus);
Ben Kwaf5858932015-04-07 15:43:39 -0700478 }
479
Ben Linaa66c432016-10-12 12:11:24 -0700480 protected boolean onContextMenuClick(InputEvent e) {
Garfield Tan239ab972016-09-16 13:33:48 -0700481 final View v;
482 final float x, y;
Steve McKay1597e942016-09-09 11:54:05 -0700483 if (e.isOverModelItem()) {
484 DocumentHolder doc = (DocumentHolder) e.getDocumentDetails();
Ben Linebf2a172016-06-03 13:46:52 -0700485
Garfield Tan239ab972016-09-16 13:33:48 -0700486 v = doc.itemView;
487 x = e.getX() - v.getLeft();
488 y = e.getY() - v.getTop();
489 } else {
Ben Lin7b38f342016-12-14 11:52:35 -0800490 v = mRecView;
Garfield Tan239ab972016-09-16 13:33:48 -0700491 x = e.getX();
492 y = e.getY();
Steve McKay79a6fe02016-06-30 12:09:34 -0700493 }
Steve McKay74956af2016-06-30 21:03:06 -0700494
Steve McKay92ae43d2016-11-08 12:06:58 -0800495 mInjector.menuManager.showContextMenu(this, v, x, y);
Steve McKay1597e942016-09-09 11:54:05 -0700496
Ben Linebf2a172016-06-03 13:46:52 -0700497 return true;
498 }
499
Steve McKay7776aa52016-01-25 19:00:22 -0800500 public void onViewModeChanged() {
501 // Mode change is just visual change; no need to kick loader.
Jon Mann30d8c792017-02-21 17:44:49 -0800502 onDisplayStateChanged();
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700503 }
504
Jon Mann30d8c792017-02-21 17:44:49 -0800505 private void onDisplayStateChanged() {
Steve McKayd0718952016-10-10 13:43:36 -0700506 updateLayout(mState.derivedMode);
Steve McKayef280152015-06-11 10:10:49 -0700507 mRecView.setAdapter(mAdapter);
508 }
509
510 /**
Ben Kwabc7df442016-02-02 23:00:02 -0800511 * Updates the layout after the view mode switches.
512 * @param mode The new view mode.
Steve McKayef280152015-06-11 10:10:49 -0700513 */
Ben Kwabc7df442016-02-02 23:00:02 -0800514 private void updateLayout(@ViewMode int mode) {
Steve McKayb03a59c2016-10-19 15:59:53 -0700515 mMode = mode;
Ben Kwabc7df442016-02-02 23:00:02 -0800516 mColumnCount = calculateColumnCount(mode);
517 if (mLayout != null) {
518 mLayout.setSpanCount(mColumnCount);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700519 }
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700520
Steve McKayac155ab2016-01-12 11:14:33 -0800521 int pad = getDirectoryPadding(mode);
522 mRecView.setPadding(pad, pad, pad, pad);
Ben Kwabc7df442016-02-02 23:00:02 -0800523 mRecView.requestLayout();
Steve McKayd5869592016-05-26 12:04:43 -0700524 if (mBandController != null) {
525 mBandController.handleLayoutChanged();
526 }
Steve McKay7776aa52016-01-25 19:00:22 -0800527 mIconHelper.setViewMode(mode);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700528 }
529
Steve McKayb03a59c2016-10-19 15:59:53 -0700530 /**
531 * Updates the layout after the view mode switches.
532 * @param mode The new view mode.
533 */
534 private void scaleLayout(float scale) {
535 assert(Build.IS_DEBUGGABLE);
Steve McKay30535bc2016-11-04 14:16:58 -0700536 if (VERBOSE) Log.v(
537 TAG, "Handling scale event: " + scale + ", existing scale: " + mLiveScale);
Steve McKayb03a59c2016-10-19 15:59:53 -0700538
539 if (mMode == MODE_GRID) {
540 float minScale = getFraction(R.fraction.grid_scale_min);
541 float maxScale = getFraction(R.fraction.grid_scale_max);
542 float nextScale = mLiveScale * scale;
543
Steve McKay30535bc2016-11-04 14:16:58 -0700544 if (VERBOSE) Log.v(TAG,
Steve McKayb03a59c2016-10-19 15:59:53 -0700545 "Next scale " + nextScale + ", Min/max scale " + minScale + "/" + maxScale);
546
547 if (nextScale > minScale && nextScale < maxScale) {
548 if (DEBUG) Log.d(TAG, "Updating grid scale: " + scale);
549 mLiveScale = nextScale;
550 updateLayout(mMode);
551 }
552
553 } else {
554 if (DEBUG) Log.d(TAG, "List mode, ignoring scale: " + scale);
555 mLiveScale = 1.0f;
556 }
557 }
558
Ben Kwabc7df442016-02-02 23:00:02 -0800559 private int calculateColumnCount(@ViewMode int mode) {
560 if (mode == MODE_LIST) {
561 // List mode is a "grid" with 1 column.
562 return 1;
563 }
564
Steve McKayb03a59c2016-10-19 15:59:53 -0700565 int cellWidth = getScaledSize(R.dimen.grid_width);
566 int cellMargin = 2 * getScaledSize(R.dimen.grid_item_margin);
567 int viewPadding =
568 (int) ((mRecView.getPaddingLeft() + mRecView.getPaddingRight()) * mLiveScale);
Steve McKayfefcd702015-08-20 16:19:38 +0000569
Steve McKayb03a59c2016-10-19 15:59:53 -0700570 // RecyclerView sometimes gets a width of 0 (see b/27150284).
571 // Clamp so that we always lay out the grid with at least 2 columns by default.
Ben Kwa2e3d4182016-03-16 13:01:20 -0700572 int columnCount = Math.max(2,
Steve McKayfefcd702015-08-20 16:19:38 +0000573 (mRecView.getWidth() - viewPadding) / (cellWidth + cellMargin));
574
Steve McKayb03a59c2016-10-19 15:59:53 -0700575 // Finally with our grid count logic firmly in place, we apply any live scaling
576 // captured by the scale gesture detector.
577 return Math.max(1, Math.round(columnCount / mLiveScale));
578 }
579
580
581 /**
582 * Moderately abuse the "fraction" resource type for our purposes.
583 */
584 private float getFraction(@FractionRes int id) {
585 return getResources().getFraction(id, 1, 0);
586 }
587
588 private int getScaledSize(@DimenRes int id) {
589 return (int) (getResources().getDimensionPixelSize(id) * mLiveScale);
Steve McKayd57f5fa2015-07-23 16:33:41 -0700590 }
591
Ben Kwabc7df442016-02-02 23:00:02 -0800592 private int getDirectoryPadding(@ViewMode int mode) {
Steve McKayac155ab2016-01-12 11:14:33 -0800593 switch (mode) {
594 case MODE_GRID:
Ben Kwabc7df442016-02-02 23:00:02 -0800595 return getResources().getDimensionPixelSize(R.dimen.grid_container_padding);
Steve McKayac155ab2016-01-12 11:14:33 -0800596 case MODE_LIST:
Ben Kwabc7df442016-02-02 23:00:02 -0800597 return getResources().getDimensionPixelSize(R.dimen.list_container_padding);
Steve McKayac155ab2016-01-12 11:14:33 -0800598 default:
599 throw new IllegalArgumentException("Unsupported layout mode: " + mode);
600 }
601 }
602
Ben Linebf2a172016-06-03 13:46:52 -0700603 private boolean handleMenuItemClick(MenuItem item) {
Steve McKay74956af2016-06-30 21:03:06 -0700604 Selection selection = mSelectionMgr.getSelection(new Selection());
Jeff Sharkey873daa32013-08-18 17:38:20 -0700605
Ben Linebf2a172016-06-03 13:46:52 -0700606 switch (item.getItemId()) {
607 case R.id.menu_open:
608 openDocuments(selection);
Garfield Tan84bd0f12016-09-12 14:18:32 -0700609 mActionModeController.finishActionMode();
Ben Linebf2a172016-06-03 13:46:52 -0700610 return true;
Jeff Sharkey873daa32013-08-18 17:38:20 -0700611
Garfield Tan239ab972016-09-16 13:33:48 -0700612 case R.id.menu_open_with:
613 showChooserForDoc(selection);
614 return true;
615
616 case R.id.menu_open_in_new_window:
Steve McKay5b0a2c12016-10-07 11:22:31 -0700617 mActions.openSelectedInNewWindow();
Garfield Tan239ab972016-09-16 13:33:48 -0700618 return true;
619
Ben Linebf2a172016-06-03 13:46:52 -0700620 case R.id.menu_share:
Steve McKayd0718952016-10-10 13:43:36 -0700621 mActions.shareSelectedDocuments();
Ben Linebf2a172016-06-03 13:46:52 -0700622 return true;
Jeff Sharkey873daa32013-08-18 17:38:20 -0700623
Ben Linebf2a172016-06-03 13:46:52 -0700624 case R.id.menu_delete:
625 // deleteDocuments will end action mode if the documents are deleted.
626 // It won't end action mode if user cancels the delete.
Steve McKaybd9f05a2016-10-10 10:18:36 -0700627 mActions.deleteSelectedDocuments();
Ben Linebf2a172016-06-03 13:46:52 -0700628 return true;
Ben Kwacb4461f2015-05-05 11:50:11 -0700629
Ben Linebf2a172016-06-03 13:46:52 -0700630 case R.id.menu_copy_to:
Tomasz Mikolajewski26640ab2017-02-06 10:37:59 +0900631 transferDocuments(selection, null, FileOperationService.OPERATION_COPY);
Ben Linebf2a172016-06-03 13:46:52 -0700632 // TODO: Only finish selection mode if copy-to is not canceled.
633 // Need to plum down into handling the way we do with deleteDocuments.
Garfield Tan84bd0f12016-09-12 14:18:32 -0700634 mActionModeController.finishActionMode();
Ben Linebf2a172016-06-03 13:46:52 -0700635 return true;
Ben Kwa41b26c12015-03-31 10:11:43 -0700636
Tomasz Mikolajewski26640ab2017-02-06 10:37:59 +0900637 case R.id.menu_compress:
638 transferDocuments(selection, mState.stack,
639 FileOperationService.OPERATION_COMPRESS);
640 // TODO: Only finish selection mode if compress is not canceled.
641 // Need to plum down into handling the way we do with deleteDocuments.
642 mActionModeController.finishActionMode();
643 return true;
644
Tomasz Mikolajewski26640ab2017-02-06 10:37:59 +0900645 // TODO: Implement extract (to the current directory).
Tomasz Mikolajewskibc7eb5f2017-01-27 16:29:30 +0900646 case R.id.menu_extract_to:
Tomasz Mikolajewski26640ab2017-02-06 10:37:59 +0900647 transferDocuments(selection, null, FileOperationService.OPERATION_EXTRACT);
Tomasz Mikolajewskibc7eb5f2017-01-27 16:29:30 +0900648 // TODO: Only finish selection mode if compress-to is not canceled.
649 // Need to plum down into handling the way we do with deleteDocuments.
650 mActionModeController.finishActionMode();
651 return true;
652
Ben Linebf2a172016-06-03 13:46:52 -0700653 case R.id.menu_move_to:
654 // Exit selection mode first, so we avoid deselecting deleted documents.
Garfield Tan84bd0f12016-09-12 14:18:32 -0700655 mActionModeController.finishActionMode();
Tomasz Mikolajewski26640ab2017-02-06 10:37:59 +0900656 transferDocuments(selection, null, FileOperationService.OPERATION_MOVE);
Ben Linebf2a172016-06-03 13:46:52 -0700657 return true;
Steve McKay1f199482015-05-20 15:58:42 -0700658
Ben Linebf2a172016-06-03 13:46:52 -0700659 case R.id.menu_cut_to_clipboard:
Ben Lind947f012016-10-18 14:32:49 -0700660 mActions.cutToClipboard();
Ben Linebf2a172016-06-03 13:46:52 -0700661 return true;
Ben Kwa512a6ba2015-03-31 08:15:21 -0700662
Ben Linebf2a172016-06-03 13:46:52 -0700663 case R.id.menu_copy_to_clipboard:
Ben Lind947f012016-10-18 14:32:49 -0700664 mActions.copyToClipboard();
Ben Linebf2a172016-06-03 13:46:52 -0700665 return true;
Aga Wronska3c237182016-01-20 16:32:33 -0800666
Ben Linebf2a172016-06-03 13:46:52 -0700667 case R.id.menu_paste_from_clipboard:
668 pasteFromClipboard();
669 return true;
670
Ben Linb2dfa122016-08-11 17:42:57 -0700671 case R.id.menu_paste_into_folder:
672 pasteIntoFolder();
673 return true;
674
Ben Linebf2a172016-06-03 13:46:52 -0700675 case R.id.menu_select_all:
Jon Mann30d8c792017-02-21 17:44:49 -0800676 mActions.selectAllFiles();
Ben Linebf2a172016-06-03 13:46:52 -0700677 return true;
678
679 case R.id.menu_rename:
680 // Exit selection mode first, so we avoid deselecting deleted
681 // (renamed) documents.
Garfield Tan84bd0f12016-09-12 14:18:32 -0700682 mActionModeController.finishActionMode();
Ben Linebf2a172016-06-03 13:46:52 -0700683 renameDocuments(selection);
684 return true;
685
Jon Mann253a9922017-03-21 18:53:27 -0700686 case R.id.menu_view_in_owner:
687 mActions.viewInOwner();
688 return true;
689
Ben Linebf2a172016-06-03 13:46:52 -0700690 default:
691 // See if BaseActivity can handle this particular MenuItem
Steve McKayd0718952016-10-10 13:43:36 -0700692 if (!mActivity.onOptionsItemSelected(item)) {
Steve McKay3abb0e32015-12-01 17:02:42 -0800693 if (DEBUG) Log.d(TAG, "Unhandled menu item selected: " + item);
694 return false;
Ben Linebf2a172016-06-03 13:46:52 -0700695 }
696 return true;
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700697 }
Steve McKayef280152015-06-11 10:10:49 -0700698 }
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700699
Ben Line4ffd062017-01-19 15:10:49 -0800700 private boolean onAccessibilityClick(View child) {
701 DocumentDetails doc = getDocumentHolder(child);
Tomasz Mikolajewskid22cc182017-03-15 16:13:46 +0900702 mActions.openDocument(doc, ActionHandler.VIEW_TYPE_PREVIEW,
703 ActionHandler.VIEW_TYPE_REGULAR);
Ben Line4ffd062017-01-19 15:10:49 -0800704 return true;
705 }
706
Ben Kwa8e3fd762015-12-17 10:37:00 -0800707 private void cancelThumbnailTask(View view) {
Steve McKayef280152015-06-11 10:10:49 -0700708 final ImageView iconThumb = (ImageView) view.findViewById(R.id.icon_thumb);
709 if (iconThumb != null) {
Ben Kwa8e3fd762015-12-17 10:37:00 -0800710 mIconHelper.stopLoading(iconThumb);
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700711 }
Steve McKayef280152015-06-11 10:10:49 -0700712 }
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700713
Steve McKayf61f93a2016-09-01 15:39:18 -0700714 // Support for opening multiple documents is currently exclusive to DocumentsActivity.
Steve McKayef280152015-06-11 10:10:49 -0700715 private void openDocuments(final Selection selected) {
Aga Wronska94e53e42016-04-07 13:09:58 -0700716 Metrics.logUserAction(getContext(), Metrics.USER_ACTION_OPEN);
717
Steve McKayf61f93a2016-09-01 15:39:18 -0700718 // Model must be accessed in UI thread, since underlying cursor is not threadsafe.
719 List<DocumentInfo> docs = mModel.getDocuments(selected);
Garfield Tan06c4b112016-09-14 11:05:58 -0700720 if (docs.size() > 1) {
Steve McKayd79cc4a2016-10-11 16:34:40 -0700721 mActivity.onDocumentsPicked(docs);
Garfield Tan06c4b112016-09-14 11:05:58 -0700722 } else {
Steve McKayd79cc4a2016-10-11 16:34:40 -0700723 mActivity.onDocumentPicked(docs.get(0));
Garfield Tan06c4b112016-09-14 11:05:58 -0700724 }
Jeff Sharkey873daa32013-08-18 17:38:20 -0700725 }
726
Garfield Tan239ab972016-09-16 13:33:48 -0700727 private void showChooserForDoc(final Selection selected) {
728 Metrics.logUserAction(getContext(), Metrics.USER_ACTION_OPEN);
729
730 assert(selected.size() == 1);
731 DocumentInfo doc =
732 DocumentInfo.fromDirectoryCursor(mModel.getItem(selected.iterator().next()));
Garfield Tan208945c2016-10-04 14:36:38 -0700733 mActions.showChooserForDoc(doc);
Garfield Tan239ab972016-09-16 13:33:48 -0700734 }
735
Tomasz Mikolajewski26640ab2017-02-06 10:37:59 +0900736 private void transferDocuments(final Selection selected, @Nullable DocumentStack destination,
737 final @OpType int mode) {
Tomasz Mikolajewskibc7eb5f2017-01-27 16:29:30 +0900738 switch (mode) {
739 case FileOperationService.OPERATION_COPY:
740 Metrics.logUserAction(getContext(), Metrics.USER_ACTION_COPY_TO);
741 break;
742 case FileOperationService.OPERATION_COMPRESS:
Tomasz Mikolajewski2e8acf02017-02-09 17:24:23 +0900743 Metrics.logUserAction(getContext(), Metrics.USER_ACTION_COMPRESS);
Tomasz Mikolajewskibc7eb5f2017-01-27 16:29:30 +0900744 break;
745 case FileOperationService.OPERATION_EXTRACT:
746 Metrics.logUserAction(getContext(), Metrics.USER_ACTION_EXTRACT_TO);
747 break;
748 case FileOperationService.OPERATION_MOVE:
749 Metrics.logUserAction(getContext(), Metrics.USER_ACTION_MOVE_TO);
750 break;
Aga Wronska94e53e42016-04-07 13:09:58 -0700751 }
752
Garfield, Tanb7e5f6b2016-06-30 18:27:47 -0700753 UrisSupplier srcs;
754 try {
Steve McKayc8889af2016-09-23 11:22:41 -0700755 ClipStore clipStorage = DocumentsApplication.getClipStore(getContext());
756 srcs = UrisSupplier.create(selected, mModel::getItemUri, clipStorage);
Tomasz Mikolajewskibc7eb5f2017-01-27 16:29:30 +0900757 } catch (IOException e) {
Garfield, Tanb7e5f6b2016-06-30 18:27:47 -0700758 throw new RuntimeException("Failed to create uri supplier.", e);
759 }
Garfield, Tan48334772016-06-28 17:17:38 -0700760
Garfield Tan03a3a392016-12-12 14:06:45 -0800761 final DocumentInfo parent = mState.stack.peek();
Tomasz Mikolajewski26640ab2017-02-06 10:37:59 +0900762 final FileOperation operation = new FileOperation.Builder()
Garfield, Tan48334772016-06-28 17:17:38 -0700763 .withOpType(mode)
Garfield Tan03a3a392016-12-12 14:06:45 -0800764 .withSrcParent(parent == null ? null : parent.derivedUri)
Garfield, Tan48334772016-06-28 17:17:38 -0700765 .withSrcs(srcs)
766 .build();
Steve McKay1abf6cf2016-04-08 17:34:11 -0700767
Tomasz Mikolajewski26640ab2017-02-06 10:37:59 +0900768 if (destination != null) {
769 operation.setDestination(destination);
770 FileOperations.start(
771 mActivity,
772 operation,
773 mInjector.dialogs::showFileOperationStatus);
774 return;
775 }
776
777 // Pop up a dialog to pick a destination. This is inadequate but works for now.
778 // TODO: Implement a picker that is to spec.
779 mLocalState.mPendingOperation = operation;
780 final Intent intent = new Intent(
781 Shared.ACTION_PICK_COPY_DESTINATION,
782 Uri.EMPTY,
783 getActivity(),
784 PickActivity.class);
785
Steve McKayf1719342016-02-17 15:02:01 -0800786 // Set an appropriate title on the drawer when it is shown in the picker.
787 // Coupled with the fact that we auto-open the drawer for copy/move operations
788 // it should basically be the thing people see first.
Tomasz Mikolajewskibc7eb5f2017-01-27 16:29:30 +0900789 int drawerTitleId;
790 switch (mode) {
791 case FileOperationService.OPERATION_COPY:
792 drawerTitleId = R.string.menu_copy;
793 break;
794 case FileOperationService.OPERATION_COMPRESS:
795 drawerTitleId = R.string.menu_compress;
796 break;
797 case FileOperationService.OPERATION_EXTRACT:
798 drawerTitleId = R.string.menu_extract;
799 break;
800 case FileOperationService.OPERATION_MOVE:
Jon Mannd1a26222017-02-14 13:15:57 -0800801 drawerTitleId = R.string.menu_move;
Tomasz Mikolajewskibc7eb5f2017-01-27 16:29:30 +0900802 break;
803 default:
804 throw new UnsupportedOperationException("Unknown mode: " + mode);
805 }
Tomasz Mikolajewski26640ab2017-02-06 10:37:59 +0900806
Steve McKayf1719342016-02-17 15:02:01 -0800807 intent.putExtra(DocumentsContract.EXTRA_PROMPT, getResources().getString(drawerTitleId));
808
Steve McKayf61f93a2016-09-01 15:39:18 -0700809 // Model must be accessed in UI thread, since underlying cursor is not threadsafe.
810 List<DocumentInfo> docs = mModel.getDocuments(selected);
Steve McKayf1719342016-02-17 15:02:01 -0800811
Steve McKayf61f93a2016-09-01 15:39:18 -0700812 // Determine if there is a directory in the set of documents
813 // to be copied? Why? Directory creation isn't supported by some roots
814 // (like Downloads). This informs DocumentsActivity (the "picker")
815 // to restrict available roots to just those with support.
816 intent.putExtra(Shared.EXTRA_DIRECTORY_COPY, hasDirectory(docs));
817 intent.putExtra(FileOperationService.EXTRA_OPERATION_TYPE, mode);
Steve McKayf1719342016-02-17 15:02:01 -0800818
Steve McKayf61f93a2016-09-01 15:39:18 -0700819 // This just identifies the type of request...we'll check it
820 // when we reveive a response.
821 startActivityForResult(intent, REQUEST_COPY_DESTINATION);
Ben Kwa41b26c12015-03-31 10:11:43 -0700822 }
823
Steve McKayd0718952016-10-10 13:43:36 -0700824 @Override
825 public void onActivityResult(@RequestCode int requestCode, int resultCode, Intent data) {
826 switch (requestCode) {
827 case REQUEST_COPY_DESTINATION:
828 handleCopyResult(resultCode, data);
829 break;
830 default:
831 throw new UnsupportedOperationException("Unknown request code: " + requestCode);
832 }
833 }
834
Steve McKayf1719342016-02-17 15:02:01 -0800835 private static boolean hasDirectory(List<DocumentInfo> docs) {
836 for (DocumentInfo info : docs) {
837 if (Document.MIME_TYPE_DIR.equals(info.mimeType)) {
838 return true;
839 }
840 }
841 return false;
842 }
843
Aga Wronska3c237182016-01-20 16:32:33 -0800844 private void renameDocuments(Selection selected) {
Aga Wronska94e53e42016-04-07 13:09:58 -0700845 Metrics.logUserAction(getContext(), Metrics.USER_ACTION_RENAME);
846
Aga Wronska3c237182016-01-20 16:32:33 -0800847 // Batch renaming not supported
848 // Rename option is only available in menu when 1 document selected
Steve McKay0af8afd2016-02-25 13:34:03 -0800849 assert(selected.size() == 1);
Aga Wronska3c237182016-01-20 16:32:33 -0800850
Steve McKayf61f93a2016-09-01 15:39:18 -0700851 // Model must be accessed in UI thread, since underlying cursor is not threadsafe.
852 List<DocumentInfo> docs = mModel.getDocuments(selected);
Jon Manncf120d42017-02-14 11:43:39 -0800853 RenameDocumentFragment.show(getChildFragmentManager(), docs.get(0));
854 }
855
856 Model getModel(){
857 return mModel;
Aga Wronska3c237182016-01-20 16:32:33 -0800858 }
859
Steve McKayd0718952016-10-10 13:43:36 -0700860 private boolean isDocumentEnabled(String mimeType, int flags) {
Steve McKay92ae43d2016-11-08 12:06:58 -0800861 return mInjector.config.isDocumentEnabled(mimeType, flags, mState);
Steve McKayc5ecf892015-12-22 18:15:31 -0800862 }
863
Ben Lindb7a0e72017-03-02 11:18:22 -0800864 /**
865 * Paste selection files from the primary clip into the current window.
866 */
Steve McKayf68210e2015-11-03 15:23:16 -0800867 public void pasteFromClipboard() {
Aga Wronska94e53e42016-04-07 13:09:58 -0700868 Metrics.logUserAction(getContext(), Metrics.USER_ACTION_PASTE_CLIPBOARD);
Ben Lindb7a0e72017-03-02 11:18:22 -0800869 // Since we are pasting into the current window, we already have the destination in the
870 // stack. No need for a destination DocumentInfo.
Garfield, Tana5588b62016-07-13 09:23:04 -0700871 mClipper.copyFromClipboard(
Steve McKay92ae43d2016-11-08 12:06:58 -0800872 mState.stack,
Ben Lin68689622016-11-09 16:10:38 -0800873 mInjector.dialogs::showFileOperationStatus);
Steve McKay1f199482015-05-20 15:58:42 -0700874 getActivity().invalidateOptionsMenu();
Steve McKay0599a442015-05-05 14:50:00 -0700875 }
876
Ben Linb2dfa122016-08-11 17:42:57 -0700877 public void pasteIntoFolder() {
878 assert (mSelectionMgr.getSelection().size() == 1);
879
880 String modelId = mSelectionMgr.getSelection().iterator().next();
881 Cursor dstCursor = mModel.getItem(modelId);
882 if (dstCursor == null) {
883 Log.w(TAG, "Invalid destination. Can't obtain cursor for modelId: " + modelId);
884 return;
885 }
Ben Linb2dfa122016-08-11 17:42:57 -0700886 DocumentInfo destination = DocumentInfo.fromDirectoryCursor(dstCursor);
887 mClipper.copyFromClipboard(
Steve McKay92ae43d2016-11-08 12:06:58 -0800888 destination,
889 mState.stack,
Ben Lin68689622016-11-09 16:10:38 -0800890 mInjector.dialogs::showFileOperationStatus);
Ben Linb2dfa122016-08-11 17:42:57 -0700891 getActivity().invalidateOptionsMenu();
892 }
893
Vladislav Kaznacheevb6da7222015-05-01 14:18:57 -0700894 private void setupDragAndDropOnDocumentView(View view, Cursor cursor) {
895 final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
896 if (Document.MIME_TYPE_DIR.equals(docMimeType)) {
897 // Make a directory item a drop target. Drop on non-directories and empty space
898 // is handled at the list/grid view level.
Ben Lin35f99e02016-08-31 12:46:04 -0700899 view.setOnDragListener(mDragHoverListener);
Vladislav Kaznacheevb6da7222015-05-01 14:18:57 -0700900 }
Vladislav Kaznacheevb6da7222015-05-01 14:18:57 -0700901 }
902
Garfield, Tanb7e5f6b2016-06-30 18:27:47 -0700903 void dragStopped(boolean result) {
904 if (result) {
Steve McKay74956af2016-06-30 21:03:06 -0700905 mSelectionMgr.clearSelection();
Garfield, Tanb7e5f6b2016-06-30 18:27:47 -0700906 }
Garfield, Tan804133e2016-04-20 15:13:56 -0700907 }
Vladislav Kaznacheevb6da7222015-05-01 14:18:57 -0700908
Garfield, Tan804133e2016-04-20 15:13:56 -0700909 @Override
910 public void runOnUiThread(Runnable runnable) {
911 getActivity().runOnUiThread(runnable);
912 }
Ben Kwa13e26052016-02-18 16:45:45 -0800913
Ben Lind0202122016-11-10 18:00:12 -0800914 // In DirectoryFragment, we close the roots drawer right away.
915 // We also want to update the Drag Shadow to indicate whether the
916 // item is droppable or not.
Ben Lin7f72a3c2016-09-27 16:37:28 -0700917 @Override
Ben Lin1c456292016-10-07 16:43:18 -0700918 public void onDragEntered(View v, Object localState) {
Ben Lin166c5c62016-11-01 12:14:38 -0700919 mActivity.setRootsDrawerOpen(false);
Ben Lind0202122016-11-10 18:00:12 -0800920 mActivity.getShadowBuilder()
921 .setAppearDroppable(DragAndDropHelper.canCopyTo(localState, getDestination(v)));
Ben Lin1c456292016-10-07 16:43:18 -0700922 v.updateDragShadow(mActivity.getShadowBuilder());
Ben Lin7f72a3c2016-09-27 16:37:28 -0700923 }
924
Ben Lind0202122016-11-10 18:00:12 -0800925 // In DirectoryFragment, we always reset the background of the Drag Shadow once it
926 // exits.
927 @Override
928 public void onDragExited(View v, Object localState) {
929 mActivity.getShadowBuilder().resetBackground();
930 v.updateDragShadow(mActivity.getShadowBuilder());
931 if (v.getParent() == mRecView) {
932 DocumentHolder holder = getDocumentHolder(v);
933 if (holder != null) {
934 holder.resetDropHighlight();
935 }
936 }
937 }
938
939 // In DirectoryFragment, we spring loads the hovered folder.
Garfield, Tan804133e2016-04-20 15:13:56 -0700940 @Override
941 public void onViewHovered(View view) {
Steve McKayd0718952016-10-10 13:43:36 -0700942 BaseActivity activity = mActivity;
Garfield, Tan804133e2016-04-20 15:13:56 -0700943 if (getModelId(view) != null) {
Ben Lind8d0ad22017-01-11 13:30:50 -0800944 mActions.springOpenDirectory(getDestination(view));
Garfield, Tan804133e2016-04-20 15:13:56 -0700945 }
Garfield, Tan7d66a862016-05-11 10:28:41 -0700946 activity.setRootsDrawerOpen(false);
Garfield, Tan804133e2016-04-20 15:13:56 -0700947 }
Ben Kwaa8c0da42016-02-25 14:10:40 -0800948
Garfield, Tanedce5542016-06-17 15:32:28 -0700949 boolean handleDropEvent(View v, DragEvent event) {
Garfield, Tan7d66a862016-05-11 10:28:41 -0700950 BaseActivity activity = (BaseActivity) getActivity();
951 activity.setRootsDrawerOpen(false);
952
Garfield, Tan804133e2016-04-20 15:13:56 -0700953 ClipData clipData = event.getClipData();
954 assert (clipData != null);
Vladislav Kaznacheevb6da7222015-05-01 14:18:57 -0700955
Ben Lin30b0dc12017-03-07 15:37:16 -0800956 assert(mClipper.getOpType(clipData) == FileOperationService.OPERATION_COPY);
Garfield, Tan804133e2016-04-20 15:13:56 -0700957
Ben Lind0202122016-11-10 18:00:12 -0800958 if (!DragAndDropHelper.canCopyTo(event.getLocalState(), getDestination(v))) {
Vladislav Kaznacheevb6da7222015-05-01 14:18:57 -0700959 return false;
960 }
Ben Kwa13e26052016-02-18 16:45:45 -0800961
Garfield, Tan804133e2016-04-20 15:13:56 -0700962 // Recognize multi-window drag and drop based on the fact that localState is not
963 // carried between processes. It will stop working when the localsState behavior
964 // is changed. The info about window should be passed in the localState then.
965 // The localState could also be null for copying from Recents in single window
966 // mode, but Recents doesn't offer this functionality (no directories).
967 Metrics.logUserAction(getContext(),
Ben Lin5a305b42016-09-08 11:33:07 -0700968 event.getLocalState() == null ? Metrics.USER_ACTION_DRAG_N_DROP_MULTI_WINDOW
Garfield, Tan804133e2016-04-20 15:13:56 -0700969 : Metrics.USER_ACTION_DRAG_N_DROP);
Ben Linff4d5842016-04-18 14:35:28 -0700970
Ben Lin5a305b42016-09-08 11:33:07 -0700971 DocumentInfo dst = getDestination(v);
Ben Lindb7a0e72017-03-02 11:18:22 -0800972 // If destination is already at top of stack, no need to pass it in
Garfield Tan85a479e2017-03-16 11:22:00 -0700973 if (dst.equals(mState.stack.peek())) {
Ben Lindb7a0e72017-03-02 11:18:22 -0800974 mClipper.copyFromClipData(
Ben Lindb7a0e72017-03-02 11:18:22 -0800975 mState.stack,
976 clipData,
977 mInjector.dialogs::showFileOperationStatus);
978 } else {
979 mClipper.copyFromClipData(
980 dst,
981 mState.stack,
982 clipData,
983 mInjector.dialogs::showFileOperationStatus);
984 }
Garfield, Tan804133e2016-04-20 15:13:56 -0700985 return true;
986 }
987
Ben Lind0202122016-11-10 18:00:12 -0800988 DocumentInfo getDestination(View v) {
Garfield, Tan804133e2016-04-20 15:13:56 -0700989 String id = getModelId(v);
990 if (id != null) {
991 Cursor dstCursor = mModel.getItem(id);
992 if (dstCursor == null) {
993 Log.w(TAG, "Invalid destination. Can't obtain cursor for modelId: " + id);
994 return null;
Ben Linff4d5842016-04-18 14:35:28 -0700995 }
Garfield, Tan804133e2016-04-20 15:13:56 -0700996 return DocumentInfo.fromDirectoryCursor(dstCursor);
Ben Linff4d5842016-04-18 14:35:28 -0700997 }
998
Ben Lin7b38f342016-12-14 11:52:35 -0800999 if (v == mRecView) {
Steve McKayd0718952016-10-10 13:43:36 -07001000 return mState.stack.peek();
Ben Kwaa8c0da42016-02-25 14:10:40 -08001001 }
1002
Garfield, Tan804133e2016-04-20 15:13:56 -07001003 return null;
1004 }
1005
1006 @Override
Ben Lin166c5c62016-11-01 12:14:38 -07001007 public void setDropTargetHighlight(View v, Object localState, boolean highlight) {
Garfield, Tan804133e2016-04-20 15:13:56 -07001008 // Note: use exact comparison - this code is searching for views which are children of
1009 // the RecyclerView instance in the UI.
1010 if (v.getParent() == mRecView) {
Ben Lin166c5c62016-11-01 12:14:38 -07001011 DocumentHolder holder = getDocumentHolder(v);
1012 if (holder != null) {
1013 if (!highlight) {
1014 holder.resetDropHighlight();
1015 } else {
Ben Lind0202122016-11-10 18:00:12 -08001016 holder.setDroppableHighlight(
1017 DragAndDropHelper.canCopyTo(localState, getDestination(v)));
Ben Lin166c5c62016-11-01 12:14:38 -07001018 }
Ben Kwa13e26052016-02-18 16:45:45 -08001019 }
1020 }
Garfield, Tan804133e2016-04-20 15:13:56 -07001021 }
Vladislav Kaznacheevb6da7222015-05-01 14:18:57 -07001022
Ben Kwa0497da82015-11-30 23:00:02 -08001023 /**
1024 * Gets the model ID for a given RecyclerView item.
1025 * @param view A View that is a document item view, or a child of a document item view.
1026 * @return The Model ID for the given document, or null if the given view is not associated with
1027 * a document item view.
1028 */
Ben Linebf2a172016-06-03 13:46:52 -07001029 protected @Nullable String getModelId(View view) {
Ben Kwa13e26052016-02-18 16:45:45 -08001030 View itemView = mRecView.findContainingItemView(view);
1031 if (itemView != null) {
1032 RecyclerView.ViewHolder vh = mRecView.getChildViewHolder(itemView);
1033 if (vh instanceof DocumentHolder) {
Steve McKaybde20e12016-09-08 19:12:54 -07001034 return ((DocumentHolder) vh).getModelId();
Vladislav Kaznacheev9400b892015-09-04 09:17:37 -07001035 }
Vladislav Kaznacheev9400b892015-09-04 09:17:37 -07001036 }
Ben Kwa13e26052016-02-18 16:45:45 -08001037 return null;
Vladislav Kaznacheev9400b892015-09-04 09:17:37 -07001038 }
1039
Ben Lin166c5c62016-11-01 12:14:38 -07001040 private @Nullable DocumentHolder getDocumentHolder(View v) {
1041 RecyclerView.ViewHolder vh = mRecView.getChildViewHolder(v);
1042 if (vh instanceof DocumentHolder) {
1043 return (DocumentHolder) vh;
1044 }
1045 return null;
1046 }
1047
Steve McKay4f78ba62016-10-04 16:48:49 -07001048 // TODO: Move to activities when Model becomes activity level object.
Steve McKay74956af2016-06-30 21:03:06 -07001049 private boolean canSelect(DocumentDetails doc) {
Garfield Tan84bd0f12016-09-12 14:18:32 -07001050 return canSetSelectionState(doc.getModelId(), true);
Steve McKay74956af2016-06-30 21:03:06 -07001051 }
1052
Steve McKay4f78ba62016-10-04 16:48:49 -07001053 // TODO: Move to activities when Model becomes activity level object.
Garfield Tan84bd0f12016-09-12 14:18:32 -07001054 private boolean canSetSelectionState(String modelId, boolean nextState) {
1055 if (nextState) {
1056 // Check if an item can be selected
1057 final Cursor cursor = mModel.getItem(modelId);
1058 if (cursor == null) {
1059 Log.w(TAG, "Couldn't obtain cursor for modelId: " + modelId);
1060 return false;
1061 }
Steve McKayf7944892016-01-21 15:10:39 -08001062
Garfield Tan84bd0f12016-09-12 14:18:32 -07001063 final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
1064 final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
Steve McKay92ae43d2016-11-08 12:06:58 -08001065 return mInjector.config.canSelectType(docMimeType, docFlags, mState);
Garfield Tan84bd0f12016-09-12 14:18:32 -07001066 } else {
Tomasz Mikolajewski26640ab2017-02-06 10:37:59 +09001067 final DocumentInfo parent = mState.stack.peek();
Garfield Tan84bd0f12016-09-12 14:18:32 -07001068 // Right now all selected items can be deselected.
1069 return true;
Steve McKayf7944892016-01-21 15:10:39 -08001070 }
Steve McKayf7944892016-01-21 15:10:39 -08001071 }
1072
Steve McKay096c78b2016-01-21 18:46:15 -08001073 public static void showDirectory(
1074 FragmentManager fm, RootInfo root, DocumentInfo doc, int anim) {
Steve McKay7c662092016-08-26 12:17:41 -07001075 if (DEBUG) Log.d(TAG, "Showing directory: " + DocumentInfo.debugString(doc));
Garfield Tane9670332017-03-06 18:33:23 -08001076 create(fm, root, doc, anim);
Steve McKay096c78b2016-01-21 18:46:15 -08001077 }
1078
1079 public static void showRecentsOpen(FragmentManager fm, int anim) {
Garfield Tane9670332017-03-06 18:33:23 -08001080 create(fm, null, null, anim);
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001081 }
1082
Steve McKay7c662092016-08-26 12:17:41 -07001083 public static void create(
1084 FragmentManager fm,
Steve McKay7c662092016-08-26 12:17:41 -07001085 RootInfo root,
1086 @Nullable DocumentInfo doc,
Steve McKay988d8a32016-09-27 09:41:17 -07001087 @AnimationType int anim) {
Steve McKay7c662092016-08-26 12:17:41 -07001088
1089 if (DEBUG) {
1090 if (doc == null) {
1091 Log.d(TAG, "Creating new fragment null directory");
1092 } else {
1093 Log.d(TAG, "Creating new fragment for directory: " + DocumentInfo.debugString(doc));
1094 }
1095 }
1096
Steve McKay096c78b2016-01-21 18:46:15 -08001097 final Bundle args = new Bundle();
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001098 args.putParcelable(Shared.EXTRA_ROOT, root);
1099 args.putParcelable(Shared.EXTRA_DOC, doc);
Aga Wronska7e5b9632016-02-26 11:36:07 -08001100 args.putParcelable(Shared.EXTRA_SELECTION, new Selection());
Steve McKay096c78b2016-01-21 18:46:15 -08001101
1102 final FragmentTransaction ft = fm.beginTransaction();
Steve McKayfb4fd2f2016-03-11 10:49:32 -08001103 AnimationView.setupAnimations(ft, anim, args);
Steve McKay096c78b2016-01-21 18:46:15 -08001104
1105 final DirectoryFragment fragment = new DirectoryFragment();
1106 fragment.setArguments(args);
1107
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001108 ft.replace(getFragmentId(), fragment);
Steve McKay096c78b2016-01-21 18:46:15 -08001109 ft.commitAllowingStateLoss();
1110 }
1111
Steve McKay096c78b2016-01-21 18:46:15 -08001112 public static @Nullable DirectoryFragment get(FragmentManager fm) {
1113 // TODO: deal with multiple directories shown at once
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001114 Fragment fragment = fm.findFragmentById(getFragmentId());
Steve McKay096c78b2016-01-21 18:46:15 -08001115 return fragment instanceof DirectoryFragment
1116 ? (DirectoryFragment) fragment
1117 : null;
1118 }
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001119
1120 private static int getFragmentId() {
1121 return R.id.container_directory;
1122 }
1123
1124 @Override
Garfield, Tanca7c0882016-07-18 16:45:27 -07001125 public void onRefresh() {
Garfield, Taneba5bb92016-07-22 10:30:49 -07001126 // Remove thumbnail cache. We do this not because we're worried about stale thumbnails as it
1127 // should be covered by last modified value we store in thumbnail cache, but rather to give
1128 // the user a greater sense that contents are being reloaded.
1129 ThumbnailCache cache = DocumentsApplication.getThumbnailCache(getContext());
1130 String[] ids = mModel.getModelIds();
1131 int numOfEvicts = Math.min(ids.length, CACHE_EVICT_LIMIT);
1132 for (int i = 0; i < numOfEvicts; ++i) {
1133 cache.removeUri(mModel.getItemUri(ids[i]));
1134 }
1135
Ben Line9abd2d2016-12-06 11:39:52 -08001136 final DocumentInfo doc = mState.stack.peek();
1137 mActions.refreshDocument(doc, (boolean refreshSupported) -> {
1138 if (refreshSupported) {
1139 mRefreshLayout.setRefreshing(false);
1140 } else {
1141 // If Refresh API isn't available, we will explicitly reload the loader
Garfield Tane9670332017-03-06 18:33:23 -08001142 mActions.loadDocumentsForCurrentStack();
Ben Line9abd2d2016-12-06 11:39:52 -08001143 }
1144 });
Garfield, Tanca7c0882016-07-18 16:45:27 -07001145 }
1146
Steve McKayd0718952016-10-10 13:43:36 -07001147 private final class ModelUpdateListener implements EventListener<Model.Update> {
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001148
Steve McKayd0718952016-10-10 13:43:36 -07001149 @Override
1150 public void accept(Model.Update update) {
Steve McKayd0718952016-10-10 13:43:36 -07001151 if (DEBUG) Log.d(TAG, "Received model update. Loading=" + mModel.isLoading());
1152
Steve McKayd0718952016-10-10 13:43:36 -07001153 mProgressBar.setVisibility(mModel.isLoading() ? View.VISIBLE : View.GONE);
1154
Steve McKayd0718952016-10-10 13:43:36 -07001155 updateLayout(mState.derivedMode);
1156
Garfield Tane9670332017-03-06 18:33:23 -08001157 mAdapter.notifyDataSetChanged();
1158
Steve McKayd0718952016-10-10 13:43:36 -07001159 if (mRestoredSelection != null) {
1160 mSelectionMgr.restoreSelection(mRestoredSelection);
1161 // Note, we'll take care of cleaning up retained selection
1162 // in the selection handler where we already have some
1163 // specialized code to handle when selection was restored.
1164 }
1165
1166 // Restore any previous instance state
1167 final SparseArray<Parcelable> container =
1168 mState.dirConfigs.remove(mLocalState.getConfigKey());
1169 final int curSortedDimensionId = mState.sortModel.getSortedDimensionId();
1170
1171 final SortDimension curSortedDimension =
1172 mState.sortModel.getDimensionById(curSortedDimensionId);
1173 if (container != null
1174 && !getArguments().getBoolean(Shared.EXTRA_IGNORE_STATE, false)) {
1175 getView().restoreHierarchyState(container);
1176 } else if (mLocalState.mLastSortDimensionId != curSortedDimension.getId()
1177 || mLocalState.mLastSortDimensionId == SortModel.SORT_DIMENSION_ID_UNKNOWN
1178 || mLocalState.mLastSortDirection != curSortedDimension.getSortDirection()) {
1179 // Scroll to the top if the sort order actually changed.
1180 mRecView.smoothScrollToPosition(0);
1181 }
1182
1183 mLocalState.mLastSortDimensionId = curSortedDimension.getId();
1184 mLocalState.mLastSortDirection = curSortedDimension.getSortDirection();
1185
Ben Lin6537acd2016-11-16 12:06:21 -08001186 if (mRefreshLayout.isRefreshing()) {
Steve McKayd0718952016-10-10 13:43:36 -07001187 new Handler().postDelayed(
1188 () -> mRefreshLayout.setRefreshing(false),
Ben Linbbb7d032016-11-15 13:35:41 -08001189 REFRESH_SPINNER_TIMEOUT);
Steve McKayd0718952016-10-10 13:43:36 -07001190 }
Garfield Tane9670332017-03-06 18:33:23 -08001191
1192 if (!mModel.isLoading()) {
1193 mActivity.notifyDirectoryLoaded(
1194 mModel.doc != null ? mModel.doc.derivedUri : null);
1195 }
1196 }
1197 }
1198
1199 private final class AdapterEnvironment implements DocumentsAdapter.Environment {
1200
1201 @Override
1202 public Features getFeatures() {
1203 return mInjector.features;
Steve McKayd0718952016-10-10 13:43:36 -07001204 }
1205
1206 @Override
Garfield Tane9670332017-03-06 18:33:23 -08001207 public Context getContext() {
1208 return mActivity;
1209 }
Steve McKayd0718952016-10-10 13:43:36 -07001210
Garfield Tane9670332017-03-06 18:33:23 -08001211 @Override
1212 public State getDisplayState() {
1213 return mState;
1214 }
1215
1216 @Override
1217 public boolean isInSearchMode() {
1218 return mInjector.searchManager.isSearching();
1219 }
1220
1221 @Override
1222 public Model getModel() {
1223 return mModel;
1224 }
1225
1226 @Override
1227 public int getColumnCount() {
1228 return mColumnCount;
1229 }
1230
1231 @Override
1232 public boolean isSelected(String id) {
1233 return mSelectionMgr.getSelection().contains(id);
1234 }
1235
1236 @Override
1237 public boolean isDocumentEnabled(String mimeType, int flags) {
1238 return mInjector.config.isDocumentEnabled(mimeType, flags, mState);
1239 }
1240
1241 @Override
1242 public void initDocumentHolder(DocumentHolder holder) {
1243 holder.addKeyEventListener(mInputHandler);
1244 holder.itemView.setOnFocusChangeListener(mFocusManager);
1245 }
1246
1247 @Override
1248 public void onBindDocumentHolder(DocumentHolder holder, Cursor cursor) {
1249 setupDragAndDropOnDocumentView(holder.itemView, cursor);
Steve McKayd0718952016-10-10 13:43:36 -07001250 }
Aga Wronskaaf5ace52016-02-17 13:50:42 -08001251 }
Steve McKayc8f165c2016-09-20 13:54:05 -07001252}