blob: d3a35d95fe32951ec0a71a532689dbbac46556a1 [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
Jeff Sharkey76112212013-08-06 11:26:10 -070021import android.app.Fragment;
22import android.app.FragmentManager;
23import android.app.FragmentTransaction;
Jeff Sharkey8b997042013-09-19 15:25:56 -070024import android.app.LoaderManager.LoaderCallbacks;
Jeff Sharkey76112212013-08-06 11:26:10 -070025import android.content.Context;
Jeff Sharkey1d890e02013-08-15 11:24:03 -070026import android.content.Intent;
Jeff Sharkey8b997042013-09-19 15:25:56 -070027import android.content.Loader;
Jeff Sharkey1d890e02013-08-15 11:24:03 -070028import android.content.pm.PackageManager;
29import android.content.pm.ResolveInfo;
Jeff Sharkey8f1edfe2013-10-23 16:05:56 -070030import android.net.Uri;
Jeff Sharkey76112212013-08-06 11:26:10 -070031import android.os.Bundle;
Jeff Sharkey8f1edfe2013-10-23 16:05:56 -070032import android.provider.Settings;
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -070033import android.text.TextUtils;
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -070034import android.text.format.Formatter;
Steve McKaycc460af2015-11-03 14:00:45 -080035import android.util.Log;
Jeff Sharkey76112212013-08-06 11:26:10 -070036import android.view.LayoutInflater;
37import android.view.View;
38import android.view.ViewGroup;
39import android.widget.AdapterView;
40import android.widget.AdapterView.OnItemClickListener;
Jeff Sharkey8f1edfe2013-10-23 16:05:56 -070041import android.widget.AdapterView.OnItemLongClickListener;
Jeff Sharkey76112212013-08-06 11:26:10 -070042import android.widget.ArrayAdapter;
43import android.widget.ImageView;
44import android.widget.ListView;
45import android.widget.TextView;
46
Jeff Sharkey724deeb2013-08-31 15:02:20 -070047import com.android.documentsui.model.DocumentInfo;
48import com.android.documentsui.model.RootInfo;
Jeff Sharkey76112212013-08-06 11:26:10 -070049
Steve McKayfefcd702015-08-20 16:19:38 +000050import java.util.ArrayList;
Jeff Sharkey8b997042013-09-19 15:25:56 -070051import java.util.Collection;
Jeff Sharkey9dd02622013-09-27 16:44:11 -070052import java.util.Collections;
Jeff Sharkeyb6a7f2c2013-08-27 18:26:48 -070053import java.util.Comparator;
Jeff Sharkey1d890e02013-08-15 11:24:03 -070054import java.util.List;
Kenny Root16d85982013-12-13 12:00:26 -080055import java.util.Objects;
Jeff Sharkey76112212013-08-06 11:26:10 -070056
57/**
58 * Display list of known storage backend roots.
59 */
60public class RootsFragment extends Fragment {
61
Steve McKaycc460af2015-11-03 14:00:45 -080062 private static final String TAG = "RootsFragment";
63 private static final String EXTRA_INCLUDE_APPS = "includeApps";
64
Jeff Sharkey76112212013-08-06 11:26:10 -070065 private ListView mList;
Jeff Sharkey9dd02622013-09-27 16:44:11 -070066 private RootsAdapter mAdapter;
Jeff Sharkey8b997042013-09-19 15:25:56 -070067 private LoaderCallbacks<Collection<RootInfo>> mCallbacks;
68
Jeff Sharkey1d890e02013-08-15 11:24:03 -070069
70 public static void show(FragmentManager fm, Intent includeApps) {
71 final Bundle args = new Bundle();
72 args.putParcelable(EXTRA_INCLUDE_APPS, includeApps);
73
Jeff Sharkey76112212013-08-06 11:26:10 -070074 final RootsFragment fragment = new RootsFragment();
Jeff Sharkey1d890e02013-08-15 11:24:03 -070075 fragment.setArguments(args);
Jeff Sharkey76112212013-08-06 11:26:10 -070076
77 final FragmentTransaction ft = fm.beginTransaction();
78 ft.replace(R.id.container_roots, fragment);
79 ft.commitAllowingStateLoss();
80 }
81
82 public static RootsFragment get(FragmentManager fm) {
83 return (RootsFragment) fm.findFragmentById(R.id.container_roots);
84 }
85
86 @Override
87 public View onCreateView(
88 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
89 final Context context = inflater.getContext();
90
91 final View view = inflater.inflate(R.layout.fragment_roots, container, false);
92 mList = (ListView) view.findViewById(android.R.id.list);
Jeff Sharkey76112212013-08-06 11:26:10 -070093 mList.setOnItemClickListener(mItemListener);
Jeff Sharkey42d26792013-09-06 13:22:09 -070094 mList.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
Jeff Sharkey76112212013-08-06 11:26:10 -070095
96 return view;
97 }
98
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -070099 @Override
Jeff Sharkey8b997042013-09-19 15:25:56 -0700100 public void onActivityCreated(Bundle savedInstanceState) {
101 super.onActivityCreated(savedInstanceState);
102
103 final Context context = getActivity();
104 final RootsCache roots = DocumentsApplication.getRootsCache(context);
Steve McKayd0a2a2c2015-03-25 14:35:33 -0700105 final State state = ((BaseActivity) context).getDisplayState();
Jeff Sharkey8b997042013-09-19 15:25:56 -0700106
107 mCallbacks = new LoaderCallbacks<Collection<RootInfo>>() {
108 @Override
109 public Loader<Collection<RootInfo>> onCreateLoader(int id, Bundle args) {
110 return new RootsLoader(context, roots, state);
111 }
112
113 @Override
114 public void onLoadFinished(
115 Loader<Collection<RootInfo>> loader, Collection<RootInfo> result) {
116 if (!isAdded()) return;
117
118 final Intent includeApps = getArguments().getParcelable(EXTRA_INCLUDE_APPS);
119
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700120 mAdapter = new RootsAdapter(context, result, includeApps);
Jeff Sharkey8b997042013-09-19 15:25:56 -0700121 mList.setAdapter(mAdapter);
122
123 onCurrentRootChanged();
124 }
125
126 @Override
127 public void onLoaderReset(Loader<Collection<RootInfo>> loader) {
128 mAdapter = null;
129 mList.setAdapter(null);
130 }
131 };
Jeff Sharkey1c903cc2013-09-02 17:19:40 -0700132 }
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700133
Jeff Sharkey8b997042013-09-19 15:25:56 -0700134 @Override
135 public void onResume() {
136 super.onResume();
Jeff Sharkeye8d13ea2014-08-08 15:10:03 -0700137 onDisplayStateChanged();
138 }
Jeff Sharkey1c903cc2013-09-02 17:19:40 -0700139
Jeff Sharkeye8d13ea2014-08-08 15:10:03 -0700140 public void onDisplayStateChanged() {
Jeff Sharkey8b997042013-09-19 15:25:56 -0700141 final Context context = getActivity();
Steve McKayd0a2a2c2015-03-25 14:35:33 -0700142 final State state = ((BaseActivity) context).getDisplayState();
Jeff Sharkey1c903cc2013-09-02 17:19:40 -0700143
Steve McKayd0a2a2c2015-03-25 14:35:33 -0700144 if (state.action == State.ACTION_GET_CONTENT) {
Jeff Sharkey8f1edfe2013-10-23 16:05:56 -0700145 mList.setOnItemLongClickListener(mItemLongClickListener);
146 } else {
147 mList.setOnItemLongClickListener(null);
148 mList.setLongClickable(false);
149 }
150
Jeff Sharkey8b997042013-09-19 15:25:56 -0700151 getLoaderManager().restartLoader(2, null, mCallbacks);
Jeff Sharkey42d26792013-09-06 13:22:09 -0700152 }
153
154 public void onCurrentRootChanged() {
155 if (mAdapter == null) return;
156
Steve McKayd0a2a2c2015-03-25 14:35:33 -0700157 final RootInfo root = ((BaseActivity) getActivity()).getCurrentRoot();
Jeff Sharkey42d26792013-09-06 13:22:09 -0700158 for (int i = 0; i < mAdapter.getCount(); i++) {
159 final Object item = mAdapter.getItem(i);
Jeff Sharkey25f10b32013-10-07 14:08:17 -0700160 if (item instanceof RootItem) {
161 final RootInfo testRoot = ((RootItem) item).root;
Kenny Root16d85982013-12-13 12:00:26 -0800162 if (Objects.equals(testRoot, root)) {
Jeff Sharkey25f10b32013-10-07 14:08:17 -0700163 mList.setItemChecked(i, true);
164 return;
165 }
Jeff Sharkey42d26792013-09-06 13:22:09 -0700166 }
167 }
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700168 }
169
Jeff Sharkey8f1edfe2013-10-23 16:05:56 -0700170 private void showAppDetails(ResolveInfo ri) {
171 final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
172 intent.setData(Uri.fromParts("package", ri.activityInfo.packageName, null));
173 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
174 startActivity(intent);
175 }
176
Jeff Sharkey76112212013-08-06 11:26:10 -0700177 private OnItemClickListener mItemListener = new OnItemClickListener() {
178 @Override
179 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Steve McKayb68dd222015-04-20 17:18:15 -0700180 Item item = mAdapter.getItem(position);
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700181 if (item instanceof RootItem) {
Steve McKayb68dd222015-04-20 17:18:15 -0700182 BaseActivity activity = BaseActivity.get(RootsFragment.this);
Ben Kwa1c0a3892016-01-26 11:50:03 -0800183 RootInfo info = ((RootItem) item).root;
184 Metrics.logRootVisited(getActivity(), info);
185 activity.onRootPicked(info);
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700186 } else if (item instanceof AppItem) {
Steve McKayb68dd222015-04-20 17:18:15 -0700187 DocumentsActivity activity = DocumentsActivity.get(RootsFragment.this);
Ben Kwa1c0a3892016-01-26 11:50:03 -0800188 ResolveInfo info = ((AppItem) item).info;
189 Metrics.logAppVisited(getActivity(), info);
190 activity.onAppPicked(info);
Steve McKaycc460af2015-11-03 14:00:45 -0800191 } else if (item instanceof SpacerItem) {
192 if (DEBUG) Log.d(TAG, "Ignoring click on spacer item.");
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700193 } else {
194 throw new IllegalStateException("Unknown root: " + item);
195 }
Jeff Sharkey76112212013-08-06 11:26:10 -0700196 }
197 };
198
Jeff Sharkey8f1edfe2013-10-23 16:05:56 -0700199 private OnItemLongClickListener mItemLongClickListener = new OnItemLongClickListener() {
200 @Override
201 public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
202 final Item item = mAdapter.getItem(position);
203 if (item instanceof AppItem) {
204 showAppDetails(((AppItem) item).info);
205 return true;
206 } else {
207 return false;
208 }
209 }
210 };
211
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700212 private static abstract class Item {
213 private final int mLayoutId;
214
215 public Item(int layoutId) {
216 mLayoutId = layoutId;
217 }
218
219 public View getView(View convertView, ViewGroup parent) {
220 if (convertView == null) {
221 convertView = LayoutInflater.from(parent.getContext())
222 .inflate(mLayoutId, parent, false);
223 }
224 bindView(convertView);
225 return convertView;
226 }
227
228 public abstract void bindView(View convertView);
229 }
230
231 private static class RootItem extends Item {
232 public final RootInfo root;
233
234 public RootItem(RootInfo root) {
235 super(R.layout.item_root);
236 this.root = root;
Jeff Sharkey76112212013-08-06 11:26:10 -0700237 }
238
239 @Override
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700240 public void bindView(View convertView) {
Jeff Sharkey76112212013-08-06 11:26:10 -0700241 final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
242 final TextView title = (TextView) convertView.findViewById(android.R.id.title);
243 final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
244
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700245 final Context context = convertView.getContext();
Jeff Sharkey7e544612014-08-29 15:38:27 -0700246 icon.setImageDrawable(root.loadDrawerIcon(context));
Jeff Sharkey76112212013-08-06 11:26:10 -0700247 title.setText(root.title);
248
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700249 // Show available space if no summary
250 String summaryText = root.summary;
251 if (TextUtils.isEmpty(summaryText) && root.availableBytes >= 0) {
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700252 summaryText = context.getString(R.string.root_available_bytes,
253 Formatter.formatFileSize(context, root.availableBytes));
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700254 }
255
256 summary.setText(summaryText);
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700257 summary.setVisibility(TextUtils.isEmpty(summaryText) ? View.GONE : View.VISIBLE);
Jeff Sharkey76112212013-08-06 11:26:10 -0700258 }
259 }
260
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700261 private static class SpacerItem extends Item {
262 public SpacerItem() {
263 super(R.layout.item_root_spacer);
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700264 }
265
266 @Override
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700267 public void bindView(View convertView) {
268 // Nothing to bind
269 }
270 }
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700271
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700272 private static class AppItem extends Item {
273 public final ResolveInfo info;
274
275 public AppItem(ResolveInfo info) {
276 super(R.layout.item_root);
277 this.info = info;
278 }
279
280 @Override
281 public void bindView(View convertView) {
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700282 final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
283 final TextView title = (TextView) convertView.findViewById(android.R.id.title);
284 final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
285
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700286 final PackageManager pm = convertView.getContext().getPackageManager();
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700287 icon.setImageDrawable(info.loadIcon(pm));
288 title.setText(info.loadLabel(pm));
289
290 // TODO: match existing summary behavior from disambig dialog
291 summary.setVisibility(View.GONE);
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700292 }
293 }
294
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700295 private static class RootsAdapter extends ArrayAdapter<Item> {
296 public RootsAdapter(Context context, Collection<RootInfo> roots, Intent includeApps) {
297 super(context, 0);
Jeff Sharkey76112212013-08-06 11:26:10 -0700298
Ben Kwa85c8cdd2015-10-09 07:48:00 -0700299 final List<RootItem> libraries = new ArrayList<>();
Daichi Hirono9fb33892015-10-23 08:36:10 +0900300 final List<RootItem> others = new ArrayList<>();
Jeff Sharkey76112212013-08-06 11:26:10 -0700301
Daichi Hirono9fb33892015-10-23 08:36:10 +0900302 for (final RootInfo root : roots) {
303 final RootItem item = new RootItem(root);
Ben Kwad18e7852016-01-08 17:24:59 -0800304 if (root.isLibrary()) {
Steve McKayd4800812016-02-02 11:41:03 -0800305 if (DEBUG) Log.d(TAG, "Adding " + root + " as library.");
Daichi Hirono9fb33892015-10-23 08:36:10 +0900306 libraries.add(item);
307 } else {
Steve McKayd4800812016-02-02 11:41:03 -0800308 if (DEBUG) Log.d(TAG, "Adding " + root + " as non-library.");
Daichi Hirono9fb33892015-10-23 08:36:10 +0900309 others.add(item);
Jeff Sharkey40457802013-09-21 13:57:33 -0700310 }
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700311 }
Jeff Sharkey40457802013-09-21 13:57:33 -0700312
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700313 final RootComparator comp = new RootComparator();
Ben Kwa85c8cdd2015-10-09 07:48:00 -0700314 Collections.sort(libraries, comp);
Daichi Hirono9fb33892015-10-23 08:36:10 +0900315 Collections.sort(others, comp);
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700316
Ben Kwa85c8cdd2015-10-09 07:48:00 -0700317 addAll(libraries);
318 add(new SpacerItem());
Daichi Hirono9fb33892015-10-23 08:36:10 +0900319 addAll(others);
Jeff Sharkey76112212013-08-06 11:26:10 -0700320
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700321 if (includeApps != null) {
322 final PackageManager pm = context.getPackageManager();
323 final List<ResolveInfo> infos = pm.queryIntentActivities(
324 includeApps, PackageManager.MATCH_DEFAULT_ONLY);
325
Steve McKayfefcd702015-08-20 16:19:38 +0000326 final List<AppItem> apps = new ArrayList<>();
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700327
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700328 // Omit ourselves from the list
329 for (ResolveInfo info : infos) {
330 if (!context.getPackageName().equals(info.activityInfo.packageName)) {
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700331 apps.add(new AppItem(info));
332 }
333 }
334
335 if (apps.size() > 0) {
336 add(new SpacerItem());
Ben Kwa85c8cdd2015-10-09 07:48:00 -0700337 addAll(apps);
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700338 }
339 }
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700340 }
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700341
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700342 @Override
343 public View getView(int position, View convertView, ViewGroup parent) {
344 final Item item = getItem(position);
345 return item.getView(convertView, parent);
346 }
Jeff Sharkey76112212013-08-06 11:26:10 -0700347
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700348 @Override
349 public boolean areAllItemsEnabled() {
350 return false;
351 }
352
353 @Override
354 public boolean isEnabled(int position) {
355 return getItemViewType(position) != 1;
356 }
357
358 @Override
359 public int getItemViewType(int position) {
360 final Item item = getItem(position);
361 if (item instanceof RootItem || item instanceof AppItem) {
362 return 0;
363 } else {
364 return 1;
Jeff Sharkey40457802013-09-21 13:57:33 -0700365 }
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700366 }
367
368 @Override
369 public int getViewTypeCount() {
370 return 2;
Jeff Sharkey76112212013-08-06 11:26:10 -0700371 }
372 }
Jeff Sharkeyb6a7f2c2013-08-27 18:26:48 -0700373
Ben Kwa85c8cdd2015-10-09 07:48:00 -0700374 public static class RootComparator implements Comparator<RootItem> {
Jeff Sharkeyb6a7f2c2013-08-27 18:26:48 -0700375 @Override
Ben Kwa85c8cdd2015-10-09 07:48:00 -0700376 public int compare(RootItem lhs, RootItem rhs) {
377 // Sort by root type, then title, then summary.
378 int score = lhs.root.derivedType - rhs.root.derivedType;
Jeff Sharkeyb6a7f2c2013-08-27 18:26:48 -0700379 if (score != 0) {
380 return score;
Jeff Sharkeyb6a7f2c2013-08-27 18:26:48 -0700381 }
Ben Kwa85c8cdd2015-10-09 07:48:00 -0700382 score = DocumentInfo.compareToIgnoreCaseNullable(lhs.root.title, rhs.root.title);
383 if (score != 0) {
384 return score;
385 }
386
387 return DocumentInfo.compareToIgnoreCaseNullable(lhs.root.summary, rhs.root.summary);
Jeff Sharkeyb6a7f2c2013-08-27 18:26:48 -0700388 }
389 }
Jeff Sharkey76112212013-08-06 11:26:10 -0700390}