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