Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -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 | d080506 | 2016-09-15 14:30:38 -0700 | [diff] [blame] | 17 | package com.android.documentsui.sidebar; |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 18 | |
Steve McKay | d9caa6a | 2016-09-15 16:36:45 -0700 | [diff] [blame] | 19 | import static com.android.documentsui.base.Shared.DEBUG; |
Steve McKay | 30535bc | 2016-11-04 14:16:58 -0700 | [diff] [blame] | 20 | import static com.android.documentsui.base.Shared.VERBOSE; |
Steve McKay | cc460af | 2015-11-03 14:00:45 -0800 | [diff] [blame] | 21 | |
Garfield Tan | 7d75f7b | 2016-09-20 16:33:24 -0700 | [diff] [blame] | 22 | import android.annotation.Nullable; |
Steve McKay | 17f7e58 | 2016-04-04 15:26:48 -0700 | [diff] [blame] | 23 | import android.app.Activity; |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 24 | import android.app.Fragment; |
| 25 | import android.app.FragmentManager; |
| 26 | import android.app.FragmentTransaction; |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 27 | import android.app.LoaderManager.LoaderCallbacks; |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 28 | import android.content.Context; |
Jeff Sharkey | 1d890e0 | 2013-08-15 11:24:03 -0700 | [diff] [blame] | 29 | import android.content.Intent; |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 30 | import android.content.Loader; |
Jeff Sharkey | 1d890e0 | 2013-08-15 11:24:03 -0700 | [diff] [blame] | 31 | import android.content.pm.PackageManager; |
| 32 | import android.content.pm.ResolveInfo; |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 33 | import android.os.Bundle; |
Steve McKay | cc460af | 2015-11-03 14:00:45 -0800 | [diff] [blame] | 34 | import android.util.Log; |
Ben Lin | 5668b86 | 2016-06-28 17:12:52 -0700 | [diff] [blame] | 35 | import android.view.ContextMenu; |
Garfield, Tan | a5588b6 | 2016-07-13 09:23:04 -0700 | [diff] [blame] | 36 | import android.view.DragEvent; |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 37 | import android.view.LayoutInflater; |
Ben Lin | 5668b86 | 2016-06-28 17:12:52 -0700 | [diff] [blame] | 38 | import android.view.MenuItem; |
| 39 | import android.view.MotionEvent; |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 40 | import android.view.View; |
Garfield, Tan | 804133e | 2016-04-20 15:13:56 -0700 | [diff] [blame] | 41 | import android.view.View.OnDragListener; |
Ben Lin | 5668b86 | 2016-06-28 17:12:52 -0700 | [diff] [blame] | 42 | import android.view.View.OnGenericMotionListener; |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 43 | import android.view.ViewGroup; |
| 44 | import android.widget.AdapterView; |
Ben Lin | 5668b86 | 2016-06-28 17:12:52 -0700 | [diff] [blame] | 45 | import android.widget.AdapterView.AdapterContextMenuInfo; |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 46 | import android.widget.AdapterView.OnItemClickListener; |
Jeff Sharkey | 8f1edfe | 2013-10-23 16:05:56 -0700 | [diff] [blame] | 47 | import android.widget.AdapterView.OnItemLongClickListener; |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 48 | import android.widget.ListView; |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 49 | |
Steve McKay | 6d20d19 | 2016-09-21 17:57:10 -0700 | [diff] [blame] | 50 | import com.android.documentsui.ActionHandler; |
Steve McKay | d080506 | 2016-09-15 14:30:38 -0700 | [diff] [blame] | 51 | import com.android.documentsui.BaseActivity; |
| 52 | import com.android.documentsui.DocumentsApplication; |
Ben Lin | d020212 | 2016-11-10 18:00:12 -0800 | [diff] [blame] | 53 | import com.android.documentsui.DragAndDropHelper; |
Ben Lin | b0761d3 | 2017-02-16 11:04:04 -0800 | [diff] [blame] | 54 | import com.android.documentsui.DragShadowBuilder; |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 55 | import com.android.documentsui.Injector; |
| 56 | import com.android.documentsui.Injector.Injected; |
Steve McKay | d080506 | 2016-09-15 14:30:38 -0700 | [diff] [blame] | 57 | import com.android.documentsui.ItemDragListener; |
Steve McKay | d080506 | 2016-09-15 14:30:38 -0700 | [diff] [blame] | 58 | import com.android.documentsui.R; |
Ben Lin | 30b0dc1 | 2017-03-07 15:37:16 -0800 | [diff] [blame] | 59 | import com.android.documentsui.TimeoutTask; |
Steve McKay | 739f94b | 2016-09-22 14:54:23 -0700 | [diff] [blame] | 60 | import com.android.documentsui.base.BooleanConsumer; |
Ben Lin | 8e91258 | 2016-09-23 14:25:14 -0700 | [diff] [blame] | 61 | import com.android.documentsui.base.DocumentInfo; |
Steve McKay | 739f94b | 2016-09-22 14:54:23 -0700 | [diff] [blame] | 62 | import com.android.documentsui.base.DocumentStack; |
Steve McKay | d9caa6a | 2016-09-15 16:36:45 -0700 | [diff] [blame] | 63 | import com.android.documentsui.base.Events; |
Steve McKay | d080506 | 2016-09-15 14:30:38 -0700 | [diff] [blame] | 64 | import com.android.documentsui.base.RootInfo; |
Steve McKay | d9caa6a | 2016-09-15 16:36:45 -0700 | [diff] [blame] | 65 | import com.android.documentsui.base.Shared; |
| 66 | import com.android.documentsui.base.State; |
Steve McKay | def4868 | 2016-10-03 09:07:38 -0700 | [diff] [blame] | 67 | import com.android.documentsui.roots.GetRootDocumentTask; |
Steve McKay | d9caa6a | 2016-09-15 16:36:45 -0700 | [diff] [blame] | 68 | import com.android.documentsui.roots.RootsCache; |
| 69 | import com.android.documentsui.roots.RootsLoader; |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 70 | |
Steve McKay | fefcd70 | 2015-08-20 16:19:38 +0000 | [diff] [blame] | 71 | import java.util.ArrayList; |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 72 | import java.util.Collection; |
Jeff Sharkey | 9dd0262 | 2013-09-27 16:44:11 -0700 | [diff] [blame] | 73 | import java.util.Collections; |
Jeff Sharkey | b6a7f2c | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 74 | import java.util.Comparator; |
Jeff Sharkey | 1d890e0 | 2013-08-15 11:24:03 -0700 | [diff] [blame] | 75 | import java.util.List; |
Kenny Root | 16d8598 | 2013-12-13 12:00:26 -0800 | [diff] [blame] | 76 | import java.util.Objects; |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 77 | |
| 78 | /** |
| 79 | * Display list of known storage backend roots. |
| 80 | */ |
Garfield, Tan | 804133e | 2016-04-20 15:13:56 -0700 | [diff] [blame] | 81 | public class RootsFragment extends Fragment implements ItemDragListener.DragHost { |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 82 | |
Steve McKay | cc460af | 2015-11-03 14:00:45 -0800 | [diff] [blame] | 83 | private static final String TAG = "RootsFragment"; |
| 84 | private static final String EXTRA_INCLUDE_APPS = "includeApps"; |
Ben Lin | 8e91258 | 2016-09-23 14:25:14 -0700 | [diff] [blame] | 85 | private static final int CONTEXT_MENU_ITEM_TIMEOUT = 500; |
Steve McKay | cc460af | 2015-11-03 14:00:45 -0800 | [diff] [blame] | 86 | |
Garfield Tan | 7d75f7b | 2016-09-20 16:33:24 -0700 | [diff] [blame] | 87 | private final OnItemClickListener mItemListener = new OnItemClickListener() { |
| 88 | @Override |
| 89 | public void onItemClick(AdapterView<?> parent, View view, int position, long id) { |
| 90 | final Item item = mAdapter.getItem(position); |
| 91 | item.open(); |
| 92 | |
| 93 | getBaseActivity().setRootsDrawerOpen(false); |
| 94 | } |
| 95 | }; |
| 96 | |
| 97 | private final OnItemLongClickListener mItemLongClickListener = new OnItemLongClickListener() { |
| 98 | @Override |
| 99 | public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { |
| 100 | final Item item = mAdapter.getItem(position); |
Garfield Tan | b285b40 | 2016-09-21 17:12:18 -0700 | [diff] [blame] | 101 | return item.showAppDetails(); |
Garfield, Tan | a5588b6 | 2016-07-13 09:23:04 -0700 | [diff] [blame] | 102 | } |
| 103 | }; |
| 104 | |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 105 | private ListView mList; |
Jeff Sharkey | 9dd0262 | 2013-09-27 16:44:11 -0700 | [diff] [blame] | 106 | private RootsAdapter mAdapter; |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 107 | private LoaderCallbacks<Collection<RootInfo>> mCallbacks; |
Garfield Tan | 208945c | 2016-10-04 14:36:38 -0700 | [diff] [blame] | 108 | private @Nullable OnDragListener mDragListener; |
| 109 | |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 110 | @Injected |
| 111 | private Injector<?> mInjector; |
Ben Lin | 821727a | 2016-07-08 10:49:53 -0700 | [diff] [blame] | 112 | |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 113 | @Injected |
| 114 | private ActionHandler mActionHandler; |
Garfield Tan | 208945c | 2016-10-04 14:36:38 -0700 | [diff] [blame] | 115 | |
Ben Lin | 821727a | 2016-07-08 10:49:53 -0700 | [diff] [blame] | 116 | public static RootsFragment show(FragmentManager fm, Intent includeApps) { |
Jeff Sharkey | 1d890e0 | 2013-08-15 11:24:03 -0700 | [diff] [blame] | 117 | final Bundle args = new Bundle(); |
| 118 | args.putParcelable(EXTRA_INCLUDE_APPS, includeApps); |
| 119 | |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 120 | final RootsFragment fragment = new RootsFragment(); |
Jeff Sharkey | 1d890e0 | 2013-08-15 11:24:03 -0700 | [diff] [blame] | 121 | fragment.setArguments(args); |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 122 | |
| 123 | final FragmentTransaction ft = fm.beginTransaction(); |
| 124 | ft.replace(R.id.container_roots, fragment); |
| 125 | ft.commitAllowingStateLoss(); |
Ben Lin | 821727a | 2016-07-08 10:49:53 -0700 | [diff] [blame] | 126 | |
| 127 | return fragment; |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 128 | } |
| 129 | |
| 130 | public static RootsFragment get(FragmentManager fm) { |
| 131 | return (RootsFragment) fm.findFragmentById(R.id.container_roots); |
| 132 | } |
| 133 | |
| 134 | @Override |
| 135 | public View onCreateView( |
| 136 | LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 137 | |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 138 | mInjector = getBaseActivity().getInjector(); |
| 139 | |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 140 | final View view = inflater.inflate(R.layout.fragment_roots, container, false); |
Ben Kwa | 74e5d41 | 2016-02-10 07:46:35 -0800 | [diff] [blame] | 141 | mList = (ListView) view.findViewById(R.id.roots_list); |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 142 | mList.setOnItemClickListener(mItemListener); |
Ben Lin | 821727a | 2016-07-08 10:49:53 -0700 | [diff] [blame] | 143 | // ListView does not have right-click specific listeners, so we will have a |
| 144 | // GenericMotionListener to listen for it. |
| 145 | // Currently, right click is viewed the same as long press, so we will have to quickly |
| 146 | // register for context menu when we receive a right click event, and quickly unregister |
| 147 | // it afterwards to prevent context menus popping up upon long presses. |
| 148 | // All other motion events will then get passed to OnItemClickListener. |
Ben Lin | 5668b86 | 2016-06-28 17:12:52 -0700 | [diff] [blame] | 149 | mList.setOnGenericMotionListener( |
| 150 | new OnGenericMotionListener() { |
Ben Lin | 8e91258 | 2016-09-23 14:25:14 -0700 | [diff] [blame] | 151 | @Override |
| 152 | public boolean onGenericMotion(View v, MotionEvent event) { |
| 153 | if (Events.isMouseEvent(event) |
| 154 | && event.getButtonState() == MotionEvent.BUTTON_SECONDARY) { |
| 155 | int x = (int) event.getX(); |
| 156 | int y = (int) event.getY(); |
| 157 | return onRightClick(v, x, y, () -> { |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 158 | mInjector.menuManager.showContextMenu( |
| 159 | RootsFragment.this, v, x, y); |
Ben Lin | 8e91258 | 2016-09-23 14:25:14 -0700 | [diff] [blame] | 160 | }); |
| 161 | } |
| 162 | return false; |
Ben Lin | 5668b86 | 2016-06-28 17:12:52 -0700 | [diff] [blame] | 163 | } |
| 164 | }); |
Jeff Sharkey | 42d2679 | 2013-09-06 13:22:09 -0700 | [diff] [blame] | 165 | mList.setChoiceMode(ListView.CHOICE_MODE_SINGLE); |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 166 | return view; |
| 167 | } |
| 168 | |
Ben Lin | 8e91258 | 2016-09-23 14:25:14 -0700 | [diff] [blame] | 169 | private boolean onRightClick(View v, int x, int y, Runnable callback) { |
Ben Lin | d020212 | 2016-11-10 18:00:12 -0800 | [diff] [blame] | 170 | final int pos = mList.pointToPosition(x, y); |
Ben Lin | 8e91258 | 2016-09-23 14:25:14 -0700 | [diff] [blame] | 171 | final Item item = mAdapter.getItem(pos); |
Ben Lin | d020212 | 2016-11-10 18:00:12 -0800 | [diff] [blame] | 172 | |
| 173 | // If a read-only root, no need to see if top level is writable (it's not) |
| 174 | if (!(item instanceof RootItem) || !((RootItem) item).root.supportsCreate()) { |
Ben Lin | 8e91258 | 2016-09-23 14:25:14 -0700 | [diff] [blame] | 175 | return false; |
| 176 | } |
Ben Lin | d020212 | 2016-11-10 18:00:12 -0800 | [diff] [blame] | 177 | |
Ben Lin | 8e91258 | 2016-09-23 14:25:14 -0700 | [diff] [blame] | 178 | final RootItem rootItem = (RootItem) item; |
Ben Lin | d020212 | 2016-11-10 18:00:12 -0800 | [diff] [blame] | 179 | getRootDocument(rootItem, (DocumentInfo doc) -> { |
| 180 | rootItem.docInfo = doc; |
Ben Lin | 8e91258 | 2016-09-23 14:25:14 -0700 | [diff] [blame] | 181 | callback.run(); |
Ben Lin | d020212 | 2016-11-10 18:00:12 -0800 | [diff] [blame] | 182 | }); |
Ben Lin | 8e91258 | 2016-09-23 14:25:14 -0700 | [diff] [blame] | 183 | return true; |
| 184 | } |
| 185 | |
Jeff Sharkey | c6cbdf1 | 2013-08-07 16:22:02 -0700 | [diff] [blame] | 186 | @Override |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 187 | public void onActivityCreated(Bundle savedInstanceState) { |
| 188 | super.onActivityCreated(savedInstanceState); |
| 189 | |
Garfield Tan | 7d75f7b | 2016-09-20 16:33:24 -0700 | [diff] [blame] | 190 | final BaseActivity activity = getBaseActivity(); |
| 191 | final RootsCache roots = DocumentsApplication.getRootsCache(activity); |
| 192 | final State state = activity.getDisplayState(); |
| 193 | |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 194 | mActionHandler = mInjector.actions; |
Garfield Tan | 208945c | 2016-10-04 14:36:38 -0700 | [diff] [blame] | 195 | |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 196 | if (mInjector.config.dragAndDropEnabled()) { |
Garfield Tan | 208945c | 2016-10-04 14:36:38 -0700 | [diff] [blame] | 197 | mDragListener = new ItemDragListener<RootsFragment>(this) { |
| 198 | @Override |
| 199 | public boolean handleDropEventChecked(View v, DragEvent event) { |
| 200 | final int position = (Integer) v.getTag(R.id.item_position_tag); |
| 201 | final Item item = mAdapter.getItem(position); |
| 202 | |
| 203 | assert (item.isDropTarget()); |
| 204 | |
Ben Lin | 174fc2e | 2017-03-01 17:53:20 -0800 | [diff] [blame] | 205 | return item.dropOn(event); |
Garfield Tan | 208945c | 2016-10-04 14:36:38 -0700 | [diff] [blame] | 206 | } |
| 207 | }; |
| 208 | } |
| 209 | |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 210 | mCallbacks = new LoaderCallbacks<Collection<RootInfo>>() { |
| 211 | @Override |
| 212 | public Loader<Collection<RootInfo>> onCreateLoader(int id, Bundle args) { |
Garfield Tan | 7d75f7b | 2016-09-20 16:33:24 -0700 | [diff] [blame] | 213 | return new RootsLoader(activity, roots, state); |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 214 | } |
| 215 | |
| 216 | @Override |
| 217 | public void onLoadFinished( |
| 218 | Loader<Collection<RootInfo>> loader, Collection<RootInfo> result) { |
Steve McKay | 323dffb | 2016-02-17 18:25:47 -0800 | [diff] [blame] | 219 | if (!isAdded()) { |
| 220 | return; |
| 221 | } |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 222 | |
Steve McKay | 323dffb | 2016-02-17 18:25:47 -0800 | [diff] [blame] | 223 | Intent handlerAppIntent = getArguments().getParcelable(EXTRA_INCLUDE_APPS); |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 224 | |
Garfield Tan | 7d75f7b | 2016-09-20 16:33:24 -0700 | [diff] [blame] | 225 | List<Item> sortedItems = sortLoadResult(result, handlerAppIntent); |
| 226 | mAdapter = new RootsAdapter(activity, sortedItems, mDragListener); |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 227 | mList.setAdapter(mAdapter); |
| 228 | |
| 229 | onCurrentRootChanged(); |
| 230 | } |
| 231 | |
| 232 | @Override |
| 233 | public void onLoaderReset(Loader<Collection<RootInfo>> loader) { |
| 234 | mAdapter = null; |
| 235 | mList.setAdapter(null); |
| 236 | } |
| 237 | }; |
Jeff Sharkey | 1c903cc | 2013-09-02 17:19:40 -0700 | [diff] [blame] | 238 | } |
Jeff Sharkey | c6cbdf1 | 2013-08-07 16:22:02 -0700 | [diff] [blame] | 239 | |
Garfield Tan | 7d75f7b | 2016-09-20 16:33:24 -0700 | [diff] [blame] | 240 | /** |
| 241 | * @param handlerAppIntent When not null, apps capable of handling the original intent will |
| 242 | * be included in list of roots (in special section at bottom). |
| 243 | */ |
| 244 | private List<Item> sortLoadResult( |
| 245 | Collection<RootInfo> roots, @Nullable Intent handlerAppIntent) { |
| 246 | final List<Item> result = new ArrayList<>(); |
| 247 | |
| 248 | final List<RootItem> libraries = new ArrayList<>(); |
| 249 | final List<RootItem> others = new ArrayList<>(); |
| 250 | |
| 251 | for (final RootInfo root : roots) { |
| 252 | final RootItem item = new RootItem(root, mActionHandler); |
| 253 | |
| 254 | Activity activity = getActivity(); |
Steve McKay | a0b5265 | 2016-10-27 12:56:55 -0700 | [diff] [blame] | 255 | if (root.isHome() && !Shared.shouldShowDocumentsRoot(activity)) { |
Garfield Tan | 7d75f7b | 2016-09-20 16:33:24 -0700 | [diff] [blame] | 256 | continue; |
| 257 | } else if (root.isLibrary()) { |
| 258 | libraries.add(item); |
| 259 | } else { |
| 260 | others.add(item); |
| 261 | } |
| 262 | } |
| 263 | |
| 264 | final RootComparator comp = new RootComparator(); |
| 265 | Collections.sort(libraries, comp); |
| 266 | Collections.sort(others, comp); |
| 267 | |
Steve McKay | 30535bc | 2016-11-04 14:16:58 -0700 | [diff] [blame] | 268 | if (VERBOSE) Log.v(TAG, "Adding library roots: " + libraries); |
Garfield Tan | 7d75f7b | 2016-09-20 16:33:24 -0700 | [diff] [blame] | 269 | result.addAll(libraries); |
| 270 | // Only add the spacer if it is actually separating something. |
| 271 | if (!libraries.isEmpty() && !others.isEmpty()) { |
| 272 | result.add(new SpacerItem()); |
| 273 | } |
| 274 | |
Steve McKay | 30535bc | 2016-11-04 14:16:58 -0700 | [diff] [blame] | 275 | if (VERBOSE) Log.v(TAG, "Adding plain roots: " + libraries); |
Garfield Tan | 7d75f7b | 2016-09-20 16:33:24 -0700 | [diff] [blame] | 276 | result.addAll(others); |
| 277 | |
| 278 | // Include apps that can handle this intent too. |
| 279 | if (handlerAppIntent != null) { |
| 280 | includeHandlerApps(handlerAppIntent, result); |
| 281 | } |
| 282 | |
| 283 | return result; |
| 284 | } |
| 285 | |
| 286 | /** |
| 287 | * Adds apps capable of handling the original intent will be included in list of roots (in |
| 288 | * special section at bottom). |
| 289 | */ |
| 290 | private void includeHandlerApps(Intent handlerAppIntent, List<Item> result) { |
Steve McKay | 30535bc | 2016-11-04 14:16:58 -0700 | [diff] [blame] | 291 | if (VERBOSE) Log.v(TAG, "Adding handler apps for intent: " + handlerAppIntent); |
Garfield Tan | 7d75f7b | 2016-09-20 16:33:24 -0700 | [diff] [blame] | 292 | Context context = getContext(); |
| 293 | final PackageManager pm = context.getPackageManager(); |
| 294 | final List<ResolveInfo> infos = pm.queryIntentActivities( |
| 295 | handlerAppIntent, PackageManager.MATCH_DEFAULT_ONLY); |
| 296 | |
| 297 | final List<AppItem> apps = new ArrayList<>(); |
| 298 | |
| 299 | // Omit ourselves from the list |
| 300 | for (ResolveInfo info : infos) { |
| 301 | if (!context.getPackageName().equals(info.activityInfo.packageName)) { |
| 302 | apps.add(new AppItem(info, mActionHandler)); |
| 303 | } |
| 304 | } |
| 305 | |
| 306 | if (apps.size() > 0) { |
| 307 | result.add(new SpacerItem()); |
| 308 | result.addAll(apps); |
| 309 | } |
| 310 | } |
| 311 | |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 312 | @Override |
| 313 | public void onResume() { |
| 314 | super.onResume(); |
Jeff Sharkey | e8d13ea | 2014-08-08 15:10:03 -0700 | [diff] [blame] | 315 | onDisplayStateChanged(); |
| 316 | } |
Jeff Sharkey | 1c903cc | 2013-09-02 17:19:40 -0700 | [diff] [blame] | 317 | |
Jeff Sharkey | e8d13ea | 2014-08-08 15:10:03 -0700 | [diff] [blame] | 318 | public void onDisplayStateChanged() { |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 319 | final Context context = getActivity(); |
Steve McKay | d0a2a2c | 2015-03-25 14:35:33 -0700 | [diff] [blame] | 320 | final State state = ((BaseActivity) context).getDisplayState(); |
Jeff Sharkey | 1c903cc | 2013-09-02 17:19:40 -0700 | [diff] [blame] | 321 | |
Steve McKay | d0a2a2c | 2015-03-25 14:35:33 -0700 | [diff] [blame] | 322 | if (state.action == State.ACTION_GET_CONTENT) { |
Jeff Sharkey | 8f1edfe | 2013-10-23 16:05:56 -0700 | [diff] [blame] | 323 | mList.setOnItemLongClickListener(mItemLongClickListener); |
| 324 | } else { |
| 325 | mList.setOnItemLongClickListener(null); |
| 326 | mList.setLongClickable(false); |
| 327 | } |
| 328 | |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 329 | getLoaderManager().restartLoader(2, null, mCallbacks); |
Jeff Sharkey | 42d2679 | 2013-09-06 13:22:09 -0700 | [diff] [blame] | 330 | } |
| 331 | |
| 332 | public void onCurrentRootChanged() { |
Steve McKay | 323dffb | 2016-02-17 18:25:47 -0800 | [diff] [blame] | 333 | if (mAdapter == null) { |
| 334 | return; |
| 335 | } |
Jeff Sharkey | 42d2679 | 2013-09-06 13:22:09 -0700 | [diff] [blame] | 336 | |
Steve McKay | d0a2a2c | 2015-03-25 14:35:33 -0700 | [diff] [blame] | 337 | final RootInfo root = ((BaseActivity) getActivity()).getCurrentRoot(); |
Jeff Sharkey | 42d2679 | 2013-09-06 13:22:09 -0700 | [diff] [blame] | 338 | for (int i = 0; i < mAdapter.getCount(); i++) { |
| 339 | final Object item = mAdapter.getItem(i); |
Jeff Sharkey | 25f10b3 | 2013-10-07 14:08:17 -0700 | [diff] [blame] | 340 | if (item instanceof RootItem) { |
| 341 | final RootInfo testRoot = ((RootItem) item).root; |
Kenny Root | 16d8598 | 2013-12-13 12:00:26 -0800 | [diff] [blame] | 342 | if (Objects.equals(testRoot, root)) { |
Jeff Sharkey | 25f10b3 | 2013-10-07 14:08:17 -0700 | [diff] [blame] | 343 | mList.setItemChecked(i, true); |
| 344 | return; |
| 345 | } |
Jeff Sharkey | 42d2679 | 2013-09-06 13:22:09 -0700 | [diff] [blame] | 346 | } |
| 347 | } |
Jeff Sharkey | c6cbdf1 | 2013-08-07 16:22:02 -0700 | [diff] [blame] | 348 | } |
| 349 | |
Ben Kwa | 74e5d41 | 2016-02-10 07:46:35 -0800 | [diff] [blame] | 350 | /** |
| 351 | * Attempts to shift focus back to the navigation drawer. |
| 352 | */ |
Ben Lin | d947f01 | 2016-10-18 14:32:49 -0700 | [diff] [blame] | 353 | public boolean requestFocus() { |
| 354 | return mList.requestFocus(); |
Ben Kwa | 74e5d41 | 2016-02-10 07:46:35 -0800 | [diff] [blame] | 355 | } |
| 356 | |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 357 | private BaseActivity getBaseActivity() { |
| 358 | return (BaseActivity) getActivity(); |
Ben Lin | 5668b86 | 2016-06-28 17:12:52 -0700 | [diff] [blame] | 359 | } |
| 360 | |
Garfield, Tan | 804133e | 2016-04-20 15:13:56 -0700 | [diff] [blame] | 361 | @Override |
| 362 | public void runOnUiThread(Runnable runnable) { |
| 363 | getActivity().runOnUiThread(runnable); |
| 364 | } |
| 365 | |
Ben Lin | d020212 | 2016-11-10 18:00:12 -0800 | [diff] [blame] | 366 | // In RootsFragment, we check whether the item corresponds to a RootItem, and whether |
| 367 | // the currently dragged objects can be droppable or not, and change the drop-shadow |
| 368 | // accordingly |
Ben Lin | 7f72a3c | 2016-09-27 16:37:28 -0700 | [diff] [blame] | 369 | @Override |
Ben Lin | 1c45629 | 2016-10-07 16:43:18 -0700 | [diff] [blame] | 370 | public void onDragEntered(View v, Object localState) { |
Ben Lin | d020212 | 2016-11-10 18:00:12 -0800 | [diff] [blame] | 371 | final int pos = (Integer) v.getTag(R.id.item_position_tag); |
| 372 | final Item item = mAdapter.getItem(pos); |
| 373 | |
| 374 | // If a read-only root, no need to see if top level is writable (it's not) |
| 375 | if (!(item instanceof RootItem) || !((RootItem) item).root.supportsCreate()) { |
| 376 | getBaseActivity().getShadowBuilder().setAppearDroppable(false); |
| 377 | v.updateDragShadow(getBaseActivity().getShadowBuilder()); |
| 378 | return; |
| 379 | } |
| 380 | |
| 381 | final RootItem rootItem = (RootItem) item; |
| 382 | getRootDocument(rootItem, (DocumentInfo doc) -> { |
Ben Lin | b0761d3 | 2017-02-16 11:04:04 -0800 | [diff] [blame] | 383 | updateDropShadow(v, localState, rootItem, doc); |
Ben Lin | d020212 | 2016-11-10 18:00:12 -0800 | [diff] [blame] | 384 | }); |
Ben Lin | 7f72a3c | 2016-09-27 16:37:28 -0700 | [diff] [blame] | 385 | } |
| 386 | |
Ben Lin | b0761d3 | 2017-02-16 11:04:04 -0800 | [diff] [blame] | 387 | private void updateDropShadow( |
| 388 | View v, Object localState, RootItem rootItem, DocumentInfo rootDoc) { |
| 389 | final DragShadowBuilder shadowBuilder = getBaseActivity().getShadowBuilder(); |
| 390 | if (rootDoc == null) { |
| 391 | Log.e(TAG, "Root DocumentInfo is null. Defaulting to appear not droppable."); |
| 392 | shadowBuilder.setAppearDroppable(false); |
| 393 | } else { |
| 394 | rootItem.docInfo = rootDoc; |
| 395 | shadowBuilder.setAppearDroppable(rootDoc.isCreateSupported() |
| 396 | && DragAndDropHelper.canCopyTo(localState, rootDoc)); |
| 397 | } |
| 398 | v.updateDragShadow(shadowBuilder); |
| 399 | } |
| 400 | |
Ben Lin | d020212 | 2016-11-10 18:00:12 -0800 | [diff] [blame] | 401 | // In RootsFragment we always reset the drag shadow as it exits a RootItemView. |
| 402 | @Override |
| 403 | public void onDragExited(View v, Object localState) { |
| 404 | getBaseActivity().getShadowBuilder().resetBackground(); |
| 405 | v.updateDragShadow(getBaseActivity().getShadowBuilder()); |
| 406 | } |
| 407 | |
| 408 | // In RootsFragment we open the hovered root. |
Garfield, Tan | 804133e | 2016-04-20 15:13:56 -0700 | [diff] [blame] | 409 | @Override |
Garfield, Tan | a5588b6 | 2016-07-13 09:23:04 -0700 | [diff] [blame] | 410 | public void onViewHovered(View v) { |
Garfield, Tan | 57facaf7 | 2016-05-27 15:02:35 -0700 | [diff] [blame] | 411 | // SpacerView doesn't have DragListener so this view is guaranteed to be a RootItemView. |
Garfield, Tan | a5588b6 | 2016-07-13 09:23:04 -0700 | [diff] [blame] | 412 | RootItemView itemView = (RootItemView) v; |
Garfield, Tan | 57facaf7 | 2016-05-27 15:02:35 -0700 | [diff] [blame] | 413 | itemView.drawRipple(); |
| 414 | |
Garfield, Tan | a5588b6 | 2016-07-13 09:23:04 -0700 | [diff] [blame] | 415 | final int position = (Integer) v.getTag(R.id.item_position_tag); |
Garfield, Tan | 7d66a86 | 2016-05-11 10:28:41 -0700 | [diff] [blame] | 416 | final Item item = mAdapter.getItem(position); |
Garfield Tan | 7d75f7b | 2016-09-20 16:33:24 -0700 | [diff] [blame] | 417 | item.open(); |
Garfield, Tan | 804133e | 2016-04-20 15:13:56 -0700 | [diff] [blame] | 418 | } |
| 419 | |
| 420 | @Override |
Ben Lin | 166c5c6 | 2016-11-01 12:14:38 -0700 | [diff] [blame] | 421 | public void setDropTargetHighlight(View v, Object localState, boolean highlight) { |
Garfield, Tan | 57facaf7 | 2016-05-27 15:02:35 -0700 | [diff] [blame] | 422 | // SpacerView doesn't have DragListener so this view is guaranteed to be a RootItemView. |
| 423 | RootItemView itemView = (RootItemView) v; |
| 424 | itemView.setHighlight(highlight); |
Garfield, Tan | 804133e | 2016-04-20 15:13:56 -0700 | [diff] [blame] | 425 | } |
| 426 | |
Ben Lin | 5668b86 | 2016-06-28 17:12:52 -0700 | [diff] [blame] | 427 | @Override |
| 428 | public void onCreateContextMenu( |
| 429 | ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { |
| 430 | super.onCreateContextMenu(menu, v, menuInfo); |
| 431 | AdapterContextMenuInfo adapterMenuInfo = (AdapterContextMenuInfo) menuInfo; |
| 432 | final Item item = mAdapter.getItem(adapterMenuInfo.position); |
Garfield Tan | 7d75f7b | 2016-09-20 16:33:24 -0700 | [diff] [blame] | 433 | |
Steve McKay | 92ae43d | 2016-11-08 12:06:58 -0800 | [diff] [blame] | 434 | BaseActivity activity = getBaseActivity(); |
| 435 | item.createContextMenu(menu, activity.getMenuInflater(), mInjector.menuManager); |
Ben Lin | 5668b86 | 2016-06-28 17:12:52 -0700 | [diff] [blame] | 436 | } |
| 437 | |
| 438 | @Override |
| 439 | public boolean onContextItemSelected(MenuItem item) { |
| 440 | AdapterContextMenuInfo adapterMenuInfo = (AdapterContextMenuInfo) item.getMenuInfo(); |
Ben Lin | 035f0c8 | 2016-07-14 10:40:00 -0700 | [diff] [blame] | 441 | // There is a possibility that this is called from DirectoryFragment since |
| 442 | // all fragments' onContextItemSelected gets called when any menu item is selected |
| 443 | // This is to guard against it since DirectoryFragment's RecylerView does not have a |
| 444 | // menuInfo |
| 445 | if (adapterMenuInfo == null) { |
| 446 | return false; |
| 447 | } |
Ben Lin | 5668b86 | 2016-06-28 17:12:52 -0700 | [diff] [blame] | 448 | final RootItem rootItem = (RootItem) mAdapter.getItem(adapterMenuInfo.position); |
Ben Lin | 035f0c8 | 2016-07-14 10:40:00 -0700 | [diff] [blame] | 449 | switch (item.getItemId()) { |
Ben Lin | 5668b86 | 2016-06-28 17:12:52 -0700 | [diff] [blame] | 450 | case R.id.menu_eject_root: |
Ben Lin | 821727a | 2016-07-08 10:49:53 -0700 | [diff] [blame] | 451 | final View ejectIcon = adapterMenuInfo.targetView.findViewById(R.id.eject_icon); |
Garfield Tan | 7d75f7b | 2016-09-20 16:33:24 -0700 | [diff] [blame] | 452 | ejectClicked(ejectIcon, rootItem.root, mActionHandler); |
Ben Lin | 5668b86 | 2016-06-28 17:12:52 -0700 | [diff] [blame] | 453 | return true; |
Garfield Tan | b285b40 | 2016-09-21 17:12:18 -0700 | [diff] [blame] | 454 | case R.id.menu_open_in_new_window: |
Steve McKay | 739f94b | 2016-09-22 14:54:23 -0700 | [diff] [blame] | 455 | mActionHandler.openInNewWindow(new DocumentStack(rootItem.root)); |
Garfield Tan | b285b40 | 2016-09-21 17:12:18 -0700 | [diff] [blame] | 456 | return true; |
| 457 | case R.id.menu_paste_into_folder: |
| 458 | mActionHandler.pasteIntoFolder(rootItem.root); |
| 459 | return true; |
Ben Lin | 5668b86 | 2016-06-28 17:12:52 -0700 | [diff] [blame] | 460 | case R.id.menu_settings: |
Garfield Tan | 7d75f7b | 2016-09-20 16:33:24 -0700 | [diff] [blame] | 461 | mActionHandler.openSettings(rootItem.root); |
Ben Lin | 5668b86 | 2016-06-28 17:12:52 -0700 | [diff] [blame] | 462 | return true; |
| 463 | default: |
| 464 | if (DEBUG) Log.d(TAG, "Unhandled menu item selected: " + item); |
| 465 | return false; |
| 466 | } |
| 467 | } |
| 468 | |
Ben Lin | d020212 | 2016-11-10 18:00:12 -0800 | [diff] [blame] | 469 | @FunctionalInterface |
| 470 | interface RootUpdater { |
| 471 | void updateDocInfoForRoot(DocumentInfo doc); |
| 472 | } |
| 473 | |
| 474 | private void getRootDocument(RootItem rootItem, RootUpdater updater) { |
| 475 | // We need to start a GetRootDocumentTask so we can know whether items can be directly |
| 476 | // pasted into root |
Ben Lin | 30b0dc1 | 2017-03-07 15:37:16 -0800 | [diff] [blame] | 477 | mActionHandler.getRootDocument( |
Ben Lin | d020212 | 2016-11-10 18:00:12 -0800 | [diff] [blame] | 478 | rootItem.root, |
Ben Lin | 30b0dc1 | 2017-03-07 15:37:16 -0800 | [diff] [blame] | 479 | CONTEXT_MENU_ITEM_TIMEOUT, |
Ben Lin | d020212 | 2016-11-10 18:00:12 -0800 | [diff] [blame] | 480 | (DocumentInfo doc) -> { |
| 481 | updater.updateDocInfoForRoot(doc); |
| 482 | }); |
Ben Lin | d020212 | 2016-11-10 18:00:12 -0800 | [diff] [blame] | 483 | } |
| 484 | |
Steve McKay | 739f94b | 2016-09-22 14:54:23 -0700 | [diff] [blame] | 485 | static void ejectClicked(View ejectIcon, RootInfo root, ActionHandler actionHandler) { |
Ben Lin | 5668b86 | 2016-06-28 17:12:52 -0700 | [diff] [blame] | 486 | assert(ejectIcon != null); |
Garfield Tan | 7d75f7b | 2016-09-20 16:33:24 -0700 | [diff] [blame] | 487 | assert(!root.ejecting); |
Ben Lin | 5668b86 | 2016-06-28 17:12:52 -0700 | [diff] [blame] | 488 | ejectIcon.setEnabled(false); |
| 489 | root.ejecting = true; |
Garfield Tan | 7d75f7b | 2016-09-20 16:33:24 -0700 | [diff] [blame] | 490 | actionHandler.ejectRoot( |
| 491 | root, |
Steve McKay | 739f94b | 2016-09-22 14:54:23 -0700 | [diff] [blame] | 492 | new BooleanConsumer() { |
Ben Lin | 5668b86 | 2016-06-28 17:12:52 -0700 | [diff] [blame] | 493 | @Override |
Steve McKay | 739f94b | 2016-09-22 14:54:23 -0700 | [diff] [blame] | 494 | public void accept(boolean ejected) { |
| 495 | // Event if ejected is false, we should reset, since the op failed. |
| 496 | // Either way, we are no longer attempting to eject the device. |
Ben Lin | 5668b86 | 2016-06-28 17:12:52 -0700 | [diff] [blame] | 497 | root.ejecting = false; |
Steve McKay | 739f94b | 2016-09-22 14:54:23 -0700 | [diff] [blame] | 498 | |
| 499 | // If the view is still visible, we update its state. |
| 500 | if (ejectIcon.getVisibility() == View.VISIBLE) { |
| 501 | ejectIcon.setEnabled(!ejected); |
| 502 | } |
Ben Lin | 5668b86 | 2016-06-28 17:12:52 -0700 | [diff] [blame] | 503 | } |
| 504 | }); |
| 505 | } |
| 506 | |
Garfield Tan | 7d75f7b | 2016-09-20 16:33:24 -0700 | [diff] [blame] | 507 | private static class RootComparator implements Comparator<RootItem> { |
Jeff Sharkey | b6a7f2c | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 508 | @Override |
Ben Kwa | 85c8cdd | 2015-10-09 07:48:00 -0700 | [diff] [blame] | 509 | public int compare(RootItem lhs, RootItem rhs) { |
Steve McKay | 55c00e7 | 2016-02-18 15:32:16 -0800 | [diff] [blame] | 510 | return lhs.root.compareTo(rhs.root); |
Jeff Sharkey | b6a7f2c | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 511 | } |
| 512 | } |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 513 | } |