RootsFragment.java revision f6db154975ef575479ba4ab59d80bcf592288252
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.content.Context;
23import android.content.Intent;
24import android.content.pm.PackageManager;
25import android.content.pm.ResolveInfo;
26import android.os.Bundle;
27import android.provider.DocumentsContract.Root;
28import android.text.TextUtils;
29import android.text.format.Formatter;
30import android.view.LayoutInflater;
31import android.view.View;
32import android.view.ViewGroup;
33import android.widget.AdapterView;
34import android.widget.AdapterView.OnItemClickListener;
35import android.widget.ArrayAdapter;
36import android.widget.ImageView;
37import android.widget.ListView;
38import android.widget.Space;
39import android.widget.TextView;
40
41import com.android.documentsui.DocumentsActivity.State;
42import com.android.documentsui.SectionedListAdapter.SectionAdapter;
43import com.android.documentsui.model.DocumentInfo;
44import com.android.documentsui.model.RootInfo;
45import com.android.internal.util.Objects;
46
47import java.util.Comparator;
48import java.util.List;
49
50/**
51 * Display list of known storage backend roots.
52 */
53public class RootsFragment extends Fragment {
54
55    private ListView mList;
56    private SectionedRootsAdapter mAdapter;
57
58    private static final String EXTRA_INCLUDE_APPS = "includeApps";
59
60    public static void show(FragmentManager fm, Intent includeApps) {
61        final Bundle args = new Bundle();
62        args.putParcelable(EXTRA_INCLUDE_APPS, includeApps);
63
64        final RootsFragment fragment = new RootsFragment();
65        fragment.setArguments(args);
66
67        final FragmentTransaction ft = fm.beginTransaction();
68        ft.replace(R.id.container_roots, fragment);
69        ft.commitAllowingStateLoss();
70    }
71
72    public static RootsFragment get(FragmentManager fm) {
73        return (RootsFragment) fm.findFragmentById(R.id.container_roots);
74    }
75
76    @Override
77    public View onCreateView(
78            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
79        final Context context = inflater.getContext();
80
81        final View view = inflater.inflate(R.layout.fragment_roots, container, false);
82        mList = (ListView) view.findViewById(android.R.id.list);
83        mList.setOnItemClickListener(mItemListener);
84        mList.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
85
86        return view;
87    }
88
89    @Override
90    public void onResume() {
91        super.onResume();
92        updateRootsAdapter();
93    }
94
95    private void updateRootsAdapter() {
96        final Context context = getActivity();
97
98        final State state = ((DocumentsActivity) context).getDisplayState();
99        state.showAdvanced = SettingsActivity.getDisplayAdvancedDevices(context);
100
101        final RootsCache roots = DocumentsApplication.getRootsCache(context);
102        final List<RootInfo> matchingRoots = roots.getMatchingRoots(state);
103        final Intent includeApps = getArguments().getParcelable(EXTRA_INCLUDE_APPS);
104
105        mAdapter = new SectionedRootsAdapter(context, matchingRoots, includeApps);
106        mList.setAdapter(mAdapter);
107
108        onCurrentRootChanged();
109    }
110
111    public void onCurrentRootChanged() {
112        if (mAdapter == null) return;
113
114        final RootInfo root = ((DocumentsActivity) getActivity()).getCurrentRoot();
115        for (int i = 0; i < mAdapter.getCount(); i++) {
116            final Object item = mAdapter.getItem(i);
117            if (Objects.equal(item, root)) {
118                mList.setItemChecked(i, true);
119                return;
120            }
121        }
122    }
123
124    private OnItemClickListener mItemListener = new OnItemClickListener() {
125        @Override
126        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
127            final DocumentsActivity activity = DocumentsActivity.get(RootsFragment.this);
128            final Object item = mAdapter.getItem(position);
129            if (item instanceof RootInfo) {
130                activity.onRootPicked((RootInfo) item, true);
131            } else if (item instanceof ResolveInfo) {
132                activity.onAppPicked((ResolveInfo) item);
133            } else {
134                throw new IllegalStateException("Unknown root: " + item);
135            }
136        }
137    };
138
139    private static class RootsAdapter extends ArrayAdapter<RootInfo> implements SectionAdapter {
140        public RootsAdapter(Context context) {
141            super(context, 0);
142        }
143
144        @Override
145        public View getView(int position, View convertView, ViewGroup parent) {
146            final Context context = parent.getContext();
147            if (convertView == null) {
148                convertView = LayoutInflater.from(context)
149                        .inflate(R.layout.item_root, parent, false);
150            }
151
152            final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
153            final TextView title = (TextView) convertView.findViewById(android.R.id.title);
154            final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
155
156            final RootInfo root = getItem(position);
157            icon.setImageDrawable(root.loadIcon(context));
158            title.setText(root.title);
159
160            // Device summary is always available space
161            final String summaryText;
162            if (root.rootType == Root.ROOT_TYPE_DEVICE && root.availableBytes >= 0) {
163                summaryText = context.getString(R.string.root_available_bytes,
164                        Formatter.formatFileSize(context, root.availableBytes));
165            } else {
166                summaryText = root.summary;
167            }
168
169            summary.setText(summaryText);
170            summary.setVisibility(TextUtils.isEmpty(summaryText) ? View.GONE : View.VISIBLE);
171
172            return convertView;
173        }
174
175        @Override
176        public View getHeaderView(View convertView, ViewGroup parent) {
177            if (convertView == null) {
178                convertView = new Space(parent.getContext());
179            }
180            return convertView;
181        }
182    }
183
184    private static class AppsAdapter extends ArrayAdapter<ResolveInfo> implements SectionAdapter {
185        public AppsAdapter(Context context) {
186            super(context, 0);
187        }
188
189        @Override
190        public View getView(int position, View convertView, ViewGroup parent) {
191            final Context context = parent.getContext();
192            final PackageManager pm = context.getPackageManager();
193            if (convertView == null) {
194                convertView = LayoutInflater.from(context)
195                        .inflate(R.layout.item_root, parent, false);
196            }
197
198            final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
199            final TextView title = (TextView) convertView.findViewById(android.R.id.title);
200            final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
201
202            final ResolveInfo info = getItem(position);
203            icon.setImageDrawable(info.loadIcon(pm));
204            title.setText(info.loadLabel(pm));
205
206            // TODO: match existing summary behavior from disambig dialog
207            summary.setVisibility(View.GONE);
208
209            return convertView;
210        }
211
212        @Override
213        public View getHeaderView(View convertView, ViewGroup parent) {
214            if (convertView == null) {
215                convertView = LayoutInflater.from(parent.getContext())
216                        .inflate(R.layout.item_root_header, parent, false);
217            }
218
219            final TextView title = (TextView) convertView.findViewById(android.R.id.title);
220            title.setText(R.string.root_type_apps);
221
222            return convertView;
223        }
224    }
225
226    private static class SectionedRootsAdapter extends SectionedListAdapter {
227        private final RootsAdapter mServices;
228        private final RootsAdapter mShortcuts;
229        private final RootsAdapter mDevices;
230        private final AppsAdapter mApps;
231
232        public SectionedRootsAdapter(Context context, List<RootInfo> roots, Intent includeApps) {
233            mServices = new RootsAdapter(context);
234            mShortcuts = new RootsAdapter(context);
235            mDevices = new RootsAdapter(context);
236            mApps = new AppsAdapter(context);
237
238            for (RootInfo root : roots) {
239                switch (root.rootType) {
240                    case Root.ROOT_TYPE_SERVICE:
241                        mServices.add(root);
242                        break;
243                    case Root.ROOT_TYPE_SHORTCUT:
244                        mShortcuts.add(root);
245                        break;
246                    case Root.ROOT_TYPE_DEVICE:
247                        mDevices.add(root);
248                        break;
249                }
250            }
251
252            if (includeApps != null) {
253                final PackageManager pm = context.getPackageManager();
254                final List<ResolveInfo> infos = pm.queryIntentActivities(
255                        includeApps, PackageManager.MATCH_DEFAULT_ONLY);
256
257                // Omit ourselves from the list
258                for (ResolveInfo info : infos) {
259                    if (!context.getPackageName().equals(info.activityInfo.packageName)) {
260                        mApps.add(info);
261                    }
262                }
263            }
264
265            final RootComparator comp = new RootComparator();
266            mServices.sort(comp);
267            mShortcuts.sort(comp);
268            mDevices.sort(comp);
269
270            if (mShortcuts.getCount() > 0) {
271                addSection(mShortcuts);
272            }
273            if (mDevices.getCount() > 0) {
274                addSection(mDevices);
275            }
276            if (mServices.getCount() > 0) {
277                addSection(mServices);
278            }
279            if (mApps.getCount() > 0) {
280                addSection(mApps);
281            }
282        }
283    }
284
285    public static class RootComparator implements Comparator<RootInfo> {
286        @Override
287        public int compare(RootInfo lhs, RootInfo rhs) {
288            if (lhs.authority == null) {
289                return -1;
290            } else if (rhs.authority == null) {
291                return 1;
292            }
293
294            final int score = DocumentInfo.compareToIgnoreCaseNullable(lhs.title, rhs.title);
295            if (score != 0) {
296                return score;
297            } else {
298                return DocumentInfo.compareToIgnoreCaseNullable(lhs.summary, rhs.summary);
299            }
300        }
301    }
302}
303