RootsFragment.java revision 96c620595bd0585f934b0971b4552c57845e9a78
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
17package com.android.documentsui;
18
19import static com.android.documentsui.DocumentsActivity.State.ACTION_GET_CONTENT;
20
21import android.app.Fragment;
22import android.app.FragmentManager;
23import android.app.FragmentTransaction;
24import android.app.LoaderManager.LoaderCallbacks;
25import android.content.Context;
26import android.content.Intent;
27import android.content.Loader;
28import android.content.pm.PackageManager;
29import android.content.pm.ResolveInfo;
30import android.net.Uri;
31import android.os.Bundle;
32import android.provider.Settings;
33import android.text.TextUtils;
34import android.text.format.Formatter;
35import android.view.LayoutInflater;
36import android.view.View;
37import android.view.ViewGroup;
38import android.widget.AdapterView;
39import android.widget.AdapterView.OnItemClickListener;
40import android.widget.AdapterView.OnItemLongClickListener;
41import android.widget.ArrayAdapter;
42import android.widget.ImageView;
43import android.widget.ListView;
44import android.widget.TextView;
45
46import com.android.documentsui.DocumentsActivity.State;
47import com.android.documentsui.model.DocumentInfo;
48import com.android.documentsui.model.RootInfo;
49import com.android.internal.util.Objects;
50import com.google.common.collect.Lists;
51
52import java.util.Collection;
53import java.util.Collections;
54import java.util.Comparator;
55import java.util.List;
56
57/**
58 * Display list of known storage backend roots.
59 */
60public class RootsFragment extends Fragment {
61
62    private ListView mList;
63    private RootsAdapter mAdapter;
64
65    private LoaderCallbacks<Collection<RootInfo>> mCallbacks;
66
67    private static final String EXTRA_INCLUDE_APPS = "includeApps";
68
69    public static void show(FragmentManager fm, Intent includeApps) {
70        final Bundle args = new Bundle();
71        args.putParcelable(EXTRA_INCLUDE_APPS, includeApps);
72
73        final RootsFragment fragment = new RootsFragment();
74        fragment.setArguments(args);
75
76        final FragmentTransaction ft = fm.beginTransaction();
77        ft.replace(R.id.container_roots, fragment);
78        ft.commitAllowingStateLoss();
79    }
80
81    public static RootsFragment get(FragmentManager fm) {
82        return (RootsFragment) fm.findFragmentById(R.id.container_roots);
83    }
84
85    @Override
86    public View onCreateView(
87            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
88        final Context context = inflater.getContext();
89
90        final View view = inflater.inflate(R.layout.fragment_roots, container, false);
91        mList = (ListView) view.findViewById(android.R.id.list);
92        mList.setOnItemClickListener(mItemListener);
93        mList.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
94
95        return view;
96    }
97
98    @Override
99    public void onActivityCreated(Bundle savedInstanceState) {
100        super.onActivityCreated(savedInstanceState);
101
102        final Context context = getActivity();
103        final RootsCache roots = DocumentsApplication.getRootsCache(context);
104        final State state = ((DocumentsActivity) context).getDisplayState();
105
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) {
115                if (!isAdded()) return;
116
117                final Intent includeApps = getArguments().getParcelable(EXTRA_INCLUDE_APPS);
118
119                mAdapter = new RootsAdapter(context, result, includeApps);
120                mList.setAdapter(mAdapter);
121
122                onCurrentRootChanged();
123            }
124
125            @Override
126            public void onLoaderReset(Loader<Collection<RootInfo>> loader) {
127                mAdapter = null;
128                mList.setAdapter(null);
129            }
130        };
131    }
132
133    @Override
134    public void onResume() {
135        super.onResume();
136
137        final Context context = getActivity();
138        final State state = ((DocumentsActivity) context).getDisplayState();
139        state.showAdvanced = state.forceAdvanced
140                | SettingsActivity.getDisplayAdvancedDevices(context);
141
142        if (state.action == ACTION_GET_CONTENT) {
143            mList.setOnItemLongClickListener(mItemLongClickListener);
144        } else {
145            mList.setOnItemLongClickListener(null);
146            mList.setLongClickable(false);
147        }
148
149        getLoaderManager().restartLoader(2, null, mCallbacks);
150    }
151
152    public void onCurrentRootChanged() {
153        if (mAdapter == null) return;
154
155        final RootInfo root = ((DocumentsActivity) getActivity()).getCurrentRoot();
156        for (int i = 0; i < mAdapter.getCount(); i++) {
157            final Object item = mAdapter.getItem(i);
158            if (item instanceof RootItem) {
159                final RootInfo testRoot = ((RootItem) item).root;
160                if (Objects.equal(testRoot, root)) {
161                    mList.setItemChecked(i, true);
162                    return;
163                }
164            }
165        }
166    }
167
168    private void showAppDetails(ResolveInfo ri) {
169        final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
170        intent.setData(Uri.fromParts("package", ri.activityInfo.packageName, null));
171        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
172        startActivity(intent);
173    }
174
175    private OnItemClickListener mItemListener = new OnItemClickListener() {
176        @Override
177        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
178            final DocumentsActivity activity = DocumentsActivity.get(RootsFragment.this);
179            final Item item = mAdapter.getItem(position);
180            if (item instanceof RootItem) {
181                activity.onRootPicked(((RootItem) item).root, true);
182            } else if (item instanceof AppItem) {
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
206        public Item(int layoutId) {
207            mLayoutId = layoutId;
208        }
209
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
219        public abstract void bindView(View convertView);
220    }
221
222    private static class RootItem extends Item {
223        public final RootInfo root;
224
225        public RootItem(RootInfo root) {
226            super(R.layout.item_root);
227            this.root = root;
228        }
229
230        @Override
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.loadIcon(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 {
253        public SpacerItem() {
254            super(R.layout.item_root_spacer);
255        }
256
257        @Override
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
266        public AppItem(ResolveInfo info) {
267            super(R.layout.item_root);
268            this.info = info;
269        }
270
271        @Override
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> {
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
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
366        public boolean areAllItemsEnabled() {
367            return false;
368        }
369
370        @Override
371        public boolean isEnabled(int position) {
372            return getItemViewType(position) != 1;
373        }
374
375        @Override
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
386        public int getViewTypeCount() {
387            return 2;
388        }
389    }
390
391    public static class RootComparator implements Comparator<RootInfo> {
392        @Override
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