blob: 3f189ee6c04f944f109d960cf123d069b230f711 [file] [log] [blame]
Jeff Sharkey76112212013-08-06 11:26:10 -07001/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.documentsui;
18
Steve McKaycc460af2015-11-03 14:00:45 -080019import static com.android.documentsui.Shared.DEBUG;
20
Garfield, Tan57facaf72016-05-27 15:02:35 -070021import android.annotation.LayoutRes;
Steve McKay17f7e582016-04-04 15:26:48 -070022import android.app.Activity;
Jeff Sharkey76112212013-08-06 11:26:10 -070023import android.app.Fragment;
24import android.app.FragmentManager;
25import android.app.FragmentTransaction;
Jeff Sharkey8b997042013-09-19 15:25:56 -070026import android.app.LoaderManager.LoaderCallbacks;
Jeff Sharkey76112212013-08-06 11:26:10 -070027import android.content.Context;
Jeff Sharkey1d890e02013-08-15 11:24:03 -070028import android.content.Intent;
Jeff Sharkey8b997042013-09-19 15:25:56 -070029import android.content.Loader;
Garfield, Tan57facaf72016-05-27 15:02:35 -070030import android.content.pm.ActivityInfo;
Jeff Sharkey1d890e02013-08-15 11:24:03 -070031import android.content.pm.PackageManager;
32import android.content.pm.ResolveInfo;
Jeff Sharkey8f1edfe2013-10-23 16:05:56 -070033import android.net.Uri;
Jeff Sharkey76112212013-08-06 11:26:10 -070034import android.os.Bundle;
Garfield, Tan57facaf72016-05-27 15:02:35 -070035import android.os.Looper;
Ben Lin5668b862016-06-28 17:12:52 -070036import android.provider.DocumentsContract.Root;
37import android.provider.DocumentsContract;
Jeff Sharkey8f1edfe2013-10-23 16:05:56 -070038import android.provider.Settings;
Steve McKay323dffb2016-02-17 18:25:47 -080039import android.support.annotation.Nullable;
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -070040import android.text.TextUtils;
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -070041import android.text.format.Formatter;
Steve McKaycc460af2015-11-03 14:00:45 -080042import android.util.Log;
Ben Lin5668b862016-06-28 17:12:52 -070043import android.view.ContextMenu;
Jeff Sharkey76112212013-08-06 11:26:10 -070044import android.view.LayoutInflater;
Ben Lin5668b862016-06-28 17:12:52 -070045import android.view.Menu;
46import android.view.MenuInflater;
47import android.view.MenuItem;
48import android.view.MotionEvent;
Jeff Sharkey76112212013-08-06 11:26:10 -070049import android.view.View;
Ben Lin5668b862016-06-28 17:12:52 -070050import android.view.View.OnClickListener;
Garfield, Tan804133e2016-04-20 15:13:56 -070051import android.view.View.OnDragListener;
Ben Lin5668b862016-06-28 17:12:52 -070052import android.view.View.OnGenericMotionListener;
Jeff Sharkey76112212013-08-06 11:26:10 -070053import android.view.ViewGroup;
54import android.widget.AdapterView;
Ben Lin5668b862016-06-28 17:12:52 -070055import android.widget.AdapterView.AdapterContextMenuInfo;
Jeff Sharkey76112212013-08-06 11:26:10 -070056import android.widget.AdapterView.OnItemClickListener;
Jeff Sharkey8f1edfe2013-10-23 16:05:56 -070057import android.widget.AdapterView.OnItemLongClickListener;
Jeff Sharkey76112212013-08-06 11:26:10 -070058import android.widget.ArrayAdapter;
59import android.widget.ImageView;
60import android.widget.ListView;
61import android.widget.TextView;
62
Jeff Sharkey724deeb2013-08-31 15:02:20 -070063import com.android.documentsui.model.RootInfo;
Jeff Sharkey76112212013-08-06 11:26:10 -070064
Steve McKayfefcd702015-08-20 16:19:38 +000065import java.util.ArrayList;
Jeff Sharkey8b997042013-09-19 15:25:56 -070066import java.util.Collection;
Jeff Sharkey9dd02622013-09-27 16:44:11 -070067import java.util.Collections;
Jeff Sharkeyb6a7f2c2013-08-27 18:26:48 -070068import java.util.Comparator;
Garfield, Tan57facaf72016-05-27 15:02:35 -070069import java.util.HashMap;
Jeff Sharkey1d890e02013-08-15 11:24:03 -070070import java.util.List;
Garfield, Tan57facaf72016-05-27 15:02:35 -070071import java.util.Map;
Kenny Root16d85982013-12-13 12:00:26 -080072import java.util.Objects;
Ben Lin5668b862016-06-28 17:12:52 -070073import java.util.function.BooleanSupplier;
74import java.util.function.Consumer;
Jeff Sharkey76112212013-08-06 11:26:10 -070075
76/**
77 * Display list of known storage backend roots.
78 */
Garfield, Tan804133e2016-04-20 15:13:56 -070079public class RootsFragment extends Fragment implements ItemDragListener.DragHost {
Jeff Sharkey76112212013-08-06 11:26:10 -070080
Steve McKaycc460af2015-11-03 14:00:45 -080081 private static final String TAG = "RootsFragment";
82 private static final String EXTRA_INCLUDE_APPS = "includeApps";
83
Jeff Sharkey76112212013-08-06 11:26:10 -070084 private ListView mList;
Jeff Sharkey9dd02622013-09-27 16:44:11 -070085 private RootsAdapter mAdapter;
Jeff Sharkey8b997042013-09-19 15:25:56 -070086 private LoaderCallbacks<Collection<RootInfo>> mCallbacks;
Ben Lin821727a2016-07-08 10:49:53 -070087 private Consumer<RootInfo> mOpenSettings = (RootInfo) -> {
88 throw new UnsupportedOperationException("Can't open settings.");
89 };
Jeff Sharkey8b997042013-09-19 15:25:56 -070090
Ben Lin821727a2016-07-08 10:49:53 -070091 public static void show(FragmentManager fm, Consumer<RootInfo> openSettings) {
92 RootsFragment fragment = show(fm, (Intent) null);
93 fragment.mOpenSettings = openSettings;
94 }
95
96 public static RootsFragment show(FragmentManager fm, Intent includeApps) {
Jeff Sharkey1d890e02013-08-15 11:24:03 -070097 final Bundle args = new Bundle();
98 args.putParcelable(EXTRA_INCLUDE_APPS, includeApps);
99
Jeff Sharkey76112212013-08-06 11:26:10 -0700100 final RootsFragment fragment = new RootsFragment();
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700101 fragment.setArguments(args);
Jeff Sharkey76112212013-08-06 11:26:10 -0700102
103 final FragmentTransaction ft = fm.beginTransaction();
104 ft.replace(R.id.container_roots, fragment);
105 ft.commitAllowingStateLoss();
Ben Lin821727a2016-07-08 10:49:53 -0700106
107 return fragment;
Jeff Sharkey76112212013-08-06 11:26:10 -0700108 }
109
110 public static RootsFragment get(FragmentManager fm) {
111 return (RootsFragment) fm.findFragmentById(R.id.container_roots);
112 }
113
114 @Override
115 public View onCreateView(
116 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Jeff Sharkey76112212013-08-06 11:26:10 -0700117
118 final View view = inflater.inflate(R.layout.fragment_roots, container, false);
Ben Kwa74e5d412016-02-10 07:46:35 -0800119 mList = (ListView) view.findViewById(R.id.roots_list);
Jeff Sharkey76112212013-08-06 11:26:10 -0700120 mList.setOnItemClickListener(mItemListener);
Ben Lin821727a2016-07-08 10:49:53 -0700121 // ListView does not have right-click specific listeners, so we will have a
122 // GenericMotionListener to listen for it.
123 // Currently, right click is viewed the same as long press, so we will have to quickly
124 // register for context menu when we receive a right click event, and quickly unregister
125 // it afterwards to prevent context menus popping up upon long presses.
126 // All other motion events will then get passed to OnItemClickListener.
Ben Lin5668b862016-06-28 17:12:52 -0700127 mList.setOnGenericMotionListener(
128 new OnGenericMotionListener() {
129 @Override
130 public boolean onGenericMotion(View v, MotionEvent event) {
131 if (Events.isMouseEvent(event)
132 && event.getButtonState() == MotionEvent.BUTTON_SECONDARY) {
133 registerForContextMenu(v);
134 v.showContextMenu(event.getX(), event.getY());
135 unregisterForContextMenu(v);
136 return true;
137 }
138 return false;
139 }
140 });
Jeff Sharkey42d26792013-09-06 13:22:09 -0700141 mList.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
Jeff Sharkey76112212013-08-06 11:26:10 -0700142 return view;
143 }
144
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700145 @Override
Jeff Sharkey8b997042013-09-19 15:25:56 -0700146 public void onActivityCreated(Bundle savedInstanceState) {
147 super.onActivityCreated(savedInstanceState);
148
149 final Context context = getActivity();
150 final RootsCache roots = DocumentsApplication.getRootsCache(context);
Steve McKayd0a2a2c2015-03-25 14:35:33 -0700151 final State state = ((BaseActivity) context).getDisplayState();
Jeff Sharkey8b997042013-09-19 15:25:56 -0700152
153 mCallbacks = new LoaderCallbacks<Collection<RootInfo>>() {
154 @Override
155 public Loader<Collection<RootInfo>> onCreateLoader(int id, Bundle args) {
156 return new RootsLoader(context, roots, state);
157 }
158
159 @Override
160 public void onLoadFinished(
161 Loader<Collection<RootInfo>> loader, Collection<RootInfo> result) {
Steve McKay323dffb2016-02-17 18:25:47 -0800162 if (!isAdded()) {
163 return;
164 }
Jeff Sharkey8b997042013-09-19 15:25:56 -0700165
Steve McKay323dffb2016-02-17 18:25:47 -0800166 Intent handlerAppIntent = getArguments().getParcelable(EXTRA_INCLUDE_APPS);
Jeff Sharkey8b997042013-09-19 15:25:56 -0700167
Garfield, Tan804133e2016-04-20 15:13:56 -0700168 mAdapter = new RootsAdapter(context, result, handlerAppIntent, state,
169 new ItemDragListener<>(RootsFragment.this));
Jeff Sharkey8b997042013-09-19 15:25:56 -0700170 mList.setAdapter(mAdapter);
171
172 onCurrentRootChanged();
173 }
174
175 @Override
176 public void onLoaderReset(Loader<Collection<RootInfo>> loader) {
177 mAdapter = null;
178 mList.setAdapter(null);
179 }
180 };
Jeff Sharkey1c903cc2013-09-02 17:19:40 -0700181 }
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700182
Jeff Sharkey8b997042013-09-19 15:25:56 -0700183 @Override
184 public void onResume() {
185 super.onResume();
Jeff Sharkeye8d13ea2014-08-08 15:10:03 -0700186 onDisplayStateChanged();
187 }
Jeff Sharkey1c903cc2013-09-02 17:19:40 -0700188
Jeff Sharkeye8d13ea2014-08-08 15:10:03 -0700189 public void onDisplayStateChanged() {
Jeff Sharkey8b997042013-09-19 15:25:56 -0700190 final Context context = getActivity();
Steve McKayd0a2a2c2015-03-25 14:35:33 -0700191 final State state = ((BaseActivity) context).getDisplayState();
Jeff Sharkey1c903cc2013-09-02 17:19:40 -0700192
Steve McKayd0a2a2c2015-03-25 14:35:33 -0700193 if (state.action == State.ACTION_GET_CONTENT) {
Jeff Sharkey8f1edfe2013-10-23 16:05:56 -0700194 mList.setOnItemLongClickListener(mItemLongClickListener);
195 } else {
196 mList.setOnItemLongClickListener(null);
197 mList.setLongClickable(false);
198 }
199
Jeff Sharkey8b997042013-09-19 15:25:56 -0700200 getLoaderManager().restartLoader(2, null, mCallbacks);
Jeff Sharkey42d26792013-09-06 13:22:09 -0700201 }
202
203 public void onCurrentRootChanged() {
Steve McKay323dffb2016-02-17 18:25:47 -0800204 if (mAdapter == null) {
205 return;
206 }
Jeff Sharkey42d26792013-09-06 13:22:09 -0700207
Steve McKayd0a2a2c2015-03-25 14:35:33 -0700208 final RootInfo root = ((BaseActivity) getActivity()).getCurrentRoot();
Jeff Sharkey42d26792013-09-06 13:22:09 -0700209 for (int i = 0; i < mAdapter.getCount(); i++) {
210 final Object item = mAdapter.getItem(i);
Jeff Sharkey25f10b32013-10-07 14:08:17 -0700211 if (item instanceof RootItem) {
212 final RootInfo testRoot = ((RootItem) item).root;
Kenny Root16d85982013-12-13 12:00:26 -0800213 if (Objects.equals(testRoot, root)) {
Jeff Sharkey25f10b32013-10-07 14:08:17 -0700214 mList.setItemChecked(i, true);
215 return;
216 }
Jeff Sharkey42d26792013-09-06 13:22:09 -0700217 }
218 }
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700219 }
220
Ben Kwa74e5d412016-02-10 07:46:35 -0800221 /**
222 * Attempts to shift focus back to the navigation drawer.
223 */
224 public void requestFocus() {
225 mList.requestFocus();
226 }
227
Jeff Sharkey8f1edfe2013-10-23 16:05:56 -0700228 private void showAppDetails(ResolveInfo ri) {
229 final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
230 intent.setData(Uri.fromParts("package", ri.activityInfo.packageName, null));
231 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
232 startActivity(intent);
233 }
234
Ben Lin5668b862016-06-28 17:12:52 -0700235 private BaseActivity getBaseActivity() {
236 return (BaseActivity) getActivity();
237 }
238
Garfield, Tan804133e2016-04-20 15:13:56 -0700239 @Override
240 public void runOnUiThread(Runnable runnable) {
241 getActivity().runOnUiThread(runnable);
242 }
243
244 /**
245 * {@inheritDoc}
246 *
247 * In RootsFragment we open the hovered root.
248 */
249 @Override
250 public void onViewHovered(View view) {
Garfield, Tan57facaf72016-05-27 15:02:35 -0700251 // SpacerView doesn't have DragListener so this view is guaranteed to be a RootItemView.
252 RootItemView itemView = (RootItemView) view;
253 itemView.drawRipple();
254
Garfield, Tan7d66a862016-05-11 10:28:41 -0700255 final int position = (Integer) view.getTag(R.id.item_position_tag);
256 final Item item = mAdapter.getItem(position);
257 item.open(this);
Garfield, Tan804133e2016-04-20 15:13:56 -0700258 }
259
260 @Override
261 public void setDropTargetHighlight(View v, boolean highlight) {
Garfield, Tan57facaf72016-05-27 15:02:35 -0700262 // SpacerView doesn't have DragListener so this view is guaranteed to be a RootItemView.
263 RootItemView itemView = (RootItemView) v;
264 itemView.setHighlight(highlight);
Garfield, Tan804133e2016-04-20 15:13:56 -0700265 }
266
Ben Lin5668b862016-06-28 17:12:52 -0700267 @Override
268 public void onCreateContextMenu(
269 ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
270 super.onCreateContextMenu(menu, v, menuInfo);
271 AdapterContextMenuInfo adapterMenuInfo = (AdapterContextMenuInfo) menuInfo;
272 final Item item = mAdapter.getItem(adapterMenuInfo.position);
273 if (item instanceof RootItem) {
274 RootItem rootItem = (RootItem) item;
275 MenuInflater inflater = getActivity().getMenuInflater();
276 inflater.inflate(R.menu.root_context_menu, menu);
277 (getBaseActivity()).getMenuManager().updateRootContextMenu(menu, rootItem.root);
278 }
279 }
280
281 @Override
282 public boolean onContextItemSelected(MenuItem item) {
283 AdapterContextMenuInfo adapterMenuInfo = (AdapterContextMenuInfo) item.getMenuInfo();
Ben Lin035f0c82016-07-14 10:40:00 -0700284 // There is a possibility that this is called from DirectoryFragment since
285 // all fragments' onContextItemSelected gets called when any menu item is selected
286 // This is to guard against it since DirectoryFragment's RecylerView does not have a
287 // menuInfo
288 if (adapterMenuInfo == null) {
289 return false;
290 }
Ben Lin5668b862016-06-28 17:12:52 -0700291 final RootItem rootItem = (RootItem) mAdapter.getItem(adapterMenuInfo.position);
Ben Lin035f0c82016-07-14 10:40:00 -0700292 switch (item.getItemId()) {
Ben Lin5668b862016-06-28 17:12:52 -0700293 case R.id.menu_eject_root:
Ben Lin821727a2016-07-08 10:49:53 -0700294 final View ejectIcon = adapterMenuInfo.targetView.findViewById(R.id.eject_icon);
295 ejectClicked(ejectIcon, rootItem.root);
Ben Lin5668b862016-06-28 17:12:52 -0700296 return true;
297 case R.id.menu_settings:
Ben Lin821727a2016-07-08 10:49:53 -0700298 mOpenSettings.accept(rootItem.root);
Ben Lin5668b862016-06-28 17:12:52 -0700299 return true;
300 default:
301 if (DEBUG) Log.d(TAG, "Unhandled menu item selected: " + item);
302 return false;
303 }
304 }
305
306 private static void ejectClicked(View ejectIcon, RootInfo root) {
307 assert(ejectIcon != null);
308 assert(ejectIcon.getContext() instanceof BaseActivity);
Ben Lin821727a2016-07-08 10:49:53 -0700309 assert (!root.ejecting);
Ben Lin5668b862016-06-28 17:12:52 -0700310 ejectIcon.setEnabled(false);
311 root.ejecting = true;
312 ejectRoot(
313 ejectIcon,
314 root.authority,
315 root.rootId,
316 new Consumer<Boolean>() {
317 @Override
318 public void accept(Boolean ejected) {
319 ejectIcon.setEnabled(!ejected);
320 root.ejecting = false;
321 }
322 });
323 }
324
325 static void ejectRoot(
326 View ejectIcon, String authority, String rootId, Consumer<Boolean> listener) {
327 BooleanSupplier predicate = () -> {
328 return !(ejectIcon.getVisibility() == View.VISIBLE);
329 };
Ben Lin821727a2016-07-08 10:49:53 -0700330 new EjectRootTask(ejectIcon.getContext(),
Ben Lin5668b862016-06-28 17:12:52 -0700331 authority,
332 rootId,
Ben Lin821727a2016-07-08 10:49:53 -0700333 predicate,
Ben Lin5668b862016-06-28 17:12:52 -0700334 listener).executeOnExecutor(ProviderExecutor.forAuthority(authority));
335 }
336
Jeff Sharkey76112212013-08-06 11:26:10 -0700337 private OnItemClickListener mItemListener = new OnItemClickListener() {
338 @Override
339 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Garfield, Tan7d66a862016-05-11 10:28:41 -0700340 final Item item = mAdapter.getItem(position);
341 item.open(RootsFragment.this);
Garfield, Tan57facaf72016-05-27 15:02:35 -0700342
343 ((BaseActivity) getActivity()).setRootsDrawerOpen(false);
Jeff Sharkey76112212013-08-06 11:26:10 -0700344 }
345 };
346
Jeff Sharkey8f1edfe2013-10-23 16:05:56 -0700347 private OnItemLongClickListener mItemLongClickListener = new OnItemLongClickListener() {
348 @Override
349 public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
350 final Item item = mAdapter.getItem(position);
Garfield, Tan57facaf72016-05-27 15:02:35 -0700351 return item.showAppDetails(RootsFragment.this);
Jeff Sharkey8f1edfe2013-10-23 16:05:56 -0700352 }
353 };
354
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700355 private static abstract class Item {
Garfield, Tan57facaf72016-05-27 15:02:35 -0700356 private final @LayoutRes int mLayoutId;
357 private final String mStringId;
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700358
Garfield, Tan57facaf72016-05-27 15:02:35 -0700359 public Item(@LayoutRes int layoutId, String stringId) {
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700360 mLayoutId = layoutId;
Garfield, Tan57facaf72016-05-27 15:02:35 -0700361 mStringId = stringId;
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700362 }
363
364 public View getView(View convertView, ViewGroup parent) {
Garfield, Tan57facaf72016-05-27 15:02:35 -0700365 if (convertView == null
366 || (Integer) convertView.getTag(R.id.layout_id_tag) != mLayoutId) {
367 convertView = LayoutInflater.from(parent.getContext())
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700368 .inflate(mLayoutId, parent, false);
Garfield, Tan57facaf72016-05-27 15:02:35 -0700369 }
370 convertView.setTag(R.id.layout_id_tag, mLayoutId);
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700371 bindView(convertView);
372 return convertView;
373 }
374
Garfield, Tan57facaf72016-05-27 15:02:35 -0700375 boolean showAppDetails(RootsFragment fragment) {
376 return false;
377 }
378
Garfield, Tan804133e2016-04-20 15:13:56 -0700379 abstract void bindView(View convertView);
380
381 abstract boolean isDropTarget();
Garfield, Tan7d66a862016-05-11 10:28:41 -0700382
383 abstract void open(RootsFragment fragment);
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700384 }
385
386 private static class RootItem extends Item {
Garfield, Tan57facaf72016-05-27 15:02:35 -0700387 private static final String STRING_ID_FORMAT = "RootItem{%s/%s}";
388
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700389 public final RootInfo root;
390
391 public RootItem(RootInfo root) {
Garfield, Tan57facaf72016-05-27 15:02:35 -0700392 super(R.layout.item_root, getStringId(root));
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700393 this.root = root;
Jeff Sharkey76112212013-08-06 11:26:10 -0700394 }
395
Garfield, Tan57facaf72016-05-27 15:02:35 -0700396 private static String getStringId(RootInfo root) {
397 // Empty URI authority is invalid, so we can use empty string if root.authority is null.
398 // Directly passing null to String.format() will write "null" which can be a valid URI
399 // authority.
400 String authority = (root.authority == null ? "" : root.authority);
401 return String.format(STRING_ID_FORMAT, authority, root.rootId);
402 }
403
Jeff Sharkey76112212013-08-06 11:26:10 -0700404 @Override
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700405 public void bindView(View convertView) {
Jeff Sharkey76112212013-08-06 11:26:10 -0700406 final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
407 final TextView title = (TextView) convertView.findViewById(android.R.id.title);
408 final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
Ben Lin821727a2016-07-08 10:49:53 -0700409 final ImageView ejectIcon = (ImageView) convertView.findViewById(R.id.eject_icon);
Jeff Sharkey76112212013-08-06 11:26:10 -0700410
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700411 final Context context = convertView.getContext();
Jeff Sharkey7e544612014-08-29 15:38:27 -0700412 icon.setImageDrawable(root.loadDrawerIcon(context));
Jeff Sharkey76112212013-08-06 11:26:10 -0700413 title.setText(root.title);
414
Ben Lin5668b862016-06-28 17:12:52 -0700415 if (root.supportsEject()) {
Ben Lin821727a2016-07-08 10:49:53 -0700416 ejectIcon.setVisibility(View.VISIBLE);
417 ejectIcon.setImageDrawable(root.loadEjectIcon(context));
418 ejectIcon.setOnClickListener(new OnClickListener() {
Ben Lin5668b862016-06-28 17:12:52 -0700419 @Override
420 public void onClick(View unmountIcon) {
421 RootsFragment.ejectClicked(unmountIcon, root);
422 }
423 });
424 } else {
Ben Lin821727a2016-07-08 10:49:53 -0700425 ejectIcon.setVisibility(View.GONE);
426 ejectIcon.setOnClickListener(null);
Ben Lin5668b862016-06-28 17:12:52 -0700427 }
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700428 // Show available space if no summary
429 String summaryText = root.summary;
430 if (TextUtils.isEmpty(summaryText) && root.availableBytes >= 0) {
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700431 summaryText = context.getString(R.string.root_available_bytes,
432 Formatter.formatFileSize(context, root.availableBytes));
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700433 }
434
435 summary.setText(summaryText);
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700436 summary.setVisibility(TextUtils.isEmpty(summaryText) ? View.GONE : View.VISIBLE);
Jeff Sharkey76112212013-08-06 11:26:10 -0700437 }
Garfield, Tan804133e2016-04-20 15:13:56 -0700438
439 @Override
440 boolean isDropTarget() {
441 return root.supportsCreate() && !root.isLibrary();
442 }
Garfield, Tan7d66a862016-05-11 10:28:41 -0700443
444 @Override
Garfield, Tan57facaf72016-05-27 15:02:35 -0700445 void open(RootsFragment fragment) {
Garfield, Tan7d66a862016-05-11 10:28:41 -0700446 BaseActivity activity = BaseActivity.get(fragment);
447 Metrics.logRootVisited(fragment.getActivity(), root);
448 activity.onRootPicked(root);
449 }
Jeff Sharkey76112212013-08-06 11:26:10 -0700450 }
451
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700452 private static class SpacerItem extends Item {
Garfield, Tan57facaf72016-05-27 15:02:35 -0700453 private static final String STRING_ID = "SpacerItem";
454
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700455 public SpacerItem() {
Garfield, Tan57facaf72016-05-27 15:02:35 -0700456 // Multiple spacer items can share the same string id as they're identical.
457 super(R.layout.item_root_spacer, STRING_ID);
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700458 }
459
460 @Override
Garfield, Tan804133e2016-04-20 15:13:56 -0700461 void bindView(View convertView) {
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700462 // Nothing to bind
463 }
Garfield, Tan804133e2016-04-20 15:13:56 -0700464
465 @Override
466 boolean isDropTarget() {
467 return false;
468 }
Garfield, Tan7d66a862016-05-11 10:28:41 -0700469
470 @Override
Garfield, Tan57facaf72016-05-27 15:02:35 -0700471 void open(RootsFragment fragment) {
Garfield, Tan7d66a862016-05-11 10:28:41 -0700472 if (DEBUG) Log.d(TAG, "Ignoring click/hover on spacer item.");
473 }
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700474 }
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700475
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700476 private static class AppItem extends Item {
Garfield, Tan57facaf72016-05-27 15:02:35 -0700477 private static final String STRING_ID_FORMAT = "AppItem{%s/%s}";
478
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700479 public final ResolveInfo info;
480
481 public AppItem(ResolveInfo info) {
Garfield, Tan57facaf72016-05-27 15:02:35 -0700482 super(R.layout.item_root, getStringId(info));
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700483 this.info = info;
484 }
485
Garfield, Tan57facaf72016-05-27 15:02:35 -0700486 private static String getStringId(ResolveInfo info) {
487 ActivityInfo activityInfo = info.activityInfo;
488
489 String component = String.format(
490 STRING_ID_FORMAT, activityInfo.applicationInfo.packageName, activityInfo.name);
491 return component;
492 }
493
494 @Override
495 boolean showAppDetails(RootsFragment fragment) {
496 fragment.showAppDetails(info);
497 return true;
498 }
499
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700500 @Override
Garfield, Tan804133e2016-04-20 15:13:56 -0700501 void bindView(View convertView) {
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700502 final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
503 final TextView title = (TextView) convertView.findViewById(android.R.id.title);
504 final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
505
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700506 final PackageManager pm = convertView.getContext().getPackageManager();
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700507 icon.setImageDrawable(info.loadIcon(pm));
508 title.setText(info.loadLabel(pm));
509
510 // TODO: match existing summary behavior from disambig dialog
511 summary.setVisibility(View.GONE);
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700512 }
Garfield, Tan804133e2016-04-20 15:13:56 -0700513
514 @Override
515 boolean isDropTarget() {
516 // We won't support drag n' drop in DocumentsActivity, and apps only show up there.
517 return false;
518 }
Garfield, Tan7d66a862016-05-11 10:28:41 -0700519
520 @Override
Garfield, Tan57facaf72016-05-27 15:02:35 -0700521 void open(RootsFragment fragment) {
Garfield, Tan7d66a862016-05-11 10:28:41 -0700522 DocumentsActivity activity = DocumentsActivity.get(fragment);
523 Metrics.logAppVisited(fragment.getActivity(), info);
524 activity.onAppPicked(info);
525 }
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700526 }
527
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700528 private static class RootsAdapter extends ArrayAdapter<Item> {
Garfield, Tan57facaf72016-05-27 15:02:35 -0700529 private static final Map<String, Long> sIdMap = new HashMap<String, Long>();
530 // the next available id to associate with a new string id
531 private static long sNextAvailableId;
Steve McKay323dffb2016-02-17 18:25:47 -0800532
Garfield, Tan804133e2016-04-20 15:13:56 -0700533 private OnDragListener mDragListener;
534
Steve McKay323dffb2016-02-17 18:25:47 -0800535 /**
Garfield, Tan804133e2016-04-20 15:13:56 -0700536 * @param handlerAppIntent When not null, apps capable of handling the original intent will
537 * be included in list of roots (in special section at bottom).
Steve McKay323dffb2016-02-17 18:25:47 -0800538 */
Aga Wronska6d50bcc2016-03-28 17:27:02 -0700539 public RootsAdapter(Context context, Collection<RootInfo> roots,
Garfield, Tan804133e2016-04-20 15:13:56 -0700540 @Nullable Intent handlerAppIntent, State state, OnDragListener dragListener) {
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700541 super(context, 0);
Jeff Sharkey76112212013-08-06 11:26:10 -0700542
Ben Kwa85c8cdd2015-10-09 07:48:00 -0700543 final List<RootItem> libraries = new ArrayList<>();
Daichi Hirono9fb33892015-10-23 08:36:10 +0900544 final List<RootItem> others = new ArrayList<>();
Jeff Sharkey76112212013-08-06 11:26:10 -0700545
Daichi Hirono9fb33892015-10-23 08:36:10 +0900546 for (final RootInfo root : roots) {
547 final RootItem item = new RootItem(root);
Aga Wronska8453b962016-03-17 11:48:20 -0700548
Steve McKay17f7e582016-04-04 15:26:48 -0700549 if (root.isHome() &&
Garfield, Tan804133e2016-04-20 15:13:56 -0700550 !Shared.shouldShowDocumentsRoot(context,
551 ((Activity) context).getIntent())) {
Aga Wronska8453b962016-03-17 11:48:20 -0700552 continue;
553 } else if (root.isLibrary()) {
Steve McKayd4800812016-02-02 11:41:03 -0800554 if (DEBUG) Log.d(TAG, "Adding " + root + " as library.");
Daichi Hirono9fb33892015-10-23 08:36:10 +0900555 libraries.add(item);
556 } else {
Steve McKayd4800812016-02-02 11:41:03 -0800557 if (DEBUG) Log.d(TAG, "Adding " + root + " as non-library.");
Daichi Hirono9fb33892015-10-23 08:36:10 +0900558 others.add(item);
Jeff Sharkey40457802013-09-21 13:57:33 -0700559 }
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700560 }
Jeff Sharkey40457802013-09-21 13:57:33 -0700561
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700562 final RootComparator comp = new RootComparator();
Ben Kwa85c8cdd2015-10-09 07:48:00 -0700563 Collections.sort(libraries, comp);
Daichi Hirono9fb33892015-10-23 08:36:10 +0900564 Collections.sort(others, comp);
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700565
Ben Kwa85c8cdd2015-10-09 07:48:00 -0700566 addAll(libraries);
Steve McKay323dffb2016-02-17 18:25:47 -0800567 // Only add the spacer if it is actually separating something.
568 if (!libraries.isEmpty() && !others.isEmpty()) {
569 add(new SpacerItem());
570 }
Daichi Hirono9fb33892015-10-23 08:36:10 +0900571 addAll(others);
Jeff Sharkey76112212013-08-06 11:26:10 -0700572
Steve McKay323dffb2016-02-17 18:25:47 -0800573 // Include apps that can handle this intent too.
574 if (handlerAppIntent != null) {
575 includeHandlerApps(context, handlerAppIntent);
576 }
Garfield, Tan804133e2016-04-20 15:13:56 -0700577
578 mDragListener = dragListener;
Steve McKay323dffb2016-02-17 18:25:47 -0800579 }
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700580
Steve McKay323dffb2016-02-17 18:25:47 -0800581 /**
Garfield, Tan804133e2016-04-20 15:13:56 -0700582 * Adds apps capable of handling the original intent will be included in list of roots (in
583 * special section at bottom).
Steve McKay323dffb2016-02-17 18:25:47 -0800584 */
585 private void includeHandlerApps(Context context, Intent handlerAppIntent) {
586 final PackageManager pm = context.getPackageManager();
587 final List<ResolveInfo> infos = pm.queryIntentActivities(
588 handlerAppIntent, PackageManager.MATCH_DEFAULT_ONLY);
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700589
Steve McKay323dffb2016-02-17 18:25:47 -0800590 final List<AppItem> apps = new ArrayList<>();
591
592 // Omit ourselves from the list
593 for (ResolveInfo info : infos) {
594 if (!context.getPackageName().equals(info.activityInfo.packageName)) {
595 apps.add(new AppItem(info));
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700596 }
Steve McKay323dffb2016-02-17 18:25:47 -0800597 }
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700598
Steve McKay323dffb2016-02-17 18:25:47 -0800599 if (apps.size() > 0) {
600 add(new SpacerItem());
601 addAll(apps);
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700602 }
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700603 }
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700604
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700605 @Override
Garfield, Tan57facaf72016-05-27 15:02:35 -0700606 public boolean hasStableIds() {
607 return true;
608 }
609
610 @Override
611 public long getItemId(int position) {
612 // Ensure this method is only called in main thread because we don't have any
613 // concurrency protection.
614 assert(Looper.myLooper() == Looper.getMainLooper());
615
616 String stringId = getItem(position).mStringId;
617
618 long id;
619 if (sIdMap.containsKey(stringId)) {
620 id = sIdMap.get(stringId);
621 } else {
622 id = sNextAvailableId++;
623 sIdMap.put(stringId, id);
624 }
625
626 return id;
627 }
628
629 @Override
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700630 public View getView(int position, View convertView, ViewGroup parent) {
631 final Item item = getItem(position);
Garfield, Tan804133e2016-04-20 15:13:56 -0700632 final View view = item.getView(convertView, parent);
633
634 if (item.isDropTarget()) {
635 view.setTag(R.id.item_position_tag, position);
636 view.setOnDragListener(mDragListener);
637 } else {
638 view.setTag(R.id.item_position_tag, null);
639 view.setOnDragListener(null);
640 }
641 return view;
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700642 }
Jeff Sharkey76112212013-08-06 11:26:10 -0700643
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700644 @Override
645 public boolean areAllItemsEnabled() {
646 return false;
647 }
648
649 @Override
650 public boolean isEnabled(int position) {
651 return getItemViewType(position) != 1;
652 }
653
654 @Override
655 public int getItemViewType(int position) {
656 final Item item = getItem(position);
657 if (item instanceof RootItem || item instanceof AppItem) {
658 return 0;
659 } else {
660 return 1;
Jeff Sharkey40457802013-09-21 13:57:33 -0700661 }
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700662 }
663
664 @Override
665 public int getViewTypeCount() {
666 return 2;
Jeff Sharkey76112212013-08-06 11:26:10 -0700667 }
668 }
Jeff Sharkeyb6a7f2c2013-08-27 18:26:48 -0700669
Ben Kwa85c8cdd2015-10-09 07:48:00 -0700670 public static class RootComparator implements Comparator<RootItem> {
Jeff Sharkeyb6a7f2c2013-08-27 18:26:48 -0700671 @Override
Ben Kwa85c8cdd2015-10-09 07:48:00 -0700672 public int compare(RootItem lhs, RootItem rhs) {
Steve McKay55c00e72016-02-18 15:32:16 -0800673 return lhs.root.compareTo(rhs.root);
Jeff Sharkeyb6a7f2c2013-08-27 18:26:48 -0700674 }
675 }
Jeff Sharkey76112212013-08-06 11:26:10 -0700676}