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