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