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 
17 package com.android.documentsui;
18 
19 import android.app.Fragment;
20 import android.app.FragmentManager;
21 import android.app.FragmentTransaction;
22 import android.app.LoaderManager.LoaderCallbacks;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.Loader;
26 import android.content.pm.PackageManager;
27 import android.content.pm.ResolveInfo;
28 import android.net.Uri;
29 import android.os.Bundle;
30 import android.provider.Settings;
31 import android.text.TextUtils;
32 import android.text.format.Formatter;
33 import android.view.LayoutInflater;
34 import android.view.View;
35 import android.view.ViewGroup;
36 import android.widget.AdapterView;
37 import android.widget.AdapterView.OnItemClickListener;
38 import android.widget.AdapterView.OnItemLongClickListener;
39 import android.widget.ArrayAdapter;
40 import android.widget.ImageView;
41 import android.widget.ListView;
42 import android.widget.TextView;
43 
44 import com.android.documentsui.BaseActivity.State;
45 import com.android.documentsui.model.DocumentInfo;
46 import com.android.documentsui.model.RootInfo;
47 import com.google.common.collect.Lists;
48 
49 import java.util.Collection;
50 import java.util.Collections;
51 import java.util.Comparator;
52 import java.util.List;
53 import java.util.Objects;
54 
55 /**
56  * Display list of known storage backend roots.
57  */
58 public class RootsFragment extends Fragment {
59 
60     private ListView mList;
61     private RootsAdapter mAdapter;
62 
63     private LoaderCallbacks<Collection<RootInfo>> mCallbacks;
64 
65     private static final String EXTRA_INCLUDE_APPS = "includeApps";
66 
show(FragmentManager fm, Intent includeApps)67     public static void show(FragmentManager fm, Intent includeApps) {
68         final Bundle args = new Bundle();
69         args.putParcelable(EXTRA_INCLUDE_APPS, includeApps);
70 
71         final RootsFragment fragment = new RootsFragment();
72         fragment.setArguments(args);
73 
74         final FragmentTransaction ft = fm.beginTransaction();
75         ft.replace(R.id.container_roots, fragment);
76         ft.commitAllowingStateLoss();
77     }
78 
get(FragmentManager fm)79     public static RootsFragment get(FragmentManager fm) {
80         return (RootsFragment) fm.findFragmentById(R.id.container_roots);
81     }
82 
83     @Override
onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)84     public View onCreateView(
85             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
86         final Context context = inflater.getContext();
87 
88         final View view = inflater.inflate(R.layout.fragment_roots, container, false);
89         mList = (ListView) view.findViewById(android.R.id.list);
90         mList.setOnItemClickListener(mItemListener);
91         mList.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
92 
93         return view;
94     }
95 
96     @Override
onActivityCreated(Bundle savedInstanceState)97     public void onActivityCreated(Bundle savedInstanceState) {
98         super.onActivityCreated(savedInstanceState);
99 
100         final Context context = getActivity();
101         final RootsCache roots = DocumentsApplication.getRootsCache(context);
102         final State state = ((BaseActivity) context).getDisplayState();
103 
104         mCallbacks = new LoaderCallbacks<Collection<RootInfo>>() {
105             @Override
106             public Loader<Collection<RootInfo>> onCreateLoader(int id, Bundle args) {
107                 return new RootsLoader(context, roots, state);
108             }
109 
110             @Override
111             public void onLoadFinished(
112                     Loader<Collection<RootInfo>> loader, Collection<RootInfo> result) {
113                 if (!isAdded()) return;
114 
115                 final Intent includeApps = getArguments().getParcelable(EXTRA_INCLUDE_APPS);
116 
117                 mAdapter = new RootsAdapter(context, result, includeApps);
118                 mList.setAdapter(mAdapter);
119 
120                 onCurrentRootChanged();
121             }
122 
123             @Override
124             public void onLoaderReset(Loader<Collection<RootInfo>> loader) {
125                 mAdapter = null;
126                 mList.setAdapter(null);
127             }
128         };
129     }
130 
131     @Override
onResume()132     public void onResume() {
133         super.onResume();
134         onDisplayStateChanged();
135     }
136 
onDisplayStateChanged()137     public void onDisplayStateChanged() {
138         final Context context = getActivity();
139         final State state = ((BaseActivity) context).getDisplayState();
140 
141         if (state.action == State.ACTION_GET_CONTENT) {
142             mList.setOnItemLongClickListener(mItemLongClickListener);
143         } else {
144             mList.setOnItemLongClickListener(null);
145             mList.setLongClickable(false);
146         }
147 
148         getLoaderManager().restartLoader(2, null, mCallbacks);
149     }
150 
onCurrentRootChanged()151     public void onCurrentRootChanged() {
152         if (mAdapter == null) return;
153 
154         final RootInfo root = ((BaseActivity) getActivity()).getCurrentRoot();
155         for (int i = 0; i < mAdapter.getCount(); i++) {
156             final Object item = mAdapter.getItem(i);
157             if (item instanceof RootItem) {
158                 final RootInfo testRoot = ((RootItem) item).root;
159                 if (Objects.equals(testRoot, root)) {
160                     mList.setItemChecked(i, true);
161                     return;
162                 }
163             }
164         }
165     }
166 
showAppDetails(ResolveInfo ri)167     private void showAppDetails(ResolveInfo ri) {
168         final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
169         intent.setData(Uri.fromParts("package", ri.activityInfo.packageName, null));
170         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
171         startActivity(intent);
172     }
173 
174     private OnItemClickListener mItemListener = new OnItemClickListener() {
175         @Override
176         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
177             Item item = mAdapter.getItem(position);
178             if (item instanceof RootItem) {
179                 BaseActivity activity = BaseActivity.get(RootsFragment.this);
180                 activity.onRootPicked(((RootItem) item).root);
181             } else if (item instanceof AppItem) {
182                 DocumentsActivity activity = DocumentsActivity.get(RootsFragment.this);
183                 activity.onAppPicked(((AppItem) item).info);
184             } else {
185                 throw new IllegalStateException("Unknown root: " + item);
186             }
187         }
188     };
189 
190     private OnItemLongClickListener mItemLongClickListener = new OnItemLongClickListener() {
191         @Override
192         public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
193             final Item item = mAdapter.getItem(position);
194             if (item instanceof AppItem) {
195                 showAppDetails(((AppItem) item).info);
196                 return true;
197             } else {
198                 return false;
199             }
200         }
201     };
202 
203     private static abstract class Item {
204         private final int mLayoutId;
205 
Item(int layoutId)206         public Item(int layoutId) {
207             mLayoutId = layoutId;
208         }
209 
getView(View convertView, ViewGroup parent)210         public View getView(View convertView, ViewGroup parent) {
211             if (convertView == null) {
212                 convertView = LayoutInflater.from(parent.getContext())
213                         .inflate(mLayoutId, parent, false);
214             }
215             bindView(convertView);
216             return convertView;
217         }
218 
bindView(View convertView)219         public abstract void bindView(View convertView);
220     }
221 
222     private static class RootItem extends Item {
223         public final RootInfo root;
224 
RootItem(RootInfo root)225         public RootItem(RootInfo root) {
226             super(R.layout.item_root);
227             this.root = root;
228         }
229 
230         @Override
bindView(View convertView)231         public void bindView(View convertView) {
232             final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
233             final TextView title = (TextView) convertView.findViewById(android.R.id.title);
234             final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
235 
236             final Context context = convertView.getContext();
237             icon.setImageDrawable(root.loadDrawerIcon(context));
238             title.setText(root.title);
239 
240             // Show available space if no summary
241             String summaryText = root.summary;
242             if (TextUtils.isEmpty(summaryText) && root.availableBytes >= 0) {
243                 summaryText = context.getString(R.string.root_available_bytes,
244                         Formatter.formatFileSize(context, root.availableBytes));
245             }
246 
247             summary.setText(summaryText);
248             summary.setVisibility(TextUtils.isEmpty(summaryText) ? View.GONE : View.VISIBLE);
249         }
250     }
251 
252     private static class SpacerItem extends Item {
SpacerItem()253         public SpacerItem() {
254             super(R.layout.item_root_spacer);
255         }
256 
257         @Override
bindView(View convertView)258         public void bindView(View convertView) {
259             // Nothing to bind
260         }
261     }
262 
263     private static class AppItem extends Item {
264         public final ResolveInfo info;
265 
AppItem(ResolveInfo info)266         public AppItem(ResolveInfo info) {
267             super(R.layout.item_root);
268             this.info = info;
269         }
270 
271         @Override
bindView(View convertView)272         public void bindView(View convertView) {
273             final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
274             final TextView title = (TextView) convertView.findViewById(android.R.id.title);
275             final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
276 
277             final PackageManager pm = convertView.getContext().getPackageManager();
278             icon.setImageDrawable(info.loadIcon(pm));
279             title.setText(info.loadLabel(pm));
280 
281             // TODO: match existing summary behavior from disambig dialog
282             summary.setVisibility(View.GONE);
283         }
284     }
285 
286     private static class RootsAdapter extends ArrayAdapter<Item> {
RootsAdapter(Context context, Collection<RootInfo> roots, Intent includeApps)287         public RootsAdapter(Context context, Collection<RootInfo> roots, Intent includeApps) {
288             super(context, 0);
289 
290             RootItem recents = null;
291             RootItem images = null;
292             RootItem videos = null;
293             RootItem audio = null;
294             RootItem downloads = null;
295 
296             final List<RootInfo> clouds = Lists.newArrayList();
297             final List<RootInfo> locals = Lists.newArrayList();
298 
299             for (RootInfo root : roots) {
300                 if (root.isRecents()) {
301                     recents = new RootItem(root);
302                 } else if (root.isExternalStorage()) {
303                     locals.add(root);
304                 } else if (root.isDownloads()) {
305                     downloads = new RootItem(root);
306                 } else if (root.isImages()) {
307                     images = new RootItem(root);
308                 } else if (root.isVideos()) {
309                     videos = new RootItem(root);
310                 } else if (root.isAudio()) {
311                     audio = new RootItem(root);
312                 } else {
313                     clouds.add(root);
314                 }
315             }
316 
317             final RootComparator comp = new RootComparator();
318             Collections.sort(clouds, comp);
319             Collections.sort(locals, comp);
320 
321             if (recents != null) add(recents);
322 
323             for (RootInfo cloud : clouds) {
324                 add(new RootItem(cloud));
325             }
326 
327             if (images != null) add(images);
328             if (videos != null) add(videos);
329             if (audio != null) add(audio);
330             if (downloads != null) add(downloads);
331 
332             for (RootInfo local : locals) {
333                 add(new RootItem(local));
334             }
335 
336             if (includeApps != null) {
337                 final PackageManager pm = context.getPackageManager();
338                 final List<ResolveInfo> infos = pm.queryIntentActivities(
339                         includeApps, PackageManager.MATCH_DEFAULT_ONLY);
340 
341                 final List<AppItem> apps = Lists.newArrayList();
342 
343                 // Omit ourselves from the list
344                 for (ResolveInfo info : infos) {
345                     if (!context.getPackageName().equals(info.activityInfo.packageName)) {
346                         apps.add(new AppItem(info));
347                     }
348                 }
349 
350                 if (apps.size() > 0) {
351                     add(new SpacerItem());
352                     for (Item item : apps) {
353                         add(item);
354                     }
355                 }
356             }
357         }
358 
359         @Override
getView(int position, View convertView, ViewGroup parent)360         public View getView(int position, View convertView, ViewGroup parent) {
361             final Item item = getItem(position);
362             return item.getView(convertView, parent);
363         }
364 
365         @Override
areAllItemsEnabled()366         public boolean areAllItemsEnabled() {
367             return false;
368         }
369 
370         @Override
isEnabled(int position)371         public boolean isEnabled(int position) {
372             return getItemViewType(position) != 1;
373         }
374 
375         @Override
getItemViewType(int position)376         public int getItemViewType(int position) {
377             final Item item = getItem(position);
378             if (item instanceof RootItem || item instanceof AppItem) {
379                 return 0;
380             } else {
381                 return 1;
382             }
383         }
384 
385         @Override
getViewTypeCount()386         public int getViewTypeCount() {
387             return 2;
388         }
389     }
390 
391     public static class RootComparator implements Comparator<RootInfo> {
392         @Override
compare(RootInfo lhs, RootInfo rhs)393         public int compare(RootInfo lhs, RootInfo rhs) {
394             final int score = DocumentInfo.compareToIgnoreCaseNullable(lhs.title, rhs.title);
395             if (score != 0) {
396                 return score;
397             } else {
398                 return DocumentInfo.compareToIgnoreCaseNullable(lhs.summary, rhs.summary);
399             }
400         }
401     }
402 }
403