Jeff Sharkey | e22d02e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 1 | /* |
| 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 McKay | f68210e | 2015-11-03 15:23:16 -0800 | [diff] [blame] | 17 | package com.android.documentsui.dirlist; |
Jeff Sharkey | e22d02e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 18 | |
Steve McKay | d080506 | 2016-09-15 14:30:38 -0700 | [diff] [blame] | 19 | import static com.android.documentsui.base.DocumentInfo.getCursorInt; |
| 20 | import static com.android.documentsui.base.DocumentInfo.getCursorString; |
Steve McKay | d9caa6a | 2016-09-15 16:36:45 -0700 | [diff] [blame] | 21 | import static com.android.documentsui.base.Shared.DEBUG; |
Steve McKay | 30535bc | 2016-11-04 14:16:58 -0700 | [diff] [blame] | 22 | import static com.android.documentsui.base.Shared.VERBOSE; |
Steve McKay | d9caa6a | 2016-09-15 16:36:45 -0700 | [diff] [blame] | 23 | import static com.android.documentsui.base.State.MODE_GRID; |
| 24 | import static com.android.documentsui.base.State.MODE_LIST; |
Steve McKay | 0599a44 | 2015-05-05 14:50:00 -0700 | [diff] [blame] | 25 | |
Steve McKay | b03a59c | 2016-10-19 15:59:53 -0700 | [diff] [blame] | 26 | import android.annotation.DimenRes; |
| 27 | import android.annotation.FractionRes; |
Steve McKay | cb9eb42 | 2016-02-09 16:17:24 -0800 | [diff] [blame] | 28 | import android.annotation.IntDef; |
Ben Kwa | f585893 | 2015-04-07 15:43:39 -0700 | [diff] [blame] | 29 | import android.app.Activity; |
Jeff Sharkey | f63b777 | 2013-10-01 17:57:41 -0700 | [diff] [blame] | 30 | import android.app.ActivityManager; |
Jeff Sharkey | 09c10bf | 2013-06-30 20:02:59 -0700 | [diff] [blame] | 31 | import android.app.Fragment; |
Jeff Sharkey | e22d02e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 32 | import android.app.FragmentManager; |
| 33 | import android.app.FragmentTransaction; |
Vladislav Kaznacheev | 89b9033 | 2015-05-01 13:46:57 -0700 | [diff] [blame] | 34 | import android.content.ClipData; |
Jeff Sharkey | e22d02e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 35 | import android.content.Context; |
Jeff Sharkey | 873daa3 | 2013-08-18 17:38:20 -0700 | [diff] [blame] | 36 | import android.content.Intent; |
Ben Lin | 743fe59 | 2017-03-01 14:36:15 -0800 | [diff] [blame] | 37 | import android.content.res.Resources; |
Jeff Sharkey | ac9e627 | 2013-08-31 21:27:44 -0700 | [diff] [blame] | 38 | import android.database.Cursor; |
Ben Lin | 743fe59 | 2017-03-01 14:36:15 -0800 | [diff] [blame] | 39 | import android.graphics.drawable.StateListDrawable; |
Jeff Sharkey | e22d02e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 40 | import android.net.Uri; |
Steve McKay | b03a59c | 2016-10-19 15:59:53 -0700 | [diff] [blame] | 41 | import android.os.Build; |
Jeff Sharkey | e22d02e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 42 | import android.os.Bundle; |
Garfield, Tan | eba5bb9 | 2016-07-22 10:30:49 -0700 | [diff] [blame] | 43 | import android.os.Handler; |
Jeff Sharkey | c8ae7a5 | 2013-09-18 16:26:49 -0700 | [diff] [blame] | 44 | import android.os.Parcelable; |
Jeff Sharkey | e22d02e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 45 | import android.provider.DocumentsContract; |
Jeff Sharkey | ac9e627 | 2013-08-31 21:27:44 -0700 | [diff] [blame] | 46 | import android.provider.DocumentsContract.Document; |
Garfield, Tan | ca7c088 | 2016-07-18 16:45:27 -0700 | [diff] [blame] | 47 | import android.support.v4.widget.SwipeRefreshLayout; |
Steve McKay | ef28015 | 2015-06-11 10:10:49 -0700 | [diff] [blame] | 48 | import android.support.v7.widget.GridLayoutManager; |
Steve McKay | c5ecf89 | 2015-12-22 18:15:31 -0800 | [diff] [blame] | 49 | import android.support.v7.widget.GridLayoutManager.SpanSizeLookup; |
Steve McKay | ef28015 | 2015-06-11 10:10:49 -0700 | [diff] [blame] | 50 | import android.support.v7.widget.RecyclerView; |
Steve McKay | ef28015 | 2015-06-11 10:10:49 -0700 | [diff] [blame] | 51 | import android.support.v7.widget.RecyclerView.RecyclerListener; |
| 52 | import android.support.v7.widget.RecyclerView.ViewHolder; |
Jeff Sharkey | 8a8fb67 | 2013-05-07 12:41:33 -0700 | [diff] [blame] | 53 | import android.util.Log; |
Jeff Sharkey | c8ae7a5 | 2013-09-18 16:26:49 -0700 | [diff] [blame] | 54 | import android.util.SparseArray; |
Ben Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 55 | import android.view.ContextMenu; |
Vladislav Kaznacheev | b6da722 | 2015-05-01 14:18:57 -0700 | [diff] [blame] | 56 | import android.view.DragEvent; |
Jeff Sharkey | e22d02e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 57 | import android.view.LayoutInflater; |
Ben Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 58 | import android.view.MenuInflater; |
Jeff Sharkey | 09c10bf | 2013-06-30 20:02:59 -0700 | [diff] [blame] | 59 | import android.view.MenuItem; |
Steve McKay | 74956af | 2016-06-30 21:03:06 -0700 | [diff] [blame] | 60 | import android.view.MotionEvent; |
Jeff Sharkey | e22d02e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 61 | import android.view.View; |
| 62 | import android.view.ViewGroup; |
Jeff Sharkey | e22d02e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 63 | import android.widget.ImageView; |
Jeff Sharkey | e22d02e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 64 | |
Steve McKay | 6d20d19 | 2016-09-21 17:57:10 -0700 | [diff] [blame] | 65 | import com.android.documentsui.ActionHandler; |
Steve McKay | bd9f05a | 2016-10-10 10:18:36 -0700 | [diff] [blame] | 66 | import com.android.documentsui.ActionModeController; |
Steve McKay | f68210e | 2015-11-03 15:23:16 -0800 | [diff] [blame] | 67 | import com.android.documentsui.BaseActivity; |
Steve McKay | d9caa6a | 2016-09-15 16:36:45 -0700 | [diff] [blame] | 68 | import com.android.documentsui.BaseActivity.RetainedState; |
Ben Lin | 9fea312 | 2016-10-10 18:32:26 -0700 | [diff] [blame] | 69 | import com.android.documentsui.DirectoryReloadLock; |
Steve McKay | f68210e | 2015-11-03 15:23:16 -0800 | [diff] [blame] | 70 | import com.android.documentsui.DocumentsApplication; |
Ben Lin | d020212 | 2016-11-10 18:00:12 -0800 | [diff] [blame] | 71 | import com.android.documentsui.DragAndDropHelper; |
Steve McKay | 17b761e | 2016-09-20 17:26:46 -0700 | [diff] [blame] | 72 | import com.android.documentsui.FocusManager; |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 73 | import com.android.documentsui.Injector; |
| 74 | import com.android.documentsui.Injector.ContentScoped; |
| 75 | import com.android.documentsui.Injector.Injected; |
Garfield, Tan | 804133e | 2016-04-20 15:13:56 -0700 | [diff] [blame] | 76 | import com.android.documentsui.ItemDragListener; |
Aga Wronska | b028905 | 2016-03-31 16:37:40 -0700 | [diff] [blame] | 77 | import com.android.documentsui.Metrics; |
Garfield Tan | e967033 | 2017-03-06 18:33:23 -0800 | [diff] [blame] | 78 | import com.android.documentsui.Model; |
Steve McKay | f68210e | 2015-11-03 15:23:16 -0800 | [diff] [blame] | 79 | import com.android.documentsui.R; |
Garfield, Tan | eba5bb9 | 2016-07-22 10:30:49 -0700 | [diff] [blame] | 80 | import com.android.documentsui.ThumbnailCache; |
Steve McKay | d9caa6a | 2016-09-15 16:36:45 -0700 | [diff] [blame] | 81 | import com.android.documentsui.base.DocumentInfo; |
Tomasz Mikolajewski | 26640ab | 2017-02-06 10:37:59 +0900 | [diff] [blame] | 82 | import com.android.documentsui.base.DocumentStack; |
Ben Lin | 2d6caf3 | 2016-09-19 17:48:54 -0700 | [diff] [blame] | 83 | import com.android.documentsui.base.EventHandler; |
Steve McKay | 990f76e | 2016-09-16 12:36:58 -0700 | [diff] [blame] | 84 | import com.android.documentsui.base.EventListener; |
Steve McKay | d9caa6a | 2016-09-15 16:36:45 -0700 | [diff] [blame] | 85 | import com.android.documentsui.base.Events.InputEvent; |
| 86 | import com.android.documentsui.base.Events.MotionInputEvent; |
Steve McKay | 98f8c5f | 2017-03-03 13:52:14 -0800 | [diff] [blame] | 87 | import com.android.documentsui.base.Features; |
Steve McKay | d9caa6a | 2016-09-15 16:36:45 -0700 | [diff] [blame] | 88 | import com.android.documentsui.base.RootInfo; |
| 89 | import com.android.documentsui.base.Shared; |
| 90 | import com.android.documentsui.base.State; |
| 91 | import com.android.documentsui.base.State.ViewMode; |
Steve McKay | c8889af | 2016-09-23 11:22:41 -0700 | [diff] [blame] | 92 | import com.android.documentsui.clipping.ClipStore; |
Ben Lin | c5e3e8e | 2016-07-13 18:16:36 -0700 | [diff] [blame] | 93 | import com.android.documentsui.clipping.DocumentClipper; |
Garfield, Tan | 9666ce6 | 2016-07-12 11:02:09 -0700 | [diff] [blame] | 94 | import com.android.documentsui.clipping.UrisSupplier; |
Steve McKay | 988d8a3 | 2016-09-27 09:41:17 -0700 | [diff] [blame] | 95 | import com.android.documentsui.dirlist.AnimationView.AnimationType; |
Steve McKay | 16e0c1f | 2016-09-15 12:41:13 -0700 | [diff] [blame] | 96 | import com.android.documentsui.picker.PickActivity; |
Steve McKay | 4f78ba6 | 2016-10-04 16:48:49 -0700 | [diff] [blame] | 97 | import com.android.documentsui.selection.BandController; |
| 98 | import com.android.documentsui.selection.GestureSelector; |
| 99 | import com.android.documentsui.selection.Selection; |
| 100 | import com.android.documentsui.selection.SelectionManager; |
| 101 | import com.android.documentsui.selection.SelectionMetadata; |
Garfield, Tan | 4833477 | 2016-06-28 17:17:38 -0700 | [diff] [blame] | 102 | import com.android.documentsui.services.FileOperation; |
Steve McKay | c83baa0 | 2016-01-06 18:32:13 -0800 | [diff] [blame] | 103 | import com.android.documentsui.services.FileOperationService; |
Ben Kwa | bc7df44 | 2016-02-02 23:00:02 -0800 | [diff] [blame] | 104 | import com.android.documentsui.services.FileOperationService.OpType; |
Steve McKay | c83baa0 | 2016-01-06 18:32:13 -0800 | [diff] [blame] | 105 | import com.android.documentsui.services.FileOperations; |
Garfield, Tan | 11d2348 | 2016-08-05 09:33:29 -0700 | [diff] [blame] | 106 | import com.android.documentsui.sorting.SortDimension; |
Garfield, Tan | 11d2348 | 2016-08-05 09:33:29 -0700 | [diff] [blame] | 107 | import com.android.documentsui.sorting.SortModel; |
Steve McKay | ce0b14c | 2016-05-03 11:25:52 -0700 | [diff] [blame] | 108 | |
Garfield, Tan | b7e5f6b | 2016-06-30 18:27:47 -0700 | [diff] [blame] | 109 | import java.io.IOException; |
Steve McKay | cb9eb42 | 2016-02-09 16:17:24 -0800 | [diff] [blame] | 110 | import java.lang.annotation.Retention; |
| 111 | import java.lang.annotation.RetentionPolicy; |
Jeff Sharkey | a5defe3 | 2013-08-05 17:56:48 -0700 | [diff] [blame] | 112 | import java.util.List; |
Steve McKay | 2bab2f8 | 2016-06-03 09:23:39 -0700 | [diff] [blame] | 113 | |
| 114 | import javax.annotation.Nullable; |
Jeff Sharkey | 09c10bf | 2013-06-30 20:02:59 -0700 | [diff] [blame] | 115 | |
| 116 | /** |
| 117 | * Display the documents inside a single directory. |
| 118 | */ |
Aga Wronska | af5ace5 | 2016-02-17 13:50:42 -0800 | [diff] [blame] | 119 | public class DirectoryFragment extends Fragment |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 120 | implements ItemDragListener.DragHost, SwipeRefreshLayout.OnRefreshListener { |
Jeff Sharkey | 09c10bf | 2013-06-30 20:02:59 -0700 | [diff] [blame] | 121 | |
Garfield Tan | e967033 | 2017-03-06 18:33:23 -0800 | [diff] [blame] | 122 | static final int TYPE_NORMAL = 1; |
| 123 | static final int TYPE_RECENT_OPEN = 2; |
Jeff Sharkey | 5b53592 | 2013-08-02 15:55:26 -0700 | [diff] [blame] | 124 | |
Aga Wronska | af5ace5 | 2016-02-17 13:50:42 -0800 | [diff] [blame] | 125 | @IntDef(flag = true, value = { |
Steve McKay | f171934 | 2016-02-17 15:02:01 -0800 | [diff] [blame] | 126 | REQUEST_COPY_DESTINATION |
| 127 | }) |
| 128 | @Retention(RetentionPolicy.SOURCE) |
| 129 | public @interface RequestCode {} |
Ben Kwa | f585893 | 2015-04-07 15:43:39 -0700 | [diff] [blame] | 130 | public static final int REQUEST_COPY_DESTINATION = 1; |
| 131 | |
Steve McKay | 97b4be4 | 2016-01-20 15:09:35 -0800 | [diff] [blame] | 132 | private static final String TAG = "DirectoryFragment"; |
| 133 | private static final int LOADER_ID = 42; |
Steve McKay | 97b4be4 | 2016-01-20 15:09:35 -0800 | [diff] [blame] | 134 | |
Garfield, Tan | eba5bb9 | 2016-07-22 10:30:49 -0700 | [diff] [blame] | 135 | private static final int CACHE_EVICT_LIMIT = 100; |
Ben Lin | bbb7d03 | 2016-11-15 13:35:41 -0800 | [diff] [blame] | 136 | private static final int REFRESH_SPINNER_TIMEOUT = 500; |
Garfield, Tan | eba5bb9 | 2016-07-22 10:30:49 -0700 | [diff] [blame] | 137 | |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 138 | private BaseActivity mActivity; |
| 139 | |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 140 | private State mState; |
Jon Mann | 30d8c79 | 2017-02-21 17:44:49 -0800 | [diff] [blame] | 141 | private Model mModel; |
Steve McKay | 990f76e | 2016-09-16 12:36:58 -0700 | [diff] [blame] | 142 | private final EventListener<Model.Update> mModelUpdateListener = new ModelUpdateListener(); |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 143 | private final DocumentsAdapter.Environment mAdapterEnv = new AdapterEnvironment(); |
Steve McKay | c8f165c | 2016-09-20 13:54:05 -0700 | [diff] [blame] | 144 | |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 145 | @Injected |
| 146 | @ContentScoped |
| 147 | private Injector<?> mInjector; |
Steve McKay | c8f165c | 2016-09-20 13:54:05 -0700 | [diff] [blame] | 148 | |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 149 | @Injected |
| 150 | @ContentScoped |
Steve McKay | 4f78ba6 | 2016-10-04 16:48:49 -0700 | [diff] [blame] | 151 | private SelectionManager mSelectionMgr; |
| 152 | |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 153 | @Injected |
| 154 | @ContentScoped |
Steve McKay | 6d20d19 | 2016-09-21 17:57:10 -0700 | [diff] [blame] | 155 | private FocusManager mFocusManager; |
| 156 | |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 157 | @Injected |
| 158 | @ContentScoped |
Steve McKay | c8889af | 2016-09-23 11:22:41 -0700 | [diff] [blame] | 159 | private ActionHandler mActions; |
Steve McKay | 6d20d19 | 2016-09-21 17:57:10 -0700 | [diff] [blame] | 160 | |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 161 | @Injected |
| 162 | @ContentScoped |
Garfield Tan | 84bd0f1 | 2016-09-12 14:18:32 -0700 | [diff] [blame] | 163 | private ActionModeController mActionModeController; |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 164 | |
Garfield Tan | 84bd0f1 | 2016-09-12 14:18:32 -0700 | [diff] [blame] | 165 | private SelectionMetadata mSelectionMetadata; |
Steve McKay | 2d1e4a3 | 2016-07-13 13:03:16 -0700 | [diff] [blame] | 166 | private UserInputHandler<InputEvent> mInputHandler; |
Steve McKay | c8f165c | 2016-09-20 13:54:05 -0700 | [diff] [blame] | 167 | private @Nullable BandController mBandController; |
Garfield Tan | 208945c | 2016-10-04 14:36:38 -0700 | [diff] [blame] | 168 | private @Nullable DragHoverListener mDragHoverListener; |
Ben Kwa | 8e3fd76 | 2015-12-17 10:37:00 -0800 | [diff] [blame] | 169 | private IconHelper mIconHelper; |
Garfield, Tan | ca7c088 | 2016-07-18 16:45:27 -0700 | [diff] [blame] | 170 | private SwipeRefreshLayout mRefreshLayout; |
Steve McKay | ef28015 | 2015-06-11 10:10:49 -0700 | [diff] [blame] | 171 | private RecyclerView mRecView; |
Garfield, Tan | 171e6f5 | 2016-07-29 14:44:58 -0700 | [diff] [blame] | 172 | private View mFileList; |
Steve McKay | ef28015 | 2015-06-11 10:10:49 -0700 | [diff] [blame] | 173 | |
Steve McKay | ef28015 | 2015-06-11 10:10:49 -0700 | [diff] [blame] | 174 | private DocumentsAdapter mAdapter; |
Steve McKay | 1f19948 | 2015-05-20 15:58:42 -0700 | [diff] [blame] | 175 | private DocumentClipper mClipper; |
Ben Kwa | bc7df44 | 2016-02-02 23:00:02 -0800 | [diff] [blame] | 176 | private GridLayoutManager mLayout; |
Steve McKay | d57f5fa | 2015-07-23 16:33:41 -0700 | [diff] [blame] | 177 | private int mColumnCount = 1; // This will get updated when layout changes. |
Steve McKay | 1f19948 | 2015-05-20 15:58:42 -0700 | [diff] [blame] | 178 | |
Steve McKay | b03a59c | 2016-10-19 15:59:53 -0700 | [diff] [blame] | 179 | private float mLiveScale = 1.0f; |
Steve McKay | 358c3ec | 2016-10-21 09:16:57 -0700 | [diff] [blame] | 180 | private @ViewMode int mMode; |
Steve McKay | b03a59c | 2016-10-19 15:59:53 -0700 | [diff] [blame] | 181 | |
Ben Kwa | 379e176 | 2015-09-21 10:49:52 -0700 | [diff] [blame] | 182 | private View mProgressBar; |
Ben Kwa | c42fa40 | 2015-09-16 08:04:37 -0700 | [diff] [blame] | 183 | |
Steve McKay | bd9f05a | 2016-10-10 10:18:36 -0700 | [diff] [blame] | 184 | private DirectoryState mLocalState; |
Ben Lin | 9fea312 | 2016-10-10 18:32:26 -0700 | [diff] [blame] | 185 | private DirectoryReloadLock mReloadLock = new DirectoryReloadLock(); |
Steve McKay | c8f165c | 2016-09-20 13:54:05 -0700 | [diff] [blame] | 186 | |
Steve McKay | 2bab2f8 | 2016-06-03 09:23:39 -0700 | [diff] [blame] | 187 | // 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, Tan | 804133e | 2016-04-20 15:13:56 -0700 | [diff] [blame] | 190 | |
Garfield, Tan | 11d2348 | 2016-08-05 09:33:29 -0700 | [diff] [blame] | 191 | private SortModel.UpdateListener mSortListener = (model, updateType) -> { |
| 192 | // Only when sort order has changed do we need to trigger another loading. |
Garfield, Tan | 61f564b | 2016-08-16 13:36:15 -0700 | [diff] [blame] | 193 | if ((updateType & SortModel.UPDATE_TYPE_SORTING) != 0) { |
Garfield Tan | e967033 | 2017-03-06 18:33:23 -0800 | [diff] [blame] | 194 | mActions.loadDocumentsForCurrentStack(); |
Garfield, Tan | 11d2348 | 2016-08-05 09:33:29 -0700 | [diff] [blame] | 195 | } |
| 196 | }; |
Garfield, Tan | 171e6f5 | 2016-07-29 14:44:58 -0700 | [diff] [blame] | 197 | |
Jon Mann | 30d8c79 | 2017-02-21 17:44:49 -0800 | [diff] [blame] | 198 | private final Runnable mOnDisplayStateChanged = this::onDisplayStateChanged; |
| 199 | |
Jeff Sharkey | e22d02e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 200 | @Override |
| 201 | public View onCreateView( |
| 202 | LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { |
Garfield Tan | 208945c | 2016-10-04 14:36:38 -0700 | [diff] [blame] | 203 | |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 204 | BaseActivity activity = (BaseActivity) getActivity(); |
Jeff Sharkey | 09c10bf | 2013-06-30 20:02:59 -0700 | [diff] [blame] | 205 | final View view = inflater.inflate(R.layout.fragment_directory, container, false); |
| 206 | |
Tomasz Mikolajewski | f5ed16b | 2016-11-24 12:49:33 +0900 | [diff] [blame] | 207 | mProgressBar = view.findViewById(R.id.progressbar); |
Steve McKay | 9de0da6 | 2016-08-25 15:18:23 -0700 | [diff] [blame] | 208 | assert(mProgressBar != null); |
Garfield, Tan | ca7c088 | 2016-07-18 16:45:27 -0700 | [diff] [blame] | 209 | |
Ben Kwa | 74e5d41 | 2016-02-10 07:46:35 -0800 | [diff] [blame] | 210 | mRecView = (RecyclerView) view.findViewById(R.id.dir_list); |
Steve McKay | ef28015 | 2015-06-11 10:10:49 -0700 | [diff] [blame] | 211 | mRecView.setRecyclerListener( |
| 212 | new RecyclerListener() { |
| 213 | @Override |
| 214 | public void onViewRecycled(ViewHolder holder) { |
| 215 | cancelThumbnailTask(holder.itemView); |
| 216 | } |
| 217 | }); |
Ben Lin | 743fe59 | 2017-03-01 14:36:15 -0800 | [diff] [blame] | 218 | |
Ben Lin | d79edd1 | 2017-03-14 17:39:36 -0700 | [diff] [blame] | 219 | mRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.refresh_layout); |
| 220 | mRefreshLayout.setOnRefreshListener(this); |
| 221 | |
Ben Lin | 743fe59 | 2017-03-01 14:36:15 -0800 | [diff] [blame] | 222 | 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 McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 232 | mRecView.setItemAnimator(new DirectoryItemAnimator(activity)); |
Garfield, Tan | 171e6f5 | 2016-07-29 14:44:58 -0700 | [diff] [blame] | 233 | mFileList = view.findViewById(R.id.file_list); |
Ben Kwa | 98bb6cf | 2015-10-14 08:00:27 -0700 | [diff] [blame] | 234 | |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 235 | mInjector = activity.getInjector(); |
Jon Mann | 30d8c79 | 2017-02-21 17:44:49 -0800 | [diff] [blame] | 236 | mModel = mInjector.getModel(); |
| 237 | mModel.reset(); |
| 238 | |
| 239 | mInjector.actions.registerDisplayStateChangedListener(mOnDisplayStateChanged); |
| 240 | |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 241 | mDragHoverListener = mInjector.config.dragAndDropEnabled() |
Garfield Tan | 208945c | 2016-10-04 14:36:38 -0700 | [diff] [blame] | 242 | ? DragHoverListener.create(new DirectoryDragListener(this), mRecView) |
| 243 | : null; |
Garfield, Tan | 804133e | 2016-04-20 15:13:56 -0700 | [diff] [blame] | 244 | |
Garfield Tan | 208945c | 2016-10-04 14:36:38 -0700 | [diff] [blame] | 245 | // Make the recycler and the empty views responsive to drop events when allowed. |
Ben Lin | 35f99e0 | 2016-08-31 12:46:04 -0700 | [diff] [blame] | 246 | mRecView.setOnDragListener(mDragHoverListener); |
Jeff Sharkey | 09c10bf | 2013-06-30 20:02:59 -0700 | [diff] [blame] | 247 | |
Jeff Sharkey | ac9e627 | 2013-08-31 21:27:44 -0700 | [diff] [blame] | 248 | return view; |
| 249 | } |
Jeff Sharkey | 09c10bf | 2013-06-30 20:02:59 -0700 | [diff] [blame] | 250 | |
Jeff Sharkey | ac9e627 | 2013-08-31 21:27:44 -0700 | [diff] [blame] | 251 | @Override |
Jeff Sharkey | f63b777 | 2013-10-01 17:57:41 -0700 | [diff] [blame] | 252 | public void onDestroyView() { |
Steve McKay | 74956af | 2016-06-30 21:03:06 -0700 | [diff] [blame] | 253 | mSelectionMgr.clearSelection(); |
Jon Mann | 30d8c79 | 2017-02-21 17:44:49 -0800 | [diff] [blame] | 254 | mInjector.actions.unregisterDisplayStateChangedListener(mOnDisplayStateChanged); |
Jeff Sharkey | f63b777 | 2013-10-01 17:57:41 -0700 | [diff] [blame] | 255 | |
| 256 | // Cancel any outstanding thumbnail requests |
Steve McKay | ef28015 | 2015-06-11 10:10:49 -0700 | [diff] [blame] | 257 | final int count = mRecView.getChildCount(); |
Jeff Sharkey | f63b777 | 2013-10-01 17:57:41 -0700 | [diff] [blame] | 258 | for (int i = 0; i < count; i++) { |
Steve McKay | ef28015 | 2015-06-11 10:10:49 -0700 | [diff] [blame] | 259 | final View view = mRecView.getChildAt(i); |
| 260 | cancelThumbnailTask(view); |
Jeff Sharkey | f63b777 | 2013-10-01 17:57:41 -0700 | [diff] [blame] | 261 | } |
Aga Wronska | 7e5b963 | 2016-02-26 11:36:07 -0800 | [diff] [blame] | 262 | |
Garfield Tan | e967033 | 2017-03-06 18:33:23 -0800 | [diff] [blame] | 263 | mModel.removeUpdateListener(mModelUpdateListener); |
| 264 | mModel.removeUpdateListener(mAdapter.getModelUpdateListener()); |
| 265 | |
Aga Wronska | 7e5b963 | 2016-02-26 11:36:07 -0800 | [diff] [blame] | 266 | super.onDestroyView(); |
Jeff Sharkey | f63b777 | 2013-10-01 17:57:41 -0700 | [diff] [blame] | 267 | } |
| 268 | |
| 269 | @Override |
Jeff Sharkey | ac9e627 | 2013-08-31 21:27:44 -0700 | [diff] [blame] | 270 | public void onActivityCreated(Bundle savedInstanceState) { |
| 271 | super.onActivityCreated(savedInstanceState); |
| 272 | |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 273 | mActivity = (BaseActivity) getActivity(); |
| 274 | mState = mActivity.getDisplayState(); |
Jeff Sharkey | ac9e627 | 2013-08-31 21:27:44 -0700 | [diff] [blame] | 275 | |
Aga Wronska | af5ace5 | 2016-02-17 13:50:42 -0800 | [diff] [blame] | 276 | // Read arguments when object created for the first time. |
| 277 | // Restore state if fragment recreated. |
| 278 | Bundle args = savedInstanceState == null ? getArguments() : savedInstanceState; |
Steve McKay | c8f165c | 2016-09-20 13:54:05 -0700 | [diff] [blame] | 279 | |
Steve McKay | bd9f05a | 2016-10-10 10:18:36 -0700 | [diff] [blame] | 280 | mLocalState = new DirectoryState(); |
| 281 | mLocalState.restore(args); |
Jeff Sharkey | 9656a53 | 2013-09-13 13:42:19 -0700 | [diff] [blame] | 282 | |
Steve McKay | 2bab2f8 | 2016-06-03 09:23:39 -0700 | [diff] [blame] | 283 | // Restore any selection we may have squirreled away in retained state. |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 284 | @Nullable RetainedState retained = mActivity.getRetainedState(); |
Steve McKay | 2bab2f8 | 2016-06-03 09:23:39 -0700 | [diff] [blame] | 285 | 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 McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 292 | mIconHelper = new IconHelper(mActivity, MODE_GRID); |
Ben Lin | 35f99e0 | 2016-08-31 12:46:04 -0700 | [diff] [blame] | 293 | mClipper = DocumentsApplication.getDocumentClipper(getContext()); |
Steve McKay | c5ecf89 | 2015-12-22 18:15:31 -0800 | [diff] [blame] | 294 | |
Ben Lin | 7b38f34 | 2016-12-14 11:52:35 -0800 | [diff] [blame] | 295 | mAdapter = new DirectoryAddonsAdapter( |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 296 | mAdapterEnv, new ModelBackedDocumentsAdapter(mAdapterEnv, mIconHelper)); |
Steve McKay | c5ecf89 | 2015-12-22 18:15:31 -0800 | [diff] [blame] | 297 | |
Steve McKay | ef28015 | 2015-06-11 10:10:49 -0700 | [diff] [blame] | 298 | mRecView.setAdapter(mAdapter); |
| 299 | |
Ben Lin | 81afd7f | 2016-08-22 18:24:03 -0700 | [diff] [blame] | 300 | 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 Kwa | bc7df44 | 2016-02-02 23:00:02 -0800 | [diff] [blame] | 308 | SpanSizeLookup lookup = mAdapter.createSpanSizeLookup(); |
| 309 | if (lookup != null) { |
| 310 | mLayout.setSpanSizeLookup(lookup); |
| 311 | } |
| 312 | mRecView.setLayoutManager(mLayout); |
| 313 | |
Steve McKay | 990f76e | 2016-09-16 12:36:58 -0700 | [diff] [blame] | 314 | mModel.addUpdateListener(mAdapter.getModelUpdateListener()); |
Ben Lin | 26819d7 | 2016-08-17 12:45:05 -0700 | [diff] [blame] | 315 | mModel.addUpdateListener(mModelUpdateListener); |
| 316 | |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 317 | mSelectionMgr = mInjector.getSelectionManager(mAdapter, this::canSetSelectionState); |
| 318 | mFocusManager = mInjector.getFocusManager(mRecView, mModel); |
Garfield Tan | e967033 | 2017-03-06 18:33:23 -0800 | [diff] [blame] | 319 | mActions = mInjector.getActionHandler(mReloadLock); |
Steve McKay | f0fceb4 | 2016-09-07 17:35:55 -0700 | [diff] [blame] | 320 | |
Ben Lin | e4ffd06 | 2017-01-19 15:10:49 -0800 | [diff] [blame] | 321 | mRecView.setAccessibilityDelegateCompat( |
Ben Lin | a355d19 | 2017-03-03 14:11:57 -0800 | [diff] [blame] | 322 | new AccessibilityEventRouter(mRecView, |
Ben Lin | e4ffd06 | 2017-01-19 15:10:49 -0800 | [diff] [blame] | 323 | (View child) -> onAccessibilityClick(child))); |
Steve McKay | 4f78ba6 | 2016-10-04 16:48:49 -0700 | [diff] [blame] | 324 | mSelectionMetadata = new SelectionMetadata(mModel::getItem); |
| 325 | mSelectionMgr.addItemCallback(mSelectionMetadata); |
| 326 | |
Ben Lin | 9fea312 | 2016-10-10 18:32:26 -0700 | [diff] [blame] | 327 | GestureSelector gestureSel = GestureSelector.create(mSelectionMgr, mRecView, mReloadLock); |
Steve McKay | 4f78ba6 | 2016-10-04 16:48:49 -0700 | [diff] [blame] | 328 | |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 329 | if (mState.allowMultiple) { |
Steve McKay | 4f78ba6 | 2016-10-04 16:48:49 -0700 | [diff] [blame] | 330 | mBandController = new BandController( |
| 331 | mRecView, |
| 332 | mAdapter, |
| 333 | mSelectionMgr, |
Ben Lin | 9fea312 | 2016-10-10 18:32:26 -0700 | [diff] [blame] | 334 | mReloadLock, |
Steve McKay | 4f78ba6 | 2016-10-04 16:48:49 -0700 | [diff] [blame] | 335 | (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 Lin | 35f99e0 | 2016-08-31 12:46:04 -0700 | [diff] [blame] | 341 | } |
| 342 | |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 343 | DragStartListener mDragStartListener = mInjector.config.dragAndDropEnabled() |
Steve McKay | f0fceb4 | 2016-09-07 17:35:55 -0700 | [diff] [blame] | 344 | ? DragStartListener.create( |
| 345 | mIconHelper, |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 346 | mActivity, |
Steve McKay | f0fceb4 | 2016-09-07 17:35:55 -0700 | [diff] [blame] | 347 | mModel, |
| 348 | mSelectionMgr, |
| 349 | mClipper, |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 350 | mState, |
Steve McKay | f0fceb4 | 2016-09-07 17:35:55 -0700 | [diff] [blame] | 351 | this::getModelId, |
| 352 | mRecView::findChildViewUnder, |
Ben Lin | fc84b34 | 2017-02-17 11:44:45 -0800 | [diff] [blame] | 353 | getContext().getDrawable(R.drawable.ic_doc_generic), |
Ben Lin | 1c45629 | 2016-10-07 16:43:18 -0700 | [diff] [blame] | 354 | mActivity.getShadowBuilder()) |
Steve McKay | f0fceb4 | 2016-09-07 17:35:55 -0700 | [diff] [blame] | 355 | : DragStartListener.DUMMY; |
| 356 | |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 357 | EventHandler<InputEvent> gestureHandler = mState.allowMultiple |
Ben Lin | 2d6caf3 | 2016-09-19 17:48:54 -0700 | [diff] [blame] | 358 | ? gestureSel::start |
| 359 | : EventHandler.createStub(false); |
Steve McKay | b03a59c | 2016-10-19 15:59:53 -0700 | [diff] [blame] | 360 | |
Steve McKay | 2d1e4a3 | 2016-07-13 13:03:16 -0700 | [diff] [blame] | 361 | mInputHandler = new UserInputHandler<>( |
Steve McKay | c8889af | 2016-09-23 11:22:41 -0700 | [diff] [blame] | 362 | mActions, |
Steve McKay | 74956af | 2016-06-30 21:03:06 -0700 | [diff] [blame] | 363 | mFocusManager, |
Steve McKay | 6d20d19 | 2016-09-21 17:57:10 -0700 | [diff] [blame] | 364 | mSelectionMgr, |
Garfield Tan | e6d61e3 | 2016-09-08 10:26:59 -0700 | [diff] [blame] | 365 | (MotionEvent t) -> MotionInputEvent.obtain(t, mRecView), |
Steve McKay | 74956af | 2016-06-30 21:03:06 -0700 | [diff] [blame] | 366 | this::canSelect, |
Ben Lin | aa66c43 | 2016-10-12 12:11:24 -0700 | [diff] [blame] | 367 | this::onContextMenuClick, |
Steve McKay | f0fceb4 | 2016-09-07 17:35:55 -0700 | [diff] [blame] | 368 | mDragStartListener::onTouchDragEvent, |
Ben Lin | 2d6caf3 | 2016-09-19 17:48:54 -0700 | [diff] [blame] | 369 | gestureHandler); |
Steve McKay | 79a6fe0 | 2016-06-30 12:09:34 -0700 | [diff] [blame] | 370 | |
Garfield Tan | e6d61e3 | 2016-09-08 10:26:59 -0700 | [diff] [blame] | 371 | new ListeningGestureDetector( |
Steve McKay | aad7883 | 2017-03-09 10:11:12 -0800 | [diff] [blame] | 372 | mInjector.features, |
Ben Lin | 35f99e0 | 2016-08-31 12:46:04 -0700 | [diff] [blame] | 373 | this.getContext(), |
| 374 | mRecView, |
Steve McKay | f0fceb4 | 2016-09-07 17:35:55 -0700 | [diff] [blame] | 375 | mDragStartListener::onMouseDragEvent, |
Ben Lin | d79edd1 | 2017-03-14 17:39:36 -0700 | [diff] [blame] | 376 | mRefreshLayout::setEnabled, |
Ben Lin | 35f99e0 | 2016-08-31 12:46:04 -0700 | [diff] [blame] | 377 | gestureSel, |
| 378 | mInputHandler, |
Steve McKay | b03a59c | 2016-10-19 15:59:53 -0700 | [diff] [blame] | 379 | mBandController, |
| 380 | this::scaleLayout); |
Steve McKay | d586959 | 2016-05-26 12:04:43 -0700 | [diff] [blame] | 381 | |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 382 | mActionModeController = mInjector.getActionModeController( |
Garfield Tan | 84bd0f1 | 2016-09-12 14:18:32 -0700 | [diff] [blame] | 383 | mSelectionMetadata, |
Steve McKay | 5b0a2c1 | 2016-10-07 11:22:31 -0700 | [diff] [blame] | 384 | this::handleMenuItemClick, |
| 385 | mRecView); |
| 386 | |
Garfield Tan | 84bd0f1 | 2016-09-12 14:18:32 -0700 | [diff] [blame] | 387 | mSelectionMgr.addCallback(mActionModeController); |
Ben Kwa | 7461a95 | 2015-09-01 11:03:01 -0700 | [diff] [blame] | 388 | |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 389 | final ActivityManager am = (ActivityManager) mActivity.getSystemService( |
Jeff Sharkey | f63b777 | 2013-10-01 17:57:41 -0700 | [diff] [blame] | 390 | Context.ACTIVITY_SERVICE); |
Garfield Tan | e967033 | 2017-03-06 18:33:23 -0800 | [diff] [blame] | 391 | boolean svelte = am.isLowRamDevice() && (mState.stack.isRecents()); |
Ben Kwa | 8e3fd76 | 2015-12-17 10:37:00 -0800 | [diff] [blame] | 392 | mIconHelper.setThumbnailsEnabled(!svelte); |
Jeff Sharkey | f63b777 | 2013-10-01 17:57:41 -0700 | [diff] [blame] | 393 | |
Garfield, Tan | 11d2348 | 2016-08-05 09:33:29 -0700 | [diff] [blame] | 394 | // If mDocument is null, we sort it by last modified by default because it's in Recents. |
| 395 | final boolean prefersLastModified = |
Garfield Tan | e967033 | 2017-03-06 18:33:23 -0800 | [diff] [blame] | 396 | (mLocalState.mDocument == null) |
| 397 | || mLocalState.mDocument.prefersSortByLastModified(); |
Garfield, Tan | 11d2348 | 2016-08-05 09:33:29 -0700 | [diff] [blame] | 398 | // Call this before adding the listener to avoid restarting the loader one more time |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 399 | mState.sortModel.setDefaultDimension( |
Garfield, Tan | 11d2348 | 2016-08-05 09:33:29 -0700 | [diff] [blame] | 400 | prefersLastModified |
| 401 | ? SortModel.SORT_DIMENSION_ID_DATE |
| 402 | : SortModel.SORT_DIMENSION_ID_TITLE); |
Garfield, Tan | 171e6f5 | 2016-07-29 14:44:58 -0700 | [diff] [blame] | 403 | |
Jeff Sharkey | a4d1f22 | 2013-09-07 14:45:03 -0700 | [diff] [blame] | 404 | // Kick off loader at least once |
Garfield Tan | e967033 | 2017-03-06 18:33:23 -0800 | [diff] [blame] | 405 | mActions.loadDocumentsForCurrentStack(); |
Jeff Sharkey | e22d02e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 406 | } |
| 407 | |
Garfield, Tan | 11d2348 | 2016-08-05 09:33:29 -0700 | [diff] [blame] | 408 | @Override |
| 409 | public void onStart() { |
| 410 | super.onStart(); |
| 411 | |
Garfield, Tan | 11d2348 | 2016-08-05 09:33:29 -0700 | [diff] [blame] | 412 | // Add listener to update contents on sort model change |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 413 | mState.sortModel.addListener(mSortListener); |
Garfield, Tan | 11d2348 | 2016-08-05 09:33:29 -0700 | [diff] [blame] | 414 | } |
| 415 | |
| 416 | @Override |
| 417 | public void onStop() { |
| 418 | super.onStop(); |
| 419 | |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 420 | mState.sortModel.removeListener(mSortListener); |
Garfield, Tan | 11d2348 | 2016-08-05 09:33:29 -0700 | [diff] [blame] | 421 | |
| 422 | // Remember last scroll location |
Steve McKay | 17b761e | 2016-09-20 17:26:46 -0700 | [diff] [blame] | 423 | final SparseArray<Parcelable> container = new SparseArray<>(); |
Garfield, Tan | 11d2348 | 2016-08-05 09:33:29 -0700 | [diff] [blame] | 424 | getView().saveHierarchyState(container); |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 425 | mState.dirConfigs.put(mLocalState.getConfigKey(), container); |
Garfield, Tan | 11d2348 | 2016-08-05 09:33:29 -0700 | [diff] [blame] | 426 | } |
| 427 | |
Steve McKay | 2bab2f8 | 2016-06-03 09:23:39 -0700 | [diff] [blame] | 428 | public void retainState(RetainedState state) { |
Steve McKay | 74956af | 2016-06-30 21:03:06 -0700 | [diff] [blame] | 429 | state.selection = mSelectionMgr.getSelection(new Selection()); |
Steve McKay | 2bab2f8 | 2016-06-03 09:23:39 -0700 | [diff] [blame] | 430 | } |
| 431 | |
Jeff Sharkey | 42d2679 | 2013-09-06 13:22:09 -0700 | [diff] [blame] | 432 | @Override |
Steve McKay | 84fa371 | 2016-02-08 19:09:42 -0800 | [diff] [blame] | 433 | public void onSaveInstanceState(Bundle outState) { |
Aga Wronska | af5ace5 | 2016-02-17 13:50:42 -0800 | [diff] [blame] | 434 | super.onSaveInstanceState(outState); |
| 435 | |
Steve McKay | bd9f05a | 2016-10-10 10:18:36 -0700 | [diff] [blame] | 436 | mLocalState.save(outState); |
Steve McKay | 84fa371 | 2016-02-08 19:09:42 -0800 | [diff] [blame] | 437 | } |
| 438 | |
| 439 | @Override |
Ben Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 440 | public void onCreateContextMenu(ContextMenu menu, |
| 441 | View v, |
| 442 | ContextMenu.ContextMenuInfo menuInfo) { |
| 443 | super.onCreateContextMenu(menu, v, menuInfo); |
Garfield Tan | 06c4b11 | 2016-09-14 11:05:58 -0700 | [diff] [blame] | 444 | final MenuInflater inflater = getActivity().getMenuInflater(); |
Ben Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 445 | |
Garfield Tan | 06c4b11 | 2016-09-14 11:05:58 -0700 | [diff] [blame] | 446 | final String modelId = getModelId(v); |
| 447 | if (modelId == null) { |
Steve McKay | 990f76e | 2016-09-16 12:36:58 -0700 | [diff] [blame] | 448 | // TODO: inject DirectoryDetails into MenuManager constructor |
| 449 | // Since both classes are supplied by Activity and created |
| 450 | // at the same time. |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 451 | mInjector.menuManager.inflateContextMenuForContainer(menu, inflater); |
Garfield Tan | 239ab97 | 2016-09-16 13:33:48 -0700 | [diff] [blame] | 452 | } else { |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 453 | mInjector.menuManager.inflateContextMenuForDocs(menu, inflater, mSelectionMetadata); |
Ben Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 454 | } |
| 455 | } |
| 456 | |
| 457 | @Override |
| 458 | public boolean onContextItemSelected(MenuItem item) { |
| 459 | return handleMenuItemClick(item); |
| 460 | } |
| 461 | |
Steve McKay | f171934 | 2016-02-17 15:02:01 -0800 | [diff] [blame] | 462 | private void handleCopyResult(int resultCode, Intent data) { |
Garfield, Tan | edce554 | 2016-06-17 15:32:28 -0700 | [diff] [blame] | 463 | |
Steve McKay | bd9f05a | 2016-10-10 10:18:36 -0700 | [diff] [blame] | 464 | FileOperation operation = mLocalState.claimPendingOperation(); |
Garfield, Tan | edce554 | 2016-06-17 15:32:28 -0700 | [diff] [blame] | 465 | |
Ben Kwa | f585893 | 2015-04-07 15:43:39 -0700 | [diff] [blame] | 466 | 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, Tan | b7e5f6b | 2016-06-30 18:27:47 -0700 | [diff] [blame] | 469 | operation.dispose(); |
Ben Kwa | f585893 | 2015-04-07 15:43:39 -0700 | [diff] [blame] | 470 | return; |
| 471 | } |
| 472 | |
Garfield, Tan | 4833477 | 2016-06-28 17:17:38 -0700 | [diff] [blame] | 473 | operation.setDestination(data.getParcelableExtra(Shared.EXTRA_STACK)); |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 474 | FileOperations.start( |
| 475 | mActivity, |
| 476 | operation, |
Ben Lin | 6868962 | 2016-11-09 16:10:38 -0800 | [diff] [blame] | 477 | mInjector.dialogs::showFileOperationStatus); |
Ben Kwa | f585893 | 2015-04-07 15:43:39 -0700 | [diff] [blame] | 478 | } |
| 479 | |
Ben Lin | aa66c43 | 2016-10-12 12:11:24 -0700 | [diff] [blame] | 480 | protected boolean onContextMenuClick(InputEvent e) { |
Garfield Tan | 239ab97 | 2016-09-16 13:33:48 -0700 | [diff] [blame] | 481 | final View v; |
| 482 | final float x, y; |
Steve McKay | 1597e94 | 2016-09-09 11:54:05 -0700 | [diff] [blame] | 483 | if (e.isOverModelItem()) { |
| 484 | DocumentHolder doc = (DocumentHolder) e.getDocumentDetails(); |
Ben Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 485 | |
Garfield Tan | 239ab97 | 2016-09-16 13:33:48 -0700 | [diff] [blame] | 486 | v = doc.itemView; |
| 487 | x = e.getX() - v.getLeft(); |
| 488 | y = e.getY() - v.getTop(); |
| 489 | } else { |
Ben Lin | 7b38f34 | 2016-12-14 11:52:35 -0800 | [diff] [blame] | 490 | v = mRecView; |
Garfield Tan | 239ab97 | 2016-09-16 13:33:48 -0700 | [diff] [blame] | 491 | x = e.getX(); |
| 492 | y = e.getY(); |
Steve McKay | 79a6fe0 | 2016-06-30 12:09:34 -0700 | [diff] [blame] | 493 | } |
Steve McKay | 74956af | 2016-06-30 21:03:06 -0700 | [diff] [blame] | 494 | |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 495 | mInjector.menuManager.showContextMenu(this, v, x, y); |
Steve McKay | 1597e94 | 2016-09-09 11:54:05 -0700 | [diff] [blame] | 496 | |
Ben Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 497 | return true; |
| 498 | } |
| 499 | |
Steve McKay | 7776aa5 | 2016-01-25 19:00:22 -0800 | [diff] [blame] | 500 | public void onViewModeChanged() { |
| 501 | // Mode change is just visual change; no need to kick loader. |
Jon Mann | 30d8c79 | 2017-02-21 17:44:49 -0800 | [diff] [blame] | 502 | onDisplayStateChanged(); |
Jeff Sharkey | a4d1f22 | 2013-09-07 14:45:03 -0700 | [diff] [blame] | 503 | } |
| 504 | |
Jon Mann | 30d8c79 | 2017-02-21 17:44:49 -0800 | [diff] [blame] | 505 | private void onDisplayStateChanged() { |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 506 | updateLayout(mState.derivedMode); |
Steve McKay | ef28015 | 2015-06-11 10:10:49 -0700 | [diff] [blame] | 507 | mRecView.setAdapter(mAdapter); |
| 508 | } |
| 509 | |
| 510 | /** |
Ben Kwa | bc7df44 | 2016-02-02 23:00:02 -0800 | [diff] [blame] | 511 | * Updates the layout after the view mode switches. |
| 512 | * @param mode The new view mode. |
Steve McKay | ef28015 | 2015-06-11 10:10:49 -0700 | [diff] [blame] | 513 | */ |
Ben Kwa | bc7df44 | 2016-02-02 23:00:02 -0800 | [diff] [blame] | 514 | private void updateLayout(@ViewMode int mode) { |
Steve McKay | b03a59c | 2016-10-19 15:59:53 -0700 | [diff] [blame] | 515 | mMode = mode; |
Ben Kwa | bc7df44 | 2016-02-02 23:00:02 -0800 | [diff] [blame] | 516 | mColumnCount = calculateColumnCount(mode); |
| 517 | if (mLayout != null) { |
| 518 | mLayout.setSpanCount(mColumnCount); |
Jeff Sharkey | 09c10bf | 2013-06-30 20:02:59 -0700 | [diff] [blame] | 519 | } |
Jeff Sharkey | 8a8fb67 | 2013-05-07 12:41:33 -0700 | [diff] [blame] | 520 | |
Steve McKay | ac155ab | 2016-01-12 11:14:33 -0800 | [diff] [blame] | 521 | int pad = getDirectoryPadding(mode); |
| 522 | mRecView.setPadding(pad, pad, pad, pad); |
Ben Kwa | bc7df44 | 2016-02-02 23:00:02 -0800 | [diff] [blame] | 523 | mRecView.requestLayout(); |
Steve McKay | d586959 | 2016-05-26 12:04:43 -0700 | [diff] [blame] | 524 | if (mBandController != null) { |
| 525 | mBandController.handleLayoutChanged(); |
| 526 | } |
Steve McKay | 7776aa5 | 2016-01-25 19:00:22 -0800 | [diff] [blame] | 527 | mIconHelper.setViewMode(mode); |
Jeff Sharkey | 09c10bf | 2013-06-30 20:02:59 -0700 | [diff] [blame] | 528 | } |
| 529 | |
Steve McKay | b03a59c | 2016-10-19 15:59:53 -0700 | [diff] [blame] | 530 | /** |
| 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 McKay | 30535bc | 2016-11-04 14:16:58 -0700 | [diff] [blame] | 536 | if (VERBOSE) Log.v( |
| 537 | TAG, "Handling scale event: " + scale + ", existing scale: " + mLiveScale); |
Steve McKay | b03a59c | 2016-10-19 15:59:53 -0700 | [diff] [blame] | 538 | |
| 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 McKay | 30535bc | 2016-11-04 14:16:58 -0700 | [diff] [blame] | 544 | if (VERBOSE) Log.v(TAG, |
Steve McKay | b03a59c | 2016-10-19 15:59:53 -0700 | [diff] [blame] | 545 | "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 Kwa | bc7df44 | 2016-02-02 23:00:02 -0800 | [diff] [blame] | 559 | 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 McKay | b03a59c | 2016-10-19 15:59:53 -0700 | [diff] [blame] | 565 | 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 McKay | fefcd70 | 2015-08-20 16:19:38 +0000 | [diff] [blame] | 569 | |
Steve McKay | b03a59c | 2016-10-19 15:59:53 -0700 | [diff] [blame] | 570 | // 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 Kwa | 2e3d418 | 2016-03-16 13:01:20 -0700 | [diff] [blame] | 572 | int columnCount = Math.max(2, |
Steve McKay | fefcd70 | 2015-08-20 16:19:38 +0000 | [diff] [blame] | 573 | (mRecView.getWidth() - viewPadding) / (cellWidth + cellMargin)); |
| 574 | |
Steve McKay | b03a59c | 2016-10-19 15:59:53 -0700 | [diff] [blame] | 575 | // 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 McKay | d57f5fa | 2015-07-23 16:33:41 -0700 | [diff] [blame] | 590 | } |
| 591 | |
Ben Kwa | bc7df44 | 2016-02-02 23:00:02 -0800 | [diff] [blame] | 592 | private int getDirectoryPadding(@ViewMode int mode) { |
Steve McKay | ac155ab | 2016-01-12 11:14:33 -0800 | [diff] [blame] | 593 | switch (mode) { |
| 594 | case MODE_GRID: |
Ben Kwa | bc7df44 | 2016-02-02 23:00:02 -0800 | [diff] [blame] | 595 | return getResources().getDimensionPixelSize(R.dimen.grid_container_padding); |
Steve McKay | ac155ab | 2016-01-12 11:14:33 -0800 | [diff] [blame] | 596 | case MODE_LIST: |
Ben Kwa | bc7df44 | 2016-02-02 23:00:02 -0800 | [diff] [blame] | 597 | return getResources().getDimensionPixelSize(R.dimen.list_container_padding); |
Steve McKay | ac155ab | 2016-01-12 11:14:33 -0800 | [diff] [blame] | 598 | default: |
| 599 | throw new IllegalArgumentException("Unsupported layout mode: " + mode); |
| 600 | } |
| 601 | } |
| 602 | |
Ben Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 603 | private boolean handleMenuItemClick(MenuItem item) { |
Steve McKay | 74956af | 2016-06-30 21:03:06 -0700 | [diff] [blame] | 604 | Selection selection = mSelectionMgr.getSelection(new Selection()); |
Jeff Sharkey | 873daa3 | 2013-08-18 17:38:20 -0700 | [diff] [blame] | 605 | |
Ben Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 606 | switch (item.getItemId()) { |
| 607 | case R.id.menu_open: |
| 608 | openDocuments(selection); |
Garfield Tan | 84bd0f1 | 2016-09-12 14:18:32 -0700 | [diff] [blame] | 609 | mActionModeController.finishActionMode(); |
Ben Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 610 | return true; |
Jeff Sharkey | 873daa3 | 2013-08-18 17:38:20 -0700 | [diff] [blame] | 611 | |
Garfield Tan | 239ab97 | 2016-09-16 13:33:48 -0700 | [diff] [blame] | 612 | case R.id.menu_open_with: |
| 613 | showChooserForDoc(selection); |
| 614 | return true; |
| 615 | |
| 616 | case R.id.menu_open_in_new_window: |
Steve McKay | 5b0a2c1 | 2016-10-07 11:22:31 -0700 | [diff] [blame] | 617 | mActions.openSelectedInNewWindow(); |
Garfield Tan | 239ab97 | 2016-09-16 13:33:48 -0700 | [diff] [blame] | 618 | return true; |
| 619 | |
Ben Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 620 | case R.id.menu_share: |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 621 | mActions.shareSelectedDocuments(); |
Ben Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 622 | return true; |
Jeff Sharkey | 873daa3 | 2013-08-18 17:38:20 -0700 | [diff] [blame] | 623 | |
Ben Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 624 | 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 McKay | bd9f05a | 2016-10-10 10:18:36 -0700 | [diff] [blame] | 627 | mActions.deleteSelectedDocuments(); |
Ben Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 628 | return true; |
Ben Kwa | cb4461f | 2015-05-05 11:50:11 -0700 | [diff] [blame] | 629 | |
Ben Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 630 | case R.id.menu_copy_to: |
Tomasz Mikolajewski | 26640ab | 2017-02-06 10:37:59 +0900 | [diff] [blame] | 631 | transferDocuments(selection, null, FileOperationService.OPERATION_COPY); |
Ben Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 632 | // 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 Tan | 84bd0f1 | 2016-09-12 14:18:32 -0700 | [diff] [blame] | 634 | mActionModeController.finishActionMode(); |
Ben Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 635 | return true; |
Ben Kwa | 41b26c1 | 2015-03-31 10:11:43 -0700 | [diff] [blame] | 636 | |
Tomasz Mikolajewski | 26640ab | 2017-02-06 10:37:59 +0900 | [diff] [blame] | 637 | 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 Mikolajewski | 26640ab | 2017-02-06 10:37:59 +0900 | [diff] [blame] | 645 | // TODO: Implement extract (to the current directory). |
Tomasz Mikolajewski | bc7eb5f | 2017-01-27 16:29:30 +0900 | [diff] [blame] | 646 | case R.id.menu_extract_to: |
Tomasz Mikolajewski | 26640ab | 2017-02-06 10:37:59 +0900 | [diff] [blame] | 647 | transferDocuments(selection, null, FileOperationService.OPERATION_EXTRACT); |
Tomasz Mikolajewski | bc7eb5f | 2017-01-27 16:29:30 +0900 | [diff] [blame] | 648 | // 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 Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 653 | case R.id.menu_move_to: |
| 654 | // Exit selection mode first, so we avoid deselecting deleted documents. |
Garfield Tan | 84bd0f1 | 2016-09-12 14:18:32 -0700 | [diff] [blame] | 655 | mActionModeController.finishActionMode(); |
Tomasz Mikolajewski | 26640ab | 2017-02-06 10:37:59 +0900 | [diff] [blame] | 656 | transferDocuments(selection, null, FileOperationService.OPERATION_MOVE); |
Ben Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 657 | return true; |
Steve McKay | 1f19948 | 2015-05-20 15:58:42 -0700 | [diff] [blame] | 658 | |
Ben Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 659 | case R.id.menu_cut_to_clipboard: |
Ben Lin | d947f01 | 2016-10-18 14:32:49 -0700 | [diff] [blame] | 660 | mActions.cutToClipboard(); |
Ben Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 661 | return true; |
Ben Kwa | 512a6ba | 2015-03-31 08:15:21 -0700 | [diff] [blame] | 662 | |
Ben Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 663 | case R.id.menu_copy_to_clipboard: |
Ben Lin | d947f01 | 2016-10-18 14:32:49 -0700 | [diff] [blame] | 664 | mActions.copyToClipboard(); |
Ben Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 665 | return true; |
Aga Wronska | 3c23718 | 2016-01-20 16:32:33 -0800 | [diff] [blame] | 666 | |
Ben Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 667 | case R.id.menu_paste_from_clipboard: |
| 668 | pasteFromClipboard(); |
| 669 | return true; |
| 670 | |
Ben Lin | b2dfa12 | 2016-08-11 17:42:57 -0700 | [diff] [blame] | 671 | case R.id.menu_paste_into_folder: |
| 672 | pasteIntoFolder(); |
| 673 | return true; |
| 674 | |
Ben Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 675 | case R.id.menu_select_all: |
Jon Mann | 30d8c79 | 2017-02-21 17:44:49 -0800 | [diff] [blame] | 676 | mActions.selectAllFiles(); |
Ben Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 677 | return true; |
| 678 | |
| 679 | case R.id.menu_rename: |
| 680 | // Exit selection mode first, so we avoid deselecting deleted |
| 681 | // (renamed) documents. |
Garfield Tan | 84bd0f1 | 2016-09-12 14:18:32 -0700 | [diff] [blame] | 682 | mActionModeController.finishActionMode(); |
Ben Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 683 | renameDocuments(selection); |
| 684 | return true; |
| 685 | |
Jon Mann | 253a992 | 2017-03-21 18:53:27 -0700 | [diff] [blame] | 686 | case R.id.menu_view_in_owner: |
| 687 | mActions.viewInOwner(); |
| 688 | return true; |
| 689 | |
Ben Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 690 | default: |
| 691 | // See if BaseActivity can handle this particular MenuItem |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 692 | if (!mActivity.onOptionsItemSelected(item)) { |
Steve McKay | 3abb0e3 | 2015-12-01 17:02:42 -0800 | [diff] [blame] | 693 | if (DEBUG) Log.d(TAG, "Unhandled menu item selected: " + item); |
| 694 | return false; |
Ben Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 695 | } |
| 696 | return true; |
Jeff Sharkey | c317af8 | 2013-07-01 16:56:54 -0700 | [diff] [blame] | 697 | } |
Steve McKay | ef28015 | 2015-06-11 10:10:49 -0700 | [diff] [blame] | 698 | } |
Jeff Sharkey | c317af8 | 2013-07-01 16:56:54 -0700 | [diff] [blame] | 699 | |
Ben Lin | e4ffd06 | 2017-01-19 15:10:49 -0800 | [diff] [blame] | 700 | private boolean onAccessibilityClick(View child) { |
| 701 | DocumentDetails doc = getDocumentHolder(child); |
Tomasz Mikolajewski | d22cc18 | 2017-03-15 16:13:46 +0900 | [diff] [blame] | 702 | mActions.openDocument(doc, ActionHandler.VIEW_TYPE_PREVIEW, |
| 703 | ActionHandler.VIEW_TYPE_REGULAR); |
Ben Lin | e4ffd06 | 2017-01-19 15:10:49 -0800 | [diff] [blame] | 704 | return true; |
| 705 | } |
| 706 | |
Ben Kwa | 8e3fd76 | 2015-12-17 10:37:00 -0800 | [diff] [blame] | 707 | private void cancelThumbnailTask(View view) { |
Steve McKay | ef28015 | 2015-06-11 10:10:49 -0700 | [diff] [blame] | 708 | final ImageView iconThumb = (ImageView) view.findViewById(R.id.icon_thumb); |
| 709 | if (iconThumb != null) { |
Ben Kwa | 8e3fd76 | 2015-12-17 10:37:00 -0800 | [diff] [blame] | 710 | mIconHelper.stopLoading(iconThumb); |
Jeff Sharkey | a35ac2d | 2013-09-10 12:04:26 -0700 | [diff] [blame] | 711 | } |
Steve McKay | ef28015 | 2015-06-11 10:10:49 -0700 | [diff] [blame] | 712 | } |
Jeff Sharkey | a35ac2d | 2013-09-10 12:04:26 -0700 | [diff] [blame] | 713 | |
Steve McKay | f61f93a | 2016-09-01 15:39:18 -0700 | [diff] [blame] | 714 | // Support for opening multiple documents is currently exclusive to DocumentsActivity. |
Steve McKay | ef28015 | 2015-06-11 10:10:49 -0700 | [diff] [blame] | 715 | private void openDocuments(final Selection selected) { |
Aga Wronska | 94e53e4 | 2016-04-07 13:09:58 -0700 | [diff] [blame] | 716 | Metrics.logUserAction(getContext(), Metrics.USER_ACTION_OPEN); |
| 717 | |
Steve McKay | f61f93a | 2016-09-01 15:39:18 -0700 | [diff] [blame] | 718 | // Model must be accessed in UI thread, since underlying cursor is not threadsafe. |
| 719 | List<DocumentInfo> docs = mModel.getDocuments(selected); |
Garfield Tan | 06c4b11 | 2016-09-14 11:05:58 -0700 | [diff] [blame] | 720 | if (docs.size() > 1) { |
Steve McKay | d79cc4a | 2016-10-11 16:34:40 -0700 | [diff] [blame] | 721 | mActivity.onDocumentsPicked(docs); |
Garfield Tan | 06c4b11 | 2016-09-14 11:05:58 -0700 | [diff] [blame] | 722 | } else { |
Steve McKay | d79cc4a | 2016-10-11 16:34:40 -0700 | [diff] [blame] | 723 | mActivity.onDocumentPicked(docs.get(0)); |
Garfield Tan | 06c4b11 | 2016-09-14 11:05:58 -0700 | [diff] [blame] | 724 | } |
Jeff Sharkey | 873daa3 | 2013-08-18 17:38:20 -0700 | [diff] [blame] | 725 | } |
| 726 | |
Garfield Tan | 239ab97 | 2016-09-16 13:33:48 -0700 | [diff] [blame] | 727 | 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 Tan | 208945c | 2016-10-04 14:36:38 -0700 | [diff] [blame] | 733 | mActions.showChooserForDoc(doc); |
Garfield Tan | 239ab97 | 2016-09-16 13:33:48 -0700 | [diff] [blame] | 734 | } |
| 735 | |
Tomasz Mikolajewski | 26640ab | 2017-02-06 10:37:59 +0900 | [diff] [blame] | 736 | private void transferDocuments(final Selection selected, @Nullable DocumentStack destination, |
| 737 | final @OpType int mode) { |
Tomasz Mikolajewski | bc7eb5f | 2017-01-27 16:29:30 +0900 | [diff] [blame] | 738 | switch (mode) { |
| 739 | case FileOperationService.OPERATION_COPY: |
| 740 | Metrics.logUserAction(getContext(), Metrics.USER_ACTION_COPY_TO); |
| 741 | break; |
| 742 | case FileOperationService.OPERATION_COMPRESS: |
Tomasz Mikolajewski | 2e8acf0 | 2017-02-09 17:24:23 +0900 | [diff] [blame] | 743 | Metrics.logUserAction(getContext(), Metrics.USER_ACTION_COMPRESS); |
Tomasz Mikolajewski | bc7eb5f | 2017-01-27 16:29:30 +0900 | [diff] [blame] | 744 | 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 Wronska | 94e53e4 | 2016-04-07 13:09:58 -0700 | [diff] [blame] | 751 | } |
| 752 | |
Garfield, Tan | b7e5f6b | 2016-06-30 18:27:47 -0700 | [diff] [blame] | 753 | UrisSupplier srcs; |
| 754 | try { |
Steve McKay | c8889af | 2016-09-23 11:22:41 -0700 | [diff] [blame] | 755 | ClipStore clipStorage = DocumentsApplication.getClipStore(getContext()); |
| 756 | srcs = UrisSupplier.create(selected, mModel::getItemUri, clipStorage); |
Tomasz Mikolajewski | bc7eb5f | 2017-01-27 16:29:30 +0900 | [diff] [blame] | 757 | } catch (IOException e) { |
Garfield, Tan | b7e5f6b | 2016-06-30 18:27:47 -0700 | [diff] [blame] | 758 | throw new RuntimeException("Failed to create uri supplier.", e); |
| 759 | } |
Garfield, Tan | 4833477 | 2016-06-28 17:17:38 -0700 | [diff] [blame] | 760 | |
Garfield Tan | 03a3a39 | 2016-12-12 14:06:45 -0800 | [diff] [blame] | 761 | final DocumentInfo parent = mState.stack.peek(); |
Tomasz Mikolajewski | 26640ab | 2017-02-06 10:37:59 +0900 | [diff] [blame] | 762 | final FileOperation operation = new FileOperation.Builder() |
Garfield, Tan | 4833477 | 2016-06-28 17:17:38 -0700 | [diff] [blame] | 763 | .withOpType(mode) |
Garfield Tan | 03a3a39 | 2016-12-12 14:06:45 -0800 | [diff] [blame] | 764 | .withSrcParent(parent == null ? null : parent.derivedUri) |
Garfield, Tan | 4833477 | 2016-06-28 17:17:38 -0700 | [diff] [blame] | 765 | .withSrcs(srcs) |
| 766 | .build(); |
Steve McKay | 1abf6cf | 2016-04-08 17:34:11 -0700 | [diff] [blame] | 767 | |
Tomasz Mikolajewski | 26640ab | 2017-02-06 10:37:59 +0900 | [diff] [blame] | 768 | 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 McKay | f171934 | 2016-02-17 15:02:01 -0800 | [diff] [blame] | 786 | // 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 Mikolajewski | bc7eb5f | 2017-01-27 16:29:30 +0900 | [diff] [blame] | 789 | 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 Mann | d1a2622 | 2017-02-14 13:15:57 -0800 | [diff] [blame] | 801 | drawerTitleId = R.string.menu_move; |
Tomasz Mikolajewski | bc7eb5f | 2017-01-27 16:29:30 +0900 | [diff] [blame] | 802 | break; |
| 803 | default: |
| 804 | throw new UnsupportedOperationException("Unknown mode: " + mode); |
| 805 | } |
Tomasz Mikolajewski | 26640ab | 2017-02-06 10:37:59 +0900 | [diff] [blame] | 806 | |
Steve McKay | f171934 | 2016-02-17 15:02:01 -0800 | [diff] [blame] | 807 | intent.putExtra(DocumentsContract.EXTRA_PROMPT, getResources().getString(drawerTitleId)); |
| 808 | |
Steve McKay | f61f93a | 2016-09-01 15:39:18 -0700 | [diff] [blame] | 809 | // Model must be accessed in UI thread, since underlying cursor is not threadsafe. |
| 810 | List<DocumentInfo> docs = mModel.getDocuments(selected); |
Steve McKay | f171934 | 2016-02-17 15:02:01 -0800 | [diff] [blame] | 811 | |
Steve McKay | f61f93a | 2016-09-01 15:39:18 -0700 | [diff] [blame] | 812 | // 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 McKay | f171934 | 2016-02-17 15:02:01 -0800 | [diff] [blame] | 818 | |
Steve McKay | f61f93a | 2016-09-01 15:39:18 -0700 | [diff] [blame] | 819 | // This just identifies the type of request...we'll check it |
| 820 | // when we reveive a response. |
| 821 | startActivityForResult(intent, REQUEST_COPY_DESTINATION); |
Ben Kwa | 41b26c1 | 2015-03-31 10:11:43 -0700 | [diff] [blame] | 822 | } |
| 823 | |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 824 | @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 McKay | f171934 | 2016-02-17 15:02:01 -0800 | [diff] [blame] | 835 | 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 Wronska | 3c23718 | 2016-01-20 16:32:33 -0800 | [diff] [blame] | 844 | private void renameDocuments(Selection selected) { |
Aga Wronska | 94e53e4 | 2016-04-07 13:09:58 -0700 | [diff] [blame] | 845 | Metrics.logUserAction(getContext(), Metrics.USER_ACTION_RENAME); |
| 846 | |
Aga Wronska | 3c23718 | 2016-01-20 16:32:33 -0800 | [diff] [blame] | 847 | // Batch renaming not supported |
| 848 | // Rename option is only available in menu when 1 document selected |
Steve McKay | 0af8afd | 2016-02-25 13:34:03 -0800 | [diff] [blame] | 849 | assert(selected.size() == 1); |
Aga Wronska | 3c23718 | 2016-01-20 16:32:33 -0800 | [diff] [blame] | 850 | |
Steve McKay | f61f93a | 2016-09-01 15:39:18 -0700 | [diff] [blame] | 851 | // Model must be accessed in UI thread, since underlying cursor is not threadsafe. |
| 852 | List<DocumentInfo> docs = mModel.getDocuments(selected); |
Jon Mann | cf120d4 | 2017-02-14 11:43:39 -0800 | [diff] [blame] | 853 | RenameDocumentFragment.show(getChildFragmentManager(), docs.get(0)); |
| 854 | } |
| 855 | |
| 856 | Model getModel(){ |
| 857 | return mModel; |
Aga Wronska | 3c23718 | 2016-01-20 16:32:33 -0800 | [diff] [blame] | 858 | } |
| 859 | |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 860 | private boolean isDocumentEnabled(String mimeType, int flags) { |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 861 | return mInjector.config.isDocumentEnabled(mimeType, flags, mState); |
Steve McKay | c5ecf89 | 2015-12-22 18:15:31 -0800 | [diff] [blame] | 862 | } |
| 863 | |
Ben Lin | db7a0e7 | 2017-03-02 11:18:22 -0800 | [diff] [blame] | 864 | /** |
| 865 | * Paste selection files from the primary clip into the current window. |
| 866 | */ |
Steve McKay | f68210e | 2015-11-03 15:23:16 -0800 | [diff] [blame] | 867 | public void pasteFromClipboard() { |
Aga Wronska | 94e53e4 | 2016-04-07 13:09:58 -0700 | [diff] [blame] | 868 | Metrics.logUserAction(getContext(), Metrics.USER_ACTION_PASTE_CLIPBOARD); |
Ben Lin | db7a0e7 | 2017-03-02 11:18:22 -0800 | [diff] [blame] | 869 | // Since we are pasting into the current window, we already have the destination in the |
| 870 | // stack. No need for a destination DocumentInfo. |
Garfield, Tan | a5588b6 | 2016-07-13 09:23:04 -0700 | [diff] [blame] | 871 | mClipper.copyFromClipboard( |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 872 | mState.stack, |
Ben Lin | 6868962 | 2016-11-09 16:10:38 -0800 | [diff] [blame] | 873 | mInjector.dialogs::showFileOperationStatus); |
Steve McKay | 1f19948 | 2015-05-20 15:58:42 -0700 | [diff] [blame] | 874 | getActivity().invalidateOptionsMenu(); |
Steve McKay | 0599a44 | 2015-05-05 14:50:00 -0700 | [diff] [blame] | 875 | } |
| 876 | |
Ben Lin | b2dfa12 | 2016-08-11 17:42:57 -0700 | [diff] [blame] | 877 | 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 Lin | b2dfa12 | 2016-08-11 17:42:57 -0700 | [diff] [blame] | 886 | DocumentInfo destination = DocumentInfo.fromDirectoryCursor(dstCursor); |
| 887 | mClipper.copyFromClipboard( |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 888 | destination, |
| 889 | mState.stack, |
Ben Lin | 6868962 | 2016-11-09 16:10:38 -0800 | [diff] [blame] | 890 | mInjector.dialogs::showFileOperationStatus); |
Ben Lin | b2dfa12 | 2016-08-11 17:42:57 -0700 | [diff] [blame] | 891 | getActivity().invalidateOptionsMenu(); |
| 892 | } |
| 893 | |
Vladislav Kaznacheev | b6da722 | 2015-05-01 14:18:57 -0700 | [diff] [blame] | 894 | 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 Lin | 35f99e0 | 2016-08-31 12:46:04 -0700 | [diff] [blame] | 899 | view.setOnDragListener(mDragHoverListener); |
Vladislav Kaznacheev | b6da722 | 2015-05-01 14:18:57 -0700 | [diff] [blame] | 900 | } |
Vladislav Kaznacheev | b6da722 | 2015-05-01 14:18:57 -0700 | [diff] [blame] | 901 | } |
| 902 | |
Garfield, Tan | b7e5f6b | 2016-06-30 18:27:47 -0700 | [diff] [blame] | 903 | void dragStopped(boolean result) { |
| 904 | if (result) { |
Steve McKay | 74956af | 2016-06-30 21:03:06 -0700 | [diff] [blame] | 905 | mSelectionMgr.clearSelection(); |
Garfield, Tan | b7e5f6b | 2016-06-30 18:27:47 -0700 | [diff] [blame] | 906 | } |
Garfield, Tan | 804133e | 2016-04-20 15:13:56 -0700 | [diff] [blame] | 907 | } |
Vladislav Kaznacheev | b6da722 | 2015-05-01 14:18:57 -0700 | [diff] [blame] | 908 | |
Garfield, Tan | 804133e | 2016-04-20 15:13:56 -0700 | [diff] [blame] | 909 | @Override |
| 910 | public void runOnUiThread(Runnable runnable) { |
| 911 | getActivity().runOnUiThread(runnable); |
| 912 | } |
Ben Kwa | 13e2605 | 2016-02-18 16:45:45 -0800 | [diff] [blame] | 913 | |
Ben Lin | d020212 | 2016-11-10 18:00:12 -0800 | [diff] [blame] | 914 | // 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 Lin | 7f72a3c | 2016-09-27 16:37:28 -0700 | [diff] [blame] | 917 | @Override |
Ben Lin | 1c45629 | 2016-10-07 16:43:18 -0700 | [diff] [blame] | 918 | public void onDragEntered(View v, Object localState) { |
Ben Lin | 166c5c6 | 2016-11-01 12:14:38 -0700 | [diff] [blame] | 919 | mActivity.setRootsDrawerOpen(false); |
Ben Lin | d020212 | 2016-11-10 18:00:12 -0800 | [diff] [blame] | 920 | mActivity.getShadowBuilder() |
| 921 | .setAppearDroppable(DragAndDropHelper.canCopyTo(localState, getDestination(v))); |
Ben Lin | 1c45629 | 2016-10-07 16:43:18 -0700 | [diff] [blame] | 922 | v.updateDragShadow(mActivity.getShadowBuilder()); |
Ben Lin | 7f72a3c | 2016-09-27 16:37:28 -0700 | [diff] [blame] | 923 | } |
| 924 | |
Ben Lin | d020212 | 2016-11-10 18:00:12 -0800 | [diff] [blame] | 925 | // 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, Tan | 804133e | 2016-04-20 15:13:56 -0700 | [diff] [blame] | 940 | @Override |
| 941 | public void onViewHovered(View view) { |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 942 | BaseActivity activity = mActivity; |
Garfield, Tan | 804133e | 2016-04-20 15:13:56 -0700 | [diff] [blame] | 943 | if (getModelId(view) != null) { |
Ben Lin | d8d0ad2 | 2017-01-11 13:30:50 -0800 | [diff] [blame] | 944 | mActions.springOpenDirectory(getDestination(view)); |
Garfield, Tan | 804133e | 2016-04-20 15:13:56 -0700 | [diff] [blame] | 945 | } |
Garfield, Tan | 7d66a86 | 2016-05-11 10:28:41 -0700 | [diff] [blame] | 946 | activity.setRootsDrawerOpen(false); |
Garfield, Tan | 804133e | 2016-04-20 15:13:56 -0700 | [diff] [blame] | 947 | } |
Ben Kwa | a8c0da4 | 2016-02-25 14:10:40 -0800 | [diff] [blame] | 948 | |
Garfield, Tan | edce554 | 2016-06-17 15:32:28 -0700 | [diff] [blame] | 949 | boolean handleDropEvent(View v, DragEvent event) { |
Garfield, Tan | 7d66a86 | 2016-05-11 10:28:41 -0700 | [diff] [blame] | 950 | BaseActivity activity = (BaseActivity) getActivity(); |
| 951 | activity.setRootsDrawerOpen(false); |
| 952 | |
Garfield, Tan | 804133e | 2016-04-20 15:13:56 -0700 | [diff] [blame] | 953 | ClipData clipData = event.getClipData(); |
| 954 | assert (clipData != null); |
Vladislav Kaznacheev | b6da722 | 2015-05-01 14:18:57 -0700 | [diff] [blame] | 955 | |
Ben Lin | 30b0dc1 | 2017-03-07 15:37:16 -0800 | [diff] [blame] | 956 | assert(mClipper.getOpType(clipData) == FileOperationService.OPERATION_COPY); |
Garfield, Tan | 804133e | 2016-04-20 15:13:56 -0700 | [diff] [blame] | 957 | |
Ben Lin | d020212 | 2016-11-10 18:00:12 -0800 | [diff] [blame] | 958 | if (!DragAndDropHelper.canCopyTo(event.getLocalState(), getDestination(v))) { |
Vladislav Kaznacheev | b6da722 | 2015-05-01 14:18:57 -0700 | [diff] [blame] | 959 | return false; |
| 960 | } |
Ben Kwa | 13e2605 | 2016-02-18 16:45:45 -0800 | [diff] [blame] | 961 | |
Garfield, Tan | 804133e | 2016-04-20 15:13:56 -0700 | [diff] [blame] | 962 | // 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 Lin | 5a305b4 | 2016-09-08 11:33:07 -0700 | [diff] [blame] | 968 | event.getLocalState() == null ? Metrics.USER_ACTION_DRAG_N_DROP_MULTI_WINDOW |
Garfield, Tan | 804133e | 2016-04-20 15:13:56 -0700 | [diff] [blame] | 969 | : Metrics.USER_ACTION_DRAG_N_DROP); |
Ben Lin | ff4d584 | 2016-04-18 14:35:28 -0700 | [diff] [blame] | 970 | |
Ben Lin | 5a305b4 | 2016-09-08 11:33:07 -0700 | [diff] [blame] | 971 | DocumentInfo dst = getDestination(v); |
Ben Lin | db7a0e7 | 2017-03-02 11:18:22 -0800 | [diff] [blame] | 972 | // If destination is already at top of stack, no need to pass it in |
Garfield Tan | 85a479e | 2017-03-16 11:22:00 -0700 | [diff] [blame] | 973 | if (dst.equals(mState.stack.peek())) { |
Ben Lin | db7a0e7 | 2017-03-02 11:18:22 -0800 | [diff] [blame] | 974 | mClipper.copyFromClipData( |
Ben Lin | db7a0e7 | 2017-03-02 11:18:22 -0800 | [diff] [blame] | 975 | 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, Tan | 804133e | 2016-04-20 15:13:56 -0700 | [diff] [blame] | 985 | return true; |
| 986 | } |
| 987 | |
Ben Lin | d020212 | 2016-11-10 18:00:12 -0800 | [diff] [blame] | 988 | DocumentInfo getDestination(View v) { |
Garfield, Tan | 804133e | 2016-04-20 15:13:56 -0700 | [diff] [blame] | 989 | 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 Lin | ff4d584 | 2016-04-18 14:35:28 -0700 | [diff] [blame] | 995 | } |
Garfield, Tan | 804133e | 2016-04-20 15:13:56 -0700 | [diff] [blame] | 996 | return DocumentInfo.fromDirectoryCursor(dstCursor); |
Ben Lin | ff4d584 | 2016-04-18 14:35:28 -0700 | [diff] [blame] | 997 | } |
| 998 | |
Ben Lin | 7b38f34 | 2016-12-14 11:52:35 -0800 | [diff] [blame] | 999 | if (v == mRecView) { |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 1000 | return mState.stack.peek(); |
Ben Kwa | a8c0da4 | 2016-02-25 14:10:40 -0800 | [diff] [blame] | 1001 | } |
| 1002 | |
Garfield, Tan | 804133e | 2016-04-20 15:13:56 -0700 | [diff] [blame] | 1003 | return null; |
| 1004 | } |
| 1005 | |
| 1006 | @Override |
Ben Lin | 166c5c6 | 2016-11-01 12:14:38 -0700 | [diff] [blame] | 1007 | public void setDropTargetHighlight(View v, Object localState, boolean highlight) { |
Garfield, Tan | 804133e | 2016-04-20 15:13:56 -0700 | [diff] [blame] | 1008 | // 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 Lin | 166c5c6 | 2016-11-01 12:14:38 -0700 | [diff] [blame] | 1011 | DocumentHolder holder = getDocumentHolder(v); |
| 1012 | if (holder != null) { |
| 1013 | if (!highlight) { |
| 1014 | holder.resetDropHighlight(); |
| 1015 | } else { |
Ben Lin | d020212 | 2016-11-10 18:00:12 -0800 | [diff] [blame] | 1016 | holder.setDroppableHighlight( |
| 1017 | DragAndDropHelper.canCopyTo(localState, getDestination(v))); |
Ben Lin | 166c5c6 | 2016-11-01 12:14:38 -0700 | [diff] [blame] | 1018 | } |
Ben Kwa | 13e2605 | 2016-02-18 16:45:45 -0800 | [diff] [blame] | 1019 | } |
| 1020 | } |
Garfield, Tan | 804133e | 2016-04-20 15:13:56 -0700 | [diff] [blame] | 1021 | } |
Vladislav Kaznacheev | b6da722 | 2015-05-01 14:18:57 -0700 | [diff] [blame] | 1022 | |
Ben Kwa | 0497da8 | 2015-11-30 23:00:02 -0800 | [diff] [blame] | 1023 | /** |
| 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 Lin | ebf2a17 | 2016-06-03 13:46:52 -0700 | [diff] [blame] | 1029 | protected @Nullable String getModelId(View view) { |
Ben Kwa | 13e2605 | 2016-02-18 16:45:45 -0800 | [diff] [blame] | 1030 | View itemView = mRecView.findContainingItemView(view); |
| 1031 | if (itemView != null) { |
| 1032 | RecyclerView.ViewHolder vh = mRecView.getChildViewHolder(itemView); |
| 1033 | if (vh instanceof DocumentHolder) { |
Steve McKay | bde20e1 | 2016-09-08 19:12:54 -0700 | [diff] [blame] | 1034 | return ((DocumentHolder) vh).getModelId(); |
Vladislav Kaznacheev | 9400b89 | 2015-09-04 09:17:37 -0700 | [diff] [blame] | 1035 | } |
Vladislav Kaznacheev | 9400b89 | 2015-09-04 09:17:37 -0700 | [diff] [blame] | 1036 | } |
Ben Kwa | 13e2605 | 2016-02-18 16:45:45 -0800 | [diff] [blame] | 1037 | return null; |
Vladislav Kaznacheev | 9400b89 | 2015-09-04 09:17:37 -0700 | [diff] [blame] | 1038 | } |
| 1039 | |
Ben Lin | 166c5c6 | 2016-11-01 12:14:38 -0700 | [diff] [blame] | 1040 | 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 McKay | 4f78ba6 | 2016-10-04 16:48:49 -0700 | [diff] [blame] | 1048 | // TODO: Move to activities when Model becomes activity level object. |
Steve McKay | 74956af | 2016-06-30 21:03:06 -0700 | [diff] [blame] | 1049 | private boolean canSelect(DocumentDetails doc) { |
Garfield Tan | 84bd0f1 | 2016-09-12 14:18:32 -0700 | [diff] [blame] | 1050 | return canSetSelectionState(doc.getModelId(), true); |
Steve McKay | 74956af | 2016-06-30 21:03:06 -0700 | [diff] [blame] | 1051 | } |
| 1052 | |
Steve McKay | 4f78ba6 | 2016-10-04 16:48:49 -0700 | [diff] [blame] | 1053 | // TODO: Move to activities when Model becomes activity level object. |
Garfield Tan | 84bd0f1 | 2016-09-12 14:18:32 -0700 | [diff] [blame] | 1054 | 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 McKay | f794489 | 2016-01-21 15:10:39 -0800 | [diff] [blame] | 1062 | |
Garfield Tan | 84bd0f1 | 2016-09-12 14:18:32 -0700 | [diff] [blame] | 1063 | final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); |
| 1064 | final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS); |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 1065 | return mInjector.config.canSelectType(docMimeType, docFlags, mState); |
Garfield Tan | 84bd0f1 | 2016-09-12 14:18:32 -0700 | [diff] [blame] | 1066 | } else { |
Tomasz Mikolajewski | 26640ab | 2017-02-06 10:37:59 +0900 | [diff] [blame] | 1067 | final DocumentInfo parent = mState.stack.peek(); |
Garfield Tan | 84bd0f1 | 2016-09-12 14:18:32 -0700 | [diff] [blame] | 1068 | // Right now all selected items can be deselected. |
| 1069 | return true; |
Steve McKay | f794489 | 2016-01-21 15:10:39 -0800 | [diff] [blame] | 1070 | } |
Steve McKay | f794489 | 2016-01-21 15:10:39 -0800 | [diff] [blame] | 1071 | } |
| 1072 | |
Steve McKay | 096c78b | 2016-01-21 18:46:15 -0800 | [diff] [blame] | 1073 | public static void showDirectory( |
| 1074 | FragmentManager fm, RootInfo root, DocumentInfo doc, int anim) { |
Steve McKay | 7c66209 | 2016-08-26 12:17:41 -0700 | [diff] [blame] | 1075 | if (DEBUG) Log.d(TAG, "Showing directory: " + DocumentInfo.debugString(doc)); |
Garfield Tan | e967033 | 2017-03-06 18:33:23 -0800 | [diff] [blame] | 1076 | create(fm, root, doc, anim); |
Steve McKay | 096c78b | 2016-01-21 18:46:15 -0800 | [diff] [blame] | 1077 | } |
| 1078 | |
| 1079 | public static void showRecentsOpen(FragmentManager fm, int anim) { |
Garfield Tan | e967033 | 2017-03-06 18:33:23 -0800 | [diff] [blame] | 1080 | create(fm, null, null, anim); |
Aga Wronska | af5ace5 | 2016-02-17 13:50:42 -0800 | [diff] [blame] | 1081 | } |
| 1082 | |
Steve McKay | 7c66209 | 2016-08-26 12:17:41 -0700 | [diff] [blame] | 1083 | public static void create( |
| 1084 | FragmentManager fm, |
Steve McKay | 7c66209 | 2016-08-26 12:17:41 -0700 | [diff] [blame] | 1085 | RootInfo root, |
| 1086 | @Nullable DocumentInfo doc, |
Steve McKay | 988d8a3 | 2016-09-27 09:41:17 -0700 | [diff] [blame] | 1087 | @AnimationType int anim) { |
Steve McKay | 7c66209 | 2016-08-26 12:17:41 -0700 | [diff] [blame] | 1088 | |
| 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 McKay | 096c78b | 2016-01-21 18:46:15 -0800 | [diff] [blame] | 1097 | final Bundle args = new Bundle(); |
Aga Wronska | af5ace5 | 2016-02-17 13:50:42 -0800 | [diff] [blame] | 1098 | args.putParcelable(Shared.EXTRA_ROOT, root); |
| 1099 | args.putParcelable(Shared.EXTRA_DOC, doc); |
Aga Wronska | 7e5b963 | 2016-02-26 11:36:07 -0800 | [diff] [blame] | 1100 | args.putParcelable(Shared.EXTRA_SELECTION, new Selection()); |
Steve McKay | 096c78b | 2016-01-21 18:46:15 -0800 | [diff] [blame] | 1101 | |
| 1102 | final FragmentTransaction ft = fm.beginTransaction(); |
Steve McKay | fb4fd2f | 2016-03-11 10:49:32 -0800 | [diff] [blame] | 1103 | AnimationView.setupAnimations(ft, anim, args); |
Steve McKay | 096c78b | 2016-01-21 18:46:15 -0800 | [diff] [blame] | 1104 | |
| 1105 | final DirectoryFragment fragment = new DirectoryFragment(); |
| 1106 | fragment.setArguments(args); |
| 1107 | |
Aga Wronska | af5ace5 | 2016-02-17 13:50:42 -0800 | [diff] [blame] | 1108 | ft.replace(getFragmentId(), fragment); |
Steve McKay | 096c78b | 2016-01-21 18:46:15 -0800 | [diff] [blame] | 1109 | ft.commitAllowingStateLoss(); |
| 1110 | } |
| 1111 | |
Steve McKay | 096c78b | 2016-01-21 18:46:15 -0800 | [diff] [blame] | 1112 | public static @Nullable DirectoryFragment get(FragmentManager fm) { |
| 1113 | // TODO: deal with multiple directories shown at once |
Aga Wronska | af5ace5 | 2016-02-17 13:50:42 -0800 | [diff] [blame] | 1114 | Fragment fragment = fm.findFragmentById(getFragmentId()); |
Steve McKay | 096c78b | 2016-01-21 18:46:15 -0800 | [diff] [blame] | 1115 | return fragment instanceof DirectoryFragment |
| 1116 | ? (DirectoryFragment) fragment |
| 1117 | : null; |
| 1118 | } |
Aga Wronska | af5ace5 | 2016-02-17 13:50:42 -0800 | [diff] [blame] | 1119 | |
| 1120 | private static int getFragmentId() { |
| 1121 | return R.id.container_directory; |
| 1122 | } |
| 1123 | |
| 1124 | @Override |
Garfield, Tan | ca7c088 | 2016-07-18 16:45:27 -0700 | [diff] [blame] | 1125 | public void onRefresh() { |
Garfield, Tan | eba5bb9 | 2016-07-22 10:30:49 -0700 | [diff] [blame] | 1126 | // 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 Lin | e9abd2d | 2016-12-06 11:39:52 -0800 | [diff] [blame] | 1136 | 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 Tan | e967033 | 2017-03-06 18:33:23 -0800 | [diff] [blame] | 1142 | mActions.loadDocumentsForCurrentStack(); |
Ben Lin | e9abd2d | 2016-12-06 11:39:52 -0800 | [diff] [blame] | 1143 | } |
| 1144 | }); |
Garfield, Tan | ca7c088 | 2016-07-18 16:45:27 -0700 | [diff] [blame] | 1145 | } |
| 1146 | |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 1147 | private final class ModelUpdateListener implements EventListener<Model.Update> { |
Aga Wronska | af5ace5 | 2016-02-17 13:50:42 -0800 | [diff] [blame] | 1148 | |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 1149 | @Override |
| 1150 | public void accept(Model.Update update) { |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 1151 | if (DEBUG) Log.d(TAG, "Received model update. Loading=" + mModel.isLoading()); |
| 1152 | |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 1153 | mProgressBar.setVisibility(mModel.isLoading() ? View.VISIBLE : View.GONE); |
| 1154 | |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 1155 | updateLayout(mState.derivedMode); |
| 1156 | |
Garfield Tan | e967033 | 2017-03-06 18:33:23 -0800 | [diff] [blame] | 1157 | mAdapter.notifyDataSetChanged(); |
| 1158 | |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 1159 | 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 Lin | 6537acd | 2016-11-16 12:06:21 -0800 | [diff] [blame] | 1186 | if (mRefreshLayout.isRefreshing()) { |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 1187 | new Handler().postDelayed( |
| 1188 | () -> mRefreshLayout.setRefreshing(false), |
Ben Lin | bbb7d03 | 2016-11-15 13:35:41 -0800 | [diff] [blame] | 1189 | REFRESH_SPINNER_TIMEOUT); |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 1190 | } |
Garfield Tan | e967033 | 2017-03-06 18:33:23 -0800 | [diff] [blame] | 1191 | |
| 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 McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 1204 | } |
| 1205 | |
| 1206 | @Override |
Garfield Tan | e967033 | 2017-03-06 18:33:23 -0800 | [diff] [blame] | 1207 | public Context getContext() { |
| 1208 | return mActivity; |
| 1209 | } |
Steve McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 1210 | |
Garfield Tan | e967033 | 2017-03-06 18:33:23 -0800 | [diff] [blame] | 1211 | @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 McKay | d071895 | 2016-10-10 13:43:36 -0700 | [diff] [blame] | 1250 | } |
Aga Wronska | af5ace5 | 2016-02-17 13:50:42 -0800 | [diff] [blame] | 1251 | } |
Steve McKay | c8f165c | 2016-09-20 13:54:05 -0700 | [diff] [blame] | 1252 | } |