DirectoryFragment.java revision ef7184a1aa0be5d496a5cb495a0f9e11f342af44
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.Loader; 25import android.net.Uri; 26import android.os.Bundle; 27import android.provider.DocumentsContract; 28import android.text.format.DateUtils; 29import android.util.SparseBooleanArray; 30import android.view.ActionMode; 31import android.view.LayoutInflater; 32import android.view.Menu; 33import android.view.MenuInflater; 34import android.view.MenuItem; 35import android.view.View; 36import android.view.ViewGroup; 37import android.widget.AbsListView; 38import android.widget.AbsListView.MultiChoiceModeListener; 39import android.widget.AdapterView; 40import android.widget.AdapterView.OnItemClickListener; 41import android.widget.BaseAdapter; 42import android.widget.GridView; 43import android.widget.ImageView; 44import android.widget.ListView; 45import android.widget.TextView; 46 47import com.android.documentsui.DocumentsActivity.DisplayState; 48import com.android.documentsui.model.Document; 49import com.android.internal.util.Predicate; 50import com.google.android.collect.Lists; 51 52import java.util.ArrayList; 53import java.util.Comparator; 54import java.util.List; 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 public static final int TYPE_NORMAL = 1; 69 public static final int TYPE_SEARCH = 2; 70 public static final int TYPE_RECENT_OPEN = 3; 71 public static final int TYPE_RECENT_CREATE = 4; 72 73 private int mType = TYPE_NORMAL; 74 75 private DocumentsAdapter mAdapter; 76 private LoaderCallbacks<List<Document>> mCallbacks; 77 78 private static final String EXTRA_URI = "uri"; 79 80 private static final int LOADER_DOCUMENTS = 2; 81 82 public static void show(FragmentManager fm, Uri uri) { 83 final Bundle args = new Bundle(); 84 args.putParcelable(EXTRA_URI, uri); 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 ft.commitAllowingStateLoss(); 92 } 93 94 public static DirectoryFragment get(FragmentManager fm) { 95 // TODO: deal with multiple directories shown at once 96 return (DirectoryFragment) fm.findFragmentById(R.id.directory); 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(); 121 updateMode(); 122 123 final Uri uri = getArguments().getParcelable(EXTRA_URI); 124 125 if (uri.getQueryParameter(DocumentsContract.PARAM_QUERY) != null) { 126 mType = TYPE_SEARCH; 127 } else if (RecentsProvider.buildRecentOpen().equals(uri)) { 128 mType = TYPE_RECENT_OPEN; 129 } else if (RecentsProvider.buildRecentCreate().equals(uri)) { 130 mType = TYPE_RECENT_CREATE; 131 } else { 132 mType = TYPE_NORMAL; 133 } 134 135 mCallbacks = new LoaderCallbacks<List<Document>>() { 136 @Override 137 public Loader<List<Document>> onCreateLoader(int id, Bundle args) { 138 final DisplayState state = getDisplayState(DirectoryFragment.this); 139 140 final Uri contentsUri; 141 if (mType == TYPE_NORMAL) { 142 contentsUri = DocumentsContract.buildContentsUri(uri); 143 } else { 144 contentsUri = uri; 145 } 146 147 final Predicate<Document> filter = new MimePredicate(state.acceptMimes); 148 149 final Comparator<Document> sortOrder; 150 if (state.sortOrder == DisplayState.SORT_ORDER_DATE || mType == TYPE_RECENT_OPEN 151 || mType == TYPE_RECENT_CREATE) { 152 sortOrder = new Document.DateComparator(); 153 } else if (state.sortOrder == DisplayState.SORT_ORDER_NAME) { 154 sortOrder = new Document.NameComparator(); 155 } else { 156 throw new IllegalArgumentException("Unknown sort order " + state.sortOrder); 157 } 158 159 return new DirectoryLoader(context, contentsUri, mType, filter, sortOrder); 160 } 161 162 @Override 163 public void onLoadFinished(Loader<List<Document>> loader, List<Document> data) { 164 mAdapter.swapDocuments(data); 165 } 166 167 @Override 168 public void onLoaderReset(Loader<List<Document>> loader) { 169 mAdapter.swapDocuments(null); 170 } 171 }; 172 173 return view; 174 } 175 176 @Override 177 public void onStart() { 178 super.onStart(); 179 getLoaderManager().restartLoader(LOADER_DOCUMENTS, getArguments(), mCallbacks); 180 } 181 182 @Override 183 public void onStop() { 184 super.onStop(); 185 getLoaderManager().destroyLoader(LOADER_DOCUMENTS); 186 } 187 188 @Override 189 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 190 super.onCreateOptionsMenu(menu, inflater); 191 inflater.inflate(R.menu.directory, menu); 192 } 193 194 @Override 195 public void onPrepareOptionsMenu(Menu menu) { 196 super.onPrepareOptionsMenu(menu); 197 final DisplayState state = getDisplayState(this); 198 menu.findItem(R.id.menu_grid).setVisible(state.mode != DisplayState.MODE_GRID); 199 menu.findItem(R.id.menu_list).setVisible(state.mode != DisplayState.MODE_LIST); 200 } 201 202 @Override 203 public boolean onOptionsItemSelected(MenuItem item) { 204 final DisplayState state = getDisplayState(this); 205 final int id = item.getItemId(); 206 if (id == R.id.menu_grid) { 207 state.mode = DisplayState.MODE_GRID; 208 updateMode(); 209 getFragmentManager().invalidateOptionsMenu(); 210 return true; 211 } else if (id == R.id.menu_list) { 212 state.mode = DisplayState.MODE_LIST; 213 updateMode(); 214 getFragmentManager().invalidateOptionsMenu(); 215 return true; 216 } else { 217 return super.onOptionsItemSelected(item); 218 } 219 } 220 221 private void updateMode() { 222 final DisplayState state = getDisplayState(this); 223 224 mListView.setVisibility(state.mode == DisplayState.MODE_LIST ? View.VISIBLE : View.GONE); 225 mGridView.setVisibility(state.mode == DisplayState.MODE_GRID ? View.VISIBLE : View.GONE); 226 227 final int choiceMode; 228 if (state.allowMultiple) { 229 choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL; 230 } else { 231 choiceMode = ListView.CHOICE_MODE_NONE; 232 } 233 234 if (state.mode == DisplayState.MODE_GRID) { 235 mListView.setAdapter(null); 236 mListView.setChoiceMode(ListView.CHOICE_MODE_NONE); 237 mGridView.setAdapter(mAdapter); 238 mGridView.setColumnWidth(getResources().getDimensionPixelSize(R.dimen.grid_width)); 239 mGridView.setNumColumns(GridView.AUTO_FIT); 240 mGridView.setChoiceMode(choiceMode); 241 mCurrentView = mGridView; 242 } else if (state.mode == DisplayState.MODE_LIST) { 243 mGridView.setAdapter(null); 244 mGridView.setChoiceMode(ListView.CHOICE_MODE_NONE); 245 mListView.setAdapter(mAdapter); 246 mListView.setChoiceMode(choiceMode); 247 mCurrentView = mListView; 248 } else { 249 throw new IllegalStateException(); 250 } 251 } 252 253 public void updateSortOrder() { 254 getLoaderManager().restartLoader(LOADER_DOCUMENTS, getArguments(), mCallbacks); 255 mListView.smoothScrollToPosition(0); 256 mGridView.smoothScrollToPosition(0); 257 } 258 259 private OnItemClickListener mItemListener = new OnItemClickListener() { 260 @Override 261 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 262 final Document doc = mAdapter.getItem(position); 263 ((DocumentsActivity) getActivity()).onDocumentPicked(doc); 264 } 265 }; 266 267 private MultiChoiceModeListener mMultiListener = new MultiChoiceModeListener() { 268 @Override 269 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 270 mode.getMenuInflater().inflate(R.menu.mode_directory, menu); 271 return true; 272 } 273 274 @Override 275 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 276 return true; 277 } 278 279 @Override 280 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 281 if (item.getItemId() == R.id.menu_open) { 282 final Uri uri = getArguments().getParcelable(EXTRA_URI); 283 final SparseBooleanArray checked = mCurrentView.getCheckedItemPositions(); 284 final ArrayList<Document> docs = Lists.newArrayList(); 285 286 final int size = checked.size(); 287 for (int i = 0; i < size; i++) { 288 if (checked.valueAt(i)) { 289 final Document doc = mAdapter.getItem(checked.keyAt(i)); 290 docs.add(doc); 291 } 292 } 293 294 ((DocumentsActivity) getActivity()).onDocumentsPicked(docs); 295 return true; 296 } else { 297 return false; 298 } 299 } 300 301 @Override 302 public void onDestroyActionMode(ActionMode mode) { 303 // ignored 304 } 305 306 @Override 307 public void onItemCheckedStateChanged( 308 ActionMode mode, int position, long id, boolean checked) { 309 if (checked) { 310 // Directories cannot be checked 311 final Document doc = mAdapter.getItem(position); 312 if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(doc.mimeType)) { 313 mCurrentView.setItemChecked(position, false); 314 } 315 } 316 317 mode.setTitle(getResources() 318 .getString(R.string.mode_selected_count, mCurrentView.getCheckedItemCount())); 319 } 320 }; 321 322 private static DisplayState getDisplayState(Fragment fragment) { 323 return ((DocumentsActivity) fragment.getActivity()).getDisplayState(); 324 } 325 326 private class DocumentsAdapter extends BaseAdapter { 327 private List<Document> mDocuments; 328 329 public DocumentsAdapter() { 330 } 331 332 public void swapDocuments(List<Document> documents) { 333 mDocuments = documents; 334 notifyDataSetChanged(); 335 } 336 337 @Override 338 public View getView(int position, View convertView, ViewGroup parent) { 339 final Context context = parent.getContext(); 340 341 if (convertView == null) { 342 final LayoutInflater inflater = LayoutInflater.from(context); 343 final DisplayState state = getDisplayState(DirectoryFragment.this); 344 if (state.mode == DisplayState.MODE_LIST) { 345 convertView = inflater.inflate(R.layout.item_doc_list, parent, false); 346 } else if (state.mode == DisplayState.MODE_GRID) { 347 convertView = inflater.inflate(R.layout.item_doc_grid, parent, false); 348 } else { 349 throw new IllegalStateException(); 350 } 351 } 352 353 final Document doc = getItem(position); 354 355 final TextView title = (TextView) convertView.findViewById(android.R.id.title); 356 final TextView summary = (TextView) convertView.findViewById(android.R.id.summary); 357 final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon); 358 359 if (doc.isThumbnailSupported()) { 360 // TODO: load thumbnails async 361 icon.setImageURI(doc.uri); 362 } else { 363 icon.setImageDrawable(DocumentsActivity.resolveDocumentIcon( 364 context, doc.uri.getAuthority(), doc.mimeType)); 365 } 366 367 title.setText(doc.displayName); 368 if (summary != null) { 369 summary.setText(DateUtils.getRelativeTimeSpanString(doc.lastModified)); 370 } 371 372 return convertView; 373 } 374 375 @Override 376 public int getCount() { 377 return mDocuments != null ? mDocuments.size() : 0; 378 } 379 380 @Override 381 public Document getItem(int position) { 382 return mDocuments.get(position); 383 } 384 385 @Override 386 public long getItemId(int position) { 387 return getItem(position).uri.hashCode(); 388 } 389 } 390} 391