DirectoryFragment.java revision be8b12e687bd10a526b1f54c2d8a52abdad15d85
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.CursorLoader;
25import android.content.Loader;
26import android.database.Cursor;
27import android.net.Uri;
28import android.os.Bundle;
29import android.provider.DocumentsContract;
30import android.provider.DocumentsContract.DocumentColumns;
31import android.text.format.DateUtils;
32import android.util.SparseBooleanArray;
33import android.view.ActionMode;
34import android.view.LayoutInflater;
35import android.view.Menu;
36import android.view.MenuInflater;
37import android.view.MenuItem;
38import android.view.View;
39import android.view.ViewGroup;
40import android.widget.AbsListView;
41import android.widget.AbsListView.MultiChoiceModeListener;
42import android.widget.AdapterView;
43import android.widget.AdapterView.OnItemClickListener;
44import android.widget.CursorAdapter;
45import android.widget.GridView;
46import android.widget.ImageView;
47import android.widget.ListView;
48import android.widget.TextView;
49
50import com.android.documentsui.DocumentsActivity.Document;
51import com.google.android.collect.Lists;
52
53import java.util.ArrayList;
54
55/**
56 * Display the documents inside a single directory.
57 */
58public class DirectoryFragment extends Fragment {
59
60    // TODO: show storage backend in item views when requested
61    // TODO: implement sorting dialog
62
63    private ListView mListView;
64    private GridView mGridView;
65
66    private AbsListView mCurrentView;
67
68    private DocumentsAdapter mAdapter;
69    private LoaderCallbacks<Cursor> mCallbacks;
70
71    private int mFlags;
72
73    private static final String EXTRA_URI = "uri";
74    private static final String EXTRA_MODE = "display_mode";
75    private static final String EXTRA_ALLOW_MULTIPLE = "allow_multiple";
76
77    private static final int MODE_LIST = 1;
78    private static final int MODE_GRID = 2;
79
80    private static final int LOADER_DOCUMENTS = 2;
81
82    public static void show(
83            FragmentManager fm, Uri uri, String displayName, boolean allowMultiple) {
84        final Bundle args = new Bundle();
85        args.putParcelable(EXTRA_URI, uri);
86        args.putInt(EXTRA_MODE, MODE_LIST);
87        args.putBoolean(EXTRA_ALLOW_MULTIPLE, allowMultiple);
88
89        final DirectoryFragment fragment = new DirectoryFragment();
90        fragment.setArguments(args);
91
92        final FragmentTransaction ft = fm.beginTransaction();
93        ft.replace(R.id.directory, fragment);
94        ft.addToBackStack(displayName);
95        ft.setBreadCrumbTitle(displayName);
96        ft.commitAllowingStateLoss();
97    }
98
99    @Override
100    public void onCreate(Bundle savedInstanceState) {
101        super.onCreate(savedInstanceState);
102        setHasOptionsMenu(true);
103    }
104
105    @Override
106    public View onCreateView(
107            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
108        final Context context = inflater.getContext();
109
110        final View view = inflater.inflate(R.layout.fragment_directory, container, false);
111
112        mListView = (ListView) view.findViewById(R.id.list);
113        mListView.setOnItemClickListener(mItemListener);
114        mListView.setMultiChoiceModeListener(mMultiListener);
115
116        mGridView = (GridView) view.findViewById(R.id.grid);
117        mGridView.setOnItemClickListener(mItemListener);
118        mGridView.setMultiChoiceModeListener(mMultiListener);
119
120        mAdapter = new DocumentsAdapter(context);
121        updateMode();
122
123        // TODO: migrate flags query to loader
124        final Uri uri = getArguments().getParcelable(EXTRA_URI);
125        mFlags = getDocumentFlags(context, uri);
126
127        mCallbacks = new LoaderCallbacks<Cursor>() {
128            @Override
129            public Loader<Cursor> onCreateLoader(int id, Bundle args) {
130                final Uri contentsUri = DocumentsContract.buildContentsUri(uri);
131                return new CursorLoader(context, contentsUri, null, null, null, null);
132            }
133
134            @Override
135            public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
136                mAdapter.swapCursor(data);
137            }
138
139            @Override
140            public void onLoaderReset(Loader<Cursor> loader) {
141                mAdapter.swapCursor(null);
142            }
143        };
144
145        return view;
146    }
147
148    @Override
149    public void onStart() {
150        super.onStart();
151        getLoaderManager().restartLoader(LOADER_DOCUMENTS, getArguments(), mCallbacks);
152
153        // TODO: clean up tracking of current directory
154        final Uri uri = getArguments().getParcelable(EXTRA_URI);
155        ((DocumentsActivity) getActivity()).onDirectoryChanged(uri, mFlags);
156    }
157
158    @Override
159    public void onStop() {
160        super.onStop();
161        getLoaderManager().destroyLoader(LOADER_DOCUMENTS);
162    }
163
164    @Override
165    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
166        super.onCreateOptionsMenu(menu, inflater);
167        inflater.inflate(R.menu.directory, menu);
168    }
169
170    @Override
171    public void onPrepareOptionsMenu(Menu menu) {
172        super.onPrepareOptionsMenu(menu);
173        final int mode = getMode();
174        menu.findItem(R.id.menu_grid).setVisible(mode != MODE_GRID);
175        menu.findItem(R.id.menu_list).setVisible(mode != MODE_LIST);
176    }
177
178    @Override
179    public boolean onOptionsItemSelected(MenuItem item) {
180        final int id = item.getItemId();
181        if (id == R.id.menu_grid) {
182            getArguments().putInt(EXTRA_MODE, MODE_GRID);
183            updateMode();
184            return true;
185        } else if (id == R.id.menu_list) {
186            getArguments().putInt(EXTRA_MODE, MODE_LIST);
187            updateMode();
188            return true;
189        } else {
190            return super.onOptionsItemSelected(item);
191        }
192    }
193
194    private void updateMode() {
195        final int mode = getMode();
196
197        mListView.setVisibility(mode == MODE_LIST ? View.VISIBLE : View.GONE);
198        mGridView.setVisibility(mode == MODE_GRID ? View.VISIBLE : View.GONE);
199
200        final int choiceMode;
201        if (getArguments().getBoolean(EXTRA_ALLOW_MULTIPLE)) {
202            choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL;
203        } else {
204            choiceMode = ListView.CHOICE_MODE_NONE;
205        }
206
207        if (mode == MODE_GRID) {
208            mListView.setAdapter(null);
209            mListView.setChoiceMode(ListView.CHOICE_MODE_NONE);
210            mGridView.setAdapter(mAdapter);
211            mGridView.setColumnWidth(getResources().getDimensionPixelSize(R.dimen.grid_width));
212            mGridView.setNumColumns(GridView.AUTO_FIT);
213            mGridView.setChoiceMode(choiceMode);
214            mCurrentView = mGridView;
215        } else {
216            mGridView.setAdapter(null);
217            mGridView.setChoiceMode(ListView.CHOICE_MODE_NONE);
218            mListView.setAdapter(mAdapter);
219            mListView.setChoiceMode(choiceMode);
220            mCurrentView = mListView;
221        }
222    }
223
224    private OnItemClickListener mItemListener = new OnItemClickListener() {
225        @Override
226        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
227            final Cursor cursor = (Cursor) mAdapter.getItem(position);
228            final Uri uri = getArguments().getParcelable(EXTRA_URI);
229            final Document doc = Document.fromCursor(uri.getAuthority(), cursor);
230            ((DocumentsActivity) getActivity()).onDocumentPicked(doc);
231        }
232    };
233
234    private MultiChoiceModeListener mMultiListener = new MultiChoiceModeListener() {
235        @Override
236        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
237            mode.getMenuInflater().inflate(R.menu.mode_directory, menu);
238            return true;
239        }
240
241        @Override
242        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
243            return true;
244        }
245
246        @Override
247        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
248            if (item.getItemId() == R.id.menu_open) {
249                final Uri uri = getArguments().getParcelable(EXTRA_URI);
250                final SparseBooleanArray checked = mCurrentView.getCheckedItemPositions();
251                final ArrayList<Document> docs = Lists.newArrayList();
252
253                final int size = checked.size();
254                for (int i = 0; i < size; i++) {
255                    if (checked.valueAt(i)) {
256                        final Cursor cursor = (Cursor) mAdapter.getItem(checked.keyAt(i));
257                        docs.add(Document.fromCursor(uri.getAuthority(), cursor));
258                    }
259                }
260
261                ((DocumentsActivity) getActivity()).onDocumentsPicked(docs);
262                return true;
263            } else {
264                return false;
265            }
266        }
267
268        @Override
269        public void onDestroyActionMode(ActionMode mode) {
270            // ignored
271        }
272
273        @Override
274        public void onItemCheckedStateChanged(
275                ActionMode mode, int position, long id, boolean checked) {
276            if (checked) {
277                final Cursor cursor = (Cursor) mAdapter.getItem(position);
278                final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
279
280                // Directories cannot be checked
281                if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(mimeType)) {
282                    mCurrentView.setItemChecked(position, false);
283                }
284            }
285
286            mode.setTitle(getResources()
287                    .getString(R.string.mode_selected_count, mCurrentView.getCheckedItemCount()));
288        }
289    };
290
291    private boolean getSupportsCreate() {
292        return (mFlags & DocumentsContract.FLAG_SUPPORTS_CREATE) != 0;
293    }
294
295    private int getMode() {
296        return getArguments().getInt(EXTRA_MODE, MODE_LIST);
297    }
298
299    private class DocumentsAdapter extends CursorAdapter {
300        public DocumentsAdapter(Context context) {
301            super(context, null, false);
302        }
303
304        @Override
305        public View newView(Context context, Cursor cursor, ViewGroup parent) {
306            final LayoutInflater inflater = LayoutInflater.from(context);
307            final int mode = getMode();
308            if (mode == MODE_LIST) {
309                return inflater.inflate(R.layout.item_doc_list, parent, false);
310            } else if (mode == MODE_GRID) {
311                return inflater.inflate(R.layout.item_doc_grid, parent, false);
312            } else {
313                throw new IllegalStateException();
314            }
315        }
316
317        @Override
318        public void bindView(View view, Context context, Cursor cursor) {
319            final TextView title = (TextView) view.findViewById(android.R.id.title);
320            final TextView summary = (TextView) view.findViewById(android.R.id.summary);
321            final ImageView icon = (ImageView) view.findViewById(android.R.id.icon);
322
323            final String guid = getCursorString(cursor, DocumentColumns.GUID);
324            final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME);
325            final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
326            final long lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED);
327            final int flags = getCursorInt(cursor, DocumentColumns.FLAGS);
328
329            if ((flags & DocumentsContract.FLAG_SUPPORTS_THUMBNAIL) != 0) {
330                final Uri uri = getArguments().getParcelable(EXTRA_URI);
331                final Uri childUri = DocumentsContract.buildDocumentUri(uri.getAuthority(), guid);
332                icon.setImageURI(childUri);
333            } else {
334                icon.setImageDrawable(DocumentsActivity.resolveDocumentIcon(context, mimeType));
335            }
336
337            title.setText(displayName);
338            if (summary != null) {
339                summary.setText(DateUtils.getRelativeTimeSpanString(lastModified));
340            }
341        }
342    }
343
344    private static int getDocumentFlags(Context context, Uri uri) {
345        final Cursor cursor = context.getContentResolver().query(uri, new String[] {
346                DocumentColumns.FLAGS }, null, null, null);
347        try {
348            if (cursor.moveToFirst()) {
349                return getCursorInt(cursor, DocumentColumns.FLAGS);
350            } else {
351                return 0;
352            }
353        } finally {
354            cursor.close();
355        }
356    }
357
358    public static String getCursorString(Cursor cursor, String columnName) {
359        return cursor.getString(cursor.getColumnIndex(columnName));
360    }
361
362    public static long getCursorLong(Cursor cursor, String columnName) {
363        return cursor.getLong(cursor.getColumnIndex(columnName));
364    }
365
366    public static int getCursorInt(Cursor cursor, String columnName) {
367        return cursor.getInt(cursor.getColumnIndex(columnName));
368    }
369}
370