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