blob: df9bce19d07fb6870f8b64f1e2332909741217d0 [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
Jeff Sharkey76112212013-08-06 11:26:10 -070019import android.app.Fragment;
20import android.app.FragmentManager;
21import android.app.FragmentTransaction;
Jeff Sharkey8b997042013-09-19 15:25:56 -070022import android.app.LoaderManager.LoaderCallbacks;
Jeff Sharkey76112212013-08-06 11:26:10 -070023import android.content.Context;
Jeff Sharkey1d890e02013-08-15 11:24:03 -070024import android.content.Intent;
Jeff Sharkey8b997042013-09-19 15:25:56 -070025import android.content.Loader;
Jeff Sharkey1d890e02013-08-15 11:24:03 -070026import android.content.pm.PackageManager;
27import android.content.pm.ResolveInfo;
Jeff Sharkey76112212013-08-06 11:26:10 -070028import android.os.Bundle;
Jeff Sharkey724deeb2013-08-31 15:02:20 -070029import android.provider.DocumentsContract.Root;
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -070030import android.text.TextUtils;
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -070031import android.text.format.Formatter;
Jeff Sharkey76112212013-08-06 11:26:10 -070032import android.view.LayoutInflater;
33import android.view.View;
34import android.view.ViewGroup;
35import android.widget.AdapterView;
36import android.widget.AdapterView.OnItemClickListener;
37import android.widget.ArrayAdapter;
38import android.widget.ImageView;
39import android.widget.ListView;
Jeff Sharkey9656a532013-09-13 13:42:19 -070040import android.widget.Space;
Jeff Sharkey76112212013-08-06 11:26:10 -070041import android.widget.TextView;
42
Jeff Sharkey1c903cc2013-09-02 17:19:40 -070043import com.android.documentsui.DocumentsActivity.State;
Jeff Sharkey76112212013-08-06 11:26:10 -070044import com.android.documentsui.SectionedListAdapter.SectionAdapter;
Jeff Sharkey724deeb2013-08-31 15:02:20 -070045import com.android.documentsui.model.DocumentInfo;
46import com.android.documentsui.model.RootInfo;
Jeff Sharkey42d26792013-09-06 13:22:09 -070047import com.android.internal.util.Objects;
Jeff Sharkey76112212013-08-06 11:26:10 -070048
Jeff Sharkey8b997042013-09-19 15:25:56 -070049import java.util.Collection;
Jeff Sharkeyb6a7f2c2013-08-27 18:26:48 -070050import java.util.Comparator;
Jeff Sharkey1d890e02013-08-15 11:24:03 -070051import java.util.List;
Jeff Sharkey76112212013-08-06 11:26:10 -070052
53/**
54 * Display list of known storage backend roots.
55 */
56public class RootsFragment extends Fragment {
57
58 private ListView mList;
59 private SectionedRootsAdapter mAdapter;
60
Jeff Sharkey8b997042013-09-19 15:25:56 -070061 private LoaderCallbacks<Collection<RootInfo>> mCallbacks;
62
Jeff Sharkey1d890e02013-08-15 11:24:03 -070063 private static final String EXTRA_INCLUDE_APPS = "includeApps";
64
65 public static void show(FragmentManager fm, Intent includeApps) {
66 final Bundle args = new Bundle();
67 args.putParcelable(EXTRA_INCLUDE_APPS, includeApps);
68
Jeff Sharkey76112212013-08-06 11:26:10 -070069 final RootsFragment fragment = new RootsFragment();
Jeff Sharkey1d890e02013-08-15 11:24:03 -070070 fragment.setArguments(args);
Jeff Sharkey76112212013-08-06 11:26:10 -070071
72 final FragmentTransaction ft = fm.beginTransaction();
73 ft.replace(R.id.container_roots, fragment);
74 ft.commitAllowingStateLoss();
75 }
76
77 public static RootsFragment get(FragmentManager fm) {
78 return (RootsFragment) fm.findFragmentById(R.id.container_roots);
79 }
80
81 @Override
82 public View onCreateView(
83 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
84 final Context context = inflater.getContext();
85
86 final View view = inflater.inflate(R.layout.fragment_roots, container, false);
87 mList = (ListView) view.findViewById(android.R.id.list);
Jeff Sharkey76112212013-08-06 11:26:10 -070088 mList.setOnItemClickListener(mItemListener);
Jeff Sharkey42d26792013-09-06 13:22:09 -070089 mList.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
Jeff Sharkey76112212013-08-06 11:26:10 -070090
91 return view;
92 }
93
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -070094 @Override
Jeff Sharkey8b997042013-09-19 15:25:56 -070095 public void onActivityCreated(Bundle savedInstanceState) {
96 super.onActivityCreated(savedInstanceState);
97
98 final Context context = getActivity();
99 final RootsCache roots = DocumentsApplication.getRootsCache(context);
100 final State state = ((DocumentsActivity) context).getDisplayState();
101
102 mCallbacks = new LoaderCallbacks<Collection<RootInfo>>() {
103 @Override
104 public Loader<Collection<RootInfo>> onCreateLoader(int id, Bundle args) {
105 return new RootsLoader(context, roots, state);
106 }
107
108 @Override
109 public void onLoadFinished(
110 Loader<Collection<RootInfo>> loader, Collection<RootInfo> result) {
111 if (!isAdded()) return;
112
113 final Intent includeApps = getArguments().getParcelable(EXTRA_INCLUDE_APPS);
114
115 mAdapter = new SectionedRootsAdapter(context, result, includeApps);
116 mList.setAdapter(mAdapter);
117
118 onCurrentRootChanged();
119 }
120
121 @Override
122 public void onLoaderReset(Loader<Collection<RootInfo>> loader) {
123 mAdapter = null;
124 mList.setAdapter(null);
125 }
126 };
Jeff Sharkey1c903cc2013-09-02 17:19:40 -0700127 }
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700128
Jeff Sharkey8b997042013-09-19 15:25:56 -0700129 @Override
130 public void onResume() {
131 super.onResume();
Jeff Sharkey1c903cc2013-09-02 17:19:40 -0700132
Jeff Sharkey8b997042013-09-19 15:25:56 -0700133 final Context context = getActivity();
Jeff Sharkey1c903cc2013-09-02 17:19:40 -0700134 final State state = ((DocumentsActivity) context).getDisplayState();
135 state.showAdvanced = SettingsActivity.getDisplayAdvancedDevices(context);
136
Jeff Sharkey8b997042013-09-19 15:25:56 -0700137 getLoaderManager().restartLoader(2, null, mCallbacks);
Jeff Sharkey42d26792013-09-06 13:22:09 -0700138 }
139
140 public void onCurrentRootChanged() {
141 if (mAdapter == null) return;
142
143 final RootInfo root = ((DocumentsActivity) getActivity()).getCurrentRoot();
144 for (int i = 0; i < mAdapter.getCount(); i++) {
145 final Object item = mAdapter.getItem(i);
146 if (Objects.equal(item, root)) {
147 mList.setItemChecked(i, true);
148 return;
149 }
150 }
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700151 }
152
Jeff Sharkey76112212013-08-06 11:26:10 -0700153 private OnItemClickListener mItemListener = new OnItemClickListener() {
154 @Override
155 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700156 final DocumentsActivity activity = DocumentsActivity.get(RootsFragment.this);
157 final Object item = mAdapter.getItem(position);
Jeff Sharkey724deeb2013-08-31 15:02:20 -0700158 if (item instanceof RootInfo) {
159 activity.onRootPicked((RootInfo) item, true);
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700160 } else if (item instanceof ResolveInfo) {
161 activity.onAppPicked((ResolveInfo) item);
162 } else {
163 throw new IllegalStateException("Unknown root: " + item);
164 }
Jeff Sharkey76112212013-08-06 11:26:10 -0700165 }
166 };
167
Jeff Sharkey724deeb2013-08-31 15:02:20 -0700168 private static class RootsAdapter extends ArrayAdapter<RootInfo> implements SectionAdapter {
Jeff Sharkey9656a532013-09-13 13:42:19 -0700169 public RootsAdapter(Context context) {
Jeff Sharkey76112212013-08-06 11:26:10 -0700170 super(context, 0);
Jeff Sharkey76112212013-08-06 11:26:10 -0700171 }
172
173 @Override
174 public View getView(int position, View convertView, ViewGroup parent) {
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700175 final Context context = parent.getContext();
Jeff Sharkey76112212013-08-06 11:26:10 -0700176 if (convertView == null) {
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700177 convertView = LayoutInflater.from(context)
Jeff Sharkey76112212013-08-06 11:26:10 -0700178 .inflate(R.layout.item_root, parent, false);
179 }
180
181 final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
182 final TextView title = (TextView) convertView.findViewById(android.R.id.title);
183 final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
184
Jeff Sharkey724deeb2013-08-31 15:02:20 -0700185 final RootInfo root = getItem(position);
Jeff Sharkeyb6a7f2c2013-08-27 18:26:48 -0700186 icon.setImageDrawable(root.loadIcon(context));
Jeff Sharkey76112212013-08-06 11:26:10 -0700187 title.setText(root.title);
188
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700189 // Device summary is always available space
190 final String summaryText;
Jeff Sharkey724deeb2013-08-31 15:02:20 -0700191 if (root.rootType == Root.ROOT_TYPE_DEVICE && root.availableBytes >= 0) {
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700192 summaryText = context.getString(R.string.root_available_bytes,
193 Formatter.formatFileSize(context, root.availableBytes));
194 } else {
195 summaryText = root.summary;
196 }
197
198 summary.setText(summaryText);
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700199 summary.setVisibility(TextUtils.isEmpty(summaryText) ? View.GONE : View.VISIBLE);
Jeff Sharkey76112212013-08-06 11:26:10 -0700200
201 return convertView;
202 }
203
204 @Override
205 public View getHeaderView(View convertView, ViewGroup parent) {
206 if (convertView == null) {
Jeff Sharkey9656a532013-09-13 13:42:19 -0700207 convertView = new Space(parent.getContext());
Jeff Sharkey76112212013-08-06 11:26:10 -0700208 }
Jeff Sharkey76112212013-08-06 11:26:10 -0700209 return convertView;
210 }
211 }
212
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700213 private static class AppsAdapter extends ArrayAdapter<ResolveInfo> implements SectionAdapter {
214 public AppsAdapter(Context context) {
215 super(context, 0);
216 }
217
218 @Override
219 public View getView(int position, View convertView, ViewGroup parent) {
220 final Context context = parent.getContext();
221 final PackageManager pm = context.getPackageManager();
222 if (convertView == null) {
223 convertView = LayoutInflater.from(context)
224 .inflate(R.layout.item_root, parent, false);
225 }
226
227 final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
228 final TextView title = (TextView) convertView.findViewById(android.R.id.title);
229 final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
230
231 final ResolveInfo info = getItem(position);
232 icon.setImageDrawable(info.loadIcon(pm));
233 title.setText(info.loadLabel(pm));
234
235 // TODO: match existing summary behavior from disambig dialog
236 summary.setVisibility(View.GONE);
237
238 return convertView;
239 }
240
241 @Override
242 public View getHeaderView(View convertView, ViewGroup parent) {
243 if (convertView == null) {
244 convertView = LayoutInflater.from(parent.getContext())
245 .inflate(R.layout.item_root_header, parent, false);
246 }
247
248 final TextView title = (TextView) convertView.findViewById(android.R.id.title);
249 title.setText(R.string.root_type_apps);
250
251 return convertView;
252 }
253 }
254
255 private static class SectionedRootsAdapter extends SectionedListAdapter {
Jeff Sharkey76112212013-08-06 11:26:10 -0700256 private final RootsAdapter mServices;
257 private final RootsAdapter mShortcuts;
258 private final RootsAdapter mDevices;
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700259 private final AppsAdapter mApps;
Jeff Sharkey76112212013-08-06 11:26:10 -0700260
Jeff Sharkey8b997042013-09-19 15:25:56 -0700261 public SectionedRootsAdapter(
262 Context context, Collection<RootInfo> roots, Intent includeApps) {
Jeff Sharkey9656a532013-09-13 13:42:19 -0700263 mServices = new RootsAdapter(context);
264 mShortcuts = new RootsAdapter(context);
265 mDevices = new RootsAdapter(context);
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700266 mApps = new AppsAdapter(context);
Jeff Sharkey76112212013-08-06 11:26:10 -0700267
Jeff Sharkey724deeb2013-08-31 15:02:20 -0700268 for (RootInfo root : roots) {
Jeff Sharkey76112212013-08-06 11:26:10 -0700269 switch (root.rootType) {
Jeff Sharkey724deeb2013-08-31 15:02:20 -0700270 case Root.ROOT_TYPE_SERVICE:
Jeff Sharkey76112212013-08-06 11:26:10 -0700271 mServices.add(root);
272 break;
Jeff Sharkey724deeb2013-08-31 15:02:20 -0700273 case Root.ROOT_TYPE_SHORTCUT:
Jeff Sharkey76112212013-08-06 11:26:10 -0700274 mShortcuts.add(root);
275 break;
Jeff Sharkey724deeb2013-08-31 15:02:20 -0700276 case Root.ROOT_TYPE_DEVICE:
Jeff Sharkey1c903cc2013-09-02 17:19:40 -0700277 mDevices.add(root);
Jeff Sharkey76112212013-08-06 11:26:10 -0700278 break;
279 }
280 }
281
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700282 if (includeApps != null) {
283 final PackageManager pm = context.getPackageManager();
284 final List<ResolveInfo> infos = pm.queryIntentActivities(
285 includeApps, PackageManager.MATCH_DEFAULT_ONLY);
286
287 // Omit ourselves from the list
288 for (ResolveInfo info : infos) {
289 if (!context.getPackageName().equals(info.activityInfo.packageName)) {
290 mApps.add(info);
291 }
292 }
293 }
294
Jeff Sharkey76112212013-08-06 11:26:10 -0700295 final RootComparator comp = new RootComparator();
296 mServices.sort(comp);
297 mShortcuts.sort(comp);
298 mDevices.sort(comp);
Jeff Sharkey76112212013-08-06 11:26:10 -0700299
Jeff Sharkey76112212013-08-06 11:26:10 -0700300 if (mShortcuts.getCount() > 0) {
301 addSection(mShortcuts);
302 }
Jeff Sharkey1c903cc2013-09-02 17:19:40 -0700303 if (mDevices.getCount() > 0) {
304 addSection(mDevices);
Jeff Sharkey76112212013-08-06 11:26:10 -0700305 }
Jeff Sharkey9656a532013-09-13 13:42:19 -0700306 if (mServices.getCount() > 0) {
307 addSection(mServices);
308 }
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700309 if (mApps.getCount() > 0) {
310 addSection(mApps);
311 }
Jeff Sharkey76112212013-08-06 11:26:10 -0700312 }
313 }
Jeff Sharkeyb6a7f2c2013-08-27 18:26:48 -0700314
Jeff Sharkey724deeb2013-08-31 15:02:20 -0700315 public static class RootComparator implements Comparator<RootInfo> {
Jeff Sharkeyb6a7f2c2013-08-27 18:26:48 -0700316 @Override
Jeff Sharkey724deeb2013-08-31 15:02:20 -0700317 public int compare(RootInfo lhs, RootInfo rhs) {
Jeff Sharkey1c903cc2013-09-02 17:19:40 -0700318 if (lhs.authority == null) {
319 return -1;
320 } else if (rhs.authority == null) {
321 return 1;
322 }
323
Jeff Sharkey724deeb2013-08-31 15:02:20 -0700324 final int score = DocumentInfo.compareToIgnoreCaseNullable(lhs.title, rhs.title);
Jeff Sharkeyb6a7f2c2013-08-27 18:26:48 -0700325 if (score != 0) {
326 return score;
327 } else {
Jeff Sharkey724deeb2013-08-31 15:02:20 -0700328 return DocumentInfo.compareToIgnoreCaseNullable(lhs.summary, rhs.summary);
Jeff Sharkeyb6a7f2c2013-08-27 18:26:48 -0700329 }
330 }
331 }
Jeff Sharkey76112212013-08-06 11:26:10 -0700332}