blob: 8b4f40ef38bea41fe71e2e8d948b4d94180c3fe1 [file] [log] [blame]
Jeff Sharkey66516692013-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 McKay35820cf2015-11-03 14:00:45 -080019import static com.android.documentsui.Shared.DEBUG;
Aga Wronska973168c2016-03-28 17:27:02 -070020import static com.android.documentsui.State.ACTION_OPEN_TREE;
Steve McKay35820cf2015-11-03 14:00:45 -080021
Jeff Sharkey66516692013-08-06 11:26:10 -070022import android.app.Fragment;
23import android.app.FragmentManager;
24import android.app.FragmentTransaction;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070025import android.app.LoaderManager.LoaderCallbacks;
Jeff Sharkey66516692013-08-06 11:26:10 -070026import android.content.Context;
Jeff Sharkey54ca29a2013-08-15 11:24:03 -070027import android.content.Intent;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070028import android.content.Loader;
Jeff Sharkey54ca29a2013-08-15 11:24:03 -070029import android.content.pm.PackageManager;
30import android.content.pm.ResolveInfo;
Jeff Sharkeyc24bbd42013-10-23 16:05:56 -070031import android.net.Uri;
Jeff Sharkey66516692013-08-06 11:26:10 -070032import android.os.Bundle;
Jeff Sharkeyc24bbd42013-10-23 16:05:56 -070033import android.provider.Settings;
Steve McKay4a1ca862016-02-17 18:25:47 -080034import android.support.annotation.Nullable;
Jeff Sharkey4ec97392013-09-10 12:04:26 -070035import android.text.TextUtils;
Jeff Sharkey9fb567b2013-08-07 16:22:02 -070036import android.text.format.Formatter;
Steve McKay35820cf2015-11-03 14:00:45 -080037import android.util.Log;
Jeff Sharkey66516692013-08-06 11:26:10 -070038import android.view.LayoutInflater;
39import android.view.View;
40import android.view.ViewGroup;
41import android.widget.AdapterView;
42import android.widget.AdapterView.OnItemClickListener;
Jeff Sharkeyc24bbd42013-10-23 16:05:56 -070043import android.widget.AdapterView.OnItemLongClickListener;
Jeff Sharkey66516692013-08-06 11:26:10 -070044import android.widget.ArrayAdapter;
45import android.widget.ImageView;
46import android.widget.ListView;
47import android.widget.TextView;
48
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -070049import com.android.documentsui.model.RootInfo;
Jeff Sharkey66516692013-08-06 11:26:10 -070050
Steve McKay58efce32015-08-20 16:19:38 +000051import java.util.ArrayList;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070052import java.util.Collection;
Jeff Sharkey6efba222013-09-27 16:44:11 -070053import java.util.Collections;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070054import java.util.Comparator;
Jeff Sharkey54ca29a2013-08-15 11:24:03 -070055import java.util.List;
Kenny Roote6585b32013-12-13 12:00:26 -080056import java.util.Objects;
Jeff Sharkey66516692013-08-06 11:26:10 -070057
58/**
59 * Display list of known storage backend roots.
60 */
61public class RootsFragment extends Fragment {
62
Steve McKay35820cf2015-11-03 14:00:45 -080063 private static final String TAG = "RootsFragment";
64 private static final String EXTRA_INCLUDE_APPS = "includeApps";
65
Jeff Sharkey66516692013-08-06 11:26:10 -070066 private ListView mList;
Jeff Sharkey6efba222013-09-27 16:44:11 -070067 private RootsAdapter mAdapter;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070068 private LoaderCallbacks<Collection<RootInfo>> mCallbacks;
69
Jeff Sharkey54ca29a2013-08-15 11:24:03 -070070
71 public static void show(FragmentManager fm, Intent includeApps) {
72 final Bundle args = new Bundle();
73 args.putParcelable(EXTRA_INCLUDE_APPS, includeApps);
74
Jeff Sharkey66516692013-08-06 11:26:10 -070075 final RootsFragment fragment = new RootsFragment();
Jeff Sharkey54ca29a2013-08-15 11:24:03 -070076 fragment.setArguments(args);
Jeff Sharkey66516692013-08-06 11:26:10 -070077
78 final FragmentTransaction ft = fm.beginTransaction();
79 ft.replace(R.id.container_roots, fragment);
80 ft.commitAllowingStateLoss();
81 }
82
83 public static RootsFragment get(FragmentManager fm) {
84 return (RootsFragment) fm.findFragmentById(R.id.container_roots);
85 }
86
87 @Override
88 public View onCreateView(
89 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Jeff Sharkey66516692013-08-06 11:26:10 -070090
91 final View view = inflater.inflate(R.layout.fragment_roots, container, false);
Ben Kwa2036dad2016-02-10 07:46:35 -080092 mList = (ListView) view.findViewById(R.id.roots_list);
Jeff Sharkey66516692013-08-06 11:26:10 -070093 mList.setOnItemClickListener(mItemListener);
Jeff Sharkey28c05ee2013-09-06 13:22:09 -070094 mList.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
Jeff Sharkey66516692013-08-06 11:26:10 -070095 return view;
96 }
97
Jeff Sharkey9fb567b2013-08-07 16:22:02 -070098 @Override
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070099 public void onActivityCreated(Bundle savedInstanceState) {
100 super.onActivityCreated(savedInstanceState);
101
102 final Context context = getActivity();
103 final RootsCache roots = DocumentsApplication.getRootsCache(context);
Steve McKaye934ce62015-03-25 14:35:33 -0700104 final State state = ((BaseActivity) context).getDisplayState();
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700105
106 mCallbacks = new LoaderCallbacks<Collection<RootInfo>>() {
107 @Override
108 public Loader<Collection<RootInfo>> onCreateLoader(int id, Bundle args) {
109 return new RootsLoader(context, roots, state);
110 }
111
112 @Override
113 public void onLoadFinished(
114 Loader<Collection<RootInfo>> loader, Collection<RootInfo> result) {
Steve McKay4a1ca862016-02-17 18:25:47 -0800115 if (!isAdded()) {
116 return;
117 }
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700118
Steve McKay4a1ca862016-02-17 18:25:47 -0800119 Intent handlerAppIntent = getArguments().getParcelable(EXTRA_INCLUDE_APPS);
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700120
Aga Wronska973168c2016-03-28 17:27:02 -0700121 mAdapter = new RootsAdapter(context, result, handlerAppIntent, state);
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700122 mList.setAdapter(mAdapter);
123
124 onCurrentRootChanged();
125 }
126
127 @Override
128 public void onLoaderReset(Loader<Collection<RootInfo>> loader) {
129 mAdapter = null;
130 mList.setAdapter(null);
131 }
132 };
Jeff Sharkey348ad682013-09-02 17:19:40 -0700133 }
Jeff Sharkey9fb567b2013-08-07 16:22:02 -0700134
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700135 @Override
136 public void onResume() {
137 super.onResume();
Jeff Sharkey669f8e72014-08-08 15:10:03 -0700138 onDisplayStateChanged();
139 }
Jeff Sharkey348ad682013-09-02 17:19:40 -0700140
Jeff Sharkey669f8e72014-08-08 15:10:03 -0700141 public void onDisplayStateChanged() {
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700142 final Context context = getActivity();
Steve McKaye934ce62015-03-25 14:35:33 -0700143 final State state = ((BaseActivity) context).getDisplayState();
Jeff Sharkey348ad682013-09-02 17:19:40 -0700144
Steve McKaye934ce62015-03-25 14:35:33 -0700145 if (state.action == State.ACTION_GET_CONTENT) {
Jeff Sharkeyc24bbd42013-10-23 16:05:56 -0700146 mList.setOnItemLongClickListener(mItemLongClickListener);
147 } else {
148 mList.setOnItemLongClickListener(null);
149 mList.setLongClickable(false);
150 }
151
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700152 getLoaderManager().restartLoader(2, null, mCallbacks);
Jeff Sharkey28c05ee2013-09-06 13:22:09 -0700153 }
154
155 public void onCurrentRootChanged() {
Steve McKay4a1ca862016-02-17 18:25:47 -0800156 if (mAdapter == null) {
157 return;
158 }
Jeff Sharkey28c05ee2013-09-06 13:22:09 -0700159
Steve McKaye934ce62015-03-25 14:35:33 -0700160 final RootInfo root = ((BaseActivity) getActivity()).getCurrentRoot();
Jeff Sharkey28c05ee2013-09-06 13:22:09 -0700161 for (int i = 0; i < mAdapter.getCount(); i++) {
162 final Object item = mAdapter.getItem(i);
Jeff Sharkeya82c2e22013-10-07 14:08:17 -0700163 if (item instanceof RootItem) {
164 final RootInfo testRoot = ((RootItem) item).root;
Kenny Roote6585b32013-12-13 12:00:26 -0800165 if (Objects.equals(testRoot, root)) {
Jeff Sharkeya82c2e22013-10-07 14:08:17 -0700166 mList.setItemChecked(i, true);
Aga Wronska8e1c9632016-02-02 15:00:18 -0800167 mList.setSelection(i);
Jeff Sharkeya82c2e22013-10-07 14:08:17 -0700168 return;
169 }
Jeff Sharkey28c05ee2013-09-06 13:22:09 -0700170 }
171 }
Jeff Sharkey9fb567b2013-08-07 16:22:02 -0700172 }
173
Ben Kwa2036dad2016-02-10 07:46:35 -0800174 /**
175 * Attempts to shift focus back to the navigation drawer.
176 */
177 public void requestFocus() {
178 mList.requestFocus();
179 }
180
Jeff Sharkeyc24bbd42013-10-23 16:05:56 -0700181 private void showAppDetails(ResolveInfo ri) {
182 final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
183 intent.setData(Uri.fromParts("package", ri.activityInfo.packageName, null));
184 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
185 startActivity(intent);
186 }
187
Jeff Sharkey66516692013-08-06 11:26:10 -0700188 private OnItemClickListener mItemListener = new OnItemClickListener() {
189 @Override
190 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700191 Item item = mAdapter.getItem(position);
Jeff Sharkey6efba222013-09-27 16:44:11 -0700192 if (item instanceof RootItem) {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700193 BaseActivity activity = BaseActivity.get(RootsFragment.this);
Aga Wronska45f75e22016-02-02 18:01:58 -0800194 RootInfo newRoot = ((RootItem) item).root;
195 Metrics.logRootVisited(getActivity(), newRoot);
196 activity.onRootPicked(newRoot);
Jeff Sharkey6efba222013-09-27 16:44:11 -0700197 } else if (item instanceof AppItem) {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700198 DocumentsActivity activity = DocumentsActivity.get(RootsFragment.this);
Ben Kwa72379982016-01-26 11:50:03 -0800199 ResolveInfo info = ((AppItem) item).info;
200 Metrics.logAppVisited(getActivity(), info);
201 activity.onAppPicked(info);
Steve McKay35820cf2015-11-03 14:00:45 -0800202 } else if (item instanceof SpacerItem) {
203 if (DEBUG) Log.d(TAG, "Ignoring click on spacer item.");
Jeff Sharkey54ca29a2013-08-15 11:24:03 -0700204 } else {
205 throw new IllegalStateException("Unknown root: " + item);
206 }
Jeff Sharkey66516692013-08-06 11:26:10 -0700207 }
208 };
209
Jeff Sharkeyc24bbd42013-10-23 16:05:56 -0700210 private OnItemLongClickListener mItemLongClickListener = new OnItemLongClickListener() {
211 @Override
212 public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
213 final Item item = mAdapter.getItem(position);
214 if (item instanceof AppItem) {
215 showAppDetails(((AppItem) item).info);
216 return true;
217 } else {
218 return false;
219 }
220 }
221 };
222
Jeff Sharkey6efba222013-09-27 16:44:11 -0700223 private static abstract class Item {
224 private final int mLayoutId;
225
226 public Item(int layoutId) {
227 mLayoutId = layoutId;
228 }
229
230 public View getView(View convertView, ViewGroup parent) {
231 if (convertView == null) {
232 convertView = LayoutInflater.from(parent.getContext())
233 .inflate(mLayoutId, parent, false);
234 }
235 bindView(convertView);
236 return convertView;
237 }
238
239 public abstract void bindView(View convertView);
240 }
241
242 private static class RootItem extends Item {
243 public final RootInfo root;
244
245 public RootItem(RootInfo root) {
246 super(R.layout.item_root);
247 this.root = root;
Jeff Sharkey66516692013-08-06 11:26:10 -0700248 }
249
250 @Override
Jeff Sharkey6efba222013-09-27 16:44:11 -0700251 public void bindView(View convertView) {
Jeff Sharkey66516692013-08-06 11:26:10 -0700252 final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
253 final TextView title = (TextView) convertView.findViewById(android.R.id.title);
254 final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
255
Jeff Sharkey6efba222013-09-27 16:44:11 -0700256 final Context context = convertView.getContext();
Jeff Sharkeycbce4702014-08-29 15:38:27 -0700257 icon.setImageDrawable(root.loadDrawerIcon(context));
Jeff Sharkey66516692013-08-06 11:26:10 -0700258 title.setText(root.title);
259
Jeff Sharkey6efba222013-09-27 16:44:11 -0700260 // Show available space if no summary
261 String summaryText = root.summary;
262 if (TextUtils.isEmpty(summaryText) && root.availableBytes >= 0) {
Jeff Sharkey9fb567b2013-08-07 16:22:02 -0700263 summaryText = context.getString(R.string.root_available_bytes,
264 Formatter.formatFileSize(context, root.availableBytes));
Jeff Sharkey9fb567b2013-08-07 16:22:02 -0700265 }
266
267 summary.setText(summaryText);
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700268 summary.setVisibility(TextUtils.isEmpty(summaryText) ? View.GONE : View.VISIBLE);
Jeff Sharkey66516692013-08-06 11:26:10 -0700269 }
270 }
271
Jeff Sharkey6efba222013-09-27 16:44:11 -0700272 private static class SpacerItem extends Item {
273 public SpacerItem() {
274 super(R.layout.item_root_spacer);
Jeff Sharkey54ca29a2013-08-15 11:24:03 -0700275 }
276
277 @Override
Jeff Sharkey6efba222013-09-27 16:44:11 -0700278 public void bindView(View convertView) {
279 // Nothing to bind
280 }
281 }
Jeff Sharkey54ca29a2013-08-15 11:24:03 -0700282
Jeff Sharkey6efba222013-09-27 16:44:11 -0700283 private static class AppItem extends Item {
284 public final ResolveInfo info;
285
286 public AppItem(ResolveInfo info) {
287 super(R.layout.item_root);
288 this.info = info;
289 }
290
291 @Override
292 public void bindView(View convertView) {
Jeff Sharkey54ca29a2013-08-15 11:24:03 -0700293 final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
294 final TextView title = (TextView) convertView.findViewById(android.R.id.title);
295 final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
296
Jeff Sharkey6efba222013-09-27 16:44:11 -0700297 final PackageManager pm = convertView.getContext().getPackageManager();
Jeff Sharkey54ca29a2013-08-15 11:24:03 -0700298 icon.setImageDrawable(info.loadIcon(pm));
299 title.setText(info.loadLabel(pm));
300
301 // TODO: match existing summary behavior from disambig dialog
302 summary.setVisibility(View.GONE);
Jeff Sharkey54ca29a2013-08-15 11:24:03 -0700303 }
304 }
305
Jeff Sharkey6efba222013-09-27 16:44:11 -0700306 private static class RootsAdapter extends ArrayAdapter<Item> {
Steve McKay4a1ca862016-02-17 18:25:47 -0800307
308 /**
309 * @param handlerAppIntent When not null, apps capable of handling the original
310 * intent will be included in list of roots (in special section at bottom).
311 */
Aga Wronska973168c2016-03-28 17:27:02 -0700312 public RootsAdapter(Context context, Collection<RootInfo> roots,
313 @Nullable Intent handlerAppIntent, State state) {
Jeff Sharkey6efba222013-09-27 16:44:11 -0700314 super(context, 0);
Jeff Sharkey66516692013-08-06 11:26:10 -0700315
Ben Kwa0c643082015-10-09 07:48:00 -0700316 final List<RootItem> libraries = new ArrayList<>();
Daichi Hironoabf39742015-10-23 08:36:10 +0900317 final List<RootItem> others = new ArrayList<>();
Jeff Sharkey66516692013-08-06 11:26:10 -0700318
Daichi Hironoabf39742015-10-23 08:36:10 +0900319 for (final RootInfo root : roots) {
320 final RootItem item = new RootItem(root);
Aga Wronskab4129d72016-03-17 11:48:20 -0700321
Aga Wronska4e627162016-03-22 14:18:43 -0700322 if (root.isHome() && Shared.isHomeRootHidden(context)) {
Aga Wronskab4129d72016-03-17 11:48:20 -0700323 continue;
Aga Wronska973168c2016-03-28 17:27:02 -0700324 } else if (root.isAdvanced()
325 && Shared.areAdvancedRootsHidden(context, state)) {
Aga Wronska1719b352016-03-21 11:28:03 -0700326 continue;
Aga Wronskab4129d72016-03-17 11:48:20 -0700327 } else if (root.isLibrary()) {
Steve McKay3ce95952016-02-02 11:41:03 -0800328 if (DEBUG) Log.d(TAG, "Adding " + root + " as library.");
Daichi Hironoabf39742015-10-23 08:36:10 +0900329 libraries.add(item);
330 } else {
Steve McKay3ce95952016-02-02 11:41:03 -0800331 if (DEBUG) Log.d(TAG, "Adding " + root + " as non-library.");
Daichi Hironoabf39742015-10-23 08:36:10 +0900332 others.add(item);
Jeff Sharkey5545f562013-09-21 13:57:33 -0700333 }
Jeff Sharkey6efba222013-09-27 16:44:11 -0700334 }
Jeff Sharkey5545f562013-09-21 13:57:33 -0700335
Jeff Sharkey6efba222013-09-27 16:44:11 -0700336 final RootComparator comp = new RootComparator();
Ben Kwa0c643082015-10-09 07:48:00 -0700337 Collections.sort(libraries, comp);
Daichi Hironoabf39742015-10-23 08:36:10 +0900338 Collections.sort(others, comp);
Jeff Sharkey6efba222013-09-27 16:44:11 -0700339
Ben Kwa0c643082015-10-09 07:48:00 -0700340 addAll(libraries);
Steve McKay4a1ca862016-02-17 18:25:47 -0800341 // Only add the spacer if it is actually separating something.
342 if (!libraries.isEmpty() && !others.isEmpty()) {
343 add(new SpacerItem());
344 }
Daichi Hironoabf39742015-10-23 08:36:10 +0900345 addAll(others);
Jeff Sharkey66516692013-08-06 11:26:10 -0700346
Steve McKay4a1ca862016-02-17 18:25:47 -0800347 // Include apps that can handle this intent too.
348 if (handlerAppIntent != null) {
349 includeHandlerApps(context, handlerAppIntent);
350 }
351 }
Jeff Sharkey54ca29a2013-08-15 11:24:03 -0700352
Steve McKay4a1ca862016-02-17 18:25:47 -0800353 /**
354 * Adds apps capable of handling the original intent will be included
355 * in list of roots (in special section at bottom).
356 */
357 private void includeHandlerApps(Context context, Intent handlerAppIntent) {
358 final PackageManager pm = context.getPackageManager();
359 final List<ResolveInfo> infos = pm.queryIntentActivities(
360 handlerAppIntent, PackageManager.MATCH_DEFAULT_ONLY);
Jeff Sharkey6efba222013-09-27 16:44:11 -0700361
Steve McKay4a1ca862016-02-17 18:25:47 -0800362 final List<AppItem> apps = new ArrayList<>();
363
364 // Omit ourselves from the list
365 for (ResolveInfo info : infos) {
366 if (!context.getPackageName().equals(info.activityInfo.packageName)) {
367 apps.add(new AppItem(info));
Jeff Sharkey6efba222013-09-27 16:44:11 -0700368 }
Steve McKay4a1ca862016-02-17 18:25:47 -0800369 }
Jeff Sharkey6efba222013-09-27 16:44:11 -0700370
Steve McKay4a1ca862016-02-17 18:25:47 -0800371 if (apps.size() > 0) {
372 add(new SpacerItem());
373 addAll(apps);
Jeff Sharkey54ca29a2013-08-15 11:24:03 -0700374 }
Jeff Sharkey6efba222013-09-27 16:44:11 -0700375 }
Jeff Sharkey54ca29a2013-08-15 11:24:03 -0700376
Jeff Sharkey6efba222013-09-27 16:44:11 -0700377 @Override
378 public View getView(int position, View convertView, ViewGroup parent) {
379 final Item item = getItem(position);
380 return item.getView(convertView, parent);
381 }
Jeff Sharkey66516692013-08-06 11:26:10 -0700382
Jeff Sharkey6efba222013-09-27 16:44:11 -0700383 @Override
384 public boolean areAllItemsEnabled() {
385 return false;
386 }
387
388 @Override
389 public boolean isEnabled(int position) {
390 return getItemViewType(position) != 1;
391 }
392
393 @Override
394 public int getItemViewType(int position) {
395 final Item item = getItem(position);
396 if (item instanceof RootItem || item instanceof AppItem) {
397 return 0;
398 } else {
399 return 1;
Jeff Sharkey5545f562013-09-21 13:57:33 -0700400 }
Jeff Sharkey6efba222013-09-27 16:44:11 -0700401 }
402
403 @Override
404 public int getViewTypeCount() {
405 return 2;
Jeff Sharkey66516692013-08-06 11:26:10 -0700406 }
407 }
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700408
Ben Kwa0c643082015-10-09 07:48:00 -0700409 public static class RootComparator implements Comparator<RootItem> {
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700410 @Override
Ben Kwa0c643082015-10-09 07:48:00 -0700411 public int compare(RootItem lhs, RootItem rhs) {
Steve McKay008e9482016-02-18 15:32:16 -0800412 return lhs.root.compareTo(rhs.root);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700413 }
414 }
Jeff Sharkey66516692013-08-06 11:26:10 -0700415}