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.google.common.collect.Lists;
50
51import java.util.Collection;
52import java.util.Collections;
53import java.util.Comparator;
54import java.util.List;
55import java.util.Objects;
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        onDisplayStateChanged();
137    }
138
139    public void onDisplayStateChanged() {
140        final Context context = getActivity();
141        final State state = ((DocumentsActivity) context).getDisplayState();
142
143        if (state.action == ACTION_GET_CONTENT) {
144            mList.setOnItemLongClickListener(mItemLongClickListener);
145        } else {
146            mList.setOnItemLongClickListener(null);
147            mList.setLongClickable(false);
148        }
149
150        getLoaderManager().restartLoader(2, null, mCallbacks);
151    }
152
153    public void onCurrentRootChanged() {
154        if (mAdapter == null) return;
155
156        final RootInfo root = ((DocumentsActivity) getActivity()).getCurrentRoot();
157        for (int i = 0; i < mAdapter.getCount(); i++) {
158            final Object item = mAdapter.getItem(i);
159            if (item instanceof RootItem) {
160                final RootInfo testRoot = ((RootItem) item).root;
161                if (Objects.equals(testRoot, root)) {
162                    mList.setItemChecked(i, true);
163                    return;
164                }
165            }
166        }
167    }
168
169    private void showAppDetails(ResolveInfo ri) {
170        final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
171        intent.setData(Uri.fromParts("package", ri.activityInfo.packageName, null));
172        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
173        startActivity(intent);
174    }
175
176    private OnItemClickListener mItemListener = new OnItemClickListener() {
177        @Override
178        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
179            final DocumentsActivity activity = DocumentsActivity.get(RootsFragment.this);
180            final Item item = mAdapter.getItem(position);
181            if (item instanceof RootItem) {
182                activity.onRootPicked(((RootItem) item).root, true);
183            } else if (item instanceof AppItem) {
184                activity.onAppPicked(((AppItem) item).info);
185            } else {
186                throw new IllegalStateException("Unknown root: " + item);
187            }
188        }
189    };
190
191    private OnItemLongClickListener mItemLongClickListener = new OnItemLongClickListener() {
192        @Override
193        public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
194            final Item item = mAdapter.getItem(position);
195            if (item instanceof AppItem) {
196                showAppDetails(((AppItem) item).info);
197                return true;
198            } else {
199                return false;
200            }
201        }
202    };
203
204    private static abstract class Item {
205        private final int mLayoutId;
206
207        public Item(int layoutId) {
208            mLayoutId = layoutId;
209        }
210
211        public View getView(View convertView, ViewGroup parent) {
212            if (convertView == null) {
213                convertView = LayoutInflater.from(parent.getContext())
214                        .inflate(mLayoutId, parent, false);
215            }
216            bindView(convertView);
217            return convertView;
218        }
219
220        public abstract void bindView(View convertView);
221    }
222
223    private static class RootItem extends Item {
224        public final RootInfo root;
225
226        public RootItem(RootInfo root) {
227            super(R.layout.item_root);
228            this.root = root;
229        }
230
231        @Override
232        public void bindView(View convertView) {
233            final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
234            final TextView title = (TextView) convertView.findViewById(android.R.id.title);
235            final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
236
237            final Context context = convertView.getContext();
238            icon.setImageDrawable(root.loadDrawerIcon(context));
239            title.setText(root.title);
240
241            // Show available space if no summary
242            String summaryText = root.summary;
243            if (TextUtils.isEmpty(summaryText) && root.availableBytes >= 0) {
244                summaryText = context.getString(R.string.root_available_bytes,
245                        Formatter.formatFileSize(context, root.availableBytes));
246            }
247
248            summary.setText(summaryText);
249            summary.setVisibility(TextUtils.isEmpty(summaryText) ? View.GONE : View.VISIBLE);
250        }
251    }
252
253    private static class SpacerItem extends Item {
254        public SpacerItem() {
255            super(R.layout.item_root_spacer);
256        }
257
258        @Override
259        public void bindView(View convertView) {
260            // Nothing to bind
261        }
262    }
263
264    private static class AppItem extends Item {
265        public final ResolveInfo info;
266
267        public AppItem(ResolveInfo info) {
268            super(R.layout.item_root);
269            this.info = info;
270        }
271
272        @Override
273        public void bindView(View convertView) {
274            final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
275            final TextView title = (TextView) convertView.findViewById(android.R.id.title);
276            final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
277
278            final PackageManager pm = convertView.getContext().getPackageManager();
279            icon.setImageDrawable(info.loadIcon(pm));
280            title.setText(info.loadLabel(pm));
281
282            // TODO: match existing summary behavior from disambig dialog
283            summary.setVisibility(View.GONE);
284        }
285    }
286
287    private static class RootsAdapter extends ArrayAdapter<Item> {
288        public RootsAdapter(Context context, Collection<RootInfo> roots, Intent includeApps) {
289            super(context, 0);
290
291            RootItem recents = null;
292            RootItem images = null;
293            RootItem videos = null;
294            RootItem audio = null;
295            RootItem downloads = null;
296
297            final List<RootInfo> clouds = Lists.newArrayList();
298            final List<RootInfo> locals = Lists.newArrayList();
299
300            for (RootInfo root : roots) {
301                if (root.isRecents()) {
302                    recents = new RootItem(root);
303                } else if (root.isExternalStorage()) {
304                    locals.add(root);
305                } else if (root.isDownloads()) {
306                    downloads = new RootItem(root);
307                } else if (root.isImages()) {
308                    images = new RootItem(root);
309                } else if (root.isVideos()) {
310                    videos = new RootItem(root);
311                } else if (root.isAudio()) {
312                    audio = new RootItem(root);
313                } else {
314                    clouds.add(root);
315                }
316            }
317
318            final RootComparator comp = new RootComparator();
319            Collections.sort(clouds, comp);
320            Collections.sort(locals, comp);
321
322            if (recents != null) add(recents);
323
324            for (RootInfo cloud : clouds) {
325                add(new RootItem(cloud));
326            }
327
328            if (images != null) add(images);
329            if (videos != null) add(videos);
330            if (audio != null) add(audio);
331            if (downloads != null) add(downloads);
332
333            for (RootInfo local : locals) {
334                add(new RootItem(local));
335            }
336
337            if (includeApps != null) {
338                final PackageManager pm = context.getPackageManager();
339                final List<ResolveInfo> infos = pm.queryIntentActivities(
340                        includeApps, PackageManager.MATCH_DEFAULT_ONLY);
341
342                final List<AppItem> apps = Lists.newArrayList();
343
344                // Omit ourselves from the list
345                for (ResolveInfo info : infos) {
346                    if (!context.getPackageName().equals(info.activityInfo.packageName)) {
347                        apps.add(new AppItem(info));
348                    }
349                }
350
351                if (apps.size() > 0) {
352                    add(new SpacerItem());
353                    for (Item item : apps) {
354                        add(item);
355                    }
356                }
357            }
358        }
359
360        @Override
361        public View getView(int position, View convertView, ViewGroup parent) {
362            final Item item = getItem(position);
363            return item.getView(convertView, parent);
364        }
365
366        @Override
367        public boolean areAllItemsEnabled() {
368            return false;
369        }
370
371        @Override
372        public boolean isEnabled(int position) {
373            return getItemViewType(position) != 1;
374        }
375
376        @Override
377        public int getItemViewType(int position) {
378            final Item item = getItem(position);
379            if (item instanceof RootItem || item instanceof AppItem) {
380                return 0;
381            } else {
382                return 1;
383            }
384        }
385
386        @Override
387        public int getViewTypeCount() {
388            return 2;
389        }
390    }
391
392    public static class RootComparator implements Comparator<RootInfo> {
393        @Override
394        public int compare(RootInfo lhs, RootInfo rhs) {
395            final int score = DocumentInfo.compareToIgnoreCaseNullable(lhs.title, rhs.title);
396            if (score != 0) {
397                return score;
398            } else {
399                return DocumentInfo.compareToIgnoreCaseNullable(lhs.summary, rhs.summary);
400            }
401        }
402    }
403}
404