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