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